添加了can的发送超时控制,添加了所有application层的文档和注释

This commit is contained in:
NeoZng 2023-01-03 22:53:49 +08:00
parent f37d813bcd
commit 48370d4411
16 changed files with 160 additions and 40 deletions

View File

@ -2,6 +2,49 @@
<p align='right'>neozng1@hnu.edu.cn</p>
## 通信机制
**应用之间不应该有任何包含关系,它们必须是平行工作的。**而这通过pub-sub的机制实现。module层提供了`message_center`模块,支持发布订阅者的消息订阅机制。以传统的框架为例,负责整车控制的应用和其他应用(或任务)是从属的树状结构,或不同的任务和应用之间通过全局变量传递消息(**请不要使用全局变量!**),而此框架下的不同应用是并行的关系。
如果一个应用希望获取另一个应用的数据,那么他应该**订阅**由此此应用发布的话题(事件)。一个应用要把自己希望共享的数据,注册到消息中心,即**发布**。为了区别不同的消息来源(你希望订阅谁的消息?哪一个消息?),可以通过**话题名**进行订阅。也就是说,消息中心作为第三方,管理所有的消息发布者和订阅者,它像报刊亭一样对消息进行中转,使得不同的应用之间不需要包含彼此,更不用全局变量也能共享消息。
> 更多关于发布-订阅的实现,请参考`modules/message_center`下的文档。
## robot_cmd
机器人命令模块是对整个机器人的抽象,对于单板控制整车的情况,
机器人命令模块是对整个机器人的抽象,对于单板控制整车的情况,该应用应该包含接收控制指令的模块,例如遥控器、视觉通信模块。该模块会处理接收到的控制数据,并将其转化为**具体的、定量的**控制信息,发送给其他模块。
如从遥控器获知当前右侧摇杆拨向上方则将遥控器发来的数值转化为底盘前进的速度值然后发送给其他应用。同时robot_cmd还要从其他应用获取反馈信息做出其他决策。可以将其视为整个机器人的**大脑**。
## gimbal
以步兵为例云台应用应当包含两个电机分别用于驱动yaw和pitch轴还有一个imu开发板一般放在云台上。gimbal模块会接收robot_cmd发来的控制信息云台的角度、转速等并通过电机提供的接口完成电机的参考值设定。gimbal还要把imu的数据反馈给cmd用于和视觉的通信以及云台状态的判断。
## shoot
还是以步兵为例发射应用应当包括摩擦轮电机、拨盘电机和弹舱盖。根据cmd应用发来的控制信息决定当前的发射模式单发、双发、连发弹舱盖的开合以及射速151830等。
## chassis
以步兵为例底盘应该包括4个电机。根据cmd应用发来的控制信息进行麦克纳姆轮的运动学解算从而获知四个电机需要的设定值然后调用电机提供的接口进行设定。chassis还要根据电机的反馈数据以及imu信息如果有imu的话即双板的情况云台一个底盘一个计算底盘的实际运动状态反馈给robot_cmd应用。
## lift
以工程机器人为例抬升机构应该包含用于抬升的执行单元可能是气缸、电磁阀、电机、点推杆等根据cmd发来的数据控制执行单元运行到特定的高度并进行必要的反馈。
## 双板兼容
此框架对单开发板/双开发板/多开发板的情况都提供了支持(多板一般只在工程机器人上出现,需要自己编写),目前通过条件编译实现了对单双板的切换。使用双板时,主控板在云台上,连接遥控器和上位机;副板在底盘上,负责底盘的运动控制和与裁判系统的通信。

View File

@ -0,0 +1,19 @@
# chassis
## 工作流程
首先进行初始化,`ChasissInit()`会被`RobotInit()`调用进行裁判系统、底盘电机的初始化。如果为双板模式则还会初始化IMU并且将消息订阅者和发布者的初始化改为`CANComm`的初始化。
操作系统启动后,工作顺序为:
1. 从cmd模块获取数据如果双板则从CANComm获取
2. 判断当前控制数据的模式,如果为停止则停止所有电机
3. 根据控制数据,计算底盘的旋转速度
4. 根据控制数据中yaw电机的编码器值`angle_offset`,将控制数据映射到底盘坐标系下
5. 进行麦克纳姆轮的运动学解算,得到每个电机的设定值
6. 获取裁判系统的数据,并根据底盘功率限制对输出进行限幅
7. 由电机的反馈数据和IMU如果有计算底盘当前的真实运动速度
8. 设置底盘反馈数据,包括运动速度和裁判系统数据
9. 将反馈数据推送到消息中心如果双板则通过CANComm发送

