前言:本文章部分代码参考自野火的例程,优先级部分参考自Julius_world
本人使用的是野火家的指南者开发板,芯片型号是STM32f103VET6
分外部中断、串口和系统滴答定时器中断 三部分
串口部分在本栏目的另外一篇会重点讲解,此次只讲串口与EXTI类似的中断部分
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
本篇源代码在这里
1 NVIC
1.1 NVIC是什么
内嵌向量中断控制器Nested Vectored Interrupt Controller (NVIC)
1.2 中断优先级配置的函数
NVIC_IRQChannelPreemptionPriority 配置抢占优先级
NVIC_IRQChannelSubPriority 配置响应优先级
1.3 抢占优先级与子优先级的描述
在CM3内核之外,STM32定义了自己配置优先级,即先分组,再配置抢占优先级和子优先级
抢占优先级不同,会涉及到中断嵌套,抢占优先级高的会优先抢占优先级低的,优先得到执行。(注意:优先级数字越小,优先级越高)
抢占优先级相同,不涉及到中断嵌套,子优先级不同,子优先级高的先响应。(例如:两个中断同时响应,这里就会先执行子优先级高的那个中断)
抢占优先级和子优先级都相同,则比较它们的硬件中断编号,中断编号越小,优先级越高。(硬件中断编号从中断向量表当中查看)
NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级,IPR 宽度为 8bit 但是STM32只用了4位来表达优先级,组别优先顺序(第0组优先级最强,第4组优先级最弱)
/* 摘自stm32f10x.h */
typedef enum IRQn {
//Cortex-M3 处理器异常编号
NonMaskableInt_IRQn = -14,
MemoryManagement_IRQn = -12,
BusFault_IRQn = -11,
UsageFault_IRQn = -10,
SVCall_IRQn = -5,
DebugMonitor_IRQn = -4,
PendSV_IRQn = -2,
SysTick_IRQn = -1,
//STM32 外部中断编号
WWDG_IRQn = 0,
PVD_IRQn = 1,
TAMP_STAMP_IRQn = 2,
// 限于篇幅,中间部分代码省略,具体的可查看库文件 stm32f10x.h
DMA2_Channel2_IRQn = 57,
DMA2_Channel3_IRQn = 58,
DMA2_Channel4_5_IRQn = 59
} IRQn_Type;
1.4 NVIC相关函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
1.5 NVIC初始化结构体
/* misc.h */
typedef struct{
uint8_t NVIC_IRQChannel;
uint8_t NVIC_IRQChannelPreemptionPriority;
uint8_t NVIC_IRQChannelSubPriority;
FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;
2 EXTI
2.1 EXTI是什么
EXTI(External interrupt/event controller)—外部中断/事件控制器
产生中断线路的目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是 软件级的
而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号 传输,属于硬件级的。
2.2 EXTI初始化结构体
/* stm32f10x_exti.h */
typedef struct{
uint32_t EXTI_Line;
EXTIMode_TypeDef EXTI_Mode;
EXTITrigger_TypeDef EXTI_Trigger;
FunctionalState EXTI_LineCmd;
}EXTI_InitTypeDef;
3 用按键的电平跳变来产生中断
3.1 硬件配置
3.2 NVIC初始化
选择EXTI0_IRQn为NVIC的一个中断源,并配置优先级分组、抢占优先级、子优先级
void NVIC_Configuration(void){
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
3.3 配置按键的GPIO口,用按键的电平跳变来产生中断
void KEY_GPIO_Config(void){
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
}
3.4 EXTI初始化
void EXTI_Config(void){
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
3.5 LED灯的GPIO口初始化
void LED_GPIO_Config(void){
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
3.6 编写中断服务函数
在这里编写
#include "stm32f10x_it.h"
#include "bsp_exti.h"
//省略
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
GPIOB->ODR ^=0x01;
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
3.7 main
#include "stm32f10x.h"
#include "bsp_exti.h"
int main(void)
{
NVIC_Configuration();
KEY_GPIO_Config();
EXTI_Config();
LED_GPIO_Config();
while(1);
}
3.8 实验现象
按下KEY1(PA0输出高电平),LED灯(PB0)会反转
4 串口发送完成产生中断
代码部分,NVIC初始化、LED灯的GPIO口初始化、main与上一小节类似,不再赘述
这里只举TXNE(接受寄存器非空)中断这个例子,其他中断事件类似
4.1 硬件配置
4.2 USART初始化结构体
typedef struct {
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字长
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校验位
uint16_t USART_Mode; // USART 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
4.3 串口配置
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 打开串口外设的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置串口的工作参数
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 串口中断优先级配置
NVIC_Configuration();
// 使能串口接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 使能串口
USART_Cmd(USART1, ENABLE);
}
4.4 编写中断服务函数
void USART1_IRQHandler(void){
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET) {
GPIOB->ODR ^=0x01;
USART_ClearFlag(USART1,USART_FLAG_RXNE);
}
}
4.5 通过MATLAB向stm32发送数据
stm32Serialport = serialport("COM5",115200,"Timeout",5)
write(stm32Serialport,1,"uint8");
delete(stm32Serialport)%注销系统之前已经打开的串口资源
4.6 实验现象
通过MATLAB向STM32发送数据,每发一次,灯反转一次
5 软件配置sysTick产生定时中断
5.1 SysTick寄存器
控制及状态寄存器 SysTick->CTRL
重装载值寄存器 SysTick->LOAD
当前值寄存器 SysTick->VAL
校准值寄存器 SysTick->CALIB
5.2 SysTick配置
// 这个 固件库函数 在 core_cm3.h中,只需调用即可
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
// reload 寄存器为24bit,最大值为2^24
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
// 配置 reload 寄存器的初始值
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
// 配置中断优先级为 1<<4-1 = 15,优先级为最低
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
// 配置 counter 计数器的值
SysTick->VAL = 0;
// 配置systick 的时钟为 72M
// 使能中断
// 使能systick
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
5.3 编写中断服务函数
void SysTick_Handler(void){
static uint16_t SysMsCount = 0;
//每 1000ms 翻转一次 GPIOB GPIO_Pin_0 电平
if(SysMsCount++ >= 1000){
SysMsCount = 0;
//1000ms 翻转一次电平
GPIOB->ODR ^= ((uint32_t)0x01 << 0);
}
}
5.4 main函数
#include "stm32f10x.h"
int main(void)
{
//LED_GPIO_Config
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*
uint32_t SystemCoreClock= SYSCLK_FREQ_72MHz;
配置 reload 寄存器的初始值为72M/1000 即每ms产生一次中断
*/
SysTick_Config(SystemCoreClock/1000);
while(1);
}
5.5 实验现象
LED灯每隔1秒会反转一次,即一个闪烁周期为2秒
6 总结
中断配置,其实挺简单的
分为系统中断和外部中断
系统中断直接调用内核固件库函数配置中断优先级等(当然,core_cm3.h的函数也可以改),外部中断需要用户自己写NVIC_Configuration()函数,再去使能相应的中断
最后,不管是系统中断还是外部中断,都要在stm32f10x_it.h里写中断服务函数