【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的引脚初始化:

  1. /*
  2. @hehung
  3. 2023-7-8
  4. email: 1398660197@qq.com
  5. wechat: hehung95
  6. reproduced and please indicate the source @hehung
  7. */
  8. #include "app_led.h"
  9. // Initialize for Led driver
  10. void Led_Init(void)
  11. {
  12.     /* Initialize the GPIO of LEDs */
  13.     gpio_function_en(LED1);
  14.     gpio_output_en(LED1);
  15.     gpio_input_dis(LED1);
  16.     gpio_function_en(LED2);
  17.     gpio_output_en(LED2);
  18.     gpio_input_dis(LED2);
  19.     gpio_function_en(LED3);
  20.     gpio_output_en(LED3);
  21.     gpio_input_dis(LED3);
  22.     gpio_function_en(LED4);
  23.     gpio_output_en(LED4);
  24.     gpio_input_dis(LED4);
  25. }


3.2 按钮程序

下面为按键程序:

app_key.c

其中:

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

  1. /*
  2. @hehung
  3. 2023-7-8
  4. email: 1398660197@qq.com
  5. wechat: hehung95
  6. reproduced and please indicate the source @hehung
  7. */
  8. #include "app_key.h"
  9. static key_status_t key_status[KEY_TOTAL_NUM] =
  10. {
  11.     KEY_RELEASED,
  12.     KEY_RELEASED,
  13.     KEY_RELEASED,
  14.     KEY_RELEASED
  15. };
  16. static key_status_t last_key_status[KEY_TOTAL_NUM] =
  17. {
  18.     KEY_RELEASED,
  19.     KEY_RELEASED,
  20.     KEY_RELEASED,
  21.     KEY_RELEASED
  22. };
  23. static Key_Func key_press_func[KEY_TOTAL_NUM] =
  24. {
  25.     NULL,
  26.     NULL,
  27.     NULL,
  28.     NULL
  29. };
  30. static Key_Func key_release_func[KEY_TOTAL_NUM] =
  31. {
  32.     NULL,
  33.     NULL,
  34.     NULL,
  35.     NULL
  36. };
  37. static void Key_InitOutput(gpio_pin_e key);
  38. static void Key_InitInput(gpio_pin_e key);
  39. static void Key_StatusJudge(key_num_t key);
  40. static void Key_InitOutput(gpio_pin_e key)
  41. {
  42.     gpio_function_en(key);
  43.     gpio_output_en(key);
  44.     gpio_set_low_level(key);
  45. }
  46. static void Key_InitInput(gpio_pin_e key)
  47. {
  48.     gpio_function_en(key);
  49.     gpio_output_dis(key);
  50.     gpio_input_en(key);
  51.     gpio_set_up_down_res(key, GPIO_PIN_PULLUP_10K);
  52. }
  53. static void Key_StatusJudge(key_num_t key)
  54. {
  55.     if ((KEY_PRESSED == key_status[key]) && (KEY_RELEASED == last_key_status[key]))
  56.     {
  57.         // pressed
  58.         if (key_press_func[key] != NULL)
  59.         {
  60.             key_press_func[key]();
  61.         }
  62.     }
  63.     else if ((KEY_PRESSED == key_status[key]) && (KEY_PRESSED == last_key_status[key]))
  64.     {
  65.         // keep pressing: TODO
  66.     }
  67.     else if ((KEY_RELEASED == key_status[key]) && (KEY_PRESSED == last_key_status[key]))
  68.     {
  69.         // released
  70.         if (key_release_func[key] != NULL)
  71.         {
  72.             key_release_func[key]();
  73.         }
  74.     }
  75.     else
  76.     {
  77.         // keep releasing: TODO
  78.     }
  79.     last_key_status[key] = key_status[key];
  80. }
  81. // matrix key scan logic
  82. void Key_Scan(void)
  83. {
  84.     uint8_t key_pending = 0xFF;
  85.     //clear all key status
  86.     key_status[KEY_NUM1] = KEY_RELEASED;
  87.     key_status[KEY_NUM2] = KEY_RELEASED;
  88.     key_status[KEY_NUM3] = KEY_RELEASED;
  89.     key_status[KEY_NUM4] = KEY_RELEASED;
  90.     // scan column
  91.     // Key3 and Key4 output low used for as base volt for check KEY1 and KEY2
  92.     Key_InitOutput(KEY3);
  93.     Key_InitOutput(KEY4);
  94.     // KEY1 and KEY2 as output used for check button pressed signals
  95.     Key_InitInput(KEY1);
  96.     Key_InitInput(KEY2);
  97.     if ((0 == gpio_get_level(KEY1)) || (0 == gpio_get_level(KEY2)))
  98.     {
  99.         //filer: TODO
  100.         if (0 == gpio_get_level(KEY1))
  101.         {
  102.             key_pending = 0x00;
  103.         }
  104.         else
  105.         {
  106.             key_pending = 0x01;
  107.         }
  108.         // Scan row
  109.         // Key1 and Key2 output low used for as base volt for check KEY3 and KEY4
  110.         Key_InitOutput(KEY1);
  111.         Key_InitOutput(KEY2);
  112.         // KEY3 and KEY4 as output used for check button pressed signals
  113.         Key_InitInput(KEY3);
  114.         Key_InitInput(KEY4);
  115.         if ((0 == gpio_get_level(KEY3)) || (0 == gpio_get_level(KEY4)))
  116.         {
  117.             // filter: TODO
  118.             if (0 == gpio_get_level(KEY3))
  119.             {
  120.                 key_pending |= 0x00;
  121.             }
  122.             else
  123.             {
  124.                 key_pending |= 0x10;
  125.             }
  126.         }
  127.     }
  128.     switch (key_pending)
  129.     {
  130.         case 0x00: key_status[KEY_NUM1] = KEY_PRESSED; break;
  131.         case 0x01: key_status[KEY_NUM3] = KEY_PRESSED; break;
  132.         case 0x10: key_status[KEY_NUM2] = KEY_PRESSED; break;
  133.         case 0x11: key_status[KEY_NUM4] = KEY_PRESSED; break;
  134.     }
  135.     Key_StatusJudge(KEY_NUM1);
  136.     Key_StatusJudge(KEY_NUM2);
  137.     Key_StatusJudge(KEY_NUM3);
  138.     Key_StatusJudge(KEY_NUM4);
  139. }
  140. // Used for install the key press function
  141. void Key_InstallPressFunc(key_num_t key, Key_Func func)
  142. {
  143.     key_press_func[key] = func;
  144. }
  145. // Used for install the key released function
  146. void Key_InstallReleaseFunc(key_num_t key, Key_Func func)
  147. {
  148.     key_release_func[key] = func;
  149. }
  150. key_status_t Key_GetStatus(key_num_t key)
  151. {
  152.     return key_status[key];
  153. }


