摘要 本章还是点灯,但是小飞哥带大家换一种点灯方式,利用PWM功能实现“呼吸灯”,什么是呼吸灯?顾名思义,像人呼吸一样的灯...简而言之就是,吸气...呼气...实现灯光渐亮渐灭的效果。
% D; S' @% P( v# |: A PWM原理介绍脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制,如下图(摘自正点原子手册) & b$ k' ~3 ?& f8 p+ K8 y1 [6 _
1 O+ `) ~# e0 s; E( j. `
- R. |0 Z) d6 tPWM 原理示意图 - C8 h1 a7 L1 k3 j
5 ?! u I6 u' v1 x: M/ f* {
上图就是一个简单的PWM 原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当 CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变 ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。 8 H: L1 }5 t) ]% C2 I5 {# h
STM32的定时器几乎都能够产生PWM波,高级定时器 TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,如下图定时器4,可以产生4路PWM波。本文,咱们只使用一路PWM,学懂原理之后,随便怎么搞~ 2 \; ~2 h. J( a
* c# F4 ~+ l7 {1 ^ }! e# _7 l5 K1 g
cubemx配置PWM本次使用的是TIM4的通道4来产生PWM波,也即是PB9。硬件连接比较简单,LED正极连接PB9,负极连接GND即可。 关于时钟等配置,就不多做介绍了 选择TIM4->勾选inter clock->通道4->PWM output CH4->PB9 0 i" q5 G# v9 Q8 e' S4 h. B
$ |& P2 ^- A7 k/ X
定时器参数配置,主要分为两部分,一部分是定时器的基本配置,分频系数、周期,这个配置不是固定的,把握频率的计算方式即可: fclk= (Fcore/(Prescaler+1)/(Period+1)
$ A8 d( v# W7 t6 Z4 D! s8 }
按照我的配置,计算有得到: fclk = 72000000/500/72=2KHZ 这个频率是直接影响到LED灯光的闪烁频率的,如果设置的太低了,会有明显的闪烁感。 % c, n1 ~ ~; @: R6 d5 _
0 T0 O- a2 K7 \/ V
PB9是被复用的
: n% v" C* i5 f
9 i6 V/ T# p; z$ u
配置是比较简单的,直接生成代码即可
( r! U6 B- S0 r8 r! o9 |- T+ U9 N PWM代码解析上面知道,PB9是被复用为PWM通道功能的,代码如下,自从有了cubemx,代码初始化似乎变得简单了呢... - void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)& M* f7 [6 r) }2 t% U
- {5 q% ^# ]' X5 ]4 u$ ?
- 6 E J) d- D; w A& k
- GPIO_InitTypeDef GPIO_InitStruct = {0};9 ~8 Y) z' A& h
- if(timHandle->Instance==TIM4), X# J8 m8 Z; A" H$ Q
- {% S- f1 |" ?7 N2 R& d# e$ m% z
- /* USER CODE BEGIN TIM4_MspPostInit 0 */
% g# U& e1 E- p7 s# J8 W6 z; t. \ - ]6 h3 I9 V3 ~5 A
- /* USER CODE END TIM4_MspPostInit 0 */
# O" t6 l3 ~/ h, [1 y% ^# j
4 A, m# n- T) d$ i- __HAL_RCC_GPIOB_CLK_ENABLE();3 K, g- N* b* G+ w& S, ^2 A
- /**TIM4 GPIO Configuration
0 t- f9 ^1 T& G& O; K6 h- g - PB9 ------> TIM4_CH4
( X6 T% h& A& ]0 o! w( g2 j - */
7 q3 k! \ o2 r* I - GPIO_InitStruct.Pin = GPIO_PIN_9;3 ?9 s/ u( z: }7 S& _' b& e" ?% Z
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;7 L, [8 u8 z* t7 X, c
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;& I! ?) s1 z8 G: ~' }8 F6 ]2 n& C
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);: U/ }& E# [. p* F/ }
- , t! X: P2 ~$ |
- /* USER CODE BEGIN TIM4_MspPostInit 1 */
/ ~2 H5 _. n# g: t$ F - ) |+ V( l$ I+ b: P' L5 Y: T/ e3 A+ d
- /* USER CODE END TIM4_MspPostInit 1 */( y% q" H* G# f; {) x+ z" l2 S
- }$ _( f" L* g0 w; e! L& w
- 5 G! c7 N( x8 O6 v7 B( U# K
- }
复制代码
$ o. [) i/ m& l" g上面我们对定时器及PWM参数的配置代码如下,其中有一个参数.pulse,这个参数是决定PWM的占空比的,计算如下: Pulse = .pulse/period 9 K B# E% m3 E0 O. q5 b4 W
计算得出的就是我们波形,高电平(由于是配置的模式1)所占整个周期的比例,占得比例越高,相应的电压会越高,对应的LED灯会越亮 8 f9 i6 B) w' Q
关于定时器的参数是被封装在一个结构体里面的: - /**
0 R& x, \( w" V8 S4 i) M - * @brief TIM Time base Configuration Structure definition) ]) @! f' `1 `
- */
1 a; t' {1 d1 w; e* i - typedef struct
9 ]4 C, R$ y$ O/ n - {* t p$ ^' R# `) F2 d; [3 n2 b" I) Z
- uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.# u4 ~6 @, e% p5 F) ?, w9 ]
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
4 n; j* E, X+ z8 [; o) @
* W& U# V8 u3 W% R1 d- uint32_t CounterMode; /*!< Specifies the counter mode.
, M% z' w' |2 [" o6 Q: N - This parameter can be a value of @ref TIM_Counter_Mode */+ I; b/ j7 F W: s) [- E3 A' e
- - L Q# V2 P1 e- h
- uint32_t Period; /*!< Specifies the period value to be loaded into the active
! R6 F' t U" V _! w - Auto-Reload Register at the next update event.
' p% r- |% h" R - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */4 w6 f; _, ~9 p m
. P! V& g; ?" l3 p) ^4 k) Z8 J- uint32_t ClockDivision; /*!< Specifies the clock division.
" I, \9 U. ~* ~: i, u( i; Z6 X - This parameter can be a value of @ref TIM_ClockDivision */( r6 P S4 ~; Q8 W4 l
" _8 Z- O+ J2 ]5 d- uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter
! W- I+ A+ S& s% m- R! J& @ - reaches zero, an update event is generated and counting restarts
]) L% q; U# Z - from the RCR value (N).
0 P# |" @6 [5 K - This means in PWM mode that (N+1) corresponds to:8 S3 Z6 P8 Z' u4 a! i( n
- - the number of PWM periods in edge-aligned mode
5 Z! w" m* m/ K3 j0 W r8 k - - the number of half PWM period in center-aligned mode
7 t% d, @$ N+ J9 Q4 Q3 e( j - GP timers: this parameter must be a number between Min_Data = 0x00 and" Z7 ?' J" `6 ?# i3 i5 E; D5 J) {
- Max_Data = 0xFF.
9 b& p6 k' m! B/ i9 y - Advanced timers: this parameter must be a number between Min_Data = 0x0000 and
8 @0 W! h% ^8 y" q5 m; k. I% b - Max_Data = 0xFFFF. */
. L0 W8 L5 l3 [& p8 J; K - , T2 o1 s6 r! R1 S% e! a
- uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.& K! I: o5 H, h" v# Y$ S
- This parameter can be a value of @ref TIM_AutoReloadPreload */
- t' _0 [9 z$ F. L% Z6 }, f - } TIM_Base_InitTypeDef;
复制代码- /**
' S1 m" \8 w6 I3 s) H - * @brief TIM One Pulse Mode Configuration Structure definition: d& l' Y4 d% F
- */
% S5 J5 J/ {# k/ N. V1 q) T - typedef struct2 o* i" G" R) X E$ g- w+ L3 P
- {
" P- r' Q6 D" k. c' Y0 s - uint32_t OCMode; /*!< Specifies the TIM mode.6 i& X: ?4 i/ q) u
- This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */; C. V% X5 L; @
- 3 Y. B$ s" ^0 B! d) t$ k
- uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register. ?* c6 t- O2 }! K
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */5 P- f8 ]8 r+ L+ D
/ k! L0 l# K" h) S0 g& G( n- uint32_t OCPolarity; /*!< Specifies the output polarity.
" ~, e4 a/ m9 {6 l# y& Z8 I - This parameter can be a value of @ref TIM_Output_Compare_Polarity */
@! M. i- L7 `5 G# z
2 |0 L. P: {; b2 w7 |+ t; ?- uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.7 ? G; `8 B- d
- This parameter can be a value of @ref TIM_Output_Compare_N_Polarity5 `# i- J4 W5 G% m# Z1 Q
- @note This parameter is valid only for timer instances supporting break feature. */
y0 d; p) T, |" M; ?7 S# L. h7 n - # ^. S: R0 e2 Q a6 `+ q
- uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.- B. m) o2 f3 j# Y R) l
- This parameter can be a value of @ref TIM_Output_Compare_Idle_State
* T" p! i* @. j. o) q - @note This parameter is valid only for timer instances supporting break feature. */
/ g( z/ |" ]1 N: S- K, Z/ A0 f/ g - 7 U! L( Y% @* |
- uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
4 J4 I; ?; D- C7 o8 _$ f - This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State; `- j7 f' w* c
- @note This parameter is valid only for timer instances supporting break feature. */) J* ~2 ^9 [: S* _6 E6 k; k J
' D O! B0 E6 |3 V* T2 Z- uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.
5 b5 a! o% T9 W$ ? - This parameter can be a value of @ref TIM_Input_Capture_Polarity */2 r0 _) P, K. p! |- k( v
0 {- @8 T4 K# a! S9 X8 I* L- uint32_t ICSelection; /*!< Specifies the input.- C/ f* f M4 m1 V: x, {8 c! ]+ [
- This parameter can be a value of @ref TIM_Input_Capture_Selection */" T6 M6 t/ a8 s+ ]8 `' D: D2 w$ V; B
- 5 b" m9 i- x6 ~1 m
- uint32_t ICFilter; /*!< Specifies the input capture filter.4 G l* W+ V2 G q
- This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */
$ N" L. E; S5 d - } TIM_OnePulse_InitTypeDef;
复制代码
. |- I6 z; Z* B/ p3 N" v同样的,PWM的配置也是被封装在一个结构体里面,所以说,用了cubemx是方便了,其实对初学者学习是不太好的,知道了怎么用功能,但是背后的代码结构可能了解的会很少...
# O$ l* J- N7 V8 a' L: X N
7 K+ p [; V) m( r
 关于PWM的函数还是非常多的,此次功能比较简单,仅仅用到了红框中的几个函数,主要看看start函数,其他的初始化过程中自动调用了,不用关心。 + u. A- _9 I e2 W T1 X
5 e/ v8 p; F+ h3 N
start函数需要传两个参数,一个是定时器,另一个是对应的PWM通道号,本次 利用到的是TIM4,通道4 5 _7 K5 w% s# v- L
0 d: Q5 O- ^; J2 o3 f! m& h
启动定时器之后,我们需要更改的参数是PWM的占空间比,HAL库以宏定义的方式给了我们定义,看了这个代码之后,你有没有一种感觉,每天满世界找优秀的代码,找优美的宏定义写法,这不就挺好的...所以,库函数本身就是一个宝藏学习资料,大家不要忽略了 / a$ C& U$ O O7 U6 Q& j: u! s+ U
: a' g, n/ w/ i
代码编写看了上面的函数介绍之后,其实写起来就很简单了,结构体能够让代码变得整洁一些,先来定义一个呼吸灯相关的结构体: - typedef struct{
" d0 d" v: D1 m! q! `9 ?9 i$ O - uint16_t LedpwmVal;//占空比调整参数( J! Y7 f$ Y; q }/ q
- uint8_t LedpwmVal_Dir:1;//调整方向,1-递增,0递减+ C: n. G& X( G7 Q0 s
- }peripheral;
复制代码
% j: R! d& e4 I$ c5 r
初始化结构体成员为0,1,启动PWM波,接下来编写呼吸灯效果代码,代码也是非常的简单,延时是不能省略的,否则效果是眼睛分辨不出来的...代码也是非常的简单
2 G; J( z1 U- a e1 \5 s# S3 I+ a' q. a# u
0 C! F8 y' N- Q, f9 v0 y! ^! J5 g5 d5 d1 D
- while (1)' y. l; A, ]4 i" k+ Z: ]' b
- {% n* H1 p5 j+ R L$ ?
- /* USER CODE END WHILE */
* |5 O( t, T3 e3 [- X
4 \* N! p. C; x5 X- /* USER CODE BEGIN 3 */
: _7 x, u J+ o/ E0 `1 d - HAL_Delay(10);//加延时,否则变化不明显,看不到效果8 B# z2 _" U# a
- & d7 a8 F5 H; J* N: M( }& W
- if(PWM.LedpwmVal_Dir)4 z( a2 n s$ @. g
- PWM.LedpwmVal++;( Y. H& h: R( C
- else
, q/ M2 Q- Q4 D. Q' d - PWM.LedpwmVal--;9 n4 [, D O; x; u" P, ]6 F
- 9 z( a4 l% I' t( r+ ^' w* n6 M/ s
- if(PWM.LedpwmVal>breath_UP)$ b3 x; \+ N4 i* M& z
- PWM.LedpwmVal_Dir=Breath_DOWN;//切换为PWM值递减状态8 k0 j# o" S& Q
-
1 W- L' K9 ?5 j! X9 b3 l - if(PWM.LedpwmVal==Breath_DOWN)
8 R- S+ x* E) `& C - PWM.LedpwmVal_Dir=1;//切换为PWM值递增状态( \* N- u# ^1 u$ K$ X/ \
- * f; H. `) O K6 H- f, \1 |
- // TIM4->CCR4 = PWM.LedpwmVal;
8 x! \9 x6 N# W& M3 A/ y - __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,PWM.LedpwmVal);//设置占空比参数
5 Y# Q1 X: Q2 d$ c6 ~3 z - }
复制代码 9 x! e) Q' a ?
9 D0 ^: Y" c/ P% e+ M4 q" M
呼吸灯波形实际是怎么样的呢,接下来通过逻辑分析仪来看看波形
! u R, |6 p9 }/ E
为了效果明显,我们选择10ms增加10的比例来看看波形: 递增效果: 1 A4 ^2 G9 g# L3 q' T
Y7 F1 C0 M# e- b
递减效果: ( j' [5 ]9 X2 z- a3 e% R* d
& I# I8 [2 P' t2 W转载自:Embedded小飞哥
( v% ~ X. g' {) T2 u% E如有侵权请联系删除' K8 k5 ]* {+ y( \8 S" W
% F ?, L0 k0 ]# t }# o% s K7 J; t
|