【飞控系列教程·④】GPIO输入模式与按键控制:消抖、流水灯、软件PWM呼吸灯详解

【飞控系列教程·④】GPIO输入模式与按键控制:消抖、流水灯、软件PWM呼吸灯详解 上一章我们学习了GPIO的输出功能成功控制了LED的亮灭。本章将进一步学习GPIO的输入功能通过读取按键状态来实现人机交互并综合运用输入和输出功能实现多种LED灯光效果。4.1 GPIO输入模式4.1.1 数字输入的基本概念当GPIO配置为输入模式时芯片内部的高阻抗缓冲器将引脚上的电平信号传递到输入数据寄存器。软件通过读取该寄存器即可获知引脚当前的电平状态。数字输入的关键在于引脚上的电平必须是确定的——要么是接近VCC的高电平逻辑1要么是接近GND的低电平逻辑0。如果引脚处于悬空状态既没有接VCC也没有接GND引脚电平会受到周围电磁干扰而随机跳变导致读取结果不稳定。4.1.2 上拉输入与下拉输入为了解决悬空引脚的问题CH582F的GPIO内部集成了可编程的上拉电阻和下拉电阻上拉输入Pull-Up Input在引脚内部通过一个弱上拉电阻约50kΩ~100kΩ连接到VCC。无外部驱动时引脚被上拉电阻拉至高电平。当外部电路如按键接地将引脚拉低时引脚变为低电平。适用场景按键一端接地按下时引脚被拉低低电平有效。下拉输入Pull-Down Input在引脚内部通过一个弱下拉电阻连接到GND。无外部驱动时引脚被下拉至低电平。当外部电路将引脚拉高时引脚变为高电平。适用场景按键一端接VCC按下时引脚被拉高高电平有效。4.1.3完整操作流程演示上电 → 全灭按 PA8 → 四灯常亮按 PA9 → 跑马灯滚动再按 PA9 → 停止滚动回到四灯常亮任意状态按 PA8 → 全部熄灭流水关闭#includeCH58x_common.h#includeLED.h// 可配置参数宏#defineKEY_DELAY_MS20// 按键消抖时间#defineFLOW_INTERVAL120// 跑马灯切换间隔ms改小速度变快// LED引脚定义#defineRLED_RGPIO_Pin_11//PA11#defineRLED_LGPIO_Pin_15//PB15#defineGLED_RGPIO_Pin_4//PB4#defineGLED_LGPIO_Pin_14//PA14// 按键引脚定义#defineKEY1_PINGPIO_Pin_8//PA8 总开关按键#defineKEY2_PINGPIO_Pin_9//PA9 跑马灯切换按键// 全局状态变量uint8_tRun_flag0;//跑马灯标志 1开启流水 0关闭流水uint8_tLed_All_Sta0;//总灯状态 0全灭 1全亮uint32_tsoft_tick0;//软件计时戳uint8_tflow_step0;//流水灯步骤索引/*************************************************************************** * 函 数void LED_Init(void) * 功 能用户指示灯引脚初始化 * 参 数无 * 返回值无 * 备 注: 无 ***************************************************************************/voidLED_Init(void){GPIOA_ModeCfg(GPIO_Pin_11,GPIO_ModeOut_PP_5mA);// PA11---RLED_R;GPIOB_ModeCfg(GPIO_Pin_15,GPIO_ModeOut_PP_5mA);// PB15---RLED_L;GPIOA_ModeCfg(GPIO_Pin_14,GPIO_ModeOut_PP_5mA);// PA14---GLED_LGPIOB_ModeCfg(GPIO_Pin_4,GPIO_ModeOut_PP_5mA);// PB4----GLED_R;GPIOB_SetBits(GPIO_Pin_4|GPIO_Pin_15);GPIOA_SetBits(GPIO_Pin_11|GPIO_Pin_14);}/************************************************************************** * 函 数void RGB_LED_Off(void) * 功 能所有LED灯关闭 * 参 数无 * 返回值无 * 备 注: 无 **************************************************************************/voidRGB_LED_Off(void){GPIOB_SetBits(GPIO_Pin_4|GPIO_Pin_15);GPIOA_SetBits(GPIO_Pin_11|GPIO_Pin_14);}/************************************************************************** * 函 数void RGB_LED_ON(void) * 功 能所有LED灯开启 * 参 数无 * 返回值无 * 备 注: 无 **************************************************************************/voidRGB_LED_ON(void){GPIOB_ResetBits(GPIO_Pin_4|GPIO_Pin_15);GPIOA_ResetBits(GPIO_Pin_11|GPIO_Pin_14);}/************************************************************************** * 函 数void Red_LED_STA(uint8_t OnOff) * 功 能设置红色LED状态 * 参 数OnOFF 00 全亮、01 RLED_L亮、10 RLED_R亮、11 全灭 * 返回值无 * 备 注: 无 **************************************************************************/voidRed_LED_STA(uint8_tOnOff){switch(OnOff){case0x00:GPIOB_ResetBits(GPIO_Pin_15);GPIOA_ResetBits(GPIO_Pin_11);break;case0x01:GPIOB_SetBits(GPIO_Pin_15);GPIOA_ResetBits(GPIO_Pin_11);break;case0x10:GPIOA_SetBits(GPIO_Pin_11);GPIOB_ResetBits(GPIO_Pin_15);break;case0x11:GPIOB_SetBits(GPIO_Pin_15);GPIOA_SetBits(GPIO_Pin_11);break;default:break;}}/************************************************************************** * 函 数void Grn_LED_STA(uint8_t OnOff) * 功 能设置绿色LED状态 * 参 数OnOFF 00 全亮、01 GLED_L亮、10 GLED_R亮、11 全灭 * 返回值无 * 备 注: 无 **************************************************************************/voidGrn_LED_STA(uint8_tOnOff){switch(OnOff){case0x00:GPIOB_ResetBits(GPIO_Pin_4);GPIOA_ResetBits(GPIO_Pin_14);break;case0x01:GPIOB_SetBits(GPIO_Pin_4);GPIOA_ResetBits(GPIO_Pin_14);break;case0x10:GPIOB_ResetBits(GPIO_Pin_4);GPIOA_SetBits(GPIO_Pin_14);break;case0x11:GPIOB_SetBits(GPIO_Pin_4);GPIOA_SetBits(GPIO_Pin_14);break;default:break;}}// 按键相关 /************************************************************************** * 函 数void KEY_Init(void) * 功 能PA8 PA9按键初始化 上拉输入 * 参 数无 * 返回值无 **************************************************************************/voidKEY_Init(void){GPIOA_ModeCfg(KEY1_PIN|KEY2_PIN,GPIO_ModeIN_PU);}/************************************************************************** * 函 数uint8_t KEY_Scan(void) * 功 能按键扫描 带消抖 * 返回值0-无按键 1-KEY1按下 2-KEY2按下 **************************************************************************/uint8_tKEY_Scan(void){if(GPIOA_ReadPortPin(KEY1_PIN)0){DelayMs(KEY_DELAY_MS);while(GPIOA_ReadPortPin(KEY1_PIN)0);DelayMs(KEY_DELAY_MS);return1;}if(GPIOA_ReadPortPin(KEY2_PIN)0){DelayMs(KEY_DELAY_MS);while(GPIOA_ReadPortPin(KEY2_PIN)0);DelayMs(KEY_DELAY_MS);return2;}return0;}/************************************************************************** * 函 数void KEY_Process(uint8_t key_val) * 功 能按键事件处理逻辑 * 参 数key_val按键扫描返回值 * 返回值无 **************************************************************************/voidKEY_Process(uint8_tkey_val){switch(key_val){case1:// PA8 总开关Led_All_Sta!Led_All_Sta;Run_flag0;// 开灯默认关闭流水flow_step0;soft_tick0;if(Led_All_Sta){RGB_LED_ON();}else{RGB_LED_Off();}break;case2:// PA9 切换流水仅灯全亮时生效if(Led_All_Sta1){Run_flag!Run_flag;flow_step0;soft_tick0;// 关闭流水立刻恢复四灯常亮if(Run_flag0){RGB_LED_ON();}}break;default:break;}}/************************************************************************** * 函 数void LED_Running(void) * 功 能非阻塞跑马灯 * 参 数无 * 返回值无 **************************************************************************/voidLED_Running(void){// 不满足流水条件直接退出if(Run_flag!1||Led_All_Sta!1){soft_tick0;return;}soft_tick;if(soft_tickFLOW_INTERVAL){soft_tick0;switch(flow_step){case0:Red_LED_STA(0x01);break;//左红case1:Red_LED_STA(0x10);break;//右红case2:Grn_LED_STA(0x01);break;//左绿case3:Grn_LED_STA(0x10);break;//右绿}flow_step(flow_step1)%4;}}#ifndef__LED_H__#define__LED_H__#includeCH58x_common.h#defineRLED_RGPIO_Pin_11#defineRLED_LGPIO_Pin_15#defineGLED_RGPIO_Pin_4#defineGLED_LGPIO_Pin_14#defineKEY1_PINGPIO_Pin_8#defineKEY2_PINGPIO_Pin_9externuint8_tRun_flag;externuint8_tLed_All_Sta;voidLED_Init(void);voidRGB_LED_Off(void);voidRed_LED_STA(uint8_tOnOff);voidGrn_LED_STA(uint8_tOnOff);voidRGB_LED_ON(void);voidKEY_Init(void);uint8_tKEY_Scan(void);voidKEY_Process(uint8_tkey_val);voidLED_Running(void);#endif#includeCH58x_common.h#includeLED.huint8_tTxBuff[]This is a tx exam\r\n;uint8_tRxBuff[100];uint8_ttrigB;/********************************************************************* * fn main * * brief 主函数 * * return none */#includeCH58x_common.h#includeLED.h#includeCH58x_common.h#includeLED.h// 修复字符串类型报错uint8_tRxBuff[100];uint8_ttrigB;/********************************************************************* * fn main * * brief 主函数 * * return none */#includeCH58x_common.h#includeLED.h// 字符串用字节数组彻底解决类型报错#includeCH58x_common.h#includeLED.huint8_tRxBuff[100];uint8_ttrigB;intmain(void){LED_Init();KEY_Init();while(1){uint8_tkeyKEY_Scan();KEY_Process(key);LED_Running();DelayMs(1);}}4.2 按键电路与消抖4.2.1 按键电路设计本控制板上的按键采用最简单的单键接地接法按键一端连接GPIO引脚PA9另一端连接GND。GPIO配置为上拉输入模式。按键工作状态1按键未按下释放状态GPIO引脚通过内部上拉电阻保持高电平读取值为1。2按键按下导通状态GPIO引脚通过按键直接连接到GND被拉至低电平读取值为0。4.2.2 按键抖动问题机械按键在按下和释放的瞬间触点之间会发生弹跳现象Contact Bounce导致电平信号在高低之间快速切换数次至数十次持续时间通常为5~20ms。如果不处理抖动一次按键可能被误判为多次按键。按键抖动的波形特征按下瞬间电平从高→低→高→低→…→稳定低电平约5~20ms后稳定。释放瞬间电平从低→高→低→高→…→稳定高电平约5~20ms后稳定。4.2.3 硬件消抖硬件消抖通过在按键两端并联电容通常0.1μF来滤除高频抖动信号。电容充放电的惯性使得电平变化变得平缓从而消除毛刺。硬件消抖的优点是无需软件处理缺点是增加了元器件成本和PCB面积。4.2.4 软件消抖软件消抖是更常用的方法其核心思想是检测到按键电平变化后延时一段时间通常20ms再次读取按键状态。如果两次读取结果一致则认为按键状态确实发生了变化。软件消抖的标准流程1读取按键电平如果检测到低电平按键可能按下。2延时20ms等待抖动消失。3再次读取按键电平如果仍然是低电平确认按键已按下。4执行按键处理逻辑。5等待按键释放可选防止一次按下被重复处理。/*** 按键扫描函数带软件消抖* 返回值0按键未按下1按键已按下*/uint8_t Key_Scan(void){/* 第一次读取按键状态 */if(GPIOA_ReadPortPin(GPIO_Pin_9) 0) /* 低电平可能按下 */{DelayMs(20); /* 延时20ms消抖 *//* 第二次读取确认按键状态 */if(GPIOA_ReadPortPin(GPIO_Pin_9) 0){/* 等待按键释放 */while(GPIOA_ReadPortPin(GPIO_Pin_9) 0);return 1; /* 确认按下并已释放 */}}return 0; /* 按键未按下 */}工程实践在实际飞控程序中通常不使用阻塞式的等待按键释放while死等而是采用非阻塞的定时器扫描方式每隔10~20ms检测一次按键状态这样不会阻塞主循环中的飞控算法。本教学示例为简化理解使用阻塞方式。4.3 按键控制LED本节将实现一个实用的功能通过按键切换LED的亮灭状态。每次按下按键LED的状态翻转一次亮变灭、灭变亮。4.3.1 完整代码实现/*** 按键控制LED翻转* 功能每按一次按键PA9LED1PA4状态翻转* 硬件按键PA0接地LED1 PA4经1kΩ电阻接PA4*/#include “CH58x_common.h”/*** brief 按键扫描函数带消抖* return 1检测到一次按键按下0无按键*/uint8_t Key_Scan(void){if(GPIOA_ReadPortPin(GPIO_Pin_9) 0){DelayMs(20); /* 消抖延时 */if(GPIOA_ReadPortPin(GPIO_Pin_9) 0){while(GPIOA_ReadPortPin(GPIO_Pin_9) 0); /* 等待释放 */return 1;}}return 0;}int main(void){SetSysClock(CLK_SOURCE_PLL_60MHz);/* 配置LED引脚为推挽输出 */GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeOut_PP_20mA);GPIOA_ResetBits(GPIO_Pin_4); /* LED初始熄灭 *//* 配置按键引脚为上拉输入 */GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeIN_PU);while(1){/* 检测按键是否按下 */if(Key_Scan() 1){/* 翻转LED状态 */GPIOA_InvertBits(GPIO_Pin_4);}}}4.3.2 代码解析1程序初始化阶段分别配置PA4为输出LED和PA0为上拉输入按键。2主循环中不断调用Key_Scan()函数检测按键。3Key_Scan()函数内部实现了完整的消抖逻辑检测到低电平→延时20ms→再次确认→等待释放→返回1。4当Key_Scan()返回1时调用GPIOA_InvertBits()翻转LED的输出电平。4.3.3 按键计数器示例扩展上述程序增加一个计数器用LED闪烁次数来显示按键按下的次数int main(void){SetSysClock(CLK_SOURCE_PLL_60MHz);GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeOut_PP_20mA);GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeIN_PU);uint8_t press_count 0;uint8_t i;while(1){if(Key_Scan() 1){press_count;if(press_count 10) press_count 0;/* LED闪烁press_count次来显示按键次数 */for(i 0; i press_count; i){GPIOA_SetBits(GPIO_Pin_4); /* 亮 */DelayMs(200);GPIOA_ResetBits(GPIO_Pin_4); /* 灭 */DelayMs(200);}DelayMs(500); /* 两次显示之间的间隔 */}}}4.4 多LED花样控制控制板上有4个LEDLED1~LED4分别对应PA4、PA5、PA12、PA13。本节将利用这4个LED实现多种花样灯光效果综合运用GPIO输出、延时、循环等编程技巧。4.4.1 流水灯效果流水灯是最经典的LED花样效果。4个LED依次点亮形成灯光流动的视觉效果。/*** 流水灯效果* LED1→LED2→LED3→LED4→LED1→… 循环*/#include “CH58x_common.h”/* LED引脚定义 */#define LED1 GPIO_Pin_4#define LED2 GPIO_Pin_5#define LED3 GPIO_Pin_12#define LED4 GPIO_Pin_13#define LED_ALL (LED1 | LED2 | LED3 | LED4)/* LED引脚数组方便循环遍历 */const uint32_t led_pins[] {LED1, LED2, LED3, LED4};int main(void){SetSysClock(CLK_SOURCE_PLL_60MHz);GPIOA_ModeCfg(LED_ALL, GPIO_ModeOut_PP_20mA);GPIOA_ResetBits(LED_ALL);while(1){for(int i 0; i 4; i){GPIOA_ResetBits(LED_ALL); /* 全灭 */GPIOA_SetBits(led_pins[i]); /* 点亮第i个LED */DelayMs(200); /* 保持200ms */}}}4.4.2 往返流水灯在单向流水灯的基础上增加反向流动效果LED1→LED2→LED3→LED4→LED3→LED2→LED1→…/* 往返流水灯序列 */const uint32_t sequence[] {LED1, LED2, LED3, LED4, LED3, LED2};const uint8_t seq_len 6;while(1){for(int i 0; i seq_len; i){GPIOA_ResetBits(LED_ALL);GPIOA_SetBits(sequence[i]);DelayMs(150);}}4.4.3 同时点亮扩散效果从中心向外扩散中间两个LED同时亮然后外侧两个LED同时亮依次类推。/* 扩散效果序列 */while(1){/* 全部熄灭 */GPIOA_ResetBits(LED_ALL);DelayMs(300);/* 中间两个LED亮 (LED2 LED3) */GPIOA_SetBits(LED2 | LED3);DelayMs(300);/* 四个LED全部亮 */GPIOA_SetBits(LED_ALL);DelayMs(300);/* 全部熄灭 */GPIOA_ResetBits(LED_ALL);DelayMs(300);/* 外侧两个LED亮 (LED1 LED4) */GPIOA_SetBits(LED1 | LED4);DelayMs(300);}4.4.4 软件PWM实现呼吸灯效果呼吸灯效果是指LED亮度从无到有、从有到无缓慢变化如同呼吸一般。实现呼吸灯效果需要PWM脉宽调制来控制LED的亮度。本控制板的LED引脚并未连接到硬件PWM通道但可以通过软件模拟PWM来实现。软件PWM原理在一个固定周期T内LED亮的时间为Ton灭的时间为T-Ton。占空比D Ton / T。当周期T足够短如2ms以内时人眼无法分辨闪烁只能感受到平均亮度。通过改变Ton的值就可以调节LED的表观亮度。/*** 呼吸灯效果 —— 软件PWM实现* 利用软件延时模拟PWM占空比变化*/#include “CH58x_common.h”#define LED1 GPIO_Pin_4#define LED_ALL LED1#define PWM_PERIOD 100 /* PWM周期单位延时计数 */int main(void){SetSysClock(CLK_SOURCE_PLL_60MHz);GPIOA_ModeCfg(LED_ALL, GPIO_ModeOut_PP_20mA);uint16_t brightness; /* 亮度值范围0~PWM_PERIOD */uint16_t cycle;while(1){/* 渐亮过程亮度从0增加到PWM_PERIOD */for(brightness 0; brightness PWM_PERIOD; brightness){/* 每个PWM周期内先亮brightness时间再灭剩余时间 */for(cycle 0; cycle 10; cycle) /* 重复10个周期使效果更平滑 */{GPIOA_SetBits(LED1);DelayUs(brightness * 2); /* 亮的时间 */GPIOA_ResetBits(LED1);DelayUs((PWM_PERIOD - brightness) * 2); /* 灭的时间 */}}/* 渐灭过程亮度从PWM_PERIOD减少到0 */for(brightness PWM_PERIOD; brightness 0; brightness–){for(cycle 0; cycle 10; cycle){GPIOA_SetBits(LED1);DelayUs(brightness * 2);GPIOA_ResetBits(LED1);DelayUs((PWM_PERIOD - brightness) * 2);}}}}性能提示软件PWM会占用大量CPU时间在飞控主程序中不建议使用。实际飞控板上的电机PWM由硬件定时器产生不占用CPU资源。软件PWM仅用于对实时性要求不高的LED显示场合。4.5 综合实验本节将综合运用按键输入和LED输出功能设计一个模式切换程序通过按键在多种LED显示模式之间切换。4.5.1 实验要求1系统上电后LED处于全灭状态模式0。2每按一次按键切换到下一种LED显示模式模式0全部LED熄灭。 模式1全部LED常亮。 模式2流水灯效果。 模式3交替闪烁LED1LED3 与 LED2LED4 交替。 模式4回到模式0。3切换模式时当前模式的效果应立即停止无缝切换到新模式。4.5.2拓展练习1增加更多显示模式如呼吸灯模式、随机闪烁模式等。2增加长按按键功能短按切换模式长按1秒进入紧急闪烁模式所有LED快速闪烁。3使用第二个按键如果有实现加速/减速功能调节LED动画的速度。4将模式编号通过LED闪烁次数来播报——切换模式时先用LED闪烁次数表示新模式的编号。学习资源B站系列视频教程手把手教你从零组装无人机百度网盘资料包SDK、教材PDF、完整源码点击下载 提取码: JZS8本系列持续更新欢迎收藏关注。如有问题欢迎在评论区交流。