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();
        }
    }
}
相关推荐
日更嵌入式的打工仔1 天前
单片机基础知识:内狗外狗/软狗硬狗
笔记·单片机
v先v关v住v获v取1 天前
12米折叠式高空作业车工作臂设计9张cad+三维图+设计说明书
科技·单片机·51单片机
单片机系统设计1 天前
基于STM32的水质检测系统
网络·stm32·单片机·嵌入式硬件·毕业设计·水质检测
唔好理总之好犀利1 天前
FreeRTOS中断内使用taskENTER_CRITICAL()进入临界区
单片机·嵌入式硬件
csg11071 天前
PIC单片机入门实战(一):PIC16F1824/PIC12F1822,从振荡器与Timer1开始
单片机·嵌入式硬件·物联网
清风6666661 天前
基于单片机的车辆超载报警系统设计及人数检测设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
QQ_21932764551 天前
基于单片机的自动售货机系统设计
单片机·嵌入式硬件
Y1rong1 天前
STM32之IIC
stm32·单片机
Nautiluss1 天前
一起调试XVF3800麦克风阵列(九)
linux·人工智能·嵌入式硬件·音频·语音识别·dsp开发