EXTI中断以及系统滴答定时器SysTick的配置和使用

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

前言

EXTI中断来判断按键按下

EXTI即外部中断/事件控制器总共支持19个中断/事件请求。每一条中断线都有独立的使能和产生中断后的标志位。
在这里插入图片描述
上图可见中断/时间线0-15总共16条线分配给了IO通过设置AFIO的AFIO_EXTICR1、AFIO_EXTICR2、AFIO_EXTICR3、AFIO_EXTICR4这四个寄存器来配置要选择哪一组pin作为外部中断输入比如 AFIO_EXTICR1这个寄存器里面分为了
在这里插入图片描述
在这里插入图片描述
意思是如果我想设置为PA0那就把 AFIO_EXTICR1 的0-3位设置为0000 如果想要设置PC3那就设置AFIO_EXTICR1 的12-15位 为0010以此类推每个AFIO_EXTICRx寄存器控制的是4组PinAFIO_EXTICR1控制的是Pin0-Pin3AFIO_EXTICR2控制的是Pin4-Pin7AFIO_EXTICR3控制的是Pin8-P11AFIO_EXTICR4控制的是Pin12-Pin15 。需要设置不同的Port就是根据当前要操作的Pin找到需要操作哪个AFIO_EXTICRx寄存器然后根据需要设置的Port即PA或PB或PC等…把相应EXTIx的值填好。

写到这里心里面产生一个疑问比如我只需要设置PA0为EXTI0输入线的输入信号那么在配置的时候其他输入线应该也是有初值在的。后面应该是需要通过关闭不需要的输入信号的中断来实现只响应PA0的变化中断。

我这边新建了一个bsp_exti.c文件内容如下

#include "bsp_exti.h"

static void EXTI_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStruct;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

    NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStruct);
}

void exti_key_gpio_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    EXTI_InitTypeDef EXTI_InitStruct;

    //配置中断优先级
    EXTI_NVIC_Config();
    
    RCC_APB2PeriphClockCmd(GPIO_KEY_RCC,ENABLE);

    GPIO_InitStruct.GPIO_Pin = EXTI_GPIO_PIN;
    GPIO_InitStruct.GPIO_Mode = EXTI_GPIO_MODE;

    GPIO_Init(EXTI_GPIO,&GPIO_InitStruct);

    //初始化EXTI
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);                //GPIO用作EXTI中断,必须打开AFIO时钟
    GPIO_EXTILineConfig(EXTI_SOURCE,EXTI_PIN_SOURCE);                  //选择的输入线为 GPIOA.Pin15

    EXTI_InitStruct.EXTI_Line = EXTI_LINE;
    EXTI_InitStruct.EXTI_Mode = EXTI_MODE;
    EXTI_InitStruct.EXTI_Trigger = EXTI_TRI;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;

    EXTI_Init(&EXTI_InitStruct);
}

头文件如下

#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H

#include "stm32f10x.h"

#define     GPIO_KEY_RCC	RCC_APB2Periph_GPIOA
#define     EXTI_GPIO       GPIOA
#define     EXTI_GPIO_PIN   GPIO_Pin_15
#define     EXTI_GPIO_MODE  GPIO_Mode_IPU

#define     EXTI_SOURCE     GPIO_PortSourceGPIOA
#define     EXTI_PIN_SOURCE GPIO_PinSource15

#define     EXTI_LINE       EXTI_Line15
#define     EXTI_MODE       EXTI_Mode_Interrupt
#define     EXTI_TRI        EXTI_Trigger_Falling

void exti_key_gpio_init(void);


#endif

EXTI_NVIC_Config()这个函数负责配置中断优先级因为这个函数是专门新建给EXTI用的所以在EXTI的c文件里面静态声明一下不允许其他地方调用。
首先就是像配置IO一样声明一个结构体变量用于传参进去NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)函数这个函数的参就是NVIC_InitTypeDef类型的结构体的地址所以新建一个结构体变量名字为NVIC_InitStruct。配置响应的参数给这个结构体变量。

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); 这个函数是用于给中断分组在NVIC_IPRx这个中断优先级寄存器中高四位代表主优先级和子优先级但是4个位中哪些位是代表主优先级哪些位代表子优先级这个又是通过分组来控制的。如果选择的分组是0那么NVIC_IPRx的高四位中四位全是代表子优先级主优先级默认是0所以别的中断要跟EXTI中断比较的话只有主优先级为0且子优先级的数字比EXTI的子优先级数字小的才能产生嵌套中断主优先级和子优先级都相同的情况下比较两个中断的序号序号越前的优先级越高但是这种情况几率很小这里不讨论。