View File

@ -4,21 +4,43 @@
## 运行流程
运行流程可以很直观的从`RobotCMDTask()`中看出。首先通过消息订阅机制,获取其他应用的反馈信息,然后使用`CalcOffsetAngle()`计算底盘和云台的offset angle使得底盘始终获知当前的正方向接着根据当前是通过键鼠or遥控器控制调用对应的函数将控制指令量化为具体的控制信息得到控制信息之后先不急着发布而是检测重要的模块和应用是否掉线或出现异常以及遥控器是否进入紧急停止模式如果以上情况发生那么将所有的控制信息都置零即让电机和其他执行单元保持静止。最后还是通过pubsub机制把具体的控制信息发布到对应话题让其他应用获取。
运行流程可以很直观的从`RobotCMDTask()`中看出。
1. 首先通过消息订阅机制,获取其他应用的反馈信息
2. 使用`CalcOffsetAngle()`计算底盘和云台的offset angle使得底盘始终获知当前的正方向
3. 接着根据当前是通过键鼠or遥控器控制调用对应的函数将控制指令量化为具体的控制信息
4. 得到控制信息之后,先不急着发布,而是检测重要的模块和应用是否掉线或出现异常,以及遥控器是否进入紧急停止模式,如果以上情况发生,那么将所有的控制信息都置零,即让电机和其他执行单元保持静止。
5. 最后通过pubsub机制把具体的控制信息发布到对应话题让其他应用获取。若为双板则将原本要推送给底盘的信息通过CANComm进行发送。
## 外部接口
## 私有函数和变量
### 遥控器控制模式:
拨轮向下打到底进入紧急停止模式(后续改为关闭遥控器停止,利用daemon);拨轮向上打开启摩擦轮,超过一半开始发射(速度环,连发)
左侧开关:
- 上:键鼠控制
- 中:视觉控制(没有识别到目标的时候仍然可以使用遥控器控制云台)
- 下:遥控器控制
右侧开关:
- 上:弹舱开
- 中:底盘云台分离(底盘不旋转,全向移动)
- 下:底盘跟随云台
### 键鼠控制模式:
遥控器左侧开关拨到最上方,进入键鼠控制模式,此时不会响应遥控器遥感和拨轮的输入.
前后左右:WSAD
开关弹舱盖:R
小陀螺:Q
发射:鼠标左键
自瞄:鼠标右键

View File

@ -0,0 +1,13 @@
# gimbal
## 工作流程
初始化pitch和yaw电机以及一个imu。订阅gimbal_cmd消息来自robot_cmd并发布gimbal_feed话题。
1. 从消息中心获取gimbal_cmd话题的消息
2. 根据消息中的控制模式进行模式切换,如果急停则关闭所有电机
3. 由设定的模式,进行电机反馈数据来源的切换,修改反馈数据指针,设置前馈控制数据指针等。
4. 设置反馈数据包括yaw电机的绝对角度和imu数据
5. 推送反馈数据到gimbal_feed话题下

View File

@ -0,0 +1,15 @@
# shoot
## 工作流程
初始化3个电机和一个舵机包括发射的2个m3508拨盘的m2006和弹仓盖上的舵机双开门舵机可能要换成2个。m2006初始化时设置为速度闭环防止上电乱转。订阅shoot_cmd话题robot_cmd发布的并发布shoot_feed话题robot_cmd会订阅
1. 从shoot_cmd获取消息
2. 根据工作模式确定是否急停
3. 如果之前是单发模式或3发模式并且冷却时间没到直接结束本次任务等待下一次进入
4. 如果已经冷却完成根据发来的拨盘模式设定m2006的闭环类型和参考值
5. 根据发来的弹速数据,设定摩擦轮的参考值
6. 根据发来的弹舱数据进行开合
7. 设定反馈数据推送到shoot_feed话题

View File

@ -2,6 +2,7 @@
#include "main.h"
#include "memory.h"
#include "stdlib.h"
#include "bsp_dwt.h"
/* can instance ptrs storage, used for recv callback */
// 在CAN产生接收中断会遍历数组,选出hcan和rxid与发生中断的实例相同的那个,调用其回调函数
@ -88,12 +89,15 @@ CANInstance *CANRegister(CAN_Init_Config_s *config)
/* @todo 目前似乎封装过度,应该添加一个指向tx_buff的指针,tx_buff不应该由CAN instance保存 */
/* 如果让CANinstance保存txbuff,会增加一次复制的开销 */
void CANTransmit(CANInstance *_instance)
uint8_t CANTransmit(CANInstance *_instance,uint8_t timeout)
{
float dwt_start = DWT_GetTimeline_ms();
while (HAL_CAN_GetTxMailboxesFreeLevel(_instance->can_handle) == 0) // 等待邮箱空闲
;
if (DWT_GetTimeline_ms() - dwt_start > timeout) // 超时
return 0;
// tx_mailbox会保存实际填入了这一帧消息的邮箱,但是知道是哪个邮箱发的似乎也没啥用
HAL_CAN_AddTxMessage(_instance->can_handle, &_instance->txconf, _instance->tx_buff, &_instance->tx_mailbox);
return 1; // 发送成功
}
void CANSetDLC(CANInstance *_instance, uint8_t length)

