2.3.1 解决的问题
2 a) ]5 r; M' E9 _4 `
! P- g* L& U4 W0 R& \0 ?' V; ?% P, z# \
解决带编码器直流电机的速度闭环问题。
5 J" b# m7 m/ Y1 o& ?5 k 2.3.2 PID理论
, A3 J G, `0 W+ n0 T6 Z( g. \( R# j' Y2 V4 B
将偏差的比例、积分、微分,通过线性组合构成控制量,用控制量对被控对象进行控制,这样的控制器称为PID控制器。在连续空间中,我们通常探讨模拟PID的控制原理,如图所示: 0 h; ^- K+ ^) x9 V7 r0 M8 I

$ I" {. F& A4 [% o7 h
我们这里用电机速度控制为例,讲解PID控制系统。r(t)为设定电机速度、y(t)为实际电机速度、e(t)=y(t)-r(t)为速度差值作为PID控制器的输入、u(t)为PID控制器的输出,作用到被控对象电机上。根据模拟PID控制器,科学家们也得出了模拟PID控制的公式,如图所示: 
7 H9 Q. T# C0 G6 v2 S
其中Kp、Ti、Td,分别为控制器的比例系数、积分系数、微分系数。该理论用在控制的例子比比皆是。但是模拟PID控制系统是在连续空间的上描述的,无法在计算机上用代码实现。于是就有数字PID控制理论,将连续空间的PID控制系统在离散空间上描述。积分变成了求和、微分变成了求斜率,于是就出现数字PID控制系统的理论公式,如图所示:
8 V0 G1 B- X) d! k4 i

* q+ c0 u$ G( G6 v9 m8 f: E( \
其中Kp、Ti、Td和上面描述的一样,T为采用周期,ek是本次差值,ek-1上一次的差值,直接通过模拟PID转化的数字PID又叫做位置式PID,该方式的PID的输出直接是控制量,非常不适合经常出现异常的系统,另外一种方式是增量式PID,每次只输出一个正向或者反向的调节量,就算出现异常,也不会产生巨大的影响。具体数学公式如下所示:该方法较多的应用于生产生活中,本论文中电机的速度PID控制当然也不例外。
/ g. u6 ^2 I- L) }3 b0 u; ~% U5 E' k

( e2 F5 p+ `+ a2 j+ o$ l* f% @
有了上面的理论基础,开始代码实现的介绍。首先就是明确增量式PID系统的输入、输出、控制对象。将速度的设定值和速度的测得值作为PID控制器的输入参数,PID的输出参数为对PWM的调节偏差,控制对象PWM进而驱动电机达到设定速度。以上内容确定之后,就是PID控制器的代码部分了。其实仔细看看增量式PID就只有一个公式,所以使用代码实现并不困难。如下所示核心代码就这一句。 d; M5 n9 ^: J- N( }& J+ G C9 Q

