|
摘要 本章还是点灯,但是小飞哥带大家换一种点灯方式,利用PWM功能实现“呼吸灯”,什么是呼吸灯?顾名思义,像人呼吸一样的灯...简而言之就是,吸气...呼气...实现灯光渐亮渐灭的效果。
4 u$ f% t+ d4 B' g; H+ N$ L- c PWM原理介绍脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制,如下图(摘自正点原子手册)
# l) [8 }# b( h
: Z& r/ [/ F! ~+ d6 u
! ?. F' Y! m; a- k# p6 uPWM 原理示意图
( g$ E* I. H* S( c1 ]0 U4 |, [+ J; `; d0 I8 L* i. _8 D8 O; F: y
上图就是一个简单的PWM 原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当 CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变 ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。
$ \- [4 A" P/ d$ s' i0 \% ]) t# o
STM32的定时器几乎都能够产生PWM波,高级定时器 TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,如下图定时器4,可以产生4路PWM波。本文,咱们只使用一路PWM,学懂原理之后,随便怎么搞~
' _, P8 A/ @; Q+ u: m4 x
- i! T5 S4 t+ E. r- n/ T) o5 a
cubemx配置PWM本次使用的是TIM4的通道4来产生PWM波,也即是PB9。硬件连接比较简单,LED正极连接PB9,负极连接GND即可。 关于时钟等配置,就不多做介绍了 选择TIM4->勾选inter clock->通道4->PWM output CH4->PB9 4 e2 m% ~6 w) S+ t, s
8 |. C X; D1 N" _, E2 O9 r
定时器参数配置,主要分为两部分,一部分是定时器的基本配置,分频系数、周期,这个配置不是固定的,把握频率的计算方式即可: fclk= (Fcore/(Prescaler+1)/(Period+1) . `2 i3 W; C& p
按照我的配置,计算有得到: fclk = 72000000/500/72=2KHZ 这个频率是直接影响到LED灯光的闪烁频率的,如果设置的太低了,会有明显的闪烁感。
1 H2 ^0 [0 g% K
: l3 a- A! I" m; G( |
PB9是被复用的 3 K# y+ I0 b$ i4 j! Z
2 M b* G f( R" M* y/ }6 d
配置是比较简单的,直接生成代码即可 ( d5 A' O6 K6 o. x
PWM代码解析上面知道,PB9是被复用为PWM通道功能的,代码如下,自从有了cubemx,代码初始化似乎变得简单了呢... - void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
0 w- r& |& A5 a+ Y - {
5 x# g6 n. P% [) x - * ~+ J' S! i' ]7 X
- GPIO_InitTypeDef GPIO_InitStruct = {0};
) f% [. }9 R8 b- \2 s$ e/ I - if(timHandle->Instance==TIM4)5 Y# f7 r4 }+ ]0 H2 A0 o7 G
- {4 F) D$ y" ^# _! Y: l3 b, F' b
- /* USER CODE BEGIN TIM4_MspPostInit 0 */
. G" I, A8 O( A8 Q+ v- u
! E2 V% g5 c/ j5 q0 C0 ^, @- /* USER CODE END TIM4_MspPostInit 0 */
' n) z& h6 c7 }# u, x1 T
$ q& A& Y2 F5 E& b* F1 I- __HAL_RCC_GPIOB_CLK_ENABLE();: J; S" e' R7 T. G0 f
- /**TIM4 GPIO Configuration: ~5 s+ ]& K; Z" ~9 @, f2 v6 g y4 R
- PB9 ------> TIM4_CH4$ r3 @; Y- t- W) w P
- */8 {7 u! l, x0 T
- GPIO_InitStruct.Pin = GPIO_PIN_9;3 m+ i8 c" _ G& r. G% Z7 G+ j
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
. F0 E& w: r W# Y, m9 T- f - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;1 a/ p, t/ Q: y6 t- {+ v7 C
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);& Y+ z5 `4 x: K/ M$ ?7 q/ F
2 Z; W. Q$ h# M, M; `4 i$ c- /* USER CODE BEGIN TIM4_MspPostInit 1 */! @3 v/ r# w! o: a5 n' f6 b: l0 z
- ; L; Z6 x6 v" i; F
- /* USER CODE END TIM4_MspPostInit 1 */
6 \6 r, d) j3 K$ J$ D - }
5 w3 _! q" F8 J: c: s5 m - : u$ D$ q! ]$ S, y& E6 E6 r, f. {
- }
复制代码
# ~1 K/ m3 L( m' p3 `( v8 Y$ j, s8 G/ A上面我们对定时器及PWM参数的配置代码如下,其中有一个参数.pulse,这个参数是决定PWM的占空比的,计算如下: Pulse = .pulse/period - W4 f- R% B0 `- `6 _
计算得出的就是我们波形,高电平(由于是配置的模式1)所占整个周期的比例,占得比例越高,相应的电压会越高,对应的LED灯会越亮 : Q8 t8 R% [9 |0 j$ j
关于定时器的参数是被封装在一个结构体里面的: - /**1 f% k+ ^; }: ]
- * @brief TIM Time base Configuration Structure definition* C' X6 t: `- l: Y3 r
- */
) {* U+ U+ {1 v" E+ s C - typedef struct: P0 T, m! |" z
- {
9 m- Q m2 A! `& P* c0 M% V - uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.5 {7 z9 u2 ?( u; e3 Y( ^
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
7 Y' ~8 ^' T- W/ G/ R7 l) X
% G2 X4 Y: e+ E9 V* v- uint32_t CounterMode; /*!< Specifies the counter mode.
0 w: S+ g8 p' O( _& j - This parameter can be a value of @ref TIM_Counter_Mode */, }! v7 }; \4 q
# x& M/ p9 t, q9 ]5 D- uint32_t Period; /*!< Specifies the period value to be loaded into the active/ C, `# j2 w: o9 z6 _4 [; c, n
- Auto-Reload Register at the next update event.
$ X3 w1 X9 {3 n - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
8 c: D9 g$ @+ [1 L; b% _- Y! s
) b; Y# p% ^% m* ^* D9 d- uint32_t ClockDivision; /*!< Specifies the clock division.7 ]9 U5 u' w) }: F
- This parameter can be a value of @ref TIM_ClockDivision */
. l+ h# B m) ]* z1 k
9 o+ b* g" d& ^- uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter& T/ z4 C6 \* o* ?1 _7 _# @1 g8 u& `
- reaches zero, an update event is generated and counting restarts/ o* U o, N) G) b/ p& I* F
- from the RCR value (N).
" g. ?# H& a* P `+ S& q, d - This means in PWM mode that (N+1) corresponds to:: `( F' O# P* @7 o4 y, \
- - the number of PWM periods in edge-aligned mode% y1 U* O' h3 z7 |; ^. T* b
- - the number of half PWM period in center-aligned mode
* N! w3 R* b7 H - GP timers: this parameter must be a number between Min_Data = 0x00 and, G' H) w3 ]0 I8 R+ F
- Max_Data = 0xFF.. s, {( }* U3 r- S$ g* A
- Advanced timers: this parameter must be a number between Min_Data = 0x0000 and5 i, l- I1 M) E1 c7 P5 _3 g
- Max_Data = 0xFFFF. */* O- d$ z3 A; X1 t
- 5 `6 W% c( ^, l1 u, Z) A; {
- uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload./ c. f) D0 p$ {
- This parameter can be a value of @ref TIM_AutoReloadPreload */
: Z0 @9 y" U H1 c7 } - } TIM_Base_InitTypeDef;
复制代码- /**5 N1 w1 C; ~" a, @
- * @brief TIM One Pulse Mode Configuration Structure definition' @1 h9 L7 `# u5 Z
- */
0 N. Q6 A) f Z* o k9 F - typedef struct
4 X: T9 b' Y8 n Y9 B0 J% v% R - {
! R0 K- A1 j: }2 C: Z7 v. m - uint32_t OCMode; /*!< Specifies the TIM mode.# b. F+ w2 G. T" W
- This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
3 f _2 }# T; C# z. r
& [) H n$ N/ Q9 M( H- uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register. z1 l# [( Y, Q. u* \
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */) p w Y/ e9 S1 O# R
@1 g8 i2 M4 N- uint32_t OCPolarity; /*!< Specifies the output polarity., _; ^3 H" W- u
- This parameter can be a value of @ref TIM_Output_Compare_Polarity */
/ @0 r+ ?: V# Y9 s: L - ; j# w6 ~% C+ B& ]; |) t+ y3 e2 B
- uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.
/ |# Q; i8 v* Q& s! i4 t - This parameter can be a value of @ref TIM_Output_Compare_N_Polarity, O; M9 B4 S! @
- @note This parameter is valid only for timer instances supporting break feature. */( o: Z( Q: E" [2 Z- h9 v9 k X
- / o/ y4 r$ q% J. d/ `' @( a* `
- uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.: a4 D, }' p# c, W
- This parameter can be a value of @ref TIM_Output_Compare_Idle_State$ F3 i& x* @# J: ]4 V S% p
- @note This parameter is valid only for timer instances supporting break feature. */
! n {9 e, e, y" q' o - / b: H) S$ {5 T5 p
- uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.. Z! C' a) z( `
- This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
. ]" A; u: N& H! } - @note This parameter is valid only for timer instances supporting break feature. */
/ c* x) Q4 [- f - ! z3 @/ A, A0 X2 W- o6 u" l' V
- uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.) s; f5 F* ~; |8 N
- This parameter can be a value of @ref TIM_Input_Capture_Polarity */' B2 Y+ j$ Z8 Y' k8 K' j" A& U, k
9 q- a( l |0 `: v- K$ e- uint32_t ICSelection; /*!< Specifies the input.- \2 l& L: f8 G5 T
- This parameter can be a value of @ref TIM_Input_Capture_Selection */
; z* B7 m! U! ?' ?) k - 1 q G$ o( c7 |, x
- uint32_t ICFilter; /*!< Specifies the input capture filter.
" [9 d e7 t* A' p1 j5 r i - This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */, \1 c: ^2 n1 F0 `# m# i4 o u
- } TIM_OnePulse_InitTypeDef;
复制代码 9 s% X6 z" ?6 ~5 R
同样的,PWM的配置也是被封装在一个结构体里面,所以说,用了cubemx是方便了,其实对初学者学习是不太好的,知道了怎么用功能,但是背后的代码结构可能了解的会很少... * w3 k9 n; h9 z' d( k
3 Y1 y7 |* J1 j
 关于PWM的函数还是非常多的,此次功能比较简单,仅仅用到了红框中的几个函数,主要看看start函数,其他的初始化过程中自动调用了,不用关心。 J1 a& j; ~+ N3 n, A' x J
' S) P; {" g7 J2 o- {( x
start函数需要传两个参数,一个是定时器,另一个是对应的PWM通道号,本次 利用到的是TIM4,通道4 6 h0 _( @' t7 |$ w2 J
5 ]) s, ~1 K Z o; u
启动定时器之后,我们需要更改的参数是PWM的占空间比,HAL库以宏定义的方式给了我们定义,看了这个代码之后,你有没有一种感觉,每天满世界找优秀的代码,找优美的宏定义写法,这不就挺好的...所以,库函数本身就是一个宝藏学习资料,大家不要忽略了
z7 m3 u' f) o3 f- y8 B! ~
+ g, q/ n3 q* v! O& K2 [1 B
代码编写看了上面的函数介绍之后,其实写起来就很简单了,结构体能够让代码变得整洁一些,先来定义一个呼吸灯相关的结构体: - typedef struct{
6 H7 X ^7 U' j+ A" n" Y - uint16_t LedpwmVal;//占空比调整参数
* q) x/ Q2 Q {$ _ - uint8_t LedpwmVal_Dir:1;//调整方向,1-递增,0递减
% y, V3 y6 N5 H0 t" } - }peripheral;
复制代码
2 Y# M4 |+ ^8 b; h* l
初始化结构体成员为0,1,启动PWM波,接下来编写呼吸灯效果代码,代码也是非常的简单,延时是不能省略的,否则效果是眼睛分辨不出来的...代码也是非常的简单
+ p0 M4 r; F% N
* z( t' t3 a0 L$ A/ ~0 x6 R! s6 I+ N$ D/ l0 c
7 X6 @4 C. U- m# o
- while (1): i H6 ^5 @" _0 q ?6 J
- {" [4 J/ R' p/ v, W$ D' t
- /* USER CODE END WHILE */2 B9 ?- W2 \& k" U$ @
- 1 [' h5 ~" H/ P+ A" N; I, s
- /* USER CODE BEGIN 3 */3 I( c" Z' l @% h
- HAL_Delay(10);//加延时,否则变化不明显,看不到效果
2 \) b* V, y% n& ^* v; g -
V& [* x) C3 m/ U) `( i$ L2 ` - if(PWM.LedpwmVal_Dir)' D1 j* R9 m: }- t
- PWM.LedpwmVal++;
1 ], K/ V, L* i7 Y( o6 l. D9 |4 |) _ - else6 b6 D- g& P" c# j. D$ N) d7 N
- PWM.LedpwmVal--;8 y6 e9 H- Y/ u- t) Q0 n
- ) ]8 B. _ X! @) G' x
- if(PWM.LedpwmVal>breath_UP)
# F( M/ d: U9 ~, |* L! E: f2 z4 k& k - PWM.LedpwmVal_Dir=Breath_DOWN;//切换为PWM值递减状态# f5 b* l6 F- F' [! h' Z
-
% G' d" |9 I$ d. O - if(PWM.LedpwmVal==Breath_DOWN)
+ ?+ ` F+ t Y# ~) n/ p3 c: V - PWM.LedpwmVal_Dir=1;//切换为PWM值递增状态; J! I- r1 f+ ?- ]% Y
-
; F& c6 ^# ~$ `; ?* a" m8 R - // TIM4->CCR4 = PWM.LedpwmVal;
7 k( o4 m4 Y. }) t& N - __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,PWM.LedpwmVal);//设置占空比参数/ H' ?0 R1 S4 Y: y) J s
- }
复制代码 4 o# L+ H G7 Y) Y1 i# b. r4 t/ d
* l: G$ F1 T5 Y2 I
呼吸灯波形实际是怎么样的呢,接下来通过逻辑分析仪来看看波形 - { q/ @4 T( U5 I |% O3 z: P+ U
为了效果明显,我们选择10ms增加10的比例来看看波形: 递增效果: 1 ^" l; Z! k, P5 _
4 a& B) x& j8 B8 I, N$ m+ T
递减效果: 5 p7 S% A% n' ?! b9 i" e5 ~
, k) S4 g- i) _4 d转载自:Embedded小飞哥
5 t- O& u' w" }0 c2 `9 D, x如有侵权请联系删除0 @8 j+ {2 u6 u
5 y) ]* V% Q. U- ?) J/ M
( U; ~8 x7 l; p |