6. 直流电机电流环控制实现

通过上一章节《直流电机速度环控制实现》的学习,我们大致的了解了PID在实际应用中是如何发挥它自动控制的作用的。 在实际应用中,并不仅仅只有使用速度作为反馈来调节直流电机工作。 相反地,现实中有许多参数可以作为反馈量,为特定场景下的直流电机控制带来更好的效果,例如本章节要讲到的电流环控制。 电流环控制,简单的说,就是希望以恒定的电流来驱动电机运转,即希望电机能输出恒定的转矩。 接下来,本章节就来介绍一下使用电流环来实现直流电机的电流控制。

本章通过我们前面学习的位置式PID和增量式PID两种控制方式分别来实现电流环的控制, 如果还不知道什么是位置式PID和增量式PID,请务必先学习前面PID算法的通俗解说这一章节。

6.1. 硬件设计

本章实验需要连接开发板和驱动板,这里给出接线表。

6.1.1. MOS管搭建驱动板

电机与MOS管搭建驱动板连接见下表所示。

电机与MOS管搭建驱动板连接

电机

MOS管搭建驱动板

电机输出M+

M+

电机输出M-

M-

MOS管搭建驱动板与主控板连接见下表所示。

MOS管搭建驱动板与主控板连接

MOS管搭建驱动板

主控板

PWM1

PE11

PWM2

PE11

SD

PB15

信号检测-电流

PA03

信号检测-电压

PA01

电源输入:5V

5V

电源输入:GND

GND

推荐使用配套的牛角排线直接连接驱动板和主控板。连接开发板的那端,请连接在“直流有/无刷电机驱动接口1”上。

6.2. 直流电机电流环控制-位置式PID实现

6.2.1. 编程要点

  1. 配置定时器可以输出PWM控制电机

  2. 配置定时器可以读取当前电路中驱动电机的电流值

  3. 配置基本定时器可以产生定时中断来执行PID运算

  4. 编写位置式PID算法

  5. 编写电流控制函数

  6. 增加上位机曲线观察相关代码

  7. 编写按键控制代码

6.2.2. 软件设计

软件部分和上一章大体相同,区别在于更换了输入PID的参数为电流值,代码中也增加了电流获取的功能, 这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到, 还有一些在前章节章节分析过的代码在这里也不在重复讲解,完整的代码请参考本章配套的工程。 关于电流值获取的部分主要查看前面的ADC采集电压电流章节。

6.2.2.1. 新建工程

我们直接在基础部分的直流有刷章节的 “201_Motor_BDC_Current_Voltage_Acquisition” 例程的基础上修改程序。

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程 “201_Motor_BDC_Current_Voltage_Acquisition”, 然后将工程文件夹重命名为 206_Motor_BDC_PID_Current_Positional,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程 “201_Motor_BDC_Current_Voltage_Acquisition”, 然后将工程文件夹重命名为 206_Motor_BDC_PID_Current_Positional,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,在工程根目录的 “src” 文件夹下面,将野火提供的 “pid” 、 “protocol” 文件夹添加进去。 其中主要是PID代码和上位机通信协议代码,关于其中的原理已经在提高部分第三章节讲过,本章不再赘述,只展示部分更改的代码。 工程文件结构如下。

文件结构
206_Motor_BDC_PID_Current_Positional
├─ ......
└─ src
   ├─ adc
   │  ├─ motor_v_c_acquisition.c
   │  └─ motor_v_c_acquisition.h
   ├─ beep
   │  ├─ bsp_beep.c
   │  └─ bsp_beep.h
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ encoder
   │  ├─ bsp_encoder.c
   │  └─ bsp_encoder.h
   ├─ gpt
   │  ├─ bsp_gpt_pwm_output.c
   │  └─ bsp_gpt_pwm_output.h
   │  ├─ bsp_gpt_timing.c
   │  └─ bsp_gpt_timing.h
   ├─ key
   │  ├─ bsp_key_irq.c
   │  └─ bsp_key_irq.h
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ motor_controls
   │  ├─ bsp_motor_control.c
   │  └─ bsp_motor_control.h
   ├─ pid
   │  ├─ bsp_pid.c   //新建文件
   │  └─ bsp_pid.h   //新建文件
   ├─ protocol
   │  ├─ protocol.c  //新建文件
   │  └─ protocol.h  //新建文件
   └─ hal_entry.c

本例程不需要在FSP中新建任何模块,只进行一些修改即可。

6.2.2.2. 定时器中断函数

定时器如何配置前面章节多次提到,这边就略过不讲了,只展示部分不同的代码。如有疑问,之前的章节有详细讲解,代码也非常简单易懂。

该代码段是一个定时器中断回调函数,用于在定时器周期结束时执行特定任务。在回调函数中,首先检查定时器是否触发了周期结束事件(TIMER_EVENT_CYCLE_END)。当事件为周期结束时,程序会获取当前的电流值。

通过 GET_ADC_VDC_VALfiltered_current 计算出电压值,再通过 GET_ADC_CURR_VAL 转换得到实际电流值。 然后,使用计算得到的电流值作为输入,调用 motor_pid_control 函数进行 PID 电机控制。

bsp_motor_control.c-定时器中断函数
// GPT中断回调函数
void gpt0_timing_callback(timer_callback_args_t *p_args)
{
    // 定时器周期结束事件
    if (TIMER_EVENT_CYCLE_END == p_args->event)
    {
        // 计算实际电流值
        float current = GET_ADC_CURR_VAL(GET_ADC_VDC_VAL((float)(filtered_current)));

        // 执行PID电机控制
        motor_pid_control(current);
    }
}

