打印
[其他ST产品]

stm32直流电机PID控制hal库(Cubemx)

[复制链接]
手机看帖
扫描二维码
随时随地手机跟帖
41
花间一壶酒sd|  楼主 | 2023-10-27 01:11 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
用户代码编写
由于用到了usart输出为了方便起见我们加入微库中的printf函数,我们为他重定义一下
在usart.c文件的末尾的 /* USER CODE BEGIN 1 */ 处加入以下代码:

使用特权

评论回复
42
花间一壶酒sd|  楼主 | 2023-10-27 01:11 | 只看该作者
int fputc(int ch,FILE *f)
{
        HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,1000);
        return (ch);
}
int fgetc(FILE *f)
{
        uint8_t ch;
        HAL_UART_Receive(&huart1,(uint8_t *)&ch,sizeof(ch),0xffff);
        return ch;
}

使用特权

评论回复
43
花间一壶酒sd|  楼主 | 2023-10-27 01:11 | 只看该作者
这里我们需要这个文件的上方加入#include <stdio.h>
并在魔法棒里面包括微库:

使用特权

评论回复
44
花间一壶酒sd|  楼主 | 2023-10-27 01:11 | 只看该作者
开启定时器以及编码器:

使用特权

评论回复
45
花间一壶酒sd|  楼主 | 2023-10-27 01:11 | 只看该作者
主程序中保持不变,我们在it.c(中断文件),最下方找到void TIM3_IRQHandler(void) 函数我们将在这里写相关函数(没有用回调函数)在 /* USER CODE END TIM3_IRQn 0 */ 的后面写入如下的代码:

使用特权

评论回复
46
花间一壶酒sd|  楼主 | 2023-10-27 01:11 | 只看该作者
        int encoder_count;
        encoder_count= TIM4->CNT;
        TIM4->CNT=0;
        printf("脉冲数:%d\n",encoder_count);

使用特权

评论回复
47
花间一壶酒sd|  楼主 | 2023-10-27 01:12 | 只看该作者
然后将线连接在编码器的输出口:
(保证此时电机是正传) 此时串口打印出来的数据可能是很大数据,也可能是较小的数据,我们规定正转时时小的数据,所以如若此时打印出来的是很大的数据,那我们就需要把两根线互换一下即可。

使用特权

评论回复
48
花间一壶酒sd|  楼主 | 2023-10-27 01:12 | 只看该作者
为了减少误差,以及防止引入浮点数等问题,我建议在底层层面上都使用脉冲数。

下面我们通过编写相关函数来输出正反转以及,此时的转速(RPM)
将 /* USER CODE END TIM3_IRQn 0 / 的后面写入如下的代码:

使用特权

评论回复
49
花间一壶酒sd|  楼主 | 2023-10-27 01:12 | 只看该作者
在rpm=encoder_count/13.0/10.0/4.06000;中
13为旋转一圈所产生的脉冲数,即脉冲数/转(Pulse Per Revolution 或PPR)
10.0 是本减少直流电机的减速比
4.0是采用了双通道,几倍频就除以几
6000是本中断0.01s一次,*6000就可以得到一分钟的转速

使用特权

评论回复
50
花间一壶酒sd|  楼主 | 2023-10-27 01:13 | 只看该作者
PI控制速度
简单验证并调试
在user文件夹下创建control.h和control.c并放入keil文件中
在main.c文件中加入一些变量:

使用特权

评论回复
51
花间一壶酒sd|  楼主 | 2023-10-27 01:13 | 只看该作者
注意主函数中原来的代码
在中断服务函数(在it.c)中只打印现在的脉冲数,并加入Control_function(); 函数:

使用特权

评论回复
52
花间一壶酒sd|  楼主 | 2023-10-27 01:13 | 只看该作者
随后在control.c文件中进行编写:
#include "control.h"
#include "main.h"
#include "moto.h"
#include "tim.h"

extern int Encoder_count,Target_Velocity; //编码器的脉冲计数,现在和目标
extern float Velocity_KP,Velocity_KI,Velocity_KD; //pid参数

void Control_function(void)
{
        Moto_pwm=Incremental_PI(Encoder_count,Target_Velocity);//**有30个脉冲
        Set_Pwm(Moto_pwm);
}
/*
*        @函数功能:增量PI控制器
*        入口参数:编码器测量值,目标速度
*        返回值:电机PWM
*        根据增量式离散PID公式
*        pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
*        e(k)代表本次偏差
*        e(k-1)代表上一次的偏差  以此类推
*        pwm代表增量输出
*        在我们的速度控制闭环系统里面,只使用PI控制
*        pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
*/
int Incremental_PI (int Encoder,int Target)
{        
         static float Bias,Pwm,Last_bias;
         Bias=Target-Encoder;                                  //计算偏差
         Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias;   //增量式PI控制器
         Last_bias=Bias;                                                    //保存上一次偏差
         return Pwm;                                           //增量输出
}
/**
*        函数功能:赋值给PWM寄存器
*        入口参数:PWM
*        返回值:无
*/
void Set_Pwm(int moto)
{
        int pwm_abs;
        {Moto(0);}//正传
                       
        pwm_abs=myabs(moto);               
        __HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,pwm_abs);
       
}

