我们已经介绍了 STM32F1 的通用定时器作为 PWM 输出的使用方法,本文来学习通用定时器作为输入捕获的使用。本章要实现的功能是:通过 TIM5 的通道 1(PA0)捕获外界输入信号的高电平脉宽,通过 printf 打印捕获的高电平时间,D1 指示灯不断闪烁表示系统正常运行。 1 k/ N2 \& B. y% C5 G
输入捕获简介 在定时器中断实验章节中我们介绍了通用定时器具有多种功能, 输入捕获就是其中一种。STM32F1 除了基本定时器 TIM6 和 TIM7,其他定时器都具有输入捕获功能。输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,通常用于测量输入信号的脉宽、测量 PWM 输入信号的频率及占空比。 输入捕获的工作原理比较简单,在输入捕获模式下,当相应的 ICx 信号检测到跳变沿后,将使用捕获/比较寄存器(TIMx_CCRx)来锁存计数器的值。简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。下面我们以输入捕获测量脉宽为例,通过一个简图来介绍输入捕获的工作原理,输入捕获脉宽测量图如下图所示。
3 P% C) j# E. o) [; ], l . o3 i1 h9 k4 r* M" i
从图中可以看出,t1-t2 时间就是我们需要测量的高电平时间,假如定时器工作在向上计数模式,测量方法是:首先设置定时器通道 x 为上升沿捕获,这样在 t1 时刻,就会捕获到当前的 CNT 值,然后立即清零 CNT,并设置通 道 x为下降沿捕获,这样到 t2 时刻,又会发生捕获事件,得到此时的 CNT 值,记为 CCRx2。根据定时器的计数频率,我们就可以算出 t1-t2 的时间,从而得到高电平脉宽。在 t1-t2 时间内可能会出现 N 次定时器溢出,因此我们还需要对定时器溢出进行处理,防止因高电平时间过长发生溢出导致测量数据不准。CNT计数的次数等于:N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。 上述涉及到的寄存器可以参考《STM32F10x 中文参考手册》-14定时器章节的寄存器部分,里面有详细寄存器功能介绍。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。STM32F1 通用定时器的输入捕获功能在前面“定时器中断实验”文中也有详细的介绍,大家可以回过头看下。 % V; B* |( g4 y F# k, |2 S
输入捕获配置步骤 接下来我们介绍下如何使用库函数对通用定时器的输入捕获进行配置。这个也是在编写程序中必须要了解的。其实输入捕获和前面定时器中断一样也是通用定时器的一个功能, 因此还是要用到定时器的相关配置函数, 具体步骤如下:(定时器相关库函数在 stm32f10x_tim.c 和 stm32f10x_tim.h 文件中) (1)使能定时器及端口时钟,并设置引脚模式等 因为输入捕获也是通用定时器的一个功能,所以需要使能相应定时器时钟。由于输入捕获通道是对应着 STM32F1 芯片的IO 口,所以需要使能对应的端口时钟,并将对应 IO 口设置为输入模式功能。例如本章输入捕获实验,我们是使用TIM5的CH1 通道来测量输入信号的脉宽,因此需要使能 TIM5 时钟,调用的库函数如下: - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能 TIM5 时钟
复制代码
: s1 Z4 m$ x2 F4 A5 m 而 TIM5 的 CH1 通道对应的管脚是 PA0,在 STM32F1 芯片数据手册引脚功能上都有介绍如下: % w" V( N' o+ `8 D$ ]8 K
7 ~( Z9 i" r" |# H8 c+ M6 e" s; {
所以需要使能 GPIOA端口时钟。因为我们使用 PA0 来测量输入信号的高电平时间,因此需要将 PA0 配置为下拉输入模式。GPIO 初始化如下: - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//管脚设置- d* h% G! t1 V; l0 M& X
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//设置下拉输入模式* ]% d: g, O/ z# K1 Y [3 m0 U6 D
- GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化结构体
复制代码9 a6 E' ?5 d4 ^" x
(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等
0 } \1 _: e* v Y, }, K) B9 \ 要使用定时器功能,必须对定时器内相关参数初始化,其库函数如下: - voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);
复制代码
# x1 U1 L1 z3 H% R" r2 H% `5 G 这个在定时器中断章节就已经介绍。
3 s, z+ J3 P9 _, b2 c: _ (3)设置通用定时器的输入捕获参数,开启输入捕获功能 初始化定时器后,需要设置对应输入捕获通道参数,比如输入通道、滤波、分频系数、映射关系、捕获极性等。输入捕获参数初始化函数如下: - void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef*TIM_ICInitStruct);
复制代码
' m0 X% R1 t5 X+ _# z: C* F 函数的第一个参数相信大家一看就清楚,是用来选择定时器的。第二个参数是一个结构体指针变量,同样我们看下这个结构体 TIM_ICInitTypeDef 成员变量: - typedef struct
$ o3 V( i4 i+ d4 t( s- Y* E0 U - { q5 r0 G. Y! T& o1 J
- uint16_t TIM_Channel; //通道9 I+ C3 ]" |+ I8 _* h1 v7 ?; G
- uint16_t TIM_ICPolarity; //捕获极性+ m7 f; B& d3 k) X4 X
- uint16_t TIM_ICSelection;//映射
0 R: s1 i5 D& j6 n* q0 `) w, p - uint16_t TIM_ICPrescaler;//分频系数& [4 c' T- h3 C
- uint16_t TIM_ICFilter; //滤波器长度
% l# {, o6 v) i; M - } TIM_ICInitTypeDef;
复制代码
) I; t. g" ?! M# d
TIM_Channel:输入捕获通道设置,通用定时器每个多达 4 个通道,假如我们使用TIM5 的通道 1,参数为TIM_Channel_1。 TIM_ICPolarity:输入信号的有效捕获极性设置,假如我们需要对输入信号上升沿开始捕获,参数为 TIM_ICPolarity_Rising,如果是下降沿捕获,参数为TIM_ICPolarity_Rising。库函数内还提供了单独设置通道捕获极性的函数,假如要修改 TIM5 的通道 1捕获极性为下降沿,函数如下: - TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);
复制代码
$ P# R1 x8 j. [6 U. f; D/ o 此函数可以理解为通用函数 TIM_OCxPolarityConfig(),函数中的 x表示通道, 所以如果是对通道 2 捕获极性操作, 可以调用 TIM_OC2PolarityConfig函数。 TIM_ICSelection:映射设置,在“定时器中断实验”章节中,我们分析了 通用定时器结构框图,知道 ICx 可以映射到 2 个 TIx 上,比如 IC1 可以直接映射到 TI1上,也可以间接映射到 TI2 上,但是不能映射到 TI3 和 TI4 上。假如我们直接映射在 TI1 上,参数为TIM_ICSelection_DirectTI。 TIM_ICPrescaler:分频系数设置,分频系数可以为 TIM_ICPSC_DIV1、TIM_ICPSC_DIV2、TIM_ICPSC_DIV4、TIM_ICPSC_DIV8,假如我们不分频,参数为TIM_ICPSC_DIV1。 TIM_ICFilter:滤波长度设置,假如我们不使用滤波器,参数为0。如果我们需要配置TIM5 的通道 1 为输入捕获功能,并且为上升沿捕获、不分频、直接映射到 TI,可以如下配置: - TIM_ICInitTypeDef TIM_ICInitStructure;
+ R& f9 W) g6 |; B' F - TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; //通道1. o. |: |( G( a; L5 Y
- TIM_ICInitStructure.TIM_ICFilter=0x00; //滤波
- ~# \2 d4 ]) U! a7 x - TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//捕获极性) O% z: |7 Z, Q; V' H
- TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //分频系数. y+ T& [+ _5 i0 D6 D B
- TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;//直接映射到 TI1+ r) R" }7 L- u. _; ]" d! K, k
- TIM_ICInit(TIM5,&TIM_ICInitStructure);
复制代码
( d3 b4 \5 A1 E0 {+ ^3 o
(4)开启捕获和定时器溢出(更新)中断) z9 q7 |% R3 z+ ]$ d& P5 o
假如我们需要检测输入信号的高电平脉宽, 就需要在第一次上升沿到来时捕获一次,然后设置为下降沿捕获,等到下降沿到来时又捕获一次。如果输入信号的高电平脉宽比较长, 那么定时器就可能溢出, 所以需要对定时器溢出进行处理,否则计算的高电平时间将不准。所以需要开启定时器溢出中断。 对定时器中断类型和使能设置的函数如下: - void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT,FunctionalState NewState);
复制代码 4 [ B/ ~, Z# R$ A( V/ q
假如我们要开启 TIM5的捕获中断和定时器溢出中断,调用函数如下: - TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);
复制代码 4 c4 J1 d; k# O9 n" p2 T7 d, L9 m
(5)设置定时器中断优先级,使能定时器中断通道
% I2 r5 v3 W4 ] s; x; }- D1 _ 在上一步我们已经使能了定时器的捕获和更新中断,只要使用到中断,就必需对 NVIC 初始化,NVIC 初始化库函数是 NVIC_Init(),这个在前面讲解 STM32中断时就已经介绍过,不清楚的可以回过头看下。 (6)编写定时器中断服务函数 最后我们还需要编写一个定时器中断服务函数, 通过中断函数处理定时器产生的相关中断。定时器中断服务函数名在 STM32F1 启动文件内就有,TIM5 中断函数名如下:
# C- \( s+ O- C) O0 p e+ | 因为定时器的中断类型有很多,所以进入中断后,我们需要在中断服务函数开头处判断是哪种类型,根据不同中断类型完成相应功能,比如我们需要在捕获中断内完成捕获成功记录,更新中断内完成溢出次数的累计。在中断结束时要清除中断标志,这些在前面定时器中断实验中都有介绍。库函数中用来读取定时器中断状态标志位的函数如下: - ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
复制代码 / y& }+ y+ s8 p2 Z
清除中断标志位的函数如下: - void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
复制代码
/ q4 q/ L/ h5 O3 D8 D+ O 当进入第一次捕获中断,我们会先清空计数器内值,例如对 TIM5 计数器值清空,函数如下: - TIM_SetCounter(TIM5,0); //定时器初值为 0
复制代码
+ [* c1 l) [0 Q2 h1 K( M7 k+ l(7)使能定时器
6 R. u; X( C+ _ y7 w: y: u 前面几个步骤已经将定时器输入捕获配置好,但还不能正常使用,只有开启定时器了才能让它正常工作,开启定时器的库函数如下: - void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码 , t$ z: E; n& ~$ q( [/ q" c
例如我们要开启 TIM5,那么调用此函数如下: - TIM_Cmd(TIM5,ENABLE); //开启定时器
复制代码
' U. S1 v3 L6 H3 C& C4 v; x2 r 将以上几步全部配置好后,我们就可以正常使用定时器输入捕获功能了。
: b( m, N6 `+ M/ f$ Y 本实验使用到硬件资源如下: (1)D1 指示灯 (2)K_UP 按键 (3)串口 1 (4)TIM5 的通道 1 D1指示灯、K_UP 按键、串口1 电路在前面章节都介绍过,至于 TIM5的通道1 它属于 STM32F1 芯片内部的资源。D1 指示灯用来提示系统正常运行,K_UP按键是接在 PA0 管脚上的,所以可以通过此按键输入一个高电平,通过串口 1的printf函数打印捕获到的高电平时间。 所要实现的功能是:使用 TIM5 的 CH1 检测输入信号高电平脉宽,将检 测的高电平脉宽时间通过printf 函数打印出来, 同时让 D1 指示灯不断闪烁表示系统正常运行。程序框架如下: (1)初始化 PA0 管脚为 TIM5 通道 1 输入捕获功能,开启捕获和溢出中断等 (2)编写 TIM5 中断服务函数 (3)编写主函数 在前面介绍输入捕获配置步骤时,就已经讲解如何初始化相关参数。下面我们打开“输入捕获实验”工程,在 APP 工程组中可以 看 到 添 加 了 input.c 文 件 , 在 StdPeriph_Driver 工 程 组 中 添 加 了stm32f10x_tim.c 库文件。定时器操作的库函数都放在 stm32f10x_tim.c 和stm32f10x_tim.h 文件中,所以使用到定时器功能就必须加入 stm32f10x_tim.c文件,同时还要包含对应的头文件路径。
$ Y3 _& R+ K8 L" p8 j
TIM5 的 CH1 初始化函数 要使用 TIM5 的 CH1 的输入捕获功能,我们必须先对它进行配置。初始化代码如下: - /****************************************************************
5 y6 m, b" z) A8 l( w2 D* t - * 函 数 名 : TIM5_CH1_Input_Init& S8 y$ W: X2 u* R
- * 函数功能 : TIM5_CH1 输入捕获初始化函数
3 S) I& h) b- e - * 输 入 : arr:自动重装载值 psc:预分频系数/ s& H3 _6 M: H+ s: \# A9 S, [7 R Z
- * 输 出 : 无+ p) i' V( |' y1 w6 E% {
- *****************************************************************/2 j9 U1 y. ^& t1 X& c7 \: T
- void TIM5_CH1_Input_Init(u16 arr,u16 psc)# j4 |4 j0 K" i, C0 f2 \% m1 G1 W; \ G
- {
; S) c- u$ |6 c4 ?7 u P& H4 ^ - TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
) r N8 b4 J3 a - TIM_ICInitTypeDef TIM_ICInitStructure;- M$ p" q2 s: @8 z- U3 v# |) P( Q& C
- NVIC_InitTypeDef NVIC_InitStructure;
: v5 y5 M' g/ H0 H6 J: D! @" ^, t - GPIO_InitTypeDef GPIO_InitStructure;
* v! ^4 B3 D$ I2 w$ D - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);5 i6 o4 {6 C' v8 `- B
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能 TIM5时钟
3 p9 N4 T8 f, v! B - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//管脚设置; T: t8 i6 i' F- P
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD; //设置下拉输入模式3 J4 G: O, d2 n7 `: C
- GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */4 t& M, d; _0 z5 B5 C/ i
- TIM_TimeBaseInitStructure.TIM_Period=arr; //自动装载
% [- Z$ H; R2 y7 |6 d - TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数
! h% g, A8 t. c6 g - TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
, n3 W9 Z5 e2 a( m3 F$ j& x* j - TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
% u& i1 {8 @9 m! b3 J' a - //设置向上计数模式
! T" V! o: C6 T) t4 Q! j7 l; z$ b - TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
1 J( W$ L* D6 U - TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; //通道1% z6 Y; U/ r' I" N. ?
- TIM_ICInitStructure.TIM_ICFilter=0x00; //滤波
5 n. V! {( K. Q) t J% ^ - TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;// 捕获极性
3 f9 H* T0 R6 w( g" l - TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //分频系数# a+ i Z: j# J/ S, h$ F3 k) A
- TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;//直接映射到 TI1& a1 D! k8 p& X" L5 u
- TIM_ICInit(TIM5,&TIM_ICInitStructure);
}/ [9 m* U* f# }7 u4 @' O - TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);* U" M! n8 C9 ^9 |8 X# ?
- NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;//中断通道9 M& [8 v; p" i; n0 B0 S1 X+ f
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢 占优先级
0 O( {- n; g& W* k* R+ Y - NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级
1 L; z* w. B5 L# H+ P* K6 D - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
0 p8 N7 k4 \: x5 ~1 T - NVIC_Init(&NVIC_InitStructure);
6 p* m' N9 @- e/ a - TIM_Cmd(TIM5,ENABLE); //使能定时器/ J4 p7 I4 D. C5 M4 p: w/ E
- }
复制代码
: l1 k, S: j) w* e, P
在TIM5_CH1_Input_Init()函数中,首先使能 GPIOA 端口时钟和 TIM5时钟,其次配置PA0管脚模式配置为下拉输入。然后配置定时器结构体TIM_TimeBaseInitTypeDef,设置重装载值域分频系数等。再然后初始化 TIM5通道 1的输入捕获相关参数,我们设置为捕获通道 1、不进行滤波、上升沿捕获、分频系数为 1、直接映射到 TI1 上。开启定时器捕获和溢出中断,配置 TIM5 的中断优先级。最后使能 TIM5。这一过程在前面步骤介绍中已经提了。 TIM5_CH1_Input_Init()函数有两个参数,用来设置定时器的自动装载值和分频系数,方便大家修改计数器频率,具体计算参考定时器中断实验章节。因为TIM5是16 位的定时器,所以arr 为 u16 类型。 其实如果你会使用 TIM5 的 CH1 作为输入捕获,那么其他通用定时器通道的使用都类似。
4 Y4 s4 H" C+ y8 y/ V! d
TIM5 中断函数 初始化 TIM5 后,中断就已经开启了,当 TIM5 的 CH1 输入通道上检测到一个上升沿就会触发捕获中断管,如果计数器计数溢出,又会产生溢出中断。具体代码如下: - /****************************************************************
3 s4 \6 C* R: B. x - * 函 数 名 : TIM5_IRQHandler5 E. a2 o/ e) W, s) `* [6 C
- * 函数功能 : TIM5 中断函数
# }& b8 ? ~# G6 H4 ] ]* U) c - * 输 入 : 无2 i5 \$ F: @: U: [ X, @
- * 输 出 : 无
+ l- ? U0 @& e - *****************************************************************/
0 `+ d& @8 Q: _5 F0 G! T; c - void TIM5_IRQHandler(void): T9 X1 e3 P$ q" H x6 P
- {
6 O n0 L2 ^ f4 m9 ^/ v$ |& B - if((TIM5_CH1_CAPTURE_STA&0x80)==0) //还未成功捕获
0 u3 O" t' I! C3 l2 }/ k5 W# x - {2 k: G1 O6 ?2 F- U$ X0 V
- if(TIM_GetITStatus(TIM5,TIM_IT_Update)) //发生更新中断' P( Z' T7 s2 l1 S9 e
- {
: \% B/ N- x0 W3 U, g9 a) P - if(TIM5_CH1_CAPTURE_STA&0X40)//捕获到了高电平+ z# j: A2 j. o& m0 v3 {( z
- {
. n: L, @! Q: t - if((TIM5_CH1_CAPTURE_STA&0x3f)==0x3f) //高电平时间太长
# V9 s x. {& m - {) T* o+ S- |: G! `& V% c( e
- TIM5_CH1_CAPTURE_STA|=0x80; //标志一次捕获成功9 V' B. F" M4 o( B' {$ t
- TIM5_CH1_CAPTURE_VAL=0xffffffff;
, u% n5 l) y' O/ Z/ K9 F3 S - }! W( |8 i: A' Y6 g5 [8 a
- else1 ^& s/ h. b* R! B. P) c% ?4 S
- {, J* @5 U6 Y# x/ h8 c
- TIM5_CH1_CAPTURE_STA++;
! f; |5 Y9 ^& [$ f0 i. p - }3 _* b0 n/ T. k. k
- }5 p1 ^. d5 j$ C+ Z' p0 m; u
- }
5 a1 m" ]7 n0 v - if(TIM_GetITStatus(TIM5,TIM_IT_CC1)) //发生捕获中断 {+ }4 N1 K4 T6 E1 [- j
- {* W% T9 \$ ?$ L- u* ] m8 T
- if(TIM5_CH1_CAPTURE_STA&0X40)//捕获到了低电平0 f- r) x4 L6 S. a
- { Y3 G' ?! Y% X" K( D
- TIM5_CH1_CAPTURE_STA|=0x80; //成功捕获一次高电平( z/ j' }- g/ Z: s- ` B
- TIM5_CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);* r D5 z) G. h
- TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);: G# m& X+ q; J( a- S) I* }
- //设置上升沿捕获: M/ K/ P* u9 L# d& f" T) [
- }
9 M7 s& Q7 \2 h5 d - else) R# I% U( w: x0 [
- {1 I. W# ?7 v' t
- TIM5_CH1_CAPTURE_STA=0;
0 E$ k; B/ }/ C, C - TIM5_CH1_CAPTURE_VAL=0;" Q$ y: R* |3 X8 u
- TIM5_CH1_CAPTURE_STA|=0x40; //捕获到高电平 标志
" ]- Z7 z) W, j$ D - TIM_Cmd(TIM5,DISABLE);) q1 ^! v0 P! N! e
- TIM_SetCounter(TIM5,0); //定时器初值为0
' e+ |' R; x7 f$ E+ H- p- Z( V) R - TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);
4 p3 L& F4 g- v G- A - //设置下降沿捕获9 r# n( v( B4 ^# Y
- TIM_Cmd(TIM5,ENABLE);( C# s# S( _3 w3 F L- S
- }
, ~) p$ w- ]+ N. q0 B2 K+ o" ^" V - }
4 q" G0 N: b, Q0 i: T) y - }8 R( O& H( a' f. h- g
- TIM_ClearITPendingBit(TIM5,TIM_IT_CC1|TIM_IT_Update);& }8 Q2 i! F3 ~' W
- }
复制代码
1 ^( s0 \9 c/ o3 }
程序中我们定义了一个u8类型的全局变量 TIM5_CH1_CAPTURE_STA,此变量可以看成类似一个字节的寄存器,bit7 用来表示捕获完成,bit6 用来表示捕获到高电平,bit5-0 用来表示捕获高电平后定时器溢出次数。程序中还定义了一个 u16 类型的全局变量 TIM5_CH1_CAPTURE_VAL,用来记录捕获到下降沿时TIM5_CNT 值。这两个全部变量的申明在 input.h 文件中,在 input.c 文件开始处,有它们的定义。 获取 TIM5 输入捕获通道1的值库函数如下: ) L# s6 ^; h* U5 y) Z! g! q
获取 TIM5 计数器值库函数如下: / c! ~8 x* C$ R; j; c
要计算输入信号的高电平脉宽,根据输入捕获的工作原理,需要先捕获一次上升沿,然后再捕获一次下降沿,在高电平期间还需要对定时器溢出进行判断,统计定时器溢出次数。由于输入捕获初始化函数中就已经设置为上升沿捕获,并且 TIM5_CH1_CAPTURE_STA 变量初始值为 0,所以当捕获到第一个上升沿时,将TIM5_CH1_CAPTURE_STA、TIM5_CH1_CAPTURE_VAL 和 TIM5_CNT 计数器清 0,然后将 TIM5_CH1_CAPTURE_STA的bit6 置 1,表示已经捕获到高电平,此时还需要将输入捕获极性设置为下降沿捕获,等待下降沿到来。如果等待下降沿期间,计数器 出 现 溢 出 , 就 在 TIM5_CH1_CAPTURE_STA 内 累 计 溢 出 次 数 , 如 果TIM5_CH1_CAPTURE_STA 的 bit5-0 都累计满,我们就强制标记捕获完成 (TIM5_CH1_CAPTURE_STA 的bit7 置 1,虽然此时还没有捕获到下降沿),当下降沿到来的时,首先将 TIM5_CH1_CAPTURE_STA 的 bit7 置 1,表示成功捕获一次高电平,然后读取 TIM5_CNT 计数器内值保存到 TIM5_CH1_CAPTURE_VAL 内,最后设置捕获极性为上升沿,回到初始捕获状态。 这样我们就完成了一次高电平的捕获,只要 TIM5_CH1_CAPTURE_STA 的 bit7 一直为 1,就不会进行第二次捕获,这个时候如果主函数把捕获到高电平数据处理后,将 TIM5_CH1_CAPTURE_STA 的 bit7 清零就可以进入第二次捕获了。
/ g) q* r4 R9 y: E" }4 h
主函数 编写好 TIM5_CH1 输入捕获初始化和中断函数后,接下来就可以编写主函数了,代码如下: - /****************************************************************
: y3 r/ N1 Q# E2 J6 B - * 函 数 名 : main
, e5 Q( }: |! ^. J# N! }! A, \ - * 函数功能 : 主函数4 Y5 ^8 v+ t4 G. L
- * 输 入 : 无
& z7 X, f* s9 i - * 输 出 : 无1 K5 u, c8 A/ a
- *****************************************************************/
0 X; X% U, c! {; Z - int main()
, L$ ^: z0 o0 G N7 @0 d4 ` - {6 f1 v1 V8 {3 p! O' u
- u8 i=0;' @ n' K) i( O* w
- u32 indata=0;! z' l d5 o( h" @$ k- E8 k
- SysTick_Init(72);
; {2 ?5 A# t9 _* C/ ]+ g) B: p - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组. \& y# u# V$ u% [
- LED_Init();
( f9 t4 a& L. i3 ?2 s3 @' o8 } - USART1_Init(9600);5 ~) h1 a/ b7 k: t0 E( w
- TIM5_CH1_Input_Init(0xffff,71); //以 1M 频率计数
( f" {( I" H$ b. H# t5 B2 X - while(1)
3 X+ s8 e- i! v2 e - {6 F: f* ~% o9 j. A8 _$ R; j
- if(TIM5_CH1_CAPTURE_STA&0x80) //成功捕获7 B* x0 k& r3 d
- {
0 k b- U S {4 {) y8 T - indata=TIM5_CH1_CAPTURE_STA&0x3f;8 d0 h- m9 B" s) |1 `
- indata*=0xffff; //溢出次数乘以一次的计数次数时间 us% [# Y3 t, e7 v9 }
- indata+=TIM5_CH1_CAPTURE_VAL;//加上高电平捕获的时间
" c. B! \3 D( F; i/ S0 ^ - printf("高电平持续时间:%d us\r\n",indata); //总的高电平时间
. P! A0 g& c. L+ f, Q; e& K - TIM5_CH1_CAPTURE_STA=0; //开始下一次捕获
+ X) {5 A1 M4 [" m2 o: U - }% T% t7 B$ S0 A0 O: b/ g5 v
- i++;/ e2 W/ u3 ?6 n5 z. e
- if(i%20==0)! D1 J9 }7 f8 y- X* B
- {$ ~8 w* `; Z! T1 _. C& {" w
- led1=!led1;2 m" z1 ~& t0 B0 X4 T% ^ j1 r( D
- }7 z9 [. y8 `- C4 L1 q5 Y v+ X
- delay_ms(10);
* ]) D- r% v: t: F8 L - }
5 Z8 w7 J* Z6 X# X( K1 u - }
复制代码
3 z8 U9 S2 V- b, ]! p8 j
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括 SysTick 系统时钟,中断分组,LED 初始化等。然后调用我们前面编写的 TIM5_CH1_Input_Init 函数,我们将 TIM5 的重装载值设置为 0XFFFF,预分频系数设置为 72-1,此时 TIM5 计数器频率为 1MHz,即计数一次时间为1us。最后进入 while 循环,首先判断 TIM5_CH1_CAPTURE_STA 变量的 bit7 是否为 1,如果为1 表示成功捕获,然后读取 TIM5_CH1_CAPTURE_STA 变量内 bit5-0 溢出次数,每一次溢出时间是 0xffffus,最后还要加上 TIM5_CH1_CAPTURE_VAL 的值保存在indata 内,通过 printf 函数打印高电平时间,然后将 TIM5_CH1_CAPTURE_STA变量清 0 即可,等待下次捕获。同时 D1 指示灯会间隔200ms 闪烁,提示系统正常运行。 将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。按下 K_UP 按键,串口输出高电平持续时间。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR 框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到 printf 发送过来的信息。(前提一定要连接好线路,USB 线一端连接电脑,另一端连接开发板“USB 转串口模块”上的 USB 下载口。先勾选下标号1 DTR 框,然后再取消勾选)如下图所示:
8 Z6 `; U! l: r- D |