STM32之LL库使用(二)

通过LL库在stm32F407实现按键扫描

最简单的按键就是类似一根导线通跟不通的问题,按键按下"导线"导通,按键松开"导线"开路。为了识别按键是按下还是松开状态,可以使用GPIO的输入功能,把按键的一个引脚接地(GND),按键的另一引脚接STM32引脚,这样按键按下时"导线"导通,STM32引脚就是低电平,程序读取到低电平就可以判断到按键处于按下状态。另外,为保证按键没有按下时为高电平,或者按键松开后引脚会自动变为高电平,一般需要在STM32引脚接一个上拉电阻,具体电路设计参考后面原理图。为读取按键输入状态,需要把对应的STM32引脚设置为普通输入模式。

cubmax配置在STM32之LL库使用(一)

一、GPIO配置

c 复制代码
/* GPIO初始化定义 */
#define KEY1_RCC_CLK_ENABLE()         LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOE)
#define KEY1_Pin                      LL_GPIO_PIN_0
#define KEY1_GPIO_Port                GPIOE
#define KEY1_DOWN_LEVEL               RESET /* 根据原理图,KEY1按下时引脚为低电平,所以这里设置为0 */

#define KEY2_RCC_CLK_ENABLE()         LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOE)
#define KEY2_Pin                      LL_GPIO_PIN_1
#define KEY2_GPIO_Port                GPIOE
#define KEY2_DOWN_LEVEL               RESET /* 根据原理图,KEY2按下时引脚为低电平,所以这里设置为0 */

#define KEY3_RCC_CLK_ENABLE()         LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOE)
#define KEY3_Pin                      LL_GPIO_PIN_2
#define KEY3_GPIO_Port                GPIOE
#define KEY3_DOWN_LEVEL               RESET /* 根据原理图,KEY3按下时引脚为低电平,所以这里设置为0 */

#define KEY4_RCC_CLK_ENABLE()         LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOE)
#define KEY4_Pin                      LL_GPIO_PIN_3
#define KEY4_GPIO_Port                GPIOE
#define KEY4_DOWN_LEVEL               RESET /* 根据原理图,KEY4按下时引脚为低电平,所以这里设置为0 */

#define KEY5_RCC_CLK_ENABLE()         LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOE)
#define KEY5_Pin                      LL_GPIO_PIN_4
#define KEY5_GPIO_Port                GPIOE
#define KEY5_DOWN_LEVEL               RESET /* 根据原理图,KEY4按下时引脚为低电平,所以这里设置为0 */

void KEY_GPIO_Init(void)
{
    LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能(开启)KEY引脚对应IO端口时钟 */
    KEY1_RCC_CLK_ENABLE();
    KEY2_RCC_CLK_ENABLE();
    KEY3_RCC_CLK_ENABLE();
    KEY4_RCC_CLK_ENABLE();
    KEY5_RCC_CLK_ENABLE();

    /************************* 配置KEY1 GPIO:输入上拉模式 *************************/
    GPIO_InitStruct.Pin = KEY1_Pin;   // 按键的I/O编号
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;// 输入模式
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;    // 按键有效电平是低电平的时候是上拉
    LL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);

    /************************* 配置KEY2 GPIO:输入上拉模式 *************************/
    GPIO_InitStruct.Pin = KEY2_Pin;   // 按键的I/O编号
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;// 输入模式
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;    // 按键有效电平是低电平的时候是上拉
    LL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);

    /************************* 配置KE3 GPIO:输入上拉模式 *************************/
    GPIO_InitStruct.Pin = KEY3_Pin;   // 按键的I/O编号
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;// 输入模式
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;    // 按键有效电平是低电平的时候是上拉
    LL_GPIO_Init(KEY3_GPIO_Port, &GPIO_InitStruct);

    /************************* 配置KEY4 GPIO:输入上拉模式 *************************/
    GPIO_InitStruct.Pin = KEY4_Pin;   // 按键的I/O编号
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;// 输入模式
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;    // 按键有效电平是低电平的时候是上拉
    LL_GPIO_Init(KEY4_GPIO_Port, &GPIO_InitStruct);

    /************************* 配置KEY5 GPIO:输入上拉模式 *************************/
    GPIO_InitStruct.Pin = KEY5_Pin;   // 按键的I/O编号
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;// 输入模式
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;    // 按键有效电平是低电平的时候是上拉
    LL_GPIO_Init(KEY5_GPIO_Port, &GPIO_InitStruct);
}

