2.3.1 解决的问题
* Y; x+ ]0 ?% {/ e
# ]& w8 _7 e) a+ e/ M& i5 S# J: \
# T8 w, i5 u$ K解决带编码器直流电机的速度闭环问题。 . K/ |# x S2 m8 T5 ~% F ~
2.3.2 PID理论
6 h- s" P. _+ D' v* i7 H% s0 c/ l2 @2 q% a# [: h
将偏差的比例、积分、微分,通过线性组合构成控制量,用控制量对被控对象进行控制,这样的控制器称为PID控制器。在连续空间中,我们通常探讨模拟PID的控制原理,如图所示: # |' D( P7 w. N( e' f [( y. m. h" f
3 s3 [+ z) J0 t
我们这里用电机速度控制为例,讲解PID控制系统。r(t)为设定电机速度、y(t)为实际电机速度、e(t)=y(t)-r(t)为速度差值作为PID控制器的输入、u(t)为PID控制器的输出,作用到被控对象电机上。根据模拟PID控制器,科学家们也得出了模拟PID控制的公式,如图所示:
) j, x# L# J9 Y5 ?6 I2 ~) Y
其中Kp、Ti、Td,分别为控制器的比例系数、积分系数、微分系数。该理论用在控制的例子比比皆是。但是模拟PID控制系统是在连续空间的上描述的,无法在计算机上用代码实现。于是就有数字PID控制理论,将连续空间的PID控制系统在离散空间上描述。积分变成了求和、微分变成了求斜率,于是就出现数字PID控制系统的理论公式,如图所示:
/ E% L* u) I d
* Y5 q: q; L' d- s% g0 g
其中Kp、Ti、Td和上面描述的一样,T为采用周期,ek是本次差值,ek-1上一次的差值,直接通过模拟PID转化的数字PID又叫做位置式PID,该方式的PID的输出直接是控制量,非常不适合经常出现异常的系统,另外一种方式是增量式PID,每次只输出一个正向或者反向的调节量,就算出现异常,也不会产生巨大的影响。具体数学公式如下所示:该方法较多的应用于生产生活中,本论文中电机的速度PID控制当然也不例外。 1 i* q' f/ E$ N
}/ q, u& x; }6 s! c
有了上面的理论基础,开始代码实现的介绍。首先就是明确增量式PID系统的输入、输出、控制对象。将速度的设定值和速度的测得值作为PID控制器的输入参数,PID的输出参数为对PWM的调节偏差,控制对象PWM进而驱动电机达到设定速度。以上内容确定之后,就是PID控制器的代码部分了。其实仔细看看增量式PID就只有一个公式,所以使用代码实现并不困难。如下所示核心代码就这一句。
6 H: T$ W/ C3 ~; z
1 f p9 N8 d: k8 U
完成上面的代码,只是完成速度PID的一部分,剩下的是尤为重要的PID参数整定。该整定方法丰富多样,最为准确的是模型计算,但是对于我们做机器人多使用试凑法。虽然需要调节一段时间,但是不需要对机器人进行建模。试凑法一般按照P、I、D的顺序进行调节。
1 [% Q1 ]0 j" ?! k' U7 H F% G% `
初始时刻将Ki和Kd都设置成0,按照经验设置Kp的初始值,就这样将系统投入运行,由小到大调节Kp。求得满意的曲线之后,若要引入积分作用,将Kp设置成之前的5/6,然后Ki由小到大开始调节。达到满意效果之后,若要引入微分作用,将Kd按照经验调节即可。经过有规律的试凑,最终达到一个我们满意的就行。
3 Y4 N t v1 ]2 x
# x( X8 w& v7 L
: M& l0 s- u9 r7 c% U
0 n" v9 c8 D% x* N$ D9 p% a' s
2.3.3 代码分享6 X4 z0 z: s' `- p+ c% P9 h; Q
1 r) c& X. d: ^( u(1)pid.h - #include "pid.h"$ g5 \7 J- Q G3 `8 D* B4 H# q
- 6 F {* ?. j4 W* w" P* `
' A i3 T7 [# W. s, a1 R- struct pid_uint pid_Task_Letf;* h2 f" W3 I4 r3 q ]& R
- struct pid_uint pid_Task_Right;
! `8 x; x# w- _7 J* j; u - 0 s8 X, ~* Z9 Z5 C# P* r3 @
- /***************************************************************************** J/ O: b" y& S' R7 F/ H
- *函数名称:PID_Init(void)7 ]1 J' M6 u; N/ h$ z+ ~# n0 s' n# t
- *函数功能:初始化PID结构体参数7 J3 ]0 q7 ?& ]3 ^$ b" W
- ****************************************************************************/% L& u% w7 [3 j! h/ e$ h8 I- h% V
- 6 V j" K+ ]% a; J( Y0 O Q
- void PID_Init(void). L3 g5 l$ F: r6 L0 O4 [+ v
- {
6 A9 O: z3 Y& I, h+ P, p - //乘以1024原因避免出现浮点数运算,全部是整数运算,这样PID控制器运算速度会更快; I% t( Q. h. M* [. | N5 Q; k/ _
- /***********************左轮速度pid****************************/5 a7 C+ R) |' {* N, J) @
- pid_Task_Letf.Kp = 1024 * 0.5;//0.4
; { V4 k% `: w( a2 W' ^9 w1 n1 x0 _ - pid_Task_Letf.Ki = 1024 * 0;
, L/ B. D6 b0 P* \" W9 ? e+ I - pid_Task_Letf.Kd = 1024 * 0.08; . p) l1 C( g$ Y O! k, X
- pid_Task_Letf.Ur = 1024 * 4000;
a" k8 D U% Y1 X6 N! S' s; v - pid_Task_Letf.Adjust = 0;
; m; s2 O4 w# H; X& d - pid_Task_Letf.En = 1;; b+ q |! n) h8 N# i! ^
- pid_Task_Letf.speedSet = 0;( V1 N- k1 \0 [5 A0 X( i
- pid_Task_Letf.speedNow = 0;# o' N( i( N6 e) A( |5 E
- reset_Uk(&pid_Task_Letf);
$ R) n0 s' [9 ^ - /***********************右轮速度pid****************************/
: n3 G3 v* k% p$ P - pid_Task_Right.Kp = 1024 * 0.35;//0.25 V% i6 O& N6 L) P
- pid_Task_Right.Ki = 1024 * 0; //不使用积分
" J* ?$ L0 w N# u1 ^1 G V( { c - pid_Task_Right.Kd = 1024 * 0.06; 6 P6 t6 p; G# F9 x4 t1 R6 T
- pid_Task_Right.Ur = 1024 * 4000;( p2 |' f5 s' y0 Y. ]' |
- pid_Task_Right.Adjust = 0;
% [- L" U9 ^. Z1 B9 W0 P& \ - pid_Task_Right.En = 1;
) s% S( v4 s, u( }. D1 V) \; k. m - pid_Task_Right.speedSet = 0;
" C) ~; R w5 T( ~. l8 L - pid_Task_Right.speedNow = 0;
& k0 Y& H8 i U" y( R) b' w - reset_Uk(&pid_Task_Right);4 I1 |: B% s7 Z* {
- }
+ A% c6 n, ^, Q a$ t
6 {: y8 `5 C7 H) H- /***********************************************************************************************' l5 ?+ }6 D9 K9 `; C
- 函 数 名:void reset_Uk(PID_Uint *p)# r3 \4 g" I; B: G2 K
- 功 能:初始化U_kk,ekk,ekkk/ e) D5 @$ [* K! \
- 说 明:在初始化时调用,改变PID参数时有可能需要调用
* M% m) K4 V" L# C% `7 w0 l - 入口参数:PID单元的参数结构体 地址# A2 P; m1 u( N: g
- ************************************************************************************************/
* ^4 x6 w2 v4 v. ^9 \
% M' y/ w# I& x! q* M! u/ v1 t; i- void reset_Uk(struct pid_uint *p), A" Y3 x" H- i( i1 p; o/ U
- {' m9 A8 I0 s. D% T- o& |4 Z
- p->U_kk=0;
/ X# G% s0 i; K8 N! l5 o - p->ekk=0;
1 W- O6 e( y# {% f& V& |1 U3 ^0 F - p->ekkk=0;
^$ \. p% e1 V' U5 B9 ~ - }
8 C; F5 y" e" b( [4 r4 n. W X
& j$ Z# J G% v2 \! z" `- /***********************************************************************************************3 H8 |$ D& u( q8 w5 ], [
- 函 数 名:s32 PID_commen(int set,int jiance,PID_Uint *p)) r1 D) i% _" @" z f
- 功 能:PID计算函数1 c j7 t' R# e' H: C( J) V
- 说 明:求任意单个PID的控制量2 p5 ^/ W1 E6 c S' W* p3 U- [
- 入口参数:期望值,实测值,PID单元结构体* W" \5 C- y8 a3 G
- 返 回 值:PID控制量! _+ M) U$ {5 h" d7 ?* i
- ************************************************************************************************// q. L1 B# _- A6 ]+ M4 t
! m7 E/ g! |- p9 e. g2 j* _: n9 |- s32 PID_common(int set,int jiance,struct pid_uint *p)
9 G& m/ G5 U) F/ G - {
0 b2 m4 Q2 @+ w+ u' w7 | - int ek=0,U_k=0;7 l$ P B2 M: R% W" ~5 p# u0 C
- # `% P V \' P
- ek=jiance - set;
- M( R3 m9 w7 K6 s' w$ v - ' X3 S! a' L" [( k8 z% a
- U_k=p->U_kk + p->Kp*(ek - p->ekk) + p->Ki*ek + p->Kd*(ek - 2*p->ekk + p->ekkk);" V T& K! n# Q! [
-
0 i! u* Z: N8 C* } - p->U_kk=U_k;
* }8 V7 J9 h8 G, z8 ~* z9 r9 P+ k* r$ j8 { - p->ekkk=p->ekk;
) S2 _5 }6 R" i. ]6 L2 u - p->ekk=ek;
2 m `( Q. L5 R& n - & W0 F/ b& }% I' A
- if(U_k>(p->Ur))
7 G D! X8 j2 w; [! p3 i0 K5 Q# O - U_k=p->Ur;# Q+ h$ G7 D' }) p* ?
- if(U_k<-(p->Ur))9 K' |+ D) Y" ^9 w: p1 V, Z: y
- U_k=-(p->Ur);
1 {* u4 q9 ~) I* h1 d - $ {3 C" J" {% Z/ ~4 x1 k
- return U_k>>10; * a0 j0 u. A3 Q/ m( T% V
- }" Y5 O/ ~2 k$ c' z" J$ u
5 T& o6 x. n) { ?" e! g- /***********************************************************************************6 m9 \3 I& N7 S* g& _
- ** 函数名称 :void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)( b, [3 Q4 \! R8 @+ C3 M( S# w4 m1 u, R
- ** 函数功能 :pid选择函数
5 i# m4 w" V* L" ^# x+ M - ***********************************************************************************/
. O- u F, M P! ~% p - ' e! ]: L: t3 T( O8 L& ]' z! Y4 E
- void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)) X" Q x* S* e
- {
% ?0 x$ y: v# i. w$ e( b - /**********************左轮速度pid*************************/
$ o( @, k, R- {+ P H - if(pl->En == 1)
1 a. U2 Q( x. Q# P3 c5 d - { . f3 @( ~+ b( ]; e* x
- pl->Adjust = -PID_common(pl->speedSet, pl->speedNow, pl);
7 m/ V4 G6 |+ C' w$ b% h$ L* ~! Y - }
, ^+ \5 ?' {4 Y7 N - else' `6 r# \; a( ]6 i( ^
- {
9 |2 T, p% c9 }$ T; I - pl->Adjust = 0;
1 @ v+ P4 C1 \1 J; D& j - reset_Uk(pl);
+ A! \ e2 q2 C$ y - pl->En = 2;
3 e( Z4 ^! F+ q5 U+ n. v, U - }1 n$ {( a8 `8 Z3 @; @
- /***********************右轮速度pid*************************// o+ d. k2 J- j) t
- if(pr->En == 1)) d1 c0 x; W4 M; _) B2 Y0 U
- {
' N, b% M* S$ J1 L6 V/ w - pr->Adjust = -PID_common(pr->speedSet, pr->speedNow, pr);
% b: f3 y6 o. S+ T2 G6 I! h& N - } 8 C* Z) ]" t. @5 l
- else
; d- g. x/ P. ^& ? p - {' P5 a3 F4 o' \ t- S* P" z
- pr->Adjust = 0;
# M. ^' O/ c7 l* [, V$ E* h - reset_Uk(pr);
% S, K2 m3 g; `$ q - pr->En = 2;
* l( T& {) @% b! n7 v$ L/ [ - }
1 F I; q+ Q h7 M+ d: u, \ - }
; f# i1 ]2 d. ]7 a% [3 r- v A - . p2 Z/ T$ n1 z4 A( I7 [' p. e
- /*******************************************************************************) Z% D7 M2 B! Z/ |% _& a
- * 函数名:Pid_Ctrl(int *leftMotor,int *rightMotor)* \$ t/ g; U( e8 x+ }5 ~. f* }. n
- * 描述 :Pid控制
0 C4 \, o' O) F7 X - *******************************************************************************/8 @6 g2 E8 i! P5 q. f' ?
5 f: D5 s, e8 z0 g" S- void Pid_Ctrl(int *leftMotor,int *rightMotor)
! C( P/ g6 I+ b7 S - {
7 V! K& _0 j* N2 ` - Pid_Which(&pid_Task_Letf, &pid_Task_Right); $ W1 k% n3 Q) l& c
- *leftMotor += pid_Task_Letf.Adjust;- a2 ^7 D( c6 K+ {8 A: g* ~
- *rightMotor += pid_Task_Right.Adjust;+ a1 U; e! D( Q% y! n, m
- }
( ` N l5 e2 `
复制代码 5 O8 [; Z) \0 H k7 l) a: L/ @
0 | J$ m, I! z: {(2)main.c 2 C1 x1 R/ h! m" [
, g7 ?) V0 ?7 b# V. g$ i
- #include "sys.h"
. ~! @% d- X( U9 x
0 @! E L; ~$ f8 ` m; N- //====================自己加入的头文件===============================
: O+ F% }9 M/ l( I/ r - #include "delay.h"3 {( q% X' w3 b: p
- #include "led.h"6 z' v/ l) ^( Z
- #include "encoder.h"
; }8 u9 b; J3 G" Y' L3 { - #include "usart3.h"
! o1 Q) r6 ?' ^ - #include "timer.h"
& j! H7 I. ~. Z( J$ I - #include "pwm.h"0 h( S7 _) R! x, S8 I; N0 s
- #include "pid.h"# u" x5 t( a3 Y/ s; j) W P
- #include "motor.h"
( z# k# W) T5 t2 ?1 @( m+ w( J" u: O - #include <stdio.h>
) V: q0 M' ~* D - /*===================================================================
+ {- i7 e+ p7 U6 O- \. y# i6 G - 程序功能:直流减速电机的速度闭环控制测试
3 ~& J7 i) q! q1 c7 I) `( ` - 程序编写:公众号:小白学移动机器人2 c, A6 `2 v; L9 `" b
- 其他 :如果对代码有任何疑问,可以私信小编,一定会回复的。, a5 M# c! D' ?' h, a& w
- =====================================================================
8 q: z+ R% D Q - ------------------关注公众号,获得更多有趣的分享---------------------
! N4 {5 Y( `& e( a3 q2 q- N - ===================================================================*/# f5 y# x" l* T' T# u
- int leftSpeedNow =0;
! W4 v' s% V2 _7 |. u" L - int rightSpeedNow =0;0 D* G2 E# w2 A, e6 P$ U
- ]; w% {1 U$ V1 x v- int leftSpeeSet = -300;//mm/s
8 {8 u1 |" b( m& d - int rightSpeedSet = -300;//mm/s6 r, X, c, ?! L& [( ?$ k L, N- Q
- / Y; I1 Q* f8 p! v$ F4 Y: o( e
- int main(void)1 k* k0 @% A8 K% \# q, L$ P
- {
" S: ~9 c2 _+ \8 D7 l
- J) z5 ~5 q5 [1 Y D5 T- GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
$ J! q7 I9 _1 E, H4 v - GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁用JTAG 启用 SWD" u7 f$ J* S" @1 f1 W5 k
-
2 A6 {+ X: ?" W- h R f - MY_NVIC_PriorityGroupConfig(2); //=====设置中断分组
4 \9 l" [, y6 t* P- I -
& H; t7 P2 {* P& a$ W0 I - delay_init(); //=====延时函数初始化
* a4 u. F- Q) w2 d - LED_Init(); //=====LED初始化 程序灯
; R; B8 Y3 h I1 k' K6 z8 \ - 7 a( m3 B" K% n' i7 L( k8 l# B
- usart3_init(9600); //=====串口3初始化 蓝牙 发送调试信息
: J1 T/ E6 W. Y% B% w- i: ~* w
, g6 L6 c3 v& j0 R, h- Encoder_Init_TIM2(); //=====初始化编码器1接口! x( j6 Q ~/ E4 n- u$ R1 E0 a
- Encoder_Init_TIM4(); //=====初始化编码器2接口" }) Y2 L3 B! M% I+ v C
- * m/ q" f: z( }$ o$ G( a
- Motor_Init(7199,0); //=====初始化PWM 10KHZ,用于驱动电机 如需初始化驱动器接口
2 M! |9 h/ h* D0 }) K2 a: V1 i -
6 V7 C4 y" M/ H6 q7 H7 O/ S - TIM3_Int_Init(50-1,7200-1); //=====定时器初始化 5ms一次中断
! K5 t6 a& q4 l- g- H) B% X
3 }/ g& W, ?9 w7 F- PID_Init(); //=====PID参数初始化
! Y" ] S+ ]7 A. | - % w# z$ J2 m7 o: \+ w2 j( C: [
- while(1)
# E. C8 U& A+ ?* A# W+ U0 [2 [( ^ N - {
: Q. i5 Q' i2 ^# k0 b5 Y" b - //给速度设定值和实时值赋值5 {" O- j3 ~7 K, a2 L
- pid_Task_Letf.speedSet = leftSpeeSet;
* P% u5 N, C% R - pid_Task_Right.speedSet = rightSpeedSet;6 ]+ V" p5 L, o1 @. {; m s; S" X
- pid_Task_Letf.speedNow = leftSpeedNow;) Y4 o5 x2 S3 {& {3 E+ R' E
- pid_Task_Right.speedNow = rightSpeedNow;
2 M. y! O/ ?, o8 o! B( f2 C - . `4 x, c% ^; n D! r
- //执行PID控制函数9 R c7 D' M2 i- E6 m
- Pid_Ctrl(&motorLeft,&motorRight);
w- ]; J, z L* E1 n0 K - ! o- O$ \9 v [3 B5 i2 ~' g
- //根据PID计算的PWM数据进行设置PWM/ x4 _, D3 N: b" G7 Z. P
- Set_Pwm(motorLeft,motorRight);
- e7 K# T! w% k - ! M1 x( H6 U5 u( f
- //打印速度2 ^0 ~+ L& P; @ Y- u( r) X' T7 g
- printf("%d,%d\r\n",leftSpeedNow,rightSpeedNow);
* y8 P2 E2 _" Z# i$ E; a6 I - }
* @( N$ y5 G, N& |( z - }
+ I5 O& y" F3 ` Q( M, z$ s
7 y7 R: v# Z k) p1 P# e- //5ms 定时器中断服务函数
" W8 \( ]% M: v% S7 \ - ; P9 B# s" o, Q0 Y! R! g7 n
- void TIM3_IRQHandler(void) //TIM3中断6 a @/ l& C' o# [' s
- { |! P0 @# I/ ] h! b
- if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否 x5 F' }! `6 r ^ b; [; f
- {
8 l6 I; q! Q4 B9 W( V6 ]+ y - TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位, c/ C# ~: X: B4 w6 m
-
- R& [/ C$ d' _0 @3 i1 j - Get_Motor_Speed(&leftSpeedNow,&rightSpeedNow);//计算电机速度6 H6 E2 n/ S1 T6 o& ?0 z
- : J2 x% e, S4 d% B8 o0 l/ C
- Led_Flash(100); //程序闪烁灯
, S3 j( D7 M" L) u/ o5 m - }2 G, C9 V: B. B: K& o* ~ B' n# T
- }
复制代码 / I0 Q3 F' j% F( A
1 ^3 F! L+ s N$ p; v7 {* |" m& s1 D2.3.4 总结: R+ i! Y, e, M+ s- n2 j0 B
: R; c" U- A8 O1 d6 a6 \以上三篇内容,是关于直流减速电机的PWM控制、速度测量以及最后电机速度的闭环控制。现在对于电机的简单控制基本告一段落,对于做一个ROS小车的电机控制,这里基本是足够的。下面我们会介绍使用IIC+DMP获取MPU6050数据。 ) Y3 [ Z# d: s6 _ G7 ~% [
& y( {, u5 P9 ^3 K' m- u# H
; I# b7 a2 g. d* K3 l, I, Z
# J1 k, _- Q5 | |