【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 __DELAY_H
#define __DELAY_H

#include "stm32f10x.h"


/* 定义系统文件夹是否支持 RTOS */
#define SYSTEM_SUPPORT_OS       1   /* 0,不支持。 1,支持 */

int32_t get_run_tim(void);
int32_t check_run_tim(int32_t _last_tim);

#if SYSTEM_SUPPORT_OS

    void delay_init(void);
    void delay_us(uint32_t nus);
    void delay_ms(uint32_t nms);    /* 会引起任务调度 */
    void delay_xms(uint32_t nms);   /* 不会引起任务调度 */

#else   /* 不使用RTOS 的延迟函数 */

    void delay_init(uint16_t sysclk);
    void delay_us(uint32_t nus);
    void delay_ms(uint16_t nms); 
       
#endif

#endif
  1. sys.c
/**
****************************************************************************
* @file     sys.c
* @author   BJX
* @version  V1.0
* @date     2023-10-28
* @brief    系统代码 暂时只有位带操作
****************************************************************************
* @attention
*
*   平台:STM32F103C8
*
****************************************************************************
*/

#include "./sys/sys.h"


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

sys.h 该头文件里的两个开、关中断的宏要被delay.c里面的函数调用。

#ifndef __SYS_H
#define __SYS_H

#include "stm32f10x.h" 

/* 开关全局中断的宏 */
#define ENABLE_INT()     __set_PRIMASK(0)    /* 使能全局中断 */
#define DISABLE_INT()    __set_PRIMASK(1)    /* 禁止全局中断 */

/* 位带操作公式:把“位带地址 + 位序号”转换成别名地址的宏  BINBAND:位带 */
#define BITBAND(addr, n)  ((addr & 0xF0000000)+0x02000000+((addr &0x00FFFFFF)<<5)+(n<<2))

/* 先把地址强制转化为指针类型,再对指针进行操作 */
#define BIT_ADDR(addr,n)       *(unsigned int*) BITBAND(addr, n)
    
#define GPIOA_ODR_Addr         (GPIOA_BASE+0X0C)    /* ODR寄存器 */
#define GPIOB_ODR_Addr         (GPIOB_BASE+0X0C)
#define GPIOC_ODR_Addr         (GPIOC_BASE+0X0C)
#define GPIOD_ODR_Addr         (GPIOD_BASE+0X0C)
#define GPIOE_ODR_Addr         (GPIOE_BASE+0X0C)
#define GPIOF_ODR_Addr         (GPIOF_BASE+0X0C)
#define GPIOG_ODR_Addr         (GPIOG_BASE+0X0C)

#define GPIOA_IDR_Addr         (GPIOA_BASE+0X08)    /* IDR寄存器 */
#define GPIOB_IDR_Addr         (GPIOB_BASE+0X08)
#define GPIOC_IDR_Addr         (GPIOC_BASE+0X08)
#define GPIOD_IDR_Addr         (GPIOD_BASE+0X08)
#define GPIOE_IDR_Addr         (GPIOE_BASE+0X08)
#define GPIOF_IDR_Addr         (GPIOF_BASE+0X08)
#define GPIOG_IDR_Addr         (GPIOG_BASE+0X08)

#define PAout(n)               BIT_ADDR(GPIOA_ODR_Addr,n) 		/* 输出 */
#define PAin(n)                BIT_ADDR(GPIOA_IDR_Addr,n)		/* 输入 */

#define PBout(n)               BIT_ADDR(GPIOB_ODR_Addr,n)		/* 输出 */
#define PBin(n)                BIT_ADDR(GPIOB_IDR_Addr,n)		/* 输入 */

#define PCout(n)               BIT_ADDR(GPIOC_ODR_Addr,n)		/* 输出 */
#define PCin(n)                BIT_ADDR(GPIOC_IDR_Addr,n) 		/* 输入 */

#define PDout(n)               BIT_ADDR(GPIOD_ODR_Addr,n) 		/* 输出 */
#define PDin(n)                BIT_ADDR(GPIOD_IDR_Addr,n)		/* 输入 */

#define PEout(n)               BIT_ADDR(GPIOE_ODR_Addr,n)		/* 输出 */
#define PEin(n)                BIT_ADDR(GPIOE_IDR_Addr,n)		/* 输入 */

#endif
  1. key_fifo.c
/**
******************************************************************************
* @file     key_fifo.c
* @author   BJX
* @version  V1.0
* @date     2024-01-27
* @brief    按键FIFO驱动
******************************************************************************
* @attention
*
*   平台: F103C8T6
*
******************************************************************************
*/