二、读取按键状态

c 复制代码
KEYState_TypeDef KEY1_StateRead(void)
{
    /* 读取此时按键值并判断是否是被按下状态,如果是被按下状态进入函数内 */
    if(LL_GPIO_IsInputPinSet(KEY1_GPIO_Port, KEY1_Pin) == KEY1_DOWN_LEVEL)
    {
        /* 延时一小段时间,消除抖动
         * 延时时间后再来判断按键状态,如果还是按下状态说明按键确实被按下 */
        LL_mDelay(30);
        if(LL_GPIO_IsInputPinSet(KEY1_GPIO_Port, KEY1_Pin) == KEY1_DOWN_LEVEL)
        {
            /* 等待按键弹开才退出按键扫描函数
             * 按键扫描完毕,确定按键被按下,返回按键被按下状态 */
            while(LL_GPIO_IsInputPinSet(KEY1_GPIO_Port, KEY1_Pin) == KEY1_DOWN_LEVEL);
            return KEY_DOWN;
        }
    }
    /* 按键没被按下,返回没被按下状态 */
    return KEY_UP;
}
KEYState_TypeDef KEY2_StateRead(void)
{
    /* 读取此时按键值并判断是否是被按下状态,如果是被按下状态进入函数内 */
    if(LL_GPIO_IsInputPinSet(KEY2_GPIO_Port, KEY2_Pin) == KEY2_DOWN_LEVEL)
    {
        /* 延时一小段时间,消除抖动
         * 延时时间后再来判断按键状态,如果还是按下状态说明按键确实被按下 */
        LL_mDelay(30);
        if(LL_GPIO_IsInputPinSet(KEY2_GPIO_Port, KEY2_Pin) == KEY2_DOWN_LEVEL)
        {
            /* 等待按键弹开才退出按键扫描函数
             * 按键扫描完毕,确定按键被按下,返回按键被按下状态 */
            while(LL_GPIO_IsInputPinSet(KEY2_GPIO_Port, KEY2_Pin) == KEY2_DOWN_LEVEL);
            return KEY_DOWN;
        }
    }
    /* 按键没被按下,返回没被按下状态 */
    return KEY_UP;
}

LL_mDelay 是基于 SysTick 硬件定时器实现的 ,但它的实现机制与 HAL 库的 HAL_Delay 有所本质区别。

在 LL 库中,LL_mDelay 通常位于 stm32f4xx_ll_utils.h(或对应的系列文件)中。它的工作原理可以概括为以下几点:

1. 它是如何工作的?

不同于 HAL 库依赖中断累加变量(uwTick),LL 库的 LL_mDelay 采用的是**阻塞式查询(Polling)**硬件寄存器的方式:

  • 频率计算:它首先会根据当前系统主频(HCLK)计算出 1 毫秒对应的 SysTick 递减计数值。
  • 寄存器监测 :它会直接读取 SysTick 的当前数值寄存器(VAL)和控制寄存器(CTRL)中的 COUNTFLAG 位。
  • 循环等待:通过循环检测硬件标志位来判断时间是否到达。

2. 使用前提

要让 LL_mDelay 正常工作,你必须在初始化时调用:

c 复制代码
LL_InitTick(HCLK_Frequency, 1000); // 配置 SysTick 每秒产生 1000 次节拍(即 1ms)

如果这一步没有正确配置(CubeMX 生成 LL 代码时通常会自动在 SystemClock_Config 中处理),LL_mDelay 将无法获得准确的时基。