接下来的PID算法实现部分,以及控制部分、与上位机通信部份都与之前没有什么不同,这里不再赘述。

6.2.2.3. hal_entry函数

在主函数里面首先做了一些外设的初始化,然后通过上位机来观察、控制电机。

hal_entry.c-hal_entry主函数
void hal_entry(void)
{
      /* TODO: add your own code here */
      uint8_t target_speed = 50;

      LED_Init();         // LED 初始化
      Debug_UART9_Init(); // SCI4 UART 调试串口初始化

      /* GPT和电机初始化 */
      Motor_GPT_PWM_Init();
      Motor_Control_Init();

      /*ADC与DMAC初始化*/
      ADC_DMAC_Init();

      /*初始化定时器*/
      GPT_Timing_Init();

      Motor_Control_Stop();  // 调用电机停止函数

      /* 协议初始化 */
      protocol_init();

      /*PID参数初始化*/
      PID_param_init();

      set_computer_value(SEND_STOP_CMD, CURVES_CH1, NULL, 0);                // 同步上位机的启动按钮状态
      set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &target_speed, 1);     // 给通道 1 发送目标值

      while(1)
          {

          /* 接收数据处理 */
          receiving_process();

          }
#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

6.2.3. 下载验证

我们按前面介绍的硬件连接好电机和驱动板,L298N和野火使用MOS管搭建的驱动板的程序是一样的。

将程序编译下载后,使用Type-C数据线连接开发板到电脑USB,打开野火调试助手-PID调试助手来观察电机的运行效果。 并通过上位机来控制电机,下图是电机运行效果图。

速度环位置式PID控制效果

6.3. 直流电机电流环控制-增量式PID实现

6.3.1. 软件设计

增量式PID与位置式PID总体上看就是实现算法的部分有所不同,但是实际控制输出等其他代码功能是完全相通的, 所以在此只介绍增量式PID算法与位置式的不同之处。下面我们详细来看。

6.3.1.1. 新建工程

我们直接在基础部分的直流有刷章节的 “206_Motor_BDC_PID_Current_Positional” 例程的基础上修改程序。

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程 “206_Motor_BDC_PID_Current_Positional”, 然后将工程文件夹重命名为 “207_Motor_BDC_PID_Current_Incremental”,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程 “206_Motor_BDC_PID_Current_Positional”, 然后将工程文件夹重命名为 “207_Motor_BDC_PID_Current_Incremental”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

6.3.1.2. PID算法初始化

增量式PID实现的速度环控制和位置式PID现实的速度环控制其控制代码大部分都是一样的, 在上面的编程要点中只有第4项是不同的,其他代码均相同,所以这里将只讲解不一样的部分代码, 完整代码请参考本节配套工程。

bsp_pid.c-增量式PID参数初始化
/**
* @brief  PID参数初始化
*     @note   无
* @retval 无
*/
void PID_param_init()
{
      /* 初始化参数 */
      pid.target_val=50;
      pid.actual_val=0.0;

      pid.err = 0.0;
      pid.err_last = 0.0;
      pid.err_next = 0.0;

      pid.Kp = 0;
      pid.Ki = 2.8f;
      pid.Kd = 0;

      float pid_temp[3] = {pid.Kp, pid.Ki, pid.Kd};
      set_computer_value(SEND_P_I_D_CMD, CURVES_CH1, pid_temp, 3);     // 给通道 1 发送 P I D 值

}

PID_param_init()函数把结构体pid参数初始化,将目标值pid.target_val设置为50.0,将实际值、偏差值和上一次偏差值等初始化为0, 其中pid.err用来保存本次偏差值,pid.err_last用来保存上一次偏差值,pid.err_next用来保存上上次的偏差值; set_computer_value()函数用来同步上位机显示的PID值。

6.3.1.3. PID算法实现

这个函数主要实现了增量式PID算法,用传入的目标值减去实际值得到误差值得到当前偏差值, 在第6~8行中实现了下面公式中的增量式PID算法。

../../_images/PID_lisan43.png ../../_images/PID_lisan63.png

然后进行误差传递,将本次偏差和上次偏差保存下来,供下次计算时使用。 在第6行中将计算后的结果累加到pid.actual_val变量,最后返回该变量,用于控制电机的PWM占空比。

bsp_pid.c-增量式PID算法实现
/**
  * @brief  PID算法实现
  * @param  actual_val:实际值
  *   @note   无
  * @retval 通过PID计算后的输出
  */
float PID_realize(float actual_val)
{
  /*计算目标值与实际值的误差*/
    pid.err=pid.target_val-actual_val;

  /*PID算法实现*/
  pid.actual_val += pid.Kp*(pid.err - pid.err_next)
                  + pid.Ki*pid.err
                  + pid.Kd*(pid.err - 2 * pid.err_next + pid.err_last);

  /* 限幅输出,防止深度饱和 */
  if(pid.actual_val > CLK_GPT_COUNTS) pid.actual_val = CLK_GPT_COUNTS;
  else if(pid.actual_val <= 0) pid.actual_val = 0;

  /*传递误差*/
  pid.err_last = pid.err_next;
  pid.err_next = pid.err;
  /*返回当前实际值*/
  return pid.actual_val;
}

6.3.2. 下载验证

我们按前面介绍的硬件连接好电机和驱动板,L298N和野火使用MOS管搭建的驱动板的程序是一样的,

将程序编译下载后,使用Type-C数据线连接开发板到电脑USB,打开野火调试助手-PID调试助手来观察电机的运行效果。 并通过上位机来控制电机,下图是电机运行效果图。

速度环位置式PID控制效果