使用特权

评论回复
53
花间一壶酒sd|  楼主 | 2023-10-27 01:13 | 只看该作者
加入后运行程序,程序中的p I 的值是需要根据自己的电机进行相应的调整
常见问题有:
电机转的超级慢:大概是p太小了(最好把i也设进去,一般i先设为1)
电机完全不转或者转的超级快:多半是Moto();这个函数(控制正反转)中的1或者0填错了

例如我的电机我设置的预期转速为 300 rpm,由上面的一个公式(rpm=encoder_count/13.0/10.0/4.0*6000)我们可以知道,当encoder_count为1的时候我们的rpm为11.538,所以我们的精度也只能停留在11.538rpm,如若超调量需要的更加精细那就可以从公式入手,或者采用T法。

使用特权

评论回复
54
花间一壶酒sd|  楼主 | 2023-10-27 01:14 | 只看该作者
实现电机的正反转
实现正反转相对于单向转动稍微复杂一些,并且我们在这里进行一下输出的限幅,
在main.c中再加入一些全局变量

使用特权

评论回复
55
花间一壶酒sd|  楼主 | 2023-10-27 01:14 | 只看该作者
再次修改it.c文件中的中断服务函数
  /* USER CODE BEGIN TIM3_IRQn 1 */

        int encode_tim;
        encode_tim= TIM4->CNT;
        if(encode_tim>0xefff)
        {
                encode_tim=encode_tim-0xffff;
        }
       
        Encoder_count= encode_tim;        //这样就可以得到负的脉冲数,当是负的时候就说明是反转
        printf("现在的脉冲数为%d\n",Encoder_count);
       
       

        float rpm_;
        rpm_=Encoder_count/13.0/10.0/4.0*6000;
        printf("PMR= %.3f\n",rpm_);
       
       
        TIM4->CNT=0;
        Control_function();//在contr.c文件中进行数据的处理
  /* USER CODE END TIM3_IRQn 1 */

使用特权

评论回复
56
花间一壶酒sd|  楼主 | 2023-10-27 01:14 | 只看该作者
修改control.c文件如下:(齐全代码):
#include "control.h"
#include "main.h"
#include "moto.h"
#include "tim.h"
extern int Encoder_count,Target_Velocity; //编码器的脉冲计数,现在和目标
extern float Velocity_KP,Velocity_KI,Velocity_KD; //pid参数
extern int target_rpm;//目标每分钟的圈数
extern int Moto_pwm;
void Control_function(void)
{
        Target_Velocity =(int)(target_rpm/6000.0*4*10*13);//将目标rpm转化为编码器需要的脉冲计数转化为int类型的
        Moto_pwm=Incremental_PI(Encoder_count,Target_Velocity);
        limiting_Pwm();
        Set_Pwm(Moto_pwm);
}
/*
*        函数功能:取绝对值
*        入口参数:int
*        返回值:无 unsingned int
*/
int myabs(int num)
{
        int temp;
        if(num<0)        temp=-num;
        else temp =num;
        return temp;
}
/**
*        函数功能:赋值给PWM寄存器
*        入口参数:PWM
*        返回值:无
*/
void Set_Pwm(int moto)
{
        int pwm_abs;
          if(moto<0)
        {Moto(1);}//反转                               
        else
        {Moto(0);}//正传
        pwm_abs=myabs(moto);               
        __HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,pwm_abs);
}
/*
*        函数功能:限制PWM赋值
*        入口参数:无
*        返回值:无
*/
void limiting_Pwm(void)
{       
          int maximum=8000;    //===PWM满幅是8400 限制在8000
          if(Moto_pwm<-maximum) Moto_pwm=-maximum;       
                if(Moto_pwm>maximum)  Moto_pwm=maximum;               
}
/*
*        @函数功能:增量PI控制器
*        入口参数:编码器测量值,目标速度
*        返回值:电机PWM
*        pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
*        e(k)代表本次偏差
*        e(k-1)代表上一次的偏差  以此类推
*        pwm代表增量输出
*        在我们的速度控制闭环系统里面,只使用PI控制
*        pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
*/
int Incremental_PI (int Encoder,int Target)
{        
         static float Bias,Pwm,Last_bias;
         Bias=Target-Encoder;                                  //计算偏差
         Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias;   //增量式PI控制器
         Last_bias=Bias;                                                    //保存上一次偏差
         return Pwm;                                           //增量输出
}

使用特权

评论回复
57
花间一壶酒sd|  楼主 | 2023-10-27 01:14 | 只看该作者
通过上位机打印波形
在user文件夹下创建四个文件分别为:show.c show.h DataScop_DP.c DataScop_DP.h
并将文件置于keil文件中。
将程序移植到相关的文件中:

使用特权

评论回复
58
花间一壶酒sd|  楼主 | 2023-10-27 01:14 | 只看该作者
show.c
#include "show.h"
#include "main.h"

#include "DataScop_DP.h"

extern int Encoder_count,Target_Velocity; //编码器的脉冲计数,现在和目标

