STM32F407基于标准库、OLED、按钮控制的多级菜单实现(基于状态机FSM)

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);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值