STM32 CubeMax直流电机速度环控制
本例基于STM32F103C8T6与CubeMax

1. 速度环控制的思路
要对直流电机进行速度环控制,首先先给电机输出一个初始占空比的PWM信号,然后适用编码器读取电机的实时转速,观察电机是转得快了还是慢了,如果转得快了就减小占空比,转的快了就增大占空比,直到达到目标转速。

算法的具体思路看之前讲PID原理的那一篇文章

2. CubeMax配置
CubeMax的配置与编码器测速模块一致

3. 接线
接线也与编码器测速中的一致

4. 代码编写
pid.c的内容如下

#ifndef _PID_H_

#define _PID_H_



#include "stm32f1xx.h"

#include "encoder.h"

#include <stdio.h>



//PID三个参数的值

#define KP1 27 //便于以后写多个pid算法并行,这里加序号1

#define KI1 25

#define KD1 2




typedef struct _PID//PID参数结构体

{

    float kp,ki,kd;

    float err,lastErr;

    float integral,maxIntegral; //积分值

    float output,maxOutput;

}PID;



void PID_Init(void);

void PID_SingleCal(PID* pid,float target,float feedback);//一次PID计算




#endif

pid.c内容如下

#include "pid.h"



PID pid1;



void PID_Init(void)//PID参数初始化

{

    pid1.err = 0;

    pid1.integral = 0;

    pid1.maxIntegral = 2000;

    pid1.maxOutput = __HAL_TIM_GetAutoreload(&PWM_TIM);

    pid1.lastErr = 0;

    pid1.output = 0;

    pid1.kp = KP1;

    pid1.ki = KI1;

    pid1.kd = KD1;

}




/****************************************

 * 作用:进行一次PID计算

 * 参数:PID参数结构体地址;目标值;反馈值

 * 返回值:无

 * ****************************************/

void PID_SingleCal(PID* pid,float target,float feedback)//一次PID计算

{

    pid->err = target - feedback;

    //printf("pid->err = %f\r\n",pid->err);

    pid->integral += pid->err;

    //printf("pid->integral = %f\r\n",pid->integral);



    if(pid->integral < -pid1.maxIntegral) pid->integral = -pid1.maxIntegral;//限制积分值

    else if(pid->integral > pid1.maxIntegral) pid->integral = pid1.maxIntegral;

    //pid->output = pid->err * pid->kp ;



   pid->output = (pid->kp * pid->err) + (pid->ki * pid->integral) + (pid1.kd * (pid->err - pid->lastErr));//全量式PID



    if(pid->output < 0) pid->output = 0;//限制积分值

    else if(pid->output > pid1.maxOutput) pid->output = pid1.maxOutput;




    pid->lastErr = pid->err;

}

此外要在编码器测速函数中增加对输出PWM占空比的控制,encoder,c文件如下

#include "encoder.h"



Motor motor1;

extern PID pid1;

uint8_t voidErr = 0;//第一次速度计算有问题,直接规避掉不输出



void Motor_Init(void)

{

    HAL_TIM_Encoder_Start(&ENCODER_TIM, TIM_CHANNEL_ALL);      //开启编码器定时器

    __HAL_TIM_ENABLE_IT(&ENCODER_TIM,TIM_IT_UPDATE);           //开启编码器定时器更新中断,防溢出处理

    HAL_TIM_Base_Start_IT(&GAP_TIM);                       //开启10ms定时器中断

    HAL_TIM_PWM_Start(&PWM_TIM, TIM_CHANNEL_2);            //开启PWM

    HAL_TIM_PWM_Start(&PWM_TIM, TIM_CHANNEL_1);            //开启PWM

    __HAL_TIM_SET_COUNTER(&ENCODER_TIM, 10000);                //编码器定时器初始值设定为10000

    motor1.lastCount = 10000;                                   //结构体内容初始化

    motor1.totalCount = 10000;

        motor1.overflowNum = 0;                                  

    motor1.speed = 0;

    motor1.direct = 0;

}



void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器回调函数,用于计算速度

{

    if(htim->Instance==ENCODER_TIM.Instance)//编码器输入定时器溢出中断                    

    {      

        if(COUNTERNUM < 10000) motor1.overflowNum++;       //如果是向上溢出

        else if(COUNTERNUM >= 10000) motor1.overflowNum--; //如果是向下溢出

        //__HAL_TIM_SetCounter(&ENCODER_TIM, 10000);             //重新设定初始值

    }

    else if(htim->Instance==GAP_TIM.Instance)//间隔定时器中断,是时候计算速度了

    {

        motor1.direct = __HAL_TIM_IS_TIM_COUNTING_DOWN(&ENCODER_TIM);//如果向上计数(正转),返回值为0,否则返回值为1



        motor1.totalCount = COUNTERNUM + motor1.overflowNum * RELOADVALUE;//一个周期内的总计数值等于目前计数值加上溢出的计数值

        motor1.speed = (float)(motor1.totalCount - motor1.lastCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 600;//算得每秒多少转,除以4是因为4倍频

        motor1.lastCount = motor1.totalCount; //记录这一次的计数值

            if(voidErr == 0) voidErr++;

            else

            {

                printf("%.3f\r\n",motor1.speed);



        PID_SingleCal(&pid1,200,motor1.speed);//进行一次PID计算

              //printf("pid.ouput = %f\r\n",pid1.output);

        __HAL_TIM_SetCompare(&PWM_TIM, TIM_CHANNEL_1, pid1.output);//修改电机PWM占空比

            }

    }

}
  1. 结果
    设置目标速度为200,最后还是能比较稳定地维持在200,只是过程比较曲折,说明参数没有调得足够好,还有改进空间

    参数整定可以参考野火的教程