本文首发于稀土掘金。该平台的作者 逐光而行 也是本人。
理论知识
STM32系列处理器外部中断/事件控制器的原理
共19个外部中断线,其中GPIO端口以下图的方式连接到16个外部中断/事件线上:
- 另外三种其他的外部中断/事件控制器的连接如下:EXTI 线 16 连接到 PVD 输出,EXTI 线 17 连接到 RTC 闹钟事件,EXTI 线 18 连接到 USB 唤醒事件
- 每个输入线可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿触发, 或者双边沿都触发)。
- 每个输入线都可以被独立屏蔽。挂起寄存器保持着状态线的中断要求。
STM32系列处理器外部中断/事件控制器的相关寄存器
(1)中断屏蔽寄存器(EXTI_IMR);
(2)事件屏蔽寄存器(EXTI_EMR);
(3)上升沿触发选择寄存器(EXTI_RTSR);
(4)下降沿触发选择寄存器(EXTI_FTSR);
(5)软件中断事件寄存器(EXTI_SWIER);
(6)挂起寄存器(EXTI_PR)
寄存器映像:
实验电路原理图
核心板上一共有四个普通按键S1-S4,分别通过上拉电阻与STM32的PD0-PD3相连,另一端接地,每次按键将在引脚上产生一个负脉冲。
实验要求
- 在四个中断按键中任意选择两个实现控制功能,控制8位流水灯旋转的方向,一个使流水灯左转,一个使流水灯右转。
- 剩下两个按键控制流水灯的转速,一个加速,一个减速。
程序主要代码记录如下:
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "exti.h"
uint16_t time=500;
int flag=1;
int main(void)
{
Delay_Init(); //延时初始化
LED_Hardware_Init(); //LED初始化
EXTI_Software_Init(); //外部中断初始化
while(1)
{
//__WFI(); // cpu sleep , wait for interrupt to wake up
//LED状态取反程序在exti.c中
if(flag==1){
LED_TOGGLE(LED1_PIN);
Delay_ms(time);
LED_TOGGLE(LED1_PIN);
Delay_ms(time);
LED_TOGGLE(LED2_PIN);
Delay_ms(time);
LED_TOGGLE(LED2_PIN);
Delay_ms(time);
LED_TOGGLE(LED3_PIN);
Delay_ms(time);
LED_TOGGLE(LED3_PIN);
Delay_ms(time);
LED_TOGGLE(LED4_PIN);
Delay_ms(time);
LED_TOGGLE(LED4_PIN);
Delay_ms(time);
LED_TOGGLE(LED5_PIN);
Delay_ms(time);
LED_TOGGLE(LED5_PIN);
Delay_ms(time);
LED_TOGGLE(LED6_PIN);
Delay_ms(time);
LED_TOGGLE(LED6_PIN);
Delay_ms(time);
LED_TOGGLE(LED7_PIN);
Delay_ms(time);
LED_TOGGLE(LED7_PIN);
Delay_ms(time);
LED_TOGGLE(LED8_PIN);
Delay_ms(time);
LED_TOGGLE(LED8_PIN);
Delay_ms(time);
}
if(flag==2){
LED_TOGGLE(LED8_PIN);
Delay_ms(time);
LED_TOGGLE(LED8_PIN);
Delay_ms(time);
LED_TOGGLE(LED7_PIN);
Delay_ms(time);
LED_TOGGLE(LED7_PIN);
Delay_ms(time);
LED_TOGGLE(LED6_PIN);
Delay_ms(time);
LED_TOGGLE(LED6_PIN);
Delay_ms(time);
LED_TOGGLE(LED5_PIN);
Delay_ms(time);
LED_TOGGLE(LED5_PIN);
Delay_ms(time);
LED_TOGGLE(LED4_PIN);
Delay_ms(time);
LED_TOGGLE(LED4_PIN);
Delay_ms(time);
LED_TOGGLE(LED3_PIN);
Delay_ms(time);
LED_TOGGLE(LED3_PIN);
Delay_ms(time);
LED_TOGGLE(LED2_PIN);
Delay_ms(time);
LED_TOGGLE(LED2_PIN);
Delay_ms(time);
LED_TOGGLE(LED1_PIN);
Delay_ms(time);
LED_TOGGLE(LED1_PIN);
Delay_ms(time);
}
if(flag==3){
time+=200;
}
if(flag==4){
time-=200;
}
}
}
exti.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
extern uint16_t time;
extern int flag;
/*******************************************************
函数名称:Exti_Init
输入参数:无
函数作用:外部中断初始化
返回参数:无
********************************************************/
void EXTI_Software_Init(void)
{
//定义结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_AFIO,ENABLE);
//端口初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//选择端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //选择工作模式 浮空输入
GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIOD
//中断线配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource1);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource3);
//中断触发方式
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line1;
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line2;
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line3;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//先占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//先占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//先占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//先占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
}
/**********************************************************
*功 能:外部中断0响应函数
*参 数:无
*返回值:无
**********************************************************/
void EXTI0_IRQHandler(void)
{
//from left to right
//if(EXTI_GetITStatus(EXTI_Line0) != RESET) //检查指定的EXTI0线路触发请求发生与否
if(EXTI_GetITStatus(EXTI_Line0) != RESET) //检查指定的EXTI1线路触发请求发生与否
{
flag=1;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0线路挂起位
}
/**********************************************************
*功 能:外部中断1响应函数
*参 数:无
*返回值:无
**********************************************************/
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) != RESET) //检查指定的EXTI1线路触发请求发生与否
{
flag=2;
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除EXTI1线路挂起位
}
/**********************************************************
*功 能:外部中断2响应函数
*参 数:无
*返回值:无
**********************************************************/
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2) != RESET) //检查指定的EXTI2线路触发请求发生与否
{
flag=3;
}
EXTI_ClearITPendingBit(EXTI_Line2); //清除EXTI2线路挂起位
}
/**********************************************************
*功 能:外部中断3响应函数
*参 数:无
*返回值:无
**********************************************************/
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3) != RESET) //检查指定的EXTI3线路触发请求发生与否
{
flag=4;
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
代码思路说明
- 执行流水灯相应操作的程序我写在了main.c中的main函数中,并通过设置全局变量,使中断响应函数改变该全局变量的值,从而触发操作。
(一开始我想把操作程序直接写在中断响应函数中,经老师指导,认为这样是复用性很差的写法,于是修改了)
- 关于多个函数间全局变量的使用,可在一个函数的声明语句之后声明该变量,并在同一文件夹下的其他文件中通过
extern
引入该变量。
(复习c语言)
- 关于流水灯左转右转的代码我写的这一版还稍显冗余。也许有一种能在for循环中调用传入参数的函数的写法,各位小伙伴自行摸索了哈。因为精力有限,我也不细究太多了。
一点感悟:
- 代码其实不是我们从0创造的,老师提供了整一个工程项目文件,我们只需要把一些关键文件的关键函数进行修改即可。不过在这个改代码的过程中我也学会了很多东西。
- 嵌入式讲求软硬结合,在实际连接到开发板上进行操作尝试的过程中,我突然理解了很多步骤的意图,这是我单纯看代码时迟迟领悟不到的。所以说“纸上得来终觉浅,绝知此事要躬行”。