#include "./key/key_fifo.h"
#include "./delay/delay.h"


static key_t    st_btn[KEY_COUNT] = {0};
static key_fifo st_key;        /* 按键FIFO变量,结构体 */

/* GPIO和PIN定义 */
static const key_gpio st_gpio_list[KEY_HARD_NUM] = {
    {GPIOA, GPIO_Pin_0, 1},     /* K1 */
    {GPIOA, GPIO_Pin_1, 1},     /* K2 */
}; 


/**
 * @brief  按键硬件初始化配置
 * @param  无
 * @retval 无
 */
void key_hard_init(void)
{    
    uint8_t i;
    GPIO_InitTypeDef GPIO_InitStruct;

    ALL_KEY_GPIO_CLK_ENABLE();                  /* 打开所有按键GPIO时钟 */

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;  /* 设置引脚模式为下拉输入 */
    
    for (i = 0; i < KEY_HARD_NUM; i++)
    {
        GPIO_InitStruct.GPIO_Pin = st_gpio_list[i].pin;
        GPIO_Init(st_gpio_list[i].gpio, &GPIO_InitStruct);
    }
}

/**
 * @brief  初始化按键变量
 * @param  无
 * @retval 无
 */
void key_var_init(void)
{
    uint8_t i;

    /* 对按键FIFO读写指针清零 */
    st_key.read = 0;
    st_key.write = 0;
    st_key.read2 = 0;

    /* 给每个按键结构体成员变量赋一组缺省值 */
    for (i = 0; i < KEY_COUNT; i++)
    {
        st_btn[i].long_tim = KEY_LONG_TIME;     /* 长按时间 0 表示不检测长按键事件 */
        st_btn[i].cnt = KEY_FILTER_TIME / 2;    /* 计数器设置为滤波时间的一半 */
        st_btn[i].state = 0;            /* 按键缺省状态,0为未按下 */
        st_btn[i].repeat_speed = 6;     /* 按键连发的速度,0表示不支持连发 */
        st_btn[i].repeat_cnt = 0;       /* 连发计数器 */
        st_btn[i].delay_cnt = 0;
        st_btn[i].click_cnt = 0;  
        st_btn[i].last_tim = 0;
    }

    /* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
    
}

/**
 * @brief  设置按键参数
 * @param  _key_id: 按键ID
 * @param  _long_tim: 长按事件时间
 * @param  _repeat_speed: 连发速度
 * @retval 无
 */
void key_set_param(uint8_t _key_id, uint16_t _long_tim, uint8_t _repeat_speed)
{
    st_btn[_key_id].long_tim = _long_tim;           /* 长按时间 0 表示不检测长按键事件 */
    st_btn[_key_id].repeat_speed = _repeat_speed;   /* 按键连发的速度,0表示不支持连发 */
    st_btn[_key_id].repeat_cnt = 0;                 /* 连发计数器 */
}

/**
 * @brief  将1个键值压入按键FIFO缓冲区。可用于模拟一个按键
 * @param  _key_code: 按键代码
 * @retval 无
 */
void key_put(uint8_t _key_code)
{
    st_key.buf[st_key.write] = _key_code;

    if(++st_key.write  >= KEY_FIFO_SIZE)
    {
        st_key.write = 0;
    }
}

/**
 * @brief  从按键FIFO缓冲区读取一个键值
 * @param  无
 * @retval 按键代码
 */
uint8_t key_get(void)
{
    uint8_t ret;

    if (st_key.read == st_key.write)
    {
        return KEY_NONE;
    }
    else
    {
        ret = st_key.buf[st_key.read];

        if (++st_key.read >= KEY_FIFO_SIZE)
        {
            st_key.read = 0;
        }
        return ret;
    }
}

/**
 * @brief  从按键FIFO缓冲区读取一个键值。独立的读指针
 * @param  无
 * @retval 按键代码
 */
uint8_t key_get2(void)
{
    uint8_t ret;

    if (st_key.read2 == st_key.write)
    {
        return KEY_NONE;
    }
    else
    {
        ret = st_key.buf[st_key.read2];

        if (++st_key.read2 >= KEY_FIFO_SIZE)
        {
            st_key.read2 = 0;
        }
        return ret;
    }
}

/**
 * @brief  读取按键的状态
 * @param  _key_id: 按键ID
 * @retval 1 表示按下, 0 表示未按下
 */
uint8_t key_get_state(key_id _key_id)
{
    return st_btn[_key_id].state;
}

/**
 * @brief  清空按键FIFO缓冲区
 * @param  无
 * @retval 无
 */
void key_clear(void)
{
    st_key.read = st_key.write;
}

/**
 * @brief  判断按键是否按下
 * @param  _id:按键id
 * @retval 1 表示按下(导通),0表示未按下(释放)
 */
static uint8_t key_pin_active(uint8_t _id)
{
    uint8_t level;
    
    if ((st_gpio_list[_id].gpio->IDR & st_gpio_list[_id].pin) == 0)
    {
        level = 0;
    }
    else
    {
        level = 1;
    }

    if (level == st_gpio_list[_id].active_level)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/**
 * @brief  判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下
 * @param  _id:按键id
 * @retval 1 表示按下(导通),0表示未按下(释放)
 */
static uint8_t key_down_func(uint8_t _id)
{
    /* 实体单键 */
    if (_id < KEY_HARD_NUM)
    {
        uint8_t i;
        uint8_t count = 0;
        uint8_t save = 255;
        
        /* 判断有几个键按下 */
        for (i = 0; i < KEY_HARD_NUM; i++)
        {
            if (key_pin_active(i)) 
            {
                count++;
                save = i;
            }
        }
        
        if (count == 1 && save == _id)
        {
            return 1;    /* 只有1个键按下时才有效 */
        }        

        return 0;
    }
    
    /* 组合键 K1K2 */
    if (_id == KEY_HARD_NUM + 0)
    {
        if (key_pin_active(KID_K1) && key_pin_active(KID_K2))
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    /* 组合键 K2K3 */
    if (_id == KEY_HARD_NUM + 1)
    {
        if (key_pin_active(KID_K2) && key_pin_active(KID_K3))
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    return 0;
}

/**
 * @brief  检测一个按键。非阻塞状态,必须被周期性的调用
 * @param  _id: 按键id
 * @retval 无
 */
static void key_detect(uint8_t _id)
{
    key_t *pbtn;

    pbtn = &st_btn[_id];
    if (key_down_func(_id))
    {
        if (pbtn->cnt < KEY_FILTER_TIME)
        {
            pbtn->cnt = KEY_FILTER_TIME;
        }
        else if (pbtn->cnt < 2 * KEY_FILTER_TIME)
        {
            pbtn->cnt++;
        }
        else
        {
            if (pbtn->state == 0)
            {
                pbtn->state = 1;

                /* 发送按钮按下的消息 */
                key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_DOWN));
            }

            if (pbtn->long_tim > 0)
            {
                if (pbtn->long_cnt < pbtn->long_tim)
                {
                    /* 发送长按消息 */
                    if (++pbtn->long_cnt == pbtn->long_tim)
                    {
                        pbtn->state = 2;
                        
                        /* 键值放入按键FIFO */
                        key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_LONG_DOWN));                        
                    }
                }
                else
                {
                    if (pbtn->repeat_speed > 0)
                    {
                        if (++pbtn->repeat_cnt >= pbtn->repeat_speed)
                        {
                            pbtn->repeat_cnt = 0;
                            /* 长按键后,每隔连按周期发送1个按键弹起事件 */
                            key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_AUTO_UP));
                        }
                    }
                }
            }
        }
    }
    else
    {
        if (pbtn->cnt > KEY_FILTER_TIME)
        {
            pbtn->cnt = KEY_FILTER_TIME;
        }
        else if (pbtn->cnt != 0)
        {
            pbtn->cnt--;
        }
        else
        {
            if (pbtn->state != 0)
            {
                /* 长按后的弹起 */
                if (pbtn->long_tim == 0)
                {
                    /* 发送短按弹起的消息 */
                    key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_UP));
                }
                else
                {
                    if (pbtn->state == 2)
                    {
                        /* 发送长按弹起的消息 */
                        key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_LONG_UP));
                        
                        #if DOUBLE_CLICK_ENABLE == 1
                            st_btn[_id].last_tim = get_run_tim();  /* 记录按键弹起时刻 */
                        #endif
                    }
                    else
                    {                       
                        #if DOUBLE_CLICK_ENABLE == 1
                        /* 发送短按弹起的消息 */
                            if (check_run_tim(st_btn[_id].last_tim) < 500)
                            {                            
                                if (pbtn->click_cnt == 1)
                                {
                                    key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_DB_UP));  /* 双击事件 */
                                    pbtn->click_cnt = 0;
                                }
                                else
                                {
                                    key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_UP));     /* 单击弹起事件 */
                                    pbtn->click_cnt = 0;
                                }
                                pbtn->delay_cnt = 80; 
                            }
                            else                       
                            {
                                pbtn->click_cnt++;                        
                                pbtn->delay_cnt = KEY_DB_CLICK_TIME;  
                            }
                            st_btn[_id].last_tim = get_run_tim();  /* 记录按键弹起时刻 */   
                        #else
                            key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_UP));     /* 单击弹起事件 */
                        #endif
                    }
                }
                pbtn->state = 0;                
            }
        }

        pbtn->long_cnt = 0;
        pbtn->repeat_cnt = 0;
    }
}