View File

@ -56,9 +56,10 @@ void CANSetDLC(CANInstance *_instance, uint8_t length);
/**
* @brief transmit mesg through CAN device,can实例发送消息
* CAN实例的tx_buff写入发送数据
*
*
* @param timeout ,ms;us,
* @param _instance* can instance owned by module
*/
void CANTransmit(CANInstance *_instance);
uint8_t CANTransmit(CANInstance *_instance,uint8_t timeout);
#endif

View File

@ -4,8 +4,7 @@
> TODO:
>
> 1. 增加数据帧的长度定义使得收发更加灵活而不是固定的8 bytes
> 2. 增加自动检测ID冲突的log输出。
> 1. 增加自动检测ID冲突的log输出。
## 使用说明
@ -65,7 +64,7 @@ typedef void (*can_callback)(can_instance*);
```c
void CANRegister(can_instance* instance, can_instance_config config);
void CANTransmit(can_instance* _instance);
uint8_t CANTransmit(can_instance* _instance, uint8_t timeout);
```
`CANRegister`是用于初始化CAN实例的接口module层的模块对象也应当为一个结构体内要包含一个`usart_instance`。调用时传入实例指针以及用于初始化的config。`CANRegister`应当在module的初始化函数内被调用推荐config采用以下的方式定义更加直观明了
@ -103,4 +102,10 @@ void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
- `HAL_CAN_RxFifo0MsgPendingCallback()`和`HAL_CAN_RxFifo1MsgPendingCallback()`都是对HAL的CAN回调函数的重定义原本的callback是`__week`修饰的弱定义当发生FIFO0或FIFO1有新消息到达的时候对应的callback会被调用。`CANFIFOxCallback()`随后被前两者调用并根据接收id和硬件中断来源哪一个CAN硬件CAN1还是CAN2调用对应的instance的回调函数进行协议解析。
- 当有一个模块注册了多个can实例时通过`CANInstance.id`,使用强制类型转换将其转换成对应模块的实例指针,就可以对不同的模块实例进行回调处理了。
- 当有一个模块注册了多个can实例时通过`CANInstance.id`,使用强制类型转换将其转换成对应模块的实例指针,就可以对不同的模块实例进行回调处理了。
## 注意事项
由于CAN总线自带发送检测如果总线上没有挂载目标设备接收id和发送报文相同的设备那么CAN邮箱会被占满而无法发送。在`CANTransmit()`中会对CAN邮箱是否已满进行`while(1)`检查。当超出`timeout`之后函数会返回零,说明发送失败。
由于卡在`while(1)`处不断检查邮箱是否空闲,调用`CANTransmit()`的任务可能无法按时挂起导致任务定时不精确。建议在没有连接CAN进行调试时按需注释掉有关CAN发送的代码部分或设定一个较小的`timeout`值,防止对其他需要精确定时的任务产生影响。

View File

@ -109,7 +109,7 @@ void CANCommSend(CANCommInstance *instance, uint8_t *data)
send_len = instance->send_buf_len - i >= 8 ? 8 : instance->send_buf_len - i;
CANSetDLC(instance->can_ins, send_len);
memcpy(instance->can_ins->tx_buff, instance->raw_sendbuf + i, send_len);
CANTransmit(instance->can_ins);
CANTransmit(instance->can_ins,1);
}
}

View File

@ -288,7 +288,7 @@ void DJIMotorControl()
{
if (sender_enable_flag[i])
{
CANTransmit(&sender_assignment[i]);
CANTransmit(&sender_assignment[i],1);
}
}
}

View File