$ q6 e" W4 i8 q4 y) ~* z4 z; k$ }3 |
完成上面的代码,只是完成速度PID的一部分,剩下的是尤为重要的PID参数整定。该整定方法丰富多样,最为准确的是模型计算,但是对于我们做机器人多使用试凑法。虽然需要调节一段时间,但是不需要对机器人进行建模。试凑法一般按照P、I、D的顺序进行调节。
h! W7 q! b* j
初始时刻将Ki和Kd都设置成0,按照经验设置Kp的初始值,就这样将系统投入运行,由小到大调节Kp。求得满意的曲线之后,若要引入积分作用,将Kp设置成之前的5/6,然后Ki由小到大开始调节。达到满意效果之后,若要引入微分作用,将Kd按照经验调节即可。经过有规律的试凑,最终达到一个我们满意的就行。 6 c9 N' m, g3 S# v! X6 S2 `
% ^- c& U: ~, L U# g" Q
+ [& r5 G$ u. ~* R* d! f 8 }9 W( o3 @) P
2.3.3 代码分享$ W: o, R/ _$ ~) t/ E* L. e
& N2 ~& l) [8 a, V+ g
(1)pid.h - #include "pid.h", e: z+ ~5 T8 _& T8 @& y: z. O8 R
$ x: a( Z1 B2 t& j) A! v7 q- 1 Y1 B A' J! L) d1 n3 x0 o
- struct pid_uint pid_Task_Letf;
5 W( M% E7 B8 u! h3 G( a - struct pid_uint pid_Task_Right;! o7 w# q" }8 a
: W" X; I3 O' O+ ?# F- /****************************************************************************
& f% q. B7 u1 d, F; p - *函数名称:PID_Init(void)' f2 s' u1 G: ` i$ g3 j; k/ e
- *函数功能:初始化PID结构体参数
7 U/ z. u$ c7 O1 i# I - ****************************************************************************/+ b/ _3 Z1 w. B2 h* C' M
u: j4 `7 c. F! J; ?- void PID_Init(void)
* ?$ [) e+ l& u2 l' q, b - {
8 a. n, \6 ]. A5 Y* | - //乘以1024原因避免出现浮点数运算,全部是整数运算,这样PID控制器运算速度会更快
$ H& [, j$ y& Z1 D7 \ - /***********************左轮速度pid****************************/
4 e9 M/ B& i ~( u+ V - pid_Task_Letf.Kp = 1024 * 0.5;//0.4
# N5 _, D( g; e! `# U3 _ - pid_Task_Letf.Ki = 1024 * 0; 5 g8 A- u5 t. n" k: a* b* l/ r# B2 g1 \
- pid_Task_Letf.Kd = 1024 * 0.08;
: H( V+ v9 |. f* x( k0 v0 s - pid_Task_Letf.Ur = 1024 * 4000;0 c4 G# g. O, p L* D, N u; ~
- pid_Task_Letf.Adjust = 0;
- `" W! H6 ]1 [4 u, D - pid_Task_Letf.En = 1;3 \3 @. C0 F+ a1 y
- pid_Task_Letf.speedSet = 0;' J o j6 I7 A% r
- pid_Task_Letf.speedNow = 0;# @' J8 z8 H: k0 M
- reset_Uk(&pid_Task_Letf);
4 R4 y/ Z! q) e - /***********************右轮速度pid****************************// x' t8 n6 M2 ^0 u1 }" m% m5 G$ U
- pid_Task_Right.Kp = 1024 * 0.35;//0.24 r) C: H3 K+ O; H& K- ~
- pid_Task_Right.Ki = 1024 * 0; //不使用积分3 `( b, g9 W2 ~6 t! A7 x
- pid_Task_Right.Kd = 1024 * 0.06;
0 V2 M d% D8 e - pid_Task_Right.Ur = 1024 * 4000;( b& G+ W* w9 @. G
- pid_Task_Right.Adjust = 0;5 v. ?* B- O7 X+ D% k; z; V
- pid_Task_Right.En = 1;# Z% T' N7 Z1 |1 L$ c. [
- pid_Task_Right.speedSet = 0;
. b4 V7 K% l* g3 O; O7 y! M2 _ - pid_Task_Right.speedNow = 0; j3 L% L8 C4 i7 i4 n
- reset_Uk(&pid_Task_Right);4 S( x8 G1 ~/ h- H' Y3 L& f$ U
- }
- p- \& E: y, z0 c0 N
2 q. m# i8 I; A- l- /***********************************************************************************************' F8 w9 A5 p J% M+ G
- 函 数 名:void reset_Uk(PID_Uint *p)
* ~; o. [! d: b" c( r) ? - 功 能:初始化U_kk,ekk,ekkk) Q, O( g0 o7 f! H- d2 r7 K
- 说 明:在初始化时调用,改变PID参数时有可能需要调用1 I1 w4 J6 u1 x0 c
- 入口参数:PID单元的参数结构体 地址
% U8 Y2 U( w5 b" u- S - ************************************************************************************************/( m1 A3 a F" f4 w) [* ~( o
8 W$ ^/ R/ k# H) }# M$ M- V* p- void reset_Uk(struct pid_uint *p). \* s$ \ ]0 T6 Z( c! ^3 H
- {
- `5 \, N* |! `$ |# Z4 Y+ h - p->U_kk=0;+ c0 y8 Y$ H- z) e
- p->ekk=0;$ K8 k& O/ z$ w9 {
- p->ekkk=0;
7 M8 j5 ^2 v0 W5 n$ d$ U - }& X$ V2 F1 g) F6 z6 O
- " U: P7 E" I, E( A- _
- /***********************************************************************************************" N- @# E- r/ w" R
- 函 数 名:s32 PID_commen(int set,int jiance,PID_Uint *p)8 Z+ K7 N& H- e$ P" u4 M6 x& i
- 功 能:PID计算函数" S3 B) J6 O3 s3 l, f) G5 |$ o: l0 H
- 说 明:求任意单个PID的控制量5 Q8 H0 X. L. y; K Z
- 入口参数:期望值,实测值,PID单元结构体7 \4 ~7 T1 J, Y$ {5 B' }
- 返 回 值:PID控制量# g2 Y$ g$ G; _% \
- ************************************************************************************************/) i5 C4 n/ ?7 F _, d8 N' ?" }8 D
8 X. Y# D% x. k) N/ `- s32 PID_common(int set,int jiance,struct pid_uint *p)* o7 u1 N, A$ x
- {
0 Z- M4 K& s. ?1 @1 g - int ek=0,U_k=0;
+ M0 s) ]. {/ ~. D5 q
/ W% b: F% U+ o( ?$ I- ek=jiance - set; 4 d/ a c' x7 V& t- e8 U0 j' r+ d1 I2 i
- , `5 x) P( ^: `
- U_k=p->U_kk + p->Kp*(ek - p->ekk) + p->Ki*ek + p->Kd*(ek - 2*p->ekk + p->ekkk);
" J, x0 @0 x, ^ _; H. }& e y, Z -
2 a% W: e7 x8 \6 G9 F5 p) p - p->U_kk=U_k;7 K- b [. Y! v7 y6 f9 B
- p->ekkk=p->ekk;4 ~1 Z9 J$ Z9 [* I5 c
- p->ekk=ek;
- [3 M9 O, ]% d/ O& s! V - / }0 [0 `" \2 @9 A8 o
- if(U_k>(p->Ur))
9 D4 a8 u/ Q( v8 g0 B' r: j$ `/ D - U_k=p->Ur;) m2 W$ O, l6 F7 A0 F7 Z1 c6 E0 |9 ^5 T
- if(U_k<-(p->Ur))
4 x# [& H$ }* _4 `/ M - U_k=-(p->Ur);
$ }% Y" Z: N: ]6 w - 0 @; |: Z7 t; f/ a7 {6 w% g# J; [, [; U
- return U_k>>10;
* }& A3 o/ W4 g; l# ^( d - }
8 {+ l8 z" m7 _% H2 d; R - 8 h. j$ h! G, D E( r" v
- /***********************************************************************************
% P- a, |3 b& N, I& I+ _3 } - ** 函数名称 :void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)
( l& e0 o9 g1 o$ p, ^2 n6 ~: {2 u! u - ** 函数功能 :pid选择函数
o' I) i9 r* e7 h( G; q( n - ***********************************************************************************/8 V7 W# w( ]. ~# {1 C
- : U$ }: c: Q( U+ C" W
- void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)
/ s* W6 p0 C, y4 ?+ g5 x - {
* S5 k4 \- ^ B% ~, R' ]8 V - /**********************左轮速度pid*************************/
3 o7 L! a# U; f! O; | - if(pl->En == 1)
; A. r- [8 P1 g+ ^5 n" u - {
. @7 k, G3 m4 V8 r! J - pl->Adjust = -PID_common(pl->speedSet, pl->speedNow, pl); 8 }3 o/ ^" ^ G# t( A
- } 6 P8 F1 d- ?1 M' O! `
- else
w" l+ U) W& u* }, M - {* ]& i# q+ j- h g- h
- pl->Adjust = 0;
/ R. ?; E5 P4 l$ w' F - reset_Uk(pl);2 x$ |. h9 `% n d
- pl->En = 2;
0 [& d6 S8 s6 R2 a3 ` - }: `% ^1 c2 W6 D. S2 e
- /***********************右轮速度pid*************************/* d- j" Z Q8 x1 o% Y
- if(pr->En == 1)* ]5 y* m0 _3 }6 V( j. F
- {! q& \3 s$ \. e( \! R
- pr->Adjust = -PID_common(pr->speedSet, pr->speedNow, pr); ' k2 P8 v) I0 S5 Z0 v" g
- }
$ B5 Y' O e9 b* o: ` - else# f5 ?! ]! A$ E f( g6 }3 ~
- {% a3 q) y# ^% S1 m% Z
- pr->Adjust = 0;: J# r: V( f; ], H; D& z7 c
- reset_Uk(pr);
. Q% v/ r% a, @ - pr->En = 2;
9 _# q$ W% q9 X - }4 P! d- i4 c" u6 m c+ @
- }
, d3 l. D% s6 K% { - * H' D/ B E+ f' s5 D+ @/ |7 ~; D
- /*******************************************************************************
% v* J5 i3 z2 R- [ - * 函数名:Pid_Ctrl(int *leftMotor,int *rightMotor) ^( k$ D0 u* J
- * 描述 :Pid控制
. Y1 w7 b6 `: K2 A$ o3 A# o% i8 E - *******************************************************************************/
" n6 l5 \9 T+ q& a% Y) {* a E! `8 @ - & }( f$ g& t% ^2 K' @' D
- void Pid_Ctrl(int *leftMotor,int *rightMotor)$ s3 ~- y" {: W. P
- {. W$ P! z' q9 a3 u8 Z
- Pid_Which(&pid_Task_Letf, &pid_Task_Right);
0 x Q5 e' S" g" |- z! q+ r - *leftMotor += pid_Task_Letf.Adjust;% |) p' m9 r3 y, ~" j5 q
- *rightMotor += pid_Task_Right.Adjust;
: v6 ~4 a: u1 b; v& `! w7 Y! m - }! V, i l% c1 t/ r C/ t+ ~8 u
复制代码 2 T6 H. L1 x% F4 O, z
, ]0 A0 b7 J; ?, V# |. j
(2)main.c
3 _* u- M5 O' i j
5 y9 x6 c, \& m" }# M" X
- #include "sys.h"0 l/ ~" p9 ~3 g
- . R' D, D! l. ^- {8 ^; ^
- //====================自己加入的头文件===============================
- R8 M# Q8 P/ X/ O# H4 R" F - #include "delay.h"
/ K0 M# {8 S5 S5 @4 l1 O- f, Y - #include "led.h"2 o2 S% m& T' P2 D. F
- #include "encoder.h". E$ j& d0 D2 u
- #include "usart3.h"( `3 R T2 j$ J# }5 w
- #include "timer.h"
* ]; b3 I* [, Q& K0 }' H6 j1 k1 M: b( S - #include "pwm.h"
3 g9 _$ @5 Y9 r( X P# W1 j; W - #include "pid.h"
; q6 ?% B3 ?3 c; H- g3 c' V3 | - #include "motor.h"- w2 x. q/ \+ G/ I3 I* F" ?
- #include <stdio.h>
( {( A, \" Q% ^% a# @4 J5 z- F - /*===================================================================; S* V- m$ _' e4 F
- 程序功能:直流减速电机的速度闭环控制测试
& q4 I7 K- J) N+ O2 G - 程序编写:公众号:小白学移动机器人
$ j5 O4 _+ `. s& q6 S4 S - 其他 :如果对代码有任何疑问,可以私信小编,一定会回复的。
( r) C. d. M2 p& B. y+ m! P - ===================================================================== a7 W% S: W/ \8 y
- ------------------关注公众号,获得更多有趣的分享---------------------
5 d4 b' Y; l: W2 o( B4 z* F - ===================================================================*/
( p1 {$ p" a" l3 Z - int leftSpeedNow =0;. c9 N7 O- p: M- z' p1 I
- int rightSpeedNow =0;4 }5 ~3 T' ], w/ G- d' z o, X) c
- ; r( S+ Q5 `' x; L) ^- i0 Z# z7 T
- int leftSpeeSet = -300;//mm/s; T% N1 S+ K3 |0 y
- int rightSpeedSet = -300;//mm/s. s; s3 J+ F( k8 H+ s
" @9 b' {/ R7 p9 f* @4 u- int main(void)
S0 L7 ?" i0 P, N7 o - {
& w8 H9 U$ \+ j
1 S+ R) \. c4 H3 {$ V0 D- GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
8 g- c+ j2 E# v) A' a8 t4 A* P - GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁用JTAG 启用 SWD
8 b% F, Q7 i+ C4 { -
4 ~1 A; [+ p. f - MY_NVIC_PriorityGroupConfig(2); //=====设置中断分组0 U0 p5 b% f% ?/ ~9 R- O$ x( Z
- + @9 C" w9 z+ L ]
- delay_init(); //=====延时函数初始化9 x+ ^2 b1 L7 M# @6 j7 i4 w
- LED_Init(); //=====LED初始化 程序灯 / h! f2 h7 j0 W2 q1 X% |! _
- * m( Y0 I0 |6 E" I9 A: x5 ~ |
- usart3_init(9600); //=====串口3初始化 蓝牙 发送调试信息 p6 K- D- R5 e6 T+ A
' w& | {- T: b. ^8 Y' m- Encoder_Init_TIM2(); //=====初始化编码器1接口0 F) K1 ~# U: K; ?/ m# ?9 R
- Encoder_Init_TIM4(); //=====初始化编码器2接口
: k Y4 ]2 m3 b, V -
2 v* Q( o3 [5 _( e4 \ - Motor_Init(7199,0); //=====初始化PWM 10KHZ,用于驱动电机 如需初始化驱动器接口9 E2 D' t B0 c& q9 S9 j$ n
- * c: T3 b. R+ s" S5 o& S
- TIM3_Int_Init(50-1,7200-1); //=====定时器初始化 5ms一次中断
. H$ E1 e* t. l
+ I+ R4 \5 B/ i, u) t* x D# i- PID_Init(); //=====PID参数初始化, ]4 B8 b/ _2 r W# d$ C% ^
-
- }% \% |9 S1 V9 S5 m) c - while(1)
, n t) w0 [5 ?* S$ t M6 [ - {
5 Z/ G4 w1 v+ q0 s - //给速度设定值和实时值赋值
; M2 G- x e( F' W7 I& H" ~( y4 o z - pid_Task_Letf.speedSet = leftSpeeSet;
, D. t0 d6 W( t2 G - pid_Task_Right.speedSet = rightSpeedSet;! k$ U- ~6 E; S: w' u* L: [7 K4 l
- pid_Task_Letf.speedNow = leftSpeedNow;
\. S2 [. Z( ]. {+ d" t4 U - pid_Task_Right.speedNow = rightSpeedNow;
! S: `/ S* i) \ -
0 f# H2 |1 `- d7 n - //执行PID控制函数, O+ k! r! G7 Q, J' n; w; C
- Pid_Ctrl(&motorLeft,&motorRight);
" P, ~4 D6 ^" J: c+ H7 T -
. e9 [1 j& ]% m$ u: j - //根据PID计算的PWM数据进行设置PWM
P- M. @' g) T& J# H - Set_Pwm(motorLeft,motorRight);. L* y I! {) ]: g
- ' u, ]! ~' y0 t
- //打印速度: X! ?4 M5 t8 A3 n8 W
- printf("%d,%d\r\n",leftSpeedNow,rightSpeedNow);
. k! x# L& r) g- g - }
- q9 Y y, Y- C - }9 {( J# b4 d l6 a- j( i$ Z. \0 Z/ X+ n
# M5 |* c0 h6 a3 J2 V/ f- //5ms 定时器中断服务函数8 f. b0 n) Q/ O6 t% J4 e9 d
- + }3 U; r4 B3 X$ f
- void TIM3_IRQHandler(void) //TIM3中断" R) f Z- O) b' T
- {( w; z5 @" W( j
- if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否
7 S0 H/ p" Y+ k* `3 V$ a - {
9 S( x+ X( ?) K: g9 v* g - TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位
2 y8 n; v e0 O! D - 3 |( e* J8 u7 n) R- D6 W
- Get_Motor_Speed(&leftSpeedNow,&rightSpeedNow);//计算电机速度7 b: s2 Q7 X6 X
- & J" |0 e' [/ Y: z& N8 Z2 @/ t2 f
- Led_Flash(100); //程序闪烁灯
1 Z t- o9 B3 t6 e- G1 G - }) ~, H* t- C) H( p L, n2 L
- }
复制代码
; { R; f V) {0 j' [& X3 q: [) f/ K0 z- \+ f Z, s. c; i# N3 h' K6 \
2.3.4 总结: Z& u5 V$ O; d/ g" h$ j2 R
8 O% H5 @: r1 n以上三篇内容,是关于直流减速电机的PWM控制、速度测量以及最后电机速度的闭环控制。现在对于电机的简单控制基本告一段落,对于做一个ROS小车的电机控制,这里基本是足够的。下面我们会介绍使用IIC+DMP获取MPU6050数据。
: b- k: |6 Y% i' h: ^( s% ~
r& Y1 m8 k, N5 p1 r5 s6 ?' r# j% w& v, @1 P- H
$ \8 A1 n, @( P3 f& Q |