基于stm32的按键驱动框架的编写

本期带来的是基于stm32的按键驱动框架的编写,本框架适用于多种按键状态的判断 在这我只写了三种状态 分别是单击 双击和长按,学会了此框架也可以把此驱动移植到其它外设上 ,本驱动主要涉及 游标链表的编写 环形队列的编写 ,游标链表主要是用来放入按键的属性 环形队列则是用来处理按键的事件 ,好接下来我们一起来完成这个按键驱动框架的编写。

游标链表

在我们普通的数据结构中链表是一种地址不相同但可以通过指针把地址关联起来 ,而游标链表则是通过 链表与数组下标新结合 它与普通链表的区别在内存方面普通链表需要malloc和free来动态的分配内存 而 游标链表的内存则是由自己管理 普通链表容易产生内存的碎片化 游标链表则不会 ,游标链表具有确定性,因为内存在程序开的时候就已经分配完成了所以不需要考虑内存方面的问题,接下来就是游标链表的具体构成

复制代码
.h

typedef     void*     sLinkType; 

extern  uint32_t sys_tick_ms;   // 毫秒计数器

typedef struct 
{
    sLinkType   data;
    uint16_t    cur; 

}sLink_t;

typedef struct
{
    sLink_t *skil;
    uint16_t  max_size; 

}staticll_t;
typedef enum
{
    LINK_LIST_false = 0,
    LINK_LIST_true = 1
}Link_list_status;
.c

Link_list_status Link_list_Init(staticll_t *slink,sLink_t *skill,uint16_t max_size)
{

    slink->max_size = max_size;
    slink->skil = skill;
    for(int i = 0; i < max_size-1; i++)
    {
        slink->skil[i].cur = i+1;
    }
    slink->skil[max_size-1].cur = 0;
    return   LINK_LIST_true;
}
//结点内存的分配
uint16_t Link_list_malloc(staticll_t *slink)
{
    uint16_t i = slink->skil[0].cur;
    if(slink->skil[0].cur)
    {
        slink->skil[0].cur = slink->skil[i].cur;
    }
    return i;
}
//结点内存的释放
void Link_list_free(staticll_t *slink,uint16_t index)
{
    slink->skil[index].cur = slink->skil[0].cur;
    slink->skil[0].cur = index;
}
//结点个数的统计
uint16_t Link_list_length(staticll_t *slink)
{
    int count = 0;
    for(int i = slink->skil[0].cur;i != 0;i = slink->skil[i].cur)
    {
        count++;
    }
    return count;
}
//结点的插入
Link_list_status Link_list_insert(staticll_t *slink, uint16_t i, sLinkType e)
{   
    uint16_t j,l;
    int k = slink->max_size-1;
    if(i<1|| i>Link_list_length(slink)+1)
    {
        return LINK_LIST_false;
    }
    j = Link_list_malloc(slink);
    if (j)
    {
       slink->skil[j].data = e;
       for (l = 1; l<i-1; l++)
       {
          k = slink->skil[k].cur;
       }
       
       slink->skil[j].cur = slink->skil[k].cur;
       slink->skil[k].cur = j;

       return LINK_LIST_true;
    }
    return LINK_LIST_false;
}
//删除结点
Link_list_status Link_list_delete(staticll_t *slink, uint16_t i)
{
    int k = slink->skil[0].cur;
    int l,j;

    if(i<1|| i>Link_list_length(slink)+1)
    {
        return LINK_LIST_false;
    }
    for(j=1;j<i-1;j++)
    {
        k = slink->skil[k].cur;
    }

	j = slink->skil[k].cur;
	slink->skil[k].cur = slink->skil[j].cur;
	Link_list_free(slink, j);

    return LINK_LIST_true;

}
//查找到结点的值
sLinkType Link_list_get(staticll_t *slink, sLinkType data)
{
    int j = slink->skil[0].cur;
    while (slink->skil[j].data != data && j!=0)
    {
        j = slink->skil[j].cur;
    }
    if (j != 0)
    {
        return slink->skil[j].data;
    }
    else
        return NULL;
}

