STM32+0.96OLED的多級菜單設計


硬件

硬件選型

  • STM32F103C8T6最小核心板
  • 0.96寸四腳OLED屏幕IIC接口
  • 普通按鍵5個

硬件連線

  • SCL ---- PA1

  • SDA ---- PA2

  • KEY_UP ---- PA4

  • KEY_DOWN ---- PA5

  • KEY_LEFT ---- PA3

  • KEY_RIGHT ---- PA6

  • KEY_OK ---- PA7

代碼開源鏈接

百度網盤

鏈接:https://pan.baidu.com/s/1W4dIgTYgQv7Pp4iX-QnwTg
提取碼:k7p4

Gitee

屏幕使用-GUI設計: 一些單片機控制屏幕的項目,和一些GUI界面設計 stm32驅動的oled屏等等 (gitee.com)

軟件開發

基礎功能-自己完成

Systick延時

我使用的是正點原子的代碼,進行了簡單的修改,但大體一樣,可用正點原子代碼。具體修改看我代碼,不修改也可用。

5ms定時器

使用的定時器3做的一個5ms的定時器中斷。定時器中斷中放置按鍵掃描函數。

按鍵掃描

也是參考的正點原子的按鍵掃描函數,但是沒有使用延時函數,使用的計數延時來達到按鍵消抖的目的。5ms掃描一次,大於2就延時10ms。該函數放在定時器5ms的中斷函數中。這里按鍵掃描給一個按鍵按下的標志位,例如isKeyUp,按鍵按下就將它置1。

/**
 * @brief 按鍵掃描函數
 * 
 * @param mode 模式為1就是連續掃描,為0就是單次
 */
void KeyScan(u8 mode)
{
    static int keyCount = 0;
    static int keyState = 0;
    if(mode == 1) keyState=0;
    if (keyState == 0 && (KEY_UP == 0||KEY_DOWN == 0||KEY_LEFT == 0||KEY_RIGHT == 0||KEY_OK == 0))
    {    
        keyCount++;
        if(keyCount>2)
        {
            keyState = 1;
            keyCount=0;
            if(KEY_UP == 0) KeyUp();
            else if(KEY_DOWN == 0) KeyDown();
            else if (KEY_LEFT == 0) KeyLeft();
            else if (KEY_RIGHT == 0) KeyRight();
            else if (KEY_OK == 0) KeyOk();
        }
    }else if (KEY_UP == 1 && KEY_DOWN == 1 && KEY_LEFT == 1 && KEY_RIGHT == 1 && KEY_OK == 1)
    {
        keyState = 0;
    }
}

void KeyUp()
{
	if(isKeyUp == 0)
		isKeyUp=1;
	LED=!LED;
}

oled的簡單使用

oled使用的是中景園電子的代碼,可以在項目代碼中查看。

多級菜單設計

定義菜單項結構體

//菜單頁參數結構體
struct MenuProperty_t
{
	u8 MenuLen;//當前菜單頁菜單項總個數
	u8 scrollBarLen;//滾動條長度,由於都是用的16SIZE的字符,所以一個菜單頁最多四個菜單項,五個菜單項滾動條就為1
};

//菜單項結構體
struct Menu_t{
	struct MenuProperty_t *MenuProperty;//當前菜單項所在菜單頁的參數
	u8 displayString[15];//當前菜單項的字符
	void (*func1) (void);//當前菜單項的功能函數
	void (*func2) (void);//當前菜單項的功能函數
	struct Menu_t *fatherMenu;//當前菜單項的父級菜單項
	struct Menu_t *childrenMenu;//當前菜單項的子級菜單項	
};

定義一個菜單頁

主界面菜單,算一級菜單,主界面一般可以拿來畫一些好玩的UI設計,我這個項目做的是一個時鍾設計。這個菜單頁就只有一個菜單項,滾動條為0。由於初始化不能先填入未初始化的數據,所以他的子菜單項初始化先設定為NULL。

//主UI
struct MenuProperty_t MainUIProperty={1,0};
struct Menu_t MainUI=
{&MainUIProperty,"MainUI       " ,NULL,NULL,NULL};