/**
 * @brief  扫描所有按键。非阻塞,10ms一次
 * @param  无
 * @retval 无
 */
void key_scan10ms(void)
{
    uint8_t i;

    for (i = 0; i < KEY_COUNT; i++)
    {   
        #if DOUBLE_CLICK_ENABLE == 1
            /* 超时判断 */
            if (st_btn[i].delay_cnt > 0)
            {
                if (--st_btn[i].delay_cnt == 0)
                {
                    if (st_btn[i].click_cnt == 1)
                    {
                        key_put((uint8_t)(KEY_MSG_STEP * i + KEY_1_UP));   /* 单击弹起 */
                    }              
                    st_btn[i].click_cnt = 0;
                }
            }
        #endif

        /* 检测按键 */
        key_detect(i);
    }
}

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

key_fifo.h

#ifndef __KEY_FIFO_H
#define __KEY_FIFO_H

#include "stm32f10x.h"


/* 使能GPIO时钟 */
#define ALL_KEY_GPIO_CLK_ENABLE() do{                         \
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   \
}while(0);

#define DOUBLE_CLICK_ENABLE     0   /* 1表示启用双击检测, 会影响单击体验变慢 */

#define KEY_HARD_NUM            2   /* 实体按键个数 */
#define KEY_COUNT               (KEY_HARD_NUM + 2)  /* 8个独立建 + 2个组合按键 */