在这里我们为什么需要用到游标链表而不使用普通链表呢 主要是因为在单片机中它的内存是十分宝贵的资源如果我们使用普通的链表会产生许多的内存碎片,而游标链表的内存是确定了的因此游标链表更适合在嵌入式的场景下。

环形队列

在本次程序中环形队列的用法主要是用来存储按键的事件,通过把事件发送到环形队列中然后分别读取出来相应事件来做相应的处理。

复制代码
.h

#define KEY_QUEUE_SIZE 16 
//环形队列的主要的结构体
typedef struct  
{
   int writer;//写指针
   int read;//读指针
   key_event_t  buf[KEY_QUEUE_SIZE];//主要的存放载体
   int count;//队列中存放的元素个数
}KEY_QUEN_st;

//环形队列的状态
typedef enum
{
   KEY_QUEUE_OK,
   KEY_QUEUE_FULL,
   KEY_QUEUE_EMPTY,
   KEY_QUEUE_ERROR
}KEY_QUEN_STATUS_em;


KEY_QUEN_STATUS_em KEY_QUEN_Init(KEY_QUEN_st *quen);
KEY_QUEN_STATUS_em KEY_QUEN_ADD(KEY_QUEN_st *quen,key_event_t *event);
KEY_QUEN_STATUS_em KEY_QUEN_GET(KEY_QUEN_st *quen,key_event_t *event);
KEY_QUEN_STATUS_em KEY_QUEN_IS_EMPTY(KEY_QUEN_st *quen);
KEY_QUEN_STATUS_em KEY_QUEN_IS_FULL(KEY_QUEN_st *quen);

.c

KEY_QUEN_STATUS_em KEY_QUEN_Init(KEY_QUEN_st *quen)
{
    quen->count = 0;
    quen->read = 0;
    quen->writer = 0;
    return KEY_QUEUE_OK;  
}
KEY_QUEN_STATUS_em KEY_QUEN_ADD(KEY_QUEN_st *quen,key_event_t *event)
{
    if(KEY_QUEN_IS_FULL(quen) == KEY_QUEUE_FULL || event == NULL)
    {
        return KEY_QUEUE_ERROR;
    }
    memcpy(&quen->buf[quen->writer], event, sizeof(key_event_t));
    quen->writer = (quen->writer + 1) % KEY_QUEUE_SIZE;
    quen->count++;
    return KEY_QUEUE_OK;

}
KEY_QUEN_STATUS_em KEY_QUEN_GET(KEY_QUEN_st *quen,key_event_t *event)
{
    if(KEY_QUEN_IS_EMPTY(quen) == KEY_QUEUE_EMPTY || event == NULL) 
    {
        return KEY_QUEUE_ERROR;
    }
    
    memcpy(event, &quen->buf[quen->read], sizeof(key_event_t));

    quen->read = (quen->read + 1) % KEY_QUEUE_SIZE;
    quen->count--;
    return KEY_QUEUE_OK;
}
KEY_QUEN_STATUS_em KEY_QUEN_IS_EMPTY(KEY_QUEN_st *quen)
{
    return (quen->count == 0) ? KEY_QUEUE_EMPTY : KEY_QUEUE_OK;
}
KEY_QUEN_STATUS_em KEY_QUEN_IS_FULL(KEY_QUEN_st *quen)
{
    return (quen->count == KEY_QUEUE_SIZE) ? KEY_QUEUE_FULL : KEY_QUEUE_OK;
}

通过这两个数据结构的构建 我们按键驱动框架也差不多完成大部分,接下来就是具体功能程序的实现

复制代码
.h
//按键的状态
typedef enum
{
    KEY_UP,
    KEY_DOWN,
    KEY_PRESS,
    KEY_RELEASE,
    KEY_ERROR,
    KEY_OK
}KEY_STATUS_em;

