|
摘要 本章还是点灯,但是小飞哥带大家换一种点灯方式,利用PWM功能实现“呼吸灯”,什么是呼吸灯?顾名思义,像人呼吸一样的灯...简而言之就是,吸气...呼气...实现灯光渐亮渐灭的效果。
Z! {' N/ v. O& w PWM原理介绍脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制,如下图(摘自正点原子手册) ) l* C8 o% G) n8 S7 i
9 A) l: i- r' T) N- |" c+ g2 y3 e
! Q, y, {) I7 o* S" _8 G7 o5 i
PWM 原理示意图
: O* v. M: p5 E/ ~8 J. ]; o& p4 |, E" G# J5 Q
上图就是一个简单的PWM 原理示意图。图中,我们假定定时器工作在向上计数PWM模式,且当 CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变 ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。 2 y5 ` z8 R/ A" Q9 E# T9 N. s( m
STM32的定时器几乎都能够产生PWM波,高级定时器 TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,如下图定时器4,可以产生4路PWM波。本文,咱们只使用一路PWM,学懂原理之后,随便怎么搞~
( P& h7 }, H* `- ?4 [! k4 h
0 g: D' L. F) [; F1 r, C/ G; \
cubemx配置PWM本次使用的是TIM4的通道4来产生PWM波,也即是PB9。硬件连接比较简单,LED正极连接PB9,负极连接GND即可。 关于时钟等配置,就不多做介绍了 选择TIM4->勾选inter clock->通道4->PWM output CH4->PB9 2 L1 p) ^1 ^9 h0 |* ^! }5 V1 Q
5 g8 {9 Y! @4 u( A) Z! A; @
定时器参数配置,主要分为两部分,一部分是定时器的基本配置,分频系数、周期,这个配置不是固定的,把握频率的计算方式即可: fclk= (Fcore/(Prescaler+1)/(Period+1)
* V( D2 K/ X( C/ V. K
按照我的配置,计算有得到: fclk = 72000000/500/72=2KHZ 这个频率是直接影响到LED灯光的闪烁频率的,如果设置的太低了,会有明显的闪烁感。 # b5 |: V5 ]1 e/ `! G
$ k7 N& N/ ^9 _ S
PB9是被复用的 3 ~5 L. `$ g/ I! G
: B A; L4 Y/ P
配置是比较简单的,直接生成代码即可 " j$ e$ Z2 i- Y$ e2 Z' Y
PWM代码解析上面知道,PB9是被复用为PWM通道功能的,代码如下,自从有了cubemx,代码初始化似乎变得简单了呢... - void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle); p- q% U3 x s
- {
2 U) X5 O4 z9 ?& k: C
' y0 J; k7 T0 X4 |+ [- c1 r- GPIO_InitTypeDef GPIO_InitStruct = {0};
% Y8 c c$ ?1 g2 v - if(timHandle->Instance==TIM4)6 E" n( v7 T) J8 g9 p. R- j! O
- {
7 d. ^9 t) J$ B2 i3 H9 P - /* USER CODE BEGIN TIM4_MspPostInit 0 */
7 V) E9 R! W8 n7 N: W- q7 I
+ S$ [/ y4 e' G4 K9 R: n1 e- /* USER CODE END TIM4_MspPostInit 0 */
- R' J, u% k; m) k) L5 v - 1 H* w3 ^& o4 M* K k
- __HAL_RCC_GPIOB_CLK_ENABLE();
% o/ h+ L/ v' S* ]9 Q2 D( k - /**TIM4 GPIO Configuration* {$ Y7 @, ^9 U+ G8 R% b
- PB9 ------> TIM4_CH42 b0 G. M# C1 [2 S" X) i, H3 h, W
- */
P. q7 y% H) ~6 E8 \- ~* X - GPIO_InitStruct.Pin = GPIO_PIN_9;/ e/ ^1 y8 f9 _6 P8 j
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;0 y0 B3 _/ h& t- Q* V0 x* V3 s9 w/ f
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;. S, N3 O- W- K# W
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);# e, @; n9 V2 T: P! M
) l& l3 P* G2 h3 m- /* USER CODE BEGIN TIM4_MspPostInit 1 */
. G* a5 t. e( t$ ], @6 h - 7 m8 l/ N2 i! ~1 a2 W5 ^% h4 r
- /* USER CODE END TIM4_MspPostInit 1 */' S, v4 y+ \/ s4 r( v2 F6 r
- }
' o# A4 k% T/ E V - o3 S3 @2 `5 \7 ?$ w
- }
复制代码
9 f Q5 b: F4 K- b4 k3 b* o9 O上面我们对定时器及PWM参数的配置代码如下,其中有一个参数.pulse,这个参数是决定PWM的占空比的,计算如下: Pulse = .pulse/period
% W' m8 c) |: u% H, R
计算得出的就是我们波形,高电平(由于是配置的模式1)所占整个周期的比例,占得比例越高,相应的电压会越高,对应的LED灯会越亮
& I; n- H) M" M+ T# p0 H; o$ A# S9 o
关于定时器的参数是被封装在一个结构体里面的: - /**2 |+ c+ _2 H4 ^& N# A
- * @brief TIM Time base Configuration Structure definition
' \* j }: j( t; Q- J - */
: X3 ]- ?' k% j& b# r2 @/ e - typedef struct* R9 y8 P, \5 G8 Q! `7 ]( Z! `
- {6 x4 K/ z$ O5 ^! k" i6 d
- uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock." @4 c3 ` Y2 }* O, C- V
- This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */+ H# d+ B3 |6 R) K8 q3 C
7 H7 }! u4 \% j6 b9 r3 U- uint32_t CounterMode; /*!< Specifies the counter mode.
@+ i+ Q" a! A: ~+ L - This parameter can be a value of @ref TIM_Counter_Mode */8 ^5 i* `, d1 C+ j3 U
- 5 \& X) u9 H7 J. n* m/ ]0 e1 K
- uint32_t Period; /*!< Specifies the period value to be loaded into the active
s/ ]/ X$ f! t6 i - Auto-Reload Register at the next update event.
* p; S# g5 Z" U4 j6 I2 _ - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
4 ?" {( P& S' u6 _- q' s4 q - - R( h6 O3 D4 Z3 _" o" b
- uint32_t ClockDivision; /*!< Specifies the clock division.
3 d) O# p) D' S4 }- n8 q - This parameter can be a value of @ref TIM_ClockDivision */
1 h+ }' e& q5 G; q3 f
1 S# a5 G, Q4 w- uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter( g$ C& K, L& y
- reaches zero, an update event is generated and counting restarts0 s- t7 x# e6 `4 ~8 G# k
- from the RCR value (N).
* O7 K. _7 e8 ^7 t6 C, H' q - This means in PWM mode that (N+1) corresponds to:! @" h5 l' p7 S- i" k
- - the number of PWM periods in edge-aligned mode
9 R7 H( E& ]3 H- L$ H - - the number of half PWM period in center-aligned mode8 {+ i( _2 D1 ]( i0 ?7 b6 n$ Z/ Q
- GP timers: this parameter must be a number between Min_Data = 0x00 and1 j2 I. N. _/ Y+ \
- Max_Data = 0xFF.$ ^* V6 e! o3 u
- Advanced timers: this parameter must be a number between Min_Data = 0x0000 and
$ G0 D; C, u4 ~" C - Max_Data = 0xFFFF. */" y- {: [' W) `( J" W/ x& x
# ^! G# l' N0 Y: r+ u- uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.
H a! p" e4 u2 C- y - This parameter can be a value of @ref TIM_AutoReloadPreload */1 B4 ?# W2 F9 Z" m, {, M
- } TIM_Base_InitTypeDef;
复制代码- /** S$ A" Z+ s% P/ N' N& |! z9 n3 j+ P7 d9 d
- * @brief TIM One Pulse Mode Configuration Structure definition
4 D6 e) b Y( X. X! _; L8 j" | - */
. j) Y. Q' e/ R: s& ]9 w- N - typedef struct. J9 X2 h6 ~/ s! ^* s
- {
$ R5 y6 w2 ?4 N( s% q5 H - uint32_t OCMode; /*!< Specifies the TIM mode.
2 `* V# y8 j1 h$ B$ } - This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
, u% d& K6 d. }9 m+ ]8 o6 b
2 r4 M r ^# O7 M# z4 T. F- uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
6 Z7 y I1 X$ H. ] - This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */) z& W, B( P( V, O. M; D
- & S1 y9 w( }+ v, T- w
- uint32_t OCPolarity; /*!< Specifies the output polarity.
& I6 f7 `% A; ~/ V! H, B. x - This parameter can be a value of @ref TIM_Output_Compare_Polarity */# _" z! c4 V! s( T4 y
% s" s3 d- b8 f E) s: h- uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.* [4 w. @$ t1 V5 i, |0 @, U. ^
- This parameter can be a value of @ref TIM_Output_Compare_N_Polarity J/ }( r: f- B5 |! G
- @note This parameter is valid only for timer instances supporting break feature. */2 i5 {4 n% l+ [6 o) V6 t
- * k8 j5 u/ t8 N5 W
- uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
6 P. @- N; ~, Q - This parameter can be a value of @ref TIM_Output_Compare_Idle_State I: ^ [. M# Y
- @note This parameter is valid only for timer instances supporting break feature. */# N4 z$ H; O! l' h' N. W- ?
1 \ p5 S3 V7 W" [' x2 {* R- uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.* H& A/ v R1 ?/ O
- This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
8 `8 N2 B: I; `/ p- ^ H - @note This parameter is valid only for timer instances supporting break feature. */
! i2 E. p8 ^5 A - * N4 b8 W8 Q/ q, Z
- uint32_t ICPolarity; /*!< Specifies the active edge of the input signal.
! l8 r6 p- x, f9 U - This parameter can be a value of @ref TIM_Input_Capture_Polarity */+ @+ D0 w* X5 V \9 Z$ G" C
L5 O' Z" i5 q \& v" p- uint32_t ICSelection; /*!< Specifies the input.
, w- t* f7 i w# P - This parameter can be a value of @ref TIM_Input_Capture_Selection */
0 R) ~* G1 E; U; n+ T - + M! j; I7 u) G4 C0 k
- uint32_t ICFilter; /*!< Specifies the input capture filter.
$ t2 x7 e' u0 T6 i4 c& n: I+ T - This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */4 h1 O, I. `, K; A; j0 i- C
- } TIM_OnePulse_InitTypeDef;
复制代码 8 v( Z* V0 i: c9 R
同样的,PWM的配置也是被封装在一个结构体里面,所以说,用了cubemx是方便了,其实对初学者学习是不太好的,知道了怎么用功能,但是背后的代码结构可能了解的会很少... & C$ }- i8 B" _/ }5 H2 x
+ N' @& y" _ h
 关于PWM的函数还是非常多的,此次功能比较简单,仅仅用到了红框中的几个函数,主要看看start函数,其他的初始化过程中自动调用了,不用关心。 3 p- ?. [5 z- E1 }# k
* b o+ n" }7 R4 Q
start函数需要传两个参数,一个是定时器,另一个是对应的PWM通道号,本次 利用到的是TIM4,通道4 % |+ Z! P$ x# f! K$ @* Y( x1 [) {4 J
$ f4 Y6 ^& O" R1 r6 y+ f1 }% ~
启动定时器之后,我们需要更改的参数是PWM的占空间比,HAL库以宏定义的方式给了我们定义,看了这个代码之后,你有没有一种感觉,每天满世界找优秀的代码,找优美的宏定义写法,这不就挺好的...所以,库函数本身就是一个宝藏学习资料,大家不要忽略了 % F1 M: g0 d6 }3 i6 q3 @$ E8 ?7 U
' B( q) J5 {# n. C
代码编写看了上面的函数介绍之后,其实写起来就很简单了,结构体能够让代码变得整洁一些,先来定义一个呼吸灯相关的结构体: - typedef struct{+ \; J1 P# ?) `+ j0 H3 X
- uint16_t LedpwmVal;//占空比调整参数8 `2 z$ M% w3 m+ L
- uint8_t LedpwmVal_Dir:1;//调整方向,1-递增,0递减
2 F9 K4 r5 a2 v5 Q+ f" s - }peripheral;
复制代码$ S" y+ r& k4 J" N0 Q- U8 u
初始化结构体成员为0,1,启动PWM波,接下来编写呼吸灯效果代码,代码也是非常的简单,延时是不能省略的,否则效果是眼睛分辨不出来的...代码也是非常的简单 7 {' w8 ~+ K2 c+ z2 O; U5 I" L
0 A# x0 B# T. V- s5 Y& p. W) ]0 [
4 m, E* M2 M' t* U" E+ g1 g6 D9 J+ X9 t7 F3 Q, A9 C6 q
- while (1)
H& u" n# k$ U' ~+ A - {
8 J3 E4 Q2 Q" L/ Z/ h) ]. E5 ^ - /* USER CODE END WHILE */
5 l0 H h3 d0 u+ Q8 s
, T9 J$ P0 d8 b- /* USER CODE BEGIN 3 */
6 q8 W- G) l+ `! s - HAL_Delay(10);//加延时,否则变化不明显,看不到效果
3 y; [6 y' q) `7 @/ H) F. Z* Q0 g - : E9 ^) H, E6 l
- if(PWM.LedpwmVal_Dir)% v; b/ u2 h6 N
- PWM.LedpwmVal++;9 `0 @$ s. G5 J7 _
- else
* r, J! S, M1 p @ - PWM.LedpwmVal--;
2 |# Q' T& C" i7 U/ h( w+ C - 8 M: q3 a* o" u( o6 e. S
- if(PWM.LedpwmVal>breath_UP)3 A; Q% Y. L1 h, Q, N( o2 [
- PWM.LedpwmVal_Dir=Breath_DOWN;//切换为PWM值递减状态3 G3 |" v1 }" V" ~! x- c# x
- 6 @' }& V% @' @3 O ]
- if(PWM.LedpwmVal==Breath_DOWN)
+ [6 }# Q- ^9 R/ F, n! J - PWM.LedpwmVal_Dir=1;//切换为PWM值递增状态+ J! Z4 W4 C1 ~ A
- 9 h$ K! Z- }' X( x$ O, P- [) [
- // TIM4->CCR4 = PWM.LedpwmVal;
3 c7 x0 \# r* ]6 a0 i, Q5 | - __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,PWM.LedpwmVal);//设置占空比参数* ]; K3 ^$ y9 Z$ c
- }
复制代码 ! r6 A/ }& f% G+ i: h" ?" j# I
$ {9 k4 k/ t; w4 ~* S- H9 y; l
呼吸灯波形实际是怎么样的呢,接下来通过逻辑分析仪来看看波形
: i6 N/ l% W9 D E l% _ q% L
为了效果明显,我们选择10ms增加10的比例来看看波形: 递增效果: ! s1 g' N$ H1 i
( [" X* b k& p6 z4 b! L+ {
递减效果:
8 `: }* I6 c, W3 J; E, _5 `( R
, o6 i5 q& i4 e. u X' E
转载自:Embedded小飞哥% R( g0 ?+ |- O7 I
如有侵权请联系删除
' `) v8 l8 Z4 B! [% c& Q9 W: T; F$ Q* ]
( G8 H* f9 {; I
|