2.3.1 解决的问题. U' x6 n4 l) v2 F- R
( C/ u% C0 f5 y
+ ^# O8 K( n5 G解决带编码器直流电机的速度闭环问题。 5 q. y% {# A; [1 Q& P3 e* F8 j
2.3.2 PID理论
" G l: T+ V. j8 }
, Y. R: b2 ~7 K. z7 m将偏差的比例、积分、微分,通过线性组合构成控制量,用控制量对被控对象进行控制,这样的控制器称为PID控制器。在连续空间中,我们通常探讨模拟PID的控制原理,如图所示:
6 Z( U. F" ]& ?& @# b$ n

& Z( x* Q' |7 r9 E# V; Z1 `
我们这里用电机速度控制为例,讲解PID控制系统。r(t)为设定电机速度、y(t)为实际电机速度、e(t)=y(t)-r(t)为速度差值作为PID控制器的输入、u(t)为PID控制器的输出,作用到被控对象电机上。根据模拟PID控制器,科学家们也得出了模拟PID控制的公式,如图所示: 
+ Q, G/ C( k h/ E2 j, |0 ^$ l
其中Kp、Ti、Td,分别为控制器的比例系数、积分系数、微分系数。该理论用在控制的例子比比皆是。但是模拟PID控制系统是在连续空间的上描述的,无法在计算机上用代码实现。于是就有数字PID控制理论,将连续空间的PID控制系统在离散空间上描述。积分变成了求和、微分变成了求斜率,于是就出现数字PID控制系统的理论公式,如图所示: " q7 ]- T* ~% @9 }5 d) I1 M, S

# [8 {& l3 `# {, f
其中Kp、Ti、Td和上面描述的一样,T为采用周期,ek是本次差值,ek-1上一次的差值,直接通过模拟PID转化的数字PID又叫做位置式PID,该方式的PID的输出直接是控制量,非常不适合经常出现异常的系统,另外一种方式是增量式PID,每次只输出一个正向或者反向的调节量,就算出现异常,也不会产生巨大的影响。具体数学公式如下所示:该方法较多的应用于生产生活中,本论文中电机的速度PID控制当然也不例外。
- k' L0 D- Y% M" H

2 |% R4 c: B+ m3 R1 r3 G- b+ q
有了上面的理论基础,开始代码实现的介绍。首先就是明确增量式PID系统的输入、输出、控制对象。将速度的设定值和速度的测得值作为PID控制器的输入参数,PID的输出参数为对PWM的调节偏差,控制对象PWM进而驱动电机达到设定速度。以上内容确定之后,就是PID控制器的代码部分了。其实仔细看看增量式PID就只有一个公式,所以使用代码实现并不困难。如下所示核心代码就这一句。 3 h, Y4 F5 ^& C; c1 G; Y

# h, m1 l% M- y4 q6 }7 e& J: L$ b
完成上面的代码,只是完成速度PID的一部分,剩下的是尤为重要的PID参数整定。该整定方法丰富多样,最为准确的是模型计算,但是对于我们做机器人多使用试凑法。虽然需要调节一段时间,但是不需要对机器人进行建模。试凑法一般按照P、I、D的顺序进行调节。
u* r+ r* I* ~- r# f4 r5 Z. l( V7 x. M
初始时刻将Ki和Kd都设置成0,按照经验设置Kp的初始值,就这样将系统投入运行,由小到大调节Kp。求得满意的曲线之后,若要引入积分作用,将Kp设置成之前的5/6,然后Ki由小到大开始调节。达到满意效果之后,若要引入微分作用,将Kd按照经验调节即可。经过有规律的试凑,最终达到一个我们满意的就行。 / R4 D6 _7 b) D* h
; `- H! @; I& U8 @: ~5 |& X D2 b
! V: ^% ^+ e2 a( h, r; W% m( L
, c8 V) a5 b( s9 `; P
2.3.3 代码分享( a% s: k) H, T5 |
6 A# v! t4 ^* _* R3 f+ T(1)pid.h - #include "pid.h"
, k. i& e/ M Q1 E3 i" T8 i - & Q6 P0 E4 W0 i
- " C2 k, j" y7 U" N) x- ~
- struct pid_uint pid_Task_Letf;
% w/ O* H) |2 u$ _ - struct pid_uint pid_Task_Right;
& ^/ n1 ^5 ?! { - $ \; { T, ~8 G1 u1 U5 G! W
- /****************************************************************************
) P; U R. |% Q5 e7 y) \4 W: g - *函数名称:PID_Init(void)% S4 X: v# I4 [' O5 `
- *函数功能:初始化PID结构体参数
2 d' T! v, P, F - ****************************************************************************/
1 L6 L' ~& [3 A1 T
! b$ W) D9 e4 z7 K! B, X+ O4 ]- void PID_Init(void)
9 r6 X* L1 }4 d' x6 j; W - {
# L% p, i- h" A) [ ]) Y! Z: A q' S - //乘以1024原因避免出现浮点数运算,全部是整数运算,这样PID控制器运算速度会更快# S6 Q8 N2 m( C0 E
- /***********************左轮速度pid****************************/
3 E! U4 _0 z! y1 g - pid_Task_Letf.Kp = 1024 * 0.5;//0.4
* \! c' ?& O2 W |% D" I - pid_Task_Letf.Ki = 1024 * 0;
, f) Y" U$ I8 {; X: C - pid_Task_Letf.Kd = 1024 * 0.08; 2 |% ~0 M( v5 G! S5 b9 T! {# k
- pid_Task_Letf.Ur = 1024 * 4000;9 C+ O$ j9 e* T" p; h* I1 H
- pid_Task_Letf.Adjust = 0;4 X! R5 w9 ^0 R. K
- pid_Task_Letf.En = 1;
2 N) X, a1 n* h9 n' Y2 v3 G8 F2 j. C - pid_Task_Letf.speedSet = 0;) r' A; Q5 y2 D" p( R
- pid_Task_Letf.speedNow = 0;. s8 U; A k# }3 J' Y
- reset_Uk(&pid_Task_Letf); , ?/ S% }/ J `# k. L7 g, }
- /***********************右轮速度pid****************************/6 Z# w" h: m" q6 Q% u2 i& c7 \7 N
- pid_Task_Right.Kp = 1024 * 0.35;//0.2
% Z* H ]. b9 T: C% P - pid_Task_Right.Ki = 1024 * 0; //不使用积分
! }0 t4 v% @3 i& _. ?& ?) L - pid_Task_Right.Kd = 1024 * 0.06;
Q3 ^1 K: C) T, a# } - pid_Task_Right.Ur = 1024 * 4000;8 ^$ w1 d2 v% d: Y
- pid_Task_Right.Adjust = 0;
" `& K0 q* b3 M% Q* {/ A$ [+ T7 E - pid_Task_Right.En = 1;
3 |5 R# P# Q9 ?, v0 X/ o! o - pid_Task_Right.speedSet = 0;
7 d6 e7 w5 R, x& W - pid_Task_Right.speedNow = 0;. R& a9 |4 {; i1 F$ E- u( V
- reset_Uk(&pid_Task_Right);
6 `, L% C; H: u* O/ r - }
) Z+ h1 V8 a; Q+ P$ h/ r
( U$ L# [, o3 X' {9 }- /***********************************************************************************************7 B1 Z. P# J7 D% r0 M: P
- 函 数 名:void reset_Uk(PID_Uint *p)
( [7 B. v/ ^: q" U - 功 能:初始化U_kk,ekk,ekkk$ ^+ I8 t! q6 N: T5 Q5 R5 I
- 说 明:在初始化时调用,改变PID参数时有可能需要调用
4 [: s0 B* j# C% D9 A" c4 d - 入口参数:PID单元的参数结构体 地址
5 B2 V3 R0 l( |$ A2 q1 U - ************************************************************************************************/6 s( `3 r e5 X1 z$ l& B% F
- ' D$ [2 l2 G% _# d- M8 A u, V. i
- void reset_Uk(struct pid_uint *p)/ x4 @" R3 ~2 S9 n: ]' q
- {2 E4 S$ Y' L0 s6 ], q
- p->U_kk=0;
) y. q* E+ M0 r2 u - p->ekk=0;$ E. \- j$ I) A1 @; n+ ^
- p->ekkk=0;
& I6 j* ~& @7 | - }
) y0 L. Y$ E0 a5 p$ o8 G' J- g1 C - * g4 [0 U: r% u# h' L
- /*********************************************************************************************** ~6 {" L* h" d- |) V! y& p& Z* r
- 函 数 名:s32 PID_commen(int set,int jiance,PID_Uint *p)$ i0 p0 w. b9 S
- 功 能:PID计算函数" w6 J1 f1 D2 v( ^( B
- 说 明:求任意单个PID的控制量
[# r! Q. z- G' j" c% N1 |' o" p- q - 入口参数:期望值,实测值,PID单元结构体
" u2 J. _, D1 Z1 W. ]/ j" Q( r - 返 回 值:PID控制量% e2 j2 _6 {# n9 R* V6 E
- ************************************************************************************************/
! ?# \% Q& }" U2 g( y/ E - h. u' e1 a/ H/ ? ?
- s32 PID_common(int set,int jiance,struct pid_uint *p)
$ r U4 I3 u+ j1 Z - {
) X, o9 g& z+ y7 l# j& [ - int ek=0,U_k=0;: I0 t3 l" N+ K7 s/ T" B
- - B& U$ V0 |4 L. T. w7 z( o
- ek=jiance - set;
7 x" a5 c" y# {5 p8 m -
( `4 f7 t9 S, D: \: C - U_k=p->U_kk + p->Kp*(ek - p->ekk) + p->Ki*ek + p->Kd*(ek - 2*p->ekk + p->ekkk);+ d2 z- P" \9 r3 x9 V, @% @ c* I5 q
-
+ R( K7 c2 v- m7 e. ^ - p->U_kk=U_k;1 ~( e+ D% B& e3 R/ A
- p->ekkk=p->ekk;
2 \' Y4 ^5 j% i- D2 l) t# u - p->ekk=ek;- Y: l5 O& `" b
- ! d, ?: g# J* m, S" L! C- P6 L y
- if(U_k>(p->Ur))
Z# q+ R. H7 L6 W - U_k=p->Ur;
# z. j1 z* Q9 \7 t8 o& n& _' ` - if(U_k<-(p->Ur))
! t3 K1 u- r6 e9 m) } - U_k=-(p->Ur);3 K1 u$ T4 R1 F* n$ ^
- 1 L* R/ e; |/ F) A3 ~6 i
- return U_k>>10;
x' h! E+ I" i) x - }# |9 s; p% m" S n( O
- % o& X' t d( K& r# b, t, ~$ C
- /***********************************************************************************
9 K* H1 H+ R! O( Y4 R/ W - ** 函数名称 :void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)
- }/ @; m6 d' W0 z1 W) ?# |& o - ** 函数功能 :pid选择函数
& E! [( W \3 T4 ?# p9 Y. M1 [' e- T - ***********************************************************************************/
% j2 O$ [+ g2 a; U; ? - / h! R% C* E/ K; o7 n
- void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)# P0 i6 N6 D. _7 o
- {6 b; E1 ?4 y# Y' z3 g8 I
- /**********************左轮速度pid*************************/; e: J8 ~9 `: _) f8 T4 o
- if(pl->En == 1)
6 u) N% k1 H8 x5 x - { : w/ e/ X! f0 G" A6 B$ V
- pl->Adjust = -PID_common(pl->speedSet, pl->speedNow, pl);
3 o s6 a- a$ q" x' ]9 Q3 p% { - }
! \6 J0 l5 T% T1 m; z0 Z1 Z - else
8 j! l7 b1 g, L; l. s+ U - {. R, _1 O; j. p7 J+ E6 A9 ~ ?# S
- pl->Adjust = 0;
4 h( s6 H6 ~, ^1 f8 [( y - reset_Uk(pl);& l! y# h3 v1 Q# A
- pl->En = 2;
y( D3 k% j0 K1 ?" Y - }
( K, ^, K8 m8 e - /***********************右轮速度pid*************************/
8 Y' Q+ N4 O# W% N# H. _ - if(pr->En == 1)
5 u6 R; e D- Q - {
8 W; c# x* o( [4 I - pr->Adjust = -PID_common(pr->speedSet, pr->speedNow, pr);
% Y' G/ _% o* I7 p9 z$ i' t - } 6 u! p E* A% G& m! z
- else
6 v! j o" `- S/ P - {# a+ K7 Y! d) T) |3 t4 b1 p
- pr->Adjust = 0;
* G5 M. T8 L/ D5 z' h' p5 a1 ? - reset_Uk(pr);
K& j- u& W3 f$ d% Q - pr->En = 2;
/ o* |: Q3 X1 \- b2 d - }, k( [* S2 D8 N0 I, J
- }% s* Y6 J& U0 w5 V& w
- . s1 f! f$ G5 V" i% j
- /*******************************************************************************, v* S$ a- v! W# {( u
- * 函数名:Pid_Ctrl(int *leftMotor,int *rightMotor)2 ]* H0 M) R! Y2 m* [2 s
- * 描述 :Pid控制
( a0 v1 r4 w8 i6 s/ J$ N( Z9 X - *******************************************************************************/1 n1 r! z: t7 Z! O- @
- ' P9 D$ ^" b4 H2 Y# \! H* q, t! w& s
- void Pid_Ctrl(int *leftMotor,int *rightMotor)' i8 u7 e1 ~7 ?5 Q% h: V/ X1 {2 U
- {# V5 h+ M3 S. J8 b: U
- Pid_Which(&pid_Task_Letf, &pid_Task_Right); / p6 A/ ]' V8 l. X3 d$ S0 Q
- *leftMotor += pid_Task_Letf.Adjust;
1 y5 x: t( O) d* j - *rightMotor += pid_Task_Right.Adjust;: N, ?6 [- a+ }7 z
- }
+ ?+ |3 d$ t* B
复制代码 0 ?, E7 F, w+ c3 {1 R0 f: J
$ b' h9 k% R3 M% y: E# q
(2)main.c
0 d q6 j y& O3 w- d6 p( N
9 {- x. a) q0 e: f
- #include "sys.h"8 E1 Y5 c. _, c) r9 [3 V
- 6 I! f. U' z. K% l
- //====================自己加入的头文件===============================
) @" m4 V1 @, z. l: t& k* T8 [ - #include "delay.h"
5 b. `1 c5 ~5 O5 y+ L. Z. ?) ? - #include "led.h"
' C/ i$ S% A- d; E4 q1 |. Z - #include "encoder.h"
3 Z8 F) m' G3 r4 P0 w9 i9 X; x+ h - #include "usart3.h"; H% @& c9 S: p0 f( y
- #include "timer.h"
& Q3 e6 ?4 w$ u# v6 H3 K: W0 ^ - #include "pwm.h"7 Y, i! G0 [# C" e
- #include "pid.h"
6 d% J6 L, I ~) p/ ? - #include "motor.h"0 o( ?; ~" f4 c
- #include <stdio.h>
+ N1 {- p5 D; t7 C+ l( J - /*===================================================================
% ~8 ~$ s/ B+ L& N1 @8 | - 程序功能:直流减速电机的速度闭环控制测试' C+ m: u, T8 f! L( W; P2 N
- 程序编写:公众号:小白学移动机器人
8 ~& t/ M6 S6 L2 L - 其他 :如果对代码有任何疑问,可以私信小编,一定会回复的。+ U' x( P' n) v! S: `0 P ]
- =====================================================================
7 d; `- l% r! W* [4 P7 ~ - ------------------关注公众号,获得更多有趣的分享---------------------
4 K' Q1 u( \9 A% g0 J% W& P - ===================================================================*/
4 V3 n; v8 n) d* C. z! L9 W6 Y - int leftSpeedNow =0;9 X8 d Y) W' A+ b
- int rightSpeedNow =0;: i3 P- |5 G) Z5 C8 g& E
7 \5 g2 B5 |0 H) j- int leftSpeeSet = -300;//mm/s
" V C) u A: J3 } - int rightSpeedSet = -300;//mm/s
& V* ~& a$ c) w! F8 Y - & U/ R/ i( ?+ ^" }" i5 y& {
- int main(void)
+ ^- X) q) A7 E1 L - { # {( v L# \% V/ M
2 c% ?& Z, A' G& K1 k1 |- GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);' L4 f2 R) O, x4 x+ S9 G& q6 H
- GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁用JTAG 启用 SWD8 {. x) F! w4 o# N. o8 Q) [
- Z( E7 D; ^* b" w
- MY_NVIC_PriorityGroupConfig(2); //=====设置中断分组8 O+ V' B' x# e# ?% d- z1 O8 }
- & C0 K' G2 _1 R7 Z# Y
- delay_init(); //=====延时函数初始化
- w, }# x3 c4 y# f; j$ ]" l - LED_Init(); //=====LED初始化 程序灯
5 N p# B8 H3 U$ I: m2 D& A - % l$ S8 y" i; L9 g
- usart3_init(9600); //=====串口3初始化 蓝牙 发送调试信息
& x( }7 h# N- r4 t9 w
: r8 q4 k1 S! C! F1 x) O- Encoder_Init_TIM2(); //=====初始化编码器1接口, m) C( y1 `4 I8 Q& c4 h7 g- _
- Encoder_Init_TIM4(); //=====初始化编码器2接口/ b6 k+ |1 W' a' l2 r: f
- # }3 O) C* t5 k& x+ W J d2 a
- Motor_Init(7199,0); //=====初始化PWM 10KHZ,用于驱动电机 如需初始化驱动器接口
- d$ j% S' x" \& K -
/ T6 n/ G7 l; @5 V0 v; K4 s; Z - TIM3_Int_Init(50-1,7200-1); //=====定时器初始化 5ms一次中断4 T! X1 q: }- A; H5 E/ V/ z
; b6 w7 c' Z8 ]9 K. S6 z$ ]* t- PID_Init(); //=====PID参数初始化7 D# o! A( N8 E7 a( w, ? m% M
-
5 a! P0 c' w) ? - while(1)7 o% D. ?4 D* c- z
- {
! p& P0 p! B* x - //给速度设定值和实时值赋值
* d) h3 R: F, u9 {) | - pid_Task_Letf.speedSet = leftSpeeSet;
: v t- n9 X- R1 f, K M9 G - pid_Task_Right.speedSet = rightSpeedSet;! Y' E' B! c S0 Q1 A
- pid_Task_Letf.speedNow = leftSpeedNow;
1 |: Y0 I% k5 V5 y8 |( W. f; x - pid_Task_Right.speedNow = rightSpeedNow;
# U8 D+ L6 R/ m) C- i9 T" W - : D* Y/ f0 B0 Y) t: A7 b: i
- //执行PID控制函数. s% d6 q% V2 k p
- Pid_Ctrl(&motorLeft,&motorRight);
+ S6 B/ l6 s4 r7 q1 O# s -
4 K. {" @3 U( F) c - //根据PID计算的PWM数据进行设置PWM8 S8 u' H3 ?6 m0 i$ G
- Set_Pwm(motorLeft,motorRight);! x! o7 \: v6 h3 }* N9 C
-
' m+ m, d4 p r( i. e& F! s7 K - //打印速度- F* ~- ^4 l* o5 ], n3 x
- printf("%d,%d\r\n",leftSpeedNow,rightSpeedNow);4 i0 I, k! O1 f7 ]4 Z! q' r/ c
- }
% Z. H5 f4 J' A+ h4 M. p - }/ g/ ~8 F$ S# ~
- ( j6 b) F0 X! g/ C
- //5ms 定时器中断服务函数4 T( T( r h: C1 y: n
. O# F7 ]& o# S0 o1 h- void TIM3_IRQHandler(void) //TIM3中断6 @/ s) E/ ~! p: z
- {
2 { d% o6 v" G+ @' b# D5 V - if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否
' O5 ]4 ^9 [6 ~0 }- b; ?/ E5 e - {! u$ Z$ F5 F- r8 s% K: h3 Y
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位. H, `, h$ F- L# D0 a9 d# N& | |1 R
-
: w3 k* h5 X0 N/ {2 [ - Get_Motor_Speed(&leftSpeedNow,&rightSpeedNow);//计算电机速度
% v* ~. p; K) A, n6 n: E -
" m9 w& J2 R/ {6 \- Q; d6 O8 Z& t9 ` - Led_Flash(100); //程序闪烁灯
" K) Q. Z( K: P: C0 ~* Q9 p% [ - }; l2 t) X) K$ Z9 B3 ]4 f5 v6 ]
- }
复制代码 7 j/ f& m9 ^/ E5 N
: X7 g6 J2 K) S3 V1 S1 o5 x2 t
2.3.4 总结+ Z+ r) w6 C$ N) B, k
1 X. Z C! w7 j: ^) W, g% S V+ I
以上三篇内容,是关于直流减速电机的PWM控制、速度测量以及最后电机速度的闭环控制。现在对于电机的简单控制基本告一段落,对于做一个ROS小车的电机控制,这里基本是足够的。下面我们会介绍使用IIC+DMP获取MPU6050数据。
$ M$ O" b! H4 \" {/ ?: n/ K9 P7 j4 y6 ~! z9 O: W2 v8 d, H& d }
3 D7 @! B8 k& l! ~" F& G* Q" [: V$ d. B4 i# G$ k* h+ X, R
|