STM32第五课:外部中断
文章目录
需求
1.设备上电后打开串口。
2.按下KEY1,串口打印“按键1触发中断”。
3.以此类推,设置4个按键。
4.其中按键1,2采用寄存器开发,3,4采用库函数开发。
一、外部中断
由图可知,当外部产生信号时,会先进入边沿检测电路,此时我们需要配置是上升沿还是下降沿检测。
配置完成后,该电路会检测是否收到该上升沿/下降沿信号。此时我们还需要配置中断屏蔽寄存器,将其置为1,这样当请求挂起寄存器为1时,就会给NVIC中断寄存器发送一个中断信号。
二、外部中断配置(以按键为例)
1.开时钟
PA0,key1为例,寄存器编写
代码如下:
RCC->APB2ENR |= 0x05;//打开GPIO和AFIO时钟
首先,由手册可知:
通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO时钟。
所以直接找到APB2 外设复位寄存器来开时钟
可见,GPIOA和AFIO的时钟在第2位和第0位(0101)
所以直接将APB2ENR或上0x05即可打开GPIO和AFIO的时钟。
2.配置IO
代码如下:
GPIOA->CRL &=~(0X0F << 0);//PA0 key1
GPIOA->CRL |= 0X04 << 0;
AFIO->EXTICR[0] &= ~(0x0F);//配置GPIOA映射EXTI线
PA0是key1按键,直接无脑先清0,再配置成0100浮空输入即可。
AFIO需要配置外部中断配置寄存器:
其中EXTICR数组分别有四个,从零开始。因为四位配置一个引脚,每组配置4个引脚,总计16个引脚(中断)。
所以配置引脚PA0,就是将EXTICR数组的第0个配置成0000即可。(也可不配置,默认就为0)
3.配置检测模式和屏蔽位
代码如下:
EXTI->RTSR &= ~(0x1); //关闭上升沿检测
EXTI->FTSR |= 0x01; //打开下降沿检测
EXTI->IMR |= 0x01; //打开exti的屏蔽位
根据具体情况选择上升沿还是下降沿,这里选则下降沿触发。
又因为key1是第零位引脚,所以不用移位,直接上升沿最后一位置零关闭,下降沿最后一位或1开启。
屏蔽器:
屏蔽位的开启直接看引脚,第几位引脚就将IMR第几位置1。
4.开NVIC,主函数分组
开NVIC
代码如下:
NVIC_SetPriority(EXTI0_IRQn,2);NVIC设置优先级00 10
NVIC_EnableIRQ(EXTI0_IRQn); //NVIC使能中断通道
这个直接跳转选参数就行,引脚是第几位就是几号line。
NVIC优先级的设置是根据分组来决定的,本次分组是5(两位抢占,两位次级)。
理解;
将设置的2转为2进制(00 10),其中前两位为抢占位,后两位为次级。
主函数分组(该部分写到main函数中while(1)前)
代码如下:
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
三、中断函数
代码如下:
//exti0的中断服务函数
void EXTI0_IRQHandler(void)
{
//判断中断标志是否被置位
if((EXTI->PR&(0x1<<0))!=0){
//如果置位,就清理标志位
EXTI->PR |= 0x1<<0;//写1是清除
printf("按键1触发中断\r\n");
}
}
中断服务函数在启动文件中查找,不用声明。
要先进行PR挂起寄存器判断,为1即为发生触发请求,进入该中断。
写1清除是规定。
四、需求实现
结果显示:
完整代码:
main.c
#include "stm32f10x.h"
#include "key.h"
#include "usart.h"
#include "stdio.h"
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
KEY1_Exti_PA0_init();
KEY2_Exti_PC4_init();
KEY3_Exti_PC5_init();
KEY4_Exti_PC6_init();
while(1)
{
}
}
usart.c
#include "usart.h"
#include "stdio.h"
void Usart1_Config()
{
//开时钟:GPIOA,USART1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);
//配置对应的IO口 PA9(tx):复用推挽 PA10(RX):浮空输入
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置串口1 8数据位,0校验位,1停止位,波特率115200
USART_InitTypeDef USART_InitStruct = {0};//可以通过结构体类型跳转
USART_InitStruct.USART_BaudRate = 115200;//波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件控制流不开
USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;//打开发送和接收
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_InitStruct);
USART_Cmd(USART1,ENABLE);
//配置串口1的中断
//在串口1产生接收的时候,会产生中断,我们直接去中断函数里面处理就可以了
//选择串口1的中断原因
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//USART1->CR1 |= 0x1<<5;//使能串口1的接收非空中断
NVIC_SetPriority(USART1_IRQn,7);//设置优先级0~15
NVIC_EnableIRQ(USART1_IRQn);//使能中断通道
}
void SendData(uint8_t data)
{
while((USART1->SR&0x01<<6)==0){}//等待上次发送完成
USART1->DR = data;//发送数据
}
int fputc(int ch, FILE *f)
{
//printf函数最终会跳转到这里来运行
while((USART1->SR&0x1<<6)==0);
//发送数据
USART1->DR = (uint8_t)ch;
return ch;
}
usart.h
#ifndef _USART_H_
#define _USART_H_
#include "stm32f10x.h"
#include "stdio.h"
void Usart1_Config();
void SendData(uint8_t data);
int fputc(int ch, FILE *f);
#endif
key.c
#include "stm32f10x.h"
#include "stdio.h"
//PA0
void KEY1_Exti_PA0_init()
{
RCC->APB2ENR |= 0x05;//打开GPIO和AFIO时钟
GPIOA->CRL &=~(0X0F << 0);//PC4 key2
GPIOA->CRL |= 0X04 << 0;
AFIO->EXTICR[0] &= ~(0x0F);//配置GPIO映射EXTI线
EXTI->RTSR &= ~(0x1); //关闭上升沿检测
EXTI->FTSR |= 0x01; //打开下降沿检测
EXTI->IMR |= 0x01; //打开exti的屏蔽位
NVIC_SetPriority(EXTI0_IRQn,2);NVIC设置优先级
NVIC_EnableIRQ(EXTI0_IRQn); //NVIC使能中断通道
}
//exti0的中断服务函数
void EXTI0_IRQHandler(void)
{
//判断中断标志是否被置位
if((EXTI->PR&(0x1<<0))!=0){
//如果置位,就清理标志位
EXTI->PR |= 0x1<<0;//写1是清除
printf("按键1触发中断\r\n");
}
}
//PC4
void KEY2_Exti_PC4_init()
{
RCC->APB2ENR |= (0x01<<4); //打开GPIOC时钟
RCC->APB2ENR |= 0x01; //AFIO时钟
GPIOC->CRL &=~(0X0F<<16);//配置PC4 key2
GPIOC->CRL |= (0X04<<16);//浮空输入
AFIO->EXTICR[1] |= 0x02;//配置GPIOC映射EXTI线,外部中断配置寄存器
EXTI->RTSR |= (0x01<<4) ; //打开上升沿检测
EXTI->FTSR &= ~(0x1<<4); //关闭下降沿检测
EXTI->IMR |= (0x01<<4); //打开exti的屏蔽位
NVIC_SetPriority(EXTI4_IRQn,2);NVIC设置优先级
NVIC_EnableIRQ(EXTI4_IRQn); //NVIC使能中断通道
}
void EXTI4_IRQHandler(void)
{
//判断中断标志是否被置位
if((EXTI->PR&(0x1<<4))!=0){
//如果置位,就清理标志位
printf("按键2触发中断\r\n");
EXTI->PR |= (0x1<<4);//写1是清除
}
}
//PC5
void KEY3_Exti_PC5_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure={0};
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);
EXTI_InitTypeDef EXTI_InitStructure={0};
EXTI_InitStructure.EXTI_Line = EXTI_Line5;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
NVIC_SetPriority(EXTI9_5_IRQn,2);NVIC设置优先级00 10
NVIC_EnableIRQ(EXTI9_5_IRQn); //NVIC使能中断通道
}
//PC6
void KEY4_Exti_PC6_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure={0};
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource6);
EXTI_InitTypeDef EXTI_InitStructure={0};
EXTI_InitStructure.EXTI_Line = EXTI_Line6;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
NVIC_SetPriority(EXTI9_5_IRQn,2);NVIC设置优先级00 10
NVIC_EnableIRQ(EXTI9_5_IRQn); //NVIC使能中断通
}
void EXTI9_5_IRQHandler(void)
{
//判断中断标志是否被置位
if(EXTI_GetITStatus(EXTI_Line5) != RESET)
{
printf("按键3触发中断\r\n");
//如果置位,就清理标志位
EXTI_ClearITPendingBit(EXTI_Line5);
}
//判断中断标志是否被置位
if(EXTI_GetITStatus(EXTI_Line6) != RESET)
{
printf("按键4触发中断\r\n");
//如果置位,就清理标志位
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
key.h
#ifndef _KEY_H_
#define _KEY_H_
void KEY1_Exti_PA0_init();
void KEY2_Exti_PC4_init();
void KEY3_Exti_PC5_init();
void KEY4_Exti_PC6_init();
#endif
总结
1.学会了使用按键进入外部中断。
2.了解了外部中断和内部中断。
3.对于寄存器和库函数开发流程,比较熟悉了。
作者:小白橘颂