基于M5StickCplus的桌面电子沙漏
可以定时的电子沙漏,设置不同的时长,在LCD屏幕上显示时间,在灯板上显示沙漏效果。设计了外壳可以在着桌面摆放。
标签
嵌入式系统
Arduino
显示
2022暑假一起练
好喝的娃哈哈
更新2022-09-06
济南大学
544

2022暑假在家一起练总结报告

一,板卡介绍

本次活动推出了M5stickCPlus板卡,该板块是M5stickC的大屏版本,主控采用ESP32-PICO-D4模组,具备蓝牙4.2与WIFI功能,小巧的机身内部集成了丰富的硬件资源,如红外、RTC、麦克风、LED、IMU、按键、蜂鸣器、PMU等,在保留原有M5StickC功能的基础上加入了无源蜂鸣器,同时屏幕尺寸升级到1.14寸、135*240分辨率的TFT屏幕,相较之前的0.96寸屏幕增加18.7%的显示面积,电池容量达到120mAh,接口同样支持HAT与Unit系列产品。

  • 开关机操作

    • 开机:按复位按键,持续至少 2 秒

    • 关机:按复位按键,持续至少 6 秒

  • 注意

    • M5StickC Plus支持的波特率: 1200 ~115200, 250K, 500K, 750K, 1500K

    • G36/G25共用同一个端口,当使用其中一个引脚时要将另外一个引脚设置为浮空输入

    • 比如要使用G36引脚作为ADC输入,则配置G25引脚为浮空状态

  • 产品特性

    • 基于 ESP32开发,支持WiFi、蓝牙

    • 内置3轴加速计与3轴陀螺仪

    • 内置Red LED

    • 集成红外发射管

    • 内置RTC

    • 集成麦克风

    • 用户按键, LCD(1.14 寸), 电源/复位按键

    • 120 mAh 锂电池

    • 拓展接口

    • 集成无源蜂鸣器

    • 可穿戴 & 可固定

FlLdruegCAiz5t8FqtQki-J8Dx70FmLO9jcfj8cTsWjTBum6oQ_xpAxA

二,项目介绍

官方说明 :

任务2:可以定时的电子沙漏,要求设置不同的时长,在LCD屏幕上显示时间,在灯板上显示沙漏效果

本项目通过M5实体按键来实现定时效果,可以设置分钟秒数,根据设置的时间自动计算沙漏时间间隔,但是由于所设计的沙漏流动过程固定,总共36个状态,所以在计算时间的时候会存在些许误差,后续可能会优化算法来减小误差。

FgazPA3naqTQIKYWyS77kFYyusfl

流沙效果通过存储在M5里面的数组来实现,总计36个状态,即要在设定的时间里闪烁36次,但是计算过程中难免会出现小数,最终会导致提前几秒闪完,目前通过增添全部黑色图案数组来弥补误差,后续可能会通过增加闪烁图案(各种表情,比如笑脸)来弥补设计缺陷

 

此外,本项目还设计了电子沙漏的外壳,打通了成为产品的最后一步,M5和外壳创新的采用了磁吸连接,方便拿取,外壳后面带有支腿,可以摆放在桌面,随时定时。主体采用橙色材料,和M5搭配更为柔和。

FgvQwGB3M-piZZELChla47oKdW5D

 

三,设计思路

本项目设计主要从两个方面出发,一是定时,二是沙漏流动过程,下面来分别展示一下

1,首先是定时的问题,一开始考虑的方案有重力感应控制时间,WiFi联网(http协议)设置时间,实体按钮控制时间。本项目最终采用了是实体按钮控制时间,该方法设计比较简单,操作起来也较为容易。方案确定之后就是要思考怎么来设定具体的时间,M5Stick可供使用的实体按键只有两个,而需要的状态控制有“加”,“减”,“确定”,“变换”等,幸好,M5StickC官方库里设计了按钮时长检测,这样一来通过检测按钮按下时间来判断状态即可。最后就是屏幕显示效果,该项目采用了极简设计,屏幕上只显示分钟和秒数,通过颜色来进行区分,灵感是来自于B站挺火的ESP8266小电视设计,简约大方。

FgxHbkrKbM-oLljSidnM0Io7bu5w

