本文主要介绍了通过中断来实现开关控制LED灯的亮和灭。
软件:Keil μVision
芯片:STM32F103C8T6
一、实验简介
本次实验通过B1端口的开关控制A1端口的LED灯,即B1端口接高电平时LED亮,B1端口接低电平时LED灭。实验电路图比较简单,这里就不做展示。
二、实验原理
本次实验主要使用了中断、GPIO输入和输出模式来进行实验。
CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级可编程中断设置。STM32并没有CM3的全部中断服务。STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级(STM32F103系列有60个可屏蔽中断)。在STM32中文参考手册第9章,第130页,比较详细的介绍了STM32的中断事件。
对于这么多中断,如何管理?通过寄存器对STM32中断进行分组,再对每个中断设置一个抢占优先级和一个响应优先级。在STM32固件库使用手册第165页中有分组情况。具体可以在B站观看相关视频。
1. NVIC简介
所以我们需要设置中断分组,比如我们设置中断分组为2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
还需要初始化NVIC,配置相关参数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00;
NVIC_Init(&NVIC_InitStructure);
这里设置中断为EXTI1中断(下一小节介绍),然后使能,设置抢占优先级和响应优先级。其中IRQChannel的值可以在stm32f10x.h中找到,也可以在STM32固件库使用手册第166也查到。下面是部分表
2.EXTI简介
通用I/O端口以下图的方式连接到16个外部中断/事件线上:(STM32中文参考手册第137页)
和之前类似,首先初始化EXTI。
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line=EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStructure);
EXTI_Line的值和GPIOX端口类似,参考上面图片,EXTI_Line的值如下
EXTI_LineCmd的值设置为ENABLE,使能,模式(EXTI_Mode)设置为中断(Interrupt),触发方式(EXTI_Trigger)选择为上下沿触发,最后调用初始化函数EXTI_Init(&EXTI_InitStructure)
然后是写中断函数,在启动文件中,已经写了中断函数的引用
中断函数如下:
void EXTI1_IRQHandler(void)
{
delay_ms(10);//消除抖动
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
最后一定要清除中断位,用EXTI_ClearPendingBit(EXTI_LineX)函数
当B1端口是高电平时,A1端口是低电平,LED灯亮;当A1端口是低电平时,A1端口是高电平,LED灯灭。
有关LED灯和按键的代码这里不做讲解,可以在我之前博客STM32F103点亮LED流水灯_江南烟浓雨的博客-CSDN博客中查看原理
三、实验代码
1.库函数方式
创建工程后,新建四个文件夹,分别为DELAY、EXTI、KEY、LED,分别对应四个C文件和四个头文件,代码如下
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
void DELAY_Init(void);
void delay_ms(u16 nms);
#endif
delay.c
#include "delay.h"
static u16 fac_ms = 0;
void DELAY_Init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_ms = (u16)(SystemCoreClock/8000);
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
exti.h
#ifndef __EXTI_H
#define __EXTI_H
#include "stm32f10x.h"
#include "delay.h"
#include "key.h"
void EXTIX_Init(void);
#endif
exti.c
#include "exti.h"
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
KEY_Init();
//GPIOB.1中断线初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line=EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI1_IRQHandler(void)
{
delay_ms(10);//消除抖动
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
void KEY_Init(void);
u8 KEY_Scan(void);
#endif
key.c
#include "key.h"
void KEY_Init(void)
{
/* 定义一个GPIO_InitTypeDef类型的结构体 */
GPIO_InitTypeDef GPIO_InitStructure;
/* 设置时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/* 将B1端口GPIO_Mode和GPIO_Speed设置为下拉输入模式 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
led.h
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
#include "key.h"
void LED_Init(void);
#endif
led.c
#include "led.h"
void LED_Init(void)
{
/* 定义一个GPIO_InitTypeDef类型的结构体 */
GPIO_InitTypeDef GPIO_InitStructure;
/* 设置时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/* 将A1端口GPIO_Mode和GPIO_Speed设置为PP模式和50MHz */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
}
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "exti.h"
#include "key.h"
#include "led.h"
int main(void)
{
DELAY_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
EXTIX_Init();
LED_Init();
while(1)
{
}
}
2.HAL函数方式
我们使用STM32CubeMX来生成代码,和上次的实验类似,需要GPIO口的设置类似。需要注意的是,需要设置PB1口位EXTI1。
GPIO口设置位上下沿触发中断
注意:NVIC中System tick timer需要设置优先级高于EXTI
EXTI中断可以比较随便的设置,这里设置为2和0
其他方面可以参考STM32CubeMX的安装与简单应用_江南烟脓雨的博客-CSDN博客
代码修改如下:
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==1)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
}
这里我们希望刚通电时(初始状态)灯的亮灭和开关状态保持一致
接下来修改stm32f1xx_hal_gpio.c中的中断函数
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==1)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
}
添加中断函数内容,如下图所示
四、实验结果
五、总结
本次实验初步实现了中断函数,因为实验比较简单,所以没有设计复杂的函数。对于中断函数的学习可以参考stm32中文手册和库函数手册,或通过网络资源学习。