refactor(modules): 按角色重排 modules/ 目录,消除命名不一致
原 modules/ 下目录大小写混用(BMI088/BUZZER/DR16/IST8310/LED/PID 为大写,
attitude/c_comm/filter/gimbal/ins/motor/referee/shoot 为小写),且把第三方
移植代码(QuaternionEKF/kalman_filter)与本工程封装(ins)混在同一目录。
新结构(全部小写,按角色分层,第三方代码单独隔离):
modules/
algorithm/ pid.c/h filter.c/h 通用算法
imu/ attitude.c/h ins.c/h 姿态与惯导封装
imu/qekf/ QuaternionEKF.c/h kalman_filter.c/h
(移植自 Wang Hongxi INS 例程)
sensor/ bmi088.c/h ist8310.c/h 板载传感器驱动
comms/ dr16.c/h c_comm.c/h referee.c/h
motor/ dji_motor.c/h 电机抽象
control/ gimbal.c/h shoot.c/h 整车控制逻辑
(底盘为 chassis.c/h)
dev/ led.c/h buzzer.c/h 板级指示/发声
共移动 58 个文件(每板 29 个),旧目录全部删除。
工程文件同步(每个工程各 2 个):
- .uvprojx: Modules 分组 16 个 <FilePath> 改为新路径;
2 个非空 <IncludePath> 元素:15 个旧模块路径收敛为 8 个新目录,
并顺带删除陈旧项 ../modules/AHRS(目录根本不存在,legacy 时代残留)
与冗余项 ..\bsp(与 ../Bsp/Inc 重复)
- .uvoptx: 16 个 <PathWithFileName> 同步
关于"为什么不用改源码": Keil 的 include 靠 IncludePath 解析,本工程所有
#include 都是纯文件名形式(无路径限定),所以移动目录只需改工程配置。
另外 .h 完全不登记在工程里(只有 16 个 .c 被登记),故头文件只需移动。
回归验证(无法在此环境编译,用等价判据):
- 两个工程引用的 98 / 95 个 <FilePath> 全部真实存在
- 磁盘上 47 / 45 个 .c 全部仍被工程引用 —— 即没有任何文件掉出编译
- 2 个 IncludePath 元素均含全部 8 个新模块目录,无旧路径残留
- 所有 IncludePath 指向的目录均真实存在
- .uvprojx/.uvoptx XML 良构
- 两棵树结构一致(chassis/gimbal 等板级差异符合预期)
⚠️ 仍需在 Keil 里点一次编译确认(见 README 的构建章节)。
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
/* =============================================================================
|
||||
* c_comm.c - 云台<->底盘 板间通讯(CToC,CAN2,帧 ID 与 legacy 完全一致)
|
||||
* 帧: 0x149 云->底 摇杆(4 个大端 u16) 0x189 云->底 控制状态
|
||||
* 0x019 底->云 发射机构上电状态
|
||||
* 云台板(主机): c_comm_master_send() 每 2ms 发摇杆帧+状态帧;
|
||||
* 键鼠在线时按 legacy 规则把 WASD/鼠标合成进摇杆量;
|
||||
* Data[0] 必须实时判定 dr16_offline()(dr16_status 掉线后不会自己清零)
|
||||
* 底盘板(从机): c_comm_slave_send() 每 2ms 回传发射机构状态;
|
||||
* 中断内解析 0x149/0x189 填充 ctoc_remote(底盘的"虚拟遥控器")
|
||||
* ========================================================================== */
|
||||
#include "c_comm.h"
|
||||
#include "robot_param.h"
|
||||
#include "referee.h"
|
||||
#include <string.h>
|
||||
|
||||
volatile ctoc_remote_t ctoc_remote;
|
||||
volatile uint8_t ctoc_shooter_power = 1;
|
||||
volatile uint32_t ctoc_reply_last_tick = 0;
|
||||
volatile uint8_t ctoc_remote_fault = 0;
|
||||
volatile float ctoc_gimbal_yaw = 0.0f;
|
||||
volatile uint8_t ctoc_att_ready = 0;
|
||||
static volatile uint32_t ctoc_att_last_tick = 0;
|
||||
/* 云台自检下发的编码器方向:+1=tick增向CCW -1=CW 0=未检测(0x1A9 flags) */
|
||||
volatile int8_t ctoc_encoder_ccw = 0;
|
||||
|
||||
/* 0x1A9 帧新鲜判定(两板公共:底盘跟随用它门控,云台留作诊断) */
|
||||
uint8_t ctoc_att_fresh(void)
|
||||
{
|
||||
return (ctoc_att_last_tick != 0 &&
|
||||
HAL_GetTick() - ctoc_att_last_tick < CTOC_OFFLINE_TIME_MS);
|
||||
}
|
||||
|
||||
static void c_comm_pack_uint16(uint8_t data[8], const uint16_t val[4])
|
||||
{
|
||||
for (uint8_t i = 0; i < 4; i++)
|
||||
{
|
||||
data[2 * i] = (uint8_t)(val[i] >> 8);
|
||||
data[2 * i + 1] = (uint8_t)(val[i] & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void c_comm_send_frame(hcan_t * hcan, uint32_t std_id, const uint8_t data[8])
|
||||
{
|
||||
CanCtrlData_s msg;
|
||||
msg.hcan = hcan;
|
||||
msg.tx_header.StdId = std_id;
|
||||
msg.tx_header.IDE = CAN_ID_STD;
|
||||
msg.tx_header.RTR = CAN_RTR_DATA;
|
||||
msg.tx_header.DLC = 8;
|
||||
msg.tx_header.TransmitGlobalTime = DISABLE;
|
||||
for (uint8_t i = 0; i < 8; i++)
|
||||
msg.tx_data[i] = data[i];
|
||||
CAN_SendTxMessage(&msg);
|
||||
}
|
||||
|
||||
#if defined(ROBOT_TYPE_GIMBAL)
|
||||
|
||||
/* ============================ 云台板(主机) ============================ */
|
||||
|
||||
static void c_comm_rx_0x019(uint32_t std_id, const uint8_t rx_data[8], uint32_t dlc)
|
||||
{
|
||||
(void)std_id;
|
||||
(void)dlc;
|
||||
ctoc_shooter_power = rx_data[0]; // 发射机构上电状态
|
||||
ctoc_remote_fault = rx_data[1] & 0x01; // 底盘电机故障位
|
||||
ctoc_reply_last_tick = HAL_GetTick();
|
||||
}
|
||||
|
||||
void c_comm_init(void)
|
||||
{
|
||||
can_rx_register(&hcan2, CTOC_MASTER_ID1, c_comm_rx_0x019);
|
||||
}
|
||||
|
||||
uint8_t ctoc_chassis_fault(void)
|
||||
{
|
||||
/* 从未收到过 0x019 视为底盘不在场,不判故障(便于仅云台台架调试);
|
||||
* 收到过之后,故障位或帧超时都判故障 */
|
||||
if (ctoc_reply_last_tick == 0)
|
||||
return 0;
|
||||
if (HAL_GetTick() - ctoc_reply_last_tick > CTOC_OFFLINE_TIME_MS)
|
||||
return 1;
|
||||
return ctoc_remote_fault;
|
||||
}
|
||||
|
||||
/* 云台板:0x1A9 姿态帧 */
|
||||
void c_comm_master_send_attitude(float yaw_deg, uint8_t flags)
|
||||
{
|
||||
uint8_t data[8] = {0};
|
||||
memcpy(data, &yaw_deg, 4); // float 小端
|
||||
data[4] = flags;
|
||||
c_comm_send_frame(&hcan2, CTOC_SLAVE_ID3, data);
|
||||
}
|
||||
|
||||
void c_comm_master_send(uint8_t keyboard_online, const uint8_t kb[12], uint8_t motor_fault, uint8_t spin_enable)
|
||||
{
|
||||
uint8_t data[8];
|
||||
|
||||
if (!keyboard_online)
|
||||
{
|
||||
// 遥控器控制:直接转发原始摇杆值
|
||||
const uint16_t sticks[4] = {dr16.rc_r_RL, dr16.rc_r_UD, dr16.rc_l_RL, dr16.rc_l_UD};
|
||||
c_comm_pack_uint16(data, sticks);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 键鼠控制:合成摇杆量(与 legacy 一致)
|
||||
uint8_t key_go = kb[8] & 0x01;
|
||||
uint8_t key_back = (kb[8] >> 1) & 0x01;
|
||||
uint8_t key_left = (kb[8] >> 2) & 0x01;
|
||||
uint8_t key_right = (kb[8] >> 3) & 0x01;
|
||||
int16_t mouse_rl = (int16_t)(((uint16_t)kb[1] << 8) | kb[0]);
|
||||
int16_t mouse_du = (int16_t)(((uint16_t)kb[3] << 8) | kb[2]);
|
||||
|
||||
const uint16_t sticks[4] = {
|
||||
(uint16_t)(1024 + (key_right - key_left) * 660), // AD->右摇杆RL
|
||||
(uint16_t)(1024 + (key_go - key_back) * 660), // WS->右摇杆UD
|
||||
(uint16_t)(1024 + mouse_rl * PC_MOUSE_RL_SENSITIVITY), // 鼠标->左摇杆RL
|
||||
(uint16_t)(1024 + mouse_du * PC_MOUSE_DU_SENSITIVITY), // 鼠标->左摇杆UD
|
||||
};
|
||||
c_comm_pack_uint16(data, sticks);
|
||||
}
|
||||
c_comm_send_frame(&hcan2, CTOC_SLAVE_ID1, data);
|
||||
|
||||
/* 在线状态必须实时判定:dr16_status 只在收帧时置 1,掉线后不会自己清零,
|
||||
* 若直接转发会让底盘永远认为遥控器在线(停车保护失效) */
|
||||
data[0] = (dr16_status && !dr16_offline()) ? 1 : 0;
|
||||
data[1] = dr16.s2;
|
||||
data[2] = dr16.key_push_ctrl;
|
||||
data[3] = dr16.key_push_shift;
|
||||
data[4] = dr16_start_flag;
|
||||
data[5] = dr16.s1;
|
||||
data[6] = (motor_fault ? 0x80 : 0) | (spin_enable ? 0x04 : 0) |
|
||||
(dr16.key_alpha1 ? 0x01 : 0) |
|
||||
(dr16.key_alpha2 ? 0x02 : 0); // bit7=故障 bit2=陀螺使能(扩展)
|
||||
data[7] = 0;
|
||||
c_comm_send_frame(&hcan2, CTOC_SLAVE_ID2, data);
|
||||
}
|
||||
|
||||
#elif defined(ROBOT_TYPE_CHASSIS)
|
||||
|
||||
/* ============================ 底盘板(从机) ============================ */
|
||||
|
||||
static void c_comm_rx_0x149(uint32_t std_id, const uint8_t rx_data[8], uint32_t dlc)
|
||||
{
|
||||
(void)std_id;
|
||||
(void)dlc;
|
||||
ctoc_remote.rc_r_rl = ((uint16_t)rx_data[0] << 8) | rx_data[1];
|
||||
ctoc_remote.rc_r_ud = ((uint16_t)rx_data[2] << 8) | rx_data[3];
|
||||
ctoc_remote.rc_l_rl = ((uint16_t)rx_data[4] << 8) | rx_data[5];
|
||||
ctoc_remote.rc_l_ud = ((uint16_t)rx_data[6] << 8) | rx_data[7];
|
||||
ctoc_remote.last_tick = HAL_GetTick();
|
||||
}
|
||||
|
||||
static void c_comm_rx_0x189(uint32_t std_id, const uint8_t rx_data[8], uint32_t dlc)
|
||||
{
|
||||
(void)std_id;
|
||||
(void)dlc;
|
||||
ctoc_remote.status = rx_data[0];
|
||||
ctoc_remote.s2 = rx_data[1];
|
||||
ctoc_remote.ctrl_push = rx_data[2];
|
||||
ctoc_remote.shift_push = rx_data[3];
|
||||
ctoc_remote.start_flag = rx_data[4];
|
||||
ctoc_remote.s1 = rx_data[5];
|
||||
ctoc_remote.spin_flag = (rx_data[6] >> 2) & 0x01; // 小陀螺使能位
|
||||
ctoc_remote.gimbal_fault = (rx_data[6] & 0x80) ? 1 : 0; // 云台电机故障位
|
||||
ctoc_remote.last_tick = HAL_GetTick();
|
||||
}
|
||||
|
||||
static void c_comm_rx_0x1a9(uint32_t std_id, const uint8_t rx_data[8], uint32_t dlc)
|
||||
{
|
||||
(void)std_id;
|
||||
(void)dlc;
|
||||
float yaw;
|
||||
memcpy(&yaw, rx_data, 4);
|
||||
ctoc_gimbal_yaw = yaw;
|
||||
ctoc_att_ready = rx_data[4] & 0x01;
|
||||
if (rx_data[4] & 0x04) // 云台自检完成位
|
||||
ctoc_encoder_ccw = (rx_data[4] & 0x02) ? 1 : -1; // tick 增方向
|
||||
ctoc_att_last_tick = HAL_GetTick();
|
||||
}
|
||||
|
||||
void c_comm_init(void)
|
||||
{
|
||||
can_rx_register(&hcan2, CTOC_SLAVE_ID1, c_comm_rx_0x149);
|
||||
can_rx_register(&hcan2, CTOC_SLAVE_ID2, c_comm_rx_0x189);
|
||||
can_rx_register(&hcan2, CTOC_SLAVE_ID3, c_comm_rx_0x1a9);
|
||||
}
|
||||
|
||||
void c_comm_slave_send(uint8_t motor_fault)
|
||||
{
|
||||
uint8_t data[8] = {0};
|
||||
data[0] = referee.shooter_power_on; // 发射机构上电状态
|
||||
data[1] = motor_fault ? 0x01 : 0; // 底盘电机故障位(扩展)
|
||||
c_comm_send_frame(&hcan2, CTOC_MASTER_ID1, data);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef C_COMM_H
|
||||
#define C_COMM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bsp_can.h"
|
||||
#include "dr16.h"
|
||||
|
||||
/* 云台<->底盘 板间通讯(CToC),帧 ID 与 legacy 完全一致
|
||||
* 0x149 云台->底盘:Data[0..1]右摇杆RL [2..3]右摇杆UD [4..5]左摇杆RL [6..7]左摇杆UD(大端16bit)
|
||||
* 0x189 云台->底盘:Data[0]遥控状态 [1]S2 [3]Shift翻转 [4]启动标志 [5]S1
|
||||
* Data[2] 保留(原 Ctrl 翻转态——沿检测已移至云台侧,
|
||||
* 底盘误判沿会误开小陀螺,勿再使用)
|
||||
* Data[6] bit0=键1 bit1=键2 bit2=小陀螺使能(本工程扩展)
|
||||
* bit7=云台电机故障(本工程扩展)
|
||||
* 0x1A9 云台->底盘:Data[0..3]=云台世界系 yaw(float,LE) Data[4]=att_ready
|
||||
* (双 INS 跟随方案的数据源,2026-09 新增)
|
||||
* 0x1A9 云台->底盘:Data[0..3]=云台世界系 yaw(float,LE)
|
||||
* Data[4] flags:bit0=att_ready bit1=编码器tick增向为CCW
|
||||
* bit2=该位有效(自检完成) [跟随符号自动综合的数据源]
|
||||
* 0x019 底盘->云台:Data[0]发射机构上电状态 Data[1] bit0=底盘电机故障(本工程扩展)
|
||||
*
|
||||
* 故障位语义:任一侧电机掉线即置位,对侧收到后必须停止自己的输出(整车安全) */
|
||||
|
||||
/* 底盘板:从 0x149/0x189 解析出的"虚拟遥控器"数据(布局同 dr16_t 的子集) */
|
||||
typedef struct
|
||||
{
|
||||
uint16_t rc_r_rl;
|
||||
uint16_t rc_r_ud;
|
||||
uint16_t rc_l_rl;
|
||||
uint16_t rc_l_ud;
|
||||
uint8_t s1;
|
||||
uint8_t s2;
|
||||
uint8_t ctrl_push;
|
||||
uint8_t shift_push;
|
||||
uint8_t start_flag;
|
||||
uint8_t status; // 云台侧遥控器在线状态
|
||||
uint8_t spin_flag; // 小陀螺使能(0x189 Data[6] bit2,云台侧沿检测)
|
||||
uint8_t gimbal_fault; // 云台电机故障标志(0x189 Data[6] bit7)
|
||||
uint32_t last_tick; // 最近收帧 tick(掉线检测)
|
||||
} ctoc_remote_t;
|
||||
|
||||
extern volatile ctoc_remote_t ctoc_remote;
|
||||
|
||||
/* 云台板:底盘回传的发射机构上电状态 */
|
||||
extern volatile uint8_t ctoc_shooter_power;
|
||||
/* 云台板:最近收到 0x019 的 tick(掉线检测/诊断) */
|
||||
extern volatile uint32_t ctoc_reply_last_tick;
|
||||
/* 云台板:底盘电机故障标志(0x019 Data[1] bit0,ISR 内更新) */
|
||||
extern volatile uint8_t ctoc_remote_fault;
|
||||
|
||||
/* 云台板:底盘故障综合判定(故障位置位 或 0x019 超时;从未收到过 0x019
|
||||
* 视为底盘不在场不判故障,便于仅云台台架调试) */
|
||||
extern uint8_t ctoc_chassis_fault(void);
|
||||
|
||||
/* 云台板:发送 0x1A9 姿态帧(2ms 周期,c_comm_task 调用)
|
||||
* yaw_deg 为世界系多圈角(att_deg_yaw),flags bit0=att_ready */
|
||||
extern void c_comm_master_send_attitude(float yaw_deg, uint8_t flags);
|
||||
|
||||
/* 底盘板:云台姿态(0x1A9 解析,ISR 内更新) */
|
||||
extern volatile float ctoc_gimbal_yaw;
|
||||
extern volatile uint8_t ctoc_att_ready;
|
||||
/* 云台自检的编码器方向:+1=tick增向CCW -1=CW 0=未检测 */
|
||||
extern volatile int8_t ctoc_encoder_ccw;
|
||||
/* 底盘板:0x1A9 帧是否新鲜(掉线检测) */
|
||||
extern uint8_t ctoc_att_fresh(void);
|
||||
|
||||
/* 注册 CAN 接收回调(can_start() 之后调用) */
|
||||
extern void c_comm_init(void);
|
||||
|
||||
/* 云台板:发送摇杆帧与控制状态帧(2ms 周期,keyboard_online=1 时用键鼠合成量替代摇杆)
|
||||
* kb 参数为图传 0x0304 解析出的键鼠数据(keyboard_online 时有效),布局:
|
||||
* [0..1]鼠标X [2..3]鼠标Y [4..5]滚轮 [6]左键 [7]右键 [8]键位bitmap
|
||||
* motor_fault:本板电机掉线标志,随 0x189 Data[6] bit7 广播 */
|
||||
extern void c_comm_master_send(uint8_t keyboard_online, const uint8_t kb[12], uint8_t motor_fault, uint8_t spin_enable);
|
||||
|
||||
/* 底盘板:回传发射机构状态与底盘电机故障位(2ms 周期) */
|
||||
extern void c_comm_slave_send(uint8_t motor_fault);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
/* =============================================================================
|
||||
* dr16.c - DT7 遥控器 + 图传键鼠数据解析
|
||||
* 输入: usart3 整帧回调(18 字节,100000-8E1,每 14ms 一帧) --> dr16_parse()
|
||||
* 输出: dr16 结构体(摇杆 364~1684 中值 1024 / 拨杆 1上2下3中 / 键鼠 /
|
||||
* 按键翻转边沿 key_push_*)/ dr16_status / dr16_start_flag
|
||||
* 边沿语义(与 legacy 一致): Q/E/Shift/Ctrl 按下瞬间翻转保持;鼠标右键按下瞬间置1
|
||||
* dr16_start_flag: 1-未连接 2-收到首帧(robot_cmd 借此触发云台角度复位)
|
||||
* 掉线: dr16_offline() = 超过 DR16_OFFLINE_TIME_MS(25ms) 无帧
|
||||
* ========================================================================== */
|
||||
#include "dr16.h"
|
||||
|
||||
dr16_t dr16;
|
||||
uint8_t dr16_status = 0;
|
||||
uint8_t dr16_start_flag = 1;
|
||||
uint32_t dr16_last_tick = 0;
|
||||
uint32_t dr16_frame_count = 0;
|
||||
|
||||
void dr16_parse(const uint8_t * data)
|
||||
{
|
||||
uint8_t last_key_q = dr16.key_q;
|
||||
uint8_t last_key_e = dr16.key_e;
|
||||
uint8_t last_key_shift = dr16.key_shift;
|
||||
uint8_t last_key_ctrl = dr16.key_ctrl;
|
||||
uint8_t last_mouse_r = dr16.mouse_key_r;
|
||||
|
||||
dr16.rc_r_RL = (((uint16_t)data[1] << 8) | data[0]) & 0x07FF; // B[0:10]
|
||||
dr16.rc_r_UD = (((uint16_t)data[2] << 5) | (data[1] >> 3)) & 0x07FF; // B[11:21]
|
||||
dr16.rc_l_RL = (((uint16_t)data[4] << 10) | (((uint16_t)data[3] << 2) | (data[2] >> 6))) & 0x07FF; // B[22:32]
|
||||
dr16.rc_l_UD = (((uint16_t)data[5] << 7) | (data[4] >> 1)) & 0x07FF; // B[33:43]
|
||||
|
||||
dr16.s2 = (data[5] >> 4) & 0x03; // B[44:45]
|
||||
dr16.s1 = (data[5] >> 6) & 0x03; // B[46:47]
|
||||
|
||||
dr16.mouse_rl = (int16_t)(((uint16_t)data[7] << 8) | data[6]); // B[48:63]
|
||||
dr16.mouse_du = (int16_t)(((uint16_t)data[9] << 8) | data[8]); // B[64:79]
|
||||
dr16.mouse_wheel = (int16_t)(((uint16_t)data[11] << 8) | data[10]); // B[80:95]
|
||||
dr16.mouse_key_l = data[12];
|
||||
dr16.mouse_key_r = data[13];
|
||||
|
||||
dr16.key_w = data[14] & 0x01;
|
||||
dr16.key_s = (data[14] >> 1) & 0x01;
|
||||
dr16.key_a = (data[14] >> 2) & 0x01;
|
||||
dr16.key_d = (data[14] >> 3) & 0x01;
|
||||
dr16.key_shift = (data[14] >> 4) & 0x01;
|
||||
dr16.key_ctrl = (data[14] >> 5) & 0x01;
|
||||
dr16.key_q = (data[14] >> 6) & 0x01;
|
||||
dr16.key_e = (data[14] >> 7) & 0x01;
|
||||
dr16.key_alpha1 = (data[14] >> 9) & 0x01;
|
||||
dr16.key_alpha2 = (data[14] >> 10) & 0x01;
|
||||
|
||||
dr16.thumb_wheel = (int16_t)(((uint16_t)data[17] << 8) | data[16]); // B[120:135]
|
||||
|
||||
/* 按键边沿检测(legacy 语义:Q/E/Shift/Ctrl 按下翻转,鼠标右键按下瞬间置 1) */
|
||||
if (last_key_q == 0 && dr16.key_q == 1)
|
||||
dr16.key_push_q = !dr16.key_push_q;
|
||||
if (last_key_e == 0 && dr16.key_e == 1)
|
||||
dr16.key_push_e = !dr16.key_push_e;
|
||||
if (last_key_shift == 0 && dr16.key_shift == 1)
|
||||
dr16.key_push_shift = !dr16.key_push_shift;
|
||||
if (last_key_ctrl == 0 && dr16.key_ctrl == 1)
|
||||
dr16.key_push_ctrl = !dr16.key_push_ctrl;
|
||||
if (last_mouse_r == 0 && dr16.mouse_key_r == 1)
|
||||
dr16.mouse_key_push_r = 1;
|
||||
else
|
||||
dr16.mouse_key_push_r = 0;
|
||||
|
||||
dr16_last_tick = HAL_GetTick();
|
||||
dr16_status = 1;
|
||||
dr16_frame_count++;
|
||||
|
||||
/* 首次收到数据:置启动标志,触发云台角度复位(与 legacy Remote_StartFlag 语义一致) */
|
||||
if (dr16_start_flag == 1)
|
||||
dr16_start_flag = 2;
|
||||
}
|
||||
|
||||
uint8_t dr16_offline(void)
|
||||
{
|
||||
return (dr16_status == 0 || HAL_GetTick() - dr16_last_tick > DR16_OFFLINE_TIME_MS);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef DR16_H
|
||||
#define DR16_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "stm32f4xx_hal.h"
|
||||
|
||||
#define DR16_OFFLINE_TIME_MS 25 // 遥控器掉线判定时间(legacy 25ms)
|
||||
|
||||
/* DT7 遥控器 + 图传键鼠数据结构,自 legacy Remote 模块移植
|
||||
* S1/S2 拨杆:1-上 2-下 3-中(与 legacy 解析保持一致)
|
||||
* 摇杆:364~1684,中值 1024 */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t rc_r_RL; // 通道0-右摇杆左右
|
||||
uint16_t rc_r_UD; // 通道1-右摇杆上下
|
||||
uint16_t rc_l_RL; // 通道2-左摇杆左右
|
||||
uint16_t rc_l_UD; // 通道3-左摇杆上下
|
||||
|
||||
uint8_t s1; // 左侧拨杆
|
||||
uint8_t s2; // 右侧拨杆
|
||||
|
||||
int16_t mouse_rl; // 鼠标X轴速度
|
||||
int16_t mouse_du; // 鼠标Y轴速度
|
||||
int16_t mouse_wheel; // 鼠标滚轮
|
||||
uint8_t mouse_key_l;
|
||||
uint8_t mouse_key_r;
|
||||
|
||||
uint8_t key_w;
|
||||
uint8_t key_s;
|
||||
uint8_t key_a;
|
||||
uint8_t key_d;
|
||||
uint8_t key_shift;
|
||||
uint8_t key_ctrl;
|
||||
uint8_t key_q;
|
||||
uint8_t key_e;
|
||||
uint8_t key_alpha1; // 图传扩展键
|
||||
uint8_t key_alpha2;
|
||||
|
||||
int16_t thumb_wheel; // 遥控器拨轮
|
||||
|
||||
/* 按键边沿检测(legacy 语义) */
|
||||
uint8_t key_push_q; // Q 键按下翻转
|
||||
uint8_t key_push_e; // E 键按下翻转
|
||||
uint8_t key_push_shift; // Shift 按下翻转
|
||||
uint8_t key_push_ctrl; // Ctrl 按下翻转
|
||||
uint8_t mouse_key_push_r; // 鼠标右键按下瞬间为 1
|
||||
} dr16_t;
|
||||
|
||||
extern dr16_t dr16;
|
||||
extern uint8_t dr16_status; // 1-已连接 0-掉线
|
||||
extern uint8_t dr16_start_flag; // 1-准备启动 2-收到首帧(与 legacy Remote_StartFlag 一致)
|
||||
|
||||
/* 解析一帧 18 字节原始数据(usart3 接收回调/中断上下文调用) */
|
||||
extern void dr16_parse(const uint8_t * data);
|
||||
|
||||
/* 最近收帧 tick(HAL_GetTick),用于掉线检测 */
|
||||
extern uint32_t dr16_last_tick;
|
||||
extern uint32_t dr16_frame_count; // 收到的帧计数(诊断用)
|
||||
|
||||
/* 判定遥控器是否掉线 */
|
||||
extern uint8_t dr16_offline(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,218 @@
|
||||
/* =============================================================================
|
||||
* referee.c - 裁判系统协议解析(0x0201 机器人状态 / 0x0202 实时功率 /
|
||||
* 0x0304 自定义控制器键鼠)
|
||||
* 输入: referee_parse_byte() 流式喂字节(USART6 整帧回调里循环调用)
|
||||
* 校验: CRC8(多项式0x31,初值0xFF) / CRC16(0x1021,0xFFFF),位运算实现,
|
||||
* 与 legacy 查表算法等价
|
||||
* 字段偏移与 legacy 逐一对应:
|
||||
* 0x0201: data[0]=robot_id, data[10..11]=功率上限, data[12]&0x04=发射机构上电
|
||||
* 0x0202: data[4..7]=实时功率(float), data[8..9]=缓冲能量(J)
|
||||
* 0x0304: 12 字节键鼠数据 -> keyboard 钩子(仅云台板注册)
|
||||
* 解析在接收中断内完成,任务层只读 referee_data_t 全局量
|
||||
* ========================================================================== */
|
||||
#include "referee.h"
|
||||
#include "main.h"
|
||||
|
||||
/* 帧结构: SOF(1) data_len(2) seq(1) CRC8(1) | cmd_id(2) data(n) CRC16(2)
|
||||
* 校验算法与 DJI 裁判系统协议一致:
|
||||
* CRC8 多项式 0x31,初值 0xFF,输入输出不反转
|
||||
* CRC16 多项式 0x1021,初值 0xFFFF,输入输出不反转 */
|
||||
|
||||
#define REF_FRAME_MAX_LEN 160
|
||||
|
||||
static uint8_t crc8_update(uint8_t crc, uint8_t data)
|
||||
{
|
||||
crc ^= data;
|
||||
for (uint8_t i = 0; i < 8; i++)
|
||||
crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x31) : (uint8_t)(crc << 1);
|
||||
return crc;
|
||||
}
|
||||
|
||||
static uint16_t crc16_update(uint16_t crc, uint8_t data)
|
||||
{
|
||||
crc ^= (uint16_t)data << 8;
|
||||
for (uint8_t i = 0; i < 8; i++)
|
||||
crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1);
|
||||
return crc;
|
||||
}
|
||||
|
||||
static uint8_t verify_crc8(const uint8_t * data, uint16_t len)
|
||||
{
|
||||
uint8_t crc = 0xFF;
|
||||
for (uint16_t i = 0; i < len - 1; i++)
|
||||
crc = crc8_update(crc, data[i]);
|
||||
return crc == data[len - 1];
|
||||
}
|
||||
|
||||
static uint8_t verify_crc16(const uint8_t * data, uint16_t len)
|
||||
{
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (uint16_t i = 0; i < len - 2; i++)
|
||||
crc = crc16_update(crc, data[i]);
|
||||
return (uint8_t)(crc & 0xFF) == data[len - 2] && (uint8_t)(crc >> 8) == data[len - 1];
|
||||
}
|
||||
|
||||
referee_data_t referee = {
|
||||
.robot_id = 0,
|
||||
.shooter_power_on = 1,
|
||||
.chassis_power_limit = 60,
|
||||
.chassis_power = 0,
|
||||
.buffer_energy = 60.0f,
|
||||
.link_status = 0,
|
||||
.last_update_tick = 0,
|
||||
};
|
||||
|
||||
static referee_keyboard_hook_f keyboard_hook = NULL;
|
||||
|
||||
void referee_set_keyboard_hook(referee_keyboard_hook_f hook)
|
||||
{
|
||||
keyboard_hook = hook;
|
||||
}
|
||||
|
||||
/* 解析状态机 */
|
||||
typedef enum
|
||||
{
|
||||
REF_STATE_SOF = 0,
|
||||
REF_STATE_LEN_LOW,
|
||||
REF_STATE_LEN_HIGH,
|
||||
REF_STATE_SEQ,
|
||||
REF_STATE_HEADER_CRC8,
|
||||
REF_STATE_CMD_LOW,
|
||||
REF_STATE_CMD_HIGH,
|
||||
REF_STATE_DATA,
|
||||
REF_STATE_CRC16_LOW,
|
||||
REF_STATE_CRC16_HIGH,
|
||||
} ref_state_e;
|
||||
|
||||
static ref_state_e ref_state = REF_STATE_SOF;
|
||||
static uint16_t ref_data_len = 0;
|
||||
static uint16_t ref_cmd_id = 0;
|
||||
static uint16_t ref_data_index = 0;
|
||||
static uint8_t ref_frame_buf[REF_FRAME_MAX_LEN];
|
||||
static uint16_t ref_frame_index = 0;
|
||||
|
||||
static void referee_dispatch(uint16_t cmd_id, const uint8_t * data, uint16_t len)
|
||||
{
|
||||
switch (cmd_id)
|
||||
{
|
||||
case 0x0201: // 机器人状态(与 legacy 偏移一致)
|
||||
if (len >= 13)
|
||||
{
|
||||
referee.robot_id = data[0];
|
||||
referee.chassis_power_limit = (uint16_t)(data[10] | (data[11] << 8));
|
||||
referee.shooter_power_on = (data[12] & 0x04) >> 2;
|
||||
referee.last_update_tick = HAL_GetTick();
|
||||
referee.link_status = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0202: // 实时功率(与 legacy 偏移一致)
|
||||
if (len >= 10)
|
||||
{
|
||||
float power;
|
||||
memcpy(&power, &data[4], 4);
|
||||
referee.chassis_power = power;
|
||||
referee.buffer_energy = (float)((uint16_t)(data[8] | (data[9] << 8)));
|
||||
referee.last_update_tick = HAL_GetTick();
|
||||
referee.link_status = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0304: // 自定义控制器(经图传链路转发)
|
||||
if (len >= 12 && keyboard_hook != NULL)
|
||||
keyboard_hook(data);
|
||||
referee.last_update_tick = HAL_GetTick();
|
||||
referee.link_status = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void referee_parse_byte(uint8_t byte)
|
||||
{
|
||||
switch (ref_state)
|
||||
{
|
||||
case REF_STATE_SOF:
|
||||
if (byte == 0xA5)
|
||||
{
|
||||
ref_frame_index = 0;
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
ref_state = REF_STATE_LEN_LOW;
|
||||
}
|
||||
break;
|
||||
|
||||
case REF_STATE_LEN_LOW:
|
||||
ref_data_len = byte;
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
ref_state = REF_STATE_LEN_HIGH;
|
||||
break;
|
||||
|
||||
case REF_STATE_LEN_HIGH:
|
||||
ref_data_len |= (uint16_t)byte << 8;
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
if (ref_data_len > REF_FRAME_MAX_LEN - 9) // 帧过长,重新找帧头
|
||||
ref_state = REF_STATE_SOF;
|
||||
else
|
||||
ref_state = REF_STATE_SEQ;
|
||||
break;
|
||||
|
||||
case REF_STATE_SEQ:
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
ref_state = REF_STATE_HEADER_CRC8;
|
||||
break;
|
||||
|
||||
case REF_STATE_HEADER_CRC8:
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
if (verify_crc8(ref_frame_buf, 5))
|
||||
ref_state = REF_STATE_CMD_LOW;
|
||||
else
|
||||
ref_state = REF_STATE_SOF;
|
||||
break;
|
||||
|
||||
case REF_STATE_CMD_LOW:
|
||||
ref_cmd_id = byte;
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
ref_state = REF_STATE_CMD_HIGH;
|
||||
break;
|
||||
|
||||
case REF_STATE_CMD_HIGH:
|
||||
ref_cmd_id |= (uint16_t)byte << 8;
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
ref_data_index = 0;
|
||||
if (ref_data_len == 0)
|
||||
ref_state = REF_STATE_CRC16_LOW;
|
||||
else
|
||||
ref_state = REF_STATE_DATA;
|
||||
break;
|
||||
|
||||
case REF_STATE_DATA:
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
if (++ref_data_index >= ref_data_len)
|
||||
ref_state = REF_STATE_CRC16_LOW;
|
||||
break;
|
||||
|
||||
case REF_STATE_CRC16_LOW:
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
ref_state = REF_STATE_CRC16_HIGH;
|
||||
break;
|
||||
|
||||
case REF_STATE_CRC16_HIGH:
|
||||
ref_frame_buf[ref_frame_index++] = byte;
|
||||
if (verify_crc16(ref_frame_buf, ref_frame_index))
|
||||
referee_dispatch(ref_cmd_id, &ref_frame_buf[7], ref_data_len);
|
||||
ref_state = REF_STATE_SOF;
|
||||
break;
|
||||
|
||||
default:
|
||||
ref_state = REF_STATE_SOF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void referee_parse_bytes(const uint8_t * data, uint16_t len)
|
||||
{
|
||||
while (len--)
|
||||
referee_parse_byte(*data++);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef REFEREE_H
|
||||
#define REFEREE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 裁判系统协议解析(0x0201 机器人状态 / 0x0202 实时功率 / 0x0304 自定义控制器)
|
||||
* CRC 校验与字段偏移和 legacy RefereeSystem 模块保持一致 */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t robot_id; // 机器人 ID(无裁判系统时为 0,4 号步兵为 4/104)
|
||||
uint8_t shooter_power_on; // 发射机构上电状态(0x0201)
|
||||
uint16_t chassis_power_limit; // 底盘功率上限(W)
|
||||
float chassis_power; // 底盘实时功率(W)
|
||||
float buffer_energy; // 底盘缓冲能量(J)
|
||||
uint8_t link_status; // 收到过有效帧置 1,掉线检测配合 last_update_tick
|
||||
uint32_t last_update_tick;
|
||||
} referee_data_t;
|
||||
|
||||
extern referee_data_t referee;
|
||||
|
||||
/* 流式解析一个字节(中断上下文调用;把串口收到的每个字节依次喂进来) */
|
||||
extern void referee_parse_byte(uint8_t byte);
|
||||
|
||||
/* 批量喂入(usart 整帧回调里循环调用 referee_parse_byte 即可,此函数为便捷封装) */
|
||||
extern void referee_parse_bytes(const uint8_t * data, uint16_t len);
|
||||
|
||||
/* 0x0304(自定义控制器)数据回调,在中断上下文执行 */
|
||||
typedef void (*referee_keyboard_hook_f)(const uint8_t data[12]);
|
||||
extern void referee_set_keyboard_hook(referee_keyboard_hook_f hook);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user