3. 与 HAL_Delay 的主要区别

特性 HAL_Delay LL_mDelay
实现方式 依赖 SysTick 中断累加全局变量 阻塞式查询 SysTick 硬件寄存器
中断依赖 必须开启 SysTick 中断 不需要开启 SysTick 中断(仅需硬件运行)
优先级影响 如果在比 SysTick 优先级高的中断里调用,会死机 不受中断优先级影响(因为它直接看硬件状态)
灵活性 易于被 RTOS 接管 纯硬件行为,更接近裸机逻辑

4. 注意事项

  • CPU 占用:由于是阻塞式查询,在延时期间 CPU 无法执行其他任务。
  • 主频依赖:如果你手动修改了系统时钟频率而没有重新调用初始化函数更新时基参数,延时会变得不准。

核心总结:
LL_mDelay 是对 SysTick 硬件的直接包装。它不需要你像在 HAL 库中那样去维护一个 Tick++ 的中断服务函数,这使得它在一些极其精简、不希望频繁进入中断的场景下非常高效。

温馨提示:
为了提高处理效率确保核心逻辑清晰 ,如果您想深入了解 LL_mDelay 内部是如何通过操作 SysTick->CTRL 寄存器来实现精准计时的,或者需要对比它在低功耗模式下的表现,我们可以针对"底层寄存器实现逻辑"进行结构化拆解讨论。

三、主函数

c 复制代码
int main(void)
{
    /********* 系统初始化配置开始(必须要的,一般不改)  *****************/
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);//使能系统配置控制器时钟
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);//使能电源接口时钟
    NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);//设置中断优先级组别为2
    SystemClock_Config();//初始化系统时钟
    /********* 系统初始化配置结束(必须要的,一般不改)  *****************/

    LED_GPIO_Init();//LED初始化
    KEY_GPIO_Init();//按键初始化

    while (1)
    {
        if(KEY1_StateRead() == KEY_DOWN) //判断KEY1是不是按下状态
        {
            LED1_ON();
        }
        if(KEY2_StateRead() == KEY_DOWN) //判断KEY2是不是按下状态
        {
            LED2_ON();
        }
        if(KEY3_StateRead() == KEY_DOWN) //判断KEY3是不是按下状态
        {
            LED3_ON();
        }
        if(KEY4_StateRead() == KEY_DOWN) //判断KEY4是不是按下状态
        {
            LED1_TOGGLE();
            LED2_TOGGLE();
            LED3_TOGGLE();
        }
        if(KEY5_StateRead() == KEY_DOWN) //判断KEY5是不是按下状态
        {
            LED1_OFF();
            LED2_OFF();
            LED3_OFF();
        }
    }
}
相关推荐
CODECOLLECT15 小时前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
BackCatK Chen15 小时前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制
全栈游侠18 小时前
STM32F103XX 02-电源与备份寄存器
stm32·单片机·嵌入式硬件
Lsir10110_18 小时前
【Linux】中断 —— 操作系统的运行基石
linux·运维·嵌入式硬件
深圳市九鼎创展科技20 小时前
瑞芯微 RK3399 开发板 X3399 评测:高性能 ARM 平台的多面手
linux·arm开发·人工智能·单片机·嵌入式硬件·边缘计算
辰哥单片机设计20 小时前
STM32项目分享:车辆防盗报警系统
stm32·单片机·嵌入式硬件
風清掦1 天前
【江科大STM32学习笔记-05】EXTI外部中断11
笔记·stm32·学习
小龙报1 天前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
范纹杉想快点毕业1 天前
嵌入式与单片机开发核心学习指南——从思维转变到第一性原理的深度实践
单片机·嵌入式硬件
Industio_触觉智能1 天前
瑞芯微RK3566开发板规格书,详细参数配置,型号EVB3566-V1,基于RK3566核心板SOM3566邮票孔封装
嵌入式硬件·开发板·rk3568·rk3566·核心板·瑞芯微