diff --git a/User_Code/module/software/btb_comm/btb.c b/User_Code/module/software/btb_comm/btb.c index f9544a7..bd4efce 100644 --- a/User_Code/module/software/btb_comm/btb.c +++ b/User_Code/module/software/btb_comm/btb.c @@ -1,5 +1,158 @@ -// -// Created by ASUS on 2025/11/17. -// - #include "btb.h" +#include +#include +// #include "crc8.h" +#include "bsp_dwt.h" +#include "bsp_fdcan.h" +#include "daemon.h" + +// 内部函数声明 +static void btb_reset_rx(btb_instance_t *ins); +static void btb_rx_callback(FDCANInstance *_instance); +static void btb_lost_callback(void *btb_ins); + +/** + * @brief 重置接收状态和缓冲区 + */ +static void btb_reset_rx(btb_instance_t *ins) +{ + memset(ins->raw_recvbuf, 0, ins->cur_recv_len); + ins->recv_state = 0; + ins->cur_recv_len = 0; +} + +/** + * @brief CAN 接收回调(由底层驱动在收到数据时调用) + */ +static void btb_rx_callback(FDCANInstance *_instance) +{ + // 从 CANInstance 的 id 字段获取 btb_instance_t 指针 + btb_instance_t *comm = (btb_instance_t *)_instance->id; + + /* 接收状态机 */ + if (_instance->rx_buff[0] == BTB_HEADER && comm->recv_state == 0) + { + if (_instance->rx_buff[1] == comm->recv_data_len) + { + comm->recv_state = 1; // 开始接收数据 + } + else + return; // 长度不匹配,忽略 + } + + if (comm->recv_state) + { + // 检查缓冲区是否溢出 + if (comm->cur_recv_len + _instance->rx_len > comm->recv_buf_len) + { + btb_reset_rx(comm); + return; + } + + // 拷贝数据 + memcpy(comm->raw_recvbuf + comm->cur_recv_len, _instance->rx_buff, _instance->rx_len); + comm->cur_recv_len += _instance->rx_len; + + // 检查是否接收完整一帧 + if (comm->cur_recv_len == comm->recv_buf_len) + { + // 验证帧尾和 CRC + if (comm->raw_recvbuf[comm->recv_buf_len - 1] == BTB_TAIL) + { + // uint8_t crc = crc_8(comm->raw_recvbuf + 2, comm->recv_data_len); //Todo:CRC + // if (comm->raw_recvbuf[comm->recv_buf_len - 2] == crc) + // { + memcpy(comm->unpacked_recv_data, comm->raw_recvbuf + 2, comm->recv_data_len); + comm->update_flag = 1; + DaemonReload(comm->comm_daemon); // 喂看门狗 + // } + } + btb_reset_rx(comm); + } + } +} + +/** + * @brief 看门狗超时回调(通信丢失) + */ +static void btb_lost_callback(void *btb_ins) +{ + btb_instance_t *comm = (btb_instance_t *)btb_ins; + btb_reset_rx(comm); + LOGWARNING("[btb] can comm rx[%d] lost, reset rx state.", &comm->fdcan_ins->rx_id); +} + +/** + * @brief 初始化 BTB 实例 + */ +btb_instance_t *btb_init(btb_config_t *config) +{ + btb_instance_t *ins = (btb_instance_t *)malloc(sizeof(btb_instance_t)); + memset(ins, 0, sizeof(btb_instance_t)); + + ins->recv_data_len = config->recv_data_len; + ins->recv_buf_len = config->recv_data_len + BTB_OFFSET_BYTES; + ins->send_data_len = config->send_data_len; + ins->send_buf_len = config->send_data_len + BTB_OFFSET_BYTES; + + // 预填充发送缓冲区固定字段 + ins->raw_sendbuf[0] = BTB_HEADER; + ins->raw_sendbuf[1] = config->send_data_len; // 数据长度 + ins->raw_sendbuf[config->send_data_len + BTB_OFFSET_BYTES - 1] = BTB_TAIL; + + // 注册 CAN 实例 + config->can_config.id = ins; + config->can_config.can_module_callback = btb_rx_callback; + ins->fdcan_ins = CANRegister(&config->can_config); // 假设此函数存在 + + // 注册看门狗 + Daemon_Init_Config_s daemon_config = { + .callback = btb_lost_callback, + .owner_id = (void *)ins, + .reload_count = config->daemon_count, + }; + ins->comm_daemon = DaemonRegister(&daemon_config); + + return ins; +} + +/** + * @brief 发送数据(自动分包) + */ +void btb_send(btb_instance_t *ins, uint8_t *data) +{ + // 计算 CRC 并填入发送缓冲区 + memcpy(ins->raw_sendbuf + 2, data, ins->send_data_len);//ToDo:CRC + // uint8_t crc = crc_8(data, ins->send_data_len); + // ins->raw_sendbuf[2 + ins->send_data_len] = crc; + + // 分包发送(CAN 单包最大 8 字节) + uint8_t remain = ins->send_buf_len; + uint8_t offset = 0; + while (remain > 0) + { + uint8_t len = (remain >= 8) ? 8 : remain; + CANSetDLC(ins->fdcan_ins, len); // 设置 DLC + memcpy(ins->fdcan_ins->tx_buff, ins->raw_sendbuf + offset, len); + CANTransmit(ins->fdcan_ins, 1); // 阻塞发送 + offset += len; + remain -= len; + } +} + +/** + * @brief 获取最新接收的数据(读取后清除更新标志) + */ +void *btb_get(btb_instance_t *ins) +{ + ins->update_flag = 0; + return ins->unpacked_recv_data; +} + +/** + * @brief 检查通信是否在线(看门狗未超时) + */ +uint8_t btb_is_online(btb_instance_t *ins) +{ + return DaemonIsOnline(ins->comm_daemon); +} \ No newline at end of file diff --git a/User_Code/module/software/btb_comm/btb.h b/User_Code/module/software/btb_comm/btb.h index aa9f0a7..db564ff 100644 --- a/User_Code/module/software/btb_comm/btb.h +++ b/User_Code/module/software/btb_comm/btb.h @@ -5,4 +5,82 @@ #ifndef TRONONEH7_SCAFFOLD_BTB_H #define TRONONEH7_SCAFFOLD_BTB_H +#include "bsp_fdcan.h" +#include "daemon.h" + +#define BTB_MAX_INSTANCE 4 // 注意均衡负载,一条总线上不要挂载过多的外设 + +#define BTB_MAX_BUFFSIZE 60 // 最大发送/接收字节数,如果不够可以增加此数值 +#define BTB_HEADER 's' // 帧头 +#define BTB_TAIL 'e' // 帧尾 +#define BTB_OFFSET_BYTES 4 // 's'+ datalen + 'e' + crc8 + +#pragma pack(1) +/* BTB 结构体, 拥有 BTB 的 app 应该包含一个 BTB 指针 */ +typedef struct +{ + FDCANInstance *fdcan_ins; + /* 发送部分 */ + uint8_t send_data_len; // 发送数据长度 + uint8_t send_buf_len; // 发送缓冲区长度,为发送数据长度+帧头单包数据长度帧尾以及校验和(4) + uint8_t raw_sendbuf[BTB_MAX_BUFFSIZE + BTB_OFFSET_BYTES]; // 额外4个bytes保存帧头帧尾和校验和 + /* 接收部分 */ + uint8_t recv_data_len; // 接收数据长度 + uint8_t recv_buf_len; // 接收缓冲区长度,为接收数据长度+帧头单包数据长度帧尾以及校验和(4) + uint8_t raw_recvbuf[BTB_MAX_BUFFSIZE + BTB_OFFSET_BYTES]; // 额外4个bytes保存帧头帧尾和校验和 + uint8_t unpacked_recv_data[BTB_MAX_BUFFSIZE]; // 解包后的数据,调用 btb_get() 后 cast 成对应的类型通过指针读取即可 + /* 接收和更新标志位*/ + uint8_t recv_state; // 接收状态, + uint8_t cur_recv_len; // 当前已经接收到的数据长度(包括帧头帧尾 datalen 和校验和) + uint8_t update_flag; // 数据更新标志位,当接收到新数据时,会将此标志位置1,调用 btb_get() 后会将此标志位置0 + + Daemon_Instance *comm_daemon; +} btb_instance_t; +#pragma pack() + +/* BTB 初始化结构体 */ +typedef struct +{ + FDCAN_Init_Config_s can_config; // CAN初始化结构体 + uint8_t send_data_len; // 发送数据长度 + uint8_t recv_data_len; // 接收数据长度 + + uint16_t daemon_count; // 守护进程计数,用于初始化守护进程 +} btb_config_t; + +/** + * @brief 初始化 BTB + * + * @param config BTB 初始化结构体 + * @return btb_instance_t* + */ +btb_instance_t *btb_init(btb_config_t *config); + +/** + * @brief 通过 BTB 发送数据 + * + * @param instance btb 实例 + * @param data 注意此地址的有效数据长度需要和初始化时传入的 datalen 相同 + */ +void btb_send(btb_instance_t *instance, uint8_t *data); + +/** + * @brief 获取 BTB 接收的数据,需要自己使用强制类型转换将返回的 void 指针转换成指定类型 + * + * @return void* 返回的数据指针 + * @attention 注意如果希望直接通过转换指针访问数据,如果数据是 union 或 struct,要检查是否使用了 pack(n) + * BTB 接收到的数据可以看作是 pack(1) 之后的,是连续存放的. + * 如果使用了 pack(n) 可能会导致数据错乱,并且无法使用强制类型转换通过 memcpy 直接访问,转而需要手动解包. + * 强烈建议通过 BTB 传输的数据使用 pack(1) + */ +void *btb_get(btb_instance_t *instance); + +/** + * @brief 检查 BTB 是否在线 + * + * @param instance + * @return uint8_t + */ +uint8_t btb_is_online(btb_instance_t *instance); + #endif //TRONONEH7_SCAFFOLD_BTB_H