基于STM32的OLED舵机菜单显示

发布时间:2023-09-26  

一、前言

本文的OLED多级菜单UI为一个综合性的STM32小项目,使用多传感器与OLED显示屏实现智能终端的效果。项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换,搭配DHT11,RTC,LED,KEY等器件实现高度智能化一体化操作。


后期自己打板设计结构,可以衍生为智能手表等小玩意。目前,项目属于裸机状态(CPU占用率100%),后期可能会加上RTOS系统。(本项目源码在本文末尾进行开源!)

二、硬件实物图

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

四、多级菜单


随着工业化和自动化的发展,如今基本上所有项目都离不开显示终端。而多级菜单更是终端显示项目中必不可少的组成因素,其实TFT-LCD屏幕上可以借鉴移植很多优秀的开源多级菜单(GUI,比如:LVGL),而0.96寸的OLED屏幕上通常需要自己去适配和编程多级菜单。


网上的普遍采用的多级菜单的方案是基于索引或者结构树,其中,索引法居多。索引法的优点:可阅读性好,拓展性也不错,查找的性能差不多是最优,就是有点占用内存空间。


4.1 索引法多级菜单实现


网上关于索引法实现多级菜单功能有很多基础教程,笔者就按照本项目中的具体实现代码过程给大家讲解一下索引法实现多级菜单。特别说明:本项目直接使用了正点原子的精英板作为核心板,所以读者朋友复现代码还是很简单的。


首先,基于索引法实现多级菜单的首要条件是先确定项目中将使用到几个功能按键(比如:向前,向后,确定,退出等等)本项目中,笔者使用到了3个按键:下一个(next),确定(enter),退出(back)。所以,接下首先定义一个结构体,结构体中一共有5个变量(3+2),分别为:当前索引序号(current),向下一个(next),确定(enter),退出(back),当前执行函数(void)。其中,标红的为需要设计的按键(笔者这里有3个),标绿的则为固定的索引号与该索引下需要执行的函数。


typedefstruct

{

u8current;//当前状态索引号

u8next;//向下一个

u8enter;//确定

6u8back;//退出

void(*current_operation)(void);//当前状态应该执行的操作

}Menu_table;

接下来就是定义一个数组去决定整个项目菜单的逻辑顺序(利用索引号)


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)},//三级界面:设置普通外设状态LED

{8,8,8,4,(*Information)},//三级界面:作者和相关项目信息


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

};

这里解释一下这个数组中各元素的意义,由于我们在前面先定义了Menu_table结构体,结构体成员变量分别与数组中元素对应。比如:{0,0,1,0,(*home)},代表了索引号为0,按向下键(next)转入索引号为0,按确定键(enter)转入索引号为1,按退出键(back)转入索引号为0,索引号为0时执行home函数。


在举一个例子帮助大家理解一下,比如,我们当前程序处在索引号为2(游戏界面),就会执行Playgame函数。此时,如果按下next按键,程序当前索引号就会变为3,并且执行索引号为3时候的Setting函数。如果按下enter按键,程序当前索引号就会变为6,并且执行索引号为6时候的ControlGame函数。如果按下back按键,程序当前索引号就会变为0,并且执行索引号为0时候的home函数。


再接下就是按键处理函数:


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;//执行当前索引号所对应的功能函数

(*current_operation_index)();//执行当前操作函数

}



//按键函数

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)由于是目前本项目是裸机状态下运行的,所以CPU占用率默认是100%的,所以这里使用按键支持连按时,对于菜单的切换更好些。


(2)可能部分索引号下的执行函数,需要使用到已经定义的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)笔者这里是使用全屏刷新去切换功能界面,同时,没有启用高级算法去加速显示,所以可能在切换界面的时候效果一般。读者朋友可以试试根据自己的UI情况使用局部刷新,这样可能项目会更加丝滑一点。


本项目中的菜单索引图:


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


4.2 内部功能实现(简化智能手表)


OLED就是正常的驱动与显示,有能力的读者朋友可以使用高级算法去加速OLED屏幕的刷新率,可以使自己的多级菜单切换起来更丝滑。


唯一需要注意的点就是需要去制作菜单里面的UI图标(注意图片大小是否合适):


3b863b79020daed4d29de8ac8e10457c_wKgaomToJ4mAUxiWAANepM_VQng904.png

如果是黑白图片的话,可以直接使用PCtoLCD2002完美版进行取模:

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

4.3 KEY按键

KEY按键注意消抖(建议裸机情况下支持连续按动),同时注意自己实际硬件情况去进行编程(电阻是否存在上拉或者下拉)。

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

4.4 DinoGame实现

43d6f1ec-9550-11ed-bfe3-dac502259ad0.png谷歌公司最近比较流行的小游戏,笔者之前有文章进行了STM32的成功复刻。博客地址:基于STM32的小游戏——谷歌小恐龙(Chrome Dino Game)_混分巨兽龙某某的博客-CSDN博客_谷歌恐龙

4.5 LED控制和DHT11模块

