提示:今天是STM32学习的第四天,今天的学习笔记是外部中断 ,看门狗,定时器中断
目录
第一讲 外部中断实验
这里 我们将介绍 STM32 外部 IO 口的中断功能,通过中断的功能,达到第八章实验的效果,即:通 过板载的 4 个按键,控制板载的两个 LED 的亮灭以及蜂鸣器的发声。 这章的代码主要分布在固件库的 stm32f10x_exti.h 和 stm32f10x_exti.c 文件中。
- 外部中断概述
- 常用寄存器和库函数配置
- 手把手用外部中断写按键实验
1.外部中断概述
这里我们首先 STM32 IO 口中断的一些基础概念。STM32 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32 的强大之处。STM32F103 的中断控制器支持 19 个外部中断/ 事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103 的 19 个外部中断为:
- 线 0~15:对应外部 IO 口的输入中断。
- 线 16:连接到 PVD 输出。
- 线 17:连接到 RTC 闹钟事件。
- 线 18:连接到 USB 唤醒事件。
从上面可以看出,STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不 止 16 个,那么 STM32 是怎么把 16 个中断线和 IO 口一一对应起来的呢?于是 STM32 就这样 设计,GPIO 的管教 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、 GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置 来决定对应的中断线配置到哪个 GPIO 上了。
对于每个中断线,我们可以设置相应的触发方式(上升沿触发,下降沿触发,边沿触发)以及使能。
是不是16个中断线就可以分配16个中断服务函数呢?IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数。
2.常用寄存器和库函数配置
2.1固件库
库函数,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
该函数将 GPIO 端口与中断线映射起来,就是使用库函数范例是:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
将中断线 2 与 GPIOE 映射起来,那么很显然是 GPIOE.2 与 EXTI2 中断线连接了。设置好中断 线映射之后,那么到底来自这个 IO 口的中断是通过什么方式触发的呢?接下来我们就要设置该中断线上中断的初始化参数了。
中断线上中断的初始化是通过库函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
下面我们用一个使用范例来说明这个库函数的使用:
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据 EXTI_InitStruct 中指定的
//参数初始化外设 EXTI 寄存器
上面的例子设置中断线 4 上的中断为下降沿触发。STM32 的外设的初始化都是通过结构体来设 置初始值的,这里就不罗嗦结构体初始化的过程了。我们来看看结构体 EXTI_InitTypeDef 的成 员变量:
typedef struct
{
uint32_t EXTI_Line;
EXTIMode_TypeDef EXTI_Mode;
EXTITrigger_TypeDef EXTI_Trigger;
FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;
有 4 个参数需要设置。第一个参数是中断线的标号,取值范围为 EXTI_Line0~EXTI_Line15。这个在上面已经讲过中断线的概念。也就是说,这个函数配置的是 某个中断线上的中断参数。第二个参数是中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。第三个参数是触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发 EXTI_Trigger_Rising_Falling,最后一个参数就是使能中断线 了。
我们设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置 NVIC 中断优先级。这个在前面已经讲解过,这里我们就接着上面的范例, 设置中断线 2 的中断优先级。(串口的时候记过)
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
我们配置完中断优先级之后,接着我们要做的就是编写中断服务函数。中断服务函数的名字是在 MDK 中事先有定义的。这里需要说明一下,库函数中STM32 的 IO 口外部中断函数只有 6 个, 分别为:
EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中 断线 10-15 共用中断函数 EXTI15_10_IRQHandler。
进入中断:中断服务函数的时候会经常使用到两个函数:
第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line); 这个函数一般使用在中断服务函数的开头判断中断是否发生。
第二个个函数是清除某个中断线上的中断标志位:
void EXTI_ClearITPendingBit(uint32_t EXTI_Line); 这个函数一般应用在中断服务函数结束之前,清除中断标志位。
常用的中断服务函数格式为:
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生
{
中断逻辑…
EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位
}
}
在这里需要说明一下,固件库还提供了两个函数用来判断外部中断状态以及清除外部状态 标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。 只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而 EXTI_GetFlagStatus 直接用来判断状态标志位。
2.2 IO 口外部中断的一般步骤:
GPIO_Init();
② 开启IO口复用时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
void GPIO_EXTILineConfig();
EXTI_Init();
NVIC_Init();
⑥ 编写中断服务函数。
EXTIx_IRQHandler();
EXTI_ClearITPendingBit();
3. 手把手用外部中断写按键实验
这里我们使用的是中断来检测按键,还是 WK_UP 控制蜂鸣器,按一次叫,再按一次停;KEY2 控制 DS0,按一次亮,再按一次灭;KEY1控制 DS1,效果同 KEY2;KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
我们的 HARDWARE 目录下面增加了 exti.c 文件,同时固件库目录增加了 stm32f10x_exti.c 文件。 exit.c 文件总共包含 5 个函数。
一个是外部中断初始化函数 void EXTIX_Init(void),另外 4 个都是中断服务函数。
void EXTI0_IRQHandler(void)是外部中断 0 的服务函数,负责 WK_UP 按键的中断检测;
void EXTI2_IRQHandler(void)是外部中断 2 的服务函数,负责 KEY2 按键的中断检测;
void EXTI3_IRQHandler(void)是外部中断 3 的服务函数,负责 KEY1 按键的中断检测;
void EXTI4_IRQHandler(void)是外部中断 4 的服务函数,负责 KEY0 按键的中断检测
下面我们列出中断线 2 的相关配置代码:
#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"
//外部中断 0 服务程序
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
KEY_Init(); //①按键端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //②开启 AFIO 时钟
//GPIOE.2 中断线以及中断初始化配置,下降沿触发
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);//③开启时钟复用IO口
EXTI_InitStructure.EXTI_Line=EXTI_Line2;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //④初始化中断线参数
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//⑤初始化 NVIC优先级
}
//⑥外部中断 2 服务程序
void EXTI2_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY2==0) //按键 KEY2
{
LED0=!LED0;
}
EXTI_ClearITPendingBit(EXTI_Line2); //清除 LINE2 上的中断标志位
}
外部中断初始化函数 void EXTIX_Init(void),该函数严格按照我们之前的步骤来初始化外 部中断,首先调用 KEY_Init()函数,利用第八章按键初始化函数,来初始化外部中断输入的 IO 口,接着调用 RCC_APB2PeriphClockCmd()函数来使能复用功能时钟。接着配置中断线和 GPIO 的映射关系,然后初始化中断线。需要说明的是因为我们的 WK_UP 按键是高电平有效的,而 KEY0、KEY1 和 KEY2 是低电平有效的,所以我们设置 WK_UP 为上升沿触发中断,而 KEY0、 KEY1 和 KEY2 则设置为下降沿触发。这里我们把所有中断都分配到第二组,把按键的抢占优 先级设置成一样,而子优先级不同,这四个按键,KEY0 的优先级最高。
接下来我们介绍各个按键的中断服务函数,一共 4 个。先看按键 KEY2 的中断服务函数 void EXTI2_IRQHandler (void),该函数代码比较简单,先延时 10ms 以消抖,再检测 KEY2 是否还 是为低电平,如果是,则执行此次操作(翻转 LED0 控制信号),如果不是,则直接跳过,在最 后有一句 EXTI_ClearITPendingBit(EXTI_Line2);通过该句清除已经发生的中断请求。同样,我 们可以发现 KEY0、KEY1 和 WK_UP 的中断服务函数和 KEY2 按键的十分相似,我们就不逐 个介绍了。
接下来我们看看 main.c 里面里面的内容:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "exti.h"
#include "beep.h"
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(.115200); //串口初始化波特率为 115200
LED_Init(); //初始化与 LED 连接的硬件接口
BEEP_Init(); //初始化蜂鸣器端口
KEY_Init(); //初始化与按键连接的硬件接口
EXTIX_Init(); //外部中断初始化
LED0=0; //点亮 LED0
while(1)
{
printf("OK\r\n");//打印 OK
delay_ms(1000);
}
}
第二讲 看门狗
在启动正常运行的时候,系统不能复位。在系统跑飞(程序异常执行)的情况,系统复位,程序重新执行。
单片机系统在外界的干扰下会出现程序跑飞的现象导致出现死循环,看门狗电路就是为了避免 这种情况的发生。看门狗的作用就是在一定时间内(通过定时计数器实现)没有接收喂狗信号 (表示 MCU 已经挂了),便实现处理器的自动复位重启(发送复位信号)。STM32 内 部自带了 2 个看门狗:独立看门狗(IWDG)和窗口看门狗(WWDG)。
1.独立看门狗(IWDG)
- 1.1独立看门狗的概述
- 1.2常用寄存器和库函数的配置
- 1.3独立看门狗的实验
1.1独立看门狗的概述
STM32 的独立看门狗由内部专门的 40Khz 低速时钟驱动,即使主时钟发生故障,它也仍然 有效。这里需要注意独立看门狗的时钟是一个内部 RC 时钟,所以并不是准确的 40Khz,而是 在 30~60Khz 之间的一个可变化的时钟,只是我们在估算的时候,以 40Khz 的频率来计算,看 门狗对时间的要求不是很精确,所以,时钟有些偏差,都是可以接受的。
STM32内置两个看门狗,提供了更高的安全性,时间的精确性和使用的灵活性。两个看门狗设备(独立看门狗/窗口看门狗)可以用来检测和解决由软件错误引起的故障。当计数器达到给定的超时值时,触发一个中断(仅适用窗口看门狗)或者产生系统复位。独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它仍有效。
独立看门狗适合应用于需要看门狗作为一个在主程序之外 能够完全独立工作,并且对时间精度要求低的场合。
窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序。
独立看门狗功能描述:在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗。此时计数器开始从其复位值0xFFF递减,当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)。无论何时,只要在键值寄存器IWDG_KR中写入0xAAAA(通常说的喂狗), 自动重装载寄存器IWDG_RLR的值就会重新加载到计数器,从而避免看门狗复位。如果程序异常,就无法正常喂狗,从而系统复位。
1.2常用寄存器和库函数的配置
1.2.1常用寄存器
- 键值寄存器IWDG_KR: 0~15位有效
- 预分频寄存器IWDG_PR:0~2位有效。具有写保护功能,要操作先取消写保护
- 重装载寄存器IWDG_RLR:0~11位有效。具有写保护功能,要操作先取消写保护。
- 状态寄存器IWDG_SR:0~1位有效
下面我们在了解几个与独立看门狗相关联的寄存器之后讲解怎么通过库函数来实现配置。 首先是键值寄存器 IWDG_KR
在键值寄存器(IWDG_KR)中写入 0xCCCC,开始启用独立看门狗;此时计数器开始从其复 位值 0xFFF 递减计数。当计数器计数到末尾 0x000 时,会产生一个复位信号(IWDG_RESET)。 无论何时,只要键寄存器 IWDG_KR 中被写入 0xAAAA, IWDG_RLR 中的值就会被重新加载到计数器中从而避免产生看门狗复位 。 IWDG_PR 和 IWDG_RLR 寄存器具有写保护功能。要修改这两个寄存器的值,必须先向 IWDG_KR 寄存器中写入 0x5555。将其他值写入这个寄存器将会打乱操作顺序,寄存器将重新 被保护。重装载操作(即写入 0xAAAA)也会启动写保护功能。 还有两个寄存器,一个预分频寄存器(IWDG_PR),该寄存器用来设置看门狗时钟的分频 系数。另一个重装载寄存器。该寄存器用来保存重装载到计数器中的值。该寄存器也是一个 32 位寄存器,但是只有低 12 位是有效的。
1.2.2 库函数的配置
只要对以上三个寄存器进行相应的设置,我们就可以启动 STM32 的独立看门狗,启动过程可以按如下步骤实现(独立看门狗相关的库函数和定义分布在文件 stm32f10x_iwdg.h 和 stm32f10x_iwdg.c 中)
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//取消写保护:0x5555使能
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PR
void IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLR
void IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KR
void IWDG_Enable(void);//使能看门狗:写0xCCCC到KR
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);//状态:重装载/预分频 更新
1)取消寄存器写保护(向 IWDG_KR 写入 0X5555) 通过这步,我们取消 IWDG_PR 和 IWDG_RLR 的写保护,使后面可以操作这两个寄存器, 设置 IWDG_PR 和 IWDG_RLR 的值。这在库函数中的实现函数是:
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
这个函数非常简单,顾名思义就是开启/取消写保护,也就是使能/失能写权限。
2)设置独立看门狗的预分频系数和重装载值 设置看门狗的分频系数的函数是:
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //设置 IWDG 预分频值
设置看门狗的重装载值的函数是:
void IWDG_SetReload(uint16_t Reload); //设置 IWDG 重装载值
设置好看门狗的分频系数 prer 和重装载值就可以知道看门狗的喂狗时间(也就是看门狗溢出时间),该时间的计算方式为:
Tout=((4×2^prer) ×rlr) /40//时钟频率LSI=40K, 一个看门狗时钟周期就是最短超时时间。最长超时时间= (IWDG_RLR寄存器最大值)X看门狗时钟周期
- 其中 Tout 为看门狗溢出时间(单位为 ms);
- prer 为看门狗时钟预分频值(IWDG_PR 值), 范围为 0~7;
- rlr 为看门狗的重装载值(IWDG_RLR 的值);
比如我们设定 prer 值为 4,rlr 值为 625,那么就可以得到 Tout=64×625/40=1000ms,这样, 看门狗的溢出时间就是 1s,只要你在一秒钟之内,有一次写入 0XAAAA 到 IWDG_KR,就不会导致看门狗复位(当然写入多次也是可以的)。这里需要提醒大家的是,看门狗的时钟不是准确的 40Khz,所以在喂狗的时候,最好不要太晚了,否则,有可能发生看门狗复位。
3)重载计数值喂狗(向 IWDG_KR 写入 0XAAAA) 库函数里面重载计数值的函数是: IWDG_ReloadCounter(); //按照 IWDG 重装载寄存器的值重装载 IWDG 计数器 通过这句,将使 STM32 重新加载 IWDG_RLR 的值到看门狗计数器里面。即实现独立看门狗的喂狗操作。
4) 启动看门狗(向 IWDG_KR 写入 0XCCCC) 库函数里面启动独立看门狗的函数是: IWDG_Enable(); //使能 IWDG 通过这句,来启动 STM32 的看门狗。
注意 IWDG 在一旦启用,就不能再被关闭!想要关 闭,只能重启,并且重启之后不能打开 IWDG,否则问题依旧,所以在这里提醒大家,如果不用 IWDG 的话,就不要去打开它,免得麻烦。
通过上面 4 个步骤,我们就可以启动 STM32 的看门狗了,使能了看门狗,在程序里面就 必须间隔一定时间喂狗,否则将导致程序复位。
1.3独立看门狗的实验
在配置看门狗后,DS0 将常亮,如果 WK_UP 按键按下,就喂狗,只要 WK_UP 不停的按, 看门狗就一直不会产生复位,保持 DS0 的常亮,一旦超过看门狗定溢出时间(Tout)还没按, 那么将会导致程序重启,这将导致 DS0 熄灭一次。
1.取消寄存器写保护:
IWDG_WriteAccessCmd();
2.设置独立看门狗的预分频系数,确定时钟:
IWDG_SetPrescaler();
3.设置看门狗重装载值,确定溢出时间:
IWDG_SetReload();
4.使能看门狗
IWDG_Enable();
5.应用程序喂狗:
IWDG_ReloadCounter();
溢出时间计算:
Tout=((4×2^prer) ×rlr) /40 (M3)
这里没有使能函数是因为由专用的低速时钟LSI驱动。
stm32f10x_iwdg.c 文件。 wdg.c 里面的代码如下:
#include "wdg.h"
//初始化独立看门狗
//prer:分频数:0~7(只有低 3 位有效!)
//分频因子=4*2^prer.但最大值只能是 256!
//rlr:重装载寄存器值:低 11 位有效.
//时间计算(大概):Tout=((4*2^prer)*rlr)/40 (ms).
void IWDG_Init(u8 prer,u16 rlr)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //①使能对寄存器 I 写操作
IWDG_SetPrescaler(prer); //②设置 IWDG 预分频值:设置 IWDG 预分频值
IWDG_SetReload(rlr); //②设置 IWDG 重装载值
IWDG_ReloadCounter(); //③按照 IWDG 重装载寄存器的值重装载 IWDG 计数器
IWDG_Enable(); //④使能 IWDG
}
//喂独立看门狗
void IWDG_Feed(void)
{
IWDG_ReloadCounter();//reload
}
void IWDG_Feed(void)函数,该函数用来喂狗,因为 STM32 的喂狗只需要向键值寄存器写 入 0XAAAA 即可,也就是调用 IWDG_ReloadCounter()函数。
接下来我们看看主函数 main 的代码。在主程序里面我们先初始化一下系统代码,然后启动按键输入和看门狗,在看门狗开启后马山点亮 LED0(DS0),并进入死循环等待按键的输入, 一旦 WK_UP 有按键,则喂狗,否则等待 IWDG 复位的到来。该部分代码如下:
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //初始化与 LED 连接的硬件接口
KEY_Init(); //按键初始化
delay_ms(500); //让人看得到灭
IWDG_Init(4,625); //与分频数为 64,重载值为 625,溢出时间为 1s
LED0=0; //点亮 LED0
while(1)
{
if(KEY_Scan(0)==WKUP_PRESS)
{
IWDG_Feed(); //如果 WK_UP 按下,则喂狗
}
delay_ms(10);
};
}
2.窗口看门狗(WWDG)
- 2.1窗口看门狗的概述
- 2.2常用寄存器和库函数的配置
- 2.3窗口看门狗的实验
2.1窗口看门狗的概述
为什么要窗口看门狗?
对于一般的看门狗,程序可以在它产生复位前的任意时刻刷新看门狗,但这有一个隐患,有可能程序跑乱了又跑回到正常的地方,或跑乱的程序正好执行了刷新看门狗操作,这样的情况下一般的看门狗就检测不出来了;
如果使用窗口看门狗,程序员可以根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗也不会滞后刷新看门狗,这样可以检测出程序没有按照正常的路径运行非正常地跳过了某些程序段的情况。
窗口看门狗:之所以称为窗口就是因为其喂狗时间是一个有上下限的范围内(窗口),你可以通过设定相关寄存器,设定其上限时间(下限固定)。喂狗的时间不能过早也不能过晚。而独立看门狗限制喂狗时间在0-x内,x由相关寄存器决定。喂狗的时间不能过晚。
STM32F的窗口看门狗中有一个7位的递减计数器T[6:0],它会在出现下述2种情况之一时产生看门狗复位:
如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),它可以用于喂狗以避免WWDG复位。
窗口看门狗的超时公式如下:
Twwdg=(4096×2^WDGTB×(T[5:0]+1)) /Fpclk1;
- Twwdg:WWDG 超时时间(单位为 ms)
- Fpclk1:APB1 的时钟频率(单位为 Khz)
- WDGTB:WWDG 的预分频系数
- T[5:0]:窗口看门狗的计数器低 6 位
根据上面的公式,假设 Fpclk1=36Mhz,那么可以得到最小-最大超时时间表如表所示:
36M 时钟下窗口看门狗的最小最大超时表
2.2常用寄存器和库函数的配置
2.1常用寄存器
接下来,我们介绍窗口看门狗的 3 个寄存器。首先介绍控制寄存器(WWDG_CR),该寄存器的各位描述如图所示:
void WWDG_Enable(uint8_t Counter);//启动并设置初始值
void WWDG_SetCounter(uint8_t Counter);//喂狗
可以看出,这里我们的 WWDG_CR 只有低八位有效,T[6:0]用来存储看门狗的计数器值, 随时更新的,每个窗口看门狗计数周期(4096×2^ WDGTB)减 1。当该计数器的值从 0X40 变 为 0X3F 的时候,将产生看门狗复位。 WDGA 位则是看门狗的激活位,该位由软件置 1,以启动看门狗,并且一定要注意的是该 位一旦设置,就只能在硬件复位后才能清零了。
窗口看门狗的第二个寄存器是配置寄存器(WWDG_CFR),该寄存器的各位及其描述如图所示:
void WWDG_EnableIT(void);//使能提前唤醒中断
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
void WWDG_SetWindowValue(uint8_t WindowValue);
状态寄存器(WWDG_SR),该寄存器用来记录当前是否有提前唤醒 的标志。该寄存器仅有位 0 有效,其他都是保留位。当计数器值达到 40h 时,此位由硬件置 1。 它必须通过软件写 0 来清除。对此位写 1 无效。即使中断未被使能,在计数器的值达到 0X40 的时候,此位也会被置 1。
FlagStatus WWDG_GetFlagStatus(void);
void WWDG_ClearFlag(void);
2.2库函数的配置
在介绍完了窗口看门狗的寄存器之后,我们介绍要如何启用 STM32 的窗口看门狗。这里 我们介绍库函数中用中断的方式来喂狗的方法,窗口看门狗库函数相关源码和定义分布在文件 stm32f10x_wwdg.c 文件和头文件 stm32f10x_wwdg.h 中。步骤如下:
1)使能 WWDG 时钟 WWDG 不同于 IWDG,IWDG 有自己独立的 40Khz 时钟,不存在使能问题。而 WWDG 使用的是 PCLK1 的时钟,需要先使能时钟。方法是: RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG 时钟使能
2)设置窗口值和分频数 设置窗口值的函数是:
void WWDG_SetWindowValue(uint8_t WindowValue); 这个函数的入口参数 WindowValue 用来设置看门狗的上窗口值。 设置分频数的函数是:
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler); 这个函数同样只有一个入口参数,用来设置看门狗的分频值。
3)开启 WWDG 中断并分组 开启 WWDG 中断的函数为:
WWDG_EnableIT(); //开启窗口看门狗中断 接下来是进行中断优先级配置,这里就不重复了,使用 NVIC_Init()函数即可。
4) 设置计数器初始值并使能看门狗 这一步在库函数里面是通过一个函数实现的:
void WWDG_Enable(uint8_t Counter); 该函数既设置了计数器初始值,同时使能了窗口看门狗。
5) 编写中断服务函数
在最后,还是要编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则当 窗口看门狗计数器值减到 0X3F 的时候,就会引起软复位了。在中断服务函数里面也要将状态 寄存器的 EWIF 位清空。 完成了以上 5 个步骤之后,我们就可以使用 STM32 的窗口看门狗了。这一章的实验,我 们将通过 DS0 来指示 STM32 是否被复位了,如果被复位了就会点亮 300ms。DS1 用来指示中断喂狗,每次中断喂狗翻转一次。
1.使能看门狗时钟:
RCC_APB1PeriphClockCmd();
2.设置分频系数:
WWDG_SetPrescaler();
3.设置上窗口值:
WWDG_SetWindowValue();
4.开启提前唤醒中断并分组(可选):
WWDG_EnableIT();
NVIC_Init();
5.使能看门狗:
WWDG_Enable();
6.喂狗:
WWDG_SetCounter();
7.编写中断服务函数
WWDG_IRQHandler();
2.3窗口看门狗的实验
库函数文件 stm32f10x_wwdg.c
u8 WWDG_CNT=0x7f;
//初始化窗口看门狗
//tr :T[6:0],计数器值
//wr :W[6:0],窗口值
//fprer:分频系数(WDGTB),仅最低 2 位有效
//Fwwdg=PCLK1/(4096*2^fprer).
void WWDG_Init(u8 tr,u8 wr,u32 fprer)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG 时钟使能
WWDG_CNT=tr&WWDG_CNT; //初始化 WWDG_CNT.
WWDG_SetPrescaler(fprer); //设置 IWDG 预分频值
WWDG_SetWindowValue(wr); //设置窗口值
WWDG_Enable(WWDG_CNT); //使能看门狗,设置 counter
WWDG_ClearFlag(); //清除提前唤醒中断标志位
WWDG_NVIC_Init(); //初始化窗口看门狗 NVIC
WWDG_EnableIT(); //开启窗口看门狗中断
}
//重设置 WWDG 计数器的值
void WWDG_Set_Counter(u8 cnt)
{
WWDG_Enable(cnt); //使能看门狗,设置 counter .
}
//窗口看门狗中断服务程序
void WWDG_NVIC_Init()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; //WWDG 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占 2 子优先级 3 组 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //抢占 2,子优先级 3,组 2
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure); //NVIC 初始化
}
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(WWDG_CNT); //当禁掉此句后,窗口看门狗将产生复位
WWDG_ClearFlag(); //清除提前唤醒中断标志位
LED1=!LED1; //LED 状态翻转
}
第一个函数 void WWDG_Init(u8 tr,u8 wr,u8 fprer)用来 设置 WWDG 的初始化值。包括看门狗计数器的值和看门狗比较值等。该函数就是按照我们上 一节讲解的 4 个步骤设计出来的代码。
注意到这里有个全局变量 WWDG_CNT,该变量用来保 存最初设置 WWDG_CR 计数器的值。在后续的中断服务函数里面,就又把该数值放回到WWDG_CR 上。 WWDG_Set_Counter()函数就是用来重设窗口看门狗的计数器值的。 然后是中断分组函数,之前有讲解,这里不重复。 最后在中断服务函数里面,先重设窗口看门狗的计数器值,然后清除提前唤醒中断标志。 最后对 LED1(DS1)取反,来监测中断服务函数的执行了状况。我们再把这几个函数名加入到 头文件里面去,以方便其他文件调用。
在完成了以上部分之后,我们就回到主函数,代码如下:
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 初始化
KEY_Init(); //按键初始化
LED0=0;
delay_ms(300);
WWDG_Init(0X7F,0X5F,WWDG_Prescaler_8);//计数器值为 7f,窗口寄存器为 5f,
//分频数为 8
while(1)
{
LED0=1;
}
}
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
第一讲 定时器中断实验
1.通用定时器
STM32定时器:STM32F10x系列总共最多有8个定时器分为三种STM32定时器。我们主要了解第二种通用定时器,学习定时计数、PWM输出、输入捕获。
通用定时器功能特点描述:STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能特点包括:
位于低速的APB1总线上(APB1)
16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)。
16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值。
4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
① 输入捕获
② 输出比较
③ PWM 生成(边缘或中间对齐模式)
④ 单脉冲模式输出
可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
①更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
②触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
③输入捕获
④输出比较
⑤支持针对定位的增量(正交)编码器和霍尔传感器电路
⑥触发输入作为外部时钟或者按周期的电流管理
lSTM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
l使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
①向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
②向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
2. 通用定时器工作过程
此图分为四个最上面就是选择时钟,其次就是时基单元中下,左边输入捕获,右边输出比较。
1.选择时钟
2.时基单元
3.输入捕获
4.输出比较
第二讲 定时器中断实验
- 1.通用定时器知识
- 2.常用寄存器和库函数配置
- 3.定时器中断实验
1.通用定时器知识
时钟选择:计数器时钟可以由下列时钟源提供
- 内部时钟(CK_INT)
- 外部时钟模式1:外部输入脚(TIx)
- 外部时钟模式2:外部触发输入(ETR)
- 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
内部时钟选择
2.常用寄存器和库函数配置
1.常用寄存器
计数器当前值寄存器CNT
预分频寄存器TIMx_PSC
自动重装载寄存器(TIMx_ARR)
控制寄存器1(TIMx_CR1)
DMA中断使能寄存器(TIMx_DIER)
2.常用库函数
定时器参数初始化:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
TIM_TimeBaseStructure.TIM_Period = 4999;
TIM_TimeBaseStructure.TIM_Prescaler =7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
定时器使能函数:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
定时器中断使能函数:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
状态标志位获取和清除
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
定时器中断实现步骤
1.能定时器时钟。
RCC_APB1PeriphClockCmd();
2.初始化定时器,配置ARR,PSC。
TIM_TimeBaseInit();
3.开启定时器中断,配置NVIC。
void TIM_ITConfig();
NVIC_Init();
4.使能定时器。
TIM_Cmd();
5.编写中断服务函数。
TIMx_IRQHandler();
3.定时器中断实验
#include "timer.h"
#include "led.h"
//通用定时器 3 中断初始化
//这里时钟选择为 APB1 的 2 倍,而 APB1 为 36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器 3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //①时钟 TIM3 使能
//定时器 TIM3 初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //②初始化 TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //③允许更新中断
//中断优先级 NVIC 设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级 0 级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级 3 级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
NVIC_Init(&NVIC_InitStructure); //④初始化 NVIC 寄存器
TIM_Cmd(TIM3, ENABLE); //⑤使能 TIM3
}
//定时器 3 中断服务程序⑥
void TIM3_IRQHandler(void) //TIM3 中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查 TIM3 更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除 TIM3 更新中断标志
LED1=!LED1;
}
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
TIM3_Int_Init(4999,7199); //10Khz 的计数频率,计数到 5000 为 500ms
while(1)
{
LED0=!LED0;
delay_ms(200);
}
}
第四讲 PWM 输出实验 输入捕获实验
- 1.PWM库函数配置
- 2.PWM输出实验
- 3.输入捕获库函数配置
- 4.输入捕获实验
1.PWM库函数配置
PWM输出库函数
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
typedef struct
{
uint16_t TIM_OCMode; //PWM模式1或者模式2
uint16_t TIM_OutputState; //输出使能 OR失能
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse; //比较值,写CCRx
uint16_t TIM_OCPolarity; //比较输出极性
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure. TIM_Pulse=100;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
设置比较值函数:
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
使能输出比较预装载:
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
使能自动重装载的预装载寄存器允许位:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
2.PWM输出实验
PWM输出配置步骤:
1.使能定时器3和相关IO口时钟。
使能定时器3时钟:RCC_APB1PeriphClockCmd();
使能GPIOB时钟:RCC_APB2PeriphClockCmd();
2.初始化IO口为复用功能输出。函数:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
3.这里我们是要把PB5用作定时器的PWM输出引脚,所以要重映射配置,
所以需要开启AFIO时钟。同时设置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
4.初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
5.初始化输出比较参数:TIM_OC2Init();
6.使能预装载寄存器: TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
7.使能定时器。TIM_Cmd();
8.不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare2();
使用定时器3的PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED【PB5]亮度由暗变亮,又从亮变暗,如此循环。
//TIM3 PWM 部分初始化
//PWM 输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //①使能定时器 3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
RCC_APB2Periph_AFIO, ENABLE); //①使能 GPIO 和 AFIO 复用功能时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //②重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出 TIM3 CH2 的 PWM 脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //①初始化 GPIO
//初始化 TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在自动重装载周期值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //③初始化 TIMx
//初始化 TIM3 Channel2 PWM 模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择 PWM 模式 2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //④初始化外设 TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_Cmd(TIM3, ENABLE); //⑤使能 TIM3
}
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
TIM3_PWM_Init(899,0); //不分频,PWM 频率=72000/900=80Khz
while(1)
{
delay_ms(10);
if(dir)led0pwmval++;
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM_SetCompare2(TIM3,led0pwmval);
}
}
3.输入捕获库函数配置
输入捕获通道初始化函数:
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
typedef struct
{
uint16_t TIM_Channel; //捕获通道1-4
uint16_t TIM_ICPolarity; //捕获极性
uint16_t TIM_ICSelection; //映射关系
uint16_t TIM_ICPrescaler; //分频系数
uint16_t TIM_ICFilter; //滤波器
} TIM_ICInitTypeDef;
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
通道极性设置独立函数:
void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
获取通道捕获值
uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx);
4.输入捕获实验
输入捕获的一般配置步骤:
1.初始化定时器和通道对应IO的时钟。
2.初始化IO口,模式为输入:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
3.初始化定时器ARR,PSC
TIM_TimeBaseInit();
4.初始化输入捕获通道
TIM_ICInit();
5.如果要开启捕获中断,
TIM_ITConfig();
NVIC_Init();
6.使能定时器:TIM_Cmd();
7.编写中断服务函数:TIMx_IRQHandler();
extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
int main(void)
{
u32 temp=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
TIM3_PWM_Init(899,0); //不分频。PWM 频率=72000/(899+1)=80Khz
TIM5_Cap_Init(0XFFFF,72-1); //以 1Mhz 的频率计数
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3)+1);
if(TIM_GetCapture2(TIM3)==300)
TIM_SetCompare2(TIM3,0);
if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
{
temp=TIM5CH1_CAPTURE_STA&0X3F;
temp*=65536;//溢出时间总和
temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
printf("HIGH:%d us\r\n",temp); //打印总的高点平时间
TIM5CH1_CAPTURE_STA=0; //开启下一次捕获
}
}
}