0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

【開源小項目】基于STM32的OLED舵機菜單顯示

嵌入式悅翔園 ? 來源:CSDN-混分巨獸龍某某 ? 2023-01-16 12:06 ? 次閱讀

一、前言

本文的OLED多級菜單UI為一個綜合性的STM32小項目,使用多傳感器與OLED顯示屏實現(xiàn)智能終端的效果。項目中的多級菜單UI使用了較為常見的結構體索引法去實現(xiàn)功能與功能之間的來回切換,搭配DHT11,RTC,LED,KEY等器件實現(xiàn)高度智能化一體化操作。

后期自己打板設計結構,可以衍生為智能手表等小玩意。目前,項目屬于裸機狀態(tài)(CPU占用率100%),后期可能會加上RTOS系統(tǒng)。(本項目源碼在本文末尾進行開源?。?/p>

二、硬件實物圖

41ca6564-9550-11ed-bfe3-dac502259ad0.png

溫度計:

41e1c31c-9550-11ed-bfe3-dac502259ad0.png41f51ee4-9550-11ed-bfe3-dac502259ad0.png

游戲機:

42731f6a-9550-11ed-bfe3-dac502259ad0.png429218a2-9550-11ed-bfe3-dac502259ad0.png

三、硬件引腳圖

OLED模塊:

VCC-->3.3V

GND-->GND

SCL-->PB10

SDA-->PB11

DHT11模塊:

DATA-->PB9

VCC-->3.3V

GND-->GND

KEY模塊(這部分筆者直接使用了正點原子精英板上的):

KEY0-->PE4

KEY1-->PE3

KEY_UP-->PA0

四、多級菜單

隨著工業(yè)化和自動化的發(fā)展,如今基本上所有項目都離不開顯示終端。而多級菜單更是終端顯示項目中必不可少的組成因素,其實TFT-LCD屏幕上可以借鑒移植很多優(yōu)秀的開源多級菜單(GUI,比如:LVGL),而0.96寸的OLED屏幕上通常需要自己去適配和編程多級菜單。

網上的普遍采用的多級菜單的方案是基于索引或者結構樹,其中,索引法居多。索引法的優(yōu)點:可閱讀性好,拓展性也不錯,查找的性能差不多是最優(yōu),就是有點占用內存空間。

4.1 索引法多級菜單實現(xiàn)

網上關于索引法實現(xiàn)多級菜單功能有很多基礎教程,筆者就按照本項目中的具體實現(xiàn)代碼過程給大家講解一下索引法實現(xiàn)多級菜單。特別說明:本項目直接使用了正點原子的精英板作為核心板,所以讀者朋友復現(xiàn)代碼還是很簡單的。

首先,基于索引法實現(xiàn)多級菜單的首要條件是先確定項目中將使用到幾個功能按鍵(比如:向前,向后,確定,退出等等)本項目中,筆者使用到了3個按鍵:下一個(next),確定(enter),退出(back)。所以,接下首先定義一個結構體,結構體中一共有5個變量(3+2),分別為:當前索引序號(current),向下一個(next),確定(enter),退出(back),當前執(zhí)行函數(shù)(void)。其中,標紅的為需要設計的按鍵(筆者這里有3個),標綠的則為固定的索引號與該索引下需要執(zhí)行的函數(shù)。

typedefstruct
{
u8current;//當前狀態(tài)索引號
u8next;//向下一個
u8enter;//確定
6u8back;//退出
void(*current_operation)(void);//當前狀態(tài)應該執(zhí)行的操作
}Menu_table;

接下來就是定義一個數(shù)組去決定整個項目菜單的邏輯順序(利用索引號)

Menu_tabletable[30]=
{
{0,0,1,0,(*home)},//一級界面(主頁面)索引,向下一個,確定,退出

{1,2,5,0,(*Temperature)},//二級界面溫濕度
{2,3,6,0,(*Palygame)},//二級界面游戲
{3,4,7,0,(*Setting)},//二級界面設置
{4,1,8,0,(*Info)},//二級界面信息

{5,5,5,1,(*TestTemperature)},//三級界面:DHT11測量溫濕度
{6,6,6,2,(*ControlGame)},//三級界面:谷歌小恐龍Dinogame
{7,7,9,3,(*Set)},//三級界面:設置普通外設狀態(tài)LED
{8,8,8,4,(*Information)},//三級界面:作者和相關項目信息

{9,9,7,3,(*LED)},//LED控制
};

這里解釋一下這個數(shù)組中各元素的意義,由于我們在前面先定義了Menu_table結構體,結構體成員變量分別與數(shù)組中元素對應。比如:{0,0,1,0,(*home)},代表了索引號為0,按向下鍵(next)轉入索引號為0,按確定鍵(enter)轉入索引號為1,按退出鍵(back)轉入索引號為0,索引號為0時執(zhí)行home函數(shù)。

在舉一個例子幫助大家理解一下,比如,我們當前程序處在索引號為2(游戲界面),就會執(zhí)行Playgame函數(shù)。此時,如果按下next按鍵,程序當前索引號就會變?yōu)?,并且執(zhí)行索引號為3時候的Setting函數(shù)。如果按下enter按鍵,程序當前索引號就會變?yōu)?,并且執(zhí)行索引號為6時候的ControlGame函數(shù)。如果按下back按鍵,程序當前索引號就會變?yōu)?,并且執(zhí)行索引號為0時候的home函數(shù)。

再接下就是按鍵處理函數(shù):

uint8_tfunc_index=0;//主程序此時所在程序的索引值

voidMenu_key_set(void)
{
if((KEY_Scan(1)==1)&&(func_index!=6))//屏蔽掉索引6下的情況,適配游戲
{
func_index=table[func_index].next;//按鍵next按下后的索引號
OLED_Clear();
}

if((KEY_Scan(1)==2)&&(func_index!=6))
{
func_index=table[func_index].enter;//按鍵enter按下后的索引號
OLED_Clear();
}

if(KEY_Scan(1)==3)
{
func_index=table[func_index].back;//按鍵back按下后的索引號
OLED_Clear();
}

current_operation_index=table[func_index].current_operation;//執(zhí)行當前索引號所對應的功能函數(shù)
(*current_operation_index)();//執(zhí)行當前操作函數(shù)
}


//按鍵函數(shù)
u8KEY_Scan(u8mode)
{
staticu8key_up=1;
if(mode)key_up=1;
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
HAL_Delay(100);//消抖
key_up=0;
if(KEY0==0)return1;
elseif(KEY1==0)return2;
elseif(WK_UP==1)return3;
}elseif(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return0;
}

說明2點:

(1)由于是目前本項目是裸機狀態(tài)下運行的,所以CPU占用率默認是100%的,所以這里使用按鍵支持連按時,對于菜單的切換更好些。

(2)可能部分索引號下的執(zhí)行函數(shù),需要使用到已經定義的3個按鍵(比如,本項目中的DInogame中)。所以,可以在需要差別化的索引號下去屏蔽原先的按鍵功能。如下:

if((KEY_Scan(1)==1)&&(func_index!=6))//屏蔽掉索引6下的情況,適配游戲
{
func_index=table[func_index].next;//按鍵next按下后的索引號
OLED_Clear();
}

if((KEY_Scan(1)==2)&&(func_index!=6))//屏蔽掉索引6下的情況,適配游戲
{
func_index=table[func_index].enter;//按鍵enter按下后的索引號
OLED_Clear();
}

(3)筆者這里是使用全屏刷新去切換功能界面,同時,沒有啟用高級算法去加速顯示,所以可能在切換界面的時候效果一般。讀者朋友可以試試根據(jù)自己的UI情況使用局部刷新,這樣可能項目會更加絲滑一點。

本項目中的菜單索引圖:

435e37ac-9550-11ed-bfe3-dac502259ad0.png

4.2 內部功能實現(xiàn)(簡化智能手表)

OLED就是正常的驅動與顯示,有能力的讀者朋友可以使用高級算法去加速OLED屏幕的刷新率,可以使自己的多級菜單切換起來更絲滑。

唯一需要注意的點就是需要去制作菜單里面的UI圖標(注意圖片大小是否合適):

436ae556-9550-11ed-bfe3-dac502259ad0.png

如果是黑白圖片的話,可以直接使用PCtoLCD2002完美版進行取模:

43bb502c-9550-11ed-bfe3-dac502259ad0.png

4.3 KEY按鍵

KEY按鍵注意消抖(建議裸機情況下支持連續(xù)按動),同時注意自己實際硬件情況去進行編程(電阻是否存在上拉或者下拉)。

43c9c5d0-9550-11ed-bfe3-dac502259ad0.png

4.4 DinoGame實現(xiàn)

43d6f1ec-9550-11ed-bfe3-dac502259ad0.png谷歌公司最近比較流行的小游戲,筆者之前有文章進行了STM32的成功復刻。博客地址:基于STM32的小游戲——谷歌小恐龍(Chrome Dino Game)_混分巨獸龍某某的博客-CSDN博客_谷歌恐龍

4.5 LED控制和DHT11模塊

LED和DHT11模塊其實都屬于外設控制,這里讀者朋友可以根據(jù)自己的實際情況去取舍。需要注意的是盡可能適配一下自己多級菜單(外設控制也需要注意一下按鍵安排,可以參考筆者項目的設計)。

五、CubeMX配置

1、RCC配置外部高速晶振(精度更高)——HSE;

43ef31bc-9550-11ed-bfe3-dac502259ad0.png

2、SYS配置:Debug設置成Serial Wire(否則可能導致芯片自鎖);

441f8a24-9550-11ed-bfe3-dac502259ad0.png

3、I2C2配置:這里不直接使用CubeMX的I2C2,使用GPIO模擬(PB10:CLK;PB11:SDA)

442eb2d8-9550-11ed-bfe3-dac502259ad0.png

4、RTC配置:年月日,時分秒;

44404a0c-9550-11ed-bfe3-dac502259ad0.png446fb1f2-9550-11ed-bfe3-dac502259ad0.png

5、TIM2配置:由上面可知DHT11的使用需要us級的延遲函數(shù),HAL庫自帶只有ms的,所以需要自己設計一個定時器;

448305b8-9550-11ed-bfe3-dac502259ad0.png

6、KEY按鍵配置:PE3,PE4和PA0設置為端口輸入(開發(fā)板原理圖)

44b15170-9550-11ed-bfe3-dac502259ad0.png

7、時鐘樹配置:

44e1bf72-9550-11ed-bfe3-dac502259ad0.png

8、文件配置

44ee37f2-9550-11ed-bfe3-dac502259ad0.png

六、代碼

6.1 OLED驅動代碼

此部分OLED的基本驅動函數(shù),筆者使用的是I2C驅動的0.96寸OLED屏幕。所以,首先需要使用GPIO模擬I2C通訊。隨后,使用I2C通訊去驅動OLED。(此部分代碼包含了屏幕驅動與基礎顯示,如果對OLED顯示不太理解的朋友可以去看看上文提到的筆者的另一篇文章)

oled.h:

#ifndef__OLED_H
#define__OLED_H

#include"main.h"

#defineu8uint8_t
#defineu32uint32_t

#defineOLED_CMD0//寫命令
#defineOLED_DATA1//寫數(shù)據(jù)

#defineOLED0561_ADD0x78//OLEDI2C地址
#defineCOM0x00//OLED
#defineDAT0x40//OLED

#defineOLED_MODE0
#defineSIZE8
#defineXLevelL0x00
#defineXLevelH0x10
#defineMax_Column128
#defineMax_Row64
#defineBrightness0xFF
#defineX_WIDTH128
#defineY_WIDTH64


//-----------------OLEDIICGPIO進行模擬----------------

#defineOLED_SCLK_Clr()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET)//GPIO_ResetBits(GPIOB,GPIO_Pin_10)//SCL
#defineOLED_SCLK_Set()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET)//GPIO_SetBits(GPIOB,GPIO_Pin_10)

#defineOLED_SDIN_Clr()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_RESET)//GPIO_ResetBits(GPIOB,GPIO_Pin_11)//SDA
#defineOLED_SDIN_Set()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_11,GPIO_PIN_SET)//GPIO_SetBits(GPIOB,GPIO_Pin_11)


//I2CGPIO模擬
voidIIC_Start();
voidIIC_Stop();
voidIIC_WaitAck();
voidIIC_WriteByte(unsignedcharIIC_Byte);
voidIIC_WriteCommand(unsignedcharIIC_Command);
voidIIC_WriteData(unsignedcharIIC_Data);
voidOLED_WR_Byte(unsigneddat,unsignedcmd);


//功能函數(shù)
voidOLED_Init(void);
voidOLED_WR_Byte(unsigneddat,unsignedcmd);

voidOLED_FillPicture(unsignedcharfill_Data);
voidOLED_SetPos(unsignedcharx,unsignedchary);
voidOLED_DisplayOn(void);
voidOLED_DisplayOff(void);
voidOLED_Clear(void);
voidOLED_On(void);
voidOLED_ShowChar(u8x,u8y,u8chr,u8Char_Size);
u32oled_pow(u8m,u8n);
voidOLED_ShowNum(u8x,u8y,u32num,u8len,u8size2);
voidOLED_ShowString(u8x,u8y,u8*chr,u8Char_Size);

#endif

oled.c:

#include"oled.h"
#include"asc.h"http://字庫(可以自己制作)
#include"main.h"



/********************GPIO模擬I2C*******************/
//注意:這里沒有直接使用HAL庫中的模擬I2C
/**********************************************
//IICStart
**********************************************/
voidIIC_Start()
{

OLED_SCLK_Set();
OLED_SDIN_Set();
OLED_SDIN_Clr();
OLED_SCLK_Clr();
}

/**********************************************
//IICStop
**********************************************/
voidIIC_Stop()
{
OLED_SCLK_Set();
OLED_SDIN_Clr();
OLED_SDIN_Set();

}

voidIIC_WaitAck()
{
OLED_SCLK_Set();
OLED_SCLK_Clr();
}
/**********************************************
//IICWritebyte
**********************************************/

voidIIC_WriteByte(unsignedcharIIC_Byte)
{
unsignedchari;
unsignedcharm,da;
da=IIC_Byte;
OLED_SCLK_Clr();
for(i=0;i<8;i++)
?{
???m=da;
??//?OLED_SCLK_Clr();
??m=m&0x80;
??if(m==0x80)
??{OLED_SDIN_Set();}
??else?OLED_SDIN_Clr();
???da=da<<1;
??OLED_SCLK_Set();
??OLED_SCLK_Clr();
?}
?
?
}
/**********************************************
//?IIC?Write?Command
**********************************************/
void?IIC_WriteCommand(unsigned?char?IIC_Command)
{
???IIC_Start();
???IIC_WriteByte(0x78);????????????//Slave?address,SA0=0
?IIC_WaitAck();
???IIC_WriteByte(0x00);???//write?command
?IIC_WaitAck();
???IIC_WriteByte(IIC_Command);
?IIC_WaitAck();
???IIC_Stop();
}
/**********************************************
//?IIC?Write?Data
**********************************************/
void?IIC_WriteData(unsigned?char?IIC_Data)
{
???IIC_Start();
???IIC_WriteByte(0x78);???//D/C#=0;?R/W#=0
?IIC_WaitAck();
???IIC_WriteByte(0x40);???//write?data
?IIC_WaitAck();
???IIC_WriteByte(IIC_Data);
?IIC_WaitAck();
???IIC_Stop();
}
?
void?OLED_WR_Byte(unsigned?dat,unsigned?cmd)
{
?if(cmd)
?{
??IIC_WriteData(dat);
?}
?else
?{
??IIC_WriteCommand(dat);
?}
}
?
void?OLED_Init(void)
{
?HAL_Delay(100);??//這個延遲很重要
?
?OLED_WR_Byte(0xAE,OLED_CMD);//--display?off
?OLED_WR_Byte(0x00,OLED_CMD);//---set?low?column?address
?OLED_WR_Byte(0x10,OLED_CMD);//---set?high?column?address
?OLED_WR_Byte(0x40,OLED_CMD);//--set?start?line?address
?OLED_WR_Byte(0xB0,OLED_CMD);//--set?page?address
?OLED_WR_Byte(0x81,OLED_CMD);?//?contract?control
?OLED_WR_Byte(0xFF,OLED_CMD);//--128
?OLED_WR_Byte(0xA1,OLED_CMD);//set?segment?remap
?OLED_WR_Byte(0xA6,OLED_CMD);//--normal?/?reverse
?OLED_WR_Byte(0xA8,OLED_CMD);//--set?multiplex?ratio(1?to?64)
?OLED_WR_Byte(0x3F,OLED_CMD);//--1/32?duty
?OLED_WR_Byte(0xC8,OLED_CMD);//Com?scan?direction
?OLED_WR_Byte(0xD3,OLED_CMD);//-set?display?offset
?OLED_WR_Byte(0x00,OLED_CMD);//
?
?OLED_WR_Byte(0xD5,OLED_CMD);//set?osc?division
?OLED_WR_Byte(0x80,OLED_CMD);//
?
?OLED_WR_Byte(0xD8,OLED_CMD);//set?area?color?mode?off
?OLED_WR_Byte(0x05,OLED_CMD);//
?
?OLED_WR_Byte(0xD9,OLED_CMD);//Set?Pre-Charge?Period
?OLED_WR_Byte(0xF1,OLED_CMD);//
?
?OLED_WR_Byte(0xDA,OLED_CMD);//set?com?pin?configuartion
?OLED_WR_Byte(0x12,OLED_CMD);//
?
?OLED_WR_Byte(0xDB,OLED_CMD);//set?Vcomh
?OLED_WR_Byte(0x30,OLED_CMD);//
?
?OLED_WR_Byte(0x8D,OLED_CMD);//set?charge?pump?enable
?OLED_WR_Byte(0x14,OLED_CMD);//
?
?OLED_WR_Byte(0xAF,OLED_CMD);//--turn?on?oled?panel
?HAL_Delay(100);?
?OLED_FillPicture(0x0);
?
}
?
?
/********************************************
//?OLED_FillPicture
********************************************/
void?OLED_FillPicture(unsigned?char?fill_Data)
{
?unsigned?char?m,n;
?for(m=0;m<8;m++)
?{
??OLED_WR_Byte(0xb0+m,0);??//page0-page1
??OLED_WR_Byte(0x00,0);??//low?column?start?address
??OLED_WR_Byte(0x10,0);??//high?column?start?address
??for(n=0;n<128;n++)
???{
????OLED_WR_Byte(fill_Data,1);
???}
?}
}
?
//坐標設置
void?OLED_SetPos(unsigned?char?x,?unsigned?char?y)
{??OLED_WR_Byte(0xb0+y,OLED_CMD);
?OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f),OLED_CMD);
}
//開啟OLED顯示
voidOLED_DisplayOn(void)
{
OLED_WR_Byte(0X8D,OLED_CMD);//SETDCDC命令
OLED_WR_Byte(0X14,OLED_CMD);//DCDCON
OLED_WR_Byte(0XAF,OLED_CMD);//DISPLAYON
}
//關閉OLED顯示
voidOLED_DisplayOff(void)
{
OLED_WR_Byte(0X8D,OLED_CMD);//SETDCDC命令
OLED_WR_Byte(0X10,OLED_CMD);//DCDCOFF
OLED_WR_Byte(0XAE,OLED_CMD);//DISPLAYOFF
}
//清屏函數(shù),清完屏,整個屏幕是黑色的!和沒點亮一樣!!!
voidOLED_Clear(void)
{
u8i,n;
for(i=0;i<8;i++)
?{
??OLED_WR_Byte?(0xb0+i,OLED_CMD);????//設置頁地址(0~7)
??OLED_WR_Byte?(0x00,OLED_CMD);??????//設置顯示位置—列低地址
??OLED_WR_Byte?(0x10,OLED_CMD);??????//設置顯示位置—列高地址
??for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
?}?//更新顯示
}
void?OLED_On(void)
{
?u8?i,n;
?for(i=0;i<8;i++)
?{
??OLED_WR_Byte?(0xb0+i,OLED_CMD);????//設置頁地址(0~7)
??OLED_WR_Byte?(0x00,OLED_CMD);??????//設置顯示位置—列低地址
??OLED_WR_Byte?(0x10,OLED_CMD);??????//設置顯示位置—列高地址
??for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA);
?}?//更新顯示
}
//在指定位置顯示一個字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白顯示;1,正常顯示
//size:選擇字體?16/12
void?OLED_ShowChar(u8?x,u8?y,u8?chr,u8?Char_Size)
{
?unsigned?char?c=0,i=0;
??c=chr-'?';//得到偏移后的值
??if(x>Max_Column-1){x=0;y=y+2;}
if(Char_Size==16)
{
OLED_SetPos(x,y);
for(i=0;i<8;i++)
???OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
???OLED_SetPos(x,y+1);
???for(i=0;i<8;i++)
???OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
???}
???else?{
????OLED_SetPos(x,y);
????for(i=0;i<6;i++)
????OLED_WR_Byte(F6x8[c][i],OLED_DATA);
?
???}
}
?
//m^n函數(shù)
u32?oled_pow(u8?m,u8?n)
{
?u32?result=1;
?while(n--)result*=m;
?return?result;
}
?
//顯示2個數(shù)字
//x,y?:起點坐標
//len?:數(shù)字的位數(shù)
//size:字體大小
//mode:模式?0,填充模式;1,疊加模式
//num:數(shù)值(0~4294967295);
void?OLED_ShowNum(u8?x,u8?y,u32?num,u8?len,u8?size2)
{
?u8?t,temp;
?u8?enshow=0;
?for(t=0;t120){x=0;y+=2;}
j++;
}
}

6.2 谷歌小恐龍游戲圖形繪制代碼

該部分為整個項目代碼的核心部分之一,任何一個游戲都是需要去繪制和構建游戲的圖形以及模型的。好的游戲往往都具有很好的游戲模型和精美UI,很多3A大作都具備這樣的特性。

dinogame.h:

#ifndef__DINOGAME_H
#define__DINOGAME_H

voidOLED_DrawBMP(unsignedcharx0,unsignedchary0,unsignedcharx1,unsignedchary1,unsignedcharBMP[]);
voidOLED_DrawBMPFast(constunsignedcharBMP[]);
voidoled_drawbmp_block_clear(intbx,intby,intclear_size);
voidOLED_DrawGround();
voidOLED_DrawCloud();
voidOLED_DrawDino();
voidOLED_DrawCactus();
intOLED_DrawCactusRandom(unsignedcharver,unsignedcharreset);
intOLED_DrawDinoJump(charreset);
voidOLED_DrawRestart();
voidOLED_DrawCover();

#endif

dinogame.c代碼:

#include"oled.h"
#include"oledfont.h"
#include"stdlib.h"

/***********功能描述:顯示顯示BMP圖片128×64起始點坐標(x,y),x的范圍0~127,y為頁的范圍0~7*****************/
voidOLED_DrawBMP(unsignedcharx0,unsignedchary0,unsignedcharx1,unsignedchary1,unsignedcharBMP[])
{
unsignedintj=0;
unsignedcharx,y;

if(y1%8==0)y=y1/8;
elsey=y1/8+1;
for(y=y0;y128)break;
IIC_WriteByte(0x0);
IIC_WaitAck();
}
IIC_Stop();
}

voidOLED_DrawGround()
{
staticunsignedintpos=0;
unsignedcharspeed=5;
unsignedintground_length=sizeof(GROUND);
unsignedcharx;

OLED_SetPos(0,7);
IIC_Start();
IIC_WriteByte(0x78);
IIC_WaitAck();
IIC_WriteByte(0x40);
IIC_WaitAck();
for(x=0;xground_length)pos=0;
}


//繪制云朵
voidOLED_DrawCloud()
{
staticintpos=128;
staticcharheight=0;
charspeed=3;
unsignedinti=0;
intx;
intstart_x=0;
intlength=sizeof(CLOUD);
unsignedcharbyte;

//if(pos+length<=?-speed)?pos?=?128;
?
?if?(pos?+?length?<=?-speed)
?{
??pos?=?128;
??height?=?rand()%3;
?}
?if(pos?127)break;
if(x127)break;
j=y*length+x;
byte=CACTUS_2[j];
IIC_WriteByte(byte);
IIC_WaitAck();
}
IIC_Stop();
}
oled_drawbmp_block_clear(pos+length,6,speed);//清除殘影
pos=pos-speed;
}


//繪制隨機出現(xiàn)的仙人掌障礙物
intOLED_DrawCactusRandom(unsignedcharver,unsignedcharreset)
{
charspeed=5;
staticintpos=128;
intstart_x=0;
intlength=0;

unsignedinti=0,j=0;
unsignedcharx,y;
unsignedcharbyte;
if(reset==1)
{
pos=128;
oled_drawbmp_block_clear(0,6,speed);
return128;
}
if(ver==0)length=8;//sizeof(CACTUS_1)/2;
elseif(ver==1)length=16;//sizeof(CACTUS_2)/2;
elseif(ver==2||ver==3)length=24;

for(y=0;y<2;?y++)
?{
??if(pos?127)break;

j=y*length+x;
if(ver==0)byte=CACTUS_1[j];
elseif(ver==1)byte=CACTUS_2[j];
elseif(ver==2)byte=CACTUS_3[j];
elsebyte=CACTUS_4[j];

IIC_WriteByte(byte);
IIC_WaitAck();
}
IIC_Stop();
}

oled_drawbmp_block_clear(pos+length,6,speed);

pos=pos-speed;
returnpos+speed;
}




//繪制跳躍小恐龍
intOLED_DrawDinoJump(charreset)
{
charspeed_arr[]={1,1,3,3,4,4,5,6,7};
staticcharspeed_idx=sizeof(speed_arr)-1;
staticintheight=0;
staticchardir=0;
//charspeed=4;

unsignedintj=0;
unsignedcharx,y;
charoffset=0;
unsignedcharbyte;
if(reset==1)
{
height=0;
dir=0;
speed_idx=sizeof(speed_arr)-1;
return0;
}
if(dir==0)
{
height+=speed_arr[speed_idx];
speed_idx--;
if(speed_idx<0)?speed_idx?=?0;
?}
?if?(dir==1)
?{
??height?-=?speed_arr[speed_idx];
??speed_idx?++;
??if?(speed_idx>sizeof(speed_arr)-1)speed_idx=sizeof(speed_arr)-1;
}
if(height>=31)
{
dir=1;
height=31;
}
if(height<=?0)
?{
??dir?=?0;
??height?=?0;
?}
?if(height?<=?7)?offset?=?0;
?else?if(height?<=?15)?offset?=?1;
?else?if(height?<=?23)?offset?=?2;
?else?if(height?<=?31)?offset?=?3;
?else?offset?=?4;
?
?for(y=0;?y<3;?y++)?//?4
?{
??OLED_SetPos(16,?5-?offset?+?y);
?
??IIC_Start();
??IIC_WriteByte(0x78);
??IIC_WaitAck();
??IIC_WriteByte(0x40);
??IIC_WaitAck();
??for?(x?=?0;?x?

6.3 谷歌小恐龍的運行控制代碼

control.h:

#ifndef__CONTROL_H
#define__CONTROL_H

intget_key();
voidGame_control();

#endif****

control.c:

#include"control.h"
#include"oled.h"
#include"dinogame.h"
#include"stdlib.h"

unsignedcharkey_num=0;
unsignedcharcactus_category=0;
unsignedcharcactus_length=8;
unsignedintscore=0;
unsignedinthighest_score=0;
intheight=0;
intcactus_pos=128;
unsignedcharcur_speed=30;
charfailed=0;
charreset=0;


intget_key()
{
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0)
{
HAL_Delay(10);//延遲
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0)
{
return2;
}
}

if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==0)
{
HAL_Delay(10);//延遲
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==0)
{
return1;
}
}

if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==1)
{
HAL_Delay(10);//延遲
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==1)
{
return3;
}
}

return0;
}

voidGame_control()
{

while(1)
{

if(get_key()==3)//wk_up按鍵按下強制退出一次循環(huán)
{
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);
elseOLED_DrawDino();

cactus_pos=OLED_DrawCactusRandom(cactus_category,reset);
if(cactus_category==0)cactus_length=8;
elseif(cactus_category==1)cactus_length=16;
elsecactus_length=24;

if(cactus_pos+cactus_length=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);
//HAL_Delay(500);
key_num=0;

}

}

6.4 多級菜單核心代碼:

menu.h:

#ifndef__MENU_H
#define__MENU_H

#include"main.h"
#defineu8unsignedchar

//按鍵定義
#defineKEY0HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)//低電平有效KEY0
#defineKEY1HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)//低電平有效
#defineWK_UPHAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)//高電平有效


typedefstruct
{
u8current;//當前狀態(tài)索引號
u8next;//向下一個
u8enter;//確定
u8back;//退出
void(*current_operation)(void);//當前狀態(tài)應該執(zhí)行的操作
}Menu_table;

//界面UI
voidhome();
voidTemperature();
voidPalygame();
voidSetting();
voidInfo();


voidMenu_key_set(void);
u8KEY_Scan(u8mode);

voidTestTemperature();
voidConrtolGame();
voidSet();
voidInformation();

voidLED();
voidRTC_display();

#endif

menu.c:

#include"menu.h"
#include"oled.h"
#include"gpio.h"
#include"dinogame.h"
#include"control.h"
#include"DHT11.h"
#include"rtc.h"

RTC_DateTypeDefGetData;//獲取日期結構體

RTC_TimeTypeDefGetTime;//獲取時間結構體


//UI界面
//主頁
/****************************************************/
//UI庫

/****************************************************/

void(*current_operation_index)();

Menu_tabletable[30]=
{
{0,0,1,0,(*home)},//一級界面(主頁面)索引,向下一個,確定,退出

{1,2,5,0,(*Temperature)},//二級界面溫濕度
{2,3,6,0,(*Palygame)},//二級界面游戲
{3,4,7,0,(*Setting)},//二級界面設置
{4,1,8,0,(*Info)},//二級界面信息

{5,5,5,1,(*TestTemperature)},//三級界面:DHT11測量溫濕度
{6,6,6,2,(*ConrtolGame)},//三級界面:谷歌小恐龍Dinogame
{7,7,9,3,(*Set)},//三級界面:設置普通外設狀態(tài)LED
{8,8,8,4,(*Information)},//三級界面:作者和相關項目信息

{9,9,7,3,(*LED)},//LED控制
};

uint8_tfunc_index=0;//主程序此時所在程序的索引值

voidMenu_key_set(void)
{
if((KEY_Scan(1)==1)&&(func_index!=6))
{
func_index=table[func_index].next;//按鍵next按下后的索引號
OLED_Clear();
}

if((KEY_Scan(1)==2)&&(func_index!=6))
{
func_index=table[func_index].enter;//按鍵enter按下后的索引號
OLED_Clear();
}

if(KEY_Scan(1)==3)
{
func_index=table[func_index].back;//按鍵back按下后的索引號
OLED_Clear();
}

current_operation_index=table[func_index].current_operation;//執(zhí)行當前索引號所對應的功能函數(shù)
(*current_operation_index)();//執(zhí)行當前操作函數(shù)
}


voidhome()
{
RTC_display();
OLED_DrawBMP(0,0,20,3,signal_BMP);
OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
OLED_DrawBMP(112,0,128,2,gImage_engery);
OLED_DrawBMP(4,6,20,8,gImage_yes);
OLED_DrawBMP(12,4,28,6,gImage_left);
OLED_DrawBMP(40,2,88,8,gImage_home);
OLED_DrawBMP(99,4,115,6,gImage_right);
OLED_DrawBMP(107,6,123,8,gImage_back);
}

voidTemperature()
{
RTC_display();
OLED_DrawBMP(0,0,20,3,signal_BMP);
OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
OLED_DrawBMP(112,0,128,2,gImage_engery);
OLED_DrawBMP(4,6,20,8,gImage_yes);
OLED_DrawBMP(12,4,28,6,gImage_left);
OLED_DrawBMP(40,2,88,8,gImage_temp);
OLED_DrawBMP(99,4,115,6,gImage_right);
OLED_DrawBMP(107,6,123,8,gImage_back);
}

voidPalygame()
{
RTC_display();
OLED_DrawBMP(0,0,20,3,signal_BMP);
OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
OLED_DrawBMP(112,0,128,2,gImage_engery);
OLED_DrawBMP(4,6,20,8,gImage_yes);
OLED_DrawBMP(12,4,28,6,gImage_left);
OLED_DrawBMP(40,2,88,8,gImage_playgame);
OLED_DrawBMP(99,4,115,6,gImage_right);
OLED_DrawBMP(107,6,123,8,gImage_back);
}

voidSetting()
{
RTC_display();
OLED_DrawBMP(0,0,20,3,signal_BMP);
OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
OLED_DrawBMP(112,0,128,2,gImage_engery);
OLED_DrawBMP(4,6,20,8,gImage_yes);
OLED_DrawBMP(12,4,28,6,gImage_left);
OLED_DrawBMP(40,2,88,8,gImage_setting);
OLED_DrawBMP(99,4,115,6,gImage_right);
OLED_DrawBMP(107,6,123,8,gImage_back);
}

voidInfo()
{
RTC_display();
OLED_DrawBMP(0,0,20,3,signal_BMP);
OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
OLED_DrawBMP(112,0,128,2,gImage_engery);
OLED_DrawBMP(4,6,20,8,gImage_yes);
OLED_DrawBMP(12,4,28,6,gImage_left);
OLED_DrawBMP(40,2,88,8,gImage_info);
OLED_DrawBMP(99,4,115,6,gImage_right);
OLED_DrawBMP(107,6,123,8,gImage_back);
}


//按鍵函數(shù),不支持連按
u8KEY_Scan(u8mode)
{
staticu8key_up=1;
if(mode)key_up=1;
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
HAL_Delay(100);//消抖
key_up=0;
if(KEY0==0)return1;
elseif(KEY1==0)return2;
elseif(WK_UP==1)return3;
}elseif(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return0;
}

voidTestTemperature()
{
DHT11();
}

voidConrtolGame()
{
Game_control();
}

voidSet()
{
OLED_ShowString(0,0,"Peripherals:Lights",16);
OLED_ShowString(0,2,"Status:Closed",16);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
}

voidInformation()
{
OLED_ShowString(0,0,"Author:Sneak",16);
OLED_ShowString(0,2,"Date:2022/8/23",16);
OLED_ShowString(0,4,"Lab:Multi-levelmenu",16);
}

voidLED()
{
OLED_ShowString(0,0,"Peripherals:Lights",16);
OLED_ShowString(0,2,"Status:Open",16);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
}



voidRTC_display()//RTC????
{
/*GettheRTCcurrentTime*/
HAL_RTC_GetTime(&hrtc,&GetTime,RTC_FORMAT_BIN);
/*GettheRTCcurrentDate*/
HAL_RTC_GetDate(&hrtc,&GetData,RTC_FORMAT_BIN);

/*DisplaydateFormat:yy/mm/dd*/

/*DisplaytimeFormat:hhss*/
OLED_ShowNum(40,0,GetTime.Hours,2,16);//hour
OLED_ShowString(57,0,":",16);
OLED_ShowNum(66,0,GetTime.Minutes,2,16);//min
OLED_ShowString(83,0,":",16);
OLED_ShowNum(93,0,GetTime.Seconds,2,16);//seconds
}

七、總結與代碼開源

總結:本項目目前還處于最初代版本,十分簡易,后期筆者將抽時間去精進優(yōu)化該多級菜單項目。其中,UI界面中的電池與信號目前都還處于貼圖狀態(tài),后期筆者會加上庫侖計測量電池電量等。文章中指出了需要注意的地方與可以改進的點,感興趣的朋友可以彼此交流交流。

代碼下載地址:https://download.csdn.net/download/black_sneak/86469358

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • OLED
    +關注

    關注

    119

    文章

    6171

    瀏覽量

    223679
  • STM32
    +關注

    關注

    2264

    文章

    10854

    瀏覽量

    354291
  • 菜單
    +關注

    關注

    0

    文章

    33

    瀏覽量

    13470
  • 開源
    +關注

    關注

    3

    文章

    3215

    瀏覽量

    42327
  • RTOS
    +關注

    關注

    21

    文章

    809

    瀏覽量

    119361

原文標題:【開源小項目】基于STM32的OLED舵機菜單顯示

文章出處:【微信號:嵌入式悅翔園,微信公眾號:嵌入式悅翔園】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    STM32簡易多級菜單(數(shù)組查表法)顯示方法

    本篇介紹了一種簡易的多級菜單顯示方法,本質是通過數(shù)組查表,實現(xiàn)各級菜單的各個頁面(狀態(tài))的切換(跳轉),并在STM32上編程實現(xiàn),通過OLED
    的頭像 發(fā)表于 06-07 09:11 ?8469次閱讀
    <b class='flag-5'>STM32</b>簡易多級<b class='flag-5'>菜單</b>(數(shù)組查表法)<b class='flag-5'>顯示</b>方法

    ST7920_12864液晶圖文菜單顯示.C

    ST7920_12864液晶圖文菜單顯示.C
    發(fā)表于 08-17 21:40

    急求一個msp430f149三級菜單顯示程序,(LCD12864顯示

    急求一個msp430f149三級菜單顯示程序,(LCD12864顯示
    發(fā)表于 07-07 21:24

    單片機小項目開源分享

    轉眼間已經工作一年,目前從事linux/android驅動工作,將大學期間單片機的小項目開源出來,供大家交流學習,比較簡單。源碼在git clone http://www.github.com
    發(fā)表于 09-17 11:33

    STM32CubeMX菜單亂碼

    win10 + SetupSTM32CubeMX-4.24.0 安裝成功,但在主界面,菜單顯示亂碼,如下,何故?
    發(fā)表于 12-26 08:51

    多級菜單顯示

    前言只要有顯示屏的地方,就要用到多級菜單顯示。在很多初學者眼里,多級菜單顯示是很難的,今天我做完了這個
    發(fā)表于 08-11 07:19

    如何搭建基于STM32驅動OLED顯示三級菜單界面框架?

    什么是主界面?如何控制界面之間的切換?如何搭建基于STM32驅動OLED顯示三級菜單界面框架?
    發(fā)表于 12-17 06:45

    【野火魯班貓2開發(fā)板體驗】Debian + 物聯(lián)網 + 綜合小項目

    野火魯班貓2單板電腦=物聯(lián)網+綜合小項目野火開發(fā)板,魯班貓2單板電腦,這次分享一個物聯(lián)網+綜合小項目使用到外設有火焰?zhèn)鞲衅?,蜂鳴器,語音模塊,led燈,0.96存iic接口的oled屏幕,超聲波
    發(fā)表于 03-08 20:12

    【魯班貓創(chuàng)意氛圍賽】魯班貓2單板電腦=物聯(lián)網+綜合小項目

    野火魯班貓2單板電腦=物聯(lián)網+綜合小項目 項目概述 這次分享一個物聯(lián)網+綜合小項目 使用的開發(fā)板 野火家的開發(fā)板,魯班貓2單板電腦開發(fā)板 項目當中使用到的外設模塊 火焰?zhèn)鞲衅?,蜂鳴器,
    發(fā)表于 05-18 20:06

    多級操作菜單顯示系統(tǒng)設計

    本文旨在提供一個輕量級的單片機多級菜單實現(xiàn)方法,以較少的系統(tǒng)資源消耗和簡單方便的方法完成菜單設計??紤]到菜單程序需要具備3個基本要素:一是每個菜單窗口要
    發(fā)表于 03-26 15:07 ?7345次閱讀
    多級操作<b class='flag-5'>菜單顯示</b>系統(tǒng)設計

    STM32二級菜單通過按鍵切換自定義任務OLED顯示的程序和工程文件

    本文檔的主要內容詳細介紹的是STM32二級菜單通過按鍵切換自定義任務OLED顯示的程序和工程文件。
    發(fā)表于 08-31 08:00 ?48次下載
    <b class='flag-5'>STM32</b>二級<b class='flag-5'>菜單</b>通過按鍵切換自定義任務<b class='flag-5'>OLED</b><b class='flag-5'>顯示</b>的程序和工程文件

    PADS在WIN10系統(tǒng)中菜單顯示不全怎么解決?

    到這里PADS菜單顯示的問題解決了,但是因為我們修改了系統(tǒng)默認字體,會影響到其它軟件,比如筆者使用的webstorm就會顯示亂碼。
    的頭像 發(fā)表于 03-14 11:57 ?8783次閱讀

    STM32學習——入門小項目

    STM32學習——入門小項目
    發(fā)表于 12-07 17:21 ?72次下載
    <b class='flag-5'>STM32</b>學習——入門<b class='flag-5'>小項目</b>

    帶有選擇選項的Arduino OLED顯示菜單

    電子發(fā)燒友網站提供《帶有選擇選項的Arduino OLED顯示菜單.zip》資料免費下載
    發(fā)表于 06-28 15:53 ?3次下載
    帶有選擇選項的Arduino <b class='flag-5'>OLED</b><b class='flag-5'>顯示</b><b class='flag-5'>菜單</b>

    STM32 OLED菜單操作

    stm32 ?oled菜單操作
    發(fā)表于 10-09 11:01 ?2次下載