Overview
This project implements a multi-level menu UI on an OLED driven by an embedded STM32. It is a compact STM32 project that integrates multiple sensors and an OLED display to realize a simple smart terminal. The multi-level menu UI uses a common struct-index approach to switch between functions, combined with DHT11, RTC, LED, and key inputs to provide integrated operation.
1. Introduction
The hardware can be adapted into a custom board for devices such as a smartwatch. At present the project runs bare-metal with CPU usage at 100%. Future work may add an RTOS.
2. Hardware Photo
3. Hardware Pinout
OLED module: VCC --> 3.3V GND --> GND SCL --> PB10 SDA --> PB11
DHT11 module: DATA --> PB9 VCC --> 3.3V GND --> GND
KEY module (this section uses pins from the development board):
KEY0 --> PE4
KEY1 --> PE3
KEY_UP --> PA0
4. Multi-level Menu
With industrial automation, almost all projects require a display terminal. Multi-level menus are a common part of display projects. On larger TFT-LCD screens many open-source GUIs can be ported, for example LVGL. On a 0.96-inch OLED, the multi-level menu usually needs to be adapted and implemented manually.
5. CubeMX Configuration
- RCC: configure external high-speed crystal oscillator (HSE) for higher accuracy.
- SYS: set Debug to Serial Wire to avoid potential chip lockup.
- I2C2: instead of using CubeMX I2C2 peripheral, this project simulates I2C with GPIOs (PB10: CLK; PB11: SDA).
- RTC: configure year, month, day, hour, minute, second.
- TIM2: DHT11 requires microsecond-level delays, while HAL provides only millisecond delays, so a timer-based microsecond delay is implemented.
- KEY inputs: configure PE3, PE4, and PA0 as GPIO inputs according to the development board schematic.
6. Code
6.1 OLED Driver Header
#ifndef __OLED_H
#define __OLED_H
#include "main.h"
#define u8 uint8_t
#define u32 uint32_t
#define OLED_CMD 0 // write command
#define OLED_DATA 1 // write data
#define OLED0561_AD 0x78 // OLED I2C address
#define COM 0x00 // OLED
#define DAT 0x40 // OLED
#define OLED_MODE 0
#define SIZE 8
#define XLevelL 0x00
#define XLevelH 0x10
#define Max_Column 128
#define Max_Row 64
#define Brightness 0xFF
#define X_WIDTH 128
#define Y_WIDTH 64
// -----------------OLED IIC GPIO emulation----------------
#define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET) // SCL
#define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET)
#define OLED_SDIN_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET) // SDA
#define OLED_SDIN_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET)
// I2C GPIO emulation
void IIC_Start();
void IIC_Stop();
void IIC_WaitAck();
void IIC_WriteByte(unsigned char IIC_Byte);
void IIC_WriteCommand(unsigned char IIC_Command);
void IIC_WriteData(unsigned char IIC_Data);
void OLED_WR_Byte(unsigned dat,unsigned cmd);
// Function prototypes
void OLED_Init(void);
void OLED_WR_Byte(unsigned dat,unsigned cmd);
void OLED_FillPicture(unsigned char fill_Data);
void OLED_SetPos(unsigned char x, unsigned char y);
void OLED_DisplayOn(void);
void OLED_DisplayOff(void);
void OLED_Clear(void);
void OLED_On(void);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size);
u32 oled_pow(u8 m,u8 n);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2);
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size);
#endif
6.2 Dino Game Graphics Header
#ifndef __DINOGAME_H
#define __DINOGAME_H
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
void OLED_DrawBMPFast(const unsigned char BMP[]);
void oled_drawbmp_block_clear(int bx, int by, int clear_size);
void OLED_DrawGround();
void OLED_DrawCloud();
void OLED_DrawDino();
void OLED_DrawCactus();
int OLED_DrawCactusRandom(unsigned char ver, unsigned char reset);
int OLED_DrawDinoJump(char reset);
void OLED_DrawRestart();
void OLED_DrawCover();
#endif
6.3 Dino Game Control Code
#include "control.h"
#include "oled.h"
#include "dinogame.h"
#include "stdlib.h"
unsigned char key_num = 0;
unsigned char cactus_category = 0;
unsigned char cactus_length = 8;
unsigned int score = 0;
unsigned int highest_score = 0;
int height = 0;
int cactus_pos = 128;
unsigned char cur_speed = 30;
char failed = 0;
char reset = 0;
int get_key(){
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==0)
{
HAL_Delay(10); // Delay
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==0)
{
return 2;
}
}
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==0)
{
HAL_Delay(10); // Delay
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==0)
{
return 1;
}
}
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==1)
{
HAL_Delay(10); // Delay
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==1)
{
return 3;
}
}
return 0;
}
void Game_control(){
while(1)
{
if(get_key() == 3) // If WK_UP key is pressed, force exit one loop
{
break;
}
if (failed == 1)
{
OLED_DrawRestart();
key_num = get_key();
if (key_num == 2)
{
if(score > highest_score) highest_score = score;
score = 0;
failed = 0;
height = 0;
reset = 1;
OLED_DrawDinoJump(reset);
OLED_DrawCactusRandom(cactus_category, reset);
OLED_Clear();
}
continue;
}
score++;
if (height <= 0) key_num = get_key();
OLED_DrawGround();
OLED_DrawCloud();
if (height>0 || key_num == 1) height = OLED_DrawDinoJump(reset);
else OLED_DrawDino();
cactus_pos = OLED_DrawCactusRandom(cactus_category, reset);
if(cactus_category == 0) cactus_length = 8;
else if(cactus_category == 1) cactus_length = 16;
else cactus_length = 24;
if (cactus_pos + cactus_length < 0)
{
cactus_category = rand()%4;
OLED_DrawCactusRandom(cactus_category, 1);
}
if ((height < 16) && (cactus_pos>=16 && cactus_pos <=32) || (cactus_pos + cactus_length>=16 && cactus_pos + cactus_length <=32))
{
failed = 1;
}
OLED_ShowString(35, 0, "HI:", 12);
OLED_ShowNum(58, 0, highest_score, 5, 12);
OLED_ShowNum(98, 0, score, 5, 12);
reset = 0;
cur_speed = score/20;
if (cur_speed > 29) cur_speed = 29;
HAL_Delay(30 - cur_speed);
key_num = 0;
}
}
6.4 Multi-level Menu Core Header
#ifndef __MENU_H
#define __MENU_H
#include "main.h"
#define u8 unsigned char
// Key definitions
#define KEY0 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) // active low
#define KEY1 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) // active low
#define WK_UP HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) // active high
typedef struct{
u8 current; // current state index
u8 next; // next state
u8 enter; // enter
u8 back; // back
void (*current_operation)(void); // operation to execute for current state
} Menu_table;
// UI screens
void home();
void Temperature();
void Palygame();
void Setting();
void Info();
void Menu_key_set(void);
u8 KEY_Scan(u8 mode);
void TestTemperature();
void ConrtolGame();
void Set();
void Information();
void LED();
void RTC_display();
#endif
7. Summary
This project is an initial, simple implementation of a multi-level menu UI on an OLED with an STM32. The UI currently uses static icons for battery and signal; future development may add battery coulomb counting to measure battery capacity. The article highlights areas to note and possible improvements for further development.