全环节概览 整个控制的流程可以分为三大步骤 :信息传入,数据解算,电流信号输出 接下来将介绍所有环节的细节
信息传入 无线信号发送 DR16遥控器开启并且与其配对的DR16无线接收机也通电时,两者就可以建立无线连接了 此时遥控器和接收机都亮绿灯
串口1通信 无线接收机和单片机的串口1连接,它们进行串口通信时具体采取的协议可以查阅RoboMaster 机器人专用遥控器(接收机)用户手册 得知:
通信参数
数值
波特率
100000bps
单元数据长度
8
校验位
1
校验方式
偶校验
结束位
1
流控
无
为了两者通信正常,我们必须在STM32CubeMX中修改串口1的设置,如图
Word Length项值为9而不是8,因为这一项的值是单位数据长度+校验位,也就是8+1=9
我们战队所使用的板子比较特殊,串口1需要手动绑定TX和RX到PA9和PA10上
数据解包 串口1从无线接收机那里得到的数据是18字节的控制帧数据,需要解包成每个按键,摇杆的数据才能进一步使用好在我们不用自己手搓这一段代码,使用学长写好的解包代码就可以实现这一过程 向项目中新建两个文件dr16.h和dr16.c,并复制以下代码内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 #include "stm32h7xx_hal.h" #include "usart.h" #ifndef DR16_H #define DR16_H #define DR16_rxBufferLengh 18 #define Key_Filter_Num 3 #define ACE_SENSE 0.1f #define ACE_SHACHE 0.003f #define PARK_SENSE 0.0055f #define key_W key[0] #define key_A key[1] #define key_S key[2] #define key_D key[3] #define key_shift key[4] #define key_ctrl key[5] #define key_Q key[6] #define key_E key[7] #define key_V key[8] #define key_F key[9] #define key_G key[10] #define key_C key[11] #define key_R key[12] #define key_B key[13] #define key_Z key[14] #define key_X key[15] #define key_W_flag keyflag[0] #define key_A_flag keyflag[1] #define key_S_flag keyflag[2] #define key_D_flag keyflag[3] #define key_shift_flag keyflag[4] #define key_ctrl_flag keyflag[5] #define key_Q_flag keyflag[6] #define key_E_flag keyflag[7] #define key_V_flag keyflag[8] #define key_F_flag keyflag[9] #define key_G_flag keyflag[10] #define key_C_flag keyflag[11] #define key_R_flag keyflag[12] #define key_B_flag keyflag[13] #define key_Z_flag keyflag[14] #define key_X_flag keyflag[15] #define W_Num 0 #define A_Num 1 #define S_Num 2 #define D_Num 3 #define shift_Num 4 #define ctrl_Num 5 #define Q_Num 6 #define E_Num 7 #define V_Num 8 #define F_Num 9 #define G_Num 10 #define C_Num 11 #define R_Num 12 #define B_Num 13 #define Z_Num 14 #define X_Num 15 typedef struct { struct { uint16_t sw; uint16_t ch0; uint16_t ch1; uint16_t ch2; uint16_t ch3; uint8_t s1; uint8_t s2; uint8_t s1_last; uint8_t s2_last; }rc; struct { int16_t x; int16_t y; int16_t z; uint8_t press_l; uint8_t press_r; uint8_t press_l_flag; uint8_t press_r_flag; }mouse; struct { uint16_t v; }keyboard; uint8_t key[18 ]; uint8_t keyflag[18 ]; uint32_t key_filter_cnt[18 ]; uint8_t isUnpackaging; uint8_t isOnline; uint32_t onlineCheckCnt; int16_t OneShoot; int16_t ThreeShoot; uint16_t c; uint16_t d; float Chassis_Y_Integ; float Chassis_X_Integ; int8_t ShootNumber; uint8_t ShootNumberCut; uint8_t Cruise_Mode; uint8_t ch0_PushedFlag; uint8_t ch1_PushedFlag; }RC_Ctrl; uint8_t DR16_callback (uint8_t * recBuffer, uint16_t len) ;void DR16Init (RC_Ctrl* RC_Ctl) ;void DR16_DataUnpack (RC_Ctrl* rc_ctrl, uint8_t * recBuffer) ;void PC_keybroad_filter (RC_Ctrl* RC_Ctl) ;extern int Check_receiver;extern RC_Ctrl rc_Ctrl;#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 #include "dr16.h" #include <stdlib.h> RC_Ctrl rc_Ctrl={ .isUnpackaging = 0 , .isOnline = 0 };uint8_t DR16_recData[DR16_rxBufferLengh];int Check_receiver=31 ;void DR16Init (RC_Ctrl* RC_Ctl) { RC_Ctl->rc.ch0=1024 ; RC_Ctl->rc.ch1=1024 ; RC_Ctl->rc.ch2=1024 ; RC_Ctl->rc.ch3=1024 ; RC_Ctl->rc.s1=3 ; RC_Ctl->rc.s2=3 ; RC_Ctl->rc.sw=1024 ; RC_Ctl->mouse.x=0 ; RC_Ctl->mouse.y=0 ; RC_Ctl->mouse.z=0 ; RC_Ctl->key_Q_flag=0 ; RC_Ctl->key_E_flag=0 ; RC_Ctl->key_R_flag=0 ; RC_Ctl->key_F_flag=0 ; RC_Ctl->key_G_flag=0 ; RC_Ctl->key_Z_flag=0 ; RC_Ctl->key_X_flag=0 ; RC_Ctl->key_C_flag=0 ; RC_Ctl->key_V_flag=0 ; RC_Ctl->key_B_flag=0 ; RC_Ctl->key_ctrl_flag=0 ; RC_Ctl->Chassis_Y_Integ=0 ; RC_Ctl->Chassis_X_Integ=0 ; RC_Ctl->ShootNumber=1 ; RC_Ctl->Cruise_Mode = 0 ; }uint8_t correct_num=0 ;void DR16_DataUnpack (RC_Ctrl* rc_ctrl, uint8_t * recBuffer) { rc_ctrl->isUnpackaging = 1 ; correct_num=0 ; if (((recBuffer[0 ] | (recBuffer[1 ] << 8 )) & 0x07ff )<=1684 && ((recBuffer[0 ] | (recBuffer[1 ] << 8 )) & 0x07ff )>=364 ) correct_num++; if ((((recBuffer[1 ] >> 3 ) | (recBuffer[2 ] << 5 )) & 0x07ff )<=1684 && (((recBuffer[1 ] >> 3 ) | (recBuffer[2 ] << 5 )) & 0x07ff )>=364 ) correct_num++; if ((((recBuffer[2 ] >> 6 ) | (recBuffer[3 ] << 2 ) |(recBuffer[4 ] << 10 )) & 0x07ff )<=1684 && (((recBuffer[2 ] >> 6 ) | (recBuffer[3 ] << 2 ) |(recBuffer[4 ] << 10 )) & 0x07ff )>=364 ) correct_num++; if ((((recBuffer[4 ] >> 1 ) | (recBuffer[5 ] << 7 )) & 0x07ff )<=1684 && (((recBuffer[4 ] >> 1 ) | (recBuffer[5 ] << 7 )) & 0x07ff )>=364 ) correct_num++; if ((((recBuffer[5 ] >> 4 )& 0x000C ) >> 2 )==1 || (((recBuffer[5 ] >> 4 )& 0x000C ) >> 2 )==2 || (((recBuffer[5 ] >> 4 )& 0x000C ) >> 2 )==3 ) correct_num++; if (((recBuffer[5 ] >> 4 )& 0x0003 )==1 || ((recBuffer[5 ] >> 4 )& 0x0003 )==2 || ((recBuffer[5 ] >> 4 )& 0x0003 )==3 ) correct_num++; if (correct_num==6 ) { rc_ctrl->rc.ch0 = (recBuffer[0 ]| (recBuffer[1 ] << 8 )) & 0x07ff ; rc_ctrl->rc.ch1 = ((recBuffer[1 ] >> 3 ) | (recBuffer[2 ] << 5 )) & 0x07ff ; rc_ctrl->rc.ch2 = ((recBuffer[2 ] >> 6 ) | (recBuffer[3 ] << 2 ) |(recBuffer[4 ] << 10 )) & 0x07ff ; rc_ctrl->rc.ch3 = ((recBuffer[4 ] >> 1 ) | (recBuffer[5 ] << 7 )) & 0x07ff ; rc_ctrl->rc.s1 = ((recBuffer[5 ] >> 4 )& 0x000C ) >> 2 ; rc_ctrl->rc.s2 = ((recBuffer[5 ] >> 4 )& 0x0003 ); rc_ctrl->rc.sw=(uint16_t )(recBuffer[16 ]|(recBuffer[17 ]<<8 ))&0x7ff ; if ((rc_ctrl->rc.ch0>1020 )&&(rc_ctrl->rc.ch0<1028 )) rc_ctrl->rc.ch0=1024 ; if ((rc_ctrl->rc.ch1>1020 )&&(rc_ctrl->rc.ch1<1028 )) rc_ctrl->rc.ch1=1024 ; if ((rc_ctrl->rc.ch2>1020 )&&(rc_ctrl->rc.ch2<1028 )) rc_ctrl->rc.ch2=1024 ; if ((rc_ctrl->rc.ch3>1020 )&&(rc_ctrl->rc.ch3<1028 )) rc_ctrl->rc.ch3=1024 ; rc_ctrl->mouse.x = recBuffer[6 ] | (recBuffer[7 ] << 8 ); rc_ctrl->mouse.y = recBuffer[8 ] | (recBuffer[9 ] << 8 ); rc_ctrl->mouse.z = recBuffer[10 ] | (recBuffer[11 ] << 8 ); rc_ctrl->mouse.press_l = recBuffer[12 ]; rc_ctrl->mouse.press_r = recBuffer[13 ]; if (rc_ctrl->mouse.x>25000 ) rc_ctrl->mouse.x=25000 ; if (rc_ctrl->mouse.x<-25000 ) rc_ctrl->mouse.x=-25000 ; if (rc_ctrl->mouse.y>25000 ) rc_ctrl->mouse.y=25000 ; if (rc_ctrl->mouse.y<-25000 ) rc_ctrl->mouse.y=-25000 ; rc_ctrl->keyboard.v = recBuffer[14 ]| (recBuffer[15 ] << 8 ); rc_ctrl->key_W=recBuffer[14 ]&0x01 ; rc_ctrl->key_S=(recBuffer[14 ]>>1 )&0x01 ; rc_ctrl->key_A=(recBuffer[14 ]>>2 )&0x01 ; rc_ctrl->key_D=(recBuffer[14 ]>>3 )&0x01 ; rc_ctrl->key_B=(recBuffer[15 ]>>7 )&0x01 ; rc_ctrl->key_V=(recBuffer[15 ]>>6 )&0x01 ; rc_ctrl->key_C=(recBuffer[15 ]>>5 )&0x01 ; rc_ctrl->key_X=(recBuffer[15 ]>>4 )&0x01 ; rc_ctrl->key_Z=(recBuffer[15 ]>>3 )&0x01 ; rc_ctrl->key_G=(recBuffer[15 ]>>2 )&0x01 ; rc_ctrl->key_F=(recBuffer[15 ]>>1 )&0x01 ; rc_ctrl->key_R=(recBuffer[15 ])&0x01 ; rc_ctrl->key_E=(recBuffer[14 ]>>7 )&0x01 ; rc_ctrl->key_Q=(recBuffer[14 ]>>6 )&0x01 ; rc_ctrl->key_ctrl=(recBuffer[14 ]>>5 )&0x01 ; rc_ctrl->key_shift=(recBuffer[14 ]>>4 )&0x01 ; Check_receiver = 0 ; } else {} rc_ctrl->isUnpackaging = 0 ; }
接收到数据时就可以进行数据的解包了,所以在串口接收中断回调函数 中调用解包函数DR16_DataUnpack()就可以把数据存储到结构体rc_Ctrl中了
1 2 3 4 5 6 7 8 9 10 extern RC_Ctrl rc_Ctrl;void HAL_UART_RxCpltCallback (UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { DR16_DataUnpack(&rc_Ctrl,dr16_rxbuffer); HAL_UART_Receive_DMA(huart,dr16_rxbuffer,sizeof (dr16_rxbuffer)); } }
不能忘记写防疯车代码 如果遥控器离线,那么串口1中接收到的数据可能会异常,严重时会导致”疯车”,也就是机器不受控制乱跑的情况 为了防止这种情况,我们引入Check_receiver来计算遥控器的延迟,如果延迟过高(比如超过了30ms)就判断车辆离线,此时立即将电机速度置0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef *htim) { if (Check_receiver > 100 ) Check_receiver = 100 ; if (Check_receiver>30 ) rc_Ctrl.isOnline = 0 ; else rc_Ctrl.isOnline = 1 ; if (rc_Ctrl.isOnline){ } Check_receiver++; }
数据解算 我们已经得到了摇杆数据,但是我们要发送给电机的是四个电机的电流数据,因此我们还需要对数据进行多层处理
摇杆数据换算 摇杆的位置数据是存储在rc_Ctrl.rc.chN,其中N取0,1,2,3 四个通道对应哪一个摇杆自行看用户手册 的配图 通道的数值值域为[364,1684],以ch0为例换算目标速度: 首先算出通道的比值(满油门的百分比,只不过没有百分号)
1 ch0_pct = ((int32_t )rc_Ctrl.rc.ch0-1024.0 )/(1684.0 -364 )*2 ;
然后将百分比乘以设计的最大速度,就得到目标速度 比如这里假设ch0映射目标角速度的大小,那么
同理,可以得到底盘运动的目标平动速度(vx,vy)
麦克拉姆轮运动学逆解算 上一步得到了底盘的目标速度(vx, vy, w),接下来我们将计算四个麦克拉姆轮各自的目标转速
麦轮长这样: 这种轮子可以实现侧向的运动 麦轮底盘中麦轮的组装方式也有讲究,CUBOT中采取图中的组装方式: 图中数字表示驱动对应麦轮的电机的CAN ID
以1号麦轮为例,记该麦轮的动力方向为m,无滚动方向为n,并作出以下假设:
自由轮滚动方向可以无阻力地跟随底盘的运动
自由轮的轴向方向不会打滑
为了得到1号麦轮的转速(蓝色向量m),我们可以根据下面的步骤来计算:
由底盘的平动速度和角速度(红色向量)计算出1号麦轮的平动方向(紫色向量的矢量和)
由1号麦轮的平动方向(紫色向量的矢量和)向无滚动方向的方向向量投影,得到由电机驱动的速度(蓝色向量n)
由n方向速度的大小反向解出m方向速度的大小
号 麦 轮 的 运 动 分 向 量 显 然 等 于 底 盘 平 动 速 度
而 运 动 分 向 量 其 中 为 底 盘 中 心 点 的 坐 标
因 此 合
方 向 上 速 度 的 大 小 合
而
又 号 电 机 的 转 动 正 方 向 和 相 反 号 的 是 相 同 方 向 的
同 理 可 得 的 表 达 式
得出四轮的速度表达式后可以发现,所有式子都包含+(b-a)w,因此不妨去掉(b-a)
1 2 3 4 5 6 7 8 vx *= vxy_MAX; vy *= vxy_MAX; w *= w_MAX;float v1 = vx + vy + w ;float v2 = -vx + vy + w ; float v3 = -vx - vy + w ; float v4 = vx - vy + w ;
PID 速度环控制 由于我们得到的是电机的转速,这和我们需要的电机电流不是一个量 这里用之前我们写的pid来实时控制电机电流 将求得的速度作为pid的目标值传入,pid控制对象就可以返回所需的输出量了
1 2 3 4 5 6 7 8 9 10 PID_target(&motor_speed_pid_1, v1*(3591.0f /187.0f )); PID_target(&motor_speed_pid_2, v2*(3591.0f /187.0f )); PID_target(&motor_speed_pid_3, v3*(3591.0f /187.0f )); PID_target(&motor_speed_pid_4, v4*(3591.0f /187.0f ));float m1 = PID_Calc(&motor_speed_pid_1,v1,0 );float m2 = PID_Calc(&motor_speed_pid_2,v2,0 );float m3 = PID_Calc(&motor_speed_pid_3,v3,0 );float m4 = PID_Calc(&motor_speed_pid_4,v4,0 );
电流信号输出 CAN通信控制电机 有了以前的CAN通信的经验,我们打开对应的can口并发送电流数据即可
1 CAN_Send(&hfdcan2,m1,m2,m3,m4)
代码汇总
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef DIPAN_H #define DIPAN_H #include "main.h" #include "motor.h" #include "pid.h" void Dipan_Init (void ) ;uint8_t Dipan_Mecanum (float vx, float vy, float w) ;#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include "dipan.h" const uint16_t vxy_MAX = 1000 ;const uint16_t w_MAX = 500 ; PID motor_speed_pid_1; PID motor_speed_pid_2; PID motor_speed_pid_3; PID motor_speed_pid_4;void Dipan_Init (void ) { PID_Init(&motor_speed_pid_1,0.6f ,0.4f ,0.0f , 0 ,1000 ,16384 ,500.0f ,600 ); PID_Init(&motor_speed_pid_2,0.6f ,0.4f ,0.0f , 0 ,1000 ,16384 ,500.0f ,600 ); PID_Init(&motor_speed_pid_3,0.6f ,0.4f ,0.0f , 0 ,1000 ,16384 ,500.0f ,600 ); PID_Init(&motor_speed_pid_4,0.6f ,0.4f ,0.0f , 0 ,1000 ,16384 ,500.0f ,600 ); }uint8_t Dipan_Mecanum (float vx, float vy, float w) { vx *= vxy_MAX; vy *= vxy_MAX; w *= w_MAX; float v1 = vx + vy + w ; float v2 = -vx + vy + w ; float v3 = -vx - vy + w ; float v4 = vx - vy + w ; PID_target(&motor_speed_pid_1, v1*(3591.0f /187.0f )); PID_target(&motor_speed_pid_2, v2*(3591.0f /187.0f )); PID_target(&motor_speed_pid_3, v3*(3591.0f /187.0f )); PID_target(&motor_speed_pid_4, v4*(3591.0f /187.0f )); float m1 = PID_Calc(&motor_speed_pid_1,v1,0 ); float m2 = PID_Calc(&motor_speed_pid_2,v2,0 ); float m3 = PID_Calc(&motor_speed_pid_3,v3,0 ); float m4 = PID_Calc(&motor_speed_pid_4,v4,0 ); return CAN_Send(&hfdcan2,m1,m2,m3,m4); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef *htim) { if (Check_receiver > 100 ) Check_receiver = 100 ; if (Check_receiver>30 ) rc_Ctrl.isOnline = 0 ; else rc_Ctrl.isOnline = 1 ; if (rc_Ctrl.isOnline){ float vx,vy,w; vx = ((int32_t )rc_Ctrl.rc.ch2-1024.0 )/(1684.0 -364 )*2 ; vy = ((int32_t )rc_Ctrl.rc.ch3-1024.0 )/(1684.0 -364 )*2 ; w = ((int32_t )rc_Ctrl.rc.ch0-1024.0 )/(1684.0 -364 )*2 ; Dipan_Mecanum( vx, vy, w); } else { Dipan_Mecanum(0 ,0 ,0 ); } Check_receiver++; }
—END— 参考:【中科大RM电控合集】各种底盘各种解算一网打尽 附件:RoboMaster 机器人专用遥控器(接收机)用户手册