2,其次是流沙效果,这是本次任务的核心,如何能更逼真的模拟流沙的效果,重力感应的效果,作者也是思考了很长时间,但无奈作者目前功力尚浅,只能采用逐个点亮的方式来模拟流沙小效果,所以就有了之前所说的数组,每个数组有36个状态,每个状态需要一个8个0或1来控制,所以代码部分就很冗余,效率也不是特别高。然后是重力感应,为了区分不同的效果,又增添个两个反方向的数组,但是每次数组的切换都要浪费大量的时间,所以采用的指针变量,直接改变地址,减少大量时间,最终效果也是不错的。

FjO8A8IM9W0BmgSaI2osxl9tF0R-

 

四,框图

 

FhSr5xvypxtCIwK7ee49mI0FQS8b

 

五,主要代码片段和说明

1,屏幕初始化函数

void LCD_init()
{
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setRotation(1);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.setCursor(0, 0);M5.Lcd.println("Set Time:");      //依次设计数字显示位置
    M5.Lcd.setCursor(10, 30);M5.Lcd.println("Min: ");       
    M5.Lcd.setCursor(10, 60);M5.Lcd.println("Sec: ");
    M5.Lcd.setCursor(90, 30);M5.Lcd.print(Minute);
    M5.Lcd.setCursor(90, 60);M5.Lcd.print(Second);
    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.setCursor(180, 30);M5.Lcd.print("+");
    M5.Lcd.setCursor(180, 60);M5.Lcd.print("+");
}

效果:

FgazPA3naqTQIKYWyS77kFYyusfl

2,时间设置和屏幕显示函数

void LCD_setTime()                         //时间设置函数
{   
    M5.Lcd.setTextSize(3);
    while (set_status == 0)               //状态标志位      0为设置时间
    {
        if (min_status == 0)                //分钟标志位      0为设置分钟
        {
            M5.update();                       //更新按钮状态,检测按压时长  
            if (M5.BtnB.wasReleasefor(50))                 //如果小键被按压50ms   (M5中间的按键称为大键,上方的按键称为小键)
            {
                en_addmin = !en_addmin;                                  //对分钟增加标志位取反,即改变增加还是减少分钟的状态
                M5.Lcd.fillRect(180, 30, 30, 30, BLACK);
                M5.Lcd.setCursor(180, 30);
                M5.Lcd.setTextColor(GREEN);
                if (en_addmin)
                {
                    M5.lcd.print("+");
                }
                else
                {
                    M5.Lcd.print("-");
                }
            }
            if (M5.BtnA.wasReleasefor(50))                 //检测大键按压状态
            {
                if (en_addmin == 1)                              //如果分钟增加标志位为一,则按压一次分钟加一
                {
                    Minute++;
                }
                else
                {
                    Minute--;
                }
                M5.Lcd.setTextColor(WHITE);
                M5.Lcd.fillRect(90, 30, 60, 28, BLACK);              //刷新
                M5.Lcd.setCursor(90, 30);
                M5.Lcd.print(Minute);
            }
            if (M5.BtnB.wasReleasefor(700))                       //长按小键,确认分钟设置完毕               
            {
                min_status = 1;
                M5.Lcd.fillRect(180, 30, 60, 30, BLACK);
                M5.Lcd.setCursor(180, 30);
                M5.Lcd.setTextColor(YELLOW);
                M5.Lcd.print("OK");                                           //输出OK
            }
        } 
        else                                                                             //对秒进行相同的操作
        {
            M5.update();
            if (M5.BtnB.wasReleasefor(50))
            {
                en_addsec = !en_addsec;
                M5.Lcd.fillRect(180, 60, 30, 30, BLACK);
                M5.Lcd.setCursor(180, 60);
                M5.Lcd.setTextColor(GREEN);
                if (en_addsec)
                {
                    M5.lcd.print("+");
                }
                else
                {
                    M5.Lcd.print("-");
                }
            }
            if (M5.BtnA.wasReleasefor(50))
            {
                if (en_addsec == 1)
                {
                    Second++;
                }
                else
                {
                    Second--;
                }
                M5.Lcd.setTextColor(WHITE);
                M5.Lcd.fillRect(90, 60, 60, 28, BLACK);
                M5.Lcd.setCursor(90, 60);
                M5.Lcd.print(Second);
            }
            if (M5.BtnB.wasReleasefor(700))
            {
                sec_status = 1;
                M5.Lcd.fillRect(180, 60, 60, 30, BLACK);
                M5.Lcd.setCursor(180, 60);
                M5.Lcd.setTextColor(YELLOW);
                M5.Lcd.print("OK");
            }
        }
        if (min_status == 1 && sec_status == 1)                       //如果分钟和秒都设置完成,则令设置状态标志位为1,跳出循环
        {
            set_status = 1;
        }
    }
}

   3,时间显示函数

