半舵·正式发售

This commit is contained in:
guoxy06
2026-08-29 19:55:45 +08:00
parent 2b18d60476
commit c40525234f
5 changed files with 72 additions and 22 deletions

View File

@@ -0,0 +1,56 @@
# CAN 非阻塞发送改造 · 隐患记录
> 记录时间:2026-08-29
> 背景:修复“拔掉 CAN1 后,CAN2 上的 pitch / 摩擦轮 / 拨盘电机明显抖动”的问题。
## 一、本次改动摘要
| 文件 | 改动 | 目的 |
| --- | --- | --- |
| bsp/can/bsp_can.c | `CANTransmit()` 由忙等改为非阻塞 | 避免某一路 CAN 离线时拖慢电机控制任务 |
| bsp/can/bsp_can.c | `CANAddFilter()` 对 `can_filter_conf` 先 `memset` 清零 | 避免未初始化字段成为垃圾匹配 ID |
| Src/can.c | CAN1/CAN2 的 `AutoBusOff` 改为 `ENABLE` | 断开后重新连接能自动从 bus-off 恢复 |
## 二、隐患与风险
### 1. 双板模式下的 `CANCommSend` 多帧发送可能丢包(当前为休眠隐患)
- 相关位置:`modules/can_comm/can_comm.c` 的 `CANCommSend()`(约 106、116、121 行)。
- `CANCommSend()` 按每 8 字节一帧连续发送。当待发送数据超过 24 字节(即超过 3 帧)时,第 4 帧起会因为 3 个发送邮箱都还在忙碌而被直接丢弃。
- 原来的忙等版本会等待邮箱空闲后继续发完,非阻塞版本不会。
- 当前工程为 `ONE_BOARD`,该路径未参与编译,暂无影响;一旦切换到 `CHASSIS_BOARD` / `GIMBAL_BOARD`,此问题会暴露。
### 2. 丢帧变为静默,失去诊断信息
- 相关位置:`bsp/can/bsp_can.c` 的 `CANTransmit()`(约 107 行)。
- 原实现邮箱满时会打印 `LOGWARNING`;现在直接 `return 0`,不重试、不记录。
- 未来若总线设备增多或发送频率升高出现真实拥塞,丢帧将不可见,排查难度增加。
- 对 DJI 电机本身影响较小:500 Hz 控制帧偶尔丢一两帧,电机内部看门狗时间远大于 2 ms,不会触发保护。
### 3. `timeout` 参数实际失效
- `CANTransmit()` 签名仍保留 `float timeout`,但函数内部用 `(void)timeout` 忽略了它。
- 调用方传入的 `1`、`0.2`、`0.5` 等值已不再生效,存在可读性与预期一致性隐患。
### 4. `AutoBusOff` 行为变化 + CubeMX 覆盖风险
- 相关位置:`Src/can.c`(约 48、80 行)。
- 改为 `ENABLE` 后,持续总线故障(如短路)会导致控制器在 bus-off ↔ 恢复之间反复循环;当前未开启 CAN 错误中断,不会产生中断风暴,但行为不同于 `DISABLE` 时的锁死状态。
- `Src/can.c` 是 CubeMX 生成文件,从 `basic_framework.ioc` 重新生成工程时会被覆盖回 `DISABLE`,需要同步修改 `.ioc` 中的 Automatic Bus-Off Management 选项。
### 5. 过滤器配置 `memset`(基本无风险)
- 相关位置:`bsp/can/bsp_can.c` 的 `CANAddFilter()`(约 32 行)。
- 属于纯修正,无副作用。
## 三、建议后续动作
- [ ] 若将来启用双板,给 `CANCommSend()` 的多帧发送增加“短且有界”的等待/重试,避免丢中间帧。
- [ ] 评估是否为 `CANTransmit()` 增加可选的丢帧统计或低频告警,在“不阻塞任务”与“可诊断”之间取得平衡。
- [ ] 在 `basic_framework.ioc` 中同步开启两个 CAN 的 Automatic Bus-Off Management,避免重新生成代码时被覆盖。
## 四、相关文件
- `bsp/can/bsp_can.c`
- `modules/can_comm/can_comm.c`
- `Src/can.c`

View File

