文章目录
使用芯片:STM32H743XIH6 .
开发板:安富莱V7
1. GPIO
注意事项:
- 对于不使用的引脚,推荐设置为模拟模式,悬空即可。
- GPIO 的速度等级高的时候,最好使能 IO 补偿单元。
- GPIO 还涉及到一个注入电流的问题,详见:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87675 .
- STM32H7的GPIO控制三极管驱动各种负载的安全措施和注意事项
1.1 GPIIO 功能模式分析
STM32H7 的 GPIO 特性如下:
- 输出状态:开漏/推挽 + 上拉/下拉电阻。
- 通过输出数据寄存器(GPIOx_ODR)或者外设(GPIO 设置为复用模式时)输出数据。
- GPIO 速度等级设置。
- 输入状态:浮空,上拉/下拉,模拟。
- 通过输入数据寄存器(GPIOx_IDR)或者外设(GPIO 设置为复用模式)输入数据。
- 通过寄存器 GPIOx_BSRR 实现对寄存器 GPIOx_ODR 的位操作。
- 通过配置寄存器 GPIOx_LCKR 的锁机制,实现冻结 IO 口配置。
- 每两个时钟周期就可以翻转一次 IO。
- 高度灵活的引脚复用功能,允许 IO 引脚既可以做 GPIO 也可以做功能复用。
STM32H7 的 GPIO 端口可以配置为如下的 8 种模式:
11. 输入浮空
12. 输入上拉
13. 输入下拉
14. 模拟功能
15. 具有上拉或下拉功能的开漏输出
16. 具有上拉或下拉功能的推挽输出
17. 具有上拉或下拉功能的复用功能推挽
18. 具有上拉或下拉功能的复用功能开漏
另外,由于上拉和下拉是可选配置,对应的 HAL 库配置使用下面 6 种就可以表示:
19. GPIO_MODE_INPUT 输入模式
20. GPIO_MODE_OUTPUT_PP 推挽输出
21. GPIO_MODE_OUTPUT_OD 开漏输出
22. GPIO_MODE_AF_PP 复用推挽
23. GPIO_MODE_AF_OD 复用开漏
24. GPIO_MODE_ANALOG 模拟模式
1.2 四种输出模式
1.2.1 推挽输出
推挽电路是两个参数相同的三极管或 MOSFET,以推挽方式存在于电路中。 电路工作时,两只对称的开关管每次只有一个导通,导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级提高电路的负载能力。 相对于开漏输出模式,推挽输出最大优势是输出高电平时,上升时间快,电压驱动能力强。
1.2.2 开漏输出
开漏端相当于 MOS 管的漏极(三极管的集电极),要得到高电平状态必须外接上拉电阻才行,因此输出高电平的驱动能力完全由外接上拉电阻决定,但是其输出低电平的驱动能力很强。
开漏形式的电路有以下几个特点:
- 输出高电平时利用外部电路的驱动能力,减少 IC 内部的驱动。
- 开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平。如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低,功耗越小。
- 开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
- 可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑 0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为 0,只有都为高电平时,与的结果才为逻辑 1 .
1.2.3 复用推挽和开漏
复用指的是 GPIO 切换到 CPU 内部设备(比如 SPI,I2C,UART 等电路),也就是 GPIO 不是作为普通IO 使用,是由内部设备直接驱动。推挽和开漏的特征同上。
1.3 四种输入模式
- 浮空输入:CPU 内部的上拉电阻、下拉电阻均断开的输入模式。
- 下拉输入:CPU 内部的下拉电阻使能、上拉电阻断开的输入模式。
- 上拉输入:CPU 内部的上拉电阻使能、下拉电阻断开的输入模式。
- 模拟输入模式: GPIO 引脚直接连接内部 ADC。
任何不使用的引脚都推荐设置为模拟输入模式:主要从功耗和防干扰考虑。
- 所有用作带上拉电阻输入的 I/O 都会在引脚外部保持为低时产生电流消耗。此电流消耗的值可通过使用的静态特性中给出的上拉 / 下拉电阻值简单算出。
- 对于输出引脚,还必须考虑任何外部下拉电阻或外部负载以估计电流消耗。
- 若外部施加了中间电平,则额外的 I/O 电流消耗是因为配置为输入的 I/O。此电流消耗是由用于区分输入值的输入施密特触发器电路导致。除非应用需要此特定配置,否则可通过将这些 I/O 配置为模拟模式以避免此供电电流消耗。 ADC 输入引脚应配置为模拟输入就是这种情况。
- 任何浮空的输入引脚都可能由于外部电磁噪声,成为中间电平或意外切换。为防止浮空引脚相关的电流消耗,它们必须配置为模拟模式,或内部强制为确定的数字值。这可通过使用上拉 / 下拉电阻或将引脚配置为输出模式做到。
1.4 GPIO 的拉电流负载和灌电流负载能力
- 拉电流负载:一种负载电流从驱动门流向外电路,称为拉电流负载。比如使用 STM32H7 的 GPIO 直接驱动 LED (MCU输出高电平)就是拉电流形式。
- 灌电流负载:负载电流从外电路流入驱动门,称为灌电流负载。如下面这种形式的 LED 驱动电路。
- STM32H7 的 IO 驱动能力(截图来自 STM32H7 数据手册):通过截图可知STM32H7 总的拉电流和灌电流不可超过 140mA,单个引脚灌电流最大不可超过 20mA .
1.5 IO 补偿单元(高速IO)
IO 补偿单元用于控制 I/O 通信压摆率(tfall / trise)以此来降低 I/O 噪声。当前 STM32H7 的速度等级可以配置为以下四种:
/** @defgroup GPIO_speed_define GPIO speed define * @brief GPIO Output Maximum frequency * @{ */
#define GPIO_SPEED_FREQ_LOW ((uint32_t)0x00000000U) /*!< Low speed */
#define GPIO_SPEED_FREQ_MEDIUM ((uint32_t)0x00000001U) /*!< Medium speed */
#define GPIO_SPEED_FREQ_HIGH ((uint32_t)0x00000002U) /*!< Fast speed */
#define GPIO_SPEED_FREQ_VERY_HIGH ((uint32_t)0x00000003U) /*!< High speed */
使用后两种速度等级的话,最好使能 IO 补偿单元。另外不同速度等级下,IO 补偿使能与否对 IO 最大速度的影响可以看此贴:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87677
1.6 GPIIO 兼容 CMOS 和 TTL 电平
下面是CMOS、TTL电平图:
1.* 【实验】无源蜂鸣器控制
资料来源:安富莱STM32H7 开发板
1.*.1 蜂鸣器简介
蜂鸣器主要有电磁式和电压式两种,而且都有无源蜂鸣器和有源蜂鸣器两类。这里使用的是电磁式无源蜂鸣器。
- 有源和无源的区别:有源蜂鸣器内部自带振荡器,给个电压就能发声,但频率是固定的,只能发出一种声音,而无源蜂鸣器频率可控,给个方波才可以发声,并且根据不同频率发出不同的声音效果。
1.*.2 电路设计
1.*.3 程序设计
1.*.3.1 bsp_beep.h
- 控制蜂鸣器的结构体变量:
typedef struct _BEEP_T
{
uint8_t ucEnalbe; // 用于使能或者禁止蜂鸣器
uint8_t ucState; // 状态变量,用于蜂鸣器鸣叫和停止的区分
uint16_t usBeepTime; // 鸣叫时间,单位 10ms
uint16_t usStopTime; // 停止鸣叫时间,单位 10ms
uint16_t usCycle; // 鸣叫和停止的循环次数
uint16_t usCount; // 用于鸣叫和停止时的计数
uint16_t usCycleCount; // 用于循环次数计数
uint8_t ucMute; // 用于静音
}BEEP_T;
1.*.3.2 bsp_beep.c
- 宏定义与全局变量:
include "bsp.h"
//#define BEEP_HAVE_POWER /* 定义此行表示有源蜂鸣器,直接通过GPIO驱动, 无需PWM , 不定义即使用无源蜂鸣器*/
#ifdef BEEP_HAVE_POWER /* 有源蜂鸣器 */
/* PA8 */
#define GPIO_RCC_BEEP RCC_AHB1Periph_GPIOA
#define GPIO_PORT_BEEP GPIOA
#define GPIO_PIN_BEEP GPIO_PIN_8
#define BEEP_ENABLE() GPIO_PORT_BEEP->BSRRL = GPIO_PIN_BEEP /* 使能蜂鸣器鸣叫 */
#define BEEP_DISABLE() GPIO_PORT_BEEP->BSRRH = GPIO_PIN_BEEP /* 禁止蜂鸣器鸣叫 */
#else /* 无源蜂鸣器 */
/* PA0 ---> TIM5_CH1 */
/* 1500表示频率1.5KHz,5000表示50.00%的占空比 */
#define BEEP_ENABLE() bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_0, TIM5, 1, 1500, 5000);
/* 禁止蜂鸣器鸣叫 */
#define BEEP_DISABLE() bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_0, TIM5, 1, 1500, 0);
#endif
BEEP_T g_tBeep; /* 定义蜂鸣器全局结构体变量 */
- 初始化蜂鸣器硬件:
/* ********************************************************************************************************* * 函 数 名: BEEP_InitHard * 功能说明: 初始化蜂鸣器硬件 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_InitHard(void)
{
#ifdef BEEP_HAVE_POWER /* 有源蜂鸣器 */
GPIO_InitTypeDef GPIO_InitStructure;
/* 打开GPIOF的时钟 */
RCC_AHB1PeriphClockCmd(GPIO_RCC_BEEP, ENABLE);
BEEP_DISABLE();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; /* 设为输出口 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; /* 设为推挽模式 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; /* 上下拉电阻不使能 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /* IO口最大速度 */
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_BEEP;
GPIO_Init(GPIO_PORT_BEEP, &GPIO_InitStructure); // 初始化GPIO
#endif
g_tBeep.ucMute = 0; /* 关闭静音 */
}
- 启动蜂鸣音:
/* ********************************************************************************************************* * 函 数 名: BEEP_Start * 功能说明: 启动蜂鸣音。 * 形 参: _usBeepTime : 蜂鸣时间,单位10ms; 0 表示不鸣叫 * _usStopTime : 停止时间,单位10ms; 0 表示持续鸣叫 * _usCycle : 鸣叫次数, 0 表示持续鸣叫 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Start(uint16_t _usBeepTime, uint16_t _usStopTime, uint16_t _usCycle)
{
if (_usBeepTime == 0 || g_tBeep.ucMute == 1)
{
return;
}
g_tBeep.usBeepTime = _usBeepTime; // 设置蜂鸣时间,单位 10ms,配置为 0 表示不鸣叫
g_tBeep.usStopTime = _usStopTime; // 设置蜂鸣时间,单位 10ms,配置为 0 表示不鸣叫
g_tBeep.usCycle = _usCycle; // 设置鸣叫次数,配置为 0 表示持续鸣叫
g_tBeep.usCount = 0;
g_tBeep.usCycleCount = 0;
g_tBeep.ucState = 0;
g_tBeep.ucEnalbe = 1; /* 设置完全局参数后再使能发声标志 */
BEEP_ENABLE(); /* 开始发声 */
}
/* ********************************************************************************************************* * 函 数 名: BEEP_KeyTone * 功能说明: 发送按键音 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_KeyTone(void)
{
BEEP_Start(5, 1, 1); /* 鸣叫50ms,停10ms, 1次 */
}
/* ********************************************************************************************************* * 函 数 名: BEEP_Pro * 功能说明: 此函数需每隔10ms调用1次该函数,用于控制蜂鸣器发声。该函数在 bsp_timer.c 中被调用。 * 该函数是蜂鸣器的主处理函数,用于实现鸣叫时间、停止鸣叫时间和循环次数的处理。 * 形 参: 无 * 返 回 值: 无 * 特别说明: * 1. 如果是裸机使用,将此函数放在 bsp.c 文件的 bsp_RunPer10ms 函数里面即可,这个函数是由滴答定时器调用的,前提是定时器的初始化函数 bsp_InitTimer 一定要调用。 2. 如果是 RTOS 使用,需要开启一个 10ms 为周期的任务调用函数 BEEP_Pro . ********************************************************************************************************* */
void BEEP_Pro(void)
{
if ((g_tBeep.ucEnalbe == 0) || (g_tBeep.usStopTime == 0) || (g_tBeep.ucMute == 1))
{
return;
}
if (g_tBeep.ucState == 0)
{
if (g_tBeep.usStopTime > 0) /* 间断发声 */
{
if (++g_tBeep.usCount >= g_tBeep.usBeepTime)
{
BEEP_DISABLE(); /* 停止发声 */
g_tBeep.usCount = 0;
g_tBeep.ucState = 1;
}
}
else
{
; /* 不做任何处理,连续发声 */
}
}
else if (g_tBeep.ucState == 1)
{
if (++g_tBeep.usCount >= g_tBeep.usStopTime)
{
/* 连续发声时,直到调用stop停止为止 */
if (g_tBeep.usCycle > 0)
{
if (++g_tBeep.usCycleCount >= g_tBeep.usCycle)
{
/* 循环次数到,停止发声 */
g_tBeep.ucEnalbe = 0;
}
if (g_tBeep.ucEnalbe == 0)
{
g_tBeep.usStopTime = 0;
return;
}
}
g_tBeep.usCount = 0;
g_tBeep.ucState = 0;
BEEP_ENABLE(); /* 开始发声 */
}
}
}
- 停止蜂鸣音:
/* ********************************************************************************************************* * 函 数 名: BEEP_Stop * 功能说明: 停止蜂鸣音。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Stop(void)
{
g_tBeep.ucEnalbe = 0;
if ((g_tBeep.usStopTime == 0) || (g_tBeep.usCycle == 0))
{
BEEP_DISABLE(); /* 必须在清控制标志后再停止发声,避免停止后在中断中又开启 */
}
}
- 暂停/恢复蜂鸣音:
/* ********************************************************************************************************* * 函 数 名: BEEP_Pause * 功能说明: 由于TIM冲突等原因,临时屏蔽蜂鸣音。通过 BEEP_Resume 恢复 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Pause(void)
{
BEEP_Stop();
g_tBeep.ucMute = 1; /* 静音 */
}
/* ********************************************************************************************************* * 函 数 名: BEEP_Resume * 功能说明: 恢复蜂鸣器正常功能 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Resume(void)
{
BEEP_Stop();
g_tBeep.ucMute = 0; /* 静音 */
}
2. GPIO FIFO
FIFO 是 First Input First Output 的缩写,先入先出队列。下面以按键FIFO 实验进行讲解。
这里以已被定义的 5 个字节的 FIFO 空间进行说明。Write 变量表示写位置,Read 变量表示读位置。初始状态时,Read = Write = 0,每向队列写入一个数据,则Write 加1 ,同理,每从队列中读出一个数据,Read 加1 ,直到Write= Read ,表示没有新的读写时间发生。
如果 Write!= Read,则认为有新的按键事件。现在通过函数 bsp_GetKey
读取一个按键值进行处理后,Read 变量变为 1,而Write 变量不变。
继续通过函数 bsp_GetKey
读取 3 个按键值进行处理后,Read 变量变为 4 . 此时 Read = Write = 4 ,表示已经没有新的按键事件需要处理。
另外值得注意的是,如果 FIFO 空间写满了,Write 会自动被重新赋值为 0,即重新从第一个字节空间填数据进去,如果这个地址空间的数据未被及时读取,那么则会被后来的数据覆盖掉。
设计按键 FIFO 主要有三个方面的好处:
- 可靠地记录每一个按键事件,避免遗漏按键事件。特别是需要实现按键的按下、长按、自动连发、弹起等事件时。
- 读取按键的函数可以设计为非阻塞的,不需要等待按键抖动滤波处理完毕。
- 按键 FIFO 程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效地降低系统资源消耗。
2.1 程序设计
程序运行框图:
2.1.1 bsp_key.h
下面按键事件的定义推荐使用枚举 enum, 不用#define
的原因:
- 便于新增键值,方便调整顺序。
- 使用{ } 将一组相关的定义封装起来便于理解。
- 编译器可帮我们避免键值重复。
对于简单的程序,可以借用按键 FIFO 来进行少量的信息传递。对于复杂的应用,推荐使用
bsp_msg
专门来做这种任务间的通信。因为bsp_msg
除了传递消息代码外,还可以传递参数结构。
#define KEY_FIFO_SIZE 10 // 定义FIFO 队列大小为10
typedef struct // 定义FIFO 结构体
{
uint8_t Buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
uint8_t Read; /* 缓冲区读指针 1 */
uint8_t Write; /* 缓冲区写指针 */
uint8_t Read2; /* 缓冲区读指针 2 */
}KEY_FIFO_T;
#define KEY_COUNT 10 /* 按键个数, 8 个独立建 + 2 个组合键 */
// 每个按键对象都分配一个结构体变量,这些结构体变量以数组的形式存在将便于简化程序代码行数。
// 使用函数指针 IsKeyDownFunc 可以将每个按键的检测以及组合键的检测代码进行统一管理。
typedef struct
{
/* 下面是一个函数指针,指向判断按键手否按下的函数 */
uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1 表示按下 */
uint8_t Count; /* 滤波器计数器 */
uint16_t LongCount; /* 长按计数器 */
uint16_t LongTime; /* 按键按下持续时间, 0 表示不检测长按 */
uint8_t State; /* 按键当前状态(按下还是弹起) */
uint8_t RepeatSpeed; /* 连续按键周期 */
uint8_t RepeatCount; /* 连续按键计数器 */
}KEY_T;
// 按键事件
typedef enum
{
KEY_NONE = 0, /* 0 表示按键事件 */
KEY_1_DOWN, /* 1 键按下 */
KEY_1_UP, /* 1 键弹起 */
KEY_1_LONG, /* 1 键长按 */
KEY_2_DOWN, /* 2 键按下 */
KEY_2_UP, /* 2 键弹起 */
KEY_2_LONG, /* 2 键长按 */
KEY_3_DOWN, /* 3 键按下 */
KEY_3_UP, /* 3 键弹起 */
KEY_3_LONG, /* 3 键长按 */
KEY_4_DOWN, /* 4 键按下 */
KEY_4_UP, /* 4 键弹起 */
KEY_4_LONG, /* 4 键长按 */
KEY_5_DOWN, /* 5 键按下 */
KEY_5_UP, /* 5 键弹起 */
KEY_5_LONG, /* 5 键长按 */
KEY_6_DOWN, /* 6 键按下 */
KEY_6_UP, /* 6 键弹起 */
KEY_6_LONG, /* 6 键长按 */
KEY_7_DOWN, /* 7 键按下 */
KEY_7_UP, /* 7 键弹起 */
KEY_7_LONG, /* 7 键长按 */
KEY_8_DOWN, /* 8 键按下 */
KEY_8_UP, /* 8 键弹起 */
KEY_8_LONG, /* 8 键长按 */
/* 组合键 */
KEY_9_DOWN, /* 9 键按下 */
KEY_9_UP, /* 9 键弹起 */
KEY_9_LONG, /* 9 键长按 */
KEY_10_DOWN, /* 10 键按下 */
KEY_10_UP, /* 10 键弹起 */
KEY_10_LONG, /* 10 键长按 */
}KEY_ENUM;
/* 按键滤波时间 50ms, 单位 10ms。 只有连续检测到 50ms 状态不变才认为有效,包括弹起和按下两种事件 即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件 */
#define KEY_FILTER_TIME 5
#define KEY_LONG_TIME 100 /* 单位 10ms, 持续 1 秒,认为长按事件 */
2.1.2 bsp_key.c
static KEY_FIFO_T s_tKey; /* 定义 FIFO 结构体变量 */
static KEY_T s_tBtn[KEY_COUNT];
static KEY_FIFO_T s_tKey; /* 按键 FIFO 变量,结构体 */
/* ********************************************************************************************************* * 函 数 名: bsp_PutKey * 功能说明: 将 1 个键值压入按键 FIFO 缓冲区。可用于模拟一个按键。 * 形 参: _KeyCode : 按键代码 * 返 回 值: 无 * 说明: 当按键事件触发后,即可调用该函数,将键值写入到FIFO 队列 ********************************************************************************************************* */
void bsp_PutKey(uint8_t _KeyCode)
{
s_tKey.Buf[s_tKey.Write] = _KeyCode;
if (++s_tKey.Write >= KEY_FIFO_SIZE)
{
s_tKey.Write = 0;
}
}
/* ********************************************************************************************************* * 函 数 名: bsp_GetKey * 功能说明: 从按键 FIFO 缓冲区读取一个键值。 * 形 参: 无 * 返 回 值: 按键代码 * 说明: 读取FIFO 队列中的值 ********************************************************************************************************* */
uint8_t bsp_GetKey(void)
{
uint8_t ret;
if (s_tKey.Read == s_tKey.Write) // 若Read = Write,则认为没有新事件需要处理
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read];
if (++s_tKey.Read >= KEY_FIFO_SIZE)
{
s_tKey.Read = 0;
}
return ret;
}
}
/* ********************************************************************************************************* * 函 数 名: bsp_GetKey2 * 功能说明: 从按键 FIFO 缓冲区读取一个键值。独立的读指针。 * 形 参: 无 * 返 回 值: 按键代码 ********************************************************************************************************* */
uint8_t bsp_GetKey2(void)
{
uint8_t ret;
if (s_tKey.Read2 == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read2];
if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
{
s_tKey.Read2 = 0; // 按键缓冲区为空,即所有的按键时间已经处理完毕
}
return ret;
}
}
/* ********************************************************************************************************* * 函 数 名: bsp_InitKey * 功能说明: 初始化按键. 该函数被 bsp_Init() 调用。 * 形 参: 无 * 返 回 值: 无 * 说 明:因为函数指针必须先赋值,才能被作为函数执行。因此在定时扫描按键之前,必须先执行下面初始化函数(即bsp_InitKey)来设置每个按键的函数指针和参数。 ********************************************************************************************************* */
void bsp_InitKey(void)
{
bsp_InitKeyVar(); /* 初始化按键变量 */
bsp_InitKeyHard(); /* 初始化按键硬件 */
}
/* ********************************************************************************************************* * 函 数 名: bsp_InitKeyVar * 功能说明: 初始化按键变量 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
static void bsp_InitKeyVar(void)
{
uint8_t i;
/* 对按键 FIFO 读写指针清零 */
s_tKey.Read = 0;
s_tKey.Write = 0;
s_tKey.Read2 = 0;
/* 给每个按键结构体成员变量赋一组缺省值 */
for (i = 0; i < KEY_COUNT; i++)
{
s_tBtn[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[i].Count = KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 ,没有设置为 0,是为了避免主板上电的瞬间,检测到一个无效按键按下或弹起事件。*/
s_tBtn[i].State = 0; /* 按键缺省状态,0 为未按下 */
s_tBtn[i].RepeatSpeed = 0; /* 按键连发的速度,0 表示不支持连发 */
s_tBtn[i].RepeatCount = 0; /* 连发计数器 */
}
/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
/* 摇杆上下左右,支持长按 1 秒后,自动连发 */
bsp_SetKeyParam(KID_JOY_U, 100, 6);
bsp_SetKeyParam(KID_JOY_D, 100, 6);
bsp_SetKeyParam(KID_JOY_L, 100, 6);
bsp_SetKeyParam(KID_JOY_R, 100, 6);
}
/* ********************************************************************************************************* * 函 数 名: KeyPinActive * 功能说明: 判断按键是否按下 * 形 参: 无 * 返 回 值: 返回值 1 表示按下(导通),0 表示未按下(释放) ********************************************************************************************************* */
static uint8_t KeyPinActive(uint8_t _id)
{
uint8_t level;
if ((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0)
{
level = 0;
}
else
{
level = 1;
}
if (level == s_gpio_list[_id].ActiveLevel)
{
return 1;
}
else
{
return 0;
}
}
/* ********************************************************************************************************* * 函 数 名: IsKeyDownFunc * 功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。 * 形 参: 无 * 返 回 值: 返回值 1 表示按下(导通),0 表示未按下(释放) ********************************************************************************************************* */
static uint8_t IsKeyDownFunc(uint8_t _id)
{
/* 实体单键 */
if (_id < HARD_KEY_NUM)
{
uint8_t i;
uint8_t count = 0;
uint8_t save = 255;
/* 判断有几个键按下 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
if (KeyPinActive(i))
{
count++;
save = i;
}
}
if (count == 1 && save == _id)
{
return 1; /* 只有 1 个键按下时才有效 */
}
return 0;
}
/* 组合键 K1K2 */
if (_id == HARD_KEY_NUM + 0)
{
if (KeyPinActive(KID_K1) && KeyPinActive(KID_K2))
{
return 1;
}
else
{
return 0;
}
}
/* 组合键 K2K3 */
if (_id == HARD_KEY_NUM + 1)
{
if (KeyPinActive(KID_K2) && KeyPinActive(KID_K3))
{
return 1;
}
else
{
return 0;
}
}
return 0;
}
/* ********************************************************************************************************* * 函 数 名: bsp_InitKeyHard * 功能说明: 配置按键对应的 GPIO * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
static void bsp_InitKeyHard(void)
{
GPIO_InitTypeDef gpio_init;
uint8_t i;
/* 第 1 步:打开 GPIO 时钟 */
ALL_KEY_GPIO_CLK_ENABLE();
/* 第 2 步:配置所有的按键 GPIO 为浮动输入模式(实际上 CPU 复位后就是输入状态) */
gpio_init.Mode = GPIO_MODE_INPUT; /* 设置输入 */
gpio_init.Pull = GPIO_NOPULL; /* 上下拉电阻不使能 */
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* GPIO 速度等级 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
gpio_init.Pin = s_gpio_list[i].pin;
HAL_GPIO_Init(s_gpio_list[i].gpio, &gpio_init);
}
}
void bsp_RunPer10ms(void)
{
bsp_KeyScan10ms(); /* 扫描按键 */
}
/* ********************************************************************************************************* * 函 数 名: bsp_KeyScan10ms * 功能说明: 扫描所有按键。非阻塞,被 systick 中断周期性的调用,每10ms 执行一次 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void bsp_KeyScan10ms(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectKey(i);
}
}
2.1.3 main.c
#include "bsp.h"
int main(void)
{
uint8_t ucKeyCode;
bsp_Init();
IRD_StartWork(); /* 启动红外解码 */
while(1)
{
bsp_Idle();
/* 处理按键事件 */
ucKeyCode = bsp_GetKey();
if (ucKeyCode > 0)
{
/* 有按键按下 */
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1 键按下 */
bsp_LedOn(1); /* 点亮 LED1 */
break;
case KEY_UP_K1: /* K1 键弹起 */
bsp_LedOff(1); /* 熄灭 LED1 */
break;
case IR_KEY_POWER: /* 遥控器 POWER 键按下 */
bsp_LedOn(1); /* 点亮 LED2 */
break;
case IR_KEY_MENU: /* 遥控器 MENU 键按下 */
bsp_LedOff(1); /* 熄灭 LED2 */
break;
case MSG_485_RX: /* 通信程序的发来的消息 */
/* 执行通信程序的指令 */
break;
default:
break;
}
}
}
}
2.2 按键检测采用中断方式或查询方式
结论:推荐使用 GPIO 查询方式。
-
从裸机的角度分析:
- 中断方式:中断方式可以快速地检测到按键按下,并执行相应的按键程序,但实际应用时由于按键的机械抖动特性,在程序进入中断后必须进行滤波处理才能判定是否有效的按键事件。如果每个按键都是独立的接一个 IO 引脚,则需要给每个 IO 都设置一个中断,程序中过多的中断会影响系统的稳定性。另外中断方式跨平台移植困难。
- 查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源。实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。
-
从 OS 的角度分析:
- 中断方式:在 OS 中要尽可能少用中断方式,因为在 RTOS 中过多的使用中断会影响系统的稳定性和可预见性(抢占式调度的 OS 基本没有可预见性)。只有比较重要的事件处理需要用中断的方式。
- 查询方式:对于用户按键推荐使用这种查询方式来实现,现在的 OS 基本都带有 CPU 利用率的功能,这个按键 FIFO 占用的还是很小的,基本都在 1%以下。
今天的文章STM32【H7】理论——GPIO分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/28279.html