refactor(protocol): 新增 comm_protocol 协议收发解析层,ble_transport 退化为纯传输
- 删除 protocol.h,帧定义和标定命令码统一迁入 comm_protocol.h - 新增 comm_protocol.c:上行组包(CoP/标定响应)、下行解析+msgq 分发 - ble_transport 剥离所有协议解析和参数 getter,仅保留字节收发+rx 回调注册 - main.c 初始化顺序:ble_transport → comm_protocol → adv_start → cal_init - 更新 README 架构图和项目结构表,补充标定流程章节 - 更新 BALANCE_BOARD_PROTOCOL.md 增加标定命令/响应帧格式文档
This commit is contained in:
+12
-177
@@ -1,8 +1,6 @@
|
||||
#include "ble_transport.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#include <bluetooth/services/nus.h>
|
||||
#include <string.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/gatt.h>
|
||||
#include <zephyr/bluetooth/uuid.h>
|
||||
@@ -15,12 +13,10 @@ LOG_MODULE_REGISTER(ble_transport, LOG_LEVEL_INF);
|
||||
#define BT_UUID_NUS_VAL BT_UUID_128_ENCODE(0x6e400001, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e)
|
||||
|
||||
/* --- 广播数据 --- */
|
||||
/* 广播包只放 NUS UUID,让中央设备按服务快速筛到本设备 */
|
||||
static const struct bt_data ad[] = {
|
||||
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
||||
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL),
|
||||
};
|
||||
/* 扫描响应单独带设备名 */
|
||||
static const struct bt_data sd[] = {
|
||||
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
|
||||
};
|
||||
@@ -30,34 +26,16 @@ static struct bt_conn *current_conn;
|
||||
static volatile bool nus_notification_enabled;
|
||||
static struct k_work_delayable adv_work;
|
||||
|
||||
/* --- 下行参数 --- */
|
||||
static float resistance_K;
|
||||
static float resistance_B;
|
||||
static float resistance_Tau;
|
||||
static float spotter_threshold;
|
||||
static bool spotter_enabled;
|
||||
static int64_t last_heartbeat_ms;
|
||||
/* 上层收数据回调,必须在 ble_transport_adv_start() 前完成注册 */
|
||||
static ble_rx_cb_t rx_cb;
|
||||
|
||||
/**
|
||||
* @brief 延时广播任务处理函数,用于断链后重新进入可连接状态。
|
||||
*
|
||||
* @param work Zephyr delayable work 入口参数,当前实现未直接使用。
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
/** @brief 延时广播任务,断链后重新进入可连接状态。 */
|
||||
static void adv_work_handler(struct k_work *work) {
|
||||
ARG_UNUSED(work);
|
||||
ble_transport_adv_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 蓝牙连接建立回调,接管当前连接并更新连接参数。
|
||||
*
|
||||
* @param conn 新建立的连接对象。
|
||||
* @param err 连接建立阶段的状态码;非 0 表示本次连接失败。
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
/** @brief 连接建立回调,接管连接引用并收紧连接参数。 */
|
||||
static void connected(struct bt_conn *conn, uint8_t err) {
|
||||
if (err) return;
|
||||
|
||||
@@ -65,19 +43,11 @@ static void connected(struct bt_conn *conn, uint8_t err) {
|
||||
k_work_cancel_delayable(&adv_work);
|
||||
nus_notification_enabled = false;
|
||||
|
||||
/* 收紧连接参数,降低数据发送抖动 */
|
||||
struct bt_le_conn_param param = { .interval_min = 6, .interval_max = 12, .latency = 0, .timeout = 400 };
|
||||
bt_conn_le_param_update(conn, ¶m);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 蓝牙断开回调,释放连接引用并安排后续重启广播。
|
||||
*
|
||||
* @param conn 已断开的连接对象。
|
||||
* @param reason 协议栈给出的断开原因码。
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
/** @brief 断开回调,释放连接引用并安排延时重广播。 */
|
||||
static void disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
ARG_UNUSED(conn);
|
||||
ARG_UNUSED(reason);
|
||||
@@ -86,80 +56,21 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) {
|
||||
current_conn = NULL;
|
||||
}
|
||||
nus_notification_enabled = false;
|
||||
|
||||
/* 延迟 1 秒再重启广播,给协议栈留状态回收时间 */
|
||||
k_work_schedule(&adv_work, K_MSEC(1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief NUS 通知状态回调,用于同步记录对端是否已开启通知。
|
||||
*
|
||||
* @param status 当前 NUS 发送通道的可用状态。
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
/** @brief NUS 通知使能状态回调。 */
|
||||
static void nus_send_enabled(enum bt_nus_send_status status) {
|
||||
nus_notification_enabled = (status == BT_NUS_SEND_STATUS_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理来自中央设备的下行协议帧,并更新本地参数缓存。
|
||||
*
|
||||
* @param conn 当前接收数据的蓝牙连接对象,当前实现未直接使用。
|
||||
* @param data 下行字节流起始地址。
|
||||
* @param len 本次收到的字节数。
|
||||
*
|
||||
* @return 无返回值。
|
||||
* @brief NUS 收数据回调,直接转发给上层注册的处理函数。
|
||||
*/
|
||||
static void nus_received_cb(struct bt_conn *conn, const uint8_t *data, uint16_t len) {
|
||||
ARG_UNUSED(conn);
|
||||
if (len < 3 || data[0] != PROTO_SYNC0 || data[1] != PROTO_SYNC1) return;
|
||||
|
||||
switch (data[2]) {
|
||||
case PROTO_TYPE_RESISTANCE:
|
||||
if (len >= sizeof(struct resistance_frame_t)) {
|
||||
uint8_t crc = crc8(data + 2, sizeof(struct resistance_frame_t) - 3, 0x31, 0x00, true);
|
||||
if (data[sizeof(struct resistance_frame_t) - 1] != crc) {
|
||||
LOG_WRN("Resistance frame CRC mismatch");
|
||||
break;
|
||||
}
|
||||
union resistance_pkt_t pkt;
|
||||
memcpy(pkt.bytes, data, sizeof(struct resistance_frame_t));
|
||||
resistance_K = pkt.frame.K;
|
||||
resistance_B = pkt.frame.B;
|
||||
resistance_Tau = pkt.frame.Tau;
|
||||
LOG_INF(
|
||||
"RX Resistance: K=%.1f B=%.1f Tau=%.2f", (double)resistance_K, (double)resistance_B,
|
||||
(double)resistance_Tau);
|
||||
}
|
||||
break;
|
||||
case PROTO_TYPE_SPOTTER:
|
||||
if (len >= sizeof(struct spotter_frame_t)) {
|
||||
uint8_t crc = crc8(data + 2, sizeof(struct spotter_frame_t) - 3, 0x31, 0x00, true);
|
||||
if (data[sizeof(struct spotter_frame_t) - 1] != crc) {
|
||||
LOG_WRN("Spotter frame CRC mismatch");
|
||||
break;
|
||||
}
|
||||
union spotter_pkt_t pkt;
|
||||
memcpy(pkt.bytes, data, sizeof(struct spotter_frame_t));
|
||||
spotter_threshold = pkt.frame.threshold;
|
||||
spotter_enabled = pkt.frame.enable != 0;
|
||||
LOG_INF("RX Spotter: threshold=%.1f enable=%d", (double)spotter_threshold, spotter_enabled);
|
||||
}
|
||||
break;
|
||||
case PROTO_TYPE_HEARTBEAT:
|
||||
if (len >= sizeof(struct heartbeat_frame_t)) {
|
||||
uint8_t crc = crc8(data + 2, sizeof(struct heartbeat_frame_t) - 3, 0x31, 0x00, true);
|
||||
if (data[sizeof(struct heartbeat_frame_t) - 1] != crc) {
|
||||
LOG_WRN("Heartbeat frame CRC mismatch");
|
||||
break;
|
||||
}
|
||||
last_heartbeat_ms = k_uptime_get();
|
||||
LOG_DBG("RX Heartbeat");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (rx_cb) {
|
||||
rx_cb(data, len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,12 +80,6 @@ static struct bt_nus_cb nus_cb = {
|
||||
};
|
||||
BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected };
|
||||
|
||||
/**
|
||||
* @brief 初始化 BLE 协议栈、NUS 服务和内部状态。
|
||||
*
|
||||
* @retval 0 BLE 协议栈和 NUS 服务初始化成功。
|
||||
* @retval 负值 Zephyr 蓝牙或 NUS 初始化阶段返回的具体错误码。
|
||||
*/
|
||||
int ble_transport_init(void) {
|
||||
k_work_init_delayable(&adv_work, adv_work_handler);
|
||||
|
||||
@@ -196,94 +101,24 @@ int ble_transport_init(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启动 BLE 广播。
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
void ble_transport_adv_start(void) {
|
||||
bt_le_adv_start(BT_LE_ADV_CONN_FAST_2, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 通过 NUS 发送二进制帧,自动重试最多 3 次。
|
||||
*
|
||||
* @param data 待发送字节缓冲区。
|
||||
* @param len 字节数。
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
void ble_transport_send(const uint8_t *data, uint16_t len) {
|
||||
if (!nus_notification_enabled || !current_conn) return;
|
||||
|
||||
/* NUS 偶发忙时短间隔重试减少丢包 */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (bt_nus_send(current_conn, data, len) == 0) return;
|
||||
k_usleep(500);
|
||||
}
|
||||
LOG_WRN("NUS send failed after retries (len=%u)", len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否已连接且通知已使能。
|
||||
*
|
||||
* @retval true 当前可以通过 NUS 对外发送数据。
|
||||
* @retval false 当前未连接或对端尚未开启通知。
|
||||
*/
|
||||
bool ble_transport_is_ready(void) {
|
||||
return nus_notification_enabled && (current_conn != NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次下发的阻力刚度参数 K。
|
||||
*
|
||||
* @return 当前缓存的阻力刚度参数 K。
|
||||
*/
|
||||
float ble_transport_get_resistance_K(void) {
|
||||
return resistance_K;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次下发的阻力阻尼参数 B。
|
||||
*
|
||||
* @return 当前缓存的阻力阻尼参数 B。
|
||||
*/
|
||||
float ble_transport_get_resistance_B(void) {
|
||||
return resistance_B;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次下发的阻力时间常数 Tau。
|
||||
*
|
||||
* @return 当前缓存的阻力时间常数 Tau。
|
||||
*/
|
||||
float ble_transport_get_resistance_Tau(void) {
|
||||
return resistance_Tau;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次下发的 Spotter 保护阈值。
|
||||
*
|
||||
* @return 当前缓存的 Spotter 阈值。
|
||||
*/
|
||||
float ble_transport_get_spotter_threshold(void) {
|
||||
return spotter_threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次下发的 Spotter 开关状态。
|
||||
*
|
||||
* @retval true Spotter 保护当前处于启用状态。
|
||||
* @retval false Spotter 保护当前处于关闭状态。
|
||||
*/
|
||||
bool ble_transport_get_spotter_enabled(void) {
|
||||
return spotter_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次收到心跳包的系统时间戳。
|
||||
*
|
||||
* @return 最近一次收到心跳包时的 uptime 毫秒值。
|
||||
*/
|
||||
int64_t ble_transport_get_last_heartbeat_ms(void) {
|
||||
return last_heartbeat_ms;
|
||||
void ble_transport_register_rx_cb(ble_rx_cb_t cb) {
|
||||
rx_cb = cb;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "comm_protocol.h"
|
||||
#include "ble_transport.h"
|
||||
#include "calibration.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(comm_protocol, LOG_LEVEL_INF);
|
||||
|
||||
#define MSGQ_DEPTH 8
|
||||
|
||||
K_MSGQ_DEFINE(rx_msgq, sizeof(struct comm_msg), MSGQ_DEPTH, 4);
|
||||
|
||||
/**
|
||||
* @brief 计算帧 CRC-8/MAXIM,校验范围为 type 字段到 payload 末尾。
|
||||
*
|
||||
* @param frame_bytes 完整帧的字节起始地址。
|
||||
* @param frame_size 帧总字节数(含 sync + crc)。
|
||||
*
|
||||
* @return CRC-8 值。
|
||||
*/
|
||||
static inline uint8_t frame_crc(const uint8_t *frame_bytes, size_t frame_size) {
|
||||
/* 跳过 sync0+sync1,校验到倒数第二字节(crc 字段之前) */
|
||||
return crc8(frame_bytes + 2, frame_size - 3, 0x31, 0x00, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BLE 收数据回调,解析下行帧并推入消息队列。
|
||||
*
|
||||
* @param data 原始字节流。
|
||||
* @param len 字节数。
|
||||
*/
|
||||
static void protocol_rx_handler(const uint8_t *data, uint16_t len) {
|
||||
if (len < 3 || data[0] != PROTO_SYNC0 || data[1] != PROTO_SYNC1) return;
|
||||
|
||||
struct comm_msg msg;
|
||||
|
||||
switch (data[2]) {
|
||||
case PROTO_TYPE_RESISTANCE:
|
||||
if (len < sizeof(struct resistance_frame_t)) return;
|
||||
if (data[sizeof(struct resistance_frame_t) - 1] != frame_crc(data, sizeof(struct resistance_frame_t))) {
|
||||
LOG_WRN("Resistance frame CRC mismatch");
|
||||
return;
|
||||
}
|
||||
union resistance_pkt_t rpkt;
|
||||
memcpy(rpkt.bytes, data, sizeof(struct resistance_frame_t));
|
||||
msg.type = COMM_MSG_RESISTANCE;
|
||||
msg.resistance.K = rpkt.frame.K;
|
||||
msg.resistance.B = rpkt.frame.B;
|
||||
msg.resistance.Tau = rpkt.frame.Tau;
|
||||
LOG_INF(
|
||||
"RX Resistance: K=%.1f B=%.1f Tau=%.2f", (double)msg.resistance.K, (double)msg.resistance.B,
|
||||
(double)msg.resistance.Tau);
|
||||
break;
|
||||
|
||||
case PROTO_TYPE_SPOTTER:
|
||||
if (len < sizeof(struct spotter_frame_t)) return;
|
||||
if (data[sizeof(struct spotter_frame_t) - 1] != frame_crc(data, sizeof(struct spotter_frame_t))) {
|
||||
LOG_WRN("Spotter frame CRC mismatch");
|
||||
return;
|
||||
}
|
||||
union spotter_pkt_t spkt;
|
||||
memcpy(spkt.bytes, data, sizeof(struct spotter_frame_t));
|
||||
msg.type = COMM_MSG_SPOTTER;
|
||||
msg.spotter.threshold = spkt.frame.threshold;
|
||||
msg.spotter.enable = spkt.frame.enable != 0;
|
||||
LOG_INF("RX Spotter: threshold=%.1f enable=%d", (double)msg.spotter.threshold, msg.spotter.enable);
|
||||
break;
|
||||
|
||||
case PROTO_TYPE_HEARTBEAT:
|
||||
if (len < sizeof(struct heartbeat_frame_t)) return;
|
||||
if (data[sizeof(struct heartbeat_frame_t) - 1] != frame_crc(data, sizeof(struct heartbeat_frame_t))) {
|
||||
LOG_WRN("Heartbeat frame CRC mismatch");
|
||||
return;
|
||||
}
|
||||
msg.type = COMM_MSG_HEARTBEAT;
|
||||
msg.heartbeat.counter = data[3];
|
||||
LOG_DBG("RX Heartbeat");
|
||||
break;
|
||||
|
||||
/* 标定命令不走 msgq:cal 模块自带入队/pending 机制,
|
||||
* sensor 线程通过 cal_execute_pending() 消费,无需额外中转 */
|
||||
case PROTO_TYPE_CAL_CMD:
|
||||
if (len < sizeof(struct cal_cmd_frame_t)) return;
|
||||
if (data[sizeof(struct cal_cmd_frame_t) - 1] != frame_crc(data, sizeof(struct cal_cmd_frame_t))) {
|
||||
LOG_WRN("Cal cmd frame CRC mismatch");
|
||||
return;
|
||||
}
|
||||
{
|
||||
union cal_cmd_pkt_t cpkt;
|
||||
memcpy(cpkt.bytes, data, sizeof(struct cal_cmd_frame_t));
|
||||
|
||||
int err = cal_enqueue_command(cpkt.frame.subcmd, cpkt.frame.target, cpkt.frame.param);
|
||||
if (err == 0) {
|
||||
LOG_INF(
|
||||
"RX CalCmd: sub=0x%02x target=%u param=%.2f", cpkt.frame.subcmd, cpkt.frame.target,
|
||||
(double)cpkt.frame.param);
|
||||
} else if (err == -EBUSY) {
|
||||
uint8_t resp[8] = { cpkt.frame.target, 0 };
|
||||
comm_protocol_send_cal_resp(CAL_STATUS_ERR_STATE, cpkt.frame.subcmd, resp);
|
||||
LOG_WRN("Cal cmd busy (sub=0x%02x)", cpkt.frame.subcmd);
|
||||
} else if (err == -EINVAL) {
|
||||
uint8_t resp[8] = { cpkt.frame.target, 0 };
|
||||
comm_protocol_send_cal_resp(CAL_STATUS_ERR_PARAM, cpkt.frame.subcmd, resp);
|
||||
LOG_WRN("Cal cmd invalid (sub=0x%02x)", cpkt.frame.subcmd);
|
||||
} else {
|
||||
uint8_t resp[8] = { cpkt.frame.target, 0 };
|
||||
comm_protocol_send_cal_resp(CAL_STATUS_ERR_STATE, cpkt.frame.subcmd, resp);
|
||||
LOG_ERR("Cal cmd unexpected err=%d (sub=0x%02x)", err, cpkt.frame.subcmd);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
LOG_DBG("Unknown frame type: 0x%02x", data[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (k_msgq_put(&rx_msgq, &msg, K_NO_WAIT)) {
|
||||
LOG_WRN("rx_msgq full, dropped type=%d", msg.type);
|
||||
}
|
||||
}
|
||||
|
||||
int comm_protocol_init(void) {
|
||||
ble_transport_register_rx_cb(protocol_rx_handler);
|
||||
LOG_INF("comm_protocol initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void comm_protocol_send_cop(uint8_t flags, int16_t cop_x, int16_t cop_y, int16_t force) {
|
||||
union cop_pkt_t pkt;
|
||||
pkt.frame.sync0 = PROTO_SYNC0;
|
||||
pkt.frame.sync1 = PROTO_SYNC1;
|
||||
pkt.frame.type = PROTO_TYPE_COP;
|
||||
pkt.frame.flags = flags;
|
||||
pkt.frame.cop_x = cop_x;
|
||||
pkt.frame.cop_y = cop_y;
|
||||
pkt.frame.force = force;
|
||||
pkt.frame.crc = frame_crc(pkt.bytes, sizeof(pkt.bytes));
|
||||
ble_transport_send(pkt.bytes, sizeof(pkt.bytes));
|
||||
}
|
||||
|
||||
void comm_protocol_send_cal_resp(uint8_t status, uint8_t subcmd, const uint8_t data[8]) {
|
||||
union cal_resp_pkt_t pkt;
|
||||
pkt.frame.sync0 = PROTO_SYNC0;
|
||||
pkt.frame.sync1 = PROTO_SYNC1;
|
||||
pkt.frame.type = PROTO_TYPE_CAL_RESP;
|
||||
pkt.frame.status = status;
|
||||
pkt.frame.subcmd = subcmd;
|
||||
memcpy(pkt.frame.data, data, 8);
|
||||
pkt.frame.crc = frame_crc(pkt.bytes, sizeof(pkt.bytes));
|
||||
ble_transport_send(pkt.bytes, sizeof(pkt.bytes));
|
||||
}
|
||||
|
||||
struct k_msgq *comm_protocol_get_msgq(void) {
|
||||
return &rx_msgq;
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include "ble_transport.h"
|
||||
#include "button.h"
|
||||
#include "calibration.h"
|
||||
#include "comm_protocol.h"
|
||||
#include "sensor.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
@@ -27,8 +29,14 @@ int main(void) {
|
||||
LOG_INF("--- GML670 System (CoP Binary Protocol) ---");
|
||||
|
||||
ble_transport_init();
|
||||
comm_protocol_init();
|
||||
ble_transport_adv_start();
|
||||
|
||||
if (cal_init()) {
|
||||
LOG_ERR("Failed to initialize calibration");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (button_init()) {
|
||||
LOG_ERR("Failed to initialize buttons");
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user