开始之前我们需要了解的是完成闭环控制输出任务前提是必须要有printf输出功能和PWM输出功能。
. y( R. t) }4 A
2 r' j" t2 O- n/ r- K1)其中printf功能是为了在最后阶段,监测实时的PWM值,检测是否实现闭环控制。0 q \; u9 o- g2 O+ p& v
9 Q! M( P4 v! R U" J3 A
关于printf功能的实现可参考教程4的内容:
9 g( H; t, I( Z" R( t- e
: D6 A; k# O& v, m) Q, ?! aSTM32CubeIDE自平衡小车教程4.配置串口并实现字符的输出/ c2 x( V. y: d5 D' ?1 O
9 E* b: F! {: V; n, a3 b
2)PWM输出功能是为了实现最后车轮能按照设定的转速转动。
2 ?+ e4 F; g! Z
8 ]. M6 ~( z2 \* u9 K6 o7 m关于PWM输出功能的实现可参考教程5的内容:
5 ]' h5 Z% U p9 D4 n' x' y- a
: Y6 i& n& N4 e' Z2 a0 i6 mSTM32CubeIDE自平衡小车教程5.直流电机转速开环控制6 H( x. {% \% m
2 a5 f& V( c s& |, o8 S以下是实现电机转速闭环控制的步骤:
+ {2 l' q; `/ E4 ^3 R7 Y, G6 o1.打开上节的工程文件,在工程文件中新加一个User文件夹,在文件夹目录下新建Src和Inc文件夹,并分别添加.c文件和.h文件并命名为motor_control.c和motor_control.h
5 ^$ u; o1 B; E c3 l% T8 t( L8 C, ^0 T& x: `, t
1 ~' I/ o9 w: g8 v1 S
4 c( F' F3 [2 l' T( b2.在motor_control.c文件中加入以下代码:
* i; h1 M9 r+ w# p' z# z7 K! o- #include "main.h"
) V' a+ U5 M1 f* N -
# K6 U6 C2 Z& A) W" z# D1 e; f6 r, F - float SpeedKp = 10.0, SpeedKi = 1, SpeedKd = 0;
6 l& n7 B% Q0 N5 G4 E -
5 M' t3 z; G* Z. H7 s - int Motor1SpeedClosedControl(int Encoder,int Target)
" U8 M# K I& e - {& \1 m* L, R+ f
- static float Error,ErrorPrev,ErrorLast,PWM;
9 C' @' v9 D8 A9 ~2 \* R -
5 m' k t4 ^$ c, Y0 Q - Error = Target - Encoder;
6 `7 v. X- N* Y7 G* p0 Q$ x, f - 8 [5 z# B- w+ c# O! n8 E
- PWM += SpeedKp*(Error - ErrorPrev) + SpeedKi*Error + SpeedKd*(Error - 2*ErrorPrev + ErrorLast);
/ ?3 r1 ^" n8 h7 _* H7 M! H -
' M. @0 O8 I! i2 b: c: Y6 | - ErrorLast = ErrorPrev;
9 m+ P& B( G) d, B( l - ErrorPrev = Error;
: P! g3 a7 w# E: V& G8 u8 r - % D+ t, e u9 u% }
- if(PWM >= 100) PWM = 100;$ ?7 g4 \* r$ c6 j
- if(PWM <= -100) PWM = -100;
# |# U- }. z2 C& O -
4 ~- `5 @& n/ ~- y0 M/ ^, K, ` - return PWM;
! O( _3 k9 k$ {/ d - }1 y5 I' f% S. E: N* W" ?
- . t# A9 E9 q0 p# s# J7 h5 G
- void SetMotor1Direction(int Pwm)//设置电机方向* R9 w/ P3 `4 b% b/ r$ N7 Z
- {) z6 a) u4 O8 U; y3 F
- if(Pwm < 0)//反转6 ?$ Z! i0 J' t
- { @& H7 p0 D- Q8 G! l% e6 S& ]
- HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_RESET);1 U9 F4 |6 D1 }2 h
- HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_SET);
7 D# [2 F% E$ o# f( ~* } - Pwm = (-Pwm);//如果计算值是负值,先取负得正,因为PWM寄存器只能是正值
* m/ d. r* @! q* ~3 R: |( y - __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, Pwm);
# e& w6 P- ?' D$ f2 G - }else8 p3 i7 T$ k! k! R* A4 a
- {
2 Q0 z1 ^) z$ j9 W) N" ?' d - HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_SET);
! x. H9 c# f+ E - HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_RESET);
" H _$ ]4 e/ {; { - __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, Pwm);1 F: @& Q/ F, |. E1 e' P
- }
# p8 C& x q0 ^ - }& W+ o3 m# @4 c
- 1 h, T' v! f5 I7 G. N% z
- int Motor2SpeedClosedControl(int Encoder,int Target)
. h' X, a# s6 s- Y C C: Q$ @" y - {& u& M: h+ z, s6 ?
- static float Error,ErrorPrev,ErrorLast,PWM;
8 @ }2 V% c3 Q' P- @/ A4 o v5 B* e -
# d( a Z5 Z- R7 z - Error = Target - Encoder;# R* G/ t8 @. h2 `
-
! L, Q/ c/ V1 q1 A' A# [' Y" a - PWM += SpeedKp*(Error - ErrorPrev) + SpeedKi*Error + SpeedKd*(Error - 2*ErrorPrev + ErrorLast);
( S# c# ]* l- Q# x( p0 O# Q - - Q9 `3 G) O2 X, p# o) T, U
- ErrorLast = ErrorPrev;3 a- g0 Y) H" C+ D* T
- ErrorPrev = Error;4 b4 _0 t! ~- z% c+ l j$ @
-
1 ]. M2 R: z. n' I9 V - if(PWM >= 100) PWM = 100;9 F7 d+ I8 t- w1 t h
- if(PWM <= -100) PWM = -100;& ~0 m7 ?' Z9 E% m
-
8 F' D" h2 ^4 Z' t+ O. X - return PWM;
' R: l/ M# m) k9 Z; o - }
. m5 ^( ?9 T' a$ P8 b" @ -
, [% q( P% t3 p( g" b - void SetMotor2Direction(int Pwm)//设置电机方向9 r0 t/ o5 \% V2 |+ \5 j5 a
- {
5 Q* }5 S& i, i& K7 K' Q - if(Pwm < 0)//反转. f; ~3 C7 }, M( s
- {
8 d a8 J$ k4 Z( a h) {: o - HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);) r2 f5 l* V0 M: v) M
- HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
' b9 {$ `- [& D, m1 O - Pwm = (-Pwm);//如果计算值是负值,先取负得正,因为PWM寄存器只能是正值0 t/ t8 r( l; x" v( o2 L6 p5 L2 e
- __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, Pwm);
# r( Q) q) v8 ~& }2 s( k" J - }
! W* O. n k/ d# i - else3 @& V5 h4 X; v2 \! b/ `* {' I
- {; R1 R! ^4 k1 N$ I: ?! I6 c0 R
- HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
0 l. y& u: D9 e/ E- ~; I - HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);5 R9 H( ?' k, @0 {, v
- __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, Pwm);, X2 ?9 z" {5 y0 F% Z% j9 c
- }9 o. f( _6 l! \+ i) ?. q1 M
- }
复制代码
, w3 Q7 q9 C, F: d7 @3 q7 B3.在motor_control.h文件中加入以下代码:& {! ]4 ?- m- m$ p
- int Motor1SpeedClosedControl;
5 T S8 \& L! I, \7 J7 D - int Motor2SpeedClosedControl;$ R" K: K5 {+ p& p
- void SetMotor1Direction(int Pwm);
4 v# _6 a7 V9 J: X) {4 ^ - void SetMotor2Direction(int Pwm);
复制代码 6 r1 A! ?' e+ Y& R( o8 j1 B4 f' a# A1 d4 m
4.在main.c文件中在上节代码的基础中添加如下代码: W+ w. S6 x6 R* o# L" r. x
- HAL_TIM_Encoder_Start(&htim3,TIM_CHANNEL_ALL);
- R/ e$ i. E$ [4 I# n+ C - HAL_TIM_Encoder_Start(&htim4,TIM_CHANNEL_ALL);
复制代码 5 N: U6 [: a. i( P; K% G/ R1 ~* k
% p$ Q; ~/ @' T O, p" p' g
, p3 D) W: ?( u/ ~4 _ 5.打开在Core文件夹下Src文件夹中的stm32f1xx_it.c文件,下拉找到void SysTick_Handler(void),并加入以下代码(要加在CODE BEGIN和CODE END中间):
4 j; B% D$ U# c$ i9 p( ^- static unsigned char Timer5msCounter;
4 R8 [/ s6 {9 D- z7 G; t. ?# k* |. h* d - static int Motor1Speed;
$ ?' {9 s$ d( M' I& @; Y - static int Motor1PWM;2 h4 s/ O- H) M% J
- static int Motor2Speed;& V& N* J, z, B& ?: p7 Z
- static int Motor2PWM;) D) o& W5 x. A3 e8 p9 B8 [& Y
- 8 P% R( r) U7 I i) Q* {
- Timer5msCounter++;) k" M% m3 L$ O" N2 ~8 Z
- if(Timer5msCounter >=5)//每5s一个周期
+ ^( \! {* [- J* h - {
9 R3 C+ T$ W7 J" u6 W) R - Timer5msCounter = 0;* U& C* U* O) {" k' }5 F
- Motor2Speed = GetTim4Encoder();- E i5 t- H" W; k/ m4 M
- Motor1Speed = GetTim3Encoder();9 F( R6 z1 D8 m4 ?2 D1 @# h
' {: T: t. x' y y4 g; ^* @- printf("M1Speed = %d \n", Motor1Speed);
- m3 e, r5 k; U; v: F; Z - printf("M2Speed = %d \n", Motor2Speed);! D/ _/ e& k8 |! |# S0 [: q
- 8 Y8 e, B+ B/ a
- Motor1PWM = Motor1SpeedClosedControl(Motor1Speed,-20);
$ B4 L. a M( v - Motor2PWM = Motor2SpeedClosedControl(Motor2Speed,30);6 i3 `) ]/ c: q. t& U$ c
6 G$ z0 W7 r, j: w: x- SetMotor1Direction(Motor1PWM);" C* U6 o9 C+ f% |8 H% b* S
- SetMotor2Direction(Motor2PWM);
5 i& a) W* Y) a+ X! n9 Z - }
% [' B! ^+ ^0 ~0 \
复制代码
: n' U5 s d# c$ q) ?7 E4 q
; O: ^! p# V8 a+ H9 h
+ X# L; S8 p1 d5 b+ J
6.在工程文件目录下的Core文件夹下添加Inc和Src文件夹,并添加encoder.c文件和encoder.h文件。
, g6 R7 o' y" z% N在encoder.c文件下添加以下代码:
3 J! X0 G" X$ r6 Z6 [6 V- #include "tim.h"//包含tim头文件
" E" T% X c1 O. y - #include "encoder.h"
& A! L, }* S+ w, T - 0 S$ h$ K; R/ w" M ]8 b) g [
- int iTim4Encoder;//存放从TIM4定时器读出来的编码器脉冲) \, O) l: o. L/ v' ^) L3 W/ ?
- int iTim3Encoder;$ Q. ~$ t7 e5 n. ]
- ! Q' ]; B* S! Y& A! _7 ^
- int GetTim4Encoder(void)//获取TIM4定时器读出来的编码器脉冲
$ ?, X& @) v! C3 y: E6 T& G2 @% ]; X - {
( k% x$ v0 O% ` - iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4));//先读取脉冲数
: T" x* ^/ i- v6 y: b6 o: h - __HAL_TIM_SET_COUNTER(&htim4,0);//再计数器清零
$ O3 B7 S `# U; C4 r - return iTim4Encoder;//返回脉冲数) W% H- s' p# U. G% ^- _
- }- l" F" I* k- I2 m: p* o
- / F' ]# s# J5 \
- int GetTim3Encoder(void)//获取TIM4定时器读出来的编码器脉冲
. p5 r2 s8 v) ? u" W. \2 J - {
5 k) ?9 |; w( M# w - iTim3Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim3));//先读取脉冲数( t5 X6 T: P7 B _, {# ?( r& r
- __HAL_TIM_SET_COUNTER(&htim3,0);//再计数器清零/ l2 v3 w) K I& n4 K2 w7 d
- return iTim3Encoder;//返回脉冲数; Z6 g! j3 I$ o2 c% w2 k* {1 @8 u
- }
复制代码 / H( n0 i: F z9 Q
在encoder.h文件下添加以下代码:
% w- }1 B: F. |' K8 ]! s$ D- int GetTim4Encoder(void);//声明函数% m B) e I- u$ x
- int GetTim3Encoder(void);
复制代码
L3 i' n# |' G; Z: j5 K7.点击编译并进行烧录,完成PID速度闭环控制任务。可在在stm32f1xx_it.c中更改小车M1和M2电机的脉冲速度,正数转向为正向,负数转向为反向,并在调试器中接收小车的脉冲速度。(需要打开小车电源开关)
$ s* y- c4 K3 _例如此时设置M1电机脉冲速度为-20,M2电机脉冲速度为40。. ]7 T, L1 _; i) I1 t' e5 R
; x/ Q9 q5 B% T& q/ F5 {
3 c5 J1 h: ~. P8 l打开串口数据调试工具,可查看小车实时输出的PWM值:9 j4 T8 C1 W9 J8 Q/ q% p
. N* E2 I6 n7 e
/ C# O& l: B5 x9 H1 a( s& A+ s [* Y
0 ~9 p0 @* w4 j1 ~
" V5 ~; j# c4 V. `$ C2 g
|