/*
    按键滤波时间50ms, 单位10ms。
    只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
    即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
*/
#define KEY_FILTER_TIME     5
#define KEY_LONG_TIME       100     /* 单位10ms, 持续1秒,认为长按事件 */

#define KEY_DB_CLICK_TIME   40      /* 双击检测时长,单位ms */

#define KEY_FIFO_SIZE       10      /* 按键FIFO最大的存储量 */

typedef struct{
    uint8_t buf[KEY_FIFO_SIZE];     /* 键值缓冲区 */
    uint8_t read;                   /* 缓冲区读指针1 */
    uint8_t write;                  /* 缓冲区写指针 */
    uint8_t read2;                  /* 缓冲区读指针2 */
}key_fifo;

typedef struct{              /* 依次定义按键所用的GPIO */
    GPIO_TypeDef* gpio;
    uint16_t pin;
    uint8_t active_level;    /* 触发电平 */
}key_gpio;

/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum{
    KID_K1 = 0,
    KID_K2,
    KID_K3,
}key_id;


/* 每个按键对应1个全局的结构体变量 */
typedef struct{
    /* 下面是一个函数指针,指向判断按键是否按下的函数 */
    uint8_t (*key_down_func)(void); /* 按键按下的判断函数,1表示按下 */

    uint8_t  cnt;           /* 滤波器计数器 */
    uint16_t long_cnt;      /* 长按计数器 */
    uint16_t long_tim;      /* 按键按下持续时间, 0表示不检测长按 */
    uint8_t  state;         /* 按键当前状态(按下还是弹起) */
    uint8_t  repeat_speed;  /* 连续按键周期 */
    uint8_t  repeat_cnt;    /* 连续按键计数器 */
    
    uint16_t delay_cnt;     /* 延迟计数器,用于双击检测 */
    uint8_t  click_cnt;      /* 单击次数 */
    int32_t  last_tim;       /* 上次按键时刻 */
}key_t;

/* 根据应用程序的功能重命名按键宏 */
#define KEY_DOWN_K1         KEY_1_DOWN
#define KEY_UP_K1           KEY_1_UP
#define KEY_LONG_DOWN_K1    KEY_1_LONG_DOWN
#define KEY_LONG_UP_K1      KEY_1_LONG_UP
#define KEY_AUTO_K1         KEY_1_AUTO_UP
#define KEY_DB_K1           KEY_1_DB_UP

