在数字电路中时钟是整个电路的心脏,电路的的一举一动都是根据时钟节拍下进行的,随着信息量逐渐提高,对硬件信息处理能力提出了更大的需求,时钟作为数字硬件的关键成员,其性能需要我们关注,尤其在高速电路设计中对模拟转换芯片对时钟性能有很高的需求,因此正确选择时钟是很关键的一步,前提是我们要了解时钟的关键参数咯。在数字电路中最常见的时钟元件有晶振和锁相环、时钟缓冲器等,本节对外部定时器进行重点讲解。
% h& |% Z& a7 n1 V4 m! X, v
! J$ p- ~7 j9 U! S- j H+ ASTM32 定时器专题讲解0 B5 v- @& O$ |# w ~" `
- d/ Y8 I0 U* l8 b8 C1 p: v
SysTick定时器的功能比较单一,主要是供给系统使用的,系统默认设置为1ms触发一次中断。而用户想要使用自己的定时器,STM32提供的用户定时器不但数量多且功能更加强大。不同型号的STM32提供的定时器数量不同。
* R X3 W, o$ f# f
$ e g% U) l$ q" P) B* Y8 i5 NSTM32F4xx 系列微控制器具有多达14个定时器。其中包括2个基本定时器,10 个通用定时器2个高级定时器。其中最大定时器时钟可通过 RCC_DCKCFGR 寄存器配置为 84MHz 或者 168MHz。一般是默认配置。& i2 U* n. A" O" U! y
- R/ ]# J. h# e
9 `0 F- C+ I; i( b# E0 g K9 f( ^) G, [4 I9 @+ |
捕获/比较通道:每一个通道对应着外部一个管脚,接收外部的一些信号或者输出一些信号。
7 s) n6 S' T" o8 b; B d) [! H* i; N+ X, w
基本定时器TIM6和TIM7:和SysTick定时器的功能差不多,基板上就是定时计数以及驱动DAC的功能。
% \+ r$ ?2 \+ c5 l, k* A
2 z- O2 \1 q6 h通用定时器TIM2、5、3、4、9、10、11、12、13、14:功能比基本定时器的功能多,且包含基本定时器的功能,多出来的功能包括输入捕获(通过捕获通道,一个外接的管脚来测量外部信号的频率和脉宽等)、输出比较、PWM输出(也对应一个专门的通道)、使用外部信号控制定时器和定时器串连的同步电路的应用等一些在特殊场合应用的功能。
9 |) ~4 J: g( j# `* I/ ]- r4 t. G+ j; P0 ^+ C
高级定时器TIM1和TIM8:包括了通用定时器的所有功能,在通用定时器的基础上增加了更加特殊更加专业的功能,主要是用于PWM控制一些工业上的电机,还有带一些死区控制、急刹车等专业功能。
6 p8 I$ ]3 w$ z) C: y6 Z$ D4 n" z1 P" J" S$ E3 g
定时器计数模式:
% o+ ?* E+ g' ]* w S% r& c [
; p. P: s5 x u T1 n7 ?; k; t' r! \向上计数模式:计数器从0计数到自动加载值(TIMx_ARR,相当于SysTick的重载数值寄存器),然后重新从0开始计数,达到设定的数值时产生一个计数器溢出事件。5 D* H# S: t; v3 S
; N; N+ Y. ], z; c3 s6 i向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。& W z% }% m; @
# i8 U& i% B0 Y- m- i
中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。该模式简单说明为先进行向上计数模式,当达到数值寄存器的值时,进行中断服务,而计数器的值不会恢复为0重新向上计数,而是保持该值转而采用向下计数模式,减到0时触发中断进行中断服务,再采用向上计数模式,周而复始。: S8 {8 ~$ \& `" ?) J, Y! y
y6 H/ P. h. P1 R
0 Z/ r- Z6 q/ m6 M- J, q定时器的时钟:
- S! y) w7 n! {9 E. x
' k4 g3 Q* p. z
1 A- v7 b' f3 c: o7 h: Q2 a; g% Y
: @1 M) T/ C& R/ x( |# ^% s
" t+ m2 B& V& U" |/ P( C) r9 X
) V/ B; Q1 o% S6 e' ?# w
APB1和APB2总线上分别挂载的外设和定时器的最大时钟频率是不同的。
7 b6 N0 D! G) M5 l: s3 }; @/ z( S4 i# h5 W7 j; z* Z
定时器的时基:
! U0 B0 c- R7 D% u, q) p
7 v6 t" x4 C- Q4 z4 g9 z( Y* I/ y5 ?+ P
, V+ U4 D1 g8 ~& r- F
) y0 E5 _0 T7 R时钟源:定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 对应的APB 预分频器后分频提供
$ b8 o- @3 V5 X) L W! A W) j4 a
$ w p! Y( `6 `" a$ ~计数器时钟:定时器时钟经过 PSC 预分频器之后,即 CK_CNT,用来驱动计数器计数,一般情况下该时钟频率值就是时钟源的频率值,为默认配置
- U0 \$ T6 K' }. t$ e0 z6 z/ |
9 M( P3 s7 ~' \+ {5 M计数器CNT:是一个 16 位/32的计数器8 ?, Y/ A4 w: C+ a
5 B4 r$ l! s/ ^/ o0 Z1 N- Z( F( w; f自动重装载寄存器:这里面装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断# R0 v: l& Q, Z. P/ m; v
, D! t. o0 f/ M3 m( R" _& Y% j计时中断时间:1/(TIMxCLK/(PSC+1)) * (ARR+1) 这里所有的加1是因为从0开始: {3 @3 A( C8 M% i& F
- u6 L% F, ^0 H# \输入捕获与输出比较:
9 B9 r. Q8 X5 _% i# Y. K) I
Y5 m5 s& {4 g& h$ M8 A7 c输入捕获:可以用来捕获外部事件,比如引脚的电平变化(上升沿,下降沿),并记录下变化的时间,通常可以用来测量外部信号的频率或者电平持续的时间。9 I: [* w' q- P2 z$ s6 o- [
+ ~8 l* p; K9 v3 V; o3 K6 K
. |2 i+ ]) N* T0 O% P2 F
! b" }; M. g. F4 t+ K7 h
6 d, ^. W+ ]# s t, ^( J! q3 k输出比较:此项功能是用来控制一个输出波形,当计数器与捕获/比较寄存器的内容相同时,输出比较功能做出相应动作,比如电平的翻转。通常用于生产PWM波形。3 j, }9 F9 c8 }
7 D7 s5 d% C! t& p2 p
9 S {9 u* L# ~2 ^
* M3 U) b7 U. o9 A) j, {$ y! {; D2 [% j% V/ C5 N1 x
STM32定时器定时中断实验:利用基本定时器实现定时1秒中断,并在中断处理函数中打印输出字符% w" B, m: c& _! z: {
; }) Q. m0 ]! ?8 r" T. R使用定时器必须知道定时器时钟源是多少,本实验使用基本定时器TIM6,挂载再APB1总线上,因此时钟源大小为84MHz,设置分频系数为8400-1,则计数器的时钟频率为10KHz,即一个CLK的周期为100us,想要实现1s中断,则装载寄存器的大小为1000-1。
1 p8 T: `+ n: {5 z c
6 x& Z2 e5 n5 r, l0 T第一步先配置时钟
8 ~# u. [' l$ j/ {. j% Q' @8 w9 Y9 W
7 c$ [* m [! W& X+ [
7 r/ [( g$ e) @8 y, l- Q% g9 z. R
* j3 y5 l; f$ l f- w
第二步配置定时器TIM6
! _6 k- R6 ?* W) P0 r; J" `* [7 B0 |5 J$ c8 q
# z0 O' f% q8 q2 T+ l2 v) x
0 ]( _8 i* l0 q- M* }, d第三步配置中断
; n( @, U, O i/ C* z2 c" f3 \9 a
9 M, a( h9 o T( V
3 y! Y# E( W6 t4 l; L# X# T7 P
6 M- T* B6 d6 K8 Z' P% Q* t6 W
$ I [, [$ b+ q" s值得注意的是,STM32有很多片内外设,而一般情况下每一种片内外设的数量不唯一,因此要有启动函数来启动用户想要的启动的目标外设。如本节中定时器数量有很多,需要一定的定时器启动函数区分不同的定时器,然后启动目标定时器,而系统定时器只有一个,再整个工作过程中一直工作,不需要专门的启动函数。HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)可以启动定时器,HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)既可以启动定时器,又可以使能定时器中断。- //mian.c
+ Z0 U% s3 y/ P- L. H& T Q$ z
8 Q. H3 {6 T9 ^- int main(void) {
/ y$ z7 O# n* k8 R - HAL_Init(); + k, _, U K) d+ v4 |9 k
- SystemClock_Config();
3 [7 Y, X$ r( N - MX_GPIO_Init(); & p7 }* i) |0 B# ]
- MX_TIM6_Init();//定时器初始化,配置参数
5 C0 B# h2 u9 a4 Y9 u - MX_USART1_UART_Init();
& D$ m4 q2 j, m8 g - printf("this is tim6 int test\n"); 1 M8 i# t6 f; N5 V5 t% K$ \# [
- HAL_TIM_Base_Start_IT(&htim6);//启动定时器并使能中断 - o, v8 G4 Q( K) `* ], Q& Y
- while (1)
5 D/ M7 h9 v( m( D4 e - { } ; e- ^6 \- h5 d, B0 k( ^
- }
复制代码
4 M: V) r9 Z% e$ y在定时器中断处理函数中有很多不同类型的事件可以触发定时器中断,如输入捕获,输出比较,由于向上计数到达指定数值时,计数器会重新更新数值,所以我们选择数值更新事件。
- X! l. G8 [- W7 t
' \+ M4 s0 M% ] U1 F+ q2 h
) f' T: q( G# I4 Q/ Y2 O! c/ b
1 \: S: h7 w- N" r9 L# X5 ~& a; }6 T) S6 H% K5 k7 L9 [8 p
所以在中断处理中我们只需重写回调函数HAL_TIM_PeriodElapsedCallback(htim);即可
/ `# i6 G: x! N! G1 i7 d4 m8 g9 Q/ v+ N1 E
- //tim.c ! t* ~: o9 J" q# L
- //重写回调函数完成逻辑功能 ' U: B' X9 c4 A. F% W9 [! y# P
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { ; C0 q8 F+ t, u* R5 n. }1 T
- if(htim->Instance == TIM6){ //判断是不是基本定时器TIM6 . C) d5 Q+ Z* x/ k/ B) D
- printf("tim6 int\n"); 2 A/ C, Y! p- `; u' q8 E) a2 l8 y2 D
- } 7 {* ]) y7 B2 T8 x5 Z1 ]
- }
复制代码 8 S) c1 p* T+ g7 P; V) Y4 y
高级定时器功能分析
. K! p) m7 k: l0 `! C3 r: G3 Q/ x
* A( j6 R U0 o$ |6 O高级定时器和通用定时器在基本定时器的基础上引入了外部引脚,可以输入捕获和输出比较功能。高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面。" h3 B) w, A; H
. M) N- j1 R5 r% K C+ X
5 o; p: p6 k% r3 T9 X4 b4 \9 }' o
0 u9 T6 ~) a# @1 Z1 R4 [
" `7 z& S- c6 d6 ~& t& y
# O# J: ]/ c: _' E8 W1 G4 Y
功能分析如下:
! E4 {, n8 q& p+ m1 s9 u( I% J6 x6 v" _2 L) C3 _; s
①时钟源:对于高级或者通用定时器来说,时钟源不仅仅只有来自APB总线的内部时钟,其时钟源还可以来自外部,通过定时器连接外部的管脚引入进来,一般情况下我们都是使用内部时钟。$ ~& F$ A1 a6 W! P; K: Z
: c p# j. f0 D" K! i% c; t$ G Q# t0 B
2 Q* g* ^$ r; b' j$ ?9 ^/ X+ V& e
) ]4 Z- e; H/ Z7 g
②控制器:包括触发控制器、从模式控制器以及编码器接口。触发控制器用来针对片内外设输出触发信号,比如为其它定时器提供时钟和触发 DAC/ADC 转换。编码器接口专门针对编码器计数而设计。从模式控制器可以控制计数器复位、启动、递增/递减、计数。$ q. S' W3 ?$ `' ^7 H
' S% S& X" \$ Z
8 B2 h' ] \& g- _0 k& m [
( a: p4 s8 X8 z$ P1 H4 {3 b* o5 D: N0 a3 m7 U: @4 d0 _' x
③时基单元:定时计数的核心,预分频器 PSC:对输入时钟进行分频得到计数器的驱动时钟CK_CNT,计数器 CNT: 在外部时钟的驱动下实时的进行计数,自动重载寄存器 ARR:用来存放与计数器,CNT 比较的值,如果两个值相等就递减重复计数器。
* u/ u6 o% g0 j6 B6 r. W
* i: w+ v8 t0 s! q y9 i0 X" P重复计数器 RCR:高级定时器特有,在定时器发生上溢或下溢事件时递减重复计数器的值,只有当重复计数器为0 时才会生成更新事件。相当于有了一个缓冲,溢出时不会直接触发中断,而是时重复计数器减一,也就是可以延长计数时间,想要延长几倍,重复计数器里的数值就是几。/ x% `" F" j: ~. \5 g# U
( o* P v" l3 u& {
+ P' m2 U5 q) r
5 d8 ~; ~( n+ w④输入捕获:输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用来测量输入信号的脉宽和频率,一个定时器可能有很多通道,每个通道对应一个管脚,但是一个定时器不会拥有超过4个通道。% U. c9 {1 B; ~8 X
1 M1 }# r3 }% Q% u* \外部通过管脚将信号输入后,信号会经过一个输入滤波器和边沿检测器。外部输入的信号杂乱,使用滤波器进行滤波调整,过滤出用户想要的频率的信号;边沿检测器用来检测输入波形的频率,以上升沿为例,第一次检测到上升沿,将计数器的值CNT放入到捕获寄存器CCR(捕获寄存器就是用来记录数值的),捕获到下一次上升沿后再把计数器的值CNT放入到捕获寄存器CCR,两次数值相减就知道中间经历了多少个上升沿,再根据中间经过的时间就可以算出频率。6 `* k0 b! J; ~3 Q @
- p R7 @* e/ p% e0 u3 h这里的预分频器的作用是规定采集多少次上升沿才记录一次。
, s9 ^$ M- G1 v8 d1 H1 `1 s8 H$ B2 F$ p* S8 {6 E8 i1 C. S) H
7 `: B, L% N- d- D; b! G
; f; U$ }9 ]* I/ k⑤公共部分:公共部分就是CCR(capture compare),捕获/比较寄存器,用来记录特定数值。! ~3 \% a5 D" Z9 R
- x+ G/ J7 t1 L
; w& |5 r y" L
5 i7 }) U* J7 Z3 ]* e
⑥输出比较:输出一些固定频率,且电平的宽度可以调节波形信号。输出比较就是通过定时器的计数比较控制外部引脚对外输出高低电平,比较输出有很多种模式,其中PWM模式是输出比较中使用的最多的模式。
) N( R/ S& E- @5 Q5 X
. v' v% g4 R( T+ W, E# E4 \
1 S. e. N- F- J2 p
) q- ]' A' {( G, y# R
' L0 y; k" W' |3 Z0 ]; `
' e- i7 T; F# _5 B输出比较的基本原理为:在CCR中记录一个值,专门用来与ARR(装载寄存器)中的值进行比较比较的,因此可以这样设定,当CNT(计数器中的值,不断变化的)小于CCR中的值时输出低电平,当CNT的值大于CCR中的值时输出高电平。不会大于ARR中的值,因为ARR的值是用户设定的最大值,超过就会溢出触发中断,CNT变回0重新计数。因此通过设置CCR中的值就可以调节高/低电平的宽度,这就是所谓的PWM波的原理。
$ U& J7 Q. e& j, R! P( g
, }3 [* r- v! M6 f互补输出和死区控制1 E* {2 p5 {2 X9 R4 t$ p
4 c7 S" a# K0 C7 T8 E高级控制定时器比通用定时器增加了可编程死区互补输出功能,常应用在工业电机控制方面,下面红框中的寄存器就是用来实现此功能的。5 r4 |8 ], L, x. Q* ^, b! O9 b
: M1 N- p! P: m0 r: R# `& v4 U# n
/ B+ ? O8 S( B+ s2 R% ?
5 [1 {+ }0 W2 O
H桥电机如下图所示,从上图中可以看到每个定时器通过DTG寄存器和输出控制单元连接了CHx和CHxN两根管脚,正是用来与H桥电机的OCx和OCxN相连. K* ]# s+ B4 ]" e; o
$ ], R7 d! H3 U4 x6 S! y, i+ k9 E
+ w+ O2 N; N% `: l4 t. K( j
' ?, @' R9 E$ r
5 n: Z, s& {0 f) ~互补输出:两根管脚一个输出高电平,另一个必须为低电平,在H桥电机中,OCx为高电平,OCxN必须为低电平,此时电机逆时针逆时针旋转,反之电机顺时针旋转。倘若两根管脚同时为高电平,则会导致电机损毁。如图,互补输出在一根管脚变为高电平的瞬间要求另一跟管脚变为低电平,但是这属于理想状态,实际上两根管脚的电平变化一定会有延时,就会出现两根管脚同时为高电平的情况,于是引入死区控制。
+ m9 c9 R& O; P, _
3 i% |5 L. h- T, v& @8 l
, g+ U: u% G. O- p
6 D- |! w* {5 v死区控制是当一根管脚由高电平变为低电平时,另一管脚不会立即变为高电平,延时一小段时间后再变为高电平,延时的这一小段时间就称之为死区,插入死去就避免了电机烧毁的问题。DTG寄存器是配置死区发生器 (Dead-time generator setup),保存的值为死区持续的时间。
* `- k1 X. {; f; e( {! [
! c! f" Q. F& F7 b6 }' c$ o- [, ]9 }# _& O! d' m8 ^
" f% @- e& M1 z" j9 @+ h3 c: J
I8 }# n, ]+ `$ B2 BSTM32定时器输入捕获实验! E6 g: J( O* {' Q8 F% d& J
; a6 [& f* A1 W& f, S' [$ ^ s$ [
输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用来测量输入信号的脉宽和频率。加入我们设置捕获下降沿,可以使能中断,捕获到下降沿后产生中断,在中断中执行用户程序。捕获到下降沿后将计数器的值保存到捕获寄存器中,当出现下一次下降沿后同样会触发中断,也会把当前计数器的值保存到捕获寄存器中。通过保存到捕获寄存器中两次数值的差值计信号频率或者电平持续的时间。% H1 x/ u. D3 o2 U( q
1 E' Y! Z% _0 `9 v+ y
4 C1 E! o' p7 T# k2 H0 }) }9 D
$ y& w8 ?" J2 L3 n) n如果想要捕获脉宽,捕获上升沿后应该立即设置为捕获下降沿。value2-value1就是高电平持续的时间,也就是脉宽。! U1 I1 D1 Y7 N8 J( V V8 A, p& c2 E
+ P! ]5 A& @, `3 r
实验:利用定时器2的输入捕获功能测量按下KEY6键后低电平持续的时间
, y+ M3 z S1 v
9 I- f0 z1 e/ {1 ` h/ M说明:根据原理图可得,按键KEY6的管脚为PA0,正常情况下管脚电平为高电平,按下后管脚电平为低电平。所以应当先设置捕获下降沿,当捕获到下降沿后立即设置未捕获上升沿。两簇计数器的差值即可得低电平持续的时间,但这知识理想状态下,实际的操作过程中人有需要考虑的因素。4 E8 S: B3 K( J3 s q
3 c2 X Q1 V! {$ c) P
; S H) G% i% s3 m
7 @/ Z3 Y. V$ a% t# _# z9 o
如果低电平的持续时间很长,那么计数器不可能无限增长,在计数时会产生N次溢出。所以有如下计算方法,当第一次捕获到下降沿的时候,在捕获中断中我们将计数器的值清零并设置下一次捕获为上升沿,这样,低电平持续的时间就为T=N*ARR+CCRx2。在第二次捕获到上升沿时,用户可在上升沿捕获中断中统计时间,设置其他参数等。+ S* }# G2 Z7 R& L" Y8 Z( Y1 N
# _0 l( T7 e/ m: b# n
& e3 n% s& `( `0 R7 B3 r: p. n6 a8 {: v2 q6 X3 p3 }) W
步骤:- K( b- J0 N) g* f( W) \) L
$ n m `; f3 z* e- ~/ C1.配置时钟RCC。
6 U4 w% e" ~, {# X, y0 p% }
3 |" E$ ]& I$ d* M+ ~+ O2.选择管脚PA0,配置为TIM2的通道1。
5 P& s- H- ^$ F3 ~8 J P- w0 c& f9 N- i# u. @# N6 t
7 [3 ~: {- s) j( {0 m2 n
$ _8 ?' R/ N% p- @4 I3 Z3.配置TIM2硬件。
5 _, S4 Y4 B: I5 S o9 K/ ?$ [& ]: d t" E* [# w& g3 H
* ]5 M% w5 B! E$ O8 L( M$ N) R* f4 s* H# o
4.TIM2协议参数配置。
4 G6 H: ~ r, {. `+ X, |8 q( f% {4 P( G3 t/ D, F' ~
1 f+ p, T; m( s- h9 r4 W
1 B; x* ` R) R5.中断配置。+ m# A+ W) o: ~4 s! e
# W% N5 _ @7 q, n; E9 p
* K. s& b) s9 ]
3 r6 n% z: }( h6 L6 A- q
- //mian.c
3 O' g0 c$ T& ?9 q5 T$ f
# N4 i# z; R. g9 d- int main(void) { 9 d" v9 {. q) T
- HAL_Init();
( P2 |: a9 p( ?6 V6 a - SystemClock_Config();
) M) @' c V; B0 V - MX_GPIO_Init();
$ b& Q' a B6 \# [: i; c" D, X8 ? - MX_TIM2_Init();//初始化TIM2,配置寄存器参数 ; _5 @7 b+ [- c b
- MX_USART1_UART_Init();
0 x, d' f6 T0 J l - printf("this is tim2 cap test\n");
+ j: `5 x* M5 z+ l0 [2 @ - : A9 C" `. Z: c
- HAL_TIM_Base_Start_IT(&htim2);//开启定时器2,使能更新溢出中断 0 ^$ P* M& b7 y
- HAL_TIM_IC_Start_IT (&htim2, TIM_CHANNEL_1); % e& S- A: v! z) A3 Z4 Q. }6 q& G
- //开启定时器2输入捕获功能,使能捕获中断,使用通道1 + R3 L+ L, D n" O( Z
- while (1)" V2 i E# d: G/ l
- {}
$ B) j+ Z5 C* ~. u' T - }
4 d: [& S' I# B
复制代码- //stm32f4xxxit.c F" ?/ B5 F. L, D \& O: `. I0 ?
- void TIM2_IRQHandler(void){ //中断函数入口
8 p4 Y) i% w5 Z - HAL_TIM_IRQHandler(&htim2); 4 \+ G" r7 y& n! }
- }
复制代码 7 k5 i+ A0 e! K3 V6 d
展开函数HAL_TIM_IRQHandler();可得如下
0 W, ?0 q' A5 @5 Y- `& ^% f. u2 l' v1 r9 N& C5 p$ u
5 {( p& I" M1 `3 N. T* f$ m+ k( e# Y9 a; z; u+ t! U; M4 ]" W
3 w) i W) \* S$ [8 i- X0 o
+ v( s1 }. \! M重写回调函数
3 ?& h6 k7 c: l; R& _9 y' C: b! m# @% p; [! o& o) G o1 x# }
- //tim.c - R" f9 i* T2 M4 h3 y
- uint8_t fall_flag = 0; //下降沿捕获标志位 8 w1 y, Y n* I) q0 `; V7 ~
- uint32_t cap_value = 0;
8 X% ~6 p5 o+ A8 m" b3 f
" t/ r/ Q" L* V" s- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ 7 [6 f( r- G7 |* f
- if(htim->Instance == TIM2) {
/ N5 _7 i+ |' c( q - if(fall_flag) { 5 I% _* n4 K$ M |" t8 R9 r) D
- cap_value += 1000000;//每次溢出给cap_value增加重装载寄存器中的值 7 S1 {9 _) D0 x# F/ \
- printf("update int \n"); : f7 e G! x9 C+ q. V2 G j; n: N
- }
3 o- a' q, ~( E3 T" p% @ - }
( I$ t: y) K! Y$ @" R - }
/ @) {( y; t: D
- c- w3 t m, g5 [- r' m- % B* `/ ]' ], u c+ M4 W/ e( V; K
- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
0 D' d0 A/ A+ \+ g - if(htim->Instance == TIM2) {
2 {, ]5 e6 g3 W - if(!fall_flag){//当前捕获到的是下降沿 5 L/ Y1 Y# m, I2 s
- printf("捕获到的是下降沿\n"); + t3 }- M0 V* V+ }% u' I
- HAL_Delay(20); //延时20ms消抖
% q& y- j: ?" f - if( HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
8 d) G, e0 g- W9 O" r$ s% O- ` - fall_flag = 1;
4 J% U8 ~: G' {$ I1 Y- E - __HAL_TIM_DISABLE(htim);//关闭定时器
6 e- Q. X* p; k$ g - __HAL_TIM_SET_COUNTER(htim,0);//设置计数器的值为0 3 Q% Q2 q1 i6 u1 ]
- TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);//清除原来的设置 ; N R) g8 Q! O& O) ^
- //设置为上升沿捕获
: q& h1 ]3 z% Q$ }# ?% L - TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
, E7 E3 Q2 c5 q. A - __HAL_TIM_ENABLE(htim); //使能开启定时器 * D* p. [: i9 I+ j, _( g# N# U
- }
) U9 m0 Y+ l$ ?$ s$ }6 m9 h4 M - }else{
6 `" Q3 D! G6 E" [) @ - printf("捕获到的是上升\n"); 6 p, s z1 j v& p; z
- fall_flag = 0; //当前捕获的是上升沿,本次捕获结束 : h4 Q, P4 m1 [. z2 V r2 v0 o8 {: k
- //最终的时间=溢出的时间+当前计数器的值+延时时间 ! h" m1 W g3 \5 R
- cap_value = cap_value + HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + 20000 ; F$ _4 M! M6 w% @7 N$ x+ Q7 ^
8 O3 }2 w. q3 |1 L+ q9 L3 @- printf("低电平持续的时间=%d秒:%d毫秒:%d微秒\n",
" q1 [# o) b( U8 f) q - cap_value/1000000,cap_value%1000000/1000,cap_value%1000); 0 n7 F" [) C! {! A0 n) Q
, T) Q; S" H# v- cap_value = 0; __HAL_TIM_DISABLE(htim);//关闭定时器
' R( F! \+ g! S# P* c7 Y1 u6 j: C - TIM_RESET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1);//清除原来的设置 - n7 f* J' T+ ~
- % C) l& t- d% R7 }7 ?1 X1 z) X
- //设置为下降沿捕获
3 s! J% t( A6 u& M - TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
: t" E5 k) D; Y8 e; ~+ `8 H - __HAL_TIM_ENABLE(htim); //打开定时器
5 [& @7 Q' y* Y8 t& M8 K% A5 B - HAL_Delay(20);
6 y* j1 T& a& Z T" Z - }
( o$ c1 I* K$ u7 S5 k5 H - } - A2 f! a: a E5 n8 L
- }
复制代码 1 J+ Z* b/ o" T* K7 }
关键功能函数说明:& c6 A/ ^/ T5 R4 Q0 D
/ O! {* B1 Q, Y4 |
! E: g5 Q2 h% P$ Q( N. [& A, i+ o% A/ a
/ o0 n+ t; M2 F; H8 \# ^: T: e" L
STM32定时器输入捕之PWM呼吸灯实验' B C9 g0 t. E
; U9 ~ J8 }: W, Q* }
PWM(Pulse Width Modulation):脉冲宽度调制.
, ~/ W4 M* [/ c3 I; @0 v, G+ W. z2 Z! [! X" c
占空比:就是输出的PWM中,高电平保持的时间与该PWM的时钟周期的时间之比 .
1 N) ~$ h( {1 i- O9 r$ z# a. e: C% s$ a% K2 h3 h
6 f9 X! X9 w$ R* e \' r; p" d# S R7 ?1 I, I
8 M% e/ P( }" @ C9 S0 ?输出一个波形,该波形的脉宽和频率均可调节,把这种波形叫做PWM波。通过调节脉宽可以控制电机的速度,灯的亮灭,舵机的角度等多种应用,整个波形的平均电压取决于占空比,占空比越大,平均电压越高,则电机越快。
9 s/ q4 ?& i0 K Y7 W
, O8 v- D v3 ]: _7 L- vPWM应用:它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量,通信,功率控制与变换等许多领域。脉冲宽度调制(PWM)是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。常见应用有:电机控制,DAC输出等。
3 z* o( Z& d' \2 T T" Z i
$ V/ g8 A- P" |9 b4 p# v
/ K, u& U' Z! ?7 n- f8 S
7 W$ @# w; |2 e# J
输出比较的基本原理为:在CCR中记录一个值(一定比ARR中的值小),专门用来与ARR(装载寄存器)中的值进行比较比较的,因此可以这样设定,当CNT(计数器中的值,不断变化的)小于CCR中的值时输出低电平,当CNT的值大于CCR中的值时输出高电平。不会大于ARR中的值,因为ARR的值是用户设定的最大值,超过就会溢出触发中断,CNT变回0重新计数。因此通过设置CCR中值的大小就可以调节高/低电平的宽度,这就是所谓的PWM波的原理。
+ c! a$ ^( S' R7 Y( V
7 G, k J3 k. Q* H7 c3 CPWM输出模式:
7 ?% t" A/ n8 S7 D" O" R: N0 v. W1 R" r; Q f& Y
0 T7 X. R/ X/ Q! c s- h8 N- g% _ G6 r# M$ c
PWM输出极性:1.高电平有效 2.低电平有效
u* i" W% M! C/ @, E) T; k0 Y5 _0 ~
- D1 Q( [3 \7 Y7 d# d输出高电平和输出低电平都叫做通道有效,至于是哪一种,需要用户自己去配置他的输出极性。例如配置PWM输出极性为低电平有效,且计数器采用PWM1模式下递增方式,则计数器的值小于CCR时,输出低电平,计数器的值大于CCR时,输出高电平。
: V9 \4 m0 H% E6 Q0 R
% P+ V4 c# g: O. x- ^呼吸灯是由亮逐渐变暗,再由暗逐渐变亮的过程,而不是瞬间亮,瞬间灭。因为电平变化的速度很快,微观实际上灯就是瞬间亮,瞬间灭,但是人眼无法分辨。宏观上就是通过平均电压来表现,在单位时间内通过配置多个有规律变化的占空比的大小就可以实现呼吸灯。
) ?! b7 e8 J* k; |' W$ w8 o/ a1 r6 P. ^- c: g
7 k% \. G; U7 W+ U% j% b# m) s5 _0 z
) S8 c8 o* F3 C# e( }8 h由上述LED灯的电器原理图可知,PF7管脚的低电平持续时间越长,灯越亮,也就是占空比越小,灯越亮。本实验采用采用PWM1模式下递增方式低电平有效。
+ N- G. w5 p; r, V7 R$ P. N- D1 G+ {- Z4 q0 ]
步骤:* l( e9 L5 \) N0 B
& ^6 D; i' k e) Y
1.配置时钟RCC.' {- c5 V1 ]5 a, Z/ }
- x& B( U7 r' r- N" s7 {" g2.选择管脚PF7,配置为TIM11的通道1。2 d2 ?3 t# F0 Z4 J6 @! i t0 q
1 n4 |2 I. O: @% v' s
1 I7 t9 X' x9 i9 K2 n
( O/ z r" _: ^+ e( b& ?3.配置TIM11硬件。
* N, v, t8 `4 z5 U- r; o3 l5 C2 T, m
- T$ w+ J* f$ I
^* T+ V, G" D' |4 u" r5 d
" O. t% `! S7 _1 H! H/ t* M4.TIM11协议参数配置。
6 Z- }$ z; ]8 w7 F3 m2 e
0 }* c" |% a. R4 e6 u1 C* i: c8 A
8 f# s! G% ^2 T9 U$ C
. _: a, K9 B2 L. h t9 Z- //mian.c 8 K" } P# |, [' _/ l
- , k: n: c @3 J5 j1 P% J
- #include "main.h" B g; |3 z* x4 ?8 ^2 R/ w- @
- #include "stm32f4xx_hal.h" - i$ J/ Y5 i3 l
- #include "tim.h"
% a# `* f/ o! V7 W3 c - #include "usart.h" / q/ R2 j9 D( s# [4 r# \) m8 }! f; c% b
- #include "gpio.h"
* n3 F; ~/ Z& V! t: `: q& x) ?
, D# t3 }# l3 {; X2 [3 r. }) X, s7 k- void SystemClock_Config(void); 0 M7 a1 q8 H6 v, N
- * Q' c/ X& J; _
- //此函数用来调节TIM11比较寄存器的大小 7 D( p+ x& ?/ J; t+ ?. {. H
- void TIM11_Set_Compare(uint32_t compare) { 3 _0 n/ P( p) R5 @3 ~0 Q- k
- TIM11->CCR1 = compare;
* `! j' g$ R* I - }
* B4 h* B# D4 y L. O - & b7 h$ {; D @2 V1 I
- int main(void) {
m3 y4 P. b% |' S" a9 b5 }% ` - int32_t compare = 0;
- ^ Y2 ~& P+ o6 x9 n5 G0 U - uint8_t dir = 0;//计数模式标志位 $ [0 H1 J* }) ? r- D9 Z% B# F
- , k+ e" X! V( L. ^( q
- HAL_Init();
# V7 n, ]/ [) Q3 o& a4 I8 O: Q - SystemClock_Config();
6 ~- w# H' O# z4 Q( O! h - MX_GPIO_Init();
3 z$ I+ h, `" }6 ?) n - MX_TIM11_Init();
: b2 s$ d3 i; z1 B8 b - MX_USART1_UART_Init(); 8 X6 [0 Y3 t. d* G& h7 t& _2 g
- printf("this is pwm led test\n");
( v9 }; G$ B& ^' g. F# y5 o - * H3 N: K! ]0 K4 {
- //启动PWM定时器
) t' s" v3 S: W, } H& J - HAL_TIM_PWM_Start(&htim11, TIM_CHANNEL_1);
" _' y$ o* K U& X8 R4 L3 \) s. s; b - / v7 K% L: e4 ?6 ~1 E( ]
- while (1) {
, d( M6 x1 Z7 e1 v# W; ^ - if(!dir){ //向上计数
. U1 Y0 O# b6 R/ K/ C& R. C' a - compare += 10;
2 h$ H' k5 G6 p$ f: {) Q& L - if(compare>=1000) {
/ p3 f2 `/ l8 v0 G7 f/ |* ?4 l - compare = 1000-1; 1 |3 D, b! v* Y0 H1 c A& K3 D
- dir = 1;
: C6 s6 A+ @8 B( e4 {. e1 Z. e - }
! S# P9 F' \! o1 V - }else{ //向下计数 ( G; R& _2 F) ?1 G( `
- compare -= 10;
! b1 G8 H: U; G# E0 v. g - if(compare<=0) { 9 a- T& y+ \3 V: Q* v% o
- compare = 0; \
8 e" J" e0 y9 d/ g* u$ K7 ]3 v - dir = 0;
* n n, ]' l$ K/ O* @5 g - } 8 G1 L0 z; E# a& k
- }
! h6 N* ?5 Y. [; E- P0 q9 Q - TIM11_Set_Compare(compare);//设置不同CCR值调节呼吸灯亮度
1 c7 H7 \* P, ~; c- X - HAL_Delay(20); //没有延时的话现象看不到,必须有一个电平的持续时间
( J5 { |8 v9 T3 u - //这20ms内亮度不变,但是肉眼不可见 8 e9 }$ m! E7 Z0 H: S/ u
- //灯缓慢亮缓慢灭 8 m7 Q8 |2 p8 u% C
- } ( l1 M: G/ q0 b1 j3 w' ?
- }7 c% Y0 t6 r/ `8 R9 m& s
复制代码 & q: g/ t8 W' C8 h! p
9 y1 u: q" L7 d. b2 a+ a
转载自:骆驼听海
' A% D4 I2 [0 N# ^9 ]
. V& E/ _& J8 m, F# T6 u$ u; u/ i4 H4 d* O# n! A2 \4 H
|