/* ─── 按键事件结构体 ─── */
typedef struct {
    uint8_t          key_id;     // 哪个按键
    KEY_STATUS_em event;      // 什么事件
    uint32_t         timestamp;  // 时间戳
} key_event_t;

 #define KEY_MAX_NUM  8

 /* 按键ID */
 typedef enum {
     KEY_ID_1 = 1,
     KEY_ID_2 = 2,
     KEY_ID_3 = 3,
     KEY_ID_4 = 4,
 } key_id_em;
 
 /* 按键内部状态 */
 typedef enum {
     KEY_STATE_IDLE  = 0,
     KEY_STATE_PRESS = 1,
     KEY_STATE_WAIT  = 2,
 } key_state_em;
 
 /* 按键配置结构体(链表节点存的数据) */
 typedef struct {
     uint8_t     key_id;

     GPIO_TypeDef *gpio_port;   // 端口:GPIOA / GPIOB...
     uint16_t      gpio_pin;    // 引脚:GPIO_Pin_0 / GPIO_Pin_1...
     uint8_t       active_level; // 有效电平:0=低电平有效,1=高电平有效

     void      (*short_cb)(void);
     void      (*long_cb)(void);
     void      (*double_cb)(void);
     
    /*长按短按双击标志位*/
    uint8_t       key_long_f;
    uint8_t       key_short_f;
    uint8_t       key_double_f;

     /* 内部使用 */
     key_state_em state;
     uint8_t      press_cnt;
     uint32_t     press_tick;
     uint32_t     release_tick;
 } key_info_st;
 
 /* 函数声明 */
 void KEY_Init(void);
 void key_register(key_info_st *key);
 void key_scan(void);
 void key_event_process(void);

.c

/* 链表:管理所有按键 */
static sLink_t    key_sll[KEY_MAX_NUM + 1];
static staticll_t key_link;
static uint16_t   key_data_head = 0;

/* 事件队列 */
static KEY_QUEN_st key_event_quen;


/* ─── 按键初始化 ─── */
void KEY_Init(void)
{
    Link_list_Init(&key_link, key_sll, KEY_MAX_NUM + 1);
    KEY_QUEN_Init(&key_event_quen);
    key_data_head = 0;
}
/* ─── 注册按键(把按键加入链表) ─── */
void key_register(key_info_st *key)
{
    uint16_t node;
    
    /* ─── 开启对应的 GPIO 时钟 ─── */
    if (key->gpio_port == GPIOA)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    else if (key->gpio_port == GPIOB)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    else if (key->gpio_port == GPIOC)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    else if (key->gpio_port == GPIOD)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
    else if (key->gpio_port == GPIOE)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
    
    /* ─── 配置引脚为输入模式 ─── */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin  = key->gpio_pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;   // 上拉输入
    GPIO_Init(key->gpio_port, &GPIO_InitStructure);
    
    /* ─── 从空闲链申请一个节点 ─── */
    node = Link_list_malloc(&key_link);
    if (node == 0) return;
    
    /* ─── 把按键信息存入节点的 data ─── */
    key_sll[node].data = key;
    
    /* ─── 初始化按键内部状态 ─── */
    key->state      = KEY_STATE_IDLE;
    key->press_cnt  = 0;
    
    /* ─── 挂到数据链尾部 ─── */
    if (key_data_head == 0)
    {
        key_data_head = node;
        key_sll[node].cur = 0;
    }
    else
    {
        uint16_t tail = key_data_head;
        while (key_sll[tail].cur != 0)
        {
            tail = key_sll[tail].cur;
        }
        key_sll[tail].cur = node;
        key_sll[node].cur = 0;
    }
}


