关于 STM32F103 EXTI 中断以及 SysTick 定时器详解
中断是什么?这个就不用在解释了吧,如果有不理解中断的,可以看看小编以前的博客。
EXTI 中断 / 事件原理
EXTI 是外部中断 / 事件的控制器,什么是中断,我想在这儿就没有必要过多叙述了吧。
在这儿,我就详细的讲解在 STM32 中外部中断 EXTI 到原理以及使用固件库如何去配置 EXTI 外部中断。
首先,我们可以大致了解一下 STM32 有哪些中断。
可以看到, STM32 的中断是非常多的,除了三个固定以外,其他中断都是可以编程的, EXTI ——外部中断甚至有 16 个之多(在内部类似的信号线路有 20 个, EXTI 总共有 20 个,可以从图中看到有些连到 EXTI 的其他中断,例如 USB 唤醒。),下面我就来详细的讲讲关于 EXTI 的工作原理。
由图可以看到,整个过程分为两个过程,一个是产生中断,一个是产生事件。
这个是外部中断的输入线,这些输入线可以通过寄存器来设置成任何一个 GPIO ,这儿可以用来实现电平变化。、
然后通过寄存器来选择触发信号,如果检测到电平跳变信号就会传递信息给后面一个有效信号 1 。通过 RTSR 和 FTSR 寄存器来配置。
这儿可以看到一个或门,在或门作用下只需要有一个信号为 1 就输出 1 ,而这儿有两个输入,一个是刚刚讲到的边沿电路检测到触发,另一条线就是软件中断事件寄存器如果检测到了,同样触发这个信号,而请求挂起寄存器会将这个·输出信号保留。
当信号检测到就可以触发中断了吗?当然不行,还有一个屏蔽寄存器,这个寄存器的作用就是检测你有没有使用 中断寄存器,这儿可以看到一个与门,当检测到信号的同时打开了中断允许才会进入中断( NVIC 中断控制器。)。 PR 寄存器,用来保存状态。 IMR 寄存器,可以打开或屏蔽中断请求。
同样,在有信号触发的同时,它有两条路可以走,一条就是刚刚讲到的 NVIC 中断,另一条路就是事件,同样有一个事件屏蔽寄存器,需要我们根据需要来选择开启关闭。当打开事件,会触发一个脉冲信号,这个脉冲信号就是事件的最终产物,这个脉冲信号可以用来给外电路使用,例如:TIM 、ADC 等等。 EMR 寄存器,用来开启或屏蔽事件。
以上就是中断的原理了。
不过产生中断的目的是用来把信号输入进 NVIC ,从而运行中断服务函数。
当中断触发配置好了中断触发模式,对于触发了中断后会执行 NVIC 的函数, NVIC 又应该怎么去配置呢?
IPRx 寄存器,用于配置优先级,只启用了高四位。
一共有 5 种配置方法,分为了主优先级与子优先级。
在固件库中,对于 NVIC 不仅要配置优先级,还要配置中断源,在 stm32f10x 头文件中有关于这个的定义。
里面有不同的型号的中断源,在这儿我们要准确的找到我们使用的型号,对于中断函数的名称,也是有要求的,我们可以去启动文件中找。
中断配置函数
这儿就是所有 EXTI 中断了。接下来我们来尝试着配置一个外部中断服务函数。
上图是以下会用到的AFIO寄存器,它是关于GPIO的重映射的。
下面代码需要包含 misc.h 头文件,NVIC 初始化结构体在这个头文件里面。
static void NVIC_Configuration(void) //staic修饰,表示函数只能在此文件使用。
{
NVIC_InitTypeDef NVIC_InitStructure; //设置NVIC的初始化结构体。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //因为NVIC的优先级有5组,这儿可以设置是哪种分组。
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //如果使用的是GPIO0引脚就用这儿,这儿注意,不是一个引脚对应一个IRQn,有些是多个引脚对应一个IRQn。
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //配置主优先级。
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //配置子优先级。
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断。
NVIC_Init(&NVIC_InitStructure); //初始化NVIC结构体。
}
void EXTI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体
EXTI_InitTypeDef EXTI_InitStructure; //EXTI结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE); //开启GPIO口的时钟和AFIO的时钟
NVIC_Configuration(); //将NVIC初始化函数加入。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //配置输入引脚。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //配置输入模式为浮空输入。
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure); //初始化GPIO,这儿因为是输入,所以不需要配置速度。
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择EXTI的信号源。
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //设置EXTI为中断模式。
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //设置为上升沿触发。
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断。
EXTI_Init(&EXTI_InitStructure); //初始化exti结构体。
}
此上,我们的中断已经配置好了,接下来我们可以去写中断函数了。
STM32 使用固件库的中断函数全部放在 stm32f10x_it.c 里面,所以我们需要打开这个文件。
可以看到里面已经有一些中断的空函数了,不过关于 EXTI 的函数是需要我们自己写,函数名也是有要求的,我们需要严格按照他的要求来。
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) //确保是否产生了EXTI Line中断
{
//但是,不知道你们有没有发现,如果是当GPIOA的0引脚和GPIOB的0引脚同时出发中断,是会进入到同一个中断函数,那我们应该怎样来使他们区分开呢?
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1) //判断是否是GPIOA的中断,这个函数可以读取GPIO的输入状态。
{
//这儿就可以有用户自行编写代码了。
}
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE); //清除中断标志位。
}
}
这就是代码注释中提到的状态读取寄存器。
记住一个特别重要的点,就是记得把初始化头文件加进中断函数文件里面。
SysTick 定时器以及定时器中断
对于 SysTick 定时器是内嵌在 NVIC 中,是一个 24 位的定时器,每计数一次的时间为 1/系统时钟,一般来说,默认是 72M 当计数器的数减为 0 时,就产生一次中断。我们可以改变时钟来改变计数一次的时间,小编以前的博客有讲到如何配置系统时钟。
一共只有四个寄存器。
只需要配置这四个寄存器就可以使用时钟中断或者计时了。需要包含 misc.h 和 core_cm3.h 头文件。
void SysTick_Init(void)
{
if (SysTick_Config(SystemCoreClock / 100000)) //10us中断一次。如果是SystemFrequency / 1000则是1ms中断一次。SystemCoreClock在system_stm32f10x.c里面,含义是系统时钟频率。SysTick_Config函数在core_cm3.h里,作用是启动定时器。
//注意这儿的时间是在未设置重装载值的时候的计数时间。
{
while (1); //若是开启失败,返回1,就会进入死循环。
}
}
//中断函数
void SysTick_Handler(void)
{
while(!((SysTick->CTRL)&(1<<16))); //当计数器值减小为0时,此为会置一。
SysTick->LOAD = 0x1111 //设置计数器初值为0x1111开始自减。
//这儿自行添加中断程序。
}
void main(void)
{
uint32_t Timer;
SysTick->LOAD = 0x1111 //设置计数器初值为0x1111开始自减。
while (1)
{
Timer = Systick->VAL; //蒋当前的时钟计数值读取出来。
}
}
到此为止,关于 SysTick 的配置和使用就到此结束了,谢谢大家的耐心观看,如果觉得小编写得不错的,关注点个赞再走吧,嘿嘿。