@@ -45,7 +45,7 @@ void MX_CAN1_Init(void)
hcan1.Init.TimeSeg1 = CAN_BS1_10TQ; hcan1.Init.TimeSeg1 = CAN_BS1_10TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_3TQ; hcan1.Init.TimeSeg2 = CAN_BS2_3TQ;
hcan1.Init.TimeTriggeredMode = DISABLE; hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE; hcan1.Init.AutoBusOff = ENABLE;
hcan1.Init.AutoWakeUp = DISABLE; hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE; hcan1.Init.AutoRetransmission = ENABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE; hcan1.Init.ReceiveFifoLocked = DISABLE;
@@ -77,7 +77,7 @@ void MX_CAN2_Init(void)
hcan2.Init.TimeSeg1 = CAN_BS1_10TQ; hcan2.Init.TimeSeg1 = CAN_BS1_10TQ;
hcan2.Init.TimeSeg2 = CAN_BS2_3TQ; hcan2.Init.TimeSeg2 = CAN_BS2_3TQ;
hcan2.Init.TimeTriggeredMode = DISABLE; hcan2.Init.TimeTriggeredMode = DISABLE;
hcan2.Init.AutoBusOff = DISABLE; hcan2.Init.AutoBusOff = ENABLE;
hcan2.Init.AutoWakeUp = DISABLE; hcan2.Init.AutoWakeUp = DISABLE;
hcan2.Init.AutoRetransmission = ENABLE; hcan2.Init.AutoRetransmission = ENABLE;
hcan2.Init.ReceiveFifoLocked = DISABLE; hcan2.Init.ReceiveFifoLocked = DISABLE;

View File