void LCD_showinit()
{
    M5.Lcd.fillScreen(BLACK);           //每次显示前先进行局部刷新,填冲为黑色,和背景一个颜色
    M5.Lcd.setTextSize(10);
    M5.Lcd.setCursor(0, 30);
    if (Minute > 9)                             //如果分钟大于9,则正常显示
    {
        M5.Lcd.print(Minute);
    }
    else
    {                                                   // 否则输出零占位,“秒”同理
        M5.Lcd.print("0");
        M5.Lcd.print(Minute);
    }
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setCursor(120, 30);
    if (Second > 9)
    {
        M5.Lcd.print(Second);
    }
    else
    {
        M5.Lcd.print("0");
        M5.Lcd.print(Second);
    }
}

FgxHbkrKbM-oLljSidnM0Io7bu5w

4,LED灯闪烁函数,灯板主控芯片为74hc595,采用shiftOut填冲两次,然后输出一次,达到一次控制两个灯板的效果

void Write_H(int row, char high_data, char low_data)
{
    digitalWrite(SRCLK_Pin, LOW);
    shiftOut(SER_Pin, SRCLK_Pin, MSBFIRST, B00000001 << (row - 1));
    shiftOut(SER_Pin, SRCLK_Pin, MSBFIRST, low_data);
    shiftOut(SER_Pin, SRCLK_Pin, MSBFIRST, B00000001 << (row - 1));
    shiftOut(SER_Pin, SRCLK_Pin, MSBFIRST, high_data);
    digitalWrite(RCLK_Pin, HIGH);
    digitalWrite(RCLK_Pin, LOW);
    delay(1);
}

5,定时器,100ms时间精度,用来计算时间间隔

void count();
Ticker Count(count, 100);
void count()
{
    time1 += 1;
} // 100ms计时器

6,同过定时器来进行时间减一,屏幕刷新,位置占位,不建议采用定时器进行刷新这么做

 

void Clock();
Ticker CLOCK(Clock, 1000);
void Clock()
{
    if (Second > 10)
    {
        Second--;
        M5.Lcd.fillRect(120, 0, 120, 135, BLACK);
        M5.Lcd.setTextColor(WHITE);
        M5.Lcd.setTextSize(10);
        M5.Lcd.setCursor(120, 30);
        M5.Lcd.print(Second);
    }
    else if (Second > 0 && Second <= 10)
    {
        Second--;
        M5.Lcd.fillRect(120, 0, 120, 135, BLACK);
        M5.Lcd.setTextColor(WHITE);
        M5.Lcd.setTextSize(10);
        M5.Lcd.setCursor(120, 30);
        M5.Lcd.print("0");
        M5.Lcd.print(Second);
    }
    else if (Second == 0)
    {
        if (Minute > 10)
        {
            Minute--;
            Second = 59;
            M5.Lcd.fillRect(0, 0, 120, 135, BLACK);
            M5.Lcd.fillRect(120, 30, 120, 135, BLACK);
            M5.Lcd.setTextColor(GREEN);
            M5.Lcd.setTextSize(10);
            M5.Lcd.setCursor(0, 30);
            M5.Lcd.print(Minute);
            M5.Lcd.setCursor(120, 30);
            M5.Lcd.setTextColor(WHITE);
            M5.Lcd.print(Second);
        }
        else
        {
            Minute--;
            Second = 59;
            M5.Lcd.fillRect(0, 0, 120, 135, BLACK);
            M5.Lcd.fillRect(120, 30, 120, 135, BLACK);
            M5.Lcd.setCursor(0, 30);
            M5.Lcd.setTextColor(GREEN);
            M5.Lcd.setTextSize(10);
            M5.Lcd.print("0");
            M5.Lcd.setCursor(60, 30);
            M5.Lcd.print(Minute);
            M5.Lcd.setCursor(120, 30);
            M5.Lcd.setTextColor(WHITE);
            M5.Lcd.print(Second);
        }
    }
}

