1.0 中断的概念
中断:简单来说就是打断的意思,在计算机系统中CPU在执行一个操作的时候,有一个比当前任务更为紧急的任务需要执行,cpu暂停当前任务转而去执行更为紧急任务的操作,执行完更为紧急任务之后再返回来执行原来未执行完的任务:这里还涉及到任务切换的概念【以上是对于中断的理解】
2.0 中断的硬件结构
以上的结构大概是GD32单片机有7个端口,每一个端口大概有8个引脚,【同一时间只能有一个引脚接入到中断引脚选择】,中断引脚选择中可以选择触发中断的模式,有上升沿触发,下降沿触发选择完成后设置中断标志位,今个NVIC嵌入式中断向量控制器,然后进入对应的片上外设。
3.0 中断优先级
中断的优先级分为4组,每一组设置有不同的抢占式优先级和响应式优先级,可以参考以下的例子作为了解。
CPU 正在执行任务A,这个时候任务B来了 ,假设目前任务A的抢占式优先级是2 响应式优先级是3,任务B的抢占式优先级是1,响应式优先级是3 , 这是时候,GPU或展停任务A的执行转而去执行任务B的任务B任务执行完毕之后再去执行A任务。
第一种场景
抢占式优先级:如下图所示,任务a的抢占式优先级是3,响应式优先级是1,任务B的抢占式优先级是2,响应式优先级是2,然后任务B会打断抢占任务A的先执行
以下可以类比得出结果
4.0 代码实现
相关库函数参考
项目架构
中断配置步骤
初始化GPIO时钟
以上初始化GPIO的时钟使用定义结构体的方式进行初始化,使用结构体更方便后续程序的移植与使用,也可以选择不使用结构体初始化的方式。
中断EXTI初始化
使用结构体的方式初始化中断,在使用NVIC嵌套中断向量控制器使能中断的时候,首先要清除指定的中断线路,否则可能会出现程序频繁的进入中断与中断卡死的情况。
中断函数编写
以上主要使用到3个中断线,第一个是EXTI_0的中断,第二个是EXTI_13的中断,第三个是EXTI_14的中断。
KEY.C程序源码
#include "gd32f30x.h" // Device header
#include <stdint.h>
#include "LED.h"// 创建结构体数组
typedef struct{rcu_periph_enum rcu;uint32_t gpio;uint32_t mode;uint32_t speed;uint32_t pin;
}Exti_Gpio_t;static Exti_Gpio_t Exti_List[] = {{RCU_GPIOA,GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_10MHZ,GPIO_PIN_0},{RCU_GPIOG,GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_10MHZ,GPIO_PIN_13},{RCU_GPIOG,GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_10MHZ,GPIO_PIN_14},
};#define Max_List_Exti sizeof(Exti_List)/sizeof(Exti_List[0])static void GPIO_Init(void){uint16_t i = 0;for(i = 0; i < Max_List_Exti; i++){rcu_periph_clock_enable(Exti_List[i].rcu);gpio_init(Exti_List[i].gpio,Exti_List[i].mode,Exti_List[i].speed,Exti_List[i].pin);}
}static void EXTI_Init(void){// 开启中断时钟rcu_periph_clock_enable(RCU_AF);// 开启中断引脚选择gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA,GPIO_PIN_SOURCE_0);// 中断引脚初始化exti_init(EXTI_0,EXTI_INTERRUPT,EXTI_TRIG_FALLING);// 清除中断标志位,如果不清除单片机上电后会立即进入中断exti_interrupt_flag_clear(EXTI_0);// 使能中断,中断引脚选择,抢占式优先级,响应式优先级nvic_irq_enable(EXTI0_IRQn,1,1);gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG,GPIO_PIN_SOURCE_13);exti_init(EXTI_13,EXTI_INTERRUPT,EXTI_TRIG_FALLING);exti_interrupt_flag_clear(EXTI_13);gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG,GPIO_PIN_SOURCE_14);exti_init(EXTI_14,EXTI_INTERRUPT,EXTI_TRIG_FALLING);exti_interrupt_flag_clear(EXTI_4);nvic_irq_enable(EXTI10_15_IRQn, 0, 1);}void KeyDrvInit(void){GPIO_Init();EXTI_Init();
}void EXTI0_IRQHandler(void){// 获取中断标志位if(exti_interrupt_flag_get(EXTI_0) != RESET){Toggle_Led_Turn(LED1);// 清除中断线路,防止该中断被反复的处理exti_interrupt_flag_clear(EXTI_0);while(1);}
}void EXTI10_15_IRQHandler(void){if(exti_interrupt_flag_get(EXTI_13) != RESET){Toggle_Led_Turn(LED2);exti_interrupt_flag_clear(EXTI_13);}if(exti_interrupt_flag_get(EXTI_14) != RESET){Toggle_Led_Turn(LED3);exti_interrupt_flag_clear(EXTI_14);}
}
KEY.H程序源码
#ifndef __KEY_H_
#define __KEY_H_
#include <stdint.h>void KeyDrvInit(void);#endif
LED.C 程序源码
#include "gd32f30x.h" // Device header
#include "Delay.h"
#include <stdint.h>// 初始化结构体结构体数据类型
typedef struct Led_gpio_t{rcu_periph_enum rcu;uint32_t gpio;uint32_t pin;
}LED_GPIO_T;// 定义一个静态全局变量保存GPIO口的资源信息
static LED_GPIO_T Gpio_List[] = {{RCU_GPIOA,GPIOA,GPIO_PIN_8},{RCU_GPIOE,GPIOE,GPIO_PIN_6},{RCU_GPIOF,GPIOF,GPIO_PIN_6}
};// 宏定义确定数组的大小
#define LED_NUM_MAX (sizeof(Gpio_List) / sizeof(Gpio_List[0]))void LED_Init_Drive(void){for(uint8_t i = 0; i < LED_NUM_MAX; i++){// 开启GPIO时钟rcu_periph_clock_enable(Gpio_List[i].rcu);// 初始化GPIOgpio_init(Gpio_List[i].gpio,GPIO_MODE_OUT_PP,GPIO_OSPEED_10MHZ,Gpio_List[i].pin);// GPIO 初始化调用的方式gpio_bit_reset(Gpio_List[i].gpio,Gpio_List[i].pin);}
}void Turn_LedOn(uint8_t LedNo){if(LedNo >= LED_NUM_MAX){return;}else{gpio_bit_set(Gpio_List[LedNo].gpio,Gpio_List[LedNo].pin);}}void Turn_OffLed(uint8_t LedOff){if(LedOff >= LED_NUM_MAX){return;}else{gpio_bit_reset(Gpio_List[LedOff].gpio,Gpio_List[LedOff].pin);}
}// 进入中断函数之后实现LED翻转功能
void Toggle_Led_Turn(uint8_t LedToggle){// 设置中断标志位FlagStatus bit_state;// 获取输出数据寄存器的值bit_state = gpio_input_bit_get(Gpio_List[LedToggle].gpio,Gpio_List[LedToggle].pin);// 取反功能bit_state = (FlagStatus)(1 - bit_state);// 写入数据寄存器的值gpio_bit_write(Gpio_List[LedToggle].gpio,Gpio_List[LedToggle].pin,bit_state);}/*注:下面的这段代码可以忽略
*/// 初始化LED灯
void LED_Init(void){// 使能RCU时钟rcu_periph_clock_enable(RCU_GPIOA);// 配置引脚输出频率gpio_init( GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_8);// 初始化GPIOE的引脚rcu_periph_clock_enable(RCU_GPIOE);// 配置引脚输出频率gpio_init( GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_6);// 初始化GPIOE的引脚rcu_periph_clock_enable(RCU_GPIOF);// 配置引脚输出频率gpio_init( GPIOF, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_6);
}// 实现循环流水灯的功能
void LED_Cycle(void){DelayInit();while(1){gpio_bit_set(GPIOA, GPIO_PIN_8); DelayNms(1000);gpio_bit_reset(GPIOA, GPIO_PIN_8);DelayNms(1000);gpio_bit_set(GPIOE, GPIO_PIN_6); DelayNms(1000);gpio_bit_reset(GPIOE, GPIO_PIN_6);DelayNms(1000);gpio_bit_set(GPIOF, GPIO_PIN_6); DelayNms(1000);gpio_bit_reset(GPIOF, GPIO_PIN_6);DelayNms(1000);}}
LED.H程序源码
#ifndef _LED_H_
#define _LED_H_
#include <stdint.h>//宏定义LED灯的引脚
#define LED1 0
#define LED2 1
#define LED3 2void LED_Init(void);void LED_Cycle(void);void LED_Init_Drive(void);void Turn_LedOn(uint8_t LedNo);void Turn_OffLed(uint8_t LedOff);void Toggle_Led_Turn(uint8_t LedToggle);#endif
MAIN.C程序源码
#include <stdio.h>
#include "gd32f30x.h"
#include "Delay.h"
#include "LED.h"
#include "Key.h"int main(void)
{ // 初始化LEDLED_Init();KeyDrvInit();while(1){} }
注:以上程序实现的效果是由KEY1,KEY2,KEY3,控制三个LED灯分别为LED1,LED2,LED3当第一个按键按下时LED1点亮,同时控制LED1的按键优先级低于控制KEY2,KEY3按键的优先级这个时候LED1灯会被抢占,也就是点亮LED1后可能无法再次被熄灭。【抢占式优先级】。
5.0 STM32类比学习
.....