/* ─── 按键扫描(每10ms调用一次) ─── */
void key_scan(void)  
{
    uint32_t now = Get_Tick();    
    uint16_t node = key_data_head;    
    while (node != 0)
    {
        key_info_st *key = (key_info_st *)key_sll[node].data;
        
        /* 读 GPIO */
        uint8_t pin_level = GPIO_ReadInputDataBit(key->gpio_port, key->gpio_pin);
        uint8_t is_pressed;
        
        if (key->active_level == 0)
            is_pressed = (pin_level == Bit_RESET);   // 低电平有效
        else
            is_pressed = (pin_level == Bit_SET);     // 高电平有效
        
        /* 状态机 */
        switch (key->state)
        {
            case KEY_STATE_IDLE:
                if (is_pressed)
                {
                    key->state      = KEY_STATE_PRESS;
                    key->press_tick = now;
                    key->key_double_f = 0;
                }
                break;
                
            case KEY_STATE_PRESS:
                if (!is_pressed)
                {
                    uint32_t duration = now - key->press_tick;
                    
                    if (duration > 2000)  // 长按 > 2秒
                    {
                        key_event_t e;
                        e.key_id    = key->key_id;
                        e.event     = KEY_PRESS;
                        e.timestamp = now;
                        KEY_QUEN_ADD(&key_event_quen, &e);
                        
                        key->state = KEY_STATE_IDLE;
                    }
                    else
                    {
                        key->release_tick = now;
                        key->state = KEY_STATE_WAIT;
                    }
                }
                break;
                
            case KEY_STATE_WAIT:
                if (is_pressed)                // 又按了 → 双击
                {
                    key_event_t e;
                    e.key_id    = key->key_id;
                    e.event     = KEY_RELEASE;
                    e.timestamp = now;
                    KEY_QUEN_ADD(&key_event_quen, &e);

                    key->key_double_f = 1;
                    key->state = KEY_STATE_PRESS;
                    key->press_tick = now;                    
               
                }
                else if (now - key->release_tick > 300)  // 超时 → 短按
                {
                    if (!key->key_double_f)        
                    {
                        key_event_t e;
                        e.key_id    = key->key_id;
                        e.event     = KEY_DOWN;
                        e.timestamp = now;
                        KEY_QUEN_ADD(&key_event_quen, &e);
                    }
                    
                    key->state = KEY_STATE_IDLE;
                }
                break;
        }
        node = key_sll[node].cur;  // 下一个按键
    }
}
/* ─── 事件处理(主循环调用) ─── */
void key_event_process(void)
{
    key_event_t e;
    
    /* 取出所有待处理事件 */
    while (KEY_QUEN_GET(&key_event_quen, &e) == KEY_QUEUE_OK)
    {
        /* 在链表中找到对应按键 */
        uint16_t node = key_data_head;
        key_info_st *key = NULL;
        
        while (node != 0)
        {
            key = (key_info_st *)key_sll[node].data;
            if (key->key_id == e.key_id)
            {
                break;
            }
            node = key_sll[node].cur;
            key = NULL;
        }
        
        if (key == NULL) continue;
        
        /* 根据事件类型执行回调 */
        switch (e.event)
        {
            case KEY_DOWN:       // 短按
                if (key->short_cb) key->short_cb();
                break;
                
            case KEY_PRESS:      // 长按
                if (key->long_cb) key->long_cb();
                break;
                
            case KEY_RELEASE:    // 双击
                if (key->double_cb) key->double_cb();
                break;
                
            default:
                break;
        }
    }
}

本次就是按键驱动框架的全部内容如果有不足的地方还请广大网友积极的纠正,谢谢

相关推荐
VALENIAN瓦伦尼安教学设备1 小时前
激光对中仪应用行业及全球市场份额解析
大数据·人工智能·嵌入式硬件
coward912 小时前
Linux内核驱动初始化流程认识(关于late_initcall和modul_init驱动初始化宏差异)
linux·嵌入式硬件
ACP广源盛139246256732 小时前
GSV2221@ACP# 高带宽低功耗显示转换芯片,赋能 TRAE SOLO 设备高清扩展升级
人工智能·嵌入式硬件·电脑·音视频
bbaydnog2 小时前
嵌入式面试高频题第4弹:函数指针进阶、堆栈分析、Makefile入门,这3个答不上来就悬了
单片机·面试·职场和发展
周周记笔记3 小时前
【元器件专题】比较NPN管与PNP管用作开关设计的区别
嵌入式硬件
szxinmai主板定制专家3 小时前
基于 ARM+FPGA 数据机床实时工业控制设计--以雕刻机为例
arm开发·人工智能·嵌入式硬件·fpga开发
崇山峻岭之间3 小时前
单片机DMA实验
单片机·嵌入式硬件
wandertp3 小时前
对信号处理及滤波器的理解---基于robomaster机器人嵌入式控制系统
arm开发·stm32·算法·信号处理
XMAIPC_Robot3 小时前
基于RK3588 ARM+FPGA电火花数控机床控制系统设计,兼顾ethercat软硬件实时
linux·arm开发·人工智能·嵌入式硬件·fpga开发