#define KEY_DOWN_K2         KEY_2_DOWN
#define KEY_UP_K2           KEY_2_UP
#define KEY_LONG_DOWN_K2    KEY_2_LONG_DOWN
#define KEY_LONG_UP_K2      KEY_2_LONG_UP
#define KEY_AUTO_K2         KEY_2_AUTO_UP
#define KEY_DB_K2           KEY_2_DB_UP

#define KEY_DOWN_K3         KEY_3_DOWN
#define KEY_UP_K3           KEY_3_UP
#define KEY_LONG_DOWN_K3    KEY_3_LONG_DOWN
#define KEY_LONG_UP_K3      KEY_3_LONG_UP
#define KEY_AUTO_K3         KEY_3_AUTO_UP
#define KEY_DB_K3           KEY_3_DB_UP

#define SYS_DOWN_K1K2           KEY_9_DOWN        /* K1 K2 组合键 */
#define SYS_UP_K1K2             KEY_9_UP
#define SYS_LONG_DOWN_K1K2      KEY_9_LONG_DOWN
#define SYS_LONG_UP_K1K2        KEY_9_LONG_UP
#define SYS_AUTO_K1K2           KEY_9_AUTO_UP
#define SYS_DB_K1K2             KEY_9_DB_UP

#define SYS_DOWN_K2K3           KEY_10_DOWN        /* K2 K3 组合键 */
#define SYS_UP_K2K3             KEY_10_UP
#define SYS_LONG_DOWN_K2K3      KEY_10_LONG_DOWN
#define SYS_LONG_UP_K2K3        KEY_10_LONG_UP
#define SYS_AUTO_K2K3           KEY_10_AUTO_UP
#define SYS_DB_K2K3             KEY_10_DB_UP


/*
    定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件

    推荐使用enum, 不用#define,原因:
    (1) 便于新增键值,方便调整顺序,使代码看起来舒服点
    (2) 编译器可帮我们避免键值重复。
*/
#define KEY_MSG_STEP    6 /* 按键消息代码步长(每个键有几个事件代码,顺序排列) */
typedef enum{
    KEY_NONE = 0,           /* 0 表示按键事件 */

    KEY_1_DOWN,             /* 1键按下 */
    KEY_1_UP,               /* 1键弹起 */
    KEY_1_LONG_DOWN,        /* 1键长按 */
    KEY_1_LONG_UP,          /* 1键长按后的弹起 */  
    KEY_1_AUTO_UP,          /* 1键长按后自动发码 */
    KEY_1_DB_UP,            /* 1键双击 */ 

    KEY_2_DOWN,             /* 2键按下 */
    KEY_2_UP,               /* 2键弹起 */
    KEY_2_LONG_DOWN,        /* 2键长按 */
    KEY_2_LONG_UP,          /* 2键长按后的弹起 */  
    KEY_2_AUTO_UP,          /* 2键长按后自动发码 */
    KEY_2_DB_UP,            /* 2键双击 */

    KEY_3_DOWN,             /* 3键按下 */
    KEY_3_UP,               /* 3键弹起 */
    KEY_3_LONG_DOWN,        /* 3键长按 */
    KEY_3_LONG_UP,          /* 3键长按后的弹起 */  
    KEY_3_AUTO_UP,          /* 3键长按后自动发码 */
    KEY_3_DB_UP,            /* 3键双击 */

    /* 组合键 */
    KEY_9_DOWN,             /* 9键按下 */
    KEY_9_UP,               /* 9键弹起 */
    KEY_9_LONG_DOWN,        /* 9键长按 */
    KEY_9_LONG_UP,          /* 9键长按后的弹起 */  
    KEY_9_AUTO_UP,          /* 9键长按后自动发码 */
    KEY_9_DB_UP,            /* 9键双击 */

    KEY_10_DOWN,            /* 10键按下 */
    KEY_10_UP,              /* 10键弹起 */
    KEY_10_LONG_DOWN,       /* 1键长按 */
    KEY_10_LONG_UP,         /* 1键长按后的弹起 */  
    KEY_10_AUTO_UP,         /* 1键长按后自动发码 */
    KEY_10_DB_UP,           /* 1键双击 */
}key_num;


void key_hard_init(void);
void key_var_init(void);
void key_put(uint8_t _key_code);
void key_set_param(uint8_t _key_id, uint16_t _long_tim, uint8_t _repeat_speed);
uint8_t key_get(void);
uint8_t key_get2(void);
uint8_t key_get_state(key_id _key_id);
void key_clear(void);
void key_scan10ms(void);


