# ps2手柄遥控车(基于freertos) **Repository Path**: chiplord/test ## Basic Information - **Project Name**: ps2手柄遥控车(基于freertos) - **Description**: 一个普通的遥控小车开源 - **Primary Language**: C/C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 0 - **Created**: 2022-03-01 - **Last Updated**: 2024-05-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于freertos的闭环控制遥控小车 #### 介绍 基于freertos的闭环控制遥控小车 #### 软件架构 基于stm32HAL库编写及移植,MCU为f407vet6 采用了freertos作为框架,便于后期增加功能,同时将闭环调速与主程序分离,并将ps2信号接收置于最高优先级。一方面可以灵活控制每层的运行周期,另一方面避免低优先级程序阻塞导致整个程序运行受到影响。 ##### 后续预计增加功能 预计会将云台的控制加入,采用freertos延迟中断形式 ##### 更新 1.UART不定长收发,基于IDLE中断帧判断。(与树莓派进行通信进行符文模块追踪,得到2位16进制模拟值(更新至stm32f4xxit.h与stm32f4xxit.c中 2.增加了控制电调及云台舵机的定时器控制,以及角度环PID计算(更新至stm32f4xxit.h与stm32f4xxit.c中 #### 说明: DMA不定长收发及云台舵机pid ​ ![IDLE](IDLE.png) ​ IDLE中断实现DMA不定长收发原理 对IDLE中断使能并开启串口dma接收 ``` c void USRT_DMA_IDLE_Start() { __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能IDLE中断 HAL_UART_Receive_DMA(&huart1,Uart1RxBuff,UART_DMA_BUFF_LEN_MAX); //开启DMA接收 } ``` DMA中断回调 ``` c void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ UART_Rx_Callback(&huart1); /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ } ``` 通过IDLE中断帧判断,如果未触发IDLE中断,则继续向缓存下一位写入 ``` c void UART_Rx_Callback(UART_HandleTypeDef *huart) { //判断IDLE中断 if(!__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)) { return; } //清除IDLE中断--直接读取SR、DR寄存器即可清除IDLE中断 __HAL_UART_CLEAR_IDLEFLAG(huart); // temp = huart->Instance->SR; // temp = huart->Instance->DR; UART1_Rx_IDLE_Callback(); } ``` 当IDLE中断触发后,进入IDLE回调,完成一帧的接收 ``` c void UART1_Rx_IDLE_Callback() { //暂停DMA HAL_UART_DMAStop(&huart1); //获取DMA缓存区剩余长度 //实际长度 = 总长度-剩余长度 Uart1RxBuffLen = UART_DMA_BUFF_LEN_MAX-__HAL_DMA_GET_COUNTER(&hdma_usart1_rx); //回发测试 HAL_UART_Transmit(&huart1,Uart1RxBuff,Uart1RxBuffLen,100); if(state==1){ if(Uart1RxBuff[1]>17) AngleV+=Angle_PID_P( 128-Uart1RxBuff[1]); if(Uart1RxBuff[0]>17) AngleP+=Angle_PID_V(128-Uart1RxBuff[0]); } //开始DMA接收 HAL_UART_Receive_DMA(&huart1,Uart1RxBuff,UART_DMA_BUFF_LEN_MAX); } //说明持续更新中... ``` #### 使用说明:编码器及ps手柄 可调用函数分别封装在两个库中,ps2.h中封装了ps通信用函数,encoder.h中封装了包含闭环控制、编码器更新以及模拟值转化为电机pwm的函数。 关于encoder.h的部分代码 ```c int WHEEL_SPD_UPD_A(void){ int Spd=(int)(int16_t)__HAL_TIM_GetCounter(&htim3)-10000;//由于类型转换太麻烦就直接设定10000初始值,减掉(计数器默认返回无符号整形) return 60*25*Spd/1320; }//编码器线数11,减速比1:30,采用ti1ti2双边沿触发计数,计数周期40ms,得到结果为每分钟转速 ``` ![官方对于ti1ti2双边沿计数的示例,相当于11*30*4](image.png) 官方对于ti1ti2双边沿计数的示例,相当于11*30*4个脉冲每圈 关于pid闭环计算 ```c int Incremental_PID_calc_A(int spdw,int spdt){//spdw:期望速度,spdt:实际速度 static int bias=0, last_bias=0, prev_bias=0; bias=spdt-spdw; double P=kp*(bias-last_bias); double I=ki*bias; double D=kd*(bias-2*last_bias+prev_bias); prev_bias=last_bias; last_bias=bias; static int PID;PID-=(int)(P+I+D); if(PID>1000)//pid限幅 PID=1000; if(PID<0) PID=0; return PID; } ``` 采用了增量式pid对轮速进行闭环计算,将比较得到的结果经过pid计算得到pwm输出值(其中重装载值为1000)。![输入图片说明](%E9%97%AD%E7%8E%AF%E6%8E%A7%E5%88%B6%E7%AE%80%E5%8D%95%E5%8E%9F%E7%90%86.png) 实现转速闭环的原理 ```c void PWM_SET(char a,int pwm){ if(pwm<0) pwm=-pwm; if(a=='A') __HAL_TIM_SET_COMPARE(&htim9,TIM_CHANNEL_1,pwm); if(a=='B') __HAL_TIM_SET_COMPARE(&htim9,TIM_CHANNEL_2,pwm); if(a=='C') __HAL_TIM_SET_COMPARE(&htim12,TIM_CHANNEL_1,pwm); if(a=='D') __HAL_TIM_SET_COMPARE(&htim12,TIM_CHANNEL_2,pwm); }//pwm输出 ``` 手柄接收到方向控制为模拟值,无摇杆时为128,为了避免抖动导致错误的移动,设置了手柄控制盲区。 ```c //将模拟值转化为pwm输出 void AutoCtrl(uint8_t X_val,uint8_t Y_val){ A=0,B=0,C=0,D=0; if(((X_val>=114)&&(X_val<=138))&&((Y_val>=114)&&(Y_val<=138))) { PWM_SET('A',0); PWM_SET('B',0); PWM_SET('C',0); PWM_SET('D',0); }//所有轮子刹车 else{//计算每个轮子速度 if(X_val>138) {A+=0.3*(X_val-138); D+=0.3*(X_val-138); C+=0.3*(114-X_val); B+=0.3*(114-X_val);} if(X_val<114) {A-=0.3*(114-X_val); D-=0.3*(114-X_val); B-=0.3*(X_val-138); C-=0.3*(X_val-138);} if(Y_val>138){ A+=0.5*(Y_val-100); B+=0.5*(Y_val-100); C+=0.5*(Y_val-100); D+=0.5*(Y_val-100); } if(Y_val<114){ A+=0.5*(Y_val-130); B+=0.5*(Y_val-130); C+=0.5*(Y_val-130); D+=0.5*(Y_val-130); } //跟据结果进行方向控制及pwm输出 if(A>=0) {HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET); PWM_SET('A',Incremental_PID_calc_A(A,V_A));} else {HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_RESET); PWM_SET('A',Incremental_PID_calc_A(-A,-V_A));} if(B>=0) {HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_11,GPIO_PIN_RESET);PWM_SET('B',Incremental_PID_calc_B(B,V_B));} else {HAL_GPIO_WritePin(GPIOD,GPIO_PIN_11,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_RESET);PWM_SET('B',Incremental_PID_calc_B(-B,-V_B)); } if(C>=0) {HAL_GPIO_WritePin(GPIOD,GPIO_PIN_6,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_7,GPIO_PIN_RESET); PWM_SET('C',Incremental_PID_calc_C(C,V_C));} else {HAL_GPIO_WritePin(GPIOD,GPIO_PIN_7,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_6,GPIO_PIN_RESET); PWM_SET('C',Incremental_PID_calc_C(-C,-V_C));} if(D>=0) {HAL_GPIO_WritePin(GPIOD,GPIO_PIN_4,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_5,GPIO_PIN_RESET); PWM_SET('D',Incremental_PID_calc_D(D,V_D));} else {HAL_GPIO_WritePin(GPIOD,GPIO_PIN_5,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_4,GPIO_PIN_RESET); PWM_SET('D',Incremental_PID_calc_D(-D,-V_D));} } } ``` #### 代码调用示例 ```c //手柄信号读取(注意不要在一个周期内多次读取datakey,否则有可能出现未知错误,推测为通信的时序问题) void StartDefaultTask(void *argument) { for(;;) { PS2_LX=PS2_AnologData(PSS_LX); //读取模拟值 PS2_LY=PS2_AnologData(PSS_LY); PS2_RX=PS2_AnologData(PSS_RX); PS2_RY=PS2_AnologData(PSS_RY); PS2_KEY=PS2_DataKey(); //更新按键及模拟值并返回按键 osDelay(40); } } //定时更新计数器并重置 void StartTask02(void *argument) { for(;;) { CNT_init();//重置计数器 osDelay(40);//计数四十毫秒 V_B=WHEEL_SPD_UPD_B(); V_A=WHEEL_SPD_UPD_A(); V_C=WHEEL_SPD_UPD_C(); V_D=WHEEL_SPD_UPD_D(); } } //将得到的ps手柄模拟值转化为pwm输出及换向 void StartTask03(void *argument) { for(;;) { AutoCtrl(255-PS2_LX,PS2_LY) ;//封装好的模拟值转换 osDelay(30); } } ```