7,点亮灯板,更换数组

沙漏流动过程数组

unsigned char New_High[]={
    0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF,
    0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0x7F,
    0x01,0x03,0x07,0x0F,0x1F,0x3F,0x3F,0x7F,
    0x01,0x03,0x07,0x0F,0x1F,0x1F,0x3F,0x7F,
    0x01,0x03,0x07,0x0F,0x0F,0x1F,0x3F,0x7F,
    0x01,0x03,0x07,0x07,0x0F,0x1F,0x3F,0x7F,
    0x01,0x03,0x03,0x07,0x0F,0x1F,0x3F,0x7F,
    0x01,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,
    0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,
    0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x3F,
    0x00,0x01,0x03,0x07,0x0F,0x1F,0x1F,0x3F,
    0x00,0x01,0x03,0x07,0x0F,0x0F,0x1F,0x3F,
    0x00,0x01,0x03,0x07,0x07,0x0F,0x1F,0x3F,
    0x00,0x01,0x03,0x03,0x07,0x0F,0x1F,0x3F,
    0x00,0x01,0x01,0x03,0x07,0x0F,0x1F,0x3F,
    0x00,0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,
    0x00,0x00,0x01,0x03,0x07,0x0F,0x1F,0x1F,
    0x00,0x00,0x01,0x03,0x07,0x0F,0x0F,0x1F,
    0x00,0x00,0x01,0x03,0x07,0x07,0x0F,0x1F,
    0x00,0x00,0x01,0x03,0x03,0x07,0x0F,0x1F,
    0x00,0x00,0x01,0x01,0x03,0x07,0x0F,0x1F,
    0x00,0x00,0x00,0x01,0x03,0x07,0x0F,0x1F,
    0x00,0x00,0x00,0x01,0x03,0x07,0x0F,0x0F,
    0x00,0x00,0x00,0x01,0x03,0x07,0x07,0x0F,
    0x00,0x00,0x00,0x01,0x03,0x03,0x07,0x0F,
    0x00,0x00,0x00,0x01,0x01,0x03,0x07,0x0F,
    0x00,0x00,0x00,0x00,0x01,0x03,0x07,0x0F,
    0x00,0x00,0x00,0x00,0x01,0x03,0x07,0x07,
    0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x07,
    0x00,0x00,0x00,0x00,0x01,0x01,0x03,0x07,
    0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x07,
    0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,
    0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x03,
    0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,
    0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  
};
void Refresh();
Ticker REFRESH(Refresh,10);            //10ms进行一次
void Refresh(){
    for(int i=0 ; i<8 ; i++){
        Write_L(i+1,*(h1+offset+i),*(l1+offset+i));      //指针确定数组
        if(direction ==1){                                             //大大减少运算时间
            h1=&New_High[0];
            l1=&New_Low[0];
        }
        else{
            h1=&inverted_high[0];
            l1=&inverted_low[0];
        }

    }
}

六,难点和尚未解决的问题

难点:

  • 屏幕显示及控制时间逻辑
  • 沙漏流动过程模拟
  • 重力感应变化时数组的切换,采用指针来获取数组地址解决

尚未解决的问题(挖坑):

  • 计算时间间隔算法误差较大,尤其是在短的时间间隔内误差更为明显
  • 沙漏模拟过程不是很逼真,仍有优化的空间
  • 在秒是个位数时,改变M5位置无法检测重力变化(BUG)
  • 目前计时完成之后必须长按关机键才能进行下次计时,不是很方便

七,活动感言

最后,非常感谢硬禾学堂能提供这次机会,在这次活动中学到了不少东西,指针的应用,灯板的驱动,外壳的设计等等,也认识到了不少有趣的人,希望以后可以完善一下灯板的程序,再接再厉。

附件下载
电子沙漏.zip
源代码,使用vscode+platformino开发,ticker.h库,M5StickCPlus.h库
团队介绍
好喝的娃哈哈
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号