PID 定距功能
本节介绍
📝本节您将了解 PID 定距功能的实现方式,通过实际值与目标值的误差,实现让实时角度以最快的时间接近目标角度。
🏆本章⽬标
1️⃣了解角度与编码器脉冲数的换算;
2️⃣实现PID定距
使用PID实现定距闭环控制
在之前的编码器驱动章节,我们知道了当电机转动时我们会采集到编码器的脉冲数,顺时针转则脉冲数为正,逆时针旋转则脉冲数为负。那么我们能不能通过了解电机转一圈产生的脉冲数,来计算旋转的角度,以及行驶距离呢?可以。
目前已知参数
13 线的编码器,软件代码同时检测 AB 相变化(即 2 倍脉冲数记录),电机减速比 1:48
![]() |
---|
则电机经过减速器后旋转一圈的编码器脉冲数为 ( 13 x 2 ) x 48 = 1248 (脉冲/转)
角度换算过程
又已知转一圈 = 360°,则每一个脉冲的角度为:
则指定角度的脉冲数为:
这里有一个地方需要注意,目前编码器的数据是每隔20ms就清零,无法做到一直获取当前实时脉冲数。
原来的代码
//编码器数据更新
//请间隔一定时间更新
void encoder_update(void)
{
motor_encoder.count = motor_encoder.temp_count;
//确定方向
motor_encoder.dir = ( motor_encoder.count >= 0 ) ? FORWARD : REVERSAL;
motor_encoder.temp_count = 0;//编码器计数值清零
}
...
//电机编码器脉冲计数
void TIMER_TICK_INST_IRQHandler(void)
{
//20ms归零中断触发
if( DL_TimerA_getPendingInterrupt(TIMER_TICK_INST) == DL_TIMER_IIDX_ZERO )
{
//编码器更新
encoder_update();
if( get_task_status() == TASK_ENABLE )
{
//按键扫描+事件管理
flex_button_scan();
}
}
}
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
现在需要我们手动的去增加一个判断,只有当前功能不是定距功能时,才在定时器中断处理编码器的速度更新。
新代码
//电机编码器脉冲计数
void TIMER_TICK_INST_IRQHandler(void)
{
//20ms归零中断触发
if( DL_TimerA_getPendingInterrupt(TIMER_TICK_INST) == DL_TIMER_IIDX_ZERO )
{
//编码器更新
if( get_functional_mode() != DISTANCE_FUNCTION )
{
encoder_update();
}
if( get_task_status() == TASK_ENABLE )
{
//按键扫描+事件管理
flex_button_scan();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
现在,我们可以使用 指定角度的脉冲数
作为目标值,当前脉冲数
作为实时值,将两者传入PID控制器中,即可完成旋转指定角度功能。
![]() |
---|
实现的代码:
//每脉冲度数 0.28846f = 360.0f / 1248.0f
#define DEGREES_PER_PULSE 0.28846f
/************************************************
功能:PID电机定距控制器
参数:target_speed = 目标值
返回:对应PID的地址
************************************************/
PID motor_distance_control(int target_angle)
{
int PWM, target_pulses;
// 将目标角度换算成目标脉冲数
target_pulses = target_angle / DEGREES_PER_PULSE;
//传入PID静态参数、目标值(目标脉冲数)、当前值(当前编码器获取的实时脉冲数)
//PID输出的值为控制信号,传入到PWM变量中
PWM = pid_calc( &distance_pid, target_pulses, get_temp_encoder() );
//控制信号转为电机实际控制值
if( PWM > 0 )//控制信号大于0
{
set_motor(0, PWM);//顺时针旋转,pwm为旋转速度
}
else if( PWM < 0 )
{
PWM = -PWM;//将负数转为正数(例如PWM=-10,则-(-10)=10)
set_motor(PWM, 0);//逆时针旋转,pwm为旋转速度
}
return distance_pid;
}
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
需要注意一个问题,电机是PWM驱动的,PWM的范围是0~9999。我们将PID控制器输出的正负值或者小数点等结果都转为 PWM 的0~9999的范围,这样电机才能够转动。正负值或者小数点的处理上面的功能函数 PID motor_speed_control(int target_speed) 已经实现了,但是如果PID计算的值超过了PWM的最大值 9999 了怎么办?所以我们还需要限制。
/************************************************
功能:初始化PID各参数
参数:pid = 对应pid的结构体地址
p = pid的静态kp值
i = pid的静态ki值
d = pid的静态kd值
maxI = pid计算后的I最大值,即最大误差累加值
maxOut = PID最大输出值
target = 目标值
************************************************/
void pid_init(PID* pid, float p, float i, float d, float maxI, float maxOut, int target)
{
pid->kp = p;
pid->ki = i;
pid->kd = d;
pid->max_change_i = maxI;
pid->max_output = maxOut;
pid->target = target;
}
....
void main(void)
{
//传入distance_pid地址
//设定 kp 为75
//设定 ki 为2
//设定 kd 为10
//设定 I的最大值 为9999
//设定 PID最大输出值 为9999
//设定 目标值 为 90
pid_init(&distance_pid, 75, 2, 10, 9999, 9999, 90 );
while(1)
{
//实现定速功能
motor_distance_control( distance_pid.target );
}
}
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
案例验证
使用上一章节的PID定速模式进行编写。复制粘贴重新命名为 09_distance_pid
。
在 app 文件夹下新增了 app_distance_pid.c 和 app_distance_pid.h。
与上一章节案例的代码更改说明
- 增加PID定距功能;
- 增加PID定距波形显示;
- 增加PID定距参数更新显示;
- 增加长按定距参数快速更新;
- 增加编码器连续读取不清零功能;
- 定时器中断根据功能区分出定速编码器读取和定距编码器读取功能;