6. 直流电机电流环控制实现¶
通过上一章节《直流电机速度环控制实现》的学习,我们大致的了解了PID在实际应用中是如何发挥它自动控制的作用的。 在实际应用中,并不仅仅只有使用速度作为反馈来调节直流电机工作。 相反地,现实中有许多参数可以作为反馈量,为特定场景下的直流电机控制带来更好的效果,例如本章节要讲到的电流环控制。 电流环控制,简单的说,就是希望以恒定的电流来驱动电机运转,即希望电机能输出恒定的转矩。 接下来,本章节就来介绍一下使用电流环来实现直流电机的电流控制。
本章通过我们前面学习的位置式PID和增量式PID两种控制方式分别来实现电流环的控制, 如果还不知道什么是位置式PID和增量式PID,请务必先学习前面PID算法的通俗解说这一章节。
6.1. 硬件设计¶
本章实验需要连接开发板和驱动板,这里给出接线表。
6.2. 直流电机电流环控制-位置式PID实现¶
6.2.1. 编程要点¶
配置定时器可以输出PWM控制电机
配置定时器可以读取当前电路中驱动电机的电流值
配置基本定时器可以产生定时中断来执行PID运算
编写位置式PID算法
编写电流控制函数
增加上位机曲线观察相关代码
编写按键控制代码
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_VAL 和 filtered_current 计算出电压值,再通过 GET_ADC_CURR_VAL 转换得到实际电流值。 然后,使用计算得到的电流值作为输入,调用 motor_pid_control 函数进行 PID 电机控制。
// 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函数¶
在主函数里面首先做了一些外设的初始化,然后通过上位机来观察、控制电机。
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调试助手来观察电机的运行效果。 并通过上位机来控制电机,下图是电机运行效果图。

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项是不同的,其他代码均相同,所以这里将只讲解不一样的部分代码, 完整代码请参考本节配套工程。
/**
* @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算法。


然后进行误差传递,将本次偏差和上次偏差保存下来,供下次计算时使用。 在第6行中将计算后的结果累加到pid.actual_val变量,最后返回该变量,用于控制电机的PWM占空比。
/**
* @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调试助手来观察电机的运行效果。 并通过上位机来控制电机,下图是电机运行效果图。
