ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32+FreeRTOS+OLED多级菜单

STM32+FreeRTOS+OLED多级菜单 在成功使用上一章节的RCT时钟后基于FreeRTOS来实现简单的时钟功能我将功能划分如下主界面、选择菜单、时间、闹钟、地区各设置选项。在做好任务的构思后先来理清各个任务之间的触发关系。在菜单中的操作命令上移、下移、进入、返回由按键通过外部中断触发在触发并接收命令后执行各任务流程刷新OLED修改时钟、闹钟设置修改地区设置并通过WiFi通知服务器修改要获取天气位置。因为菜单选项较多因此引入Free RTOS中的队列来用于记录当前菜单位置需引入queue.h。菜单的结构分为两个部分来定义更清楚第一部分是菜单中的某个选项与他对应的下一级菜单之间的联系比如闹钟选项与其子菜单的关系。第二部分是当前菜单中相关的设置menu_index用于说明当前菜单页中被选中的菜单项下标0 开始menu_item_count则是说明当前菜单页包含的菜单项总数。typedef struct menu_item_s { const char *next_menu_name; void (*action)(void); // 可选确认时执行的动作 const struct menu_item_s *next_items; // 子菜单数组 uint8_t next_count; // 子菜单项数 }menu_item_t; typedef struct { const menu_item_t *menu_items; uint8_t menu_index; uint8_t menu_item_count; }menu_page_t; extern menu_page_t menu_stack[MENU_STACK_MAX]; extern xQueueHandle xQueueKey; //示例 这里主菜单中选择闹钟菜单-》对应的闹钟子菜单中有4个选项 const menu_item_t main_menu[] { {Time, NULL, time_menu, 2}, {Alarm, NULL, alarm_menu, 4}, {Area, NULL, area_menu, 1}, }; const menu_item_t alarm_menu[] { {Alarm Add, alarm_add_page, NULL, 0}, {Alarm Delete, alarm_delete_page, NULL, 0}, {Alarm Enable, alarm_enable_page, NULL, 0}, {Alarm Disable, alarm_disable_page, NULL, 0}, };这里因为涉及到按键和OLED屏两个硬件设备因此我将任务划分为两部分按键任务用于检测是否有按键中断显示任务负责接收按键的时间信息并通知执行任务。这里还是使用的轮询扫描我明天再看看能不能优化一下直接用中断发送信号量来获取。void key_task(void *pvParameters) { key_event_t evt; while (1) { evt KEY_Scan(); if (evt ! NO_PRESS) { xQueueSend(xQueueKey, evt, 0); } vTaskDelay(pdMS_TO_TICKS(20)); // 去抖 } } void oledmenu_task(void *pvParameters) { key_event_t evt; main_window_render(); // 开机先显示一次 while (1) { if (xQueueReceive(xQueueKey, evt, pdMS_TO_TICKS(500)) pdTRUE) { if(menu_top -1) { // 如果栈为空按下任意键进入主菜单 menu_push(main_menu, sizeof(main_menu)/sizeof(main_menu[0])); menu_render(); continue; } menu_key_handler(evt); menu_render(); } else{ // 没有按键事件,栈为空刷新主界面时间显示 if (menu_top -1) { main_window_render(); } } } }和菜单的栈相关函数如下主要需要考虑的就是出栈、入栈以及相关函数使用时存在的边界问题发生越界会导致程序崩溃。void menu_push(const menu_item_t *items, uint8_t count) { if (menu_top MENU_STACK_MAX - 1) { menu_top; menu_stack[menu_top].menu_items items; menu_stack[menu_top].menu_index 0; menu_stack[menu_top].menu_item_count count; } } void menu_pop(void) { if (menu_top 0) { menu_top--; } } menu_page_t *menu_current(void)//当前菜单 { if (menu_top 0) { return menu_stack[menu_top]; } return NULL; } void menu_render(void)//渲染选择菜单 { menu_page_t *p menu_current(); if (p NULL) return; // 栈为空直接返回 OLED_Clear(); for (int i 0; i p-menu_item_count; i) { char line[24]; sprintf(line, %c %s, (i p-menu_index) ? : , p-menu_items[i].next_menu_name); OLED_ShowString(0, i*24, (u8*)line, 16); } OLED_Refresh(); } void menu_key_handler(key_event_t key) { menu_page_t *p menu_current(); if (p NULL) return; switch (key) { case KEY_UP: if (p-menu_index 0) {p-menu_index--;} break; case KEY_DOWN: if (p-menu_index p-menu_item_count - 1) p-menu_index; break; case KEY_OK: if (p-menu_items[p-menu_index].action) p-menu_items[p-menu_index].action(); if (p-menu_items[p-menu_index].next_items) menu_push(p-menu_items[p-menu_index].next_items, p-menu_items[p-menu_index].next_count); break; case KEY_BACK: menu_pop(); if (menu_top -1) main_window_render(); break; case NO_PRESS: break; } }实现效果如图所示。总结栈的结构确实很适合来做菜单的任务。改进只做了还没完全实现功能可以通过加入WiFi来获取在线的准确时间以及天气。
返回列表