【STM32】_01_按键 FIFO 移植笔记,实现长按、短按、双击、组合键等七种功能

功能

本人移植平台为STM32F103C8+FreeRTOS实现按键短按按下 短按抬起 长按按下 长按抬起 长按连发 单键双击 组合按键 均为非阻塞方式,适合裸机RTOS
这篇笔记是为了在需要时方便个人拉取,因此只贴了相关代码,而没有太多的文字描述。移植参考的是安富莱的相关教程,相关链接已经贴在了帖子结尾处。
如果有朋友需要详细一点的笔记,或其它问题,可在评论区交流,有时间了在完善。

代码

  1. delay.c 中的 get_run_tim()check_run_tim() 函数被 key_fifo.c里面的函数调用。
/**
****************************************************************************
* @file     delay.c
* @author   BJX
* @version  V1.0
* @date     2023-10-xx
* @brief    延时函数,正点模板
****************************************************************************
* @attention 
*           系统定时器(SysTick)是一个 24bit的向下递减的计数器,
*           计数器每计数一次的时间为 1/SYSCLK。
*
****************************************************************************
*/

#include "./delay/delay.h"
#include "./sys/sys.h"

static uint8_t fac_us = 0;    /* us延时倍乘数 */

/*
    全局运行时间,单位1ms,最长可以表示 24.85天
    如果你的产品连续运行时间超过这个数,则必须考虑溢出问题
*/
__IO int32_t gil_run_tim = 0;


/**
 * @brief   systick中断服务获取CPU运行时间,单位1ms。
 *          最长可以表示 24.85天,如果你的产品连续运行时间超过这个数,则必须考虑溢出问题
 * @param   无
 * @retval  CPU运行时间,单位1ms
 */
int32_t get_run_tim(void)
{
   
   
    int32_t run_time;

    DISABLE_INT();      /* 关中断 */

    run_time = gil_run_tim;    /* 这个变量在Systick中断中被改写,因此需要关中断进行保护 */

    ENABLE_INT();          /* 开中断 */

    return run_time;
}

/**
 * @brief   计算当前运行时间和给定时刻之间的差值。处理了计数器循环。
 * @param   _last_tim:上个时刻
 * @retval  当前时间和过去时间的差值,单位1ms
 */
int32_t check_run_tim(int32_t _last_tim)
{
   
   
    int32_t now_time;
    int32_t time_diff;

    DISABLE_INT();      /* 关中断 */

    now_time = gil_run_tim;    /* 这个变量在Systick中断中被改写,因此需要关中断进行保护 */

    ENABLE_INT();          /* 开中断 */
    
    if (now_time >= _last_tim)
    {
   
   
        time_diff = now_time - _last_tim;
    }
    else
    {
   
   
        time_diff = 0x7FFFFFFF - _last_tim + now_time;
    }

    return time_diff;
}


