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);
// 初始化GPIO
gpio_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 2
void 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)
{
// 初始化LED
LED_Init();
KeyDrvInit();
while(1){
}
}
注:以上程序实现的效果是由KEY1,KEY2,KEY3,控制三个LED灯分别为LED1,LED2,LED3当第一个按键按下时LED1点亮,同时控制LED1的按键优先级低于控制KEY2,KEY3按键的优先级这个时候LED1灯会被抢占,也就是点亮LED1后可能无法再次被熄灭。【抢占式优先级】。
下面的内容是关于STM32的可以不看【------用于类比学习---------】
5.0 STM32外部中断
初始化外部中断的步骤
1: RCC 开启外部中断的时钟【中断引脚选择时钟AFIO选择哪一组引脚进入中断】
2: RCC 开启GPIO时钟
3:EXTI 中断引脚选择与中断的触发方式
4:NVIC 嵌套中断向量控制器【配置中断优先级分组和中断的优先级同时使能中断】
5:编写中断函数执行中断操作
中断按照这个步骤一一打通即可
外部中断实现旋转编码器计算次数的功能
CountSensor.c 代码
#include "stm32f10x.h" // Device header
#include <stdint.h>
uint16_t g_CountSensor_Count;
/*
*****************************************
* @brief : 初始化旋转编码器计算次数,使能rcc时钟
* @param : 无参数
* @param : 无参数
* @retval: 无返回值
*****************************************
*/
void CountSensor_Init(void)
{
// 时钟初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// GPIO初始化
GPIO_InitTypeDef GPIO_InitStructre;
GPIO_InitStructre.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructre.GPIO_Speed = GPIO_Pin_14;
GPIO_InitStructre.GPIO_Pin = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructre);
// 中断引脚选择与配置方式
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
// 使能中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;
NVIC_Init(&NVIC_InitStructure);
}
uint16_t CountSensor_Cet(void)
{
return g_CountSensor_Count;
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14) == 0)
{
g_CountSensor_Count ++;
}
// 进一次中断清除一次中断标志位
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
CountSensor.h
#ifndef __COUNTSENSOR_H_
#define __COUNTSENSOR_H_
#include <stdint.h>
void CountSensor_Init(void);
uint16_t CountSensor_Cet(void);
#endif
MAIN.C
#include "stm32f10x.h"
#include <stdint.h>
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
#include "CountSensor.h"
#include "OLED.h"
int main(void)
{
OLED_Init();
CountSensor_Init();
/*显示静态字符串*/
OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:
while (1)
{
OLED_ShowNum(1, 7, CountSensor_Cet(), 5);
}
}
OLED.H文件
#include "stm32f10x.h"
#include "OLED_Font.h"
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}
OLED.H
#ifndef __OLED_H
#define __OLED_H
#include <stdint.h>
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
#endif