学习STM32有一段时间了,完整的做了一套系统,记录一下。
一、目标功能:1、STM32 输出PWM控制直流电机转速。
? ? ? ? ? ? ? ? ? 2、采用增量式PID控制方法调节电机转速。
? ? ? ? ? ? ? ? ? 3、电机的转速值 和转速曲线实时显示在TFTLCD屏幕上。
? ? ? ? ? ? ? ? ? 4、电机的转速值 和PWM值每0.2s从串口输出。
? ? ? ? ? ? ? ? ? 5、通过串口输入,可以随时写入所需要达到的转速以及调整PID参数值。
二、系统组成:
? ? ? ? ? ? ? ? ? 1、STM32 ZET6 正点原子战舰版(带有TFTLCD屏幕)
? ? ? ? ? ? ? ? ? 2、L298N 直流电机驱动器
? ? ? ? ? ? ? ? ? 3、1000线 AB相单片机用编码器
? ? ? ? ? ? ? ? ? 4、12V直流电机
? ? ? ? ? ? ? ? ? 5、12V直流电源
? ? ? ? ? ? ? ? ? 6、杜邦线及导线若干
? ? ? ? ? ? ? ? ??
三、开发环境: KEIL-MDK?ARM 软件
? ? ? ? ? ? ? ? ? ?STM32 CUBE MX软件
四、接线说明:1、PWM 采用STM32 TIM2 CH1输出 ,输出频率10K HZ。
? ? ? ? ? ? ? ? ? 2、STM32 TIM2 CH1接入L298N 的BLA端,输出PWM到电机控制器。
? ? ? ? ? ? ? ? ? 2、编码器AB相 输入接入单片机TIM3 采用编码器模式,编码器AB相输入接入TIM3 CHI1 及CH2。
? ? ? ? ? ? ? ? ?3、单片机PE2及PE3接入L298N 的IN1 及IN2。作为电机正反转控制信号。
? ? ? ? ? ? ? ? ?4、电机接入L298N 的out1 及out2.
? ? ? ? ? ? ? ? ?5、直流电源正极接入L298N +12V ,负极接入L298N GND并与单片机GND连接。
? ?五、MX CUBE部分配置:
? ? ? ? ? ? ? ? ?1、FSMC配置(PB0为背光接口,注意不要遗漏配置)
?2、TFTLCD驱动程序
? ? ? ?驱动程序借用了正点原子HAL库示例程序中的LCD.c及LCD.h文件。删除其中关于FSMC的配置程序行。
3、TIM2配置(产生PWM波,频率10KHz)
4、TIM3配置(编码器模式),用于编码器测速。
5、TIM6配置(定时0.2s) ,定时器中断用于每0.2s 计算转速值,输入PWM值。
?6、NVIC优先级配置:这里建议将串口优先级最高。实际运行中发现,如果串口中断优先级不是最高,偶尔会出现输入的转速及PID值不正确的现象。
?7、GPIO及串口配置:GPIO 口使用PE2 及PE3 用于控制电机正反转。串口配置略。
六、部分程序说明
1、LCD屏幕上速度网格绘制程序
2、增量式PID函数
typedef struct { ?? ?float ActualSpeed ; ? ? ? ? ? ? ? ? ? ? ? ? ?//当前值 ?? ?float error; ? ? ? ? ? ? ? ? ? ? ?//偏差 ?? ?float lasterror; ? ? ? ? ? ? ? ? ?//前一次偏差 ?? ?float last_lasterror; ? ? ? ? ? ? ?//前两次偏差 ?? ?int_least16_t PWM; ? ? ? ? ? ? ? ? //输出PWM(有符号型) ?? ?int_least16_t PWM_ADD; ? ? ? ? ? ? //增量PWM(有符号型)? ?? }PID;
/* 增量式PID 算法 */ ?int PID_SET(uint16_t actual_speed,uint16_t set_speed) ?{ ?? ? ? ?pid.error = set_speed - actual_speed; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //设定值减当前值,计算本次偏差 ?? ?? ?? ? ? ?pid.PWM_ADD=KP*(pid.error-pid.lasterror)+KI*pid.error+KD*(pid.error-2.0f*pid.lasterror+pid.last_lasterror); ?? ??? ? ?? ? ? ?pid.PWM+=pid.PWM_ADD; ?? ?? ?? ? ? ?pid.last_lasterror = pid.lasterror; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//将此次偏差保留为上上次偏差 ?? ? ? ? ? ? ? ? ? ? ?? ?? ? ? ?pid.lasterror = pid.error; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //将此次偏差保留为上次偏差 ?? ?? ?? ? if(pid.PWM > 7200) pid.PWM = 7200; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//限副 PWM100% 为7200 ?? ? if(pid.PWM < -7200) pid.PWM = -7200; ?? ?? ? ? ? ? return pid.PWM; ?} ? ?/* 增量式PID 参数初始化 */ ?void PID_init(void) ? { ?? ? ?pid.lasterror=0; ?? ? ?pid.last_lasterror = 0; ?? ? ?pid.PWM = 0; ? }?
3、速度值转换函数:用于将得到的速度值存入数组,在LCD屏幕上显示。
/* 速度值转化函数 */ //速度值分解千、百、十、个位后转成16进制ASC码 最后加上单位r/min void speed_change(short motorspeed) { ?? ?if(motorspeed<0) motorspeed=(-motorspeed); ? ?//反转转速为负数,转换成正数 ?? ? ?? ?motornum[0]= (motorspeed/1000)+0x30; ?? ?motornum[1] = (motorspeed%1000/100)+0x30; ?? ?motornum[2] = (motorspeed%1000%100/10)+0x30; ?? ?motornum[3] = (motorspeed%1000%100%10)+0x30; ?? ?motornum[4]=0x72; ?? ?motornum[5]=0x2F; ?? ?motornum[6]=0x6d; ?? ?motornum[7]=0x69; ?? ?motornum[8]=0x6e;? ? }
4、串口取PID值函数:用于将从串口取到的PID值取出来。PID值可以为小数。
/* 串口输入取PID参数数据函数 */
double Get_PIDDate(void) ? ?? { ?? ?double date=0; ?? ?double dat[Uart1_Rx_Cnt]; ?? ?uint8_t i,j,k,n; ?? ?for(i=1;i<Uart1_Rx_Cnt;i++) ? ?//判断小数点的位置 ?? ?{ ?? ??? ?if(RxBuffer[i]=='.')? ?? ??? ?{ ?? ??? ??? ?n=i; ? //记录小数点位置 ? ?? ??? ? ? ?break; ?? ??? ?} ?? ??? ?else n=?? ?Uart1_Rx_Cnt-2;?? ? ? ? //如果没有输入小数点、则n= 数组位数减去PID开头 以及0x0a 0x0d ? ? ? ? ? ?? ? ?? ?} ?? ?for(j=1;j<n;j++) ? ? ? ? ? ? ? //小数点前的数求和 ?? ?{ ?? ??? ?dat[j]=(RxBuffer[j]-'0')*pow(10,n-j-1); ?? ??? ?date+=dat[j]; ?? ?}
?? ?for(k=n+1;k<Uart1_Rx_Cnt-2;k++) ? //加小数点后的数,如果没有小数点此函数不运行 ?? ?{ ?? ??? ?dat[k]=(RxBuffer[k]-'0')*pow(10,n-k); ?? ??? ?date+=dat[k]; ?? ?} ?? ? ?? ?return date; } 5、串口取速度函数
/* 串口输入取速度数据函数 */ ? int Get_SpeedDate(void) ? { ?? ? ?uint16_t date=0; ?? ? ?uint16_t dat[5]; ?? ? ?uint8_t i; ?? ? ?for(i=1;i<Uart1_Rx_Cnt-2;i++) ? //位数减去S开头 以及0x0a 0x0d ?? ? ?{ ?? ? ?dat[i]=(RxBuffer[i]-'0')*pow(10,Uart1_Rx_Cnt-3-i); ?//pow为几次方数学函数,将串口输入的数转化成十进制(-48为转化成十进制)存入数组dat ?? ? ?date+=dat[i]; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//各位累加 ?? ? ?} ?? ? ? ?? ? ?return date; ?? ? }
6、串口中断回调函数:写了一个小协议,S开头输入代表设定所需转速,p开头输入代表设定PID的KP值,i开头输入代表设定PID的KI值,d开头输入代表设定PID的KD值。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) ? { ? ? ? ? ?? ? ?? ? ? ? ?/*此函数有如下BUG: 当发送超过数量超过267,触发数据溢出后仍然能够接收到超出部分的数据并发送出去*/ ?? ?if(Uart1_Rx_Cnt>=255) ?? ?{ ?? ??? ? ?? ??? ?Uart1_Rx_Cnt=0; ?? ??? ?memset(RxBuffer,0x00,sizeof(RxBuffer)); ? ? ? ? ? ? ? ? ? //将数组RxBuffer所有位清零 ?? ??? ?HAL_UART_Transmit(&huart1,(uint8_t*)"数据溢出",10,0xffff); ?? ??? ? ?? ?} ?? ?else? ?? ?{ ?? ??? ?RxBuffer[Uart1_Rx_Cnt++]=aRxBuffer; ? ? ? ? ? ? ? ? ? ? ? //将接收到的数据依次放入数组 ?? ??? ?if((RxBuffer[Uart1_Rx_Cnt-1]==0x0a)&&(RxBuffer[Uart1_Rx_Cnt-2]==0x0d)) ?//判断已接收完成 ?? ??? ?{ ?? ??? ??? ?switch (RxBuffer[0]) ?? ??? ??? ?{ ?? ??? ??? ??? ?case 0x73: ? ? ? ? ? //s开头输入的为速度值 ?? ??? ??? ? ?? ??? ??? ??? ?Set_Speed=Get_SpeedDate(); ?//从串口接收数组中取出设定速度值 ?? ??? ??? ? ? ?printf("设定的速度值SetSpeed=%d\r\n",Set_Speed); ?? ??? ??? ? ? ?HAL_UART_Transmit(&huart1,(uint8_t *)&RxBuffer,Uart1_Rx_Cnt,0xffff); ? //将接收到的数据发送出去 ?? ??? ??? ? ? ?while(HAL_UART_GetState(&huart1)==HAL_UART_STATE_BUSY_TX); ? ? ? ? ? ? //检测已发送完成 ?? ??? ??? ? ? ?Uart1_Rx_Cnt=0; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数清零 ?? ??? ??? ? ? ?memset(RxBuffer,0x00,sizeof(RxBuffer)); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数组清零 ?? ??? ??? ??? ?break; ?? ??? ??? ? ?? ??? ??? ? ?? ??? ??? ??? ?case 0x70 : ? ? ? // p开头输入的为KP值 ?? ??? ??? ? ?? ??? ??? ??? ?KP=Get_PIDDate(); ?//从串口接收数组中取出设定P值 ?? ??? ??? ? ? ?printf("设定的P值KP=%f\r\n",KP); ?? ??? ??? ? ? ?HAL_UART_Transmit(&huart1,(uint8_t *)&RxBuffer,Uart1_Rx_Cnt,0xffff); ? //将接收到的数据发送出去 ?? ??? ??? ? ? ?while(HAL_UART_GetState(&huart1)==HAL_UART_STATE_BUSY_TX); ? ? ? ? ? ? //检测已发送完成 ?? ??? ??? ? ? ?Uart1_Rx_Cnt=0; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数清零 ?? ??? ??? ? ? ?memset(RxBuffer,0x00,sizeof(RxBuffer)); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数组清零 ?? ??? ??? ??? ?break; ?? ??? ??? ? ?? ??? ??? ??? ?case 0x69: ? ? ? ? //i 开头的为KI值 ?? ??? ??? ? ?? ??? ??? ??? ?KI=Get_PIDDate(); ?//从串口接收数组中取出设定P值 ?? ??? ??? ? ? ?printf("设定的I值KI=%f\r\n",KI); ?? ??? ??? ? ? ?HAL_UART_Transmit(&huart1,(uint8_t *)&RxBuffer,Uart1_Rx_Cnt,0xffff); ? //将接收到的数据发送出去 ?? ??? ??? ? ? ?while(HAL_UART_GetState(&huart1)==HAL_UART_STATE_BUSY_TX); ? ? ? ? ? ? //检测已发送完成 ?? ??? ??? ? ? ?Uart1_Rx_Cnt=0; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数清零 ?? ??? ??? ? ? ?memset(RxBuffer,0x00,sizeof(RxBuffer)); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数组清零 ?? ??? ??? ??? ?break; ?? ??? ??? ? ?? ??? ??? ??? ?case 0x64: ? ? ? ? // d开头的为KI值 ?? ??? ??? ? ?? ??? ??? ??? ?KD=Get_PIDDate(); ?//从串口接收数组中取出设定P值 ?? ??? ??? ? ? ?printf("设定的D值KD=%f\r\n",KD); ?? ??? ??? ? ? ?HAL_UART_Transmit(&huart1,(uint8_t *)&RxBuffer,Uart1_Rx_Cnt,0xffff); ? //将接收到的数据发送出去 ?? ??? ??? ? ? ?while(HAL_UART_GetState(&huart1)==HAL_UART_STATE_BUSY_TX); ? ? ? ? ? ? //检测已发送完成 ?? ??? ??? ? ? ?Uart1_Rx_Cnt=0; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数清零 ?? ??? ??? ? ? ?memset(RxBuffer,0x00,sizeof(RxBuffer)); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //接收数组清零 ?? ??? ??? ??? ?break; ?? ??? ??? ? ?? ??? ??? ?default: ?printf("输入的参数有误,请重新输入!"); break; ?? ??? ??? ?} ?? ??? ?} ?? ?} ?? ?HAL_UART_Receive_IT(&huart1,(uint8_t *)&aRxBuffer,1); ? ? ? ? ? ? ? ? ? ? ? ? ?//接收一次数据后再次开启接收中断 ?? ? }
7、定时器中断回调函数:实现屏幕显示速度值及曲线,输出PWM等功能。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { ?? ? ?? ? ?? ?if (htim == (&htim6)) ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?{ ?? ??? ?motorspeed = (short)__HAL_TIM_GET_COUNTER(&htim3)*0.075; ?//200ms内脉冲数转换成r/min(1000编码器。0.2秒收到的脉冲,编码器模式4倍频) ?? ??? ? ? ? ? ? ? ? ?//先将__HAL_TIM_GET_COUNTER(&htim3)的计数值强制转换为short(-32768到32767)型,假如反转,转速从65535开始向下计数 ?? ??? ? ? ? ? ? ? ? ?// 转换成short后为-1,-2,-3... ?? ??? ? ?? ??? ?__HAL_TIM_SET_COUNTER(&htim3,0); //编码器计数器清零 ?? ??? ? ?? ??? ? LCD_ShowString(10,20,210,16,16,"motorspeed="); //显示motorspeed= ?? ??? ? ?? ??? ? speed_change(motorspeed); ? ? ? ? ? ? ? ? ? ? //速度值写入数组 ?? ??? ? ?? ??? ? LCD_ShowString(100,20,210,16,16,motornum); ? ?//将数组内的速度值显示出来 ?? ??? ? ?? ??? ? pwmVal = ?PID_SET(abs(motorspeed),Set_Speed); ? ? ? ? ? ? ? //20ms中断,执行PWM_SET函数 返回的PWM值给pwmVal ?? ??? ? ?? ??? ?__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,pwmVal); ? ? ?//将pwmVal值传递给TIM2通道的PWM输出 ?? ??? ? ?? ??? ? printf("PWMval=%d\r\n",pwmVal); ? ? ? ? ? ? ? ? ? ? ? ?//串口输出PWM值 ?? ??? ? ?? ??? ? printf("motorspeed=%d r/min\r\n",motorspeed); ? ? ? ? ? ? ? ?//串口输出电机速度值 ?? ??? ? ?? ??? ?LCD_DrawPoint(ntime++,motorspeed/Speed_n()); ? ? ? ? ? ? ? //屏幕显示速度点,最高4000r/min, 每一行代表4000/800=5r/min。 ?? ??? ? ?? ??? ? ?? ??? ?if (ntime >= 479) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//列满后刷新屏幕 ?? ??? ?{ ?? ??? ??? ?ntime=1; ?? ??? ??? ?LCD_Clear(WHITE); ?? ??? ??? ?LCD_SpeedDrawLine(); ?? ??? ?} ?? ??? ? ?? ?} ?? ? ?? ? }
8、其它:略。
七、效果视频
|