硬件
硬件選型
- 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();
}