【Telink B91】1. B91入坑总结以及串口Demo测试
【Telink B91】2. 矩阵按键以及freeRTOS应用
【Telink B91】3. 硬件I2C驱动OLED
【Telink B91】4. 硬件I2C采集HS3003温湿度信息

前言

试用了一下B91的FreeRTOS以及矩阵按钮,本文将讲解如何使用矩阵按键结合freeRTOS实现按钮对LED的控制。

1 FreeRTOS

官方提供的SDK中有FreeRTOS的示例工程,本文就是在此基础上实现的矩阵按钮的控制代码。

关于SDK获取途径,可以参考我上一篇帖子:【Telink B91】1. B91入坑总结以及串口Demo测试

1688799273773.png

如上图,选择Freertos_Demo可以验证对freeRTOS的应用。但是有以下事项需要注意:

官方提供的FreeRTOS的时钟配置有问题,并不精确,需要重新设置,打开3rd-party->freertos-V5->include->FreeRTOSConfig.h,并修改如下参数,因为时钟频率是24MHz。

1688799422356.png


2 矩阵按钮控制

2.1 原理图

矩阵按钮的原理图如下,按钮两边分别连接到两个引脚上,构成了一组2X2的矩阵按钮。

1688799619520.png


2.2 工作原理

矩阵按钮的两个方向:行和列分别连接到一个按钮的两边,所以就需要检测两次,先检测行引脚状态(列引脚设置为输出低,行引脚设置为输入),再检测列引脚状态(行引脚设置为输出低,列引脚设置为输入)就可以确定具体是哪个按钮按下了。

工作流程如下:

1. 设置TL_Key3和TL_Key4引脚为输出引脚,输出低电平;
2. 设置TL_Key1和TL_Key2引脚为输入,并且设置内部电阻拉高,用于检测引脚是否按下;此时,如果key1和key2被按下,TL_Key1都会检测到低电平,如果key3和key4任何一个按下,TL_Key2都会检测到低电平;
3. 如果上面检测到了TL_Key1或者TL_Key2为低电平,则说明有按钮被按下了,此时需要开始检测行引脚状态用于确实具体按下的引脚;
4. 设置TL_Key1和TL_Key1引脚为输出引脚,输出低电平;
5. 设置TL_Key3和TL_Key4引脚为输入,并且设置内部电阻拉高,用于检测引脚是否按下;此时,如果key1和key3被按下,TL_Key3都会检测到低电平,如果key2和key4任何一个按下,TL_Key4都会检测到低电平;

示例:

如果上述第2步检测到了TL_Key2为低电平,则说明Key3或者Key4被按下了(也可能同时按下);如果第5步检测到了TL_Key3为低电平,则说明按下的按钮为Key3,如果低5步TL_Key3和TL_Key4都检测到了低电平,说明按钮Key3和Key4都按下了。

3 代码实现

从上面可以知道矩阵按钮的工作原理,现在就可以开始实现代码逻辑了,如下代码从兼容性的角度出发,考虑了后期扩展功能,所以稍微复杂了一点点。

3.1 LED引脚初始化

下面为LED的引脚初始化:

