基于嵌入式STM32的智能手表设计实现

发布时间:2023-09-25  

一、前言


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


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


二、硬件实物图

94921f12-5af3-11ee-939d-92fbcf53809c.png

温度计:

94b8fba0-5af3-11ee-939d-92fbcf53809c.png

94e01744-5af3-11ee-939d-92fbcf53809c.png

游戏机:


95001a44-5af3-11ee-939d-92fbcf53809c.png

952181c0-5af3-11ee-939d-92fbcf53809c.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个),标绿的则为固定的索引号与该索引下需要执行的函数。


typedef struct

{

    u8 current;     //当前状态索引号

    u8 next;   //向下一个

    u8 enter;      //确定

    6u8 back;   //退出

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

} Menu_table;

 

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


Menu_table  table[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_t  func_index = 0; //主程序此时所在程序的索引值

 

void  Menu_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)();//执行当前操作函数

}

 

//按键函数

u8 KEY_Scan(u8 mode)

{

 static u8 key_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)return 1;

  else if(KEY1==0)return 2;

  else if(WK_UP==1)return 3;

 }else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; 

 return 0;

}


说明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情况使用局部刷新,这样可能项目会更加丝滑一点。


本项目中的菜单索引图:

95548bce-5af3-11ee-939d-92fbcf53809c.png

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


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


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


955910e0-5af3-11ee-939d-92fbcf53809c.png

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

957668fc-5af3-11ee-939d-92fbcf53809c.png

4.3 KEY按键


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

957f5ffc-5af3-11ee-939d-92fbcf53809c.png

4.4 DinoGame实现

9590b28e-5af3-11ee-939d-92fbcf53809c.png

谷歌公司最近比较流行的小游戏,笔者之前有文章进行了STM32的成功复刻。博客地址: 谷歌小恐龙在线 — 免费玩谷歌小恐龙 (dino.zone)


4.5 LED控制和DHT11模块


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


五、CubeMX配置


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


9598a462-5af3-11ee-939d-92fbcf53809c.png

2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);

95ab2812-5af3-11ee-939d-92fbcf53809c.png

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

95bf7326-5af3-11ee-939d-92fbcf53809c.png

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

95cc54a6-5af3-11ee-939d-92fbcf53809c.png

95dc532e-5af3-11ee-939d-92fbcf53809c.png

5、TIM2配置:由上面可知DHT11的使用需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;

95f13c8a-5af3-11ee-939d-92fbcf53809c.png

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

95f65f3a-5af3-11ee-939d-92fbcf53809c.png

7、时钟树配置:

960cd134-5af3-11ee-939d-92fbcf53809c.png

8、文件配置

961d91a4-5af3-11ee-939d-92fbcf53809c.png

六、代码


6.1 OLED驱动代码


此部分OLED的基本驱动函数,笔者使用的是I2C驱动的0.96寸OLED屏幕。所以,首先需要使用GPIO模拟I2C通讯。随后,使用I2C通讯去驱动OLED。(此部分代码包含了屏幕驱动与基础显示)


oled.h:


 


 


#ifndef __OLED_H

#define __OLED_H

 

#include "main.h"

 

#define u8 uint8_t

#define u32 uint32_t

 

#define OLED_CMD  0 //写命令

#define OLED_DATA 1 //写数据

 

#define OLED0561_ADD 0x78  // OLED I2C地址

#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进行模拟----------------

 

#define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET) //GPIO_ResetBits(GPIOB,GPIO_Pin_10)//SCL

#define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET) //GPIO_SetBits(GPIOB,GPIO_Pin_10)

 

#define OLED_SDIN_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET) // GPIO_ResetBits(GPIOB,GPIO_Pin_11)//SDA

#define OLED_SDIN_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET) // GPIO_SetBits(GPIOB,GPIO_Pin_11)

 

 

//I2C GPIO模拟

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

 

 

//功能函数

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

 


 


oled.c:


 


 


#include "oled.h"

#include "asc.h"    //字库(可以自己制作)

#include "main.h"

 

 

 

/********************GPIO 模拟I2C*******************/

//注意:这里没有直接使用HAL库中的模拟I2C

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

//IIC Start

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

void IIC_Start()

{

 

 OLED_SCLK_Set() ;

 OLED_SDIN_Set();

 OLED_SDIN_Clr();

 OLED_SCLK_Clr();

}

 

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

//IIC Stop

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

void IIC_Stop()

{

 OLED_SCLK_Set() ;

 OLED_SDIN_Clr();

 OLED_SDIN_Set();

 

}

 

void IIC_WaitAck()

{

 OLED_SCLK_Set() ;

 OLED_SCLK_Clr();

}

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

// IIC Write byte

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

 

void IIC_WriteByte(unsigned char IIC_Byte)

{

 unsigned char i;

 unsigned char m,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);//

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

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

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

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

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

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

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

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