312 lines
8.8 KiB
C
312 lines
8.8 KiB
C
/*
|
|
* 自然写智能点阵笔嵌入式固件软件 V1.0
|
|
* ble_send_task.c - BLE数据发送任务
|
|
*
|
|
* 功能说明:
|
|
* 1. 从坐标队列获取数据并打包为BLE通知帧
|
|
* 2. 7字节紧凑坐标编码格式
|
|
* 3. 发送速率控制(适配BLE连接间隔)
|
|
* 4. 笔落下/抬起事件通知
|
|
* 5. 设备信息特征值更新(电量/固件版本)
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#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);
|
|
}
|
|
}
|
|
}
|