static uint8_t key_pin_active(uint8_t _id);
static uint8_t key_down_func(uint8_t _id);
static void key_detect(uint8_t _id);

#endif /* __KEY_FIFO_H */

  1. key.c 要确保 key_task() 每10ms被调用一次。
/**
***************************************************************************
* @file     key.c
* @author   BJX
* @version  V1.0
* @date     2023-xx-xx
* @brief    按键驱动
***************************************************************************
* @attention
*
*   平台: F103C8T6
*
***************************************************************************
*/

#include "./key/key.h"
#include "./key/key_fifo.h"
#include "./usart/usart.h"

/**
 * @brief  按键初始化
 * @param  无
 * @retval 无
 */
void key_init(void)
{
    key_hard_init();        /* 初始化按键变量 */
    key_var_init();        /* 初始化按键硬件 */
}

/**
 * @brief  按键任务处理
 * @param  无
 * @retval 无
 */
void key_task(void)
{
    uint8_t key_code;        /* 按键代码 */
    
    key_scan10ms();

    /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用key_get读取键值即可。 */
    key_code = key_get();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
    if (key_code != KEY_NONE)
    {
        switch (key_code)
        {
            case KEY_DOWN_K1:            
                printf("K1键按下\r\n");
                break;

            case KEY_UP_K1:                
                printf("K1键单击---\r\n");
                break;
            
            case KEY_LONG_DOWN_K1:            
                printf("K1键长按按下\r\n");
                break;

            case KEY_LONG_UP_K1:                
                printf("K1键长按弹起\r\n");
                break;

            case KEY_AUTO_K1:                
                printf("K1键长按连发\r\n");
                break;
            
            case KEY_DB_K1:                
                printf("K1键双击***\r\n");
                break;

            default:
                /* 其它的键值不处理 */
                break;
        }
    }
}

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

key.h

#ifndef __KEY_H
#define __KEY_H

#include "stm32f10x.h"

void key_init(void);
void key_task(void);

#endif /* __KEY_H */

此移植参考 安富莱按键FIFO教程例子安富莱按键FIFO教程视频

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
STM32中,DMA的双缓冲模式可以通过配置DMA的通道控制寄存器来实现。下面是一个示例代码,可以帮助你理解如何使用STM32的DMA双缓冲模式。 首先,需要定义两个缓冲区,用于DMA的读写操作。在本示例中,我们假设缓冲区大小为16字节。 ```c #define BUFFER_SIZE 16 uint8_t buffer1[BUFFER_SIZE]; uint8_t buffer2[BUFFER_SIZE]; ``` 然后,需要配置DMA通道的控制寄存器。在本示例中,我们使用DMA1通道1,并启用双缓冲模式。 ```c DMA_HandleTypeDef hdma; hdma.Instance = DMA1_Channel1; hdma.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma.Init.PeriphInc = DMA_PINC_DISABLE; hdma.Init.MemInc = DMA_MINC_ENABLE; hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma.Init.Mode = DMA_CIRCULAR; hdma.Init.Priority = DMA_PRIORITY_HIGH; hdma.Init.FIFOMode = DMA_FIFOMODE_DISABLE; hdma.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; hdma.Init.MemBurst = DMA_MBURST_SINGLE; hdma.Init.PeriphBurst = DMA_PBURST_SINGLE; HAL_DMA_Init(&hdma); __HAL_DMA_DISABLE(&hdma); hdma.Instance->CR |= DMA_SxCR_DBM; ``` 接下来,需要启动DMA传输。 ```c HAL_DMA_Start(&hdma, (uint32_t)&peripheral_device, (uint32_t)buffer1, BUFFER_SIZE); ``` 在DMA传输期间,当缓冲区1已经被填满时,DMA会自动切换到缓冲区2进行数据传输。当缓冲区2也被填满时,DMA会再次切换回缓冲区1。 当需要读取DMA传输的数据时,可以通过检查DMA传输期间使用的缓冲区来获取传输的数据。 ```c uint8_t* buffer = (hdma.Instance->CR & DMA_SxCR_CT) ? buffer2 : buffer1; ``` 最后,在传输完成后,需要停止DMA传输并释放DMA通道。 ```c HAL_DMA_Stop(&hdma); HAL_DMA_DeInit(&hdma); ``` 以上就是一个基本的DMA双缓冲模式的示例代码。需要注意的是,在使用DMA双缓冲模式时,需要保证缓冲区大小足够大,以避免数据溢出。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值