|
摘要 本章还是点灯,但是小飞哥带大家换一种点灯方式,利用PWM功能实现“呼吸灯”,什么是呼吸灯?顾名思义,像人呼吸一样的灯...简而言之就是,吸气...呼气...实现灯光渐亮渐灭的效果。 / x2 d# Q/ ?6 k1 ]* K I- F5 Q
PWM原理介绍脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制,如下图(摘自正点原子手册)
1 w) a# d2 _: _& d Y6 {0 i0 w8 A
! [4 `' t) H0 ^2 k0 O. u
2 F/ @% e: p _3 x2 w) H" ePWM 原理示意图
* _' D& K- g" t5 w4 }
6 M' | u) f) m. c* R上图就是一个简单的PWM 原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当 CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变 ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。
; q B6 A+ o4 C3 O) Z. S4 Z( h
STM32的定时器几乎都能够产生PWM波,高级定时器 TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,如下图定时器4,可以产生4路PWM波。本文,咱们只使用一路PWM,学懂原理之后,随便怎么搞~ 9 H- _9 s3 I! @1 R6 U* v, d5 q6 W7 \
6 p; }! y$ V# U4 ]/ }- ~
cubemx配置PWM本次使用的是TIM4的通道4来产生PWM波,也即是PB9。硬件连接比较简单,LED正极连接PB9,负极连接GND即可。 关于时钟等配置,就不多做介绍了 选择TIM4->勾选inter clock->通道4->PWM output CH4->PB9
1 g: y8 T3 I: ?. e8 [
& g* k4 O1 p- {) l" d5 i: S
定时器参数配置,主要分为两部分,一部分是定时器的基本配置,分频系数、周期,这个配置不是固定的,把握频率的计算方式即可: fclk= (Fcore/(Prescaler+1)/(Period+1)
0 F! {9 m# N" U6 \4 p( a+ I
按照我的配置,计算有得到: fclk = 72000000/500/72=2KHZ 这个频率是直接影响到LED灯光的闪烁频率的,如果设置的太低了,会有明显的闪烁感。
3 c- `4 u, ^0 l0 d6 o8 G
' i* J5 X; [0 V. M/ S x- j
PB9是被复用的 1 O+ U( q* ?: E' f. F3 r
6 m1 X Y1 U5 c" C+ R3 V! x
配置是比较简单的,直接生成代码即可
1 e, b& S- n0 M8 i PWM代码解析上面知道,PB9是被复用为PWM通道功能的,代码如下,自从有了cubemx,代码初始化似乎变得简单了呢... - void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle): A. z& J, q0 @: J3 K" ~8 I
- {
; q; k/ l+ ?# ~+ i" r - & ^5 v8 C- b) t% Y+ j
- GPIO_InitTypeDef GPIO_InitStruct = {0};2 h0 n8 q/ K' ^9 \
- if(timHandle->Instance==TIM4)0 L$ b! }& k2 K3 o5 |
- {
) Y, S S) l4 D7 y - /* USER CODE BEGIN TIM4_MspPostInit 0 */' P% u; F6 @7 @/ o9 W V: U$ c" d
- 1 A) D5 T; V0 [0 o- }% [9 B
- /* USER CODE END TIM4_MspPostInit 0 */% M% }- {" K1 ~, g
- 5 k: S6 j; Y" V
- __HAL_RCC_GPIOB_CLK_ENABLE();. x: K9 G8 u1 F) P1 {) g$ \$ ^
- /**TIM4 GPIO Configuration
% F3 ?6 D9 z3 E/ C+ ?3 I& N3 q - PB9 ------> TIM4_CH48 C1 }0 o5 x8 S4 c6 w) y* c' l
- */0 [3 H9 B, ^7 {
- GPIO_InitStruct.Pin = GPIO_PIN_9;( y2 ?/ Y0 f+ c
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;# U. Y5 E( x2 ~
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;4 s" h" O+ N6 B. V8 I. W
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/ h' E2 T: L) V& G5 |; p: B: X
" D4 |4 a0 ^0 Q. a- /* USER CODE BEGIN TIM4_MspPostInit 1 */$ s& E5 X8 W& V! ^: E+ b
- 3 T% R4 L, l$ w$ h: N2 @7 S& L4 |
- /* USER CODE END TIM4_MspPostInit 1 */( H2 O/ R. I: K: G3 X9 W+ K1 h! d
- }
' u! d2 V( [! z" j6 T
a& D: k8 g( J# O! k" G# k& w2 h- }
复制代码
* s. Z" x; q& X+ f上面我们对定时器及PWM参数的配置代码如下,其中有一个参数.pulse,这个参数是决定PWM的占空比的,计算如下: Pulse = .pulse/period 5 c- q, E. l* K7 M% R
计算得出的就是我们波形,高电平(由于是配置的模式1)所占整个周期的比例,占得比例越高,相应的电压会越高,对应的LED灯会越亮
( v# R) c: X0 h/ M* H3 i0 t
关于定时器的参数是被封装在一个结构体里面的: - /**
) k, ~/ H7 M7 O - * @brief TIM Time base Configuration Structure definition
" q, D; B( l( v$ J2 E1 C4 f - */& t( g8 d+ \! {& B/ B! q6 [
- typedef struct: c- W% L2 R! b9 d, c) Z! k
- {" ~4 t( l! X5 ?9 F% K; Q
- uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.
$ M8 ~: j: {( K% b/ ?; v3 b - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */- U. y4 W) s' Q0 T* W# G$ a$ ]
2 \; |1 @0 H8 A3 f+ y$ k; g- uint32_t CounterMode; /*!< Specifies the counter mode.! e, L: F7 [* d5 _
- This parameter can be a value of @ref TIM_Counter_Mode */, i5 M; V2 p0 @& K a# }- A( O
! v3 r5 G% i/ L- D5 G- uint32_t Period; /*!< Specifies the period value to be loaded into the active
5 [" D: I* k+ h9 i; g- o - Auto-Reload Register at the next update event.2 ` f% y& S+ }% @
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
5 K! a9 e, O# D; X: {5 B3 ?0 J
) H9 t3 f1 g8 b- @5 s" w- uint32_t ClockDivision; /*!< Specifies the clock division.
" N: h& {) Y3 L - This parameter can be a value of @ref TIM_ClockDivision */5 \0 [1 b$ u% S6 |
- # u/ v- M3 E4 |4 ]4 N8 B2 C3 F
- uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter
- h3 y, d' c% u* z - reaches zero, an update event is generated and counting restarts- g/ E8 X" i$ \) j$ N' K
- from the RCR value (N).
2 A5 _* }; W* C3 ^$ e: t" \ - This means in PWM mode that (N+1) corresponds to:
! L4 l) m/ ]+ J* Y+ H% M9 `. T" @! N - - the number of PWM periods in edge-aligned mode2 N$ `. O+ P6 C. f! H; m6 j
- - the number of half PWM period in center-aligned mode
9 ?' ~6 n3 i, x4 J* H5 t' G- |- Z - GP timers: this parameter must be a number between Min_Data = 0x00 and
, X! j" O; I% S8 \" C* Z - Max_Data = 0xFF.
5 @. m9 R' e$ ^7 ]8 Q3 M - Advanced timers: this parameter must be a number between Min_Data = 0x0000 and
0 f: G; F8 e# D1 C - Max_Data = 0xFFFF. */
' x% G7 v/ x# [% g; x" |9 m5 V - 8 j6 @/ f) n n1 }
- uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.
7 I5 }0 n; N' R5 p - This parameter can be a value of @ref TIM_AutoReloadPreload */
- x. z3 M4 G3 r$ [ - } TIM_Base_InitTypeDef;
复制代码- /**& L; H$ F0 ?! }
- * @brief TIM One Pulse Mode Configuration Structure definition
' C9 p2 ^5 X3 _( D6 J' Q - */& p' G5 I2 `2 V3 ?) t& o
- typedef struct
( {! [5 V+ Q" w5 t - {
8 n# S' O& k7 ~: }. _+ u% ` - uint32_t OCMode; /*!< Specifies the TIM mode.
/ M. [: X- }. ^6 C9 x7 K, t( n - This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */3 |: Q# }+ ^3 p! C
1 M- Z, o9 U; Q$ F5 w- uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
/ e9 u- O6 P& ^4 ^+ x - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
]; A2 A- M5 X - $ g7 p( S* ^9 p
- uint32_t OCPolarity; /*!< Specifies the output polarity.5 }; |, V& V+ r: O- {
- This parameter can be a value of @ref TIM_Output_Compare_Polarity */
6 ^+ o) h7 C6 G& H6 q" E
0 U9 p/ I, M+ R- uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.. y- n( n0 W' x% s A* Y- n" \8 {9 }* q
- This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
' ~- l: G5 y" \) f; u9 {1 L% y - @note This parameter is valid only for timer instances supporting break feature. */
& v0 h7 i* w% K# e - % [' T7 T% d/ i6 n. [8 o% W$ }' E1 k
- uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
) m3 j- E b# f$ B/ m a) T - This parameter can be a value of @ref TIM_Output_Compare_Idle_State. W$ q" R# i7 a: A. {
- @note This parameter is valid only for timer instances supporting break feature. */
, Z; a, m1 ~$ R9 N4 ~, l, u
# I6 H- G( l! ?; |0 u4 W& j- uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state. z4 I# J- l+ v2 {+ O: D
- This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State% y# [7 E8 ^7 K
- @note This parameter is valid only for timer instances supporting break feature. */1 X. m- W* b% _. ]/ V% M5 U" v
- 8 i: p. M3 h8 L* }
- uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.* a% @2 `, k6 `8 q/ {( n: `
- This parameter can be a value of @ref TIM_Input_Capture_Polarity */
/ x4 C: L) ?) V: l: Z
; X' Z0 A. N1 e. q9 K7 f* r; ?- uint32_t ICSelection; /*!< Specifies the input.
& P1 D. J" ~# n: c& W - This parameter can be a value of @ref TIM_Input_Capture_Selection */4 ^: S: W7 L. C* y- W# M- z
, h3 T7 [3 z1 [8 K/ I9 n0 `- uint32_t ICFilter; /*!< Specifies the input capture filter.8 ^8 w6 s/ s- y1 q% W
- This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */7 o: x* Q1 x/ A' v7 P3 f: A
- } TIM_OnePulse_InitTypeDef;
复制代码 7 y% \7 o9 f( T! e/ \. o
同样的,PWM的配置也是被封装在一个结构体里面,所以说,用了cubemx是方便了,其实对初学者学习是不太好的,知道了怎么用功能,但是背后的代码结构可能了解的会很少... / _0 p4 R5 ^3 G8 w
. h6 b0 A7 c: z( ~$ z7 Y
 关于PWM的函数还是非常多的,此次功能比较简单,仅仅用到了红框中的几个函数,主要看看start函数,其他的初始化过程中自动调用了,不用关心。 9 A4 X' G7 x9 Q7 k Q- J8 {8 S
/ ^$ F; a; N) P) J" T7 W1 ?
start函数需要传两个参数,一个是定时器,另一个是对应的PWM通道号,本次 利用到的是TIM4,通道4 1 Z4 r( u/ A5 [$ p& _% b* i
0 M+ [1 E' P5 R( N& `
启动定时器之后,我们需要更改的参数是PWM的占空间比,HAL库以宏定义的方式给了我们定义,看了这个代码之后,你有没有一种感觉,每天满世界找优秀的代码,找优美的宏定义写法,这不就挺好的...所以,库函数本身就是一个宝藏学习资料,大家不要忽略了 8 \% Y( S4 V; B; Y b
7 B$ `7 Q0 ]. W( h& ]- W- B
代码编写看了上面的函数介绍之后,其实写起来就很简单了,结构体能够让代码变得整洁一些,先来定义一个呼吸灯相关的结构体: - typedef struct{
& j) Q& c' Z+ t/ ? - uint16_t LedpwmVal;//占空比调整参数' v# K& ~7 n7 D M
- uint8_t LedpwmVal_Dir:1;//调整方向,1-递增,0递减4 I: M' v8 N b7 s
- }peripheral;
复制代码. X2 F6 X {1 u+ h
初始化结构体成员为0,1,启动PWM波,接下来编写呼吸灯效果代码,代码也是非常的简单,延时是不能省略的,否则效果是眼睛分辨不出来的...代码也是非常的简单
n- _: K3 X, _0 n
7 p5 ]/ f* a) @: V$ [+ n% L* [7 g8 I5 \/ N! t6 ~- ^+ f
1 a* K5 u5 v+ E+ X9 w1 I; b" k7 }9 u- while (1)8 Q8 t) B2 L) K* ~+ u
- {! i7 J' w" ~$ a& `9 `) g
- /* USER CODE END WHILE */
5 n% J1 ]( e: Q/ |2 x) z
/ e7 M: b0 V$ s- /* USER CODE BEGIN 3 */
1 F' e; T, N' A: y - HAL_Delay(10);//加延时,否则变化不明显,看不到效果2 i$ j! [' p; H H) ?7 m
- # E( B, _# f. G% ^9 T
- if(PWM.LedpwmVal_Dir); d1 i7 K8 K$ C
- PWM.LedpwmVal++;1 F8 _7 e4 q6 G O( u! q& @
- else
+ M/ y) ]: X' I8 ? - PWM.LedpwmVal--; r4 s% ~1 \" l* K% p
-
+ p% [$ n- I% {0 ` - if(PWM.LedpwmVal>breath_UP)
8 }: Y+ R" b6 `. ~, y; s* j$ U - PWM.LedpwmVal_Dir=Breath_DOWN;//切换为PWM值递减状态; J* y1 K) |9 l
- 9 g4 k9 s/ o& g$ u' g3 s4 g% t
- if(PWM.LedpwmVal==Breath_DOWN)' i8 w$ C5 I1 U# e
- PWM.LedpwmVal_Dir=1;//切换为PWM值递增状态9 f; Z5 n c m4 o, |( j
-
; R" B+ I9 Y" @- S - // TIM4->CCR4 = PWM.LedpwmVal;) y p* q1 [6 B n7 S/ o
- __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,PWM.LedpwmVal);//设置占空比参数
: b$ e9 H" Y3 ?) p# h6 M - }
复制代码 2 N8 ~* `' q! ]0 ]' a
/ Z% b4 ]5 e B8 W: @3 A
呼吸灯波形实际是怎么样的呢,接下来通过逻辑分析仪来看看波形 # f2 i8 N4 u9 I1 h) j3 O
为了效果明显,我们选择10ms增加10的比例来看看波形: 递增效果:
& ]) i' n" X! W3 F7 _4 [$ _
( S; S8 {; c" z/ c* t
递减效果: . d: \- x- Q* O/ r2 H6 r. {( ^
3 ^9 R4 E+ W% g- R4 ~6 w: R8 Y
转载自:Embedded小飞哥2 p, O9 i! a2 [0 H. s* k
如有侵权请联系删除
! s+ I0 _# A- E3 [; j7 E6 ] q" Q$ k; |! Y4 K
% l3 ~9 \, P' x |