/* 判断是否使用OS */
#if SYSTEM_SUPPORT_OS

    /* 添加公共头文件 FreeRTOS 使用 */
    #include "FreeRTOS.h"
    #include "task.h"  

    static uint16_t fac_ms = 0;   /* ms延时倍乘数,在 os下,代表每个节拍的ms数 */

    extern void xPortSysTickHandler(void);

    /**
     * @brief   systick中断服务函数,使用OS时用到
     * @param   无
     * @retval  无
     */
    void SysTick_Handler(void)
    {
   
   
        /* OS开始跑了,才执行正常的调度处理 */
        if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) 
        {
   
   
            xPortSysTickHandler();
            
            /* 全局运行时间每1ms增1 */
            gil_run_tim++;
            
             /* 这个变量是 int32_t 类型,最大数为 0x7FFFFFFF */
            if (gil_run_tim == 0x7FFFFFFF)   
            {
   
   
                gil_run_tim = 0;
            }
        }
    }
    

    /**
     * @brief  使用RTOS时的延迟函数初始化
     * @param  无
     * @retval 无
     */
    void delay_init(void)
    {
   
   
        uint32_t reload;

        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);  /* 选择外部时钟 HCLK */
        fac_us = SystemCoreClock / 1000000;     /* 不论是否使用 OS,fac_us 都需要使用 */
        reload = SystemCoreClock / 1000000;     /* 每秒钟的计数次数 单位为 M */

        /* 根据 configTICK_RATE_HZ 设定溢出时间 reload 为 24 位寄存器, */
        reload *= 1000000 / configTICK_RATE_HZ;  /* 最大值:16777216,在 72M 下,约合 0.233s 左右  */     
        fac_ms = 1000 / configTICK_RATE_HZ;          /* 代表 OS 可以延时的最少单位 */
        SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;   /* 开启 SYSTICK 中断 */
        SysTick->LOAD = reload;                      /* 每 1/configTICK_RATE_HZ 秒中断一次 */
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;    /* 开启 SYSTICK */
    }


    /**
     * @brief  微秒级延时
     * @param  nus:要延时的 us 数(0~204522252)
     * @retval 无
     */
    void delay_us(uint32_t nus)
    {
   
   
        uint32_t ticks;
        uint32_t told, tnow, tcnt = 0;
        uint32_t reload = SysTick->LOAD;   /* LOAD 的值(LOAD:定时器重装值) */

        ticks = nus * fac_us;         /* 需要的节拍数 */
        told = SysTick->VAL;          /* 刚进入时的计数器值 */

        while (1)
        {
   
   
            tnow = SysTick->VAL;    /* VAL:当前计数值 */

            if (tnow != told)
            {
   
   
                /* 这里注意一下 SYSTICK 是一个递减的计数器就可以了. */
                if (tnow < told)
                    tcnt += told - tnow;
                else
                    tcnt += reload - tnow + told;

                told = tnow;
                if (tcnt >= ticks)
                    break;   /* 时间超过/等于要延迟的时间,则退出. */
            }
        }
    }


    /**
     * @brief  毫秒级延时,会引起任务调度
     * @param  nms 延时时长,范围:0~65535
     * @retval 无
     */
    void delay_ms(uint32_t nms)
    {
   
   
        if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)   /* 系统已经运行 */
        {
   
   
            if (nms >= fac_ms)   /* 延时的时间大于 OS 的最少时间周期 */
            {
   
   
                vTaskDelay(nms / fac_ms);   /* FreeRTOS 延时 */
            }
            nms %= fac_ms;   /* OS 已经无法提供这么小的延时了 采用普通方式延时*/
        }
        delay_us((uint32_t)(nms * 1000));   /* 普通方式延时 */
    }


    /**
     * @brief  毫秒级延时,不会引起任务调度
     * @param  nms 延时时长,范围:0~204522252
     * @retval 无
     */
    void delay_xms(uint32_t nms)
    {
   
   
        uint32_t i;
        for (i = 0; i < nms; i++)
        {
   
   
            delay_us(1000);
        }        
    }

#else

    /**
     * @brief   初始化延迟函数
     * @param   sysclk: 系统时钟频率(即CPU频率(HCLK)),单位为 MHz
     * @retval  无
     */
    void delay_init(uint16_t sysclk)
    {
   
   
        /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
        SysTick->CTRL = 0;  
    
        fac_us = sysclk;    /* 作为1us的基础时基 */
    }


    /**
     * @brief   微秒级延时
     * @param   xus 延时时长,范围:0~233015
     * @retval  无
     */
    void delay_us(uint32_t nus)
    {
   
   
        uint32_t temp;

        SysTick->LOAD = nus * fac_us;   /* 时间加载 */
        SysTick->VAL = 0x00;            /* 清空计数器 */
        SysTick->CTRL |= 1 << 0 ;       /* 开始倒数 */

        do
        {
   
   
            temp = SysTick->CTRL;
        } while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */

        SysTick->CTRL &= ~(1 << 0) ;    /* 关闭SYSTICK */
        SysTick->VAL = 0X00;            /* 清空计数器 */
    }


    /**
     * @brief   延时nms
     * @param   nms: 要延时的ms数 (0< nms <= 65535)
     * @retval  无
     */
    void delay_ms(uint16_t nms)
    {
   
   
        /* 这里用1000,是考虑到可能有超频应用,比如128Mhz的时候, 
            delay_us最大只能延时1048576us左右了 */
        uint32_t repeat = nms / 1000;   
        uint32_t remain = nms % 1000;

        while (repeat)
        {
   
   
            delay_us(1000 * 1000);      /* 利用delay_us 实现 1000ms 延时 */
            repeat--;
        }

        if (remain)
        {
   
   
            delay_us(remain * 1000); /* 利用delay_us, 把尾数延时(remain ms)给做了 */
        }
    }