LED和DHT11模块其实都属于外设控制,这里读者朋友可以根据自己的实际情况去取舍。需要注意的是尽可能适配一下自己多级菜单(外设控制也需要注意一下按键安排,可以参考笔者项目的设计)。

五、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级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;

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

6、KEY按键配置:PE3,PE4和PA0设置为端口输入(开发板原理图)

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

7、时钟树配置:

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

8、文件配置

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

六、代码

6.1 OLED驱动代码

此部分OLED的基本驱动函数,笔者使用的是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//写数据


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



//功能函数

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"//字库(可以自己制作)

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

elseOLED_SDIN_Clr();

da=da<<1;

OLED_SCLK_Set();

OLED_SCLK_Clr();

}



}

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

//IICWriteCommand

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

voidIIC_WriteCommand(unsignedcharIIC_Command)

{

IIC_Start();

IIC_WriteByte(0x78);//Slaveaddress,SA0=0

IIC_WaitAck();

IIC_WriteByte(0x00);//writecommand

IIC_WaitAck();

IIC_WriteByte(IIC_Command);

IIC_WaitAck();

IIC_Stop();

}

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

//IICWriteData

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

voidIIC_WriteData(unsignedcharIIC_Data)

{

IIC_Start();

IIC_WriteByte(0x78);//D/C#=0;R/W#=0

IIC_WaitAck();

IIC_WriteByte(0x40);//writedata

IIC_WaitAck();

IIC_WriteByte(IIC_Data);

IIC_WaitAck();

IIC_Stop();

}


voidOLED_WR_Byte(unsigneddat,unsignedcmd)

{

if(cmd)

{

IIC_WriteData(dat);

}

else

{

IIC_WriteCommand(dat);

}

}


voidOLED_Init(void)

{

HAL_Delay(100);//这个延迟很重要


OLED_WR_Byte(0xAE,OLED_CMD);//--displayoff

OLED_WR_Byte(0x00,OLED_CMD);//---setlowcolumnaddress

OLED_WR_Byte(0x10,OLED_CMD);//---sethighcolumnaddress

OLED_WR_Byte(0x40,OLED_CMD);//--setstartlineaddress

OLED_WR_Byte(0xB0,OLED_CMD);//--setpageaddress

OLED_WR_Byte(0x81,OLED_CMD);//contractcontrol

OLED_WR_Byte(0xFF,OLED_CMD);//--128

OLED_WR_Byte(0xA1,OLED_CMD);//setsegmentremap

OLED_WR_Byte(0xA6,OLED_CMD);//--normal/reverse

OLED_WR_Byte(0xA8,OLED_CMD);//--setmultiplexratio(1to64)

OLED_WR_Byte(0x3F,OLED_CMD);//--1/32duty

OLED_WR_Byte(0xC8,OLED_CMD);//Comscandirection

OLED_WR_Byte(0xD3,OLED_CMD);//-setdisplayoffset

OLED_WR_Byte(0x00,OLED_CMD);//


OLED_WR_Byte(0xD5,OLED_CMD);//setoscdivision

OLED_WR_Byte(0x80,OLED_CMD);//


OLED_WR_Byte(0xD8,OLED_CMD);//setareacolormodeoff

OLED_WR_Byte(0x05,OLED_CMD);//


OLED_WR_Byte(0xD9,OLED_CMD);//SetPre-ChargePeriod

OLED_WR_Byte(0xF1,OLED_CMD);//


OLED_WR_Byte(0xDA,OLED_CMD);//setcompinconfiguartion

OLED_WR_Byte(0x12,OLED_CMD);//


OLED_WR_Byte(0xDB,OLED_CMD);//setVcomh

OLED_WR_Byte(0x30,OLED_CMD);//


OLED_WR_Byte(0x8D,OLED_CMD);//setchargepumpenable

OLED_WR_Byte(0x14,OLED_CMD);//


OLED_WR_Byte(0xAF,OLED_CMD);//--turnonoledpanel

HAL_Delay(100);

OLED_FillPicture(0x0);


}



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

//OLED_FillPicture

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

voidOLED_FillPicture(unsignedcharfill_Data)

{

unsignedcharm,n;

for(m=0;m<8;m++)

{

OLED_WR_Byte(0xb0+m,0);//page0-page1

OLED_WR_Byte(0x00,0);//lowcolumnstartaddress

OLED_WR_Byte(0x10,0);//highcolumnstartaddress

for(n=0;n<128;n++)

{

OLED_WR_Byte(fill_Data,1);

}

}

}


//坐标设置

voidOLED_SetPos(unsignedcharx,unsignedchary)

{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

}

//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!

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

}//更新显示

}

voidOLED_On(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(1,OLED_DATA);

}//更新显示

}

//在指定位置显示一个字符,包括部分字符

//x:0~127

//y:0~63

//mode:0,反白显示;1,正常显示

//size:选择字体16/12

voidOLED_ShowChar(u8x,u8y,u8chr,u8Char_Size)

{

unsignedcharc=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);

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>