1. 用到的库:
STM32F4XX标准库,OLED显示驱动和字库(点击下载)
2. 菜单状态机的基本逻辑
3. 运行效果
4. 实现方式
基本思路:每个菜单的页面是一个状态,根据不同的按钮事件改变状态机的状态,根据当前状态机的状态绘制对应的菜单、做出对应的按钮响应。
1) 定义菜单的基本结构体和对外接口(Menu.h)
#ifndef __MENU_H__
#define __MENU_H__
#include "stm32f4xx.h"
/* 菜单基本事件 */
typedef enum
{
MENU_EVENT_KEY1,
MENU_EVENT_KEY2,
MENU_EVENT_KEY3,
MENU_EVENT_REFLASH // 菜单屏幕刷新
}Menu_Event;
typedef enum
{
MENU_STATE_MAIN, // 主界面
MENU_STATE_PER_OPER // 基本外设操作界面
}Menu_State;
typedef struct
{
uint8_t state; // 当前页面
uint8_t curPos; // 光标位置
float temperature;
float humidity;
}Menu, *FSM_Menu;
/* 基本接口声明 */
void FSM_Menu_Init(FSM_Menu fsm);
void FSM_Menu_Proc(FSM_Menu fsm);
void FSM_Menu_Event(FSM_Menu fsm, Menu_Event event);
#endif //__MENU_H__
2) Menu.c的内部声明
#define FSM_CHECK(fsm) if(fsm == NULL) return
/* 内部函数声明 */
static void FSM_Menu_Key1Proc(FSM_Menu fsm); // 按钮1逻辑处理
static void FSM_Menu_Key2Proc(FSM_Menu fsm); // 按钮2逻辑处理
static void FSM_Menu_Key3Proc(FSM_Menu fsm); // 按钮3逻辑处理
static void FSM_Menu_Reflash(FSM_Menu fsm);
static void PerOperationReflash(FSM_Menu fsm); // 基本外设操作页面显示
static void MainReflash(FSM_Menu fsm); // 主菜单显示
/************************************************/
3)菜单的基础逻辑函数(Menu.c)
void FSM_Menu_Init(FSM_Menu fsm)
{
FSM_CHECK(fsm);
/* 初始化基本数据 */
fsm->state = MENU_STATE_MAIN;
fsm->curPos = 0;
fsm->humidity = 0.0f;
fsm->temperature = 0.0f;
/* 初始化要调用的外设 */
drv_key_init();
drv_beep_init();
drv_led_init();
drv_fan_init();
drv_motor_init();
drv_sht20_init();
OLED_Init();
/* 首次更新菜单显示 */
FSM_Menu_Reflash(fsm);
}
/// @brief 用于FSM基础按键轮询检测处理
/// @param fsm
void FSM_Menu_Proc(FSM_Menu fsm)
{
FSM_CHECK(fsm);
if(drv_key_GetKey(GPIOC, KEY1))
{
FSM_Menu_Event(fsm, MENU_EVENT_KEY1);
}
else if(drv_key_GetKey(GPIOC, KEY2))
{
FSM_Menu_Event(fsm, MENU_EVENT_KEY2);
}
else if(drv_key_GetKey(GPIOC, KEY3))
{
FSM_Menu_Event(fsm, MENU_EVENT_KEY3);
}
}
/// @brief 用来处理外部调用发生的菜单事件
/// @param fsm 状态机指针
/// @param event 具体事件
void FSM_Menu_Event(FSM_Menu fsm, Menu_Event event)
{
FSM_CHECK(fsm);
switch (event)
{
case MENU_EVENT_KEY1:
FSM_Menu_Key1Proc(fsm);
break;
case MENU_EVENT_KEY2:
FSM_Menu_Key2Proc(fsm);
break;
case MENU_EVENT_KEY3:
FSM_Menu_Key3Proc(fsm);
break;
case MENU_EVENT_REFLASH:
FSM_Menu_Reflash(fsm);
break;
default:
break;
}
}
/// @brief 用于判断当前菜单页面 执行相应的显示函数
/// @param fsm
static void FSM_Menu_Reflash(FSM_Menu fsm)
{
OLED_Clear();
switch (fsm->state)
{
case MENU_STATE_MAIN:
MainReflash(fsm);
break;
case MENU_STATE_PER_OPER:
PerOperationReflash(fsm);
break;
default:
break;
}
OLED_Refresh();
}
4)按钮处理函数(Menu.c)
static void FSM_Menu_Key1Proc(FSM_Menu fsm)
{
/* 主页面按钮1按下处理 */
if (fsm->state == MENU_STATE_MAIN)
{
/* 移动光标位置 并且刷新菜单显示 */
fsm->curPos++;
if (fsm->curPos >= 3)
{
fsm->curPos = 0;
}
FSM_Menu_Reflash(fsm);
}/* 外设页面按钮1按下处理 */
else if (fsm->state == MENU_STATE_PER_OPER)
{
/* 移动光标位置 并且刷新菜单显示 */
fsm->curPos++;
if (fsm->curPos >= 4)
{
fsm->curPos = 0;
}
FSM_Menu_Reflash(fsm);
}
}
static void FSM_Menu_Key2Proc(FSM_Menu fsm)
{
/* 主页面按钮2按下处理 */
if (fsm->state == MENU_STATE_MAIN)
{
/* 根据光标位置进行操作 */
if (fsm->curPos == 0) //温度显示
{
fsm->temperature = drv_sht20_read_temperature();
fsm->humidity = 0.0f;
FSM_Menu_Reflash(fsm);
}
if (fsm->curPos == 1) //湿度显示
{
fsm->humidity = drv_sht20_read_humidity();
fsm->temperature = 0.0f;
FSM_Menu_Reflash(fsm);
}
if (fsm->curPos == 2) //转换到外设控制菜单 刷新显示
{
fsm->state = MENU_STATE_PER_OPER;
fsm->curPos = 0;
FSM_Menu_Reflash(fsm);
}
} /* 外设界面按钮2处理 */
else if (fsm->state == MENU_STATE_PER_OPER)
{
switch (fsm->curPos)
{
case 0: // 翻转LED
drv_led_toggle(LED1);
drv_led_toggle(LED2);
drv_led_toggle(LED3);
break;
case 1: // 翻转蜂鸣器
drv_beep_toggle();
break;
case 2: // 翻转风扇
drv_fan_toggle();
break;
case 3: // 翻转震动马达
drv_motor_toggle();
break;
default:
break;
}
}
}
static void FSM_Menu_Key3Proc(FSM_Menu fsm)
{
/* 外设菜单页面按钮3处理 */
if (fsm->state == MENU_STATE_PER_OPER)
{
/* 重置光标 回到主菜单 */
fsm->state = MENU_STATE_MAIN;
fsm->curPos = 0;
FSM_Menu_Reflash(fsm);
}
}
5)菜单页面绘制函数(Menu.c)
/// @brief 主菜单页面绘制函数
/// @param fsm
static void MainReflash(FSM_Menu fsm)
{
uint8_t i = 0;
char *table[3] = {"1. Temperature", "2. Humidity", "3. Other Opera"};
/* 字体大小 宽/高 6x8/6x12/8x16/12x24 */
/* 绘制思路:
1. 绘制菜单标题 使用8x16号字体
2. 逐行绘制菜单选项 使用 6x12 号字体
1. 根据光标位置绘制箭头或者空格(2个字符)
2. 绘制每一行的选项 y坐标公式: i * 12 + 16
3. 如果温湿度不为0.0f 在第5行绘制温湿度
*/
OLED_ShowString(0,0, "Main menu", 16, 1);
for (; i < 3; i++)
{
/* 根据光标位置绘制箭头 */
if (fsm->curPos == i)
{
OLED_ShowString(0, i*12 + 16, "->", 12, 1);
}
else
{
OLED_ShowString(0, i*12 + 16, " ", 12, 1);
}
/* 绘制菜单选项 */
OLED_ShowString(12, i*12 + 16, table[i], 12, 1);
}
// 在第5行绘制温湿度
if (fsm->temperature != 0.0f)
{
OLED_ShowString(12, 16 + 3*12,"temp:", 12, 1);
/* 绘制整数位 */
i = (uint8_t)fsm->temperature;
OLED_ShowNum(12 + 6*6, 16 + 3*12 , i, 2, 12, 1);
/* 绘制小数点 */
OLED_ShowChar(12 + 8*6, 16 + 3*12, '.', 12, 1);
/* 绘制小数位 */
i = ((uint16_t)(fsm->temperature * 100) % 100);
OLED_ShowNum(12 + 9*6, 16 + 3*12 , i, 2, 12, 1);
}
else if (fsm->humidity != 0.0f)
{
OLED_ShowString(12, 16 + 3*12,"humi:", 12, 1);
/* 绘制整数位 */
i = (uint8_t)fsm->humidity;
OLED_ShowNum(12 + 6*6, 16 + 3*12 , i, 2, 12, 1);
/* 绘制小数点 */
OLED_ShowChar(12 + 8*6, 16 + 3*12, '.', 12, 1);
/* 绘制小数位 */
i = ((uint16_t)(fsm->humidity * 100) % 100);
OLED_ShowNum(12 + 9*6, 16 + 3*12 , i, 2, 12, 1);
}
}
/// @brief 外设操作页面绘制函数
/// @param fsm
static void PerOperationReflash(FSM_Menu fsm)
{
uint8_t i = 0;
char *table[4] = {"1. UserLed", "2. Beep", "3. Fan", "4. Motor"};
/* 字体大小 宽/高 6x8/6x12/8x16/12x24 */
/* 绘制思路:
1. 绘制菜单标题 使用8x16号字体
2. 逐行绘制菜单选项 使用 6x12 号字体
1. 根据光标位置绘制箭头或者空格(2个字符)
2. 绘制每一行的选项 y坐标公式: i * 12 + 16
*/
OLED_ShowString(0,0, "Other menu", 16, 1);
for (; i < 4; i++)
{
/* 根据光标位置 绘制箭头 */
if (fsm->curPos == i)
{
OLED_ShowString(0, i*12 + 16, "->", 12, 1);
}
else
{
OLED_ShowString(0, i*12 + 16, " ", 12, 1);
}
/* 绘制菜单选项 */
OLED_ShowString(12, i*12 + 16, table[i], 12, 1);
}
}
6) main函数(main.c)
int main(void)
{
Menu menu;
FSM_Menu_Init(&menu);
while(1)
{
FSM_Menu_Proc(&menu);
}
}