四、GPIO中断实现按键功能
4.1 GPIO简介
输入输出(I/O)是一个非常重要的概念。I/O泛指所有类型的输入输出端口,包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO(General-Purpose Input/Output)则是一个常见的术语,指的是通用输入输出接口。
下面有请DeepSeek发言
LPC1110系列Cortex-M0微控制器的GPIO口的结构特点:
1 | 端口可由软件配置为输入输出 |
2 | 引脚默认为输入(所以点灯时需要改下方向) |
3 | 端口引脚的读写操作可屏蔽 |
4 | 每个单独引脚可被用作外部中断输入引脚 |
5 | 每个GPIO中断可配置为 高、低电平、下降、上升沿或双边沿触发 |
6 | 可对单独端口的中断级别进行配置 |
4.2 GPIO口的寄存器
所有GPIO寄存器都为32位
GPIO端口基址为
端口0 | 0x5000 0000 |
端口1 | 0x5001 0000 |
端口2 | 0x5002 0000 |
端口3 | 0x5003 0000 |
4.2.1 数据寄存器 GPIOnDATA
用于读取输入引脚的状态数据或者配置输出引脚的输出状态
对应端口位置后四位范围为 0000 ~ 3FFC
11:0 | PIOn_0 ~ PIOn_11的输入/输出数据 |
31:12 | 保留 |
4.2.2 方向寄存器 GPIOnDIR
11:0 | PIOn_0 ~ PIOn_11的输入/输出方向 0为输入, 1为输出 位数0-11与0-11引脚一一对应 |
31:12 | 保留 |
4.2.3 中断触发寄存器 GPIOnIS
相较于基地址偏移量0x8004 即0x500n 8004
11:0 | PIOn_x 0为边沿触发,1为电平触发 |
31:12 | 保留 |
4.2.4 中断双边沿触发寄存器 GPIOnIBE
相较于基地址偏移量0x8008 即0x500n 8008
11:0 | 0为通过4.2.5中寄存器GPIOnIEV控制PIOn_x的中断 1为通过PIOn_x上双边沿触发中断 |
31:12 | 保留 |
4.2.5 中断事件寄存器 GPIOnIEV
相较于基地址偏移量0x800C 即0x500n 800C
11:0 | 0为上升沿或者高电平触发中断 1为下降沿或者低电平触发中断 具体边沿还是电平 看4.2.3中GPIOnIS的设置 |
31:12 | 保留 |
4.2.6 中断屏蔽寄存器 GPIOnIE
相较于基地址偏移量0x8010 即0x500n 8010
11:0 | 0为中断被屏蔽 1为中断不被屏蔽 |
31:12 | 保留 |
4.2.7 原始中断状态寄存器 GPIOnIRS
相较于基地址偏移量0x8014 即0x500n 8014
屏蔽之前的中断状态
11:0 | 0为无中断 1为满足中断要求 |
31:12 | 保留 |
4.2.8 屏蔽中断状态寄存器 GPIOnMIS
相较于基地址偏移量0x8018 即0x500n 8018
考虑了屏蔽操作之后是否有中断
11:0 | 0为无中断,或者中断被屏蔽 1为满足中断要求 |
31:12 | 保留 |
4.2.9 中断清除寄存器 GPIOnIC
相较于基地址偏移量0x801C 即0x500n 801C
11:0 | 0无操作 1为清除PIOn_x上的边沿检测逻辑 |
31:12 | 保留 |
4.3 LPC上的GPIO按键
按键按下引脚低电平,不按是高电平
4.4 按键控制LED闪烁频率
任务:
1. BUTTON(PIO3_5)按键按下,闪烁频率为1Hz,再次按下,恢复闪烁频率为0.5Hz;
2. WEAKUP(PIO1_4)按键按下,闪烁频率为2Hz,再次按下,恢复闪烁频率为0.5Hz;
3. 适当考虑按键防抖功能。
思路:
对于闪烁频率的修改,首先考虑用什么控制LED闪烁,结合上章可以用SysTick,然后按键按下改变SysTick周期即可
对于按键防抖,由于按键固有的物理结构,按下后弹簧一上一下会影响中断,需要用延时函数过滤抖动。
抖动时间大概10ms这样, 我们可以用个延时函数过滤掉这个抖动过程,延时20ms就足够了
代码:
利用之前写过的函数即可,复制个新工程,然后main文件里代码如下
#include <LPC11xx.h>
#include "LED.h"
//延时ms函数 // 太粗糙了,而且要根据机器指令与时钟周期关系调整,也就防抖延时用一下
__inline void delay_ms(uint32_t a) //约1ms延时函数
{
uint32_t i;
while( a -- != 0)
{
for(i = 0; i<5500; i++);
}
}
int flag1 = 0, flag2 = 0; // 判断botton 和 wakeup 按键上一次状态
int main()
{
LED_Init();
// PIO1_4
LPC_IOCON->PIO1_4 &= ~(0x1F); // 清除之前的配置
LPC_IOCON->PIO1_4 |= 0x00; // 配置为GPIO功能
LPC_GPIO1->DIR &= ~(1UL << 4);// 设置GPIO方向为输入
LPC_GPIO1->IS &= ~(0x1 << 4); // 清除第 4 位,设置为边沿触发
LPC_GPIO1->IBE &= ~(0x1 << 4); // 清除第 4 位,设置为单边沿触发
LPC_GPIO1->IEV &= ~(0x1 << 4); // 清除第 4 位,设置为低电平触发
LPC_GPIO1 -> IE |= (0x1<<4); // 使能端口中断
LPC_IOCON->PIO1_4 |= (1UL << 5); // 使能滞后模式
LPC_GPIO1->IC |= (1UL << 4); // 清除中断标志位
NVIC_EnableIRQ(EINT1_IRQn); // 使能GPIO1中断
// PIO3_5
LPC_IOCON->PIO3_5 &= ~(0x1F); // 清除之前的配置
LPC_IOCON->PIO3_5 |= 0x00; // 配置为GPIO功能
LPC_GPIO3->DIR &= ~(1UL << 5);// 设置GPIO方向为输入
LPC_GPIO3->IS &= ~(0x1 << 5); // 清除第 5 位,设置为边沿触发
LPC_GPIO3->IBE &= ~(0x1 << 5); // 清除第 5 位,设置为单边沿触发
LPC_GPIO3->IEV &= ~(0x1 << 5); // 清除第 5 位,设置为低电平触发
LPC_GPIO3 -> IE |= (0x1<<5); // 使能端口中断
LPC_IOCON->PIO3_5 |= (1UL << 5); // 使能滞后模式
LPC_GPIO3->IC |= (1UL << 5); //清除中断标志
NVIC_EnableIRQ(EINT3_IRQn);
SysTick_Config(SystemCoreClock/100); // 0.01s进一次中断 1s翻转一次 0.5 Hz
while(1)
{
}
}
void SysTick_Handler() /// 系统节拍定时器中断函数
{
static unsigned long ticks;
if(ticks++ >= 99)
{
ticks = 0;
LED_Toggle();
}
}
// GPIO3_5的中断服务函数,处理BUTTON按键按下事件
void PIOINT3_IRQHandler(void)
{
if((LPC_GPIO3->MIS & (1UL << 5)) == (1UL << 5))// 检查是否是PIO3_5的中断
{
delay_ms(20); // 消抖
while((LPC_GPIO3->DATA & (1UL << 5)) == 0);
delay_ms(20);
if(flag1)
SysTick_Config(SystemCoreClock/100); // 0.01s进一次中断 1s翻转一次 0.5 Hz
else
SysTick_Config(SystemCoreClock/200); // 0.005s进一次中断 0.5s翻转一次 1 Hz
flag1 = !flag1;
LPC_GPIO3->IC |= (1UL << 5); // 清除中断标志
}
}
// GPIO1_4的中断服务函数,处理WAKEUP按键按下事件
void PIOINT1_IRQHandler(void)
{
if((LPC_GPIO1->MIS & (1UL << 4)) == (1UL << 4)) // 检查是否是PIO1_4的中断
{
delay_ms(20);
while((LPC_GPIO1->DATA & (1UL << 4)) == 0);
delay_ms(20);
if(flag2)
SysTick_Config(SystemCoreClock/100); // 0.01s进一次中断 1s翻转一次 0.5 Hz
else
SysTick_Config(SystemCoreClock/400); // 0.0025s进一次中断 0.2s翻转一次 2 Hz
flag2 = !flag2;
LPC_GPIO1->IC |= (1UL << 4); // 清除中断标志
}
}
模块化一下,新建Button.c Button.h文件,便于之后移植工程
main.c
#include <LPC11xx.h>
#include "LED.h"
#include "Button.h"
int main()
{
LED_Init();
WAKEUP_Init();
Button_Init();
while(1)
{
}
}
void SysTick_Handler() /// 系统节拍定时器中断函数
{
static unsigned long ticks;
if(ticks++ >= 99)
{
ticks = 0;
LED_Toggle();
}
}
Button.c
#include "Button.h"
int flag1 = 0, flag2 = 0; // 判断botton 和 wakeup 按键上一次状态
//延时ms函数 // 太粗糙了,而且要根据机器指令与时钟周期关系调整,也就防抖延时用一下
__inline void delay_ms(uint32_t a) //约1ms延时函数
{
uint32_t i;
while( a -- != 0)
{
for(i = 0; i<5500; i++);
}
}
void WAKEUP_Init(void)
{
LPC_SYSCON -> SYSAHBCLKCTRL |= (1UL << 6) | (1UL << 16); // 使能GPIO时钟和IO时钟
// PIO1_4
LPC_IOCON->PIO1_4 &= ~(0x1F); // 清除之前的配置
LPC_IOCON->PIO1_4 |= 0x00; // 配置为GPIO功能
LPC_GPIO1->DIR &= ~(1UL << 4);// 设置GPIO方向为输入
LPC_GPIO1->IS &= ~(0x1 << 4); // 清除第 4 位,设置为边沿触发
LPC_GPIO1->IBE &= ~(0x1 << 4); // 清除第 4 位,设置为单边沿触发
LPC_GPIO1->IEV &= ~(0x1 << 4); // 清除第 4 位,设置为低电平触发
LPC_GPIO1 -> IE |= (0x1<<4); // 使能端口中断
LPC_IOCON->PIO1_4 |= (1UL << 5); // 使能滞后模式
LPC_GPIO1->IC |= (1UL << 4); // 清除中断标志位
NVIC_EnableIRQ(EINT1_IRQn); // 使能GPIO1中断
}
void Button_Init(void)
{
LPC_SYSCON -> SYSAHBCLKCTRL |= (1UL << 6) | (1UL << 16); // 使能GPIO时钟和IO时钟
// PIO3_5
LPC_IOCON->PIO3_5 &= ~(0x1F); // 清除之前的配置
LPC_IOCON->PIO3_5 |= 0x00; // 配置为GPIO功能
LPC_GPIO3->DIR &= ~(1UL << 5);// 设置GPIO方向为输入
LPC_GPIO3->IS &= ~(0x1 << 5); // 清除第 5 位,设置为边沿触发
LPC_GPIO3->IBE &= ~(0x1 << 5); // 清除第 5 位,设置为单边沿触发
LPC_GPIO3->IEV &= ~(0x1 << 5); // 清除第 5 位,设置为低电平触发
LPC_GPIO3 -> IE |= (0x1<<5); // 使能端口中断
LPC_IOCON->PIO3_5 |= (1UL << 5); // 使能滞后模式
LPC_GPIO3->IC |= (1UL << 5); //清除中断标志
NVIC_EnableIRQ(EINT3_IRQn);
}
// GPIO3_5的中断服务函数,处理BUTTON按键按下事件
void PIOINT3_IRQHandler(void)
{
if((LPC_GPIO3->MIS & (1UL << 5)) == (1UL << 5))// 检查是否是PIO3_5的中断
{
delay_ms(20); // 消抖
while((LPC_GPIO3->DATA & (1UL << 5)) == 0);
delay_ms(20);
if(flag1)
SysTick_Config(SystemCoreClock/100); // 0.01s进一次中断 1s翻转一次 0.5 Hz
else
SysTick_Config(SystemCoreClock/200); // 0.005s进一次中断 0.5s翻转一次 1 Hz
flag1 = !flag1;
LPC_GPIO3->IC |= (1UL << 5); // 清除中断标志
}
}
// GPIO1_4的中断服务函数,处理WAKEUP按键按下事件
void PIOINT1_IRQHandler(void)
{
if((LPC_GPIO1->MIS & (1UL << 4)) == (1UL << 4)) // 检查是否是PIO1_4的中断
{
delay_ms(20);
while((LPC_GPIO1->DATA & (1UL << 4)) == 0);
delay_ms(20);
if(flag2)
SysTick_Config(SystemCoreClock/100); // 0.01s进一次中断 1s翻转一次 0.5 Hz
else
SysTick_Config(SystemCoreClock/400); // 0.0025s进一次中断 0.2s翻转一次 2 Hz
flag2 = !flag2;
LPC_GPIO1->IC |= (1UL << 4); // 清除中断标志
}
}
Button.h
#ifndef _BUTTON_H_
#define _BUTTON_H_
#include <LPC11xx.h>
void WAKEUP_Init(void);
void Button_Init(void);
#endif