前言5 L% B+ Y0 p7 @
原来做的差速小车是基于Arduino控制的,感觉有些简单,也有些基础,Arduino方便简单的同时,可操作性感觉也少了很多,所以想将控制器换成STM32,然后将树莓派作为上位机,STM32作为下位机,通过树莓派和STM32进行通讯,实现对差速移动小车的控制,本人也是寒假期间初学STM32,也是奔着应用去的,所以对于STM32编程原理方面可能不太精通,这里偏重于记录应用层面的知识。
T" H. P4 p5 K" F% w0 f9 h+ n; Q8 G( z+ W- E
一、PWM调速原理. {3 @) {& ^/ T' ?( ]
直流电机驱动是最简单的,给电机通上电就能转,根据电机的公式:, S# J7 `- |' ?- g: [$ Q
/ L. V4 }& K- c- [
: O+ s0 z1 i$ z y9 K可知:当提高电压时,反电势升高,进而转速升高,电压与转速大致有如图所示的关系
7 ^9 d# W( a; z: Z5 }: @+ ` L& D' [. M, X8 X' M$ L
+ U$ z6 L# s$ H3 p. P8 j
3 @2 F6 K9 J6 A% W所以我们只要控制给电机通电的电压即可控制电机的转速,但是在实际的控制中,控制直流电机需要通过H桥控制电机的正反转,如图,当T1和T4二极管导通时,有粉色通路;当T2和T3二极管导通时,有蓝色通路,这样我们就可以实现弱电控制强电,通过二极管的通断来控制电机的转向。
- C- j, z8 W8 G2 z/ W7 V- l# p: X7 |4 q
但是这样电机通电时电压就是Us,我们如果想自由的控制Us的电压值基本是不能实现的,因为电机是接到单片机的引脚上的,引脚的供电电压值是确定的,我们就要使用控制二极管的通断时间对电机的转速进行控制,即PWM控制。6 J8 i$ r5 A' J4 E- i W1 G! w
4 q+ y+ c. \1 P图中的D1~D4二极管为续流的作用,因为电机中有绕组,在断电后,电感的电流不能瞬时变为0,所以在断电后电流沿棕色和绿色的通路放点。+ h9 K" m1 a, W8 R9 G1 T
' |: f6 `3 b5 c
1 r9 d, Y; o0 ^# Z0 w
$ T- @$ @7 C7 t9 y6 x+ B" u
' N* k% D1 W4 V
7 W2 i( v5 v9 R+ p' Q% d& b
在一个周期内,我们通过控制通电的时间就可以调控平均电压,而平均电压的高低直接控制电机的转速,通电时间/周期,就可以得到占空比,我们也就是通过控制电机的占空比来控制电机的转速的。
; N5 X2 f9 a" k, M R, G* F; Y
# g5 e* F9 V3 l, X9 H% i+ K3 Y
) Z0 S" U4 e8 M
1 D; R9 z4 B. j* y" _! ~& L) d 在实际应用过程中,我们不用自己搭建H桥,而是使用电机驱动板(如:L298N)对直流电机进行驱动,L298N内搭载两个H桥电路,可以实现对两个电机的转向和转速进行控制。, _) d5 q' O8 j& I
& ~/ `' Q0 ^- s* ^
+ _( E& {9 m$ a ^
4 R m5 b) Q" c 这是淘宝商家提供的电机驱动板控制表,将IN1~4接到单片机的引脚,我们就可以通过引脚输出PWM控制信号,对直流电机进行控制。
, q+ T) ~2 v: v, a* O9 N! c- V$ K; W4 t) T P8 i7 v' e9 ^: N
二、STM32编程实现5 Z9 Z# U+ R( k$ j, N
在STM32中如果想输出PWM信号,需要借助定时器,通过定时器的捕获/比较通道的PWM输出
. w" M7 {+ H2 Z J' q/ ], X7 K. E
7 x F+ }) p" y
% v% U5 c% R* I# I K- a
/ Y2 r& l$ s9 f- \4 p9 a) Z) k2 s
当我们对定时器设置了预装载值arr和比较值ccr后,可以通过配置PWM模式,使定时器CNT计数值超过ccr后产生有效信号,并通过配置相应寄存器设置有效信号是1还是0,而配置PWM的输出方式,具体原理信息可以参考原子哥的视频,也可以参考中文参考手册的14.4.7内容。
- n; D1 c8 n- |3 Q$ q B8 H( t4 x7 c; E( V0 [3 j
' Z% F# X$ A6 e+ ?. r0 S+ W3 G+ R9 w5 p2 E# v
在程序中,我们使用库函数进行配置,配置步骤如下:. O$ M& ^* @# S$ S6 _
1.使能定时器和相关外设引脚时钟 :RCC_APBxPeriphClockCmd()
% I4 w4 T$ z& B3 a+ n9 _2.配置IO口为复用输出模式(查手册8.1.11)配置成相应的模式(复用推挽输出)
6 z0 J' ]: t" L0 W. e9 r3.初始化定时器:TIM_TimeBaseInit()5 r# v+ ~3 R5 L' @; i8 d& Y
4.初始化TIM2 Channe1234 PWM模式:TIM_OCxInit()6 P% y' r; c. f$ U
5.使能OCx通道的预装载寄存器:TIM_OC1PreloadConfig()) A- G; `) @9 d& Y" Q5 ]. o
6.使能时钟:TIM_Cmd()
: O3 n3 ~) Y ^5 X6 t1 o- b- M7.在主函数中配置占空比进行调速:TIM_SetCompare1()* A7 M! G2 q. g1 C! Z6 t5 x
. v! m. i% K% o/ _
- #include "sys.h"
6 c' _; o7 f. E: @& Q9 T2 G
- c$ @* R, |! h- /********************
1 E9 M% s6 H6 ^ - 功能:通用时钟2用来产生通道1234四路PWM信号
) b( l" }2 E b! g2 k3 r - 函数:TIM2_PWM_Init(u16 arr,u16 psc)
0 M- T- [3 X5 j3 ` - 作者:K.Fire
( f# W& O! A. X3 E+ E7 V - 日期:2022.01.304 b+ H1 t* U/ X6 y, @9 E
- 引脚:PA0 PA1 PA2 PA3
1 C( c7 t/ o& L6 w- i! U% b3 ~ - 参数:arr:自动重装值 psc:时钟预分频数
2 `3 E; {7 _6 D5 ?- w$ C: y - *****************/
' v7 \* ]! }3 c5 |9 o9 m B: r$ Z
4 e% x/ R" ?5 X( h8 H- void TIM2_PWM_Init(u16 arr,u16 psc)
# B' d: W4 E( I7 \3 P! O8 ? - { 0 I4 l, u* D9 Z* v" d0 ?0 A1 V
- GPIO_InitTypeDef GPIO_InitStructure;
! r r+ \; y2 c% Y$ N - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;3 u+ `; l, M/ a1 e3 q- Q
- TIM_OCInitTypeDef TIM_OCInitStructure;5 U! {. @7 I9 g1 T/ h0 h: P# \8 V
- 3 w9 j6 D8 ~- K% U" L# W
- //使能GPIOA外设模块时钟
* _! ]. ?* _" M/ a% N - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
& F# g$ E7 H2 ? - 2 d# c. E! H+ i! [( O
- //设置引脚为复用输出功能,输出TIM2 CH1的PWM脉冲波形 GPIOA.0' J' v+ S5 ~3 d3 }# y& u
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM2_CH1
+ P2 R+ q9 i3 o- V9 i# c- K - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出% G" h1 |) e ]: V* f
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
' H9 T, S i" }4 _ - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO+ G) [. i/ q9 y
-
0 o2 f- z% ]" |9 V- y# E+ P - //设置引脚为复用输出功能,输出TIM2 CH2的PWM脉冲波形 GPIOA.1
% V3 D$ O3 w3 N+ _1 E8 a) Z p: _ - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM2_CH2% i) e3 n" o9 b7 x! y2 n. s* ?
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
9 e( ?8 T) G* ]. ] - $ h9 e. x7 p) B0 F% `# V. `' I3 j7 h1 h
- //设置引脚为复用输出功能,输出TIM2 CH3的PWM脉冲波形 GPIOA.2
+ C9 I9 n6 K, n) a* w - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //TIM2_CH3
+ x9 m% k$ `, ^# k - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO) X. U9 f) t* n0 R5 Y; K, N3 i
- 2 o4 |! j9 p/ M
- //设置引脚为复用输出功能,输出TIM2 CH4的PWM脉冲波形 GPIOA.31 P0 [" [$ f# p' C& h; ]- q
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //TIM2_CH4
4 o( R3 R: f2 R! Z: @9 @ - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO: y# b, p9 ~: a5 _( S0 C
+ O4 }. M4 R' G& D: w! b- //使能定时器2时钟
% o1 n- v/ Q/ [; S2 d: [ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
* O. A/ O2 b, O% S' [ - //初始化TIM2) o8 u d7 _0 f) K, t' |
- TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 g1 F5 B- a b0 e
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
' Y, h+ [5 C, [8 E( k4 S* W. P - TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim+ t% A, b* Q0 m* a
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式. G! x0 L' G7 M9 q
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
' ~) j0 V0 m" z7 T -
7 c4 L) s& t& t, k - //初始化TIM2 Channe1234 PWM模式
S, U) ]3 j. R5 Z& h- ? - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2; T8 I' M1 \5 U9 A7 }5 x6 W k n* @
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
b5 h. J2 [% \6 f# c - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
7 g6 d8 ~; b, k% g2 L2 H$ G -
& R3 l$ d# v9 q2 ^2 ]; R. R - TIM_OC1Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC1
6 Z t" g7 D. G/ @' e* k9 U - TIM_OC2Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC2' m- Y; V) _& e1 H; |3 Y; [
- TIM_OC3Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC3) k9 {% t% A9 b' b
- TIM_OC4Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC4
9 Z3 p1 \) y$ _; M; Z9 p
; B& W/ ?7 B, o5 a4 u( M- TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR1上的预装载寄存器
: |* w9 l3 O- | - TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR2上的预装载寄存器+ _% Z; a$ B6 M2 n- y
- TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR3上的预装载寄存器" g0 I5 c: h) ]- }/ _9 A
- TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR4上的预装载寄存器
, y8 E2 ~1 p" J& L# Y6 J - - h' x3 ~+ C% ?
- TIM_Cmd(TIM2, ENABLE); //使能TIM2
- L$ J3 ?5 \ ?: @2 X0 Y - " d# C, q3 D1 @, c& I) G
- }4 |9 N& x) }5 Y8 e$ e
复制代码
8 D. F* D# I/ d6 l总结+ n4 q% _6 {* u2 C) z4 ?; A7 }
原理部分我讲的不清晰,我也是看原子哥的视频学的,大家可以通过传送门去B站看原子哥的视频,先更新PWM调速,因为这一部分我测试成功了,等我编码器来了在对编码器和蓝牙进行测试。
- m6 o3 ]' G0 |6 m( ~! p! p/ {+ k7 F/ K7 x; Q# ?- R
6 E) k) w& C- w/ v1 E) E# h
! H1 F1 g H$ p4 h
|