做艺术品的网站国内搜索引擎大全
Arduin ESP32 +epaper(电子墨水屏)时钟相册制作教程
- 🔖epaper(电子墨水屏)采用的是:合宙1.54“ 电子墨水屏(e-paper)
- 📍相关篇《Arduino框架下ESP32/ESP8266+合宙1.54“ 电子墨水屏(e-paper)驱动显示示例》
- 🌿电子屏幕相关资料:
https://www.waveshare.net/wiki/1.54inch_e-Paper_Module
- 🪓图片取模教程和工具介绍:https://www.waveshare.net/wiki/Image2Lcd%E5%9B%BE%E7%89%87%E5%8F%96%E6%A8%A1%E6%AD%A5%E9%AA%A4
- 📌同类型的,微雪品牌的相关资料参考(提供了包含约50种规格的电子墨水屏(e-paper)):
https://www.waveshare.net/wiki/1.54inch_e-Paper_Module
- 🔱个人采用的是esp32测试,esp8266应该也可以的。
🔖有关数据管理和更新,可以参考以下内容:
- 📌通过网页实现图片数据管理,可以参考《Arduino ESP32当作FTP服务器 访问SD卡文件》
- 📌《Arduino ESP32 Web服务器SD卡文件管理》
🛠图片取模制作
- 🔧制作软件:
Image2Lcd
- 🌿图片尺寸要求:200X200像素点
- 📍图片素材获取源:
https://icons8.com/icons/set/raspberry-pi
- 📍图片素材获取源2:
https://fontawesome.com/icons?d=gallery&p=2
🍭字模数据转BIN文件
- 🔨需要借助c代码,使用C编译器指令或
VSCode
将字模数组转bin文件。200X200像素的图片去取模转BIN文件后的大小都是5KB。
#include <stdio.h>
const unsigned char gImage_yun[5000] = {图片字符数组}
int main()
{size_t size = sizeof(gImage_yun[0]);size_t count = sizeof(gImage_yun) / size;FILE *fp = fopen("gImage_yun.bin", "wb");if (fp == NULL){printf("Failed to open file.\n");return 1;}size_t elements_written = fwrite(gImage_yun, size, count, fp);if (elements_written != count){printf("Failed to write data to file.\n");fclose(fp);return 1;}fclose(fp);printf("Data written successfully.\n");return 0;
}
- 🔖如何配合
CodeRuner
插件使用:
-
🔖如果不使用该插件的话,可以在终端命令行中输入
gcc .\Font.c
命令按回车,然后执行.\a.exe
命令回车。(前提是安装了C编译器并且添加到了系统环境变量中)
-
🔖运行成功后,会自动生成:
.bin
文件。
-
🌿将生成的
.BIN
文件拷贝到工程项目的data
文件夹内。如果使用SD卡配合文件系统也是可以的。
📘LittleFS文件系统配置
- ⚡Arduino平台的所有的文件系统(SPIFFS/LittleFS/FatFS文件系统),仅针对ESP32,仅支持Arduino IDE
1.8.x.x版本
下使用,Arduino IDE 2.x.x不支持
!!!(ESP8266支持Arduino IDE 2.x.x版本) - 📍esp32文件系统上传插件下载地址:
https://github.com/lorol/arduino-esp32fs-plugin/releases
- 📍ESP8266 littlefs文件系统下载地址:
https://github.com/earlephilhower/arduino-littlefs-upload/releases
📗LittleFS系统文件上传说明
- ✨首次上传程序时,需要注意,删除全部内容:
- 🔱上传LittleFS系统文件
🍭电子墨水屏注意事项
- 🔱支持局刷的屏幕,注意使用的时候不能一直用局刷对屏幕进行刷新,需要在做几次局刷之后,对屏幕进行一次全刷清屏。否则会造成屏幕显示效果异常。
- 🔱注意屏幕不能长时间上电,在屏幕不刷新的时候,要将屏幕设置成睡眠模式,或者进行断电处理。否则屏幕长时间保持高电压状态,会损坏膜片,无法修复。
- 🔱使用墨水屏的时候,建议刷新时间间隔至少是180s, 并且至少每24小时做一次刷新,如果长期不使用墨水屏的话,要将墨水屏刷白存放。(具体储存环境需求参考数据手册)
- 🔱屏幕进入睡眠模式之后,会忽略发送的图片数据,只有重新初始化才能正常刷新。
📘电子墨水屏显示内容说明
- 🌿对于黑白款的电子墨水屏,内容支持全刷和局刷。在使用过程中如果需要切换不同的刷新方式时,需要先停止使用SPI总线(
SPI.endTransaction()
),推荐在刷完显示内容后,调用epd.Sleep()
之后,停与屏幕的SPI通讯。再次刷新屏幕的时候,再进行一次初始化。
⛳图片更新方式
- 🌿通过Arduino IDE
1.8.x.x
版本,只需选择上传littleFS
进行文件上传即可,但是还是需要在当前工程目录下有data
目录,才可更新显示内容素材。
- 图片数据文件上传后,进行复位,打开串口,如果文件系统初始化成功,读取到文件会打印出来:
📝主程序代码:
/*** @filename : epd1in54-demo.ino* @brief : 1.54inch e-paper display demo* @author : Yehui from Waveshare** Copyright (C) Waveshare September 5 2017** Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated documnetation files (the "Software"), to deal* in the Software without restriction, including without limitation the rights* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell* copies of the Software, and to permit persons to whom the Software is* furished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included in* all copies or substantial portions of the Software.** ESP32引脚定义:* BUSY -> 13 || RES -> 12 || DC -> 14 || CS -> 27 || SCL -> 18 || SDA -> 23 ||*黑字:COLORED 白色:UNCOLORED支持局刷的屏幕,注意使用的时候不能一直用局刷对屏幕进行刷新,需要在做几次局刷之后,对屏幕进行一次全刷清屏。否则会造成屏幕显示效果异常。
注意屏幕不能长时间上电,在屏幕不刷新的时候,要将屏幕设置成睡眠模式,或者进行断电处理。否则屏幕长时间保持高电压状态,会损坏膜片,无法修复。
使用墨水屏的时候,建议刷新时间间隔至少是180s, 并且至少每24小时做一次刷新,如果长期不使用墨水屏的话,要将墨水屏刷白存放。(具体储存环境需求参考数据手册)
屏幕进入睡眠模式之后,会忽略发送的图片数据,只有重新初始化才能正常刷新。*/#include <SPI.h>
#include "epd1in54.h"
#include "epdpaint.h"
#include "imagedata.h"#include <WiFi.h> // esp32开发板自带库
#include <time.h>
#include <sys/time.h>
#include <Ticker.h>
#include <LittleFS.h> // 如果使用LittleFS
#include "FS.h"
#include "esp_task_wdt.h"
//#include "RTClib.h" //by Adafruit库来实现软件RTC时间
#define TZ 8 // (utc+) TZ in hours
#define DST_MN 0 // use 60mn for summer time in some countries
#define TZ_MN ((TZ)*60)
#define TZ_SEC ((TZ)*3600)
#define daylightOffset_sec 3600#define LED_PIN 2
#define COLORED 0
#define UNCOLORED 1#define bitmapSize 5000#define PIC_TIME 75#define Conter_MAX 10 //图片最多显示数量const char* ssid = "MERCURY_D268G";//填写wifi信息
const char* password = "pba5ayzk";
uint8_t Conter=0;
String pic1[10];/*** Due to RAM not enough in Arduino UNO, a frame buffer is not allowed.* In this case, a smaller image buffer is allocated and you have to * update a partial display several times.* 1 byte = 8 pixels, therefore you have to set 8*N pixels at a time.*/
unsigned char image[1024];
Paint paint(image, 0, 0); // width should be the multiple of 8
Epd epd;Ticker Refresh; //图片切换
Ticker UPDATE; //时间显示
Ticker blinker;//unsigned long time_start_ms;
//unsigned long time_now_s;
const char * weekday[] = { "7", "1","2", "3","4", "5", "6"};
uint8_t blinkerPace = 1; //seconds
static bool TOGGLE = false;
//bool Refresh_flag = true;void listDir(fs::FS &fs, const char * dirname, uint8_t levels);
void Picture_Refresh();
void UP_Date_Data();void blink() {digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}void setup() {// put your setup code here, to run once:Serial.begin(9600);Serial.println();pinMode(LED_PIN, OUTPUT);WiFi.mode(WIFI_STA);WiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {delay(500);Serial.println("Connecting to WiFi...");}// rtc.begin(DateTime(F(__DATE__), F(__TIME__)));//不联网,使用编译时间// rtc.adjust(DateTime(2024,3, 9, 23, 12, 10));Serial.println("Connected to WiFi");if (!LittleFS.begin()) { // LittleFS初始化Serial.println("LittleFS Mount Failed");} else {Serial.println("LittleFS Mounted Successfully");}configTime(TZ_SEC, daylightOffset_sec , "ntp3.aliyun.com", "ntp.ntsc.ac.cn", "1.cn.pool.ntp.org");//asia.pool.ntp.org//rtc.begin(DateTime(F(__DATE__), F(__TIME__)));Refresh.attach(PIC_TIME, Picture_Refresh); // 每隔60秒执行一次blinker.attach(blinkerPace, blink);//lut_partial_update if (epd.Init(lut_full_update) != 0) {Serial.print("e-Paper init failed");return;}epd.ClearFrameMemory(0xFF); // bit set = white, bit reset = blackepd.DisplayFrame();epd.ClearFrameMemory(0xff); // bit set = white, bit reset = blackepd.DisplayFrame();delay(2000);paint.SetRotate(ROTATE_0);//屏幕旋转 指定显示区域paint.Clear(UNCOLORED);//白色区域内容显示epd.SetFrameMemory(gImage_bird);//gImage_rp / gImage_birdepd.DisplayFrame();epd.SetFrameMemory(gImage_bird);epd.DisplayFrame();epd.Sleep();SPI.endTransaction();delay(2000);UPDATE.once(5,UP_Date_Data); //时间更新一次// epd.Reset();Serial.println("Display complete");listDir(LittleFS, "/", 1);// 禁用硬件看门狗esp_task_wdt_init(10 * 1000 * 1000,false); // 参数为0表示不启用看门狗}void loop() {// put your main code here, to run repeatedly:
}void listDir(fs::FS &fs, const char * dirname, uint8_t levels){Serial.printf("Listing directory: %s\r\n", dirname);File root = fs.open(dirname);if(!root){Serial.println("- failed to open directory");return;}if(!root.isDirectory()){Serial.println(" - not a directory");return;}File file = root.openNextFile();while(file){if(file.isDirectory()){Serial.print(" DIR : ");Serial.println(file.name());if(levels){listDir(fs, file.path(), levels -1);}} else {Serial.print(" FILE: ");pic1[Conter]=file.name();Serial.print( pic1[Conter].c_str());Serial.print("\tSIZE: ");Serial.println(file.size());Conter++;}file = root.openNextFile();Conter %=Conter_MAX;}
}void Picture_Refresh()
{// 读取文件内容到缓冲区static uint8_t buffer_imge[bitmapSize]; // 确保这里定义的缓冲区足够大以容纳字模数据static uint8_t CNT=0;File bitmapFile;String pic_name = "/"+ pic1[CNT++];Serial.println(pic_name.c_str());//打印当前要显示图片的名称bitmapFile = LittleFS.open(pic_name, "r");CNT %= Conter;if (!bitmapFile) {Serial.println("Failed to open bitmap file for reading!");return;}else
{
//Serial.print("Reading" );
uint16_t len = bitmapFile.size();
// uint32_t start = millis();while(len){size_t toRead = len;if(toRead > bitmapSize){toRead = bitmapSize;}bitmapFile.read(buffer_imge, toRead);len -= toRead;}// uint32_t end = millis() - start;
// Serial.printf("5000 bytes Reading in %u ms\r\n", end);epd.ClearFrameMemory(0xff); //刷白epd.DisplayFrame();epd.ClearFrameMemory(0xff); // bit set = white, bit reset = blackepd.DisplayFrame();delay(800);paint.SetRotate(ROTATE_0);epd.SetFrameMemory(buffer_imge);epd.DisplayFrame();epd.SetFrameMemory(buffer_imge);epd.DisplayFrame();epd.Sleep();SPI.endTransaction();}UPDATE.once(60,UP_Date_Data);//调用时间更新
}void UP_Date_Data()
{char buff[32];time_t now = time(nullptr);struct tm* timeinfo;timeinfo = localtime(&now); // 转换为本地时间strftime(buff, sizeof(buff), "%c", timeinfo);Serial.println(buff);Serial.printf(PSTR("20%02d-%02d-%02d %02d:%02d:%02d\r\n"),\(timeinfo->tm_year)%100,timeinfo->tm_mon+1,timeinfo->tm_mday,\timeinfo->tm_hour,timeinfo->tm_min,timeinfo->tm_sec);String Date_data =String(timeinfo->tm_mon+1)+"-"+String(timeinfo->tm_mday) +" "+String(timeinfo->tm_hour)+":"+String(timeinfo->tm_min)+" T"+weekday[timeinfo->tm_wday];String Time_data = String(timeinfo->tm_hour)+":"+String(timeinfo->tm_min);//"20"+String((timeinfo->tm_year)%100)+"-"+ +":"+String(timeinfo->tm_sec);Serial.println(Date_data);Serial.println(Time_data);delay(800);epd.ClearFrameMemory(0xFF); // bit set = white, bit reset = blackepd.DisplayFrame();epd.ClearFrameMemory(0xFF); // bit set = white, bit reset = blackepd.DisplayFrame();paint.SetRotate(ROTATE_0);paint.SetWidth(200);paint.SetHeight(26);paint.Clear(UNCOLORED);//白底paint.DrawStringAt(0, 3,Date_data.c_str(), &Font24, COLORED);//UNCOLORED:白色epd.SetFrameMemory(paint.GetImage(), 0, 174, paint.GetWidth(), paint.GetHeight()); epd.DisplayFrame();epd.SetFrameMemory(paint.GetImage(), 0, 174, paint.GetWidth(), paint.GetHeight()); epd.DisplayFrame();epd.Sleep();}
📘工程源码:
链接: https://pan.baidu.com/s/1jd7jHhtjmozOxTA38kuGsw?pwd=dngy
提取码: dngy
- 🔖此文章仅作为个人学习探索知识的总结,不作为他人或引用者的理论依据,由于学识所限,难免会出现错误或纰漏,欢迎大家指正。