当前位置:   article > 正文

学习PID—步进电机速度环控制实现_pid控制算法对机器人的速度和转角进行精确控制

pid控制算法对机器人的速度和转角进行精确控制

参考链接:学习PID—步进电机速度环控制实现

原理概述

        步进电机是一种数字信号驱动的电机,其主要优点之一就是拥有很好的开环控制能力,控制系统不需要传感器和相应电路的反馈电机信息。

        在负载不超载和脉冲频率合适的情况下,步进电机接收到的脉冲数和转子的角位移就是严格成正比关系。

        虽然步进电机可以很好的开环控制,但实际在一些开环系统中,步进电机有可能由于自身性能及系统机械结构等因素的影响,在快速启停或负载突变时出现失步、过冲甚至堵转,控制器无法知晓和矫正,这些现象在某些对精度要求较高的系统中可能导致严重后果。

        而加入传感器反馈组成闭环系统后,可以检测是否有失步等现象发生并及时纠正偏差。

        步进电机闭环控制方案有很多种,可以完全控制步进电机的转矩和位置,改善步进电机的转矩频率特性,降低发热和平均功耗,提高电机运行效率。

        平时听到的一些控制名词,比如速度环、位置环和电流环这些,就可以用于步进电机的闭环控制,当然这些名词同样适用于其他的电机闭环系统。

        下面我们通过步进电机转速作为被控量(也就是速度环),使用旋转编码器作为反馈传感器,PID 算法进行控制的闭环控制系统

图片

速度闭环控制–增量式 PID

        下面我们可以通过分别创建:

pid.h 和 pid.c 文件用来存放 PID 控制器相关程序。

stepper_ctrl.c、stepper_ctrl.h 文件用来存步进电机速度环控制程序及相关宏定义

操作步骤

•定时器 IO 配置

•步进电机、编码器相关外设初始化

•速度闭环控制实现

•PID 参数整定

测试环境

STM32F103VE版本以上,外部高速晶振:8MHz,RTC晶振:32.768KHz。

各总线运行时钟:系统时钟 = SYCCLK = AHB = 72MHz,APB2 = 72MHz ,APB1 = 36MHz

步进电机驱动器接口

如图所示:

图片

编码器与步进电机的接线

图片

代码解析

        第一步查看stepper_ctrl.h文件,通过这个文件是初始化步进电机GPIO等

  1. #ifndef __STEP_MOTOR_INIT_H
  2. #define __STEP_MOTOR_INIT_H
  3. #include "stm32f1xx_hal.h"
  4. //Motor 方向
  5. #define MOTOR_DIR_PIN GPIO_PIN_6
  6. #define MOTOR_DIR_GPIO_PORT GPIOE
  7. #define MOTOR_DIR_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
  8. //Motor 使能
  9. #define MOTOR_EN_PIN GPIO_PIN_5
  10. #define MOTOR_EN_GPIO_PORT GPIOE
  11. #define MOTOR_EN_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
  12. //Motor 脉冲
  13. #define MOTOR_PUL_IRQn TIM8_CC_IRQn
  14. #define MOTOR_PUL_IRQHandler TIM8_CC_IRQHandler
  15. #define MOTOR_PUL_TIM TIM8
  16. #define MOTOR_PUL_CLK_ENABLE() __HAL_RCC_TIM8_CLK_ENABLE()
  17. #define MOTOR_PUL_PORT GPIOC
  18. #define MOTOR_PUL_PIN GPIO_PIN_6
  19. #define MOTOR_PUL_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
  20. #define MOTOR_PUL_CHANNEL_x TIM_CHANNEL_1
  21. #define MOTOR_TIM_IT_CCx TIM_IT_CC1
  22. #define MOTOR_TIM_FLAG_CCx TIM_FLAG_CC1
  23. /*频率相关参数*/
  24. //定时器实际时钟频率为:72MHz/TIM_PRESCALER
  25. //具体需要的频率可以自己计算
  26. #define TIM_PRESCALER 16 /*补充:对F103例程测试,提高分频利于位置环稳定状态*/
  27. // 定义定时器周期,输出比较模式周期设置为0xFFFF
  28. #define TIM_PERIOD 0xFFFF
  29. /************************************************************/
  30. #define HIGH GPIO_PIN_SET //高电平
  31. #define LOW GPIO_PIN_RESET //低电平
  32. #define ON LOW //开
  33. #define OFF HIGH //关
  34. #define CW HIGH //顺时针
  35. #define CCW LOW //逆时针
  36. //控制使能引脚
  37. /* 带参宏,可以像内联函数一样使用 */
  38. #define MOTOR_EN(x) HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,x)
  39. #define MOTOR_PUL(x) HAL_GPIO_WritePin(MOTOR_PUL_GPIO_PORT,MOTOR_PUL_PIN,x)
  40. #define MOTOR_DIR(x) HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,x)
  41. #define MOTOR_EN_TOGGLE HAL_GPIO_TogglePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN)
  42. #define MOTOR_PUL_TOGGLE HAL_GPIO_TogglePin(MOTOR_PUL_PORT,MOTOR_PUL_PIN)
  43. extern TIM_HandleTypeDef TIM_StepperHandle;
  44. extern void stepper_Init(void);
  45. #endif /* __STEP_MOTOR_INIT_H */

stepper_ctrl.h 步进电机的步距角、驱动器细分数和 PID 控制用到的目标速度

        步进电机本身的参数和闭环控制需要用到的参数,包括步进电机的步距角、驱动器细分数和 PID 控制用到的目标速度等。

        其中宏 PULSE_RATIO 是细分后的步进电机单圈脉冲数与编码器单圈脉冲数的比值,因为在整个速度闭环控制系统中,反馈和 PID 计算得出的都是编码器的脉冲数。

  1. #ifndef __STEP_MOTOR_CTRL_H
  2. #define __STEP_MOTOR_CTRL_H
  3. #include "stepper_init.h"
  4. #include "encoder.h"
  5. /*宏定义*/
  6. /*******************************************************/
  7. #define T1_FREQ (SystemCoreClock/TIM_PRESCALER) // 频率ft值
  8. /*电机单圈参数*/
  9. #define STEP_ANGLE 1.8f //步进电机的步距角 单位:度
  10. #define FSPR ((float)(360.0f/STEP_ANGLE))//步进电机的一圈所需脉冲数
  11. #define MICRO_STEP 32 //细分器细分数
  12. #define SPR (FSPR*MICRO_STEP) //细分后一圈所需脉冲数
  13. #define PULSE_RATIO ((float)(SPR/ENCODER_TOTAL_RESOLUTION))//步进电机单圈脉冲数与编码器单圈脉冲的比值
  14. #define TARGET_DISP 2 //步进电机运动时的目标圈数,单位:转
  15. #define SPEED_LIMIT 10000 //最大启动速度限制
  16. #define SAMPLING_PERIOD 50 //PID采样频率,单位Hz
  17. .......
  18. ........
  19. ........
  20. void MSD_ENA(int NewState);
  21. void Set_Stepper_Stop(void);
  22. void Set_Stepper_Start(void);
  23. void Stepper_Speed_Ctrl(void);
  24. #endif /* __STEP_MOTOR_CTRL_H */

        定义了一个结构体 __SYS_STATUS ,用来管理驱动器和电机的运行状态。

  1. typedef struct {
  2. unsigned char stepper_dir : 1; //步进电机方向
  3. unsigned char stepper_running : 1; //步进电机运行状态
  4. unsigned char MSD_ENA : 1; //驱动器使能状态
  5. }__SYS_STATUS;

增量式 PID

        PID 控制器的入口参数从原来的目标值更改为了反馈回来的实际值,而目标值在控制器外赋值,控制器的返回值变为 PID 计算得出的增量值,实际值的累加则放到了控制器外。

        整个增量式 PID 控制器的原理并没有变化,只是调整了部分代码的组织逻辑,这么做可以更方便的在程序的其他位置调用 PID 控制器。

如图所示:

图片

步进电机闭环控制系统

  1. /**
  2. * @brief 步进电机刹车
  3. * @param 无
  4. * @retval 无
  5. */
  6. void Set_Stepper_Stop(void)
  7. {
  8. /*失能比较通道*/
  9. TIM_CCxChannelCmd(MOTOR_PUL_TIM,MOTOR_PUL_CHANNEL_x,TIM_CCx_DISABLE);
  10. sys_status.stepper_running = 0;
  11. }
  12. /**
  13. * @brief 启动步进电机
  14. * @param 无
  15. * @retval 无
  16. */
  17. void Set_Stepper_Start(void)
  18. {
  19. /*使能驱动器*/
  20. MSD_ENA(0);
  21. /*使能比较通道输出*/
  22. TIM_CCxChannelCmd(MOTOR_PUL_TIM,MOTOR_PUL_CHANNEL_x,TIM_CCx_ENABLE);
  23. sys_status.MSD_ENA = 1;
  24. sys_status.stepper_running = 1;
  25. }
  26. /**
  27. * @brief 步进电机增量式PID控制
  28. * @retval 无
  29. * @note 基本定时器中断内调用
  30. */
  31. void Stepper_Speed_Ctrl(void)
  32. {
  33. /* 编码器相关变量 */
  34. static __IO int32_t last_count = 0;
  35. __IO int32_t capture_count = 0;
  36. __IO int32_t capture_per_unit = 0;
  37. /* 经过pid计算后的期望值 */
  38. static __IO float cont_val = 0.0f;
  39. __IO float timer_delay = 0.0f;
  40. /* 当电机运动时才启动pid计算 */
  41. if((sys_status.MSD_ENA == 1) && (sys_status.stepper_running == 1))
  42. {
  43. /* 计算单个采样时间内的编码器脉冲数 */
  44. capture_count =(int)__HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (encoder_overflow_count * ENCODER_TIM_PERIOD);
  45. /* 单位时间内的编码器脉冲数作为实际值传入pid控制器 */
  46. cont_val += PID_realize((float)capture_count);// 进行 PID 计算
  47. /* 判断速度方向 */
  48. cont_val > 0 ? (MOTOR_DIR(CW)) : (MOTOR_DIR(CCW));
  49. /* 计算得出的期望值取绝对值 */
  50. timer_delay = fabsf(cont_val);
  51. /* 限制最大启动速度 */
  52. timer_delay >= SPEED_LIMIT ? (timer_delay = SPEED_LIMIT) : timer_delay;
  53. /* 计算比较计数器的值 */
  54. OC_Pulse_num = ((uint16_t)(T1_FREQ / ((float)timer_delay * PULSE_RATIO))) >> 1;
  55. printf("实际值:%d,目标值:%.0f\r\n", capture_per_unit, pid.target_val);// 打印实际值和目标值
  56. }
  57. else
  58. {
  59. /*停机状态所有参数清零*/
  60. last_count = 0;
  61. cont_val = 0;
  62. pid.actual_val = 0;
  63. pid.err = 0;
  64. pid.err_last = 0;
  65. pid.err_next = 0;
  66. }

定义了一些用于编码器测速和 PID 计算的中间变量

        判断驱动器和电机运行状态,如果驱动器使能并且电机处于运动状态,才能执行闭环控制

  1. if((sys_status.MSD_ENA == 1) && (sys_status.stepper_running == 1))

        读取编码器计数值并计算在单个采样周期中的计数值 capture_per_unit ,单位是脉冲每毫秒,实际表示编码器脉冲的频率,这里为了后续计算方便并没有写成以转每秒为单位的速度;

  1. capture_count =(int)__HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (encoder_overflow_count * ENCODER_TIM_PERIOD);

        在电机停止或由运行变为停止时,需要清零编码器读数的中间值和 PID 控制器中的累加数据,以免影响电机再次启动时的控制效果。

  1. /*停机状态所有参数清零*/
  2. last_count = 0;
  3. cont_val = 0;
  4. pid.actual_val = 0;
  5. pid.err = 0;
  6. pid.err_last = 0;
  7. pid.err_next = 0;

执行过程

        整个 Stepper_Speed_Ctrl 闭环控制函数中,传入 PID 和 PID 输出的参数都是编码器的数据,也就是编码器的脉冲频率,但是实际被控量是步进电机的转轴速度,需要做转换。

        将编码器的脉冲频率 capture_per_unit 乘上一个系数PULSE_RATIO 便可得到步进电机所需的脉冲频率,这个系数是由步进电机经过细分后转轴转一圈所需的脉冲数,与编码器转一圈发出的脉冲数之间的比值得出。

        不过此时的频率还是以 ms为单位的,为了后续计算方便,需要统一成以 s 为单位,因为本例程的采样周期是 20ms,所以单位转换只需要乘上 1s 内的采样次数 50 即可。

        得到步进电机需要的脉冲频率后,把它转换成可以写入捕获比较寄存器的值。

        当定时器配置为输出比较模式时,通过修改捕获比较寄存器当中的值,可以改变步进电机脉冲的周期,从而改变电机转速。

定时器控制

        基本定时器 TIM6 的定时中断中循环调用闭环控制程序,TIM6 配置为 20ms 中断一次,也就是说闭环控制的采样周期是 20ms。

  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  2. {
  3. /* 判断触发中断的定时器 */
  4. if(htim->Instance == BASIC_TIM)
  5. {
  6. Stepper_Speed_Ctrl();
  7. }
  8. else if(htim->Instance == ENCODER_TIM)
  9. {
  10. /* 判断当前计数方向 */
  11. if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))
  12. /* 下溢 */
  13. encoder_overflow_count--;
  14. else
  15. /* 上溢 */
  16. encoder_overflow_count++;
  17. }
  18. }