/**************************************************************************
函数功能:虚拟示波器往上位机发送数据
入口参数:无
返回  值:无
**************************************************************************/
void DataScope(void)
{   
        int Send_Count,i;//计数需要的变量
       
        DataScope_Get_Channel_Data( Encoder_count, 1 );      
        DataScope_Get_Channel_Data( Target_Velocity, 2 );      
//        DataScope_Get_Channel_Data( 0, 3 );              
//        DataScope_Get_Channel_Data( 0 , 4 );   
//        DataScope_Get_Channel_Data(0, 5 ); //用您要显示的数据替换0就行了
//        DataScope_Get_Channel_Data(0 , 6 );//用您要显示的数据替换0就行了
//        DataScope_Get_Channel_Data(0, 7 );
//        DataScope_Get_Channel_Data( 0, 8 );
//        DataScope_Get_Channel_Data(0, 9 );  
//        DataScope_Get_Channel_Data( 0 , 10);//一共可以打印10个数据查看波形
        Send_Count = DataScope_Data_Generate(2);//打印几个数据就在这里改为几
        for( i = 0 ; i < Send_Count; i++)
        {
        while((USART1->SR&0X40)==0);  
        USART1->DR = DataScope_OutPut_Buffer[i];
        }
}

使用特权

评论回复
59
花间一壶酒sd|  楼主 | 2023-10-27 01:15 | 只看该作者
show.h
#ifndef __SHOW_H
#define __SHOW_H

void DataScope(void);
#endif

使用特权

评论回复
60
花间一壶酒sd|  楼主 | 2023-10-27 01:15 | 只看该作者
DataScop_DP.c
#include "DataScop_DP.h"

unsigned char DataScope_OutPut_Buffer[42] = {0};           //串口发送缓冲区
//函数说明:将单精度浮点数据转成4字节数据并存入指定地址
//附加说明:用户无需直接操作此函数
//target:目标单精度数据
//buf:待写入数组
//beg:指定从数组第几个元素开始写入
//函数无返回
void Float2Byte(float *target,unsigned char *buf,unsigned char beg)
{
    unsigned char *point;
    point = (unsigned char*)target;          //得到float的地址
    buf[beg]   = point[0];
    buf[beg+1] = point[1];
    buf[beg+2] = point[2];
    buf[beg+3] = point[3];
}
//函数说明:将待发送通道的单精度浮点数据写入发送缓冲区
//Data:通道数据
//Channel:选择通道(1-10)
//函数无返回
void DataScope_Get_Channel_Data(float Data,unsigned char Channel)
{
        if ( (Channel > 10) || (Channel == 0) ) return;  //通道个数大于10或等于0,直接跳出,不执行函数
  else
  {
     switch (Channel)
                {
      case 1:  Float2Byte(&Data,DataScope_OutPut_Buffer,1); break;
      case 2:  Float2Byte(&Data,DataScope_OutPut_Buffer,5); break;
                  case 3:  Float2Byte(&Data,DataScope_OutPut_Buffer,9); break;
                  case 4:  Float2Byte(&Data,DataScope_OutPut_Buffer,13); break;
                  case 5:  Float2Byte(&Data,DataScope_OutPut_Buffer,17); break;
                  case 6:  Float2Byte(&Data,DataScope_OutPut_Buffer,21); break;
                  case 7:  Float2Byte(&Data,DataScope_OutPut_Buffer,25); break;
                  case 8:  Float2Byte(&Data,DataScope_OutPut_Buffer,29); break;
                  case 9:  Float2Byte(&Data,DataScope_OutPut_Buffer,33); break;
                  case 10: Float2Byte(&Data,DataScope_OutPut_Buffer,37); break;
                }
  }         
}
//函数说明:生成 DataScopeV1.0 能正确识别的帧格式
//Channel_Number,需要发送的通道个数
//返回发送缓冲区数据个数
//返回0表示帧格式生成失败
unsigned char DataScope_Data_Generate(unsigned char Channel_Number)
{
        if ( (Channel_Number > 10) || (Channel_Number == 0) ) { return 0; }  //通道个数大于10或等于0,直接跳出,不执行函数
  else
  {       
         DataScope_OutPut_Buffer[0] = ';  //帧头
               
         switch(Channel_Number)   
   {
                 case 1:   DataScope_OutPut_Buffer[5]  =  5; return  6;  
                 case 2:   DataScope_OutPut_Buffer[9]  =  9; return 10;
                 case 3:   DataScope_OutPut_Buffer[13] = 13; return 14;
                 case 4:   DataScope_OutPut_Buffer[17] = 17; return 18;
                 case 5:   DataScope_OutPut_Buffer[21] = 21; return 22;  
                 case 6:   DataScope_OutPut_Buffer[25] = 25; return 26;
                 case 7:   DataScope_OutPut_Buffer[29] = 29; return 30;
                 case 8:   DataScope_OutPut_Buffer[33] = 33; return 34;
                 case 9:   DataScope_OutPut_Buffer[37] = 37; return 38;
     case 10:  DataScope_OutPut_Buffer[41] = 41; return 42;
   }         
  }
        return 0;
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则