• 直流无刷电机PID控制


    最近做了直流无刷减速电机的控制的项目,针对项目中遇到的问题做下总结。
    #PID_Control

    PID 代码(速度环 位置环 串级

    STM32F407VET6

    STM32CubeMX

    更新记录

    V1.0.0 2022/8/5

    1. 完善了RTOS程序模板,包括RTT功能.
    2. 完成了位置PID 速度环控制.
    3. 增加了上位机串口通信协议,可用上位机调节PID参数.

    V1.0.1 2022/8/8

    1. 实现了通过M法测速 将单位时间内编码器的脉冲数转换为速度,
      速度分两个 a、电机转子的转速 b、减速器输出轴转速

    b = a / REDUCTION_RATIO(减速比).

    /* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数 */
    Shaft_Speed = ((float)(pitch_cfg.cnt)) / (((float)(ENCODER_TOTAL_RESOLUTION)) * 0.01f);

    2、需要理清楚几个概念:

    1、每转脉冲数 PPR 圆周分辨率,Pulse Per Revolution 或 PPR (分辨率)
    2、每转计数 CPR CPR = PPR * 4
    3、RPM 每分钟转速

    例如,某旋转编码器的分辨率为100PPR,表示每旋转一圈,该编码器可输出100个脉冲信号;
    同理,某旋转编码器的分辨率为256PPR,则表示每旋转一圈的脉冲数为256个.

    3、设置PID的目标值 为转轴转速 来实现调速,减速器输出轴转速太小了,上位机不支持.


    V1.0.2 2022/8/11

    1. 增加了对电机位置的控制,通过控制脉冲数控制电机位置
      /* 电机转一圈的脉冲数*/
      #define PULSE_SUM 124264=12672

    V1.0.3 2022/8/16

    1. 增加了串级PID,位置环和速度环
      /* 电机转一圈的脉冲数*/
      #define PULSE_SUM 12672 // htim3.Init.Prescaler = 0,理论上最多可转4圈
      #define PULSE_SUM_FOUR 3168 // htim3.Init.Prescaler = 4-1 ,相当于之前1us采集一次编码器脉冲,现在4us采集一次,总采集数为65535 一圈脉冲数为 3168,理论上最多可转20圈,而且速度按最大值进行旋转,所以位置PID速度不可控。

    /* Private define ------------------------------------------------------------*/
    /* 编码器接口倍频数 */
    #define ENCODER_MODE                           TIM_ENCODERMODE_TI12
    #define PITCH_TICK2DEG                     0.028869198  //TT电机
    #define PITCH_MOTOR_MAX_OUTPUT             400          //扭矩
    #define YAW_MOTOR_MAX_OUTPUT               400
    #define DOWN_LIMIT_OFFSET                  -200
    #define ENCODER_TIM_PERIOD                 65535   /*计数器最大值*/
    /* 编码器物理分辨率 */
    #define ENCODER_RESOLUTION                  12         //PPR
    /* 减速电机减速比 */
    #define REDUCTION_RATIO                     264
    /* 电机转一圈的脉冲数*/
    #define PULSE_SUM                           12672   // htim3.Init.Prescaler = 0,理论上最多可转4圈
    #define PULSE_SUM_FOUR                      3168    // htim3.Init.Prescaler = 4-1 ,相当于之前1us采集一次编码器脉冲,现在4us采集一次,
                                                        // 总采集数为65535  一圈脉冲数为 3168,理论上最多可转20圈,而且速度按最大值进行旋转,所以位置PID速度不可控。
    /* 经过倍频之后的总分辨率 */
    #if (ENCODER_MODE == TIM_ENCODERMODE_TI12)
      #define ENCODER_TOTAL_RESOLUTION             (ENCODER_RESOLUTION * 4)  /* 4倍频后的总分辨率 CPR*/
    #else
      #define ENCODER_TOTAL_RESOLUTION             (ENCODER_RESOLUTION * 2)  /* 2倍频后的总分辨率 */
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    /***
    * @brief  bsp_motor_get_position
    * @param  None
    * @retval None
    */
    int16_t bsp_motor_get_position(motor_id_e id)
    {
      int32_t pos = 0;
      if(id == YAW_MOTOR)
      {
        pos = -(int16_t)__HAL_TIM_GET_COUNTER(&htim4);
        //pos = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
      }
      else if(id == PITCH_MOTOR)
      {
        //pos = (int32_t)__HAL_TIM_GET_COUNTER(&htim2);
        pos = (int16_t)__HAL_TIM_GET_COUNTER(&htim1);
      }
      return pos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    一、速度环PID

    /***
    * @brief  pitch_test
    * @param  None
    * @retval None
    */
    /* 电机转轴转速 */
    __IO float Shaft_Speed = 0.0f;
    
    void vPitch_Task(void const * argument)
    {
      TickType_t xLastWakeTime = xTaskGetTickCount();
    	//set_p_i_d(15.0, 2.0, 0.0);
    	//set_pid_target(30.0);
    	static int count = 0;
      int test = 1;
    	int pid_out = 0;
    	
      pitch_cfg.pwm = 0;
      pitch_cfg.run2up = 1;
    
      while(test)
      {
        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10));
     
        if(pitch_cfg.run2up)
        {
          bsp_motor_set_direction(PITCH_MOTOR, HD2UP);//向上运动
    			//bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));
        }
        else
        {
          bsp_motor_set_direction(PITCH_MOTOR, HD2DOWN);//向上运动
        }
        
        pitch_cfg.cnt = bsp_motor_get_position(PITCH_MOTOR);
    		DBG_Printf("%s: Encoder:%d \r\n", __FUNCTION__, pitch_cfg.cnt);
    		/* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数  */
        Shaft_Speed = ((float)(pitch_cfg.cnt)) / (((float)(ENCODER_TOTAL_RESOLUTION)) * 0.01f);
    		
    		DBG_Printf("Speed of motor shaft:%f r/s \r\n", Shaft_Speed);  //电机转子的转速
    		//DBG_Printf("SPeed of motor output shaft:%f r/s \r\n", Shaft_Speed / REDUCTION_RATIO);  //减速器输出轴的转速
    		
    		uint32_t speed_temp = 0;
    		speed_temp = (uint32_t)Shaft_Speed;
    #if (PID_ASSISTANT_EN)
    	count++;
    	if (count==8)
    	{
    		count = 0;
    		set_computer_value(SEND_FACT_CMD, CURVES_CH1, &speed_temp , 1); // 给通道 1 发送实际值
    	}
    
    #endif
    	
    		pid_out = (int)PID_realize(speed_temp);
    		
    		DBG_Printf("%s: PID_OUT:%d \r\n", __FUNCTION__, pid_out);
    		pitch_cfg.pwm = pwm_val_protect(pid_out);
    	  DBG_Printf("%s: PWM:%d \r\n", __FUNCTION__, pitch_cfg.pwm);
    	  DBG_Printf("\r\n");
    	  bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    二、位置环PID

    /***
    * @brief
    *   pitch_test
    * @param  None
    * @retval None
    */
    /* 电机转轴转速 */
    __IO float Shaft_Speed = 0.0f;
    extern __IO int16_t EncoderOverflowCnt;
    
    void vPitch_Task(void const * argument)
    {
      TickType_t xLastWakeTime = xTaskGetTickCount();
    	 __IO int32_t encoderNow = 0;    /*当前时刻总计数值*/
    	
    	//set_p_i_d(15.0, 2.0, 0.0);
    	//set_pid_target(30.0);
    	static int count = 0;
      int test = 1;
    	int pid_out = 0;
    	
      pitch_cfg.pwm = 0;
      pitch_cfg.run2up = 1;
    
      while(test)
      {
        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10));
     
    		
        if(pitch_cfg.run2up)
        {
          bsp_motor_set_direction(PITCH_MOTOR, HD2UP);//向上运动
    			//bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));
        }
        else
        {
          bsp_motor_set_direction(PITCH_MOTOR, HD2DOWN);//向上运动
        }
    		DBG_Printf("\r\n");
        pitch_cfg.cnt = bsp_motor_read_encoder(PITCH_MOTOR);
    		DBG_Printf("%s: Encoder:%d \r\n", __FUNCTION__,pitch_cfg.cnt);
    		DBG_Printf("%s:flowCnt:%d \r\n", __FUNCTION__,EncoderOverflowCnt);
    		/*【1】读取编码器的值*/
    		encoderNow = pitch_cfg.cnt + EncoderOverflowCnt * ENCODER_TIM_PERIOD;/*获取当前的累计值*/
       
    		DBG_Printf("%s: encoder_Now:%d \r\n", __FUNCTION__, encoderNow);
    		/*【2】PID运算,得到PWM控制值*/
    		pid_out = (int)PID_realize(encoderNow);
    		DBG_Printf("%s: pid_out:%d \r\n", __FUNCTION__, pid_out);
    		pitch_cfg.pwm = pwm_val_protect(pid_out);
    		
    	
    		/*【3】PWM控制电机*/
    		DBG_Printf("%s: pwm:%d \r\n", __FUNCTION__, pitch_cfg.pwm);
    	  uint32_t value_tt = 0;
    	if (pid.target_val > 0)
        {
    		  bsp_motor_set_direction(PITCH_MOTOR, HD2UP);//向上运动
    			bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));/*传入编码器的[总计数值],实现电机【位置】控制*/
    		}	
        else if (pid.target_val < 0)	
        {
    		  bsp_motor_set_direction(PITCH_MOTOR, HD2DOWN);//向下运动
    			bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));/*传入编码器的[总计数值],实现电机【位置】控制*/
    		}	
        else
        {
    		  bsp_motor_set_speed(PITCH_MOTOR, &value_tt);
    		}			
    	  
    		/*【4】数据上传到上位机显示*/
    #if (PID_ASSISTANT_EN)
    	count++;
    	if (count==8)
    	{
    		count = 0;
    		int Temp = encoderNow;
    		set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Temp , 1); // 给通道 1 发送实际值
    	}
    
    #endif
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    三、串级PID

    /***
    * @brief  pitch_test
    * @param  None
    * @retval None
    */
    /* 电机转轴转速 */
    
    extern __IO int16_t EncoderOverflowCnt;
    
    void vPitch_Task(void const * argument)
    {
      TickType_t xLastWakeTime = xTaskGetTickCount();
    	int encoderNow = 0;    /*当前时刻总计数值*/
    	static uint32_t location_timer = 0;    // 位置环周期
    	static __IO int encoderLast = 0;   /*上一时刻总计数值*/
    	int encoderDelta = 0; /*当前时刻与上一时刻编码器的变化量*/
    	static int count = 0;
      int32_t actual_speed = 0.0f;
      int test = 1;
    	uint32_t value_tt = 0;
    	int actual_speed_int = 0;
    	
      pitch_cfg.pwm = 0;
     
      while(test)
      {
        vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10));
     
    		DBG_Printf("\r\n");
        pitch_cfg.cnt = bsp_motor_read_encoder(PITCH_MOTOR);
    		DBG_Printf("%s: Encoder:%d \r\n", __FUNCTION__,pitch_cfg.cnt);
    		DBG_Printf("%s:flowCnt:%d \r\n", __FUNCTION__,EncoderOverflowCnt);
    		
    		/*【1】读取编码器的值*/
    		encoderNow = pitch_cfg.cnt + EncoderOverflowCnt * ENCODER_TIM_PERIOD;/*获取当前的累计值*/
    		
        /* 计算速度环的传入值 */
    		encoderDelta = encoderNow - encoderLast; /*得到变化值  速度环的传入值*/
    	  encoderLast = encoderNow;/*更新上次的累计值*/
    		
    		DBG_Printf("%s: encoderNow:%d \r\n", __FUNCTION__, encoderNow);
    		DBG_Printf("%s: encoderDelta:%d \r\n", __FUNCTION__, encoderDelta);
    		/*【2】位置PID运算,得到速度目标值*/
    		if ((location_timer++ % 2) == 0)
    		{
    		  float control_val = 0;   /*当前控制值*/
    			/*位置PID计算*/
    		  control_val = location_pid_realize(&pid_location, encoderNow);  
    		  DBG_Printf("%s: speed_current:%.2f \r\n", __FUNCTION__, control_val);
          /*目标速度值限制*/
    		  speed_val_protect(&control_val);
    
    		  /*设定速度PID的目标值*/
    		  set_pid_target(&pid_speed, control_val); 
    			DBG_Printf("%s: pid_speed_target:%.2f \r\n", __FUNCTION__, control_val);
    			
    			#if defined(PID_ASSISTANT_EN)
    			if ((location_timer % 16) == 8)
    			{
    				int temp_speed = 0;
    				temp_speed = (int)control_val;
    				set_computer_value(SEND_TARGET_CMD, CURVES_CH2, &temp_speed, 1);     // 给通道2发送速度目标值
    			}
    			#endif
    		}
    		
    		/*【3】速度PID运算,得到PWM控制值*/
    		actual_speed = (int32_t)((encoderDelta) / (ENCODER_TOTAL_RESOLUTION*0.01f));
    		DBG_Printf("%s: actual_speed:%d \r\n", __FUNCTION__, actual_speed);
    		
    		actual_speed_int = actual_speed;
    		pitch_cfg.pwm =  pwm_val_protect((int)speed_pid_realize(&pid_speed, actual_speed));
    
    		DBG_Printf("%s: actual_pwm:%d \r\n", __FUNCTION__, pitch_cfg.pwm);
    		if (pid_speed.target_val > 0)
        {
    		  bsp_motor_set_direction(PITCH_MOTOR, HD2UP);//向上运动
    			bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));/*传入编码器的[总计数值],实现电机【位置】控制*/
    		}	
        else if (pid_speed.target_val < 0)	
        {
    		  bsp_motor_set_direction(PITCH_MOTOR, HD2DOWN);//向下运动
    			bsp_motor_set_speed(PITCH_MOTOR, &(pitch_cfg.pwm));/*传入编码器的[总计数值],实现电机【位置】控制*/
    		}	
        else
        {
    		  bsp_motor_set_speed(PITCH_MOTOR, &value_tt);
    		}			
    	  
    		/*【4】数据上传到上位机显示*/
    #if (PID_ASSISTANT_EN)
    	count++;
    	if(count%12 == 5)
    	{
    		set_computer_value(SEND_FACT_CMD, CURVES_CH1, &encoderNow, 1);   /*给通道1发送实际的电机【位置】值*/
    	}
    	else if(count%12 == 10)
    	{
    		set_computer_value(SEND_FACT_CMD, CURVES_CH2, &actual_speed_int, 1); /*给通道2发送实际的电机【速度】值*/
    	}
    
    #endif
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
  • 相关阅读:
    软考-网络安全审计技术原理与应用
    ubuntu 20 安装搜狗输入法
    基于SpringBoot的社区医院管理系统设计与实现(源码+lw+部署文档+讲解等)
    vue3 父组件使用ref获取获取子组件的属性方法
    第6节-PhotoShop基础课程-认识选区
    【毕业设计】基于深度学习卷积神经网络的手写字符识别
    TCGA官网下载和TCGAbiolinks下载的文件数量竟然不一样?
    设计模式之命令模式
    【C++】STL入门—— 一张图带你了解常用的string类函数
    C++变量声明和变量定义
  • 原文地址:https://blog.csdn.net/qq_26972441/article/details/126547559