main

  1. #include "main.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include "usart.h"
  5. #include "stepper_init.h"
  6. #include "key.h"
  7. #include "led.h"
  8. #include "pid.h"
  9. #include "tim.h"
  10. #include "stepper_ctrl.h"
  11. #include "encoder.h"
  12. #include "protocol.h"
  13. extern _pid pid;
  14. extern int pid_status;
  15. /**
  16. * @brief 主函数
  17. * @param 无
  18. * @retval 无
  19. */
  20. int main(void)
  21. {
  22. /* 初始化系统时钟为72MHz */
  23. SystemClock_Config();
  24. /* 开启复用寄存器时钟 */
  25. __HAL_RCC_SYSCFG_CLK_ENABLE();
  26. /*补充:PID例程中 MOTOR_PUL_IRQn 优先级需要调为最高 */
  27. /* Set Interrupt Group Priority */
  28. HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  29. /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  30. DEBUG_USART_Config();
  31. protocol_init(); /* 初始化串口通信协议 */
  32. HAL_InitTick(5);
  33. /*按键中断初始化*/
  34. Key_GPIO_Config();
  35. /*led初始化*/
  36. LED_GPIO_Config();
  37. /* 初始化基本定时器定时,20ms产生一次中断 */
  38. TIMx_Configuration();
  39. /* 编码器接口初始化 */
  40. Encoder_Init();
  41. /*步进电机初始化*/
  42. stepper_Init();
  43. /* 上电默认停止电机 */
  44. Set_Stepper_Stop();
  45. /* PID算法参数初始化 */
  46. PID_param_init();
  47. /* 目标速度转换为编码器的脉冲数作为pid目标值 */
  48. pid.target_val = TARGET_DISP * ENCODER_TOTAL_RESOLUTION;
  49. while(1)
  50. {
  51. /* 接收数据处理 */
  52. receiving_process();
  53. /* 扫描KEY1,启动电机 */
  54. if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON )
  55. {
  56. Set_Stepper_Start();
  57. }
  58. /* 扫描KEY2,停止电机 */
  59. if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON )
  60. {
  61. Set_Stepper_Stop();
  62. }
  63. /* 扫描KEY3,增大目标位置 */
  64. if( Key_Scan(KEY3_GPIO_PORT,KEY3_PIN) == KEY_ON )
  65. {
  66. /* 位置增加2圈 */
  67. pid.target_val += 8000;
  68. }
  69. /* 扫描KEY4,减小目标位置 */
  70. if( Key_Scan(KEY4_GPIO_PORT,KEY4_PIN) == KEY_ON )
  71. {
  72. /* 位置减小2圈 */
  73. pid.target_val -= 8000;
  74. }
  75. }
  76. /**
  77. * @brief 定时器更新事件回调函数
  78. * @param 无
  79. * @retval 无
  80. */
  81. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  82. {
  83. /* 判断触发中断的定时器 */
  84. if(htim->Instance == BASIC_TIM)
  85. {
  86. Stepper_Speed_Ctrl();
  87. }
  88. else if(htim->Instance == ENCODER_TIM)
  89. {
  90. /* 判断当前计数方向 */
  91. if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))
  92. /* 下溢 */
  93. encoder_overflow_count--;
  94. else
  95. /* 上溢 */
  96. encoder_overflow_count++;
  97. }
  98. }
  99. /**
  100. * @brief System Clock Configuration
  101. * The system Clock is configured as follow :
  102. * System Clock source = PLL (HSE)
  103. * SYSCLK(Hz) = 72000000
  104. * HCLK(Hz) = 72000000
  105. * AHB Prescaler = 1
  106. * APB1 Prescaler = 2
  107. * APB2 Prescaler = 1
  108. * HSE Frequency(Hz) = 8000000
  109. * HSE PREDIV1 = 2
  110. * PLLMUL = 9
  111. * Flash Latency(WS) = 0
  112. * @param None
  113. * @retval None
  114. */
  115. void SystemClock_Config(void)
  116. {
  117. RCC_ClkInitTypeDef clkinitstruct = {0};
  118. RCC_OscInitTypeDef oscinitstruct = {0};
  119. /* Enable HSE Oscillator and activate PLL with HSE as source */
  120. oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  121. oscinitstruct.HSEState = RCC_HSE_ON;
  122. oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  123. oscinitstruct.PLL.PLLState = RCC_PLL_ON;
  124. oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  125. oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9;
  126. if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
  127. {
  128. /* Initialization Error */
  129. while(1);
  130. }
  131. /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */
  132. clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  133. clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  134. clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  135. clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
  136. clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;
  137. if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
  138. {
  139. while(1);
  140. }
  141. }

encoder.h

  1. #ifndef __ENCOEDER_H
  2. #define __ENCOEDER_H
  3. #include "stm32f1xx.h"
  4. /* 定时器选择 */
  5. #define ENCODER_TIM TIM4
  6. #define ENCODER_TIM_CLK_ENABLE() __HAL_RCC_TIM4_CLK_ENABLE()
  7. #define ENCODER_TIM_AF_CLK_ENABLE() __HAL_AFIO_REMAP_TIM4_ENABLE()
  8. /* 定时器溢出值 */
  9. #define ENCODER_TIM_PERIOD 65535
  10. /* 定时器预分频值 */
  11. #define ENCODER_TIM_PRESCALER 0
  12. /* 定时器中断 */
  13. #define ENCODER_TIM_IRQn TIM4_IRQn
  14. #define ENCODER_TIM_IRQHandler TIM4_IRQHandler
  15. /* 编码器接口引脚 */
  16. #define ENCODER_TIM_CH1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
  17. #define ENCODER_TIM_CH1_GPIO_PORT GPIOD
  18. #define ENCODER_TIM_CH1_PIN GPIO_PIN_12
  19. #define ENCODER_TIM_CH2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
  20. #define ENCODER_TIM_CH2_GPIO_PORT GPIOD
  21. #define ENCODER_TIM_CH2_PIN GPIO_PIN_13
  22. /* 编码器接口倍频数 */
  23. #define ENCODER_MODE TIM_ENCODERMODE_TI12
  24. /* 编码器接口输入捕获通道相位设置 */
  25. #define ENCODER_IC1_POLARITY TIM_ICPOLARITY_FALLING
  26. #define ENCODER_IC2_POLARITY TIM_ICPOLARITY_RISING
  27. /* 编码器物理分辨率 */
  28. #define ENCODER_RESOLUTION 1000
  29. /* 经过倍频之后的总分辨率 */
  30. #if ((ENCODER_MODE == TIM_ENCODERMODE_TI1) || (ENCODER_MODE == TIM_ENCODERMODE_TI2))
  31. #define ENCODER_TOTAL_RESOLUTION (ENCODER_RESOLUTION * 2) /* 2倍频后的总分辨率 */
  32. #else
  33. #define ENCODER_TOTAL_RESOLUTION (ENCODER_RESOLUTION * 4) /* 4倍频后的总分辨率 */
  34. #endif
  35. extern __IO int16_t encoder_overflow_count;
  36. extern TIM_HandleTypeDef TIM_EncoderHandle;
  37. void Encoder_Init(void);
  38. #endif /* __BSP_ENCODER_H */