/*
  • @hehung
  • 2023-7-8
  • email: 1398660197@qq.com
  • wechat: hehung95
  • reproduced and please indicate the source @hehung
  • */
  • #include "app_led.h"
  • // Initialize for Led driver
  • void Led_Init(void)
  • {
  •     /* Initialize the GPIO of LEDs */
  •     gpio_function_en(LED1);
  •     gpio_output_en(LED1);
  •     gpio_input_dis(LED1);
  •     gpio_function_en(LED2);
  •     gpio_output_en(LED2);
  •     gpio_input_dis(LED2);
  •     gpio_function_en(LED3);
  •     gpio_output_en(LED3);
  •     gpio_input_dis(LED3);
  •     gpio_function_en(LED4);
  •     gpio_output_en(LED4);
  •     gpio_input_dis(LED4);
  • }
  • 复制代码


    3.2 按钮程序

    下面为按键程序:

    app_key.c

    其中:

    - Key_Scan为处理逻辑函数,可以在任务中调用这个函数即可
    - 如果需要正确使用按钮按下功能或者释放功能,还需要注册相关的函数,需要自己先实现对应的按键按下或者释放对应的动作功能,并通过函数Key_InstallPressFunc以及Key_InstallReleaseFunc注册功能,下面会举例说明

    /*
  • @hehung
  • 2023-7-8
  • email: 1398660197@qq.com
  • wechat: hehung95
  • reproduced and please indicate the source @hehung
  • */
  • #include "app_key.h"
  • static key_status_t key_status[KEY_TOTAL_NUM] =
  • {
  •     KEY_RELEASED,
  •     KEY_RELEASED,
  •     KEY_RELEASED,
  •     KEY_RELEASED
  • };
  • static key_status_t last_key_status[KEY_TOTAL_NUM] =
  • {
  •     KEY_RELEASED,
  •     KEY_RELEASED,
  •     KEY_RELEASED,
  •     KEY_RELEASED
  • };
  • static Key_Func key_press_func[KEY_TOTAL_NUM] =
  • {
  •     NULL,
  •     NULL,
  •     NULL,
  •     NULL
  • };
  • static Key_Func key_release_func[KEY_TOTAL_NUM] =
  • {
  •     NULL,
  •     NULL,
  •     NULL,
  •     NULL
  • };
  • static void Key_InitOutput(gpio_pin_e key);
  • static void Key_InitInput(gpio_pin_e key);
  • static void Key_StatusJudge(key_num_t key);
  • static void Key_InitOutput(gpio_pin_e key)
  • {
  •     gpio_function_en(key);
  •     gpio_output_en(key);
  •     gpio_set_low_level(key);
  • }
  • static void Key_InitInput(gpio_pin_e key)
  • {
  •     gpio_function_en(key);
  •     gpio_output_dis(key);
  •     gpio_input_en(key);
  •     gpio_set_up_down_res(key, GPIO_PIN_PULLUP_10K);
  • }
  • static void Key_StatusJudge(key_num_t key)
  • {
  •     if ((KEY_PRESSED == key_status[key]) && (KEY_RELEASED == last_key_status[key]))
  •     {
  •         // pressed
  •         if (key_press_func[key] != NULL)
  •         {
  •             key_press_func[key]();
  •         }
  •     }
  •     else if ((KEY_PRESSED == key_status[key]) && (KEY_PRESSED == last_key_status[key]))
  •     {
  •         // keep pressing: TODO
  •     }
  •     else if ((KEY_RELEASED == key_status[key]) && (KEY_PRESSED == last_key_status[key]))
  •     {
  •         // released
  •         if (key_release_func[key] != NULL)
  •         {
  •             key_release_func[key]();
  •         }
  •     }
  •     else
  •     {
  •         // keep releasing: TODO
  •     }
  •     last_key_status[key] = key_status[key];
  • }
  • // matrix key scan logic
  • void Key_Scan(void)
  • {
  •     uint8_t key_pending = 0xFF;
  •     //clear all key status
  •     key_status[KEY_NUM1] = KEY_RELEASED;
  •     key_status[KEY_NUM2] = KEY_RELEASED;
  •     key_status[KEY_NUM3] = KEY_RELEASED;
  •     key_status[KEY_NUM4] = KEY_RELEASED;
  •     // scan column
  •     // Key3 and Key4 output low used for as base volt for check KEY1 and KEY2
  •     Key_InitOutput(KEY3);
  •     Key_InitOutput(KEY4);
  •     // KEY1 and KEY2 as output used for check button pressed signals
  •     Key_InitInput(KEY1);
  •     Key_InitInput(KEY2);
  •     if ((0 == gpio_get_level(KEY1)) || (0 == gpio_get_level(KEY2)))
  •     {
  •         //filer: TODO
  •         if (0 == gpio_get_level(KEY1))
  •         {
  •             key_pending = 0x00;
  •         }
  •         else
  •         {
  •             key_pending = 0x01;
  •         }
  •         // Scan row
  •         // Key1 and Key2 output low used for as base volt for check KEY3 and KEY4
  •         Key_InitOutput(KEY1);
  •         Key_InitOutput(KEY2);
  •         // KEY3 and KEY4 as output used for check button pressed signals
  •         Key_InitInput(KEY3);
  •         Key_InitInput(KEY4);
  •         if ((0 == gpio_get_level(KEY3)) || (0 == gpio_get_level(KEY4)))
  •         {
  •             // filter: TODO
  •             if (0 == gpio_get_level(KEY3))
  •             {
  •                 key_pending |= 0x00;
  •             }
  •             else
  •             {
  •                 key_pending |= 0x10;
  •             }
  •         }
  •     }
  •     switch (key_pending)
  •     {
  •         case 0x00: key_status[KEY_NUM1] = KEY_PRESSED; break;
  •         case 0x01: key_status[KEY_NUM3] = KEY_PRESSED; break;
  •         case 0x10: key_status[KEY_NUM2] = KEY_PRESSED; break;
  •         case 0x11: key_status[KEY_NUM4] = KEY_PRESSED; break;
  •     }
  •     Key_StatusJudge(KEY_NUM1);
  •     Key_StatusJudge(KEY_NUM2);
  •     Key_StatusJudge(KEY_NUM3);
  •     Key_StatusJudge(KEY_NUM4);
  • }
  • // Used for install the key press function
  • void Key_InstallPressFunc(key_num_t key, Key_Func func)
  • {
  •     key_press_func[key] = func;
  • }
  • // Used for install the key released function
  • void Key_InstallReleaseFunc(key_num_t key, Key_Func func)
  • {
  •     key_release_func[key] = func;
  • }
  • key_status_t Key_GetStatus(key_num_t key)
  • {
  •     return key_status[key];
  • }
  • 复制代码


    app_key.h

    /*
  • @hehung
  • 2023-7-8
  • email: 1398660197@qq.com
  • wechat: hehung95
  • reproduced and please indicate the source @hehung
  • */
  • #ifndef APP_KEY_H_
  • #define APP_KEY_H_
  • #include "app_config.h"
  • #define KEY_TOTAL_NUM            (4U)
  • // function pointer
  • typedef void (*Key_Func)(void);
  • typedef enum
  • {
  •     KEY_PRESSED = 0U,
  •     KEY_RELEASED = 1U
  • } key_status_t;
  • typedef enum
  • {
  •     KEY_NUM1 = 0U,
  •     KEY_NUM2,
  •     KEY_NUM3,
  •     KEY_NUM4
  • } key_num_t;
  • extern void Key_Scan(void);
  • extern key_status_t Key_GetStatus(key_num_t key);
  • extern void Key_InstallPressFunc(key_num_t key, Key_Func func);
  • #endif /* APP_KEY_H_ */
  • 复制代码


    3.3 FreeRTOS

    注册了两个任务:

    - 一个是LED任务,用来控制LED4每500ms翻转一次
    - 一个是按键任务,用于控制对应按键动作

    当按键KEY1按下时,LED1翻转

    当按键KEY2按下时,LED2翻转

    当按键KEY3按下时,LED3翻转

    当按键KEY4按下时,LED1,LED2,LED3都会翻转

    任务Os_TaskKey中会调用Key_InstallPressFunc先注册Key1~Key4的按下函数,代码如下,但是调用Key_Scan开始按键的周期采样按断以及处理:

    /*
  • @hehung
  • 2023-7-8
  • email: 1398660197@qq.com
  • wechat: hehung95
  • reproduced and please indicate the source @hehung
  • */
  • #include "app_freeRTOS.h"
  • #include <FreeRTOS.h>
  • #include <task.h>
  • #include "app_led.h"
  • #include "app_key.h"
  • #include "printf.h"
  • static void Os_TaskLed(void *pvParameters);
  • static void Os_TaskKey(void *pvParameters);
  • /* Initialize for OS driver */
  • void Os_Start(void)
  • {
  •     // Installing the tasks
  •     xTaskCreate(Os_TaskLed, "led_t", 256, (void*)0, 8, 0);
  •     xTaskCreate(Os_TaskKey, "key_t", 512, (void*)0, 2, 0);
  •     vTaskStartScheduler();
  • }
  • // Led control task
  • static void Os_TaskLed(void *pvParameters)
  • {
  •     (void)(pvParameters);
  •     // Initialize LEDs
  •     Led_Init();
  •     while(1)
  •     {
  •         gpio_toggle(LED4);
  •         vTaskDelay(500);
  •     }
  • }
  • void Os_TaskKey1_pressed(void)
  • {
  •     gpio_toggle(LED1);
  • }
  • void Os_TaskKey2_pressed(void)
  • {
  •     gpio_toggle(LED2);
  • }
  • void Os_TaskKey3_pressed(void)
  • {
  •     gpio_toggle(LED3);
  • }
  • void Os_TaskKey4_pressed(void)
  • {
  •     gpio_toggle(LED1);
  •     gpio_toggle(LED2);
  •     gpio_toggle(LED3);
  • }
  • static void Os_TaskKey(void *pvParameters)
  • {
  •     (void)(pvParameters);
  •     // Installing the key press handler function
  •     Key_InstallPressFunc(KEY_NUM1, Os_TaskKey1_pressed);
  •     Key_InstallPressFunc(KEY_NUM2, Os_TaskKey2_pressed);
  •     Key_InstallPressFunc(KEY_NUM3, Os_TaskKey3_pressed);
  •     Key_InstallPressFunc(KEY_NUM4, Os_TaskKey4_pressed);
  •     while(1)
  •     {
  •         Key_Scan();
  •         vTaskDelay(10);
  •     }
  • }
  • 复制代码


    3.4 主函数

    主函数如下:

    int main (void)
  • {
  •     sys_init(LDO_1P4_LDO_1P8, VBAT_MAX_VALUE_GREATER_THAN_3V6);
  •     //Note: This function can improve the performance of some modules, which is described in the function comments.
  •     //Called immediately after sys_init, set in other positions, some calibration values may not take effect.
  •     user_read_flash_value_calib();
  •     CCLK_24M_HCLK_24M_PCLK_24M;
  •     printf("\r\n");  // user for avoid print garbled code on the first character
  •     printf("\r\n ==== Test project for B91 ==== \r\n");
  •     // Install the tasks and start FreeRTOS scheduler
  •     Os_Start();
  •     while (1) {}
  •     return 0;
  • }
  • 复制代码


    4 实现效果


    如下演示视频,演示了4个按钮的控制功能。