TM4C12G单片机中断方式实现按键检测
1、首先看一下板子上按键的原理图
可以发现PF0和PF4两个引脚接了两个按键,当按键按下的时候就会接地,即变成低电平,前面讲GPIO作输入时是利用GPIOPinRead()
函数来循环读取io状态判断按键是否被按下,但这样显然有一个问题,即CPU在这一时刻不能作其他事情,所以能不能通过一种办法使CPU做其他事情的同时还能检测按键,即今天我们要说的内容:外部中断。
2、中断
什么是单片机的中断?举一个例子,假如说玩游戏时,女朋友突然打电话过来,我们不得不停下游戏去接这个电话,然后接完电话继续进行我们的游戏。在这个过程中,电话铃声就可以理解为一个中断的触发,然后我们接了电话,即响应了这个中断,通话过程是中断程序做的事情,然后接完电话玩游戏就是单片机做完中断以后继续原来的工作。
那么在单片机的C语言代码是怎么实现的呢?
其实,我们会定义一个函数,但是这个函数和我们一般使用的函数不同,它是不需要被我们手动调用的,它的执行完全被单片机自动唤醒,唤醒的条件被用户定义。以今天要说的按键为例,即当检测到有低电平或下降沿时,单片机会停下当前正在进行的代码,转到我们定义的中断函数中,执行一系列操作。
3、代码实现
这里我们用按键1,即PF4。
(1)首先开启PORTF端口外设
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF))
{}
(2)配置PF0为上拉输入
GPIODirModeSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_DIR_MODE_IN);
GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);//上拉
GPIO_PIN_TYPE_STD_WPU
的意思是配置成上拉模式,即没有按键按下时高电平,有按键按下变为低电平。
(3)配置中断触发类型
GPIOIntTypeSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_FALLING_EDGE);//下降沿
GPIO_FALLING_EDGE
的意思是下降沿,这里也可以设置为GPIO_LOW_LEVEL
,即低电平。按键按下这个事件,电平由高到底,所以下降沿或低电平作为中断触发类型都可以。
可以看下库函数中的其他参数:
//*****************************************************************************
//
// Values that can be passed to GPIOIntTypeSet as the ui32IntType parameter,
// and returned from GPIOIntTypeGet.
//
//*****************************************************************************
#define GPIO_FALLING_EDGE 0x00000000 // Interrupt on falling edge
#define GPIO_RISING_EDGE 0x00000004 // Interrupt on rising edge
#define GPIO_BOTH_EDGES 0x00000001 // Interrupt on both edges
#define GPIO_LOW_LEVEL 0x00000002 // Interrupt on low level
#define GPIO_HIGH_LEVEL 0x00000006 // Interrupt on high level
#define GPIO_DISCRETE_INT 0x00010000 // Interrupt for individual pins
从宏的名称便可以看出中断触发类型。
(4)设置中断处理程序的地址
GPIOIntRegister(GPIO_PORTF_BASE, io_interrupt);
这里解释一下,io_interrupt
是一个函数,在C语言里面函数名其实也是一个地址,将该中断处理程序的函数名作为参数传进这个GPIOIntRegister
函数中,即告诉了单片机这个中断处理函数的存储地址,当有中断事件发生时便进行寻址,找到这个函数进行执行。
如果不用这个函数,我们也可以到中断向量表里手动进行注册,TM4C123G单片机的中断向量表位于startup_TM4C123.s
文件。但是因为手动注册比较麻烦,所以推荐大家使用GPIOIntRegister
函数。
(5)开启中断
GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);
IntEnable(INT_GPIOF);
IntMasterEnable();
在单片机中,好多外设都有中断,为了有序的管理这些中断,便有了一些“开关”,通过代码控制各个部分中断的开启关闭。以这三行代码为例:
IntMasterEnable();
开启了总开关
IntEnable(INT_GPIOF);
开启了F端口的开关,类似的其他外设:
//*****************************************************************************
//
// The following are defines for the interrupt assignments.
//
//*****************************************************************************
#define INT_ADC0SS0 INT_CONCAT(INT_ADC0SS0_, INT_DEVICE_CLASS)
#define INT_AES0 INT_CONCAT(INT_AES0_, INT_DEVICE_CLASS)
#define INT_CAN0 INT_CONCAT(INT_CAN0_, INT_DEVICE_CLASS)
#define INT_COMP0 INT_CONCAT(INT_COMP0_, INT_DEVICE_CLASS)
#define INT_DES0 INT_CONCAT(INT_DES0_, INT_DEVICE_CLASS)
#define INT_EMAC0 INT_CONCAT(INT_EMAC0_, INT_DEVICE_CLASS)
#define INT_EPI0 INT_CONCAT(INT_EPI0_, INT_DEVICE_CLASS)
#define INT_FLASH INT_CONCAT(INT_FLASH_, INT_DEVICE_CLASS)
#define INT_GPIOC INT_CONCAT(INT_GPIOC_, INT_DEVICE_CLASS)
GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);
开启了PF4的开关
(6)编写中断处理函数
void io_interrupt(void)
{
uint32_t s = GPIOIntStatus(GPIO_PORTF_BASE, true);
GPIOIntClear(GPIO_PORTF_BASE, s);
if((s&GPIO_PIN_4) == GPIO_PIN_4)
{
while(!GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_4))//等待按键松开
;
//TODO
flag = !flag;
}
}
中断处理函数即是中断被触发后单片机做的事情,其实中断的触发在单片机底层是将一个寄存器的某一位写为1,然后这个1触发了中断,因此为了保证下一次中断被重新触发,在进入中断后我们需要用GPIOIntStatus
函数获取这个F端口的状态,然后用GPIOIntClear
将其清零。
因为所有F端口下的引脚都会进入这个函数中进行处理,比如说这个单片机是PF0 - PF7,所以我们需要用这个if((s&GPIO_PIN_4) == GPIO_PIN_4)
语句判断究竟是那个引脚触发了中断,这里是PF4,按键引脚。
然后等待按键松开,进行自己想要的操作,这里是将一个变量进行了非操作。
最后附上测试的源代码:
程序功能:通过按键切换红蓝灯的闪烁。
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"
#include "inc/hw_ints.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
void io_interrupt(void);
void delay_ms(uint32_t n);
bool flag = 1;
int main(void)
{
//时钟频率:40MHz
SysCtlClockSet(SYSCTL_SYSDIV_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
//开启外设
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF))
{}
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1);//设置PF1为输出,红灯
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_2);//设置PF2为输出,蓝灯
//设置PF4为上拉输入模式
GPIODirModeSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_DIR_MODE_IN);
GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
//配置中断
GPIOIntTypeSet(GPIO_PORTF_BASE, GPIO_PIN_4, GPIO_FALLING_EDGE);
GPIOIntRegister(GPIO_PORTF_BASE, io_interrupt);
//开启中断
GPIOIntEnable(GPIO_PORTF_BASE, GPIO_INT_PIN_4);
IntEnable(INT_GPIOF);
IntMasterEnable();
while(1)
{
if(flag == 1)
{
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0xFF);
delay_ms(100);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0x00);
delay_ms(100);
}
else
{
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0xFF);
delay_ms(100);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0x00);
delay_ms(100);
}
}
}
//中断处理程序
void io_interrupt(void)
{
uint32_t s = GPIOIntStatus(GPIO_PORTF_BASE, true);
GPIOIntClear(GPIO_PORTF_BASE, s);
if((s&GPIO_PIN_4) == GPIO_PIN_4)
{
while(!GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_4))//等待按键松开
;
flag = !flag;
}
}
void delay_ms(uint32_t n)
{
for(uint32_t i = 0; i < n; i++)
SysCtlDelay(SysCtlClockGet()/3000);
}