encoder.c

  1. #include "encoder.h"
  2. /* 定时器溢出次数 */
  3. __IO int16_t encoder_overflow_count = 0;
  4. TIM_HandleTypeDef TIM_EncoderHandle;
  5. /**
  6. * @brief 编码器接口引脚初始化
  7. * @param 无
  8. * @retval 无
  9. */
  10. static void Encoder_GPIO_Init(void)
  11. {
  12. GPIO_InitTypeDef GPIO_InitStruct = {0};
  13. /* 定时器通道引脚端口时钟使能 */
  14. ENCODER_TIM_CH1_GPIO_CLK_ENABLE();
  15. ENCODER_TIM_CH2_GPIO_CLK_ENABLE();
  16. /* 设置重映射 */
  17. ENCODER_TIM_AF_CLK_ENABLE();
  18. /* 设置输入类型 */
  19. GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
  20. /* 设置上拉 */
  21. GPIO_InitStruct.Pull = GPIO_PULLUP;
  22. /* 设置引脚速率 */
  23. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  24. /* 选择要控制的GPIO引脚 */
  25. GPIO_InitStruct.Pin = ENCODER_TIM_CH1_PIN;
  26. /* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
  27. HAL_GPIO_Init(ENCODER_TIM_CH1_GPIO_PORT, &GPIO_InitStruct);
  28. /* 选择要控制的GPIO引脚 */
  29. GPIO_InitStruct.Pin = ENCODER_TIM_CH2_PIN;
  30. /* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
  31. HAL_GPIO_Init(ENCODER_TIM_CH2_GPIO_PORT, &GPIO_InitStruct);
  32. }
  33. /**
  34. * @brief 配置TIMx编码器模式
  35. * @param 无
  36. * @retval 无
  37. */
  38. static void TIM_Encoder_Init(void)
  39. {
  40. TIM_Encoder_InitTypeDef Encoder_ConfigStructure;
  41. /* 使能编码器接口时钟 */
  42. ENCODER_TIM_CLK_ENABLE();
  43. /* 定时器初始化设置 */
  44. TIM_EncoderHandle.Instance = ENCODER_TIM;
  45. TIM_EncoderHandle.Init.Prescaler = ENCODER_TIM_PRESCALER;
  46. TIM_EncoderHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
  47. TIM_EncoderHandle.Init.Period = ENCODER_TIM_PERIOD;
  48. TIM_EncoderHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  49. TIM_EncoderHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  50. /* 设置编码器倍频数 */
  51. Encoder_ConfigStructure.EncoderMode = ENCODER_MODE;
  52. /* 编码器接口通道1设置 */
  53. Encoder_ConfigStructure.IC1Polarity = ENCODER_IC1_POLARITY;
  54. Encoder_ConfigStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  55. Encoder_ConfigStructure.IC1Prescaler = TIM_ICPSC_DIV1;
  56. Encoder_ConfigStructure.IC1Filter = 0;
  57. /* 编码器接口通道2设置 */
  58. Encoder_ConfigStructure.IC2Polarity = ENCODER_IC2_POLARITY;
  59. Encoder_ConfigStructure.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  60. Encoder_ConfigStructure.IC2Prescaler = TIM_ICPSC_DIV1;
  61. Encoder_ConfigStructure.IC2Filter = 0;
  62. /* 初始化编码器接口 */
  63. HAL_TIM_Encoder_Init(&TIM_EncoderHandle, &Encoder_ConfigStructure);
  64. /* 清零计数器 */
  65. __HAL_TIM_SET_COUNTER(&TIM_EncoderHandle, 0);
  66. /* 清零中断标志位 */
  67. __HAL_TIM_CLEAR_IT(&TIM_EncoderHandle,TIM_IT_UPDATE);
  68. /* 使能定时器的更新事件中断 */
  69. __HAL_TIM_ENABLE_IT(&TIM_EncoderHandle,TIM_IT_UPDATE);
  70. /* 设置更新事件请求源为:计数器溢出 */
  71. __HAL_TIM_URS_ENABLE(&TIM_EncoderHandle);
  72. /* 设置中断优先级 */
  73. HAL_NVIC_SetPriority(ENCODER_TIM_IRQn, 5, 1);
  74. /* 使能定时器中断 */
  75. HAL_NVIC_EnableIRQ(ENCODER_TIM_IRQn);
  76. /* 使能编码器接口 */
  77. HAL_TIM_Encoder_Start(&TIM_EncoderHandle, TIM_CHANNEL_ALL);
  78. }
  79. /**
  80. * @brief 编码器接口初始化
  81. * @param 无
  82. * @retval 无
  83. */
  84. void Encoder_Init(void)
  85. {
  86. Encoder_GPIO_Init(); /* 引脚初始化 */
  87. TIM_Encoder_Init(); /* 配置编码器接口 */
  88. }

pid.c

  1. /* Includes ------------------------------------------------------------------*/
  2. #include "pid.h"
  3. #include "math.h"
  4. #include "stepper_ctrl.h"
  5. #include "protocol.h"
  6. /* 定义全局变量 */
  7. _pid pid;
  8. float set_point=0.0;
  9. int pid_status=0;
  10. /**
  11. * @brief PID参数初始化
  12. * @note 无
  13. * @retval 无
  14. */
  15. void PID_param_init()
  16. {
  17. /* 初始化参数 */
  18. pid.target_val=0.0;
  19. pid.actual_val=0.0;
  20. pid.err = 0.0;
  21. pid.err_last = 0.0;
  22. pid.err_next = 0.0;
  23. pid.Kp = 1.2;
  24. pid.Ki = 0;
  25. pid.Kd = 0;
  26. }
  27. /**
  28. * @brief 设置目标值
  29. * @param val 目标值
  30. * @note 无
  31. * @retval 无
  32. */
  33. void set_pid_actual(float temp_val)
  34. {
  35. pid.target_val = temp_val; // 设置当前的目标值
  36. }
  37. /**
  38. * @brief 获取目标值
  39. * @param 无
  40. * @note 无
  41. * @retval 目标值
  42. */
  43. float get_pid_actual(void)
  44. {
  45. return pid.target_val; // 设置当前的目标值
  46. }
  47. /**
  48. * @brief 设置比例、积分、微分系数
  49. * @param p:比例系数 P
  50. * @param i:积分系数 i
  51. * @param d:微分系数 d
  52. * @note 无
  53. * @retval 无
  54. */
  55. void set_p_i_d(float p, float i, float d)
  56. {
  57. pid.Kp = p; // 设置比例系数 P
  58. pid.Ki = i; // 设置积分系数 I
  59. pid.Kd = d; // 设置微分系数 D
  60. }
  61. /**
  62. * @brief 增量式PID算法实现
  63. * @param val:当前实际值
  64. * @note 无
  65. * @retval 通过PID计算后的输出
  66. */
  67. float PID_realize(float temp_val)
  68. {
  69. /*传入实际值*/
  70. pid.actual_val = temp_val;
  71. /*计算目标值与实际值的误差*/
  72. pid.err=pid.target_val-pid.actual_val;
  73. /*PID算法实现*/
  74. float increment_val = pid.Kp*(pid.err - pid.err_next) + pid.Ki*pid.err + pid.Kd*(pid.err - 2 * pid.err_next + pid.err_last);
  75. /*传递误差*/
  76. pid.err_last = pid.err_next;
  77. pid.err_next = pid.err;
  78. /*返回增量值*/
  79. return increment_val;
  80. }

pid.h

  1. #ifndef __PID_H
  2. #define __PID_H
  3. #include "stm32f1xx.h"
  4. #include "usart.h"
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. /*pid*/
  8. typedef struct
  9. {
  10. float target_val; //目标值
  11. float actual_val; //实际值
  12. float err; //定义当前偏差值
  13. float err_next; //定义下一个偏差值
  14. float err_last; //定义上一个偏差值
  15. float Kp, Ki, Kd; //定义比例、积分、微分系数
  16. }_pid;
  17. extern void PID_param_init(void);
  18. extern void set_pid_actual(float temp_val);
  19. extern float get_pid_actual(void);
  20. extern void set_p_i_d(float p, float i, float d);
  21. extern float PID_realize(float temp_val);
  22. extern void time_period_fun(void);
  23. #endif

位置式 PID

        创建了 4 个文件:pid.h 和 pid.c 文件用来存放 PID 控制器相关程序,stepper_ctrl.c、stepper_ctrl.h 文件用来存步进电机速度环控制程序及相关宏定义。

        stepper_init.h 和stepper_ctrl.h 中的宏定义与增量式 PID中的宏定义完全相同。

pid.c

        PID 控制器的入口参数从原来的目标值更改为了反馈回来的实际值,而目标值在控制器外赋值,控制器的返回值变为 PID 计算得出的位置值。

        整个位置式 PID 控制器的原理并没有变化,只是调整了部分代码的组织逻辑,这么做可以更方便的在程序的其他位置调用 PID 控制器。

  1. #include "pid.h"
  2. #include "math.h"
  3. #include "stepper_ctrl.h"
  4. #include "protocol.h"
  5. /* 定义全局变量 */
  6. _pid pid;
  7. float set_point=0.0;
  8. int pid_status=0;
  9. /**
  10. * @brief PID参数初始化
  11. * @note 无
  12. * @retval 无
  13. */
  14. void PID_param_init()
  15. {
  16. /* 初始化参数 */
  17. pid.target_val=0.0;
  18. pid.actual_val=0.0;
  19. pid.err=0.0;
  20. pid.err_last=0.0;
  21. pid.integral=0.0;
  22. pid.Kp = 1.2;
  23. pid.Ki = 0;
  24. pid.Kd = 0;
  25. }
  26. /**
  27. * @brief 设置目标值
  28. * @param val 目标值
  29. * @note 无
  30. * @retval 无
  31. */
  32. void set_pid_actual(float temp_val)
  33. {
  34. pid.target_val = temp_val; // 设置当前的目标值
  35. }
  36. /**
  37. * @brief 获取目标值
  38. * @param 无
  39. * @note 无
  40. * @retval 目标值
  41. */
  42. float get_pid_actual(void)
  43. {
  44. return pid.target_val; // 设置当前的目标值
  45. }
  46. /**
  47. * @brief 设置比例、积分、微分系数
  48. * @param p:比例系数 P
  49. * @param i:积分系数 i
  50. * @param d:微分系数 d
  51. * @note 无
  52. * @retval 无
  53. */
  54. void set_p_i_d(float p, float i, float d)
  55. {
  56. pid.Kp = p; // 设置比例系数 P
  57. pid.Ki = i; // 设置积分系数 I
  58. pid.Kd = d; // 设置微分系数 D
  59. }
  60. /**
  61. * @brief 位置式PID算法实现
  62. * @param actual_val:当前实际值
  63. * @note 无
  64. * @retval 通过PID计算后的输出
  65. */
  66. float PID_realize(float actual_val)
  67. {
  68. /*传入实际值*/
  69. pid.actual_val = actual_val;
  70. /*计算目标值与实际值的误差*/
  71. pid.err = pid.target_val - pid.actual_val;
  72. /*误差累积*/
  73. pid.integral += pid.err;
  74. /*PID算法实现*/
  75. pid.actual_val = pid.Kp*pid.err
  76. + pid.Ki*pid.integral
  77. + pid.Kd*(pid.err-pid.err_last);
  78. /*误差传递*/
  79. pid.err_last = pid.err;
  80. /*PID算法实现,并返回计算值*/
  81. return pid.actual_val;
  82. }

步进电机闭环控制系统

  1. #include "math.h"
  2. #include "tim.h"
  3. #include "stepper_ctrl.h"
  4. #include "pid.h"
  5. #include "protocol.h"
  6. extern _pid pid;
  7. extern __IO uint16_t OC_Pulse_num; //比较输出的计数值
  8. __SYS_STATUS sys_status = {0};
  9. /**
  10. * @brief 驱动器紧急停止
  11. * @param NewState:使能或者禁止
  12. * @retval 无
  13. */
  14. void MSD_ENA(int NewState)
  15. {
  16. if(NewState)
  17. {
  18. //ENA失能,禁止驱动器输出,(脱机状态)此时电机为无保持力矩状态,可以手动旋转电机
  19. MOTOR_EN(OFF);
  20. sys_status.MSD_ENA = 0;
  21. }
  22. else
  23. {
  24. //ENA使能,此时电机为保持力矩状态
  25. MOTOR_EN(ON);
  26. sys_status.MSD_ENA = 1;
  27. }
  28. }
  29. /**
  30. * @brief 步进电机刹车
  31. * @param 无
  32. * @retval 无
  33. */
  34. void Set_Stepper_Stop(void)
  35. {
  36. /*失能比较通道*/
  37. TIM_CCxChannelCmd(MOTOR_PUL_TIM,MOTOR_PUL_CHANNEL_x,TIM_CCx_DISABLE);
  38. sys_status.stepper_running = 0;
  39. }
  40. /**
  41. * @brief 启动步进电机
  42. * @param 无
  43. * @retval 无
  44. */
  45. void Set_Stepper_Start(void)
  46. {
  47. /*使能驱动器*/
  48. MSD_ENA(0);
  49. /*使能比较通道输出*/
  50. TIM_CCxChannelCmd(MOTOR_PUL_TIM,MOTOR_PUL_CHANNEL_x,TIM_CCx_ENABLE);
  51. sys_status.MSD_ENA = 1;
  52. sys_status.stepper_running = 1;
  53. }
  54. /**
  55. * @brief 步进电机位置式PID控制
  56. * @retval 无
  57. * @note 基本定时器中断内调用
  58. */
  59. void Stepper_Speed_Ctrl(void)
  60. {
  61. /* 编码器相关变量 */
  62. __IO int32_t capture_per_unit = 0;
  63. __IO int32_t capture_count = 0;
  64. static __IO int32_t last_count = 0;
  65. /* 经过pid计算后的期望值 */
  66. __IO int32_t cont_val = 0;
  67. /* 当电机运动时才启动pid计算 */
  68. if((sys_status.MSD_ENA == 1) && (sys_status.stepper_running == 1))
  69. {
  70. /* 计算单个采样时间内的编码器脉冲数 */
  71. capture_count =(int)__HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (encoder_overflow_count * ENCODER_TIM_PERIOD);
  72. /* 单位时间内的编码器脉冲数作为实际值传入pid控制器 */
  73. cont_val = PID_realize((float)capture_count);// 进行 PID 计算
  74. /* 判断方向 */
  75. cont_val > 0 ? (MOTOR_DIR(CW)) : (MOTOR_DIR(CCW));
  76. /* 对计算得出的期望值取绝对值 */
  77. cont_val = abs(cont_val);
  78. /* 限制最大启动速度 */
  79. cont_val >= SPEED_LIMIT ? (cont_val = SPEED_LIMIT) : cont_val;
  80. /* 计算比较计数器的值 */
  81. OC_Pulse_num = ((uint16_t)(T1_FREQ / ((float)cont_val * PULSE_RATIO))) >> 1;
  82. printf("实际值:%d,目标值:%.0f\r\n", capture_count, pid.target_val);// 打印实际值和目标值
  83. }
  84. else
  85. {
  86. capture_per_unit = 0;
  87. cont_val = 0;
  88. pid.actual_val = 0;
  89. pid.err = 0;
  90. pid.err_last = 0;
  91. pid.integral = 0;
  92. }
  93. }

main.c

  1. /**
  2. ******************************************************************************
  3. * @file main.c
  4. * @author fire
  5. * @version V1.0
  6. * @date 2020-xx-xx
  7. * @brief 步进电机-位置环
  8. ******************************************************************************
  9. * @attention
  10. *
  11. * 实验平台:野火 F103-拂晓 STM32 开发板
  12. * 论坛 :http://www.firebbs.cn
  13. * 淘宝 :https://fire-stm32.taobao.com
  14. *
  15. ******************************************************************************
  16. */
  17. /* Includes ------------------------------------------------------------------*/
  18. #include "main.h"
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include "./usart/bsp_debug_usart.h"
  22. #include "./stepper/bsp_stepper_init.h"
  23. #include "./key/bsp_key.h"
  24. #include "./led/bsp_led.h"
  25. #include "./pid/bsp_pid.h"
  26. #include "./tim/bsp_basic_tim.h"
  27. #include "./stepper/bsp_stepper_ctrl.h"
  28. #include "./Encoder/bsp_encoder.h"
  29. #include "./protocol/protocol.h"
  30. extern _pid pid;
  31. extern int pid_status;
  32. /**
  33. * @brief 主函数
  34. * @param 无
  35. * @retval 无
  36. */
  37. int main(void)
  38. {
  39. /* 初始化系统时钟为72MHz */
  40. SystemClock_Config();
  41. /* 开启复用寄存器时钟 */
  42. __HAL_RCC_SYSCFG_CLK_ENABLE();
  43. /* Set Interrupt Group Priority */
  44. HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  45. /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  46. DEBUG_USART_Config();
  47. protocol_init(); /* 初始化串口通信协议 */
  48. HAL_InitTick(5);
  49. /*按键中断初始化*/
  50. Key_GPIO_Config();
  51. /*led初始化*/
  52. LED_GPIO_Config();
  53. /* 初始化基本定时器定时,20ms产生一次中断 */
  54. TIMx_Configuration();
  55. /* 编码器接口初始化 */
  56. Encoder_Init();
  57. /*步进电机初始化*/
  58. stepper_Init();
  59. /* 上电默认停止电机 */
  60. Set_Stepper_Stop();
  61. /* PID算法参数初始化 */
  62. PID_param_init();
  63. /* 目标速度转换为编码器的脉冲数作为pid目标值 */
  64. pid.target_val = TARGET_DISP * ENCODER_TOTAL_RESOLUTION;
  65. while(1)
  66. {
  67. /* 接收数据处理 */
  68. receiving_process();
  69. /* 扫描KEY1,启动电机 */
  70. if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON )
  71. {
  72. Set_Stepper_Start();
  73. }
  74. /* 扫描KEY2,停止电机 */
  75. if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON )
  76. {
  77. Set_Stepper_Stop();
  78. }
  79. /* 扫描KEY3,增大目标位置 */
  80. if( Key_Scan(KEY3_GPIO_PORT,KEY3_PIN) == KEY_ON )
  81. {
  82. /* 位置增加2圈 */
  83. pid.target_val += 8000;
  84. }
  85. /* 扫描KEY4,减小目标位置 */
  86. if( Key_Scan(KEY4_GPIO_PORT,KEY4_PIN) == KEY_ON )
  87. {
  88. /* 位置减小2圈 */
  89. pid.target_val -= 8000;
  90. }
  91. }
  92. }
  93. /**
  94. * @brief 定时器更新事件回调函数
  95. * @param 无
  96. * @retval 无
  97. */
  98. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  99. {
  100. /* 判断触发中断的定时器 */
  101. if(htim->Instance == BASIC_TIM)
  102. {
  103. Stepper_Speed_Ctrl();
  104. }
  105. else if(htim->Instance == ENCODER_TIM)
  106. {
  107. /* 判断当前计数方向 */
  108. if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))
  109. /* 下溢 */
  110. encoder_overflow_count--;
  111. else
  112. /* 上溢 */
  113. encoder_overflow_count++;
  114. }
  115. }
  116. void SystemClock_Config(void)
  117. {
  118. RCC_ClkInitTypeDef clkinitstruct = {0};
  119. RCC_OscInitTypeDef oscinitstruct = {0};
  120. /* Enable HSE Oscillator and activate PLL with HSE as source */
  121. oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  122. oscinitstruct.HSEState = RCC_HSE_ON;
  123. oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  124. oscinitstruct.PLL.PLLState = RCC_PLL_ON;
  125. oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  126. oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9;
  127. if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
  128. {
  129. /* Initialization Error */
  130. while(1);
  131. }
  132. /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
  133. clocks dividers */
  134. clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  135. clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  136. clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  137. clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
  138. clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;
  139. if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
  140. {
  141. /* Initialization Error */
  142. while(1);
  143. }
  144. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/929594
推荐阅读
相关标签
  

闽ICP备14008679号