【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 1688799273773.png](https://static.assets-stash.eet-china.com/forum/202307/08/152841wlblrvopbozpgbop.png)
如上图,选择Freertos_Demo可以验证对freeRTOS的应用。但是有以下事项需要注意:
官方提供的FreeRTOS的时钟配置有问题,并不精确,需要重新设置,打开3rd-party->freertos-V5->include->FreeRTOSConfig.h,并修改如下参数,因为时钟频率是24MHz。
![1688799422356.png 1688799422356.png](https://static.assets-stash.eet-china.com/forum/202307/08/152848mzjzydtzptztmp4c.png)
2 矩阵按钮控制
2.1 原理图
矩阵按钮的原理图如下,按钮两边分别连接到两个引脚上,构成了一组2X2的矩阵按钮。
![1688799619520.png 1688799619520.png](https://static.assets-stash.eet-china.com/forum/202307/08/152848mjrtp0ujjjijtzop.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个按钮的控制功能。