基于STM32 的直流电机PID调速 及TFTLCD屏幕显示速度值及速度曲线

学习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、其它:略。

七、效果视频

STM32直流电机PID调速

  • 8
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STM32中使用PID控制来输出PWM信号控制电机的速度是一种常见的方法。首先,需要设定目标速度,并将位置控制器的输出进行限幅。这可以通过编写一个函数来实现,如引用\[1\]中所示的PWM_Restrict函数。该函数将输入的PWM值与目标速度进行比较,并根据比较结果进行限幅处理,最后返回限幅后的PWM值。 在串级PID控制中,将两个或多个PID控制器按照串联的方式连接起来,其中外环控制位置,内环控制速度。对于直流电机速度位置闭环控制来说,外环输入为电机旋转的目标圈数,输出为速度;内环输入为速度,输出为PWM增量。这种串级PID控制的思想可以实现对电机速度的精准控制。具体实现时,前一个PID程序输出的是圈数,而后级PID将其作为速度处理。这是因为后级PID的期望值就是前级PID的输出值,经过PID计算得出的是电机需要的旋转速度。为了让电机按照期望速度旋转,我们需要对前级PID的输出进行限幅处理,将其限制在期望速度范围内。这样可以避免电机超速旋转或速度不达标的情况发生。具体的限幅幅值就是期望速度。这个限幅操作可以在实际的程序中实现。引用\[2\]中提供了一个例子来解释这个过程。 在STM32中,可以使用PID算法来实现对电机速度的控制。PID算法的控制框图如引用\[3\]所示。在控制电机速度时,期望输入即为电机的期望速度值。将期望输入与由编码器测得的实际速度进行差值计算,得到误差值,然后将该误差值传递给PID控制部分,计算出需要输出的控制信号。最后,将该控制信号传递给控制器,即输出给电机驱动板,从而实现对电机速度的精准控制。 综上所述,使用STM32PID控制来输出PWM信号控制电机的速度可以通过设定目标速度并进行限幅处理来实现。同时,可以采用串级PID控制的思想,将位置控制和速度控制结合,实现对电机速度的精准控制。 #### 引用[.reference_title] - *1* *2* [基于stm32直流电机串级PID控制(代码开源)](https://blog.csdn.net/weixin_45720060/article/details/129947250)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【STM32STM32F103C8T6实现直流电机速度PID控制](https://blog.csdn.net/qq_52785580/article/details/123002248)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值