app_key.h

  1. /*
  2. @hehung
  3. 2023-7-8
  4. email: 1398660197@qq.com
  5. wechat: hehung95
  6. reproduced and please indicate the source @hehung
  7. */
  8. #ifndef APP_KEY_H_
  9. #define APP_KEY_H_
  10. #include "app_config.h"
  11. #define KEY_TOTAL_NUM            (4U)
  12. // function pointer
  13. typedef void (*Key_Func)(void);
  14. typedef enum
  15. {
  16.     KEY_PRESSED = 0U,
  17.     KEY_RELEASED = 1U
  18. } key_status_t;
  19. typedef enum
  20. {
  21.     KEY_NUM1 = 0U,
  22.     KEY_NUM2,
  23.     KEY_NUM3,
  24.     KEY_NUM4
  25. } key_num_t;
  26. extern void Key_Scan(void);
  27. extern key_status_t Key_GetStatus(key_num_t key);
  28. extern void Key_InstallPressFunc(key_num_t key, Key_Func func);
  29. #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开始按键的周期采样按断以及处理:

  1. /*
  2. @hehung
  3. 2023-7-8
  4. email: 1398660197@qq.com
  5. wechat: hehung95
  6. reproduced and please indicate the source @hehung
  7. */
  8. #include "app_freeRTOS.h"
  9. #include <FreeRTOS.h>
  10. #include <task.h>
  11. #include "app_led.h"
  12. #include "app_key.h"
  13. #include "printf.h"
  14. static void Os_TaskLed(void *pvParameters);
  15. static void Os_TaskKey(void *pvParameters);
  16. /* Initialize for OS driver */
  17. void Os_Start(void)
  18. {
  19.     // Installing the tasks
  20.     xTaskCreate(Os_TaskLed, "led_t", 256, (void*)0, 8, 0);
  21.     xTaskCreate(Os_TaskKey, "key_t", 512, (void*)0, 2, 0);
  22.     vTaskStartScheduler();
  23. }
  24. // Led control task
  25. static void Os_TaskLed(void *pvParameters)
  26. {
  27.     (void)(pvParameters);
  28.     // Initialize LEDs
  29.     Led_Init();
  30.     while(1)
  31.     {
  32.         gpio_toggle(LED4);
  33.         vTaskDelay(500);
  34.     }
  35. }
  36. void Os_TaskKey1_pressed(void)
  37. {
  38.     gpio_toggle(LED1);
  39. }
  40. void Os_TaskKey2_pressed(void)
  41. {
  42.     gpio_toggle(LED2);
  43. }
  44. void Os_TaskKey3_pressed(void)
  45. {
  46.     gpio_toggle(LED3);
  47. }
  48. void Os_TaskKey4_pressed(void)
  49. {
  50.     gpio_toggle(LED1);
  51.     gpio_toggle(LED2);
  52.     gpio_toggle(LED3);
  53. }
  54. static void Os_TaskKey(void *pvParameters)
  55. {
  56.     (void)(pvParameters);
  57.     // Installing the key press handler function
  58.     Key_InstallPressFunc(KEY_NUM1, Os_TaskKey1_pressed);
  59.     Key_InstallPressFunc(KEY_NUM2, Os_TaskKey2_pressed);
  60.     Key_InstallPressFunc(KEY_NUM3, Os_TaskKey3_pressed);
  61.     Key_InstallPressFunc(KEY_NUM4, Os_TaskKey4_pressed);
  62.     while(1)
  63.     {
  64.         Key_Scan();
  65.         vTaskDelay(10);
  66.     }
  67. }


3.4 主函数

主函数如下:

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


4 实现效果


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