@@ -147,7 +147,7 @@ void ChassisInit()
}; };
chassis_motor_steering_config.can_init_config.tx_id = 2; chassis_motor_steering_config.can_init_config.tx_id = 2;
motor_steering_rf = DJIMotorInit(&chassis_motor_steering_config); motor_steering_rf = DJIMotorInit(&chassis_motor_steering_config);///
chassis_motor_steering_config.can_init_config.tx_id = 1; chassis_motor_steering_config.can_init_config.tx_id = 1;
motor_steering_lb = DJIMotorInit(&chassis_motor_steering_config); motor_steering_lb = DJIMotorInit(&chassis_motor_steering_config);
@@ -164,11 +164,11 @@ void ChassisInit()
.MaxOut = 50, .MaxOut = 50,
// .Derivative_LPF_RC = 0.01, // 0.01 // .Derivative_LPF_RC = 0.01, // 0.01
}; };
PIDInit(&chassis_follow_pid, &chassis_follow_pid_conf); PIDInit(&chassis_follow_pid, &chassis_follow_pid_conf);////
// 裁判系统初始化 // 裁判系统初始化
referee_data = UITaskInit(&huart6,&ui_data); // 裁判系统初始化,会同时初始化UI referee_data = UITaskInit(&huart6,&ui_data); // 裁判系统初始化,会同时初始化UI//
SuperCap_Init_Config_s cap_conf = { SuperCap_Init_Config_s cap_conf = {
.can_config = { .can_config = {
@@ -391,7 +391,6 @@ void ChassisTask() {
DJIMotorEnable(motor_steering_rf); DJIMotorEnable(motor_steering_rf);
DJIMotorEnable(motor_steering_lb); DJIMotorEnable(motor_steering_lb);
} }
// 根据控制模式设定旋转速度 // 根据控制模式设定旋转速度
switch (chassis_cmd_recv.chassis_mode) switch (chassis_cmd_recv.chassis_mode)
{ {
@@ -403,7 +402,7 @@ void ChassisTask() {
float single_round_offset = chassis_cmd_recv.offset_angle; float single_round_offset = chassis_cmd_recv.offset_angle;
if(single_round_offset > 180.0f) if(single_round_offset > 180.0f)
{ {
single_round_offset -= 360.0f; single_round_offset -= 360.0f;///
} }
else if(single_round_offset < -180.0f) else if(single_round_offset < -180.0f)
{ {

View File

@@ -29,6 +29,7 @@ static uint8_t idx; // 全局CAN实例索引,每次有新的模块注册会自
static void CANAddFilter(CANInstance *_instance) static void CANAddFilter(CANInstance *_instance)
{ {
CAN_FilterTypeDef can_filter_conf; CAN_FilterTypeDef can_filter_conf;
memset(&can_filter_conf, 0, sizeof(can_filter_conf)); // 防止未初始化字段(如FilterIdHigh等)成为垃圾匹配ID
static uint8_t can1_filter_idx = 0, can2_filter_idx = 14; // 0-13给can1用,14-27给can2用 static uint8_t can1_filter_idx = 0, can2_filter_idx = 14; // 0-13给can1用,14-27给can2用
can_filter_conf.FilterMode = CAN_FILTERMODE_IDLIST; // 使用id list模式,即只有将rxid添加到过滤器中才会接收到,其他报文会被过滤 can_filter_conf.FilterMode = CAN_FILTERMODE_IDLIST; // 使用id list模式,即只有将rxid添加到过滤器中才会接收到,其他报文会被过滤
@@ -105,24 +106,18 @@ CANInstance *CANRegister(CAN_Init_Config_s *config)
/* 如果让CANinstance保存txbuff,会增加一次复制的开销 */ /* 如果让CANinstance保存txbuff,会增加一次复制的开销 */
uint8_t CANTransmit(CANInstance *_instance, float timeout) uint8_t CANTransmit(CANInstance *_instance, float timeout)
{ {
static uint32_t busy_count; (void)timeout; // 保留接口兼容性;改为非阻塞发送,避免阻塞调用它的任务
static volatile float wait_time __attribute__((unused)); // for cancel warning
float dwt_start = DWT_GetTimeline_ms(); // 邮箱全满说明上一帧还没发出去,或总线上没有节点回应ACK(例如断开某一路CAN)。
while (HAL_CAN_GetTxMailboxesFreeLevel(_instance->can_handle) == 0) // 等待邮箱空闲 // 此时直接放弃本帧,保证电机控制等任务的周期稳定,不因一路CAN离线而拖慢另一路CAN。
if (HAL_CAN_GetTxMailboxesFreeLevel(_instance->can_handle) == 0)
{ {
if (DWT_GetTimeline_ms() - dwt_start > timeout) // 超时 return 0;
{
LOGWARNING("[bsp_can] CAN MAILbox full! failed to add msg to mailbox. Cnt [%d]", busy_count);
busy_count++;
return 0;
}
} }
wait_time = DWT_GetTimeline_ms() - dwt_start;
// tx_mailbox会保存实际填入了这一帧消息的邮箱,但是知道是哪个邮箱发的似乎也没啥用 // tx_mailbox会保存实际填入了这一帧消息的邮箱,但是知道是哪个邮箱发的似乎也没啥用
if (HAL_CAN_AddTxMessage(_instance->can_handle, &_instance->txconf, _instance->tx_buff, &_instance->tx_mailbox)) if (HAL_CAN_AddTxMessage(_instance->can_handle, &_instance->txconf, _instance->tx_buff, &_instance->tx_mailbox) != HAL_OK)
{ {
LOGWARNING("[bsp_can] CAN bus BUS! cnt:%d", busy_count);
busy_count++;
return 0; return 0;
} }
return 1; // 发送成功 return 1; // 发送成功

View File

@@ -40,7 +40,7 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
rc_ctrl[TEMP].rc.rocker_l_ = (((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | (sbus_buf[4] << 10)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 2 rc_ctrl[TEMP].rc.rocker_l_ = (((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | (sbus_buf[4] << 10)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 2
rc_ctrl[TEMP].rc.rocker_l1 = (((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 3 rc_ctrl[TEMP].rc.rocker_l1 = (((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 3
rc_ctrl[TEMP].rc.dial = ((sbus_buf[16] | (sbus_buf[17] << 8)) & 0x07FF) - RC_CH_VALUE_OFFSET; rc_ctrl[TEMP].rc.dial = ((sbus_buf[16] | (sbus_buf[17] << 8)) & 0x07FF) - RC_CH_VALUE_OFFSET;
rc_ctrl[TEMP].rc.rocker_l_= rc_ctrl[TEMP].rc.rocker_l_+5.5; rc_ctrl[TEMP].rc.rocker_l_= rc_ctrl[TEMP].rc.rocker_l_+0.5;
// 左侧拨轮 // 左侧拨轮
RectifyRCjoystick(); RectifyRCjoystick();
// 开关,0左1右 // 开关,0左1右