开始之前我们需要了解的是完成闭环控制输出任务前提是必须要有printf输出功能和PWM输出功能。, o+ k& {4 S% C, w
4 R# `6 T/ E) w5 Y9 |: R- I
1)其中printf功能是为了在最后阶段,监测实时的PWM值,检测是否实现闭环控制。4 @6 a: _1 b5 R1 L
$ o* S* w# c# K
关于printf功能的实现可参考教程4的内容:. ~! I# m: ?' \' j+ g
! Y( e" V" h8 x- C. w f: YSTM32CubeIDE自平衡小车教程4.配置串口并实现字符的输出
9 F! N5 f3 H! g# }
; d2 X! a0 W! w! q$ C5 @2 F c2)PWM输出功能是为了实现最后车轮能按照设定的转速转动。! X# r' h9 H+ `: E, S0 v7 ?
% y$ ]- ?, f* X0 T1 L% E8 i6 K2 {
关于PWM输出功能的实现可参考教程5的内容:& O5 Z/ f! o) a Y3 B- a: Q3 M+ o& |
- ]' M+ h# a9 b) q
STM32CubeIDE自平衡小车教程5.直流电机转速开环控制
; _. c# B7 O( l! ?% z' R$ e! K& o1 S9 S! z. D+ a5 _6 _* T( ?5 `
以下是实现电机转速闭环控制的步骤:, g& [) d B& b: g5 G. X, E
1.打开上节的工程文件,在工程文件中新加一个User文件夹,在文件夹目录下新建Src和Inc文件夹,并分别添加.c文件和.h文件并命名为motor_control.c和motor_control.h
+ E3 V/ J: l, T3 x3 |. ^
0 Y& \& T. l' @4 b! z6 h$ Z
/ S" ^) N# W3 D! p9 |4 f) a. d# a3 P) [$ ]
2.在motor_control.c文件中加入以下代码:
$ T/ X0 ], S/ c% M' n8 a' e- #include "main.h"
/ q2 L. j" c" u2 q: F6 L/ s P& d -
/ r% ~! N3 S/ \" ~& a - float SpeedKp = 10.0, SpeedKi = 1, SpeedKd = 0;
2 |/ r& `4 c: ^! j _; d* ?0 t! j+ X -
9 S$ I; p. E1 r, h+ `# _/ t) E - int Motor1SpeedClosedControl(int Encoder,int Target)
- m$ v5 X/ e. i$ j @: E - {
% C" T# }) r6 m! w1 `6 t - static float Error,ErrorPrev,ErrorLast,PWM;
" }9 F2 F+ X# A/ ?. | - 0 o ?5 Y6 V. E4 `; Q6 A$ l6 M
- Error = Target - Encoder; D4 T! s- D8 T9 \: \' t
-
1 H9 U) P+ l7 X6 P$ j - PWM += SpeedKp*(Error - ErrorPrev) + SpeedKi*Error + SpeedKd*(Error - 2*ErrorPrev + ErrorLast);! \6 f) d+ ^1 }. m
- $ t# ~$ @3 ^0 Q. n9 I
- ErrorLast = ErrorPrev;. Q: u( b# @ @1 w2 y' l
- ErrorPrev = Error;
7 K0 G0 G7 U# l2 R7 e+ P - 4 K4 F/ x2 k7 ], D0 U( V$ m
- if(PWM >= 100) PWM = 100;8 U! K9 {! O n8 @8 h4 ]/ I
- if(PWM <= -100) PWM = -100;: m. W; h2 P: q# \! B9 {! g6 a
- 5 j* [: z- f" f) s$ W. u$ {6 ?
- return PWM;
% Y! E) d F2 Q' o8 }. | - }2 u8 [( p8 l9 E
- * m. u `4 i( L& f
- void SetMotor1Direction(int Pwm)//设置电机方向/ T( R5 W+ `1 T ~
- {7 M7 v9 `+ Y {( V' Y+ ?' m
- if(Pwm < 0)//反转
: u+ D+ d' A# u2 T, X. C - {
5 p6 y# f. w- I- K9 y - HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_RESET);
; i2 G, r3 Z; Z4 h* ? - HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_SET);
( b4 w# a% J6 j1 s& s - Pwm = (-Pwm);//如果计算值是负值,先取负得正,因为PWM寄存器只能是正值
% Z2 x$ i; [# w ^' o2 i - __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, Pwm);
+ T3 @( t; ?- Q) k - }else5 W2 w1 Y: X9 D8 k/ k* Z
- {
9 u" x9 n- V" [9 N1 q - HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_SET);/ g* r! j! ]1 q" N# S. @# ^
- HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_RESET);
1 m$ b7 u$ N& N v5 a" r - __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, Pwm);7 C3 N7 ^, ?: m
- }
4 k: m4 u# z; x% ^- Y- I. D - }
* [; G1 u; I( d/ H, x -
* U2 {+ ]* p: }2 v b - int Motor2SpeedClosedControl(int Encoder,int Target)
g% _! b: O" S5 d$ s2 F% ` ^$ { - {7 O6 C/ ?3 O5 }9 q
- static float Error,ErrorPrev,ErrorLast,PWM;
' s' U- ~2 e" ?; q - . g* K% a0 j9 s2 O; e2 h
- Error = Target - Encoder;
9 Z" \' e/ B9 @( N0 E -
9 W& v& B1 ~5 t; n0 _ - PWM += SpeedKp*(Error - ErrorPrev) + SpeedKi*Error + SpeedKd*(Error - 2*ErrorPrev + ErrorLast);6 {. ]) K: R# h) i0 O5 o
-
- T- f3 F, T7 a6 m1 b - ErrorLast = ErrorPrev;
& Q1 b2 C8 b4 ?5 H5 l - ErrorPrev = Error;
* u# D/ \+ [; U8 ~$ A, X5 @% { - 1 ~- D" `2 o1 d5 v" z. w7 ^
- if(PWM >= 100) PWM = 100;
# p7 f% s+ _' m' ]# u - if(PWM <= -100) PWM = -100; l) h- s9 s+ ], ]1 O, N+ q
-
$ m( Y& \5 h- y! G$ @! d - return PWM;
" Z' y6 s# d7 s$ k w6 {( z - }/ V( i$ j' F# g7 q' z
-
- L' A, o8 t; w* k7 k N+ E - void SetMotor2Direction(int Pwm)//设置电机方向
- \9 ]0 ?4 d% B - {/ X# r( b! [: c/ `+ `
- if(Pwm < 0)//反转3 I: w; j J7 |3 y* m
- {
' d2 x$ }3 k0 H! Y( B v9 H - HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);
; J7 w9 q9 S5 P- S( ^ - HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
" @5 Y6 ? J- p E n - Pwm = (-Pwm);//如果计算值是负值,先取负得正,因为PWM寄存器只能是正值4 R' Y N% p, j9 o% O
- __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, Pwm);
, q- Y% _: l, i1 N - }
( P1 h' p- G6 f+ P& z4 H - else6 N/ R# D1 |/ B+ m9 `
- {
1 U' \5 J$ q8 z - HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
2 C1 q% l: W6 k& }/ n5 z/ p: }6 { - HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);
; }- _9 m; Y" J' G8 u, N/ s ~+ P - __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, Pwm);+ ]3 l8 Q! P1 q/ h- U
- }
/ S7 F" L X, K7 ^ - }
复制代码 ) G9 a5 {9 s1 @" p/ v( I2 P1 \$ g
3.在motor_control.h文件中加入以下代码:
5 X2 @: Q, h8 V2 C1 |" Y- j- int Motor1SpeedClosedControl;& q* c c. P6 I
- int Motor2SpeedClosedControl;
1 Y: y9 y7 ~- K5 O; p1 i$ b - void SetMotor1Direction(int Pwm);9 ]1 W4 d6 f9 O
- void SetMotor2Direction(int Pwm);
复制代码 ' W% Q( Y# Q- T* h2 E
4.在main.c文件中在上节代码的基础中添加如下代码:7 p% {' P0 H9 R6 |8 h) Y( Q; \
- HAL_TIM_Encoder_Start(&htim3,TIM_CHANNEL_ALL);
4 ^) G$ ^2 ]( b' S2 ] - HAL_TIM_Encoder_Start(&htim4,TIM_CHANNEL_ALL);
复制代码 i& h, Z4 @ U2 Y
- C n! [3 o- Q0 p) X! K
5 L6 X! p$ E$ u+ R* y0 V 5.打开在Core文件夹下Src文件夹中的stm32f1xx_it.c文件,下拉找到void SysTick_Handler(void),并加入以下代码(要加在CODE BEGIN和CODE END中间):, g5 |" L7 R, q: V
- static unsigned char Timer5msCounter;( @4 B T( g, p- R
- static int Motor1Speed;& p1 K( @; T4 n, z+ Z( y. M
- static int Motor1PWM;3 n& [6 {$ c* `2 V/ q# j
- static int Motor2Speed;
; T7 Z' i1 W, f0 B - static int Motor2PWM;, A9 U3 [7 I+ L e; f
?8 g% a& e& L- Timer5msCounter++;! E# d3 ]( k+ ~
- if(Timer5msCounter >=5)//每5s一个周期 J& G5 r0 T4 O% p0 ~4 m: _
- {
" o }& ?/ L$ W; p$ A - Timer5msCounter = 0;3 J! W5 @' L3 | a& P
- Motor2Speed = GetTim4Encoder();
8 n7 w3 T" z( M! W4 U4 X5 V; e. J - Motor1Speed = GetTim3Encoder();
& Q; p; X, _3 {
8 M' w8 ?# r1 U- printf("M1Speed = %d \n", Motor1Speed);
H* `# v: l0 R7 G5 m" \ - printf("M2Speed = %d \n", Motor2Speed);
- ~/ B) p' x V0 Z" t, P4 P" A
+ t$ L/ P, U& ?: R! N- Motor1PWM = Motor1SpeedClosedControl(Motor1Speed,-20);* d& ]1 k) N( s7 K! l p
- Motor2PWM = Motor2SpeedClosedControl(Motor2Speed,30);
! J j2 b/ {. q& v1 t4 Z, E: c - 0 r% i; Q; y/ c ?
- SetMotor1Direction(Motor1PWM);+ ~7 C6 K9 p( s% E- l) y) [7 b
- SetMotor2Direction(Motor2PWM);# s6 d- s% ~4 H- h/ f. b
- }( R6 r, ? }/ t) o
复制代码
1 D+ V4 b! `* a& z. y7 Y* }& z
5 v. s: B* ^+ U2 `$ A5 Y9 V; n
# x0 `' g$ Y1 o6.在工程文件目录下的Core文件夹下添加Inc和Src文件夹,并添加encoder.c文件和encoder.h文件。- x, b) z' B; r+ t0 q0 J! M
在encoder.c文件下添加以下代码:
! N8 _2 } I; s2 ^$ }/ A, C- #include "tim.h"//包含tim头文件, g$ c7 `- A" G8 c
- #include "encoder.h"
+ Q* c- h8 a' n' _, m5 j
- \' M7 q. m) y- int iTim4Encoder;//存放从TIM4定时器读出来的编码器脉冲' d x8 o( ^6 [
- int iTim3Encoder;) C8 C1 m |, |) P* w& C
- % ]; O. e7 X q3 g( h/ S
- int GetTim4Encoder(void)//获取TIM4定时器读出来的编码器脉冲1 o4 `& h& ~! G, P& C8 I
- { q" u' p# M d- L9 C
- iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4));//先读取脉冲数9 c- ]. W ^; j/ s" A) n7 s
- __HAL_TIM_SET_COUNTER(&htim4,0);//再计数器清零+ P$ T0 @6 H6 G) G, c, g0 e
- return iTim4Encoder;//返回脉冲数
6 V% e: x' R a3 W6 c - }
. g+ R% _, d: ^0 T
% y" z& F4 Z& e& u2 R/ S$ `: j. v- int GetTim3Encoder(void)//获取TIM4定时器读出来的编码器脉冲4 e2 D; Y- y) m* p5 e0 v- \
- {
9 j B4 y% e" S& j - iTim3Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim3));//先读取脉冲数7 V5 t4 h6 o, T) R! q
- __HAL_TIM_SET_COUNTER(&htim3,0);//再计数器清零
+ n3 V- P$ H3 F Y - return iTim3Encoder;//返回脉冲数7 ?5 L$ H" R3 L! F: l, r
- }
复制代码
0 k4 w- j) Q- `! h在encoder.h文件下添加以下代码:
8 O9 J2 O! j# e, K* V- int GetTim4Encoder(void);//声明函数
4 o& u6 B7 W9 E% @: w - int GetTim3Encoder(void);
复制代码
w( {$ |* a0 Z4 H7.点击编译并进行烧录,完成PID速度闭环控制任务。可在在stm32f1xx_it.c中更改小车M1和M2电机的脉冲速度,正数转向为正向,负数转向为反向,并在调试器中接收小车的脉冲速度。(需要打开小车电源开关)2 C) { V' ?1 k$ G9 i$ e, s: z& f
例如此时设置M1电机脉冲速度为-20,M2电机脉冲速度为40。
) u: Q- o' E+ i* c) s
8 A) C x+ a# D. W* p4 a
& ?+ m2 t0 E5 A" T% q8 I打开串口数据调试工具,可查看小车实时输出的PWM值:
! H' h8 I* O9 m1 D1 `2 h* ]2 ~( D: |3 S) P5 b
+ f* B3 e& [& Q( x/ H
5 ^$ R4 b, W7 w" O. c; P% f6 {$ I! p6 R+ O" P- s# n) i
|