|
摘要 本章还是点灯,但是小飞哥带大家换一种点灯方式,利用PWM功能实现“呼吸灯”,什么是呼吸灯?顾名思义,像人呼吸一样的灯...简而言之就是,吸气...呼气...实现灯光渐亮渐灭的效果。 4 q9 i$ B7 X1 E1 B6 x3 E
PWM原理介绍脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制,如下图(摘自正点原子手册)
. L7 ?+ |0 s+ Y; H0 `3 q5 `. G
; ^9 U' v: o b* \& D
# n' \* w. k. I3 f: |7 @& rPWM 原理示意图 : {. o0 j9 c7 \
: N) M- f9 _+ \6 |上图就是一个简单的PWM 原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当 CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变 ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。
0 {: G6 b- x; U
STM32的定时器几乎都能够产生PWM波,高级定时器 TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,如下图定时器4,可以产生4路PWM波。本文,咱们只使用一路PWM,学懂原理之后,随便怎么搞~
4 _8 b. k/ a1 }# X2 f6 {- U
. _/ ?7 C- ~$ a! @
cubemx配置PWM本次使用的是TIM4的通道4来产生PWM波,也即是PB9。硬件连接比较简单,LED正极连接PB9,负极连接GND即可。 关于时钟等配置,就不多做介绍了 选择TIM4->勾选inter clock->通道4->PWM output CH4->PB9 4 X) k% f' ^, Q4 I& a4 x# k8 g
; Q& H8 h4 w6 |* g! K
定时器参数配置,主要分为两部分,一部分是定时器的基本配置,分频系数、周期,这个配置不是固定的,把握频率的计算方式即可: fclk= (Fcore/(Prescaler+1)/(Period+1) $ F0 Y2 ]5 u7 ^0 A, M7 Z
按照我的配置,计算有得到: fclk = 72000000/500/72=2KHZ 这个频率是直接影响到LED灯光的闪烁频率的,如果设置的太低了,会有明显的闪烁感。 1 S. b! J4 P$ Y5 ]
' b R% i8 A& ~! L
PB9是被复用的 ; t$ P3 b6 E* a( `& i5 M
: n! i& M5 y" v- m
配置是比较简单的,直接生成代码即可 ( {9 C" k/ H& k
PWM代码解析上面知道,PB9是被复用为PWM通道功能的,代码如下,自从有了cubemx,代码初始化似乎变得简单了呢... - void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
0 Q3 I: e3 G2 T5 U0 O4 ~# J - {
6 E# Y9 i: i- F; q* l9 m$ T1 e
% @: D6 \- \9 B- GPIO_InitTypeDef GPIO_InitStruct = {0};% Q* m6 B1 ?6 Z b
- if(timHandle->Instance==TIM4)" C( j# {$ Y: `- n! E9 v/ z& K% f
- {
& E! V! v4 R* m - /* USER CODE BEGIN TIM4_MspPostInit 0 */8 y& A/ a+ z$ e3 y
- . f8 J8 f* j' N/ ^2 p/ p. T
- /* USER CODE END TIM4_MspPostInit 0 */8 F. a, g: F8 r" W
, \7 |, @, a2 w! t- __HAL_RCC_GPIOB_CLK_ENABLE();
( d/ }4 I) Q; e% s - /**TIM4 GPIO Configuration( Z: v/ S! `. M
- PB9 ------> TIM4_CH4
! y: e# H+ F! k" M( P4 a0 y4 {, G - */
( |8 {9 `2 f$ a" |+ }, M/ z - GPIO_InitStruct.Pin = GPIO_PIN_9;
0 G$ H4 ?! k, y6 I - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
: X& {% i6 c% g# y% {- D - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;# |; n- H5 R7 w$ U2 B( j# d. r
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);) Z4 F) Y5 W, X; Q" z
- 7 m% C4 M: O x2 I' V# c
- /* USER CODE BEGIN TIM4_MspPostInit 1 */
/ j- m, ^7 c5 Q& w5 t
, O8 a" |, ]! t3 u/ l- /* USER CODE END TIM4_MspPostInit 1 */1 r3 a9 a- X0 ^( k
- }
) J. L5 F- t* T2 o - ) ]8 ~+ ~4 g2 Z$ F, a; {! X
- }
复制代码 ) O8 M% f8 i9 ^ X; ~8 f( D
上面我们对定时器及PWM参数的配置代码如下,其中有一个参数.pulse,这个参数是决定PWM的占空比的,计算如下: Pulse = .pulse/period
# K1 l. Q+ P/ p
计算得出的就是我们波形,高电平(由于是配置的模式1)所占整个周期的比例,占得比例越高,相应的电压会越高,对应的LED灯会越亮
" W7 j% ]. |0 @% q; i
关于定时器的参数是被封装在一个结构体里面的: - /**0 `4 M3 Q/ X( {* N0 M: m
- * @brief TIM Time base Configuration Structure definition
- m" d& H( d9 _9 D: F( u - */0 c' f7 V% }& z, R0 [" r+ w
- typedef struct: v$ o) d Y* C2 {9 y3 T( A
- {6 Y* p8 N& y0 Q0 b
- uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.
0 V' I" s7 x8 E3 f( Y; ~ - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
* O0 `' K( ]3 T- ` |) G - ! t) J8 i5 c# {5 M' f
- uint32_t CounterMode; /*!< Specifies the counter mode.
8 p6 z$ q. ~& E, {7 u3 [+ m- v" \ - This parameter can be a value of @ref TIM_Counter_Mode */2 U8 a. \; h1 {4 j) z
- 2 A. ~( G+ y9 D) {1 J: ]7 ?4 V& p& `
- uint32_t Period; /*!< Specifies the period value to be loaded into the active+ R/ d9 Q" n; {! r0 h# N, w
- Auto-Reload Register at the next update event.3 `! n! E" X* {5 @
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
2 w4 r1 M) b' ]8 j1 E - $ F, u9 j& W2 ` w* N* ]& n' E4 g+ F
- uint32_t ClockDivision; /*!< Specifies the clock division.7 N( B/ m5 F# r) A8 n& D1 r
- This parameter can be a value of @ref TIM_ClockDivision */# G' ~6 ]% A5 I4 \7 L' Q2 y$ M3 R
) [& V" f# V4 @" Y& t9 X- uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter% P; h& ~; s. S# ^3 Q" I( p
- reaches zero, an update event is generated and counting restarts2 e. _9 x9 {5 H1 g/ j1 J; n
- from the RCR value (N).
4 O- R% k+ c/ e" v! W& c- m - This means in PWM mode that (N+1) corresponds to:
1 V. J9 `. T( R3 J1 \1 K, X - - the number of PWM periods in edge-aligned mode' d6 l( z# w+ v: b4 n' m
- - the number of half PWM period in center-aligned mode* T ?* D6 n6 s' r4 Q
- GP timers: this parameter must be a number between Min_Data = 0x00 and
+ P8 }9 P3 n: ?; U$ i! J - Max_Data = 0xFF.% H l2 Z) {7 `: i) ]- q
- Advanced timers: this parameter must be a number between Min_Data = 0x0000 and$ F4 _ i* i, O2 \7 p w; l
- Max_Data = 0xFFFF. */( H, N; Y7 t8 A" A% _+ P
- 4 p1 }5 M+ ]) F$ F" p2 F L/ G. G
- uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.- y! G: c7 z F( @1 N
- This parameter can be a value of @ref TIM_AutoReloadPreload */
, c% i/ f4 @, w# w - } TIM_Base_InitTypeDef;
复制代码- /**
) o8 u( a* M- h% w$ w- A - * @brief TIM One Pulse Mode Configuration Structure definition7 M8 i; B7 H% V6 o# I" j2 o3 F, n
- */3 @ W) s' V5 z/ N! k
- typedef struct
3 f+ o: w" D- X' `1 b - {
, b+ j2 H& _ u! ?0 R6 ^% P - uint32_t OCMode; /*!< Specifies the TIM mode.- {3 r3 ~: f0 W9 C
- This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
( e) D5 s; T( h% h) _3 Z, z1 a
2 v; E7 a6 ?/ \' L/ @, Y/ R- uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
/ f; H& B, u6 P/ N: Z) ~' j( b8 a3 Y - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
9 f$ r! q% \, ~% u0 b! L. y - 4 y6 a4 b3 `9 A, z
- uint32_t OCPolarity; /*!< Specifies the output polarity.
& m, k: k$ K: I& c - This parameter can be a value of @ref TIM_Output_Compare_Polarity */
; K1 H' H- ` B5 P# N, U8 w - ; P- g' z1 D8 E ]9 @/ y
- uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.
* y3 f& m9 r% h - This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
3 i- m0 p( c+ Q+ o+ ?- {/ f - @note This parameter is valid only for timer instances supporting break feature. */% T6 o% R! [6 h# J7 [# J
! m- T7 M9 Q. y$ |- uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.: A$ s+ P8 q0 z; J. \- o" T |8 Z; s j
- This parameter can be a value of @ref TIM_Output_Compare_Idle_State
3 n/ K+ r9 B _+ p - @note This parameter is valid only for timer instances supporting break feature. */0 H r+ f- R5 |" H& w
3 |& G# j* N+ m8 l% y( g- uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state., X, g- ^1 b) |0 c2 @4 @
- This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State. b# ^0 c" e5 `2 B; ^3 G1 s
- @note This parameter is valid only for timer instances supporting break feature. */
4 a, m: ]( L4 |' n5 h* `, q7 w
8 ~/ W, m F( p, A- uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.0 U9 G' l) |. h; Q% o- b
- This parameter can be a value of @ref TIM_Input_Capture_Polarity */! a, {% a) \- g$ [: |" a, a
/ P0 l5 G3 Z4 ~7 S/ X6 E- uint32_t ICSelection; /*!< Specifies the input.
. ]$ ^. v2 N7 |0 [% {- _: m - This parameter can be a value of @ref TIM_Input_Capture_Selection */) `8 A7 J, _/ q: ?4 f- j
- - X: _( x5 J2 c% L9 R
- uint32_t ICFilter; /*!< Specifies the input capture filter.8 H7 d" _9 p7 [. O
- This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */0 p% }1 \ o) t
- } TIM_OnePulse_InitTypeDef;
复制代码
; K: J$ F# G2 i8 ]7 m同样的,PWM的配置也是被封装在一个结构体里面,所以说,用了cubemx是方便了,其实对初学者学习是不太好的,知道了怎么用功能,但是背后的代码结构可能了解的会很少...
. t' C2 ^( D8 e/ p: n H3 Y
& Z1 S3 m' ~5 W2 R1 k& a
 关于PWM的函数还是非常多的,此次功能比较简单,仅仅用到了红框中的几个函数,主要看看start函数,其他的初始化过程中自动调用了,不用关心。 ! @$ R: s2 G. A# K: J& j* h6 A
$ l: L! s+ |" A" X% e* C% o
start函数需要传两个参数,一个是定时器,另一个是对应的PWM通道号,本次 利用到的是TIM4,通道4 7 G1 C% N. Z) c( q3 N
" |/ Z2 T, u( p- E; e
启动定时器之后,我们需要更改的参数是PWM的占空间比,HAL库以宏定义的方式给了我们定义,看了这个代码之后,你有没有一种感觉,每天满世界找优秀的代码,找优美的宏定义写法,这不就挺好的...所以,库函数本身就是一个宝藏学习资料,大家不要忽略了 5 _8 k0 T" @( [5 q4 O* i
9 \- Z0 g2 T s: e4 {
代码编写看了上面的函数介绍之后,其实写起来就很简单了,结构体能够让代码变得整洁一些,先来定义一个呼吸灯相关的结构体: - typedef struct{& j) W% Q3 ^: H9 x6 X6 u
- uint16_t LedpwmVal;//占空比调整参数3 H1 A6 o* k3 D* l5 N# w
- uint8_t LedpwmVal_Dir:1;//调整方向,1-递增,0递减. I ?% c3 r% H' N4 u
- }peripheral;
复制代码
+ m9 P$ ~' z& P- V: a3 w% J2 b
初始化结构体成员为0,1,启动PWM波,接下来编写呼吸灯效果代码,代码也是非常的简单,延时是不能省略的,否则效果是眼睛分辨不出来的...代码也是非常的简单
9 Q$ ?/ w8 n7 M2 q% O% Y& X5 _' q d+ m) A3 f% @4 t4 ^& q
! v$ A4 \8 j6 E! `3 M& F1 P% h5 i
7 \1 ?9 J' h* q2 f: x, l
- while (1)7 n) v6 B# G, V$ \& u
- {
# C# B. S, ]% D# U' d. r - /* USER CODE END WHILE */
! h1 `" B* y% _ R0 r. |- z - 5 s: @- ?& T" ?& x+ ]# I
- /* USER CODE BEGIN 3 */
' v" R, ]6 B$ H5 l" v- P - HAL_Delay(10);//加延时,否则变化不明显,看不到效果8 c0 X9 ]" L, F- ~; v; F% h
-
, f& J: X! L! o - if(PWM.LedpwmVal_Dir)' a3 c! M" G( p2 b
- PWM.LedpwmVal++;: Q3 t, n) p! R
- else
' y+ N- m, r) R" W& E8 Y - PWM.LedpwmVal--;8 h, y& G$ ~% e1 @* c
- ! B1 M' T! Q8 }* `% v( w
- if(PWM.LedpwmVal>breath_UP)
) u2 h5 V7 l0 j5 F7 R - PWM.LedpwmVal_Dir=Breath_DOWN;//切换为PWM值递减状态+ L1 w' h% I; r/ ?2 y, K6 `6 h
-
! v! X P% e1 _0 U# x - if(PWM.LedpwmVal==Breath_DOWN), b( X; N1 f# z& a4 M) T
- PWM.LedpwmVal_Dir=1;//切换为PWM值递增状态8 C% m8 I$ S) a4 I- I _$ z
-
0 q. h) l1 b1 x1 e! [ - // TIM4->CCR4 = PWM.LedpwmVal;5 h1 M, S# a' `4 G
- __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,PWM.LedpwmVal);//设置占空比参数4 [" `$ r. O- Q" D
- }
复制代码 8 f: Z# Y6 m) l+ j& G2 _0 L9 X* i2 b; K
5 c' E' ^# m, F) B, L呼吸灯波形实际是怎么样的呢,接下来通过逻辑分析仪来看看波形
; r/ W! S4 m! s% g
为了效果明显,我们选择10ms增加10的比例来看看波形: 递增效果:
& o5 J2 G5 o7 P. U/ }% a
F4 @+ y, D5 {0 {$ A
递减效果:
1 O# t! E8 `0 W! ], F7 h& }
5 f/ Q/ ~& U4 M) X: C5 t( S
转载自:Embedded小飞哥
" y- E3 L/ T) F; x6 |& y如有侵权请联系删除
. i# b' }; X& P; C2 q1 V8 n9 E
2 F, T) f/ |, v' @/ |
; q ~+ Q" D% i1 ~9 G |