#endif

/****************************** END OF FILE ******************************/

delay.h

#ifndef<
### STM32实现按键按和短按并结合FIFO队列 为了实现STM32上的按键检测机制,可以采用轮询方式来监测按键状态,并利用FIFO队列存储按键事件以便后续处理。下面是一个具体的实现方案。 #### 定义按键结构体 定义一个用于表示按键事件的数据结构,该结构不仅包含按键编号还应记录按下持续时间以区分短按操作: ```c typedef struct { uint8_t keyId; // 按键ID uint16_t pressTimeMs; // 按下时(ms),用来判断是短按还是按 } KeyEvent; ``` #### 初始化FIFO队列 创建一个固定大小的环形缓冲区作为FIFO队列,初始化其头指针、尾指针及最大容量等参数: ```c #define QUEUE_SIZE 10 // FIFO队列的最大度 static KeyEvent fifoQueue[QUEUE_SIZE]; // 存储按键事件的实际数组 volatile int head = 0, tail = 0; // 头部索引与尾部索引 ``` #### 更新函数`key_Polling()` 编写更新函数定期读取ADC值或其他输入源获取当前按键的状态变化情况,并据此生成相应的按键事件对象推送到FIFO队列中等待进一步处理[^1]: ```c #include "stm32f1xx_hal.h" // 假设有一个全局变量adVal保存了最近一次采样的AD转换结果 extern uint16_t adVal; void key_Polling(uint16_t adVal) { static uint32_t lastPressTimestamp = 0; if (/* 判断是否有新的按键动作 */) { uint32_t currentTime = HAL_GetTick(); uint16_t duration = currentTime - lastPressTimestamp; KeyEvent event = {.keyId = /* 获取具体哪个按钮 */, .pressTimeMs = duration}; // 将新产生的按键事件加入到FIFO队列末端 enqueue(&event); lastPressTimestamp = currentTime; } } ``` #### `enqueue()` 函数实现FIFO队列入栈逻辑 当有新的按键事件发生时,调用此方法将其追加至队列末尾;如果队满则丢弃最旧的一条记录: ```c bool enqueue(KeyEvent *newEvent) { if ((tail + 1) % QUEUE_SIZE != head) { // 如果不是已满,则允许插入 fifoQueue[tail] = *newEvent; tail = (tail + 1) % QUEUE_SIZE; return true; } else { return false; // 队列满了无法再添加更多项 } } ``` #### 主循环中的按键处理部分 在主程序循环内不断检查是否存在待处理的任务(即非空的FIFO),若有则取出最先到达的那个按键命令执行对应的动作: ```c while (true) { if (head != tail) { // 当前存在未处理的按键事件 KeyEvent currentEvent = fifoQueue[head]; handleKeyCode(currentEvent.keyId, currentEvent.pressTimeMs); // 对不同类型的按键做出响应 head = (head + 1) % QUEUE_SIZE; // 移动头部指向下一个位置准备接收下一组数据 } // 继续其他任务... } ``` 上述代码片段展示了如何基于STM32平台构建一套简单的按键管理系统,它能够有效地捕捉用户的交互行为并通过FIFO队列管理这些信息流,从而简化应用程序的设计复杂度[^5]。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值