这里我配置的NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 即给EXTI中断分组到分组1去那么NVIC_IPRx寄存器的高四位中的最高位即第七位为0或为1控制的是EXTI的主优先级为0或者1NVIC_IPRx的第4-6位控制是子优先级子优先级有8级 (0-7)。

NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; 这个配置的是EXTI产生的中断通道是 EXTI10-15通道(因为我用的按键是PA15所以我选择的输入源就是PA15输入线即为EXTI15库函数规定输入线为10-15时NVIC_IRQChannel的值要设置为EXTI15_10_IRQn)

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
这两句则是设置主优先级为1 子优先级为1

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
开对应Channel的NVIC中断

然后再调用初始化函数 把结构体变量的地址作为形参传进去NVIC_Init(&NVIC_InitStruct);

NVIC初始化函数就写好了接下来就是初始化按键 初始化EXTI并且调用EXTI_NVIC_Config();函数配置好EXTI中断的优先级和通道并开启中断响应……

  • 第一步–配置按键。先开按键所处的Port的所处的APB时钟线时钟。然后配置相应的Pin为输入上拉模式最后调用GPIO_Init(EXTI_GPIO,&GPIO_InitStruct);函数初始化相应的Pin。

  • 第二步–开始初始化EXTI

    首先配置时钟。调用RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE)开启时钟因为选择IO作为EXTI输入需要用到AFIO所以这里开时钟开的是AFIO的时钟。

    GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

    这个函数第一个参数GPIO_PortSource: selects the GPIO port to be used as source for EXTI lines.This parameter can be GPIO_PortSourceGPIOx where x can be (A…G).
    也就是说这里需要填选择哪一个Port作为EXTI输入

    第二个参数GPIO_PinSource: specifies the EXTI line to be configured.This parameter can be GPIO_PinSourcex where x can be (0…15).
    也就是这里要填哪一个Pin作为EXTI输入

    其实这个GPIO_EXTILineConfig()函数就是根据我们传入的参判断之后帮我们操作了AFIO_EXTICRx寄存器。

  • 第三步–配置EXTI结构体变量调用初始化EXTI函数
    EXTI_Line 选择是哪一天EXTI输入线我这里是PA15所以这里要填EXTI_Line15
    EXTI_Mode选择输入信号产生符合要求的变化之后是发生中断还是事件
    EXTI_Trigger选择输入信号发生什么动作会产生中断/事件可以选择上升沿、下降沿以及双边沿
    EXTI_LineCmd选择是否使能EXTI产生中断/事件
    调用EXTI_Init(&EXTI_InitStruct);即可初始化EXTI中断。

在主函数中初始化EXTI按键中断

unsigned char i=0;

int main(void)
{
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	
	led_gpio_init();
	
	exti_key_gpio_init();
	
	while(1)
	{

	}
}

在中断服务函数的.c文件中

void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line15) != RESET)          //确定产生了中断
    {

        GPIOA->ODR ^= GPIO_Pin_8;

    }
    EXTI_ClearITPendingBit(EXTI_Line15);
}

通过调用EXTI_GetITStatus();得到返回值来判断相应的Pin是否产生中断来判断按键是否按下。
EXTI_GetITStatus()这个函数其实就是根据我们传入的参(我们选择的EXTI线-EXTI15)。
首先判断一下我们是否打开了EXTI14这条EXTI线的中断主要就是判断EXTI_IMR这个寄存器的对应位这个寄存器的0-19位代表了EXTI0-19位是否使能中断。如果设置为符合要求就产生事件那么这函数判断的是EXTI_EMR这个寄存器的0-19位。

然后再根据 挂起寄存器(EXTI_PR)的0-19位来判断相应的EXTI线是否产生了触发请求。

最后判断一下 如果当前的EXTI线允许中断/事件且挂起寄存器里面的对应位被置1即产生了触发请求。两者同时成立那么就会产生中断/事件我这里配置的是产生中断所以当我按下按键之后就会产生一个中断并调用EXTI15_10_IRQHandler这个服务函数。值得注意的是EXTI15_10_IRQHandler()这个函数名不是乱起的跟51单片机的中断入口的序号一样这个名字是写在启动文件里面的如果这个名字没写对那么MCU会调用库里面弱定义的EXTI15_10_IRQHandler()函数内容是while(1)即永远停在这个服务函数里面。

