|
摘要 本章还是点灯,但是小飞哥带大家换一种点灯方式,利用PWM功能实现“呼吸灯”,什么是呼吸灯?顾名思义,像人呼吸一样的灯...简而言之就是,吸气...呼气...实现灯光渐亮渐灭的效果。
4 F; P) U- r a, m- r) S1 H PWM原理介绍脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制,如下图(摘自正点原子手册) " l) ^7 u- _( T8 K6 _0 |
" `" ? F2 ]/ j
' U/ k4 k6 j. h9 sPWM 原理示意图 & n" w2 B7 J' W5 l- Z9 a
; M9 h5 X" }7 ^$ g. R/ D' v0 C
上图就是一个简单的PWM 原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当 CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变 ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。
- w& l9 ~6 M+ d- T) |
STM32的定时器几乎都能够产生PWM波,高级定时器 TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,如下图定时器4,可以产生4路PWM波。本文,咱们只使用一路PWM,学懂原理之后,随便怎么搞~
% \( Q) l$ ?9 ?
/ F8 a+ ?' p0 S: ^' @
cubemx配置PWM本次使用的是TIM4的通道4来产生PWM波,也即是PB9。硬件连接比较简单,LED正极连接PB9,负极连接GND即可。 关于时钟等配置,就不多做介绍了 选择TIM4->勾选inter clock->通道4->PWM output CH4->PB9 ' T4 |, _: g% c6 U3 S: ^) b8 d6 H
7 N2 W& ~3 {$ i
定时器参数配置,主要分为两部分,一部分是定时器的基本配置,分频系数、周期,这个配置不是固定的,把握频率的计算方式即可: fclk= (Fcore/(Prescaler+1)/(Period+1)
, }0 x7 [$ J7 Q7 ^
按照我的配置,计算有得到: fclk = 72000000/500/72=2KHZ 这个频率是直接影响到LED灯光的闪烁频率的,如果设置的太低了,会有明显的闪烁感。
$ q; S: _/ W' E- I1 {5 C: [
7 g9 U$ X ?! V7 X
PB9是被复用的
- ]1 K. q7 M. \& c
; H: A, d# y5 x, L3 b6 X
配置是比较简单的,直接生成代码即可
- |4 j$ z7 P) I) Z% p. y PWM代码解析上面知道,PB9是被复用为PWM通道功能的,代码如下,自从有了cubemx,代码初始化似乎变得简单了呢... - void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)3 @* K( W! S3 u' O
- {
, ^' e; \% x a' Y0 A) J
) v. v& k s* J- GPIO_InitTypeDef GPIO_InitStruct = {0};
3 E! H: I* C6 A, b; f1 Y; _ - if(timHandle->Instance==TIM4)
' j! i! f+ w! G8 `" ~( u* `9 K - {; d2 G. @4 H. E a. z" n4 u ~
- /* USER CODE BEGIN TIM4_MspPostInit 0 */, c0 G2 i- [ l x' c7 C
- # i9 T E8 S; @5 ~ J! C! H
- /* USER CODE END TIM4_MspPostInit 0 */
" N5 O5 ?& c; Y# ]3 k - 7 K/ i( [3 J5 \/ l; e
- __HAL_RCC_GPIOB_CLK_ENABLE();" B& f1 S- I; S
- /**TIM4 GPIO Configuration/ ]8 t& D _9 x( T: \
- PB9 ------> TIM4_CH4
6 Y0 l3 s1 W8 m+ _1 o! L4 J. }; V - */
/ x+ s* ^) c8 R - GPIO_InitStruct.Pin = GPIO_PIN_9;
2 h! M8 U; R4 Q1 I% g* B/ V - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;1 L( ^9 L1 S) B! @
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;- J# B3 Z; d, ]- Y
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);4 y/ s) t" t% u" N1 X
- ' v% I8 N1 P" c
- /* USER CODE BEGIN TIM4_MspPostInit 1 */
2 \+ e6 ]3 R9 q
- I5 U1 N$ T. m- /* USER CODE END TIM4_MspPostInit 1 */
- U7 m ~3 c! j) I) t5 d# z C - }6 [. w# Q/ {& H
- 8 f8 H3 T9 q0 {7 h9 b) i* g7 H
- }
复制代码 + R) O: z. l% M: I. n
上面我们对定时器及PWM参数的配置代码如下,其中有一个参数.pulse,这个参数是决定PWM的占空比的,计算如下: Pulse = .pulse/period 0 }# B* R2 r/ s) E
计算得出的就是我们波形,高电平(由于是配置的模式1)所占整个周期的比例,占得比例越高,相应的电压会越高,对应的LED灯会越亮
# M. p3 Q" M) G6 ~3 s' Z: z. k
关于定时器的参数是被封装在一个结构体里面的: - /**
' n& T( ^; @3 D& T9 Z3 { - * @brief TIM Time base Configuration Structure definition& @) S: N [4 a& {- `
- */
E( C! e' U6 K4 i* i - typedef struct
; g& ]; ~. I+ P* e - {* T5 g9 T+ h7 X# n
- uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.( T+ h+ h+ T+ b" {6 k
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */- u l! O8 _; P) [! @
& u! i* u3 }- R# B6 Y- uint32_t CounterMode; /*!< Specifies the counter mode.% ^3 E& W5 [# T% N* |9 t
- This parameter can be a value of @ref TIM_Counter_Mode */) W- l8 [ K/ V" Z0 `+ Z
/ \, \% z5 i4 U: x. q5 s3 ?- uint32_t Period; /*!< Specifies the period value to be loaded into the active* `; M& A6 |5 r9 p* Q# A' N
- Auto-Reload Register at the next update event.# k' W7 i) t% m: b% |
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */3 n& { Q2 I, F
- + ~6 Q' t6 o8 Q2 o
- uint32_t ClockDivision; /*!< Specifies the clock division.
* X9 C7 R6 M% V, P - This parameter can be a value of @ref TIM_ClockDivision */' g5 A; j- ]; @' i3 X1 \
- & i" s0 q- I5 m( {: n
- uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter
) X: j2 ^% @% }7 l3 I - reaches zero, an update event is generated and counting restarts( X: h. q" `0 F; d( g* V/ n8 Z
- from the RCR value (N).
9 u: [- I. y+ _/ S' w3 C4 } - This means in PWM mode that (N+1) corresponds to:! k5 p1 l" B; ]4 M! ]" |
- - the number of PWM periods in edge-aligned mode0 A( i0 h) Z' p# e! p
- - the number of half PWM period in center-aligned mode( S! W8 U* e% S2 J
- GP timers: this parameter must be a number between Min_Data = 0x00 and% b9 H5 D2 c- A/ [
- Max_Data = 0xFF.
3 o1 q7 Q+ a. [! @0 s8 X - Advanced timers: this parameter must be a number between Min_Data = 0x0000 and
1 V7 J5 v9 l7 s! F9 ~ - Max_Data = 0xFFFF. */0 U; w/ V( D7 N) ?% a
- 6 }: n2 N0 _1 u6 |9 Y" L
- uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.2 T) g: F) E9 Y- I' ?& p- g
- This parameter can be a value of @ref TIM_AutoReloadPreload */
/ I1 ~2 |0 w6 O+ I9 O" d - } TIM_Base_InitTypeDef;
复制代码- /**- P1 C. H6 h6 f# c7 |
- * @brief TIM One Pulse Mode Configuration Structure definition, n6 Q" V; J$ R2 @
- */; L, z+ W5 J- z3 b9 s* A
- typedef struct; P3 u) M9 g! S0 ?! ? D
- {
9 g, @) K/ v; b0 y/ u4 { - uint32_t OCMode; /*!< Specifies the TIM mode.0 l/ m$ T/ A: ]9 ?9 Y" j& y5 B
- This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
- l( {! x1 {0 i3 r) i! W6 {8 j - g2 d% |: H% Y7 p5 [) a
- uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
, l: O. c+ J: f5 V O9 i - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
9 K* z d3 n5 q+ @7 G: R
' Z( U! b$ i2 V: s. [/ S- uint32_t OCPolarity; /*!< Specifies the output polarity.
) ~9 I- k' f+ X - This parameter can be a value of @ref TIM_Output_Compare_Polarity */8 P% k( }6 P' M
* W8 N' C9 F4 g" t) W- Y- uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.- @# l3 C. N% N. P$ B d
- This parameter can be a value of @ref TIM_Output_Compare_N_Polarity% Q' V; _6 d5 `8 _. c
- @note This parameter is valid only for timer instances supporting break feature. */$ S. x3 g x8 P
- ; s9 {. B/ p3 E8 H) g+ j
- uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
: T- A* G1 ?0 @* q - This parameter can be a value of @ref TIM_Output_Compare_Idle_State
8 b% [7 k& O, W+ z9 s: r8 | - @note This parameter is valid only for timer instances supporting break feature. */
, i1 J N3 U; f& ^
6 ?/ f+ N6 X3 `& }/ A- uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
( P% |" p3 P- L T - This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State* e+ W1 H& H# ?4 k, l& L
- @note This parameter is valid only for timer instances supporting break feature. */2 B( A) y5 a, `7 a8 k
/ n1 T) J4 x+ B3 Y8 q6 a- {; s1 o- uint32_t ICPolarity; /*!< Specifies the active edge of the input signal./ o/ X3 F9 }' P0 t7 f, d
- This parameter can be a value of @ref TIM_Input_Capture_Polarity */9 D2 {' X, d* J2 q4 n/ W8 n' r7 [
- 8 X$ Q% r& n+ d- v
- uint32_t ICSelection; /*!< Specifies the input.
' ]; X/ X* k& Z# q i" w - This parameter can be a value of @ref TIM_Input_Capture_Selection */
3 F# b9 d2 Y, f& P5 i - 9 }( m6 M w8 T! t. [5 E
- uint32_t ICFilter; /*!< Specifies the input capture filter.; m0 {+ Z# ~9 ~
- This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */
9 I- k7 |6 x/ i5 J; c5 b( U2 L - } TIM_OnePulse_InitTypeDef;
复制代码 6 a) y# d- G _1 n1 Y
同样的,PWM的配置也是被封装在一个结构体里面,所以说,用了cubemx是方便了,其实对初学者学习是不太好的,知道了怎么用功能,但是背后的代码结构可能了解的会很少... / L! M1 a$ U# s/ R. ]7 l
, K, Q( q0 k7 w3 f7 L! M
 关于PWM的函数还是非常多的,此次功能比较简单,仅仅用到了红框中的几个函数,主要看看start函数,其他的初始化过程中自动调用了,不用关心。 ; z( g$ m- u5 y: o
1 b9 a/ K1 X# B4 o
start函数需要传两个参数,一个是定时器,另一个是对应的PWM通道号,本次 利用到的是TIM4,通道4
7 B3 D% l7 @, I) Q
+ E, j9 m' I- W
启动定时器之后,我们需要更改的参数是PWM的占空间比,HAL库以宏定义的方式给了我们定义,看了这个代码之后,你有没有一种感觉,每天满世界找优秀的代码,找优美的宏定义写法,这不就挺好的...所以,库函数本身就是一个宝藏学习资料,大家不要忽略了 Y. k- P4 d, w7 E4 a2 ]; ~( {
! i) `6 n5 U: ^* H, a( p
代码编写看了上面的函数介绍之后,其实写起来就很简单了,结构体能够让代码变得整洁一些,先来定义一个呼吸灯相关的结构体: - typedef struct{
' Q" y8 P: b+ E- c - uint16_t LedpwmVal;//占空比调整参数
: ^) V- L: ?2 g# {8 V& U2 [: a - uint8_t LedpwmVal_Dir:1;//调整方向,1-递增,0递减
! m; P% b: V1 `- s6 r - }peripheral;
复制代码- e. v$ H, B! i- ?
初始化结构体成员为0,1,启动PWM波,接下来编写呼吸灯效果代码,代码也是非常的简单,延时是不能省略的,否则效果是眼睛分辨不出来的...代码也是非常的简单 ; W8 y- o7 q9 q
. X; H( }; y2 d6 _% {- t1 J" @1 q' P# I2 v- T
! ]* ], O2 Q' v8 F8 [6 A
- while (1): |1 k1 D' k7 x8 D+ z
- {
# Y H9 v3 _& v$ R$ k - /* USER CODE END WHILE */
8 q$ C0 k5 M/ t) Q - 7 A& C4 e1 c) m I* U* n
- /* USER CODE BEGIN 3 */! e5 N& E& n- {2 n" M9 C% z/ \ `
- HAL_Delay(10);//加延时,否则变化不明显,看不到效果% _- a& {& D) I5 s
-
) L1 a: J$ [0 t5 p$ H4 ~( K! A5 E - if(PWM.LedpwmVal_Dir)
5 w& d8 Q/ M% `6 m - PWM.LedpwmVal++;
" `. v/ {1 @( ~ D - else% ]7 @& w$ V- @# j- N( e. l
- PWM.LedpwmVal--;
% N. }1 `1 H0 J, s# @) h7 }7 t U -
- m; Q/ F! ^9 \: G7 k, @ - if(PWM.LedpwmVal>breath_UP)
7 N1 j+ h l* P$ \ - PWM.LedpwmVal_Dir=Breath_DOWN;//切换为PWM值递减状态7 p9 R1 g5 J( Y" r# u
-
6 \8 Z# H$ x* o4 i - if(PWM.LedpwmVal==Breath_DOWN); {9 p( R* h9 T! q! Q
- PWM.LedpwmVal_Dir=1;//切换为PWM值递增状态 C6 W; }5 c. p9 F' c0 B
-
$ r# K- k+ J/ ~& I - // TIM4->CCR4 = PWM.LedpwmVal;
& K* [/ K: `6 p3 z' y) @5 X - __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,PWM.LedpwmVal);//设置占空比参数
. }+ p9 g: T3 p - }
复制代码 ( n% ^' Z T2 P
; w$ F4 o* }# S9 l% b
呼吸灯波形实际是怎么样的呢,接下来通过逻辑分析仪来看看波形 / ?/ O: e" J j0 @
为了效果明显,我们选择10ms增加10的比例来看看波形: 递增效果: 6 T' z! i0 G) \4 S5 E/ X; }
$ J% T- d9 u1 b# n+ M- E
递减效果: " Q2 |; X8 U& p0 M
0 Y+ E* B/ M6 o$ D9 E. J7 \转载自:Embedded小飞哥$ J* d* u$ Q* o% B
如有侵权请联系删除" z% |1 s! t" t. r) k
6 R: w0 b" M1 P4 F9 @7 R% r
% f, r/ R2 m5 z; D" k8 ? |