/* * 自然写智能点阵笔嵌入式固件软件 V1.0 * ble_send_task.c - BLE数据发送任务 * * 功能说明: * 1. 从坐标队列获取数据并打包为BLE通知帧 * 2. 7字节紧凑坐标编码格式 * 3. 发送速率控制(适配BLE连接间隔) * 4. 笔落下/抬起事件通知 * 5. 设备信息特征值更新(电量/固件版本) */ #include #include #include #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "event_groups.h" #include "ble_gatt_server.h" /* ========== BLE帧格式定义 ========== */ /* 帧头标识 */ #define BLE_FRAME_HEADER 0xAA55 /* 帧类型 */ #define FRAME_TYPE_COORDINATE 0x00 /* 坐标数据帧 */ #define FRAME_TYPE_PEN_DOWN 0x01 /* 笔落下事件 */ #define FRAME_TYPE_PEN_UP 0x02 /* 笔抬起事件 */ #define FRAME_TYPE_DEVICE_INFO 0x03 /* 设备信息帧 */ #define FRAME_TYPE_PAGE_CHANGE 0x04 /* 翻页事件 */ /* 最大BLE MTU通知载荷 */ #define BLE_MAX_NOTIFY_SIZE 20 /* 批量发送缓冲区大小(可打包多个坐标点) */ #define BATCH_BUFFER_SIZE 3 /* ========== 外部引用 ========== */ extern QueueHandle_t g_coordinate_queue; extern EventGroupHandle_t g_ble_event_group; extern SemaphoreHandle_t g_system_mutex; /* 坐标数据包结构 */ typedef struct { uint32_t raw_x; uint32_t raw_y; uint16_t pressure; uint32_t timestamp_ms; uint32_t page_id; uint8_t pen_state; } CoordinatePacket; /* ========== 静态变量 ========== */ /* 发送缓冲区 */ static uint8_t s_send_buffer[BLE_MAX_NOTIFY_SIZE]; /* BLE连接状态 */ static volatile bool s_ble_connected = false; /* 当前页面ID(检测翻页) */ static uint32_t s_current_page_id = 0; /* 发送统计 */ static uint32_t s_total_sent = 0; static uint32_t s_send_failures = 0; /* ========== CRC-16 CCITT计算 ========== */ /** * CRC-16 CCITT校验计算 * 用于BLE传输数据帧的完整性校验 * * @param data 数据缓冲区 * @param length 数据长度 * @return CRC-16校验值 */ static uint16_t crc16_ccitt(const uint8_t *data, uint16_t length) { uint16_t crc = 0xFFFF; uint16_t i; for (i = 0; i < length; i++) { crc ^= (uint16_t)data[i] << 8; uint8_t j; for (j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } } } return crc; } /* ========== 坐标编码 ========== */ /** * 将坐标数据编码为7字节紧凑格式 * * 编码格式: * 字节0-1: X坐标高16位(大端序) * 字节2-3: Y坐标高16位 * 字节4: X低4位(高半字节) | Y低4位(低半字节) * 字节5: 压力值高8位 * 字节6: 压力值低4位(高半字节) | 标志位(低半字节) * * @param packet 坐标数据包 * @param output 输出缓冲区(至少7字节) * @param flags 标志位(低2位:00=数据, 01=笔落下, 02=笔抬起) */ static void encode_coordinate(const CoordinatePacket *packet, uint8_t *output, uint8_t flags) { /* X坐标(20位精度) */ uint32_t x = packet->raw_x & 0xFFFFF; output[0] = (uint8_t)((x >> 12) & 0xFF); /* X高8位 */ output[1] = (uint8_t)((x >> 4) & 0xFF); /* X次高8位 */ /* Y坐标(20位精度) */ uint32_t y = packet->raw_y & 0xFFFFF; output[2] = (uint8_t)((y >> 12) & 0xFF); /* Y高8位 */ output[3] = (uint8_t)((y >> 4) & 0xFF); /* Y次高8位 */ /* X低4位和Y低4位合并到一个字节 */ output[4] = (uint8_t)(((x & 0x0F) << 4) | (y & 0x0F)); /* 压力值(12位精度) */ uint16_t p = packet->pressure & 0x0FFF; output[5] = (uint8_t)((p >> 4) & 0xFF); /* 压力高8位 */ /* 压力低4位 | 标志位 */ output[6] = (uint8_t)(((p & 0x0F) << 4) | (flags & 0x0F)); } /* ========== BLE通知发送 ========== */ /** * 通过BLE GATT通知发送数据帧 * * @param data 帧数据 * @param length 帧长度 * @return 0成功, -1未连接, -2发送失败 */ static int ble_send_notification(const uint8_t *data, uint16_t length) { if (!s_ble_connected) { return -1; } /* 调用BLE GATT服务器发送通知 */ int ret = ble_gatt_notify(BLE_CHAR_STROKE_DATA, data, length); if (ret == 0) { s_total_sent++; } else { s_send_failures++; } return ret; } /** * 发送笔状态事件(落下/抬起) */ static void send_pen_event(uint8_t event_type) { uint8_t frame[7]; memset(frame, 0, sizeof(frame)); /* 事件帧只需要标志位,坐标和压力都为0 */ frame[6] = event_type & 0x0F; ble_send_notification(frame, 7); } /** * 发送翻页事件 * 当检测到坐标所在页面发生变化时通知上位机 */ static void send_page_change_event(uint32_t new_page_id) { uint8_t frame[8]; frame[0] = FRAME_TYPE_PAGE_CHANGE; frame[1] = (uint8_t)((new_page_id >> 24) & 0xFF); frame[2] = (uint8_t)((new_page_id >> 16) & 0xFF); frame[3] = (uint8_t)((new_page_id >> 8) & 0xFF); frame[4] = (uint8_t)(new_page_id & 0xFF); /* CRC校验 */ uint16_t crc = crc16_ccitt(frame, 5); frame[5] = (uint8_t)((crc >> 8) & 0xFF); frame[6] = (uint8_t)(crc & 0xFF); frame[7] = 0; ble_send_notification(frame, 8); } /** * 更新设备信息特征值(电量、固件版本等) * 上位机可以随时读取此特征值获取笔的状态 */ static void update_device_info(uint8_t battery_percent) { uint8_t info[4]; info[0] = battery_percent; /* 电量百分比 */ info[1] = 2; /* 固件主版本号 */ info[2] = 1; /* 固件次版本号 */ info[3] = 5; /* 固件补丁版本号 → V2.1.5 */ ble_gatt_update_characteristic(BLE_CHAR_DEVICE_INFO, info, sizeof(info)); } /* ========== BLE连接事件回调 ========== */ /** * BLE连接建立回调(由BLE协议栈调用) */ void on_ble_connected(void) { s_ble_connected = true; BaseType_t higher_priority_woken = pdFALSE; xEventGroupSetBitsFromISR(g_ble_event_group, EVT_BLE_CONNECTED, &higher_priority_woken); portYIELD_FROM_ISR(higher_priority_woken); } /** * BLE连接断开回调 */ void on_ble_disconnected(void) { s_ble_connected = false; BaseType_t higher_priority_woken = pdFALSE; xEventGroupSetBitsFromISR(g_ble_event_group, EVT_BLE_DISCONNECTED, &higher_priority_woken); portYIELD_FROM_ISR(higher_priority_woken); } /* ========== BLE发送主任务 ========== */ /** * BLE发送任务(FreeRTOS任务函数) * * 运行流程: * 1. 等待BLE连接建立 * 2. 监听笔状态事件(落下/抬起)并发送事件通知 * 3. 从坐标队列读取数据,编码为7字节格式发送 * 4. 翻页检测与通知 * 5. 定期更新设备信息特征值 */ void ble_send_task(void *pvParameters) { (void)pvParameters; CoordinatePacket packet; uint32_t info_update_counter = 0; /* 注册BLE连接回调 */ ble_gatt_register_connect_callback(on_ble_connected); ble_gatt_register_disconnect_callback(on_ble_disconnected); /* 启动BLE广播 */ ble_gatt_start_advertising(); while (1) { /* 等待BLE连接 */ if (!s_ble_connected) { xEventGroupWaitBits(g_ble_event_group, EVT_BLE_CONNECTED, pdTRUE, pdFALSE, portMAX_DELAY); } /* 检查笔状态事件 */ EventBits_t events = xEventGroupGetBits(g_ble_event_group); if (events & EVT_PEN_DOWN) { xEventGroupClearBits(g_ble_event_group, EVT_PEN_DOWN); send_pen_event(FRAME_TYPE_PEN_DOWN); } if (events & EVT_PEN_UP) { xEventGroupClearBits(g_ble_event_group, EVT_PEN_UP); send_pen_event(FRAME_TYPE_PEN_UP); } /* 从坐标队列读取数据(超时10ms,避免永久阻塞) */ if (xQueueReceive(g_coordinate_queue, &packet, pdMS_TO_TICKS(10)) == pdTRUE) { /* 翻页检测 */ if (packet.page_id != s_current_page_id && s_current_page_id != 0) { send_page_change_event(packet.page_id); } s_current_page_id = packet.page_id; /* 编码并发送坐标 */ uint8_t encoded[7]; encode_coordinate(&packet, encoded, FRAME_TYPE_COORDINATE); ble_send_notification(encoded, 7); } /* 每500次循环更新一次设备信息(约每5秒) */ info_update_counter++; if (info_update_counter >= 500) { info_update_counter = 0; /* 读取当前电量 */ extern uint8_t power_get_battery_percent(void); uint8_t battery = power_get_battery_percent(); update_device_info(battery); } } }