如果是EXTI8即PA8、PB8……作为输入信号那么中断服务函数需要用EXTI9_5_IRQHandler。
那么如果我们PA7、PA9同时作为输入信号按下任意一个按键都能进入EXTI9_5_IRQHandler这个服务函数那么就需要额外判断一下输入源根据GPIO->IDR这个寄存器判断是哪个按键按下了然后再执行相应的操作。

以上是EXTI中断的方法。

滴答定时器产生定时中断

SysTick—系统定时器是属于 CM3 内核中的一个外设内嵌在 NVIC 中。系统定时器是一个 24bit
的向下递减的计数器计数器每计数一次的时间为 1/SYSCLK一般我们设置系统时钟 SYSCLK
等于 72M。当当前数值寄存器的值递减到 0 的时候系统定时器就产生一次中断以此循环往
复。

因为 SysTick 是属于 CM3 内核的外设所以所有基于 CM3 内核的单片机都具有这个系统定时器
使得软件在 CM3 单片机中可以很容易的移植。系统定时器一般用于操作系统用于产生时基维
持操作系统的心跳。

SysTick—系统定时器有 4 个寄存器简要介绍如下。在使用 SysTick 产生定时的时候只需要配
置前三个寄存器最后一个校准寄存器不需要使用。
在这里插入图片描述

因为是内核里面的外设配置的东西比较少赋好值之后使能中断、使能定时器之后就可以产生中断。

我这里是新建了一个bsp_systick.c文件内容如下

#include "bsp_systick.h"


uint32_t delay_time=0;




void systick_init(uint32_t ms)
{
    if(SysTick_Config(ms))              //SysTick_Config(uint32_t ticks)  这个函数回返回一个uint32_t类型的变量 如果为1说明滴答定时器的重载寄存器赋的初值太大 进入报错处理 为0说明正常
    {                                   //uint32_t ticks 这个形参传入的就是为重载寄存器赋的初值 范围是0-2^24(24位寄存器) 
        while (1)                       //函数的作用就是初始化系统的滴答定时器,并且开启中断,时间就按照传入的值来定
        {
            ;
        }
    }
}


void delay_xms(uint32_t xms)
{
    delay_time = xms;

    while (delay_time != 0)
    {
        ;
    } 
}


void delay_time_sub(void)
{
    if(delay_time > 0)
    {
        delay_time--;
    }
}

因为时钟输入是72M那么计数一次的时间就是1/72000000如果我想要计时10ms则是需要填720000。公式是 计数值/频率(Hz)=时间(s)。那么就是计数值=时间x频率=0.01(秒)x72000000Hz=720000次。

所以在main函数初始化时就规定了产生一次系统定时器中断的时间就是10ms如果需要改那么重新改变SysTick_Config()的形参(LOAD 重载寄存器的值)就可以改变产生一次中断的时间间隔。

main函数如下

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_systick.h"

unsigned char i=0;

int main(void)
{
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

	led_gpio_init();
	
	systick_init(720000);		//10ms中断一次
	
	while(1)
	{
		for(i=0;i<2;i++)
		{
			delay_xms(100);
			GPIO_ResetBits(GPIOA,GPIO_Pin_8);
			delay_xms(100);
			GPIO_SetBits(GPIOA,GPIO_Pin_8);
		}
		SysTick -> CTRL &= ~SysTick_CTRL_ENABLE_Msk;
	}
}

执行效果就是 PA8这里的一颗红色LED每隔一秒切换一下状态亮灭两次之后长灭。灯是低电平点亮
是因为我把滴答定时器关掉了所以灯在第二次灭之后就不在点亮了。
如果再想把滴答定时器打开那么就SysTick -> CTRL |= SysTick_CTRL_ENABLE_Msk; 就可以重新打开滴答定时器了

额外说下这个滴答定时器配置函数这个函数是系统写好的传入的参就是重载寄存器的数值直接影响到中断一次的间隔。

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

查了手册
SysTick_CTRL_TICKINT_Msk 这个位代表的是 systick下数到0之后会不会产生异常请求如果这个位为1就会产生异常请求然后进入服务函数。
SysTick_CTRL_ENABLE_Msk 这个位代表的是 是否开启systick定时器

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“EXTI中断以及系统滴答定时器SysTick的配置和使用” 的相关文章