@ -16,7 +16,7 @@ static void HTMotorSetMode(HTMotor_Mode_t cmd, HTMotorInstance *motor)
static uint8_t buf[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
buf[7] = (uint8_t)cmd;
memcpy(motor->motor_can_instace->tx_buff, buf, sizeof(buf));
CANTransmit(motor->motor_can_instace);
CANTransmit(motor->motor_can_instace,1);
}
/* 两个用于将uint值和float值进行映射的函数,在设定发送值和解析反馈值时使用 */
@ -144,7 +144,7 @@ void HTMotorControl()
{ // 若该电机处于停止状态,直接将发送buff置零
memset(motor_can->tx_buff + 6, 0, sizeof(uint16_t));
}
CANTransmit(motor_can);
CANTransmit(motor_can,1);
}
}

View File

@ -117,7 +117,7 @@ void LKMotorControl()
if (idx) // 如果有电机注册了
{
CANTransmit(sender_instance);
CANTransmit(sender_instance,1);
}
}

View File

@ -29,9 +29,9 @@ typedef enum
ANGLE_LOOP = 0b0100,
// only for checking
_ = 0b0011,
__ = 0b0110,
___ = 0b0111
SPEED_AND_CURRENT_LOOP = 0b0011,
ANGLE_AND_SPEED_LOOP = 0b0110,
ALL_THREE_LOOP = 0b0111
} Closeloop_Type_e;
typedef enum
@ -80,7 +80,6 @@ typedef struct
{
float *other_angle_feedback_ptr; // 其他反馈来源的反馈数据指针
float *other_speed_feedback_ptr;
// float *angle_foward_ptr; //前馈数据指针
// float *speed_foward_ptr;
// float *current_foward_ptr;

View File

@ -71,14 +71,14 @@ remote_control
拨轮向下打到底进入紧急停止模式;拨轮向上打开启摩擦轮,超过一半开始发射(速度环,连发)
拨轮向下打到底进入紧急停止模式(后续改为关闭遥控器停止,利用daemon);拨轮向上打开启摩擦轮,超过一半开始发射(速度环,连发)
左侧开关`s[1]`:
左侧开关:
- 上:键鼠控制
- 中:视觉控制(没有识别到目标的时候仍然可以使用遥控器控制云台)
- 下:遥控器控制
右侧开关`s[0]`:
右侧开关:
- 上:弹舱开
- 中:底盘云台分离(底盘不旋转,全向移动)
- 下:底盘跟随云台

View File

@ -11,7 +11,7 @@ static RC_ctrl_t rc_ctrl[2]; //[0]:当前数据TEMP,[1]:上一次的数据LAST.
static USARTInstance *rc_usart_instance;
/**
* @brief
* @brief ,660-660,0
*
*/
static void RectifyRCjoystick()
@ -31,7 +31,7 @@ static void RectifyRCjoystick()
*/
static void sbus_to_rc(const uint8_t *sbus_buf)
{
memcpy(&rc_ctrl[1], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据
memcpy(&rc_ctrl[1], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断
// 摇杆,直接解算时减去偏置
rc_ctrl[TEMP].rc.rocker_r_ = ((sbus_buf[0] | (sbus_buf[1] << 8)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 0
rc_ctrl[TEMP].rc.rocker_r1 = (((sbus_buf[1] >> 3) | (sbus_buf[2] << 5)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 1
@ -40,8 +40,8 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
rc_ctrl[TEMP].rc.dial = ((sbus_buf[16] | (sbus_buf[17] << 8)) & 0x07FF) - RC_CH_VALUE_OFFSET; // 左侧拨轮
RectifyRCjoystick();
// 开关,0左1右
rc_ctrl[TEMP].rc.switch_right = ((sbus_buf[5] >> 4) & 0x0003); //!< Switch left
rc_ctrl[TEMP].rc.switch_left = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch right
rc_ctrl[TEMP].rc.switch_right = ((sbus_buf[5] >> 4) & 0x0003); //!< Switch right
rc_ctrl[TEMP].rc.switch_left = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch left
// 鼠标解析
rc_ctrl[TEMP].mouse.x = sbus_buf[6] | (sbus_buf[7] << 8); //!< Mouse X axis
@ -81,8 +81,7 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
/**
* @brief protocol resolve callback
* this func would be called when usart3 idle interrupt happens
* sbus_to_rc的简单封装
*
* sbus_to_rc的简单封装,bsp_usart的回调函数中
*/
static void RemoteControlRxCallback()
{

View File

@ -35,7 +35,7 @@ SuperCapInstance *SuperCapInit(SuperCap_Init_Config_s *supercap_config)
void SuperCapSend(SuperCapInstance *instance, uint8_t *data)
{
memcpy(instance->can_ins->tx_buff, data, 8);
CANTransmit(instance->can_ins);
CANTransmit(instance->can_ins,1);
}
SuperCap_Msg_s SuperCapGet(SuperCapInstance *instance)