关于单片机按键非阻塞检测的一种方法
按键作为一种开关型的硬件外设,在大部分的嵌入式单片机软件开发中都会使用到。但是,单片机的开发很多时候都是裸机开发,几乎用不到RTOS,裸机开发说明单片机是按顺序执行的,是个单线程系统,而按键检测需要进行消抖,消抖是需要等待的,在裸机系统中,就会造成阻塞,导致系统性能下降。
下面,是一个利用状态机实现按键的非阻塞检测效果的代码。分两个平台实现,一个是杰里的AC791N系列芯片,一个是STM32F429IGT6芯片,而STM系列我是基于正点原子的阿波罗开发板实现的。
杰里AC791N利用状态机实现按键的非阻塞检测
提示:不是用AC791N系列芯片的可直接跳过!!!
key.h的配置如下:
#ifndef __KEY_H_
#define __KEY_H_
#define KEY_GPIO_EN (1)/* GPIO按键使能 0:关闭 1:打开 */
#define CFG_HW_GPIOKEY_NUM (2)/* GPIO按键数量 */
enum APP_KEY_CODE_T {
APP_KEY_CODE_FN1 = 0x00,
APP_KEY_CODE_FN2,
};
// 按键事件枚举
typedef enum{
APP_KEY_DEFAULE_EVENT = 0,
APP_KEY_LONGPRESS_EVENT,
APP_KEY_LONGLONGPRESS_EVENT,
APP_KEY_FACTORY_EVENT,
APP_KEY_UP_AFTER_LONGPRESS_EVENT,
APP_KEY_UP_AFTER_LONGLONGPRESS_EVENT,
APP_KEY_CLICK_EVENT,
APP_KEY_DOUBLECLICK_EVENT,
APP_KEY_TRIPLECLICK_EVENT,
APP_KEY_FOURTHCLICK_EVENT,
APP_KEY_FIFTHCLICK_EVENT,
APP_KEY_SIXTHCLICK_EVENT,
APP_KEY_EVENT_NUM = APP_KEY_SIXTHCLICK_EVENT,
}APP_KEY_EVENT_T;
// 按键状态枚举
typedef enum{
APP_KEY_DEFAULE_STATUS = 0,
APP_KEY_DITHER_STATUS,
APP_KEY_DOWN_STATUS,
APP_KEY_LONGPRESS_WAIT_RELEASE_STATUS,
APP_KEY_INTERVAL_DEAL_STATUS,
APP_KEY_STATE_NUM = APP_KEY_INTERVAL_DEAL_STATUS
}APP_KEY_STATE_T;
#endif
key.c如下代码:
#include "system/includes.h"
#include "asm/gpio.h"
#include "app_config.h"
#include "storage_device.h"
#include "generic/log.h"
#include "os/os_api.h"
#include "event/key_event.h"
#include "event/device_event.h"
#include "event/net_event.h"
#include "fs/fs.h"
#include "asm/pwm.h"
#include "device/device.h"
#include "key.h"
#define TIMER_PERIOD 10 // 按键扫描时间
#define KEY_DEBOUNCE_INTERVAL_MS 20 // 消抖时间
#define KEY_LONGPRESS_THRESHOLD_MS3000 // 长按事件触发时间
#define KEY_LONGLONGPRESS_THRESHOLD_MS6000 // 长长按事件触发时间
#define KEY_CHECKER_INTERVAL_MS 400 // 多击触发时间间隔
// 按键触发配置
typedef enum{
MX_KEY_PULLUP_CONFIG = 0X00,
MX_KEY_PULLDOWN_CONFIG = 0X01,
}MX_KEY_CONFIG;
// 按键触发值
enum HAL_KEY_GPIOKEY_VAL_T {
HAL_KEY_GPIOKEY_VAL_LOW = 0,
HAL_KEY_GPIOKEY_VAL_HIGH,
};
// 按键IO口配置
typedef struct {
uint8_t port;
MX_KEY_CONFIG config;
}MX_KEY_IO_CONFIG;
// 单个按键的所有配置
typedef struct HAL_KEY_GPIOKEY_CFG {
MX_KEY_IO_CONFIG key_config;
enum HAL_KEY_GPIOKEY_VAL_T key_down;
APP_KEY_STATE_T status;
APP_KEY_EVENT_T event;
int timer_count;
}HAL_KEY_GPIOKEY_CFG_T;
// 按键配置初始化
HAL_KEY_GPIOKEY_CFG_T gpio_key_cfg[CFG_HW_GPIOKEY_NUM] = {
{{IO_PORTA_09,MX_KEY_PULLUP_CONFIG,NULL},HAL_KEY_GPIOKEY_VAL_LOW,APP_KEY_DEFAULE_STATUS,APP_KEY_DEFAULE_EVENT,0},
{{IO_PORTA_00,MX_KEY_PULLUP_CONFIG,NULL},HAL_KEY_GPIOKEY_VAL_LOW,APP_KEY_DEFAULE_STATUS,APP_KEY_DEFAULE_EVENT,0},
};
// 判断按键是否按下
static bool hal_gpiokey_pressed(uint8_t index)
{
return (gpio_read(gpio_key_cfg[index].key_config.port) == gpio_key_cfg[index].key_down);
}
// 按键扫描函数
static void hal_key_debounce_handler(void *param)
{
static bool key_press = 0;
for(int ii=0;ii<CFG_HW_GPIOKEY_NUM;ii++)
{
key_press = hal_gpiokey_pressed(ii);
gpio_key_cfg[ii].timer_count++;
//printf("%s:%d",__func__,gpio_key_cfg[ii].status);
switch(gpio_key_cfg[ii].status)
{
case APP_KEY_DEFAULE_STATUS:
if(key_press)
{
//按下,进消抖
gpio_key_cfg[ii].timer_count = 0;
gpio_key_cfg[ii].status = APP_KEY_DITHER_STATUS;
gpio_key_cfg[ii].event = APP_KEY_DEFAULE_EVENT;
}
else
{
gpio_key_cfg[ii].timer_count = 0;
}
break;
case APP_KEY_DITHER_STATUS:
if(key_press)
{
if(gpio_key_cfg[ii].timer_count * TIMER_PERIOD >= KEY_DEBOUNCE_INTERVAL_MS)
{
//消抖成功
gpio_key_cfg[ii].status = APP_KEY_DOWN_STATUS;
}
}
else
{
//消抖失败
gpio_key_cfg[ii].status = APP_KEY_DEFAULE_STATUS;
gpio_key_cfg[ii].timer_count = 0;
}
break;
case APP_KEY_DOWN_STATUS:
if(key_press)
{
//判断长按
if(gpio_key_cfg[ii].timer_count * TIMER_PERIOD >= KEY_LONGLONGPRESS_THRESHOLD_MS)
{
//发送长长按事件
if(gpio_key_cfg[ii].event == APP_KEY_LONGPRESS_EVENT)
{
// 长长按触发
gpio_key_cfg[ii].event = APP_KEY_LONGLONGPRESS_EVENT;
//mx_send_msg(MX_KEY_HANDLE,ii,gpio_key_cfg[ii].event);
if(ii == 0){
mx_ui_task_post(MX_LVGL_TASK_KEY_ID,gpio_key_cfg[ii].event);
}
}
}
else if(gpio_key_cfg[ii].timer_count * TIMER_PERIOD >= KEY_LONGPRESS_THRESHOLD_MS)
{
if(gpio_key_cfg[ii].event != APP_KEY_LONGPRESS_EVENT)
{
// 长按触发
gpio_key_cfg[ii].event = APP_KEY_LONGPRESS_EVENT;
//mx_send_msg(MX_KEY_HANDLE,ii,gpio_key_cfg[ii].event);
if(ii == 0){ // 发送事件
mx_ui_task_post(MX_LVGL_TASK_KEY_ID,gpio_key_cfg[ii].event);
} else if(ii == 1){
mx_ui_task_post(MX_LVGL_TASK_TOUCH_KEY_ID, gpio_key_cfg[ii].event);
}
}
}
}
else
{
if(gpio_key_cfg[ii].event == APP_KEY_DEFAULE_EVENT)
{
gpio_key_cfg[ii].event = APP_KEY_CLICK_EVENT;
gpio_key_cfg[ii].status = APP_KEY_INTERVAL_DEAL_STATUS;
}
else if(gpio_key_cfg[ii].event >= APP_KEY_CLICK_EVENT)
{
if(gpio_key_cfg[ii].event >= APP_KEY_EVENT_NUM)
{
gpio_key_cfg[ii].event = APP_KEY_EVENT_NUM;
}
else
{
gpio_key_cfg[ii].event++;
}
gpio_key_cfg[ii].status = APP_KEY_INTERVAL_DEAL_STATUS;
}
else
{
gpio_key_cfg[ii].status = APP_KEY_LONGPRESS_WAIT_RELEASE_STATUS;
}
gpio_key_cfg[ii].timer_count = 0;
}
break;
case APP_KEY_INTERVAL_DEAL_STATUS:
if(key_press)
{
//多击处理
gpio_key_cfg[ii].status = APP_KEY_DOWN_STATUS;
gpio_key_cfg[ii].timer_count = 0;
}
else
{
//多击等待
if(gpio_key_cfg[ii].timer_count * TIMER_PERIOD >= KEY_CHECKER_INTERVAL_MS)
{
//多击结束
gpio_key_cfg[ii].status = APP_KEY_DEFAULE_EVENT;
gpio_key_cfg[ii].timer_count = 0;
//mx_send_msg(MX_KEY_HANDLE,ii,gpio_key_cfg[ii].event);
// 多击触发
if(ii == 0){
mx_ui_task_post(MX_LVGL_TASK_KEY_ID,gpio_key_cfg[ii].event);
} else if(ii == 1){
mx_ui_task_post(MX_LVGL_TASK_TOUCH_KEY_ID, gpio_key_cfg[ii].event);
}
gpio_key_cfg[ii].event = APP_KEY_DEFAULE_EVENT;
}
}
break;
case APP_KEY_LONGPRESS_WAIT_RELEASE_STATUS:
if(!key_press)
{
gpio_key_cfg[ii].status = APP_KEY_DEFAULE_EVENT;
gpio_key_cfg[ii].timer_count = 0;
if(gpio_key_cfg[ii].event == APP_KEY_LONGLONGPRESS_EVENT)
{
}
else if(gpio_key_cfg[ii].event == APP_KEY_LONGPRESS_EVENT)
{
}
gpio_key_cfg[ii].event = APP_KEY_DEFAULE_EVENT;
}
break;
default:
gpio_key_cfg[ii].status = APP_KEY_DEFAULE_EVENT;
gpio_key_cfg[ii].timer_count = 0;
break;
}
}
}
static void mx_key_scan_init(void)
{
// 初始化按键
for(int i = 0; i < CFG_HW_GPIOKEY_NUM; i++) {
gpio_direction_input(gpio_key_cfg[i].key_config.port);
gpio_set_pull_down(gpio_key_cfg[i].key_config.port, gpio_key_cfg[i].key_config.config);
gpio_set_pull_up(gpio_key_cfg[i].key_config.port, !gpio_key_cfg[i].key_config.config);
gpio_set_die(gpio_key_cfg[i].key_config.port, 1);
}
// 定期扫描
sys_timer_add_to_task("sys_timer", NULL, hal_key_debounce_handler, TIMER_PERIOD);
}
static void mx_key_init(void)
{
if(production_test_io_get()){
return;
}
sys_timeout_add_to_task("sys_timer", NULL, mx_key_scan_init, 1000);//1秒后再检测按键
}
late_initcall(mx_key_init);
STM32F429IGT6利用状态机实现按键的非阻塞检测
其实原理都一样,跟杰里的AC791N的检测是差不多的,只不过需要一些配置而已。
sys_tick系统时钟配置
sys_tick.h:
#ifndef __SYS_TICK_H_
#define __SYS_TICK_H_
#include "stm32f4xx.h" // Device header
void sys_tick_init(void);
uint32_t sys_tick_get_time_ms(void);
#endif
sys_tick:
#include "sys_tick.h"
/*全局计数变量*/
static volatile uint32_t sys_tick_cnt = 0;
/**
* @brief 滴答定时器配置
* @note 定时时间1ms, SystemCoreClock = 180000000
* @param None
* @retval None
*/
void sys_tick_init(void)
{
if(SysTick_Config(SystemCoreClock / 1000)){
while(1); /*配置错误死循环*/
}
}
/**
* @brief SysTick时钟溢出回调,提供系统心跳节拍
* @note 需要注释掉stm32f4xx_it.c中原本的回调函数
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
sys_tick_cnt++;
}
/**
* @brief 获取计数时间
* @note None
* @param None
* @retval 当前计数时间
*/
uint32_t sys_tick_get_time_ms(void)
{
return sys_tick_cnt;
}
**注意:**在我的时钟树配置中,系统时钟就是180M,如果系统时钟布置180M的,需要根据系统配置进行修改!!!
按键配置
key.h:
#ifndef __KEY_H_
#define __KEY_H_
#include "stm32f4xx.h" // Device header
#include "gpio.h"
#include "sys_tick.h"
#define KEY_NUM (3) /*按键数量*/
#define KEY_SCANF_ENABLE (1) /*按键扫描开关*/
/*按键参数配置*/
#define KEY_DEBOUNCE_TIME_MS 20 /* 按键消抖时间(ms) */
#define KEY_LONG_PRESS_TIME_MS 2000 /* 长按判定时间(ms) */
#define KEY_DOUBLE_CLICK_GAP_MS 300 /* 双击间隔时间(ms) */
#define KEY_CONTINUOUS_PRESS_MS 100 /* 连击间隔时间(ms) */
/*触发电平配置*/
typedef enum{
GPIO_TRIGGER_LOW = 0X00,
GPIO_TRIGGER_HIGH,
} GPIO_TRIGGER_CFG;
/*按键状态配置*/
typedef enum{
KEY_STATUS_DEFAULT = 0X00,
KEY_STATUS_PRESS,
KEY_STATUS_DEBOUNCE,
KEY_STATUS_WAIT, // 等待第二次或者多次按下
KEY_STATUS_LONG_PRESS_WAIT_RELEASE, // 长按等待释放状态
} KEY_STATUS_CFG;
/*按键事件配置*/
typedef enum{
KEY_EVENT_DEFAULT = 0X00,
KEY_EVENT_LONG_PRESS,
KEY_EVENT_LONG_RELAX,
KEY_EVENT_CLICKED,
KEY_EVENT_DOUBLE_CLICKED,
KEY_EVENT_THIRD_CLICKED,
KEY_EVENT_FOUR_CLICKED,
KEY_EVENT_MORE_CLICKED,
} KEY_EVENT_CFG;
typedef struct{
GPIO_TypeDef *key_port;
uint16_t key_pin;
GPIOPuPd_TypeDef set_input;
} KEY_CFG;
/*按键配置*/
typedef struct{
KEY_CFG key_cofig;
GPIO_TRIGGER_CFG key_trigger;
KEY_STATUS_CFG key_status;
KEY_EVENT_CFG key_event;
uint32_t key_clicked_cnt; /*按键按下次数*/
uint32_t key_press_time; /* 按键按下时刻 */
uint32_t key_last_click_time; /* 上一次单击时刻 */
uint8_t key_valid; /* 按键是否有效 */
} KEY_CONFIG_T;
void key_gpio_init(void);
void key_scanf(void);
// 获取按键事件
KEY_EVENT_CFG key_get_event(uint8_t key_index);
#endif
key.c:
#include "key.h"
#if KEY_SCANF_ENABLE
/*按键配置初始化*/
KEY_CONFIG_T gpio_key_cfg[KEY_NUM] = {
{{GPIOH, GPIO_Pin_3, GPIO_PuPd_UP}, GPIO_TRIGGER_LOW, KEY_STATUS_DEFAULT, KEY_EVENT_DEFAULT, 0, 0, 0, 1},
{{GPIOH, GPIO_Pin_2, GPIO_PuPd_UP}, GPIO_TRIGGER_LOW, KEY_STATUS_DEFAULT, KEY_EVENT_DEFAULT, 0, 0, 0, 1},
{{GPIOC, GPIO_Pin_13, GPIO_PuPd_UP}, GPIO_TRIGGER_LOW, KEY_STATUS_DEFAULT, KEY_EVENT_DEFAULT, 0, 0, 0, 1},
};
/*按键初始化*/
void key_gpio_init(void)
{
for(uint8_t i = 0; i < KEY_NUM; i++){
gpio_config_begin(gpio_key_cfg[i].key_cofig.key_port, gpio_key_cfg[i].key_cofig.key_pin);
gpio_set_mode(GPIO_Mode_IN);
gpio_set_pupd(gpio_key_cfg[i].key_cofig.set_input);
gpio_config_apply();
}
}
static uint8_t key_gpio_read(uint8_t num)
{
return (GPIO_ReadInputDataBit(gpio_key_cfg[num].key_cofig.key_port, gpio_key_cfg[num].key_cofig.key_pin) == GPIO_TRIGGER_LOW);
}
static uint32_t get_tick_ms(void)
{
return sys_tick_get_time_ms();
}
void key_scanf(void)
{
uint8_t key_press;
uint32_t wait_time;
static uint32_t last_scan_time = 0;
uint32_t current_time = get_tick_ms();
/* 按键扫描间隔控制(10ms扫描一次) */
if(current_time – last_scan_time < 10) return;
last_scan_time = current_time;
for(uint8_t i = 0; i < KEY_NUM; i++){
if(!gpio_key_cfg[i].key_valid) continue;
key_press = key_gpio_read(i);
/* 如果已经有事件待处理,跳过本次扫描 */
if(gpio_key_cfg[i].key_event != KEY_EVENT_DEFAULT) {
continue;
}
switch(gpio_key_cfg[i].key_status)
{
case KEY_STATUS_DEFAULT:
if(key_press){ /* 检测到按键按下 */
gpio_key_cfg[i].key_status = KEY_STATUS_DEBOUNCE;
gpio_key_cfg[i].key_press_time = current_time;
}
break;
case KEY_STATUS_DEBOUNCE:
/* 消抖处理 */
if(current_time – gpio_key_cfg[i].key_press_time >= KEY_DEBOUNCE_TIME_MS){
if(key_gpio_read(i)){ /* 确认按键按下 */
gpio_key_cfg[i].key_status = KEY_STATUS_PRESS;
}else{ /* 抖动,回到默认状态 */
gpio_key_cfg[i].key_status = KEY_STATUS_DEFAULT;
}
}
break;
case KEY_STATUS_PRESS:
if(current_time – gpio_key_cfg[i].key_press_time >= KEY_LONG_PRESS_TIME_MS){
gpio_key_cfg[i].key_event = KEY_EVENT_LONG_PRESS;
gpio_key_cfg[i].key_status = KEY_STATUS_LONG_PRESS_WAIT_RELEASE;
gpio_key_cfg[i].key_clicked_cnt = 0; /* 长按后重置计数 */
break; /* 跳出当前按键的状态处理 */
}
if(!key_gpio_read(i)){ /* 按键释放 */
/* 短按释放,记录释放时间并进入等待状态 */
gpio_key_cfg[i].key_clicked_cnt++; /* 增加点击计数 */
gpio_key_cfg[i].key_last_click_time = current_time;
gpio_key_cfg[i].key_status = KEY_STATUS_WAIT;
}
break;
case KEY_STATUS_WAIT:
/* 等待第二次或者多次按下 */
wait_time = current_time – gpio_key_cfg[i].key_last_click_time;
if(wait_time > KEY_DOUBLE_CLICK_GAP_MS){ /* 等待超时 */
uint32_t click_count = gpio_key_cfg[i].key_clicked_cnt;
if(click_count == 1) {
gpio_key_cfg[i].key_event = KEY_EVENT_CLICKED;
} else if(click_count == 2) {
gpio_key_cfg[i].key_event = KEY_EVENT_DOUBLE_CLICKED;
} else if(click_count == 3) {
gpio_key_cfg[i].key_event = KEY_EVENT_THIRD_CLICKED;
} else if(click_count == 4) {
gpio_key_cfg[i].key_event = KEY_EVENT_FOUR_CLICKED;
} else if(click_count > 4) {
gpio_key_cfg[i].key_event = KEY_EVENT_MORE_CLICKED;
}
/* 触发事件后,重置状态 */
gpio_key_cfg[i].key_status = KEY_STATUS_DEFAULT;
gpio_key_cfg[i].key_clicked_cnt = 0;
} else if(key_gpio_read(i)){ /* 在等待时间内再次按下 */
gpio_key_cfg[i].key_status = KEY_STATUS_DEBOUNCE;
gpio_key_cfg[i].key_press_time = current_time;
}
break;
case KEY_STATUS_LONG_PRESS_WAIT_RELEASE:
/* 长按等待释放状态,检测按键是否释放 */
if(!key_gpio_read(i)){
/* 触发长按释放事件 */
gpio_key_cfg[i].key_event = KEY_EVENT_LONG_RELAX;
gpio_key_cfg[i].key_status = KEY_STATUS_DEFAULT;
gpio_key_cfg[i].key_clicked_cnt = 0;
}
break;
default:
gpio_key_cfg[i].key_status = KEY_STATUS_DEFAULT;
break;
}
}
}
/**
* @brief 获取按键事件
* @note 在调用该函数之前必须对按键IO口进行初始化,并且进行按键扫描
* @param key_index:需要获取事件的按键的IO口
* @retval 返回该IO口触发的按键事件
*/
KEY_EVENT_CFG key_get_event(uint8_t key_index)
{
if(key_index >= KEY_NUM || !gpio_key_cfg[key_index].key_valid){
return KEY_EVENT_DEFAULT;
}
KEY_EVENT_CFG event = gpio_key_cfg[key_index].key_event;
// 触发完成后清空按键事件
if(event != KEY_EVENT_DEFAULT) {
gpio_key_cfg[key_index].key_event = KEY_EVENT_DEFAULT;
}
return event;
}
#endif
下面是在main.c中运行:
#include "stm32f4xx.h"
#include "gpio.h"
#include "sys_tick.h"
#include "key.h"
void sys_init(void)
{
// 两个led灯的初始化
gpio_config_begin(GPIOB, GPIO_Pin_0);
gpio_config_apply();
gpio_config_begin(GPIOB, GPIO_Pin_1);
gpio_config_apply();
sys_tick_init();
key_gpio_init();
}
int main(void)
{
sys_init();
KEY_EVENT_CFG event0;
for(;;) {
key_scanf();
if((sys_tick_get_time_ms() / 1000) % 2 == 0){ // led0, 1s亮灭一次
GPIO_ResetBits(GPIOB, GPIO_Pin_0);
} else{
GPIO_SetBits(GPIOB, GPIO_Pin_0);
}
event0 = key_get_event(0);
if(event0 == KEY_EVENT_LONG_PRESS){ // led1由按键控制亮灭
GPIO_SetBits(GPIOB, GPIO_Pin_1);
} else if(event0 == KEY_EVENT_LONG_RELAX){
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}
}
}
附上:上面代码中的led灯gpio的配置
gpio做了普适性适配,可通过接口修改配置,也可以使用默认配置。
gpio.h:
#ifndef __GPIO_H_
#define __GPIO_H_
#include "stm32f4xx.h"
/*空指针定义*/
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
/**
* @brief 获取GPIO端口对应的时钟使能位
* @param GPIOx: GPIO端口,可以是GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG, GPIOH, GPIOI
* @return 对应的RCC时钟使能位
*/
#define GET_GPIO_CLOCK_ENABLE(GPIOx) \\
((GPIOx) == GPIOA ? RCC_AHB1Periph_GPIOA : \\
(GPIOx) == GPIOB ? RCC_AHB1Periph_GPIOB : \\
(GPIOx) == GPIOC ? RCC_AHB1Periph_GPIOC : \\
(GPIOx) == GPIOD ? RCC_AHB1Periph_GPIOD : \\
(GPIOx) == GPIOE ? RCC_AHB1Periph_GPIOE : \\
(GPIOx) == GPIOF ? RCC_AHB1Periph_GPIOF : \\
(GPIOx) == GPIOG ? RCC_AHB1Periph_GPIOG : \\
(GPIOx) == GPIOH ? RCC_AHB1Periph_GPIOH : \\
(GPIOx) == GPIOI ? RCC_AHB1Periph_GPIOI :\\
(GPIOx) == GPIOJ ? RCC_AHB1Periph_GPIOJ : \\
(GPIOx) == GPIOK ? RCC_AHB1Periph_GPIOK : 0)
/**
* @brief 判断GPIO端口挂载的总线类型
* @param GPIOx: GPIO端口
* @return 总线类型:RCC_APB2Periph_GPIOx, RCC_APB1Periph_GPIOx, RCC_AHB1Periph_GPIOx等
* @note 对于STM32F429IGT6,所有GPIO都挂在AHB1总线上
*/
#define GET_GPIO_BUS_TYPE(GPIOx) RCC_AHB1Periph_GPIOx
/**
* @brief 使能GPIO端口时钟
* @param GPIOx: GPIO端口
* @note 根据GPIO端口自动选择对应的时钟使能位,并开启AHB1总线时钟
*/
#define GPIO_CLOCK_ENABLE(GPIOx) \\
do { \\
uint32_t rcc_periph = GET_GPIO_CLOCK_ENABLE(GPIOx); \\
if (rcc_periph != 0) { \\
RCC_AHB1PeriphClockCmd(rcc_periph, ENABLE); \\
} \\
} while(0)
/**
* @brief 关闭GPIO端口时钟
* @param GPIOx: GPIO端口
*/
#define GPIO_CLOCK_DISABLE(GPIOx) \\
do { \\
uint32_t rcc_periph = GET_GPIO_CLOCK_ENABLE(GPIOx); \\
if (rcc_periph != 0) { \\
RCC_AHB1PeriphClockCmd(rcc_periph, DISABLE); \\
} \\
} while(0)
#endif
/**
* @brief 开始配置一个新的GPIO引脚(自动重置为默认配置)
* @note 每次开始配置引脚前都要先调用该接口
* @param GPIOx: GPIO端口
* @param GPIO_Pin_x: GPIO引脚
* @note 自动重置为默认配置,并记录引脚信息
*/
void gpio_config_begin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin_x);
/**
* @brief 设置GPIO模式
* @param mode: GPIO模式
*/
void gpio_set_mode(GPIOMode_TypeDef mode);
/**
* @brief 设置GPIO输出类型(仅对输出和复用模式有效)
* @param type: GPIO输出类型
*/
void gpio_set_output_type(GPIOOType_TypeDef type);
/**
* @brief 设置GPIO上下拉电阻
* @param pupd: GPIO上下拉配置
*/
void gpio_set_pupd(GPIOPuPd_TypeDef pupd);
/**
* @brief 设置GPIO速度
* @param speed: GPIO速度
*/
void gpio_set_speed(GPIOSpeed_TypeDef speed);
/**
* @brief 应用配置到硬件(执行GPIO_Init)
* @note 使能时钟,执行GPIO初始化,然后自动重置内部状态,引脚配置完成后必须调用
*/
void gpio_config_apply(void);
gpio.c:
#include "gpio.h"
/*静态全局变量定义*/
GPIO_InitTypeDef GPIO_InitStructure;
/* 默认配置结构体 */
typedef struct {
GPIOMode_TypeDef mode;
GPIOOType_TypeDef type;
GPIOPuPd_TypeDef pupd;
GPIOSpeed_TypeDef speed;
} GPIO_Config;
/* 默认配置定义 */
GPIO_Config gpio_default_config = {
.mode = GPIO_Mode_OUT,
.type = GPIO_OType_PP,
.pupd = GPIO_PuPd_NOPULL,
.speed = GPIO_Speed_100MHz
};
/* 当前配置*/
static GPIO_Config *current_config = &gpio_default_config;
/* 当前引脚信息*/
static struct {
GPIO_TypeDef* GPIOx;
uint16_t GPIO_Pin_x;
uint8_t clock_enabled; /* 时钟是否已使能 */
} current_pin_info = {NULL, 0, 0};
/**
* @brief 开始配置一个新的GPIO引脚(自动重置为默认配置)
* @param GPIOx: GPIO端口
* @param GPIO_Pin_x: GPIO引脚
* @note 自动重置为默认配置,并记录引脚信息
*/
void gpio_config_begin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin_x)
{
/* 重置为默认配置 */
current_config = &gpio_default_config;
/* 记录引脚信息 */
current_pin_info.GPIOx = GPIOx;
current_pin_info.GPIO_Pin_x = GPIO_Pin_x;
current_pin_info.clock_enabled = 0;
}
/**
* @brief 设置GPIO模式
* @param mode: GPIO模式
*/
void gpio_set_mode(GPIOMode_TypeDef mode)
{
current_config->mode = mode;
}
/**
* @brief 设置GPIO输出类型(仅对输出和复用模式有效)
* @param type: GPIO输出类型
*/
void gpio_set_output_type(GPIOOType_TypeDef type)
{
current_config->type = type;
}
/**
* @brief 设置GPIO上下拉电阻
* @param pupd: GPIO上下拉配置
*/
void gpio_set_pupd(GPIOPuPd_TypeDef pupd)
{
current_config->pupd = pupd;
}
/**
* @brief 设置GPIO速度
* @param speed: GPIO速度
*/
void gpio_set_speed(GPIOSpeed_TypeDef speed)
{
current_config->speed = speed;
}
/**
* @brief 应用配置到硬件(执行GPIO_Init)
* @note 使能时钟,执行GPIO初始化,然后自动重置内部状态
*/
void gpio_config_apply(void)
{
if (current_pin_info.GPIOx == NULL) {
return; /* 引脚信息无效,未调用gpio_config_begin */
}
/* 使能GPIO时钟 */
if (!current_pin_info.clock_enabled) {
GPIO_CLOCK_ENABLE(current_pin_info.GPIOx);
current_pin_info.clock_enabled = 1;
}
/* 配置GPIO初始化结构体 */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = current_config->mode;
GPIO_InitStructure.GPIO_Pin = current_pin_info.GPIO_Pin_x;
GPIO_InitStructure.GPIO_PuPd = current_config->pupd;
GPIO_InitStructure.GPIO_Speed = current_config->speed;
/* 仅对输出模式和复用模式设置输出类型 */
if (current_config->mode == GPIO_Mode_OUT ||
current_config->mode == GPIO_Mode_AF) {
GPIO_InitStructure.GPIO_OType = current_config->type;
}
/* 执行初始化 */
GPIO_Init(current_pin_info.GPIOx, &GPIO_InitStructure);
/* 重置内部状态*/
current_pin_info.GPIOx = NULL;
current_pin_info.GPIO_Pin_x = 0;
current_pin_info.clock_enabled = 0;
}
**注意:**在使用上面的gpio配置使,必须以gpio_config_begin这个函数开始,必须以gpio_config_apply这个函数结束,才能完成对一个gpio的配置!!!
网硕互联帮助中心








评论前必须登录!
注册