主菜單,算二級菜單,拿來做我想要顯示的數據項分類,父菜單就是MainUI,子菜單項初始化先設定為NULL。注意字符串盡量寫15個字符,用空格也要占位,使得后面數據好刷新。這個菜單頁就有四個菜單項,滾動條為0。

//主菜單
struct MenuProperty_t menuMainProperty={4,0};
struct Menu_t menuMain[4]=
{
	{&menuMainProperty,"last menu     ", NULL,NULL, &MainUI,NULL},
	{&menuMainProperty,"Animal        ", NULL,NULL, &MainUI,NULL},
	{&menuMainProperty,"Pid           ", NULL,NULL, &MainUI,NULL},
	{&menuMainProperty,"Time set      ", NULL,TimeSetInit, &MainUI,NULL}
};

animal的子菜單,算3級菜單,這個就是真的想要顯示的animal菜單項的數據。父菜單就是menuMain,子菜單項初始化先設定為NULL。這個菜單頁就有六個菜單項,滾動條長度為2,因為一面最多顯示4個,滾動一下往下移一個。

注意:要是你定義的是單個項,取地址就要加&,要是定義的數組,就可以用數組名取該數組首地址。

//animal的子菜單
struct MenuProperty_t setMenu1Property={6,2};
struct Menu_t setMenu1[6]=
{
	{&setMenu1Property,"last menu     ",NULL,NULL,menuMain,NULL},
	{&setMenu1Property,"bull          ",NULL,NULL,menuMain,NULL},
	{&setMenu1Property,"bird          ",NULL,NULL,menuMain,NULL},
	{&setMenu1Property,"dog           ",NULL,NULL,menuMain,NULL},
	{&setMenu1Property,"bow           ",NULL,NULL,menuMain,NULL},
	{&setMenu1Property,"fish          ",NULL,NULL,menuMain,NULL}
};

OLED頁面刷新函數

刷新頁面信息,要是在主頁面就清空一下在畫圖,要是沒有在主頁面,使用覆蓋來達到刷新的效果。

void DisplayRefreash(struct Menu_t *nowMenu,u8 selectItem,u8 scrollBar)
{
	int i = 0;
	static u8 lastSelectItem=0;//記錄上次索引
	if(nowMenu==&MainUI)//當回到主菜單時,由於沒有全占屏,所以全部清屏,再畫
	{
		OLED_Clear();
		MainUiSet();
	}else 
	{	
		OLED_ShowChar(0,lastSelectItem*16, ' ',16,1);//清除上次索引箭頭
		OLED_ShowChar(0,selectItem*16,     '>',16,1);//畫出這次索引箭頭
		for(i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->scrollBarLen);i++)
		{
			OLED_ShowString(8,i*16,nowMenu[i+scrollBar].displayString,16,1);
		}
	}
	OLED_Refresh();
	lastSelectItem = selectItem;
}

OLED數據刷新函數

當每個頁面的數據要刷新時,就只需要把上一次的數據覆蓋就行了,所以每次就要寫滿一行的字符,或者你每行的字符長度相同也可以達到相同的目的。

void DisplayRefreashData(struct Menu_t *nowMenu,u8 selectItem,u8 scrollBar)
{
	int i = 0;
	for(i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->scrollBarLen);i++)
	{
		OLED_ShowString(8,i*16,nowMenu[i+scrollBar].displayString,16,1);
	}
	OLED_Refresh();
}

刷新數據前,數據的更改可以通過Sprintf函數來重新定義每個菜單項的顯示字符串

void GuiDataDisplayRefresh()
{
	if(menuPoint == setMenu1)
	{
		sprintf((char*)setMenu1[1].displayString,"bull  %3d     ",count1);
		sprintf((char*)setMenu1[2].displayString,"bird  %3d     ",count2);
		sprintf((char*)setMenu1[3].displayString,"dog   %3d     ",count3);
		sprintf((char*)setMenu1[4].displayString,"bow   %3d     ",count4);
		sprintf((char*)setMenu1[5].displayString,"fish  %3d     ",count5);
		DisplayRefreashData(menuPoint,selectItem,scrollBar);
	}
	else if(menuPoint==&MainUI)
	{
		MainUiSet();
		OLED_Refresh();
	}
}

菜單初始化

主要拿來初始化一些菜單項的子菜單,以及當前菜單的指針指向。

全局變量

menuPoint    當前菜單指向地址
selectItem   當前索引 0-3
scrollBar    當前滾動條所在位置,最上處為0
void GuiInit()
{
	MainUI.childrenMenu = menuMain;
	menuMain[1].childrenMenu = setMenu1;
	menuMain[2].childrenMenu = setMenu2;
	menuMain[3].childrenMenu = setMenu3;
	menuPoint = &MainUI;
	DisplayRefreash(menuPoint,selectItem,scrollBar);
}

按鍵控制函數

上下按鍵主要拿來切換現在的索引和滾動條

左右鍵主要拿來實現功能函數

void GuiControl()
{
	if(isKeyUp==1)//上鍵按下
	{
		isKeyUp=0;//標志位清零
		selectItem--;//當前菜單在當前菜單頁的索引--
		if(selectItem<0&&scrollBar!=0)//小於0,但是滾動條不在0,就減滾動條
		{
			selectItem = 0;
			scrollBar--;
		}else if(selectItem<0&&scrollBar==0)//小於0,滾動條也在0,就將索引移到最后,滾動條到最大
		{
			selectItem = menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen;
			scrollBar  = menuPoint->MenuProperty->scrollBarLen;
		}
		DisplayRefreash(menuPoint,selectItem,scrollBar);//刷新顯示
	}else if(isKeyDown==1)//和上鍵差不多
	{
		isKeyDown=0;
		selectItem++;
		//假如索引大於最大值,但是滾動條不在最大值,保持索引最大值,滾動條++
		if(selectItem>(menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen)&&scrollBar!=menuPoint->MenuProperty->scrollBarLen)
		{
			selectItem = menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen;
			scrollBar++;
		}
		//假如索引大於最大值,滾動條在最大值,移動到第一個位置
		else if(selectItem>(menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen)&&scrollBar==menuPoint->MenuProperty->scrollBarLen)
		{
			selectItem=0;
			scrollBar =0;
		}
		DisplayRefreash(menuPoint,selectItem,scrollBar);
	}else if(isKeyLeft==1)
	{
		//假如當前菜單的func1不為空,執行相關函數
		if(menuPoint[selectItem+scrollBar].func1!=NULL)
		{
			menuPoint[selectItem+scrollBar].func1();
		}
		isKeyLeft=0;
		DisplayRefreash(menuPoint,selectItem,scrollBar);
	}else if(isKeyRight==1)
	{
		if(selectItem==0 && scrollBar==0 && menuPoint[selectItem].fatherMenu!=NULL)//假如索引為零而且父菜單不為空,指向父指針
		{
			menuPoint = menuPoint[selectItem].fatherMenu;
		}
		else if(menuPoint[selectItem+scrollBar].childrenMenu!=NULL)//假如該索引子菜單頁不為空,指向子菜單
		{
			if(menuPoint[selectItem+scrollBar].func2!=NULL)//假如當前菜單的func2不為空,執行相關函數
			{
				menuPoint[selectItem+scrollBar].func2();
			}
			menuPoint = menuPoint[selectItem+scrollBar].childrenMenu;
			selectItem = 0;
		}
		else if(menuPoint[selectItem+scrollBar].func2!=NULL)//假如當前菜單的func2不為空,執行相關函數
		{
			menuPoint[selectItem+scrollBar].func2();
		}
		isKeyRight=0;
		DisplayRefreash(menuPoint,selectItem,scrollBar);
	}else if(isKeyOk==1)
	{
		isKeyOk=0;
		DisplayRefreash(menuPoint,selectItem,scrollBar);
	}
	GuiDataDisplayRefresh();
}

項目參考

STM32F1多級菜單代碼講解_嗶哩嗶哩_bilibili


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM