From f289938a65ae013f852dcc818dbd21fb5c8d93c1 Mon Sep 17 00:00:00 2001 From: fsender Date: Sun, 1 Dec 2024 19:57:36 +0800 Subject: [PATCH] Bump to 1.4.0 with many new feats --- CHANGELOG.md | 46 +++ README.md | 11 +- examples/ex02_demo/ex02_demo.ino | 225 +++++++----- .../ex04_wifi/2_wifi_config/2_wifi_config.ino | 119 +++--- .../4_wifi_text_show/4_wifi_text_show.ino | 4 +- extra/ctg_timelib/TimeLib.h | 9 + extra/ctg_timelib/ctg_timelib.cpp | 104 ++++++ extra/ctg_timelib/ctg_timelib.hpp | 158 ++++++++ library.json | 2 +- library.properties | 2 +- src/guy_button.cpp | 15 +- src/guy_button.h | 4 +- src/guy_driver_config.h | 95 ++++- .../guy_154b_270b_290b/guy_154b_270b_290b.cpp | 2 +- src/guy_epaper/guy_epaper_config.h | 4 +- src/guy_epaper/lcdDebug/ctg_stack_c_defines.h | 6 +- src/guy_epaper/lcdDebug/lcdDebug.cpp | 36 +- src/guy_version.h | 10 +- src/guy_wireless.cpp | 71 +++- src/readguy.cpp | 342 ++++++++++++++++-- src/readguy.h | 113 ++++-- 21 files changed, 1112 insertions(+), 266 deletions(-) create mode 100644 extra/ctg_timelib/TimeLib.h create mode 100644 extra/ctg_timelib/ctg_timelib.cpp create mode 100644 extra/ctg_timelib/ctg_timelib.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index fd187b9..c7f6c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +## Release 1.4.0 + +### 新增内容 + +1. 新增用户引脚配置数据字段的读取和写入接口 + +2. 附加了时间管理库, 位于 `extra/ctg_timelib` 文件夹内. 本库可用于替代 [Arduino TimeLib](https://github.com/PaulStoffregen/Time), 使ESPxxxx系列主控可以与GNU C time库使用同一个时间访问接口, 并显著减小代码体积. + +3. 新增 `getDepth` 函数, 用于获得当前墨水屏的显示颜色深度 + +4. 新增 `setAutoFullRefresh` 函数, 可用于设置自动慢刷, 防止连续快刷导致屏幕对比度降低甚至刷坏屏幕 + +5. 新增 `screenshot` 函数, 可截屏并保存在SD卡中. + +6. 新增 `SDdeinit` 函数, 调用后SD卡将会不可用, 用于安全卸载SD卡或者重新检测SD卡的可用性. + +7. 新增 `recoverI2C` 函数, 在I2C引脚被复用的情形下, 使用I2C功能会影响其他引脚的复用功能. 该函数可以在I2C用完之后还原这些复用引脚的功能设置 + +8. 新增 `setSDbusy` 函数, 在SD卡的CS引脚被复用作按键时, 调用 `setSDbusy(1)` 标记此时SD卡正在被占用, `setSDbusy(0)` 来释放. **该函数仅建议在需要兼容SD卡被复用作按键的硬件上使用. 其他情况用处不大. 前往** `guy_driver_config.h` **文件并禁用宏** `READGUY_ALLOW_SDCS_AS_BUTTON` **是最佳选择** + +9. 新增实验性功能 `READGUY_ALLOW_DC_AS_BUTTON`, `READGUY_ALLOW_EPDCS_AS_BUTTON`, `READGUY_ALLOW_SDCS_AS_BUTTON`, 详见文件 `guy_driver_config.h` . + +10. 内置服务器功能现在支持设置请求类型为 UPLOAD, HEAD, POST, PUT, PATCH, DELETE 和 OPTIONS 的服务函数, 以及请求返回 404 Not Found 的响应. + +### 优化 + +1. 优化了大部分代码结构 (给一些函数加上const和inline标志, 提高效率) , 并修复了部分变量未初始化就调用的bug + +2. 优化了配置网页的结构, 现在配置引脚的按钮在网页最下方了 + +3. 优化部分示例程序, 增加并更新部分注释, 更易读 更易用 + +4. 完全移除了内部对 RTC 的支持. + +5. 为 1.54 寸 LilyGo 屏幕, 2.9 寸前置光屏幕和 2.7 寸屏幕提供了睡眠模式下快速唤醒的支持. + +6. 为 LCD 墨水屏模拟器提供了快速但不支持同屏灰度的模拟模式. + +### Bug 修复 + +1. 修复在禁用 dynamic_pin_settings 时, 数组访问越界的bug + +2. 修复了禁用 WiFi 功能但未配置引脚时, 程序会异常继续运行的bug + +3. 按键任务函数结构重构, 解决了按键被复用时, 无法设置按键为高电平触发的情况 + ## Release 1.3.7 - 2024/3/11 1. 配网页面, 增加了用户引脚数据 (可以配置其他功能的引脚. 此功能似乎是为了准备给esp32s3用sdmmc库驱动sd卡用的) diff --git a/README.md b/README.md index 5407ea0..7a4e7de 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,10 @@ -**版本1.3.7正式发布!欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~ +**版本1.4,0正式发布!欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~ **即将发布7个全新的屏幕驱动: 欢迎支持! (详见后面的驱动表格)** -**发布好的全新驱动程序版本号将会是2.0.0!** - 欢迎克隆, 项目交流QQ群: 926824162 (萌新可以进来问问题的哟), 项目的 Bilibili 主页: [BV1f94y187wz](https://www.bilibili.com/video/BV1f94y187wz/) 记得三连+关注我这个宝藏up主哦~ 注意, 有问题一定要先加群问, 先不要提issue, 提了小影 *(也就是作者FriendshipEnder)* 也不会看的. @@ -253,17 +251,20 @@ firmware.bin 0x10000 menuconfig 内容: -``` +``` ini FREERTOS_HZ = 1000 ESP32_DEFAULT_CPU_FREQ_MHZ = 240 ESP32_BROWNOUT_DET_LVL = 0 ESP_PHY_REDUCE_TX_POWER=y FATFS_API_ENCODING_UTF_8 = true +; SDK_TOOLCHAIN_SUPPORTS_TIME_WIDE_64_BITS = true +; SPI_MASTER_IN_IRAM = true ; 该选项能提高SPI传输速率 +; SPI_MASTER_ISR_IN_IRAM = true ``` --- -Copyright © 2022-2023 FriendshipEnder. All Rights reserved. +Copyright © 2022-2025 FriendshipEnder. All Rights reserved. 版权声明:需要经过作者@friendshipender的许可才能商用。 可以联系邮箱playyinzhe@qq.com询问商用事宜 diff --git a/examples/ex02_demo/ex02_demo.ino b/examples/ex02_demo/ex02_demo.ino index 272c2e6..db0fba9 100644 --- a/examples/ex02_demo/ex02_demo.ino +++ b/examples/ex02_demo/ex02_demo.ino @@ -88,63 +88,67 @@ void setup(){ // ------------------- 2 - 使用ReadGuy来显示字符串 ------<< - guy.setFont(&FreeMonoBold9pt7b); //设置显示的字体 + guy.setFont(&FreeMonoBold9pt7b); //设置显示的字体. 字体的更多信息可以查阅 Github 上的 Freefont - guy.setTextColor(0,1); //设置显示的颜色. 0代表黑色, 1代表白色 + guy.setTextColor(0,1); //设置显示的颜色. 0代表深色, 1代表白色 + //注意这个函数不是设定显示灰度的函数! 这只是设置像素显示到屏幕缓存里的颜色 + //屏幕缓存对于每个像素点只能保存黑白两种颜色信息. 若是显示到屏幕则为深色和白色 + //所有通过 setDepth 以外的函数设置的颜色都只有深色和白色 + //如果要实现灰度显示, 请参考后面的代码, 会提供相关说明的. 其中设置颜色灰度应当使用 setDepth() 函数 guy.drawString("Hello Readguy!",10,10); //用此函数将字符串显示到屏幕缓存内 - //guy.print("Hello Readguy!"); //使用这个函数也能显示出字符串, 但是需要提前使用setCursor确定显示坐标 + //guy.print("Hello Readguy!"); //使用这个函数也能显示出字符串, 但是需要提前使用setCursor确定显示坐标 - guy.display(READGUY_FAST); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上 - // 但是, 上电初始化之后的首次刷新必为慢速刷新 - //guy.display(READGUY_SLOW); // 慢速刷新. + guy.display(READGUY_FAST); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上 + // 但是, 上电初始化之后的首次刷新必为慢速刷新 + //guy.display(READGUY_SLOW); // 慢速刷新. - guy.setCursor(10,30); //设置显示的坐标 + guy.setCursor(10,30); //设置显示的坐标 - guy.print("Hello~"); //或者用print函数在屏幕上打印字符串, 数值, 字符等等... 两种函数都行 + guy.print("Hello~"); //或者用print函数在屏幕上打印字符串, 数值, 字符等等... 两种函数都行 - guy.display(READGUY_SLOW); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影 + guy.display(READGUY_SLOW); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影 guy.drawString(guy.SDinside()?"SD card OK.":"No SD card!",10,50); //检查readguy是否插入了SD卡 guy.drawString("with " _READGUY_PLATFORM ".",10,70); //检查readguy运行在ESP8266上还是ESP32上. - guy.display(); //不带参数时, 默认使用快速刷新 + guy.display(); //不带参数时, 默认使用快速刷新 - guy.setTextColor(0); //设置显示的颜色. 一个参数时,代表文本背景色为透明; - //两个参数时, 代表文本背景色为后面的那个参数代表的颜色. 此次函数调用之后将显示为透明背景. + guy.setTextColor(0); //设置显示的颜色. 一个参数时,代表文本背景色为透明; + //两个参数时, 代表文本背景色为后面的那个参数代表的颜色. 此次函数调用之后将显示为透明背景. - guy.setCursor(10,80); //设置显示的坐标, 用于显示别的内容 + guy.setCursor(10,80); //设置显示的坐标, 用于显示别的内容 - guy.setTextSize(2); //或者用此函数在屏幕上打印字符串. + guy.setTextSize(2); //或者用此函数在屏幕上打印字符串. - guy.print('a'); //使用print打印字符. + guy.print('a'); //使用print打印字符. - guy.display(); //快刷 + guy.display(); //快刷 - guy.setTextSize(1); //恢复常规大小字符 + guy.setTextSize(1); //恢复常规大小字符 - guy.setFont(&fonts::Font0); //设置小字体 [字体设定部分参考lovyanGFX的示例] + guy.setFont(&fonts::Font0); //设置小字体 [字体设定部分参考lovyanGFX等库的示例, 请查阅这些库的代码] - guy.setCursor(10,110); //设置显示的坐标, 用于显示别的内容 + guy.setCursor(10,110); //设置显示的坐标, 用于显示别的内容 - guy.print(926824162); //使用print打印数字. + guy.print(926824162); //使用print打印数字. (本数字为ReadGuy库的官方QQ交流群号) - guy.print(' '); //使用print打印字符. + guy.print(' '); //使用print打印字符. - guy.print(1.61834,5); //使用print打印浮点数字, 5代表显示5位数 + guy.print(1.61834,5); //使用print打印浮点数字, 5代表显示5位数 - guy.display(); //快刷 + guy.display(); //快刷 - guy.setCursor(10,130); //设置显示的坐标, 用于显示别的内容 + guy.setCursor(10,130); //设置显示的坐标, 用于显示别的内容 guy.printf("Small font using %s. It's a long string in the E-paper. " "Readguy always print it in a new line","printf"); - //使用 print 或者 printf 显示长串字符串时, 后面的字符会自动显示到下一行.而 drawString 不会这样 + //使用 print 或者 printf 显示长串字符串时, 后面的字符会自动显示到下一行.而 drawString 不会这样 guy.display(); //快刷 @@ -154,30 +158,30 @@ void setup(){ guy.fillScreen(1); - guy.display(READGUY_SLOW); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影 + guy.display(READGUY_SLOW); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影 - for(int i=1;i<16;i++){ //灰度测试, 循环设置不同灰度 + for(int i=1;i<16;i++){ //灰度测试, 循环设置不同灰度 - guy.setDepth(i); //设置灰度的颜色深度. 可接受的值为1~15.(从白到黑) - //注意1. 在此函数内参数越接近0越白, 越接近15越黑. 输入参数为0或者>15时则自动设为15(最黑). - //这和其他绘图函数中的 "0黑1白" 表示方法相反. - //注意2. 此函数需要在程序中调用display()函数刷新屏幕之后才会生效, 且此次刷新必须是快刷. - //若该函数在两次display()函数之间被多次调用, 则最终呈现的灰度依据最后一次调用的setDepth()函数. - // OK: guy.setDepth(4); guy.print("hello"); guy.display(); - // guy.setDepth(8); guy.print("world"); guy.display(); - // NOT OK: guy.setDepth(4); guy.print("hello"); - // guy.setDepth(8); guy.print("world"); guy.display(); - // 最终呈现两个print函数等效于只调用一次 guy.setDepth(8); + guy.setDepth(i); //设置灰度的颜色深度. 可接受的值为1~15.(从白到黑) + //注意1. 在此函数内参数越接近0越白, 越接近15越黑. 输入参数为0或者>15时则自动设为15(最黑). + //这和其他绘图函数中的 "0黑1白" 表示方法相反. + //注意2. 此函数需要在程序中调用display()函数刷新屏幕之后才会生效, 且此次刷新必须是快刷. + //若该函数在两次display()函数之间被多次调用, 则最终呈现的灰度依据最后一次调用的setDepth()函数. + // OK: guy.setDepth(4); guy.print("hello"); guy.display(); + // guy.setDepth(8); guy.print("world"); guy.display(); + // NOT OK: guy.setDepth(4); guy.print("hello"); + // guy.setDepth(8); guy.print("world"); guy.display(); + // 最终呈现两个print函数等效于只调用一次 guy.setDepth(8); guy.fillRect(10,i*10,20,10,0); //显示填充矩形. - //注意, 此处最后一个参数0代表是黑色, 黑色的深度则是由前面的setDepth函数设定的. - //即使您希望使用setDepth(1)之后显示一个浅灰色的矩形, 也需要将这个参数设为0(黑色) + //注意, 此处最后一个参数0代表是深色, 深色的深度则是由前面的setDepth函数设定的. + //即使您希望使用setDepth(1)之后显示一个浅灰色的矩形, 也需要将这个参数设为0(深色) - guy.setCursor(32,i*10); + guy.setCursor(32,i*10); //设置文本显示坐标 - guy.printf("Grey%d",i); + guy.printf("Grey%d",i); //使用类似C语言的printf函数来显示带数字的格式化字符串 - guy.display(); //快刷显示. + guy.display(); //快刷显示. } delay(2000); @@ -185,34 +189,34 @@ void setup(){ // --------------------- 4 - 显示BMP格式图片文件 ----<<<< // [此部分更多可以参考lovyanGFX的示例] - guy.fillScreen(1); //清屏 + guy.fillScreen(1); //清屏 - guy.display(); //显示白屏,用于将来显示图片. + guy.display(); //显示白屏,用于将来显示图片. - LGFX_Sprite sp(&guy); //创建一个Sprite (可以存储一些像素, 快速读写) + LGFX_Sprite sp(&guy); //创建一个Sprite (可以存储一些像素, 快速读写) sp.createFromBmpFile(guy.guyFS(),"/test.bmp"); //从文件创建BMP图像信息. //使用guy.guyFS()函数返回可用的文件系统. 默认为SD卡. 当SD卡不可用时, 自动切换到内置的LittleFS. //显示打开的图片信息(宽度和高度), 和剩余内存 - Serial.printf("[%lu] sp.w: %d, h: %d, res: %d.\n",millis(),sp.width(),sp.height(),ESP.getFreeHeap()); + Serial.printf("[%lu] sp.w: %d, h: %d, res: %d.\n",millis(),sp.width(),sp.height(),guy.getFreeMem()); - guy.drawImage(sp,10,10); //使用抖动像素的方式显示图片(不是灰度, 只有黑点和白点的那种显示效果) + guy.drawImage(sp,10,10); //使用抖动像素的方式显示图片(不是灰度, 只有黑点和白点的那种显示效果) - guy.display(); //自从1.2.0更新之后, drawImage不再刷屏, 此处需要额外调用display函数刷屏 + guy.display(); //自从1.2.0更新之后, drawImage不再刷屏, 此处需要额外调用display函数刷屏 Serial.printf("[%lu] drawn dithering bmp.\n",millis()); //显示信息 delay(2000); - guy.setGreyQuality(1); //设置灰度刷新方式. 对于支持连续刷灰度的屏幕才有效. + guy.setGreyQuality(1); //设置灰度刷新方式. 对于支持连续刷灰度的屏幕才有效. // 1(默认)为连续刷新, 0为循环调用 setDepth+display 来刷新 (可能会有白边) //如果连续刷新效果不好, 请将此处改为0再试一次. guy.draw16grey(sp,10,10); //使用16级灰度的方式显示图片 需要的时间比较长 - sp.deleteSprite(); //关闭图片文件, 释放图片占用的大量内存 + sp.deleteSprite(); //关闭图片文件, 释放图片占用的大量内存 Serial.printf("[%lu] drawn 16-layer greyscale bmp.\n",millis()); //显示信息 @@ -221,72 +225,72 @@ void setup(){ // ---------------------- 5 - 其他屏幕功能测试 ---<<<<< guy.setFont(&FreeMonoBold9pt7b); //设置文本字体 - guy.setTextColor(0); //设置文本颜色 - guy.fillScreen(1); //用白色清屏. + guy.setTextColor(0); //设置文本颜色 + guy.fillScreen(1); //用白色清屏. guy.display(READGUY_SLOW); //慢刷. 注意, 进行慢刷操作之后, 所有之前显示的灰度内容均会被重新刷成纯黑色 //不管是浅灰色还是深灰色, 进行慢刷之后只有黑白色. 原来的非白色像素(浅灰色,深灰色和黑色等) 会全刷成白色. guy.drawString("Rotation 0",10,12); //默认旋转方向为0. 实际的默认方向取决于屏幕IC. 大多数屏幕IC是竖屏. drawLines(); //调用 画线函数, 绘制屏幕上的一些线条.并显示. - guy.fillScreen(1); //用白色清屏. + guy.fillScreen(1); //用白色清屏. guy.setRotation(1); //设置旋转方向, 旋转完成之后会对画面进行裁切. - guy.drawString("setRotation 1",12,10); //设置旋转方向 + guy.drawString("Set Rot 1",12,10); //设置旋转方向 drawLines(); - guy.fillScreen(1); //用白色清屏. - guy.setRotation(2); //方向2和方向0 的宽度和高度相同.旋转了180度. - guy.drawString("Rot 2",12,10); //设置旋转方向 + guy.fillScreen(1); //用白色清屏. + guy.setRotation(2); //方向2和方向0 的宽度和高度相同.旋转了180度. + guy.drawString("Rot 2",12,10); //设置旋转方向 drawLines(); - guy.fillScreen(1); //用白色清屏. - guy.setRotation(3); //方向3和方向1 的宽度和高度也是相同的.旋转了180度. - guy.drawString("Rotate 3",15,10); //设置旋转方向 + guy.fillScreen(1); //用白色清屏. + guy.setRotation(3); //方向3和方向1 的宽度和高度也是相同的.旋转了180度. + guy.drawString("Rotate 3",15,10); //设置旋转方向 drawLines(); - guy.drawString("Sleeping...",10,30); //此部分程序演示如何使用屏幕睡眠(降低耗电量) + guy.drawString("Sleeping...",10,30);//此部分程序演示如何使用屏幕睡眠(降低耗电量) - guy.invertDisplay(); //对屏幕上的一切都进行反色处理 + guy.invertDisplay(); //对屏幕上的一切都进行反色处理 - guy.display(); //即将进入睡眠状态 + guy.display(); //即将进入睡眠状态 - guy.sleepEPD(); //进入睡眠模式 + guy.sleepEPD(); //进入睡眠模式. 睡眠模式下再次刷新屏幕必须使用慢速刷新 //注意, 如果没有设置RST引脚, 则实际上不会进入睡眠模式. 睡眠模式依赖RST引脚的复位信号. delay(3000); - guy.setTextColor(1); //设置文本颜色为白色,因为被反色的屏幕的当前像素颜色以黑色像素为主 + guy.setTextColor(1); //设置文本颜色为白色, 因为被反色的屏幕的当前像素颜色以黑色像素为主 guy.drawString("Wake Up! ~\\(^_^)/~",10,50); //退出睡眠状态 - guy.display(READGUY_SLOW); //使用慢刷 来唤醒处于低功耗状态下的屏幕. + guy.display(READGUY_SLOW); //使用全屏慢刷 来唤醒处于低功耗状态下的屏幕. // ------------------ 6 - 可以利用灰度来达到的一些显示效果 --<<<<<< - guy.fillScreen(1); //清屏 + guy.fillScreen(1); //清屏 - guy.display(FILL_WHITE,READGUY_SLOW); //慢刷清屏. 左侧的FILL_WHITE表示 不写入屏幕缓存, 直接刷全白 - //可以改为FILL_BLACK来设置写入缓存全黑. - //以上的方式均不会修改屏幕缓存中的内容. 右侧的false表示全屏慢刷. + guy.display(FILL_WHITE,READGUY_SLOW); //慢刷清屏. 左侧的FILL_WHITE表示 不写入屏幕缓存, 直接刷全白 + //可以改为FILL_BLACK来设置写入缓存全黑. + //以上的两种 guy.display(...) 方法均不会修改屏幕缓存中的内容. - guy.setTextColor(0); //设置显示的颜色. 0代表黑色, 一个参数代表黑白显示. - //注意这个函数不是设定显示灰度的函数! + guy.setTextColor(0); //设置显示的颜色. 0代表深色, 1代表白色. 一个参数代表黑白显示. + //注意这个函数不是设定显示灰度的函数! - guy.setDepth(0); //恢复黑色显示, 此语句等效于setDepth(15) - guy.setFont(&FreeMonoBold9pt7b); //设置文本字体 + guy.setDepth(0); //恢复黑色显示, 此语句等效于setDepth(15) + guy.setFont(&FreeMonoBold9pt7b); //设置文本字体 guy.drawString("I love Readguy!",10,10); //设置显示坐标并显示 - guy.display(); //setDepth()函数需要调用display()函数刷新屏幕之后才会生效 + guy.display(); //setDepth()函数需要调用display()函数刷新屏幕之后才会生效 - guy.setDepth(4); //把灰度深度设为一个浅色, 方便勾勒阴影. + guy.setDepth(4); //把灰度深度设为一个浅色, 方便勾勒阴影. guy.drawString("I love Readguy!",12,12); //错开2像素并减淡颜色灰度,可以实现阴影效果 guy.display(); const char *st="Programmed by"; - guy.setDepth(7); //设置颜色深度 + guy.setDepth(7); //设置颜色深度 guy.drawString(st,11,31); //设置显示坐标 guy.display(); guy.setDepth(4); //这里有一点需要注意: 如果显示的位置原本就已经显示过灰度数据了 - guy.drawString(st,10,31); //(其实这里的像素在内存里存储的还是黑色), 即使新设定的颜色深度比之前的颜色深, + guy.drawString(st,10,31); //(其实这里的像素在内存里存储的还是深色), 即使新设定的颜色深度比之前的颜色深, guy.drawString(st,11,30); //也不会覆盖这里原来的浅灰色. 此处的灰度设定只对原来是纯白色的像素有效. guy.display(); //如果要更改这个像素点的颜色, 只能全部刷白屏之后再重新刷这些灰度颜色. guy.setDepth(15); //(最好是慢刷白屏,如果要坚持使用快刷,也需要把灰度设置为全黑色对应的15再刷) @@ -295,31 +299,31 @@ void setup(){ guy.display(); const char str[]="FriendshipEnder"; //实现类似于渐变色的效果 - guy.setCursor(8,50); + guy.setCursor(8,50); //设置显示字符的基础位置坐标 for(int i=0;i<15;i++){ //在 for 循环内循环修改显示深度 - guy.setDepth(15-i); - guy.print(str[i]); - guy.display(); //注意每次使用 setDepth 之后需要再刷一次屏, 灰度设置才能生效 - } + guy.setDepth(15-i); //设置显示颜色深度 (灰度), 越小越浅(白), 1为最浅, 15为纯黑色 + guy.print(str[i]); //显示单个字符 + guy.display(); //注意每次使用 setDepth 之后需要再刷一次屏, 灰度设置才能生效 + } //设置灰度显示之后必须刷屏才能设置不同的其他灰度, 一次刷出来的只能是一种颜色 - guy.setDepth(6); - guy.drawString("Follow me~~",14,68); + guy.setDepth(6); //设置灰度显示的深度为6 (其实也是很浅的灰色了, 但对于一些屏幕来说这个颜色也很黑了) + guy.drawString("Follow me~~",14,68); //指定坐标显示文本 guy.display(); - guy.setDepth(2); - guy.drawString("Follow me~~",16,70); + guy.setDepth(2); //设置灰度显示的深度为2 (几乎是白色了) + guy.drawString("Follow me~~",16,70); //指定坐标显示文本 guy.display(); - guy.setDepth(5); - guy.setTextColor(0); - guy.drawString("on Bilibili!",14,90); + guy.setDepth(5); //设置灰度显示的深度为5 + guy.setTextColor(0); //设置文本为深色 (所有通过 setDepth 以外的函数设置的颜色都只有深色和白色) + guy.drawString("on Bilibili!",14,90); //指定坐标显示文本 guy.display(); - guy.setDepth(6); //绘制外框 + guy.setDepth(6); //绘制外框, 浅灰色 guy.drawRoundRect(8,86,guy.textWidth("on Bilibili!")+14,28,7,0); guy.display(); - guy.setDepth(15); //恢复到正常黑色 + guy.setDepth(15); //恢复到正常黑色 guy.fillRoundRect(10,88,guy.textWidth("on Bilibili!")+10,24,5,0); guy.setTextColor(1); guy.drawString("on Bilibili!",16,92); @@ -327,30 +331,51 @@ void setup(){ delay(5000); - guy.sleepEPD(); //进入睡眠模式 + // guy.sleepEPD(); //进入睡眠模式, 屏幕不再刷新. + guy.setTextColor(0); //重新将文本颜色恢复到黑色. // ------------------ 7 - 屏幕以外的功能: 比如wifi和按钮 -<<<<<<< - guy.ap_setup(); //启动ESP芯片内置 WiFi 热点, 启动后即可在wifi列表上查看readguy + guy.ap_setup(); //启动ESP芯片内置 WiFi 热点, 启动后即可在wifi列表上查看readguy - guy.server_setup(); //启动服务器功能, 默认地址为192.168.4.1 + guy.server_setup(); //启动服务器功能, 默认地址为192.168.4.1 } -int bright=128; //设置亮度 +int bright=128; //设置亮度 + +int showTextPlace=10; //设置文本显示位置 void loop(){ - guy.server_loop(); //让服务器保持服务 - delay(10); - if(bright%7==0){ - Serial.printf("getBtn: %d\n",guy.getBtn()); //每隔一段时间, 检测一下按键. + + guy.server_loop(); //让服务器保持服务 + + int buttonValue = guy.getBtn(); //程序会自动检测按键. 使用 guy.getBtn() 可以实时查看当前按键的状态. + + if (buttonValue != 0){ //0 代表没有按键被按下 + + guy.fillScreen(1); //白色清屏 + guy.setCursor(4,showTextPlace);//设置显示文本位置. showTextPlace 可以让每次按下按钮刷屏位置不一样. + guy.print("Button: "); //显示先导字符串 + guy.print(buttonValue); //按下不同的按键, 或者不同的按法, 会显示不同的数字. 自己按以下试试吧. + guy.display(); //显示出来 + + if(showTextPlace != 10) + showTextPlace = 10; //在这里显示过文本了, 下一次在另一个地方显示 + else showTextPlace = 20; //没在这里显示过文本, 下一次回到上次的地方显示 + + Serial.printf("Button: %d\n", buttonValue); //在串口上也显示按键按下的内容. } + + //以下的语句用于让背光闪烁呼吸灯. if(bright==511) bright=0; else bright++; guy.setBright(bright>=256?511-bright:bright); + delay(10); } void drawLines(){ //声明一个函数, 用于显示一些线条. 此函数在后面的程序中会用到的 + //具体都有哪些绘图函数可以用, 请参考以下 LovyanGFX 或者 TFT_eSPI 或者 Adafruit_GFX 这几个库的的说明吧 guy.drawFastHLine(0,0,20); //使用 drawFastHLine 绘制横向线段 guy.drawFastVLine(0,0,20); //使用 drawFastVLine 绘制纵向线段 diff --git a/examples/ex04_wifi/2_wifi_config/2_wifi_config.ino b/examples/ex04_wifi/2_wifi_config/2_wifi_config.ino index 34cae14..70ae335 100644 --- a/examples/ex04_wifi/2_wifi_config/2_wifi_config.ino +++ b/examples/ex04_wifi/2_wifi_config/2_wifi_config.ino @@ -43,7 +43,7 @@ #include //arduino功能基础库. 在platformIO平台上此语句不可或缺 #include "readguy.h" //包含readguy_driver 基础驱动库 -#include +#include // settimeofday 函数 需要 ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动. @@ -135,7 +135,7 @@ void setup(){ }while(conf_status!=2); // conf_status==2说明连接上了 - WiFi.mode(WIFI_STA); //从WIFI_AP_STA模式切换到WIFI_STA模式, 不再提供readguy热点. + // WiFi.mode(WIFI_STA); //从WIFI_AP_STA模式切换到WIFI_STA模式, 不再提供readguy热点. guy.println("Getting NTP time..."); //连接成功之后尝试获取NTP时间 Serial.println("[readguy] Getting NTP time..."); //连接成功之后尝试获取NTP时间 @@ -163,6 +163,11 @@ void loop(){ //其中, sv 参数指向了一个服务器类型的变量. 当有来自客户端的请求时, 需要通过sv来发送响应消息. void f1(server_t sv){ //使用PSTR来减少对内存的消耗(不加PSTR()或者F()则字符串会存到.rodata,占用宝贵的内存) + if(WiFi.status() == WL_CONNECTED) { + sv->send_P(200, PSTR("text/html"),PSTR( + "已连接, 不需要再配网了。")); + return; + } String webpage_html = F( "" "" @@ -229,74 +234,72 @@ void f2(server_t sv){ } /*----------------- NTP code ------------------*/ +#define NTP_SERVERS 4 WiFiUDP udp; uint8_t packetBuffer[48]; const int16_t timeZone = 8; //Beijing const int16_t localPort = 1337; -time_t get_ntp_time_impl(uint8_t _server) -{ - const char * ntpServerName[4] = { +time_t getNTPTime(){ + const char * ntpServerName[NTP_SERVERS] = { "ntp1.aliyun.com","time.windows.com","cn.ntp.org.cn","cn.pool.ntp.org" }; - char ntpHost[32]; - IPAddress ntpServerIP; // NTP server's ip address - - while (udp.parsePacket() > 0) ; // discard any previously received packets - Serial.println(F("Transmit NTP Request")); - // get a random server from the pool - strncpy_P(ntpHost,ntpServerName[_server],31); - ntpHost[31] = '\0'; - WiFi.hostByName(ntpHost, ntpServerIP); - Serial.print(FPSTR(ntpServerName[_server])); - Serial.write(':'); - Serial.println(ntpServerIP); - - // set all bytes in the buffer to 0 - memset(packetBuffer, 0, 48); - // Initialize values needed to form NTP request - // (see URL above for details on the packets) - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval - packetBuffer[3] = 0xEC; // Peer Clock Precision - // 8 bytes of zero for Root Delay & Root Dispersion - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; - // all NTP fields have been given values, now - // you can send a packet requesting a timestamp: - udp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123 - udp.write(packetBuffer, 48); - udp.endPacket(); - - uint32_t beginWait = millis(); - while (millis() - beginWait < 1500) { - int size = udp.parsePacket(); - if (size >= 48) { - Serial.println("Receive NTP Response"); - udp.read(packetBuffer, 48); // read packet into the buffer - unsigned long secsSince1900; - // convert four bytes starting at location 40 to a long integer - secsSince1900 = (unsigned long)packetBuffer[40] << 24; - secsSince1900 |= (unsigned long)packetBuffer[41] << 16; - secsSince1900 |= (unsigned long)packetBuffer[42] << 8; - secsSince1900 |= (unsigned long)packetBuffer[43]; - return secsSince1900 - 2208988800UL; // + timeZone * 3600; //时区数据 舍弃即可 - } - } - Serial.println("No NTP Response :-("); - return 0; // return 0 if unable to get the time -} -time_t getNTPTime(){ time_t _now = 0; if(!WiFi.isConnected()) return 0; udp.begin(localPort); Serial.print("Local port: "); Serial.println(localPort); - for(int i=0;i<4;i++){//最多尝试10次对时请求 - _now=get_ntp_time_impl(i); + for(int i=0;i 0) ; // discard any previously received packets + Serial.println(F("Transmit NTP Request")); + // get a random server from the pool + strncpy_P(ntpHost,ntpServerName[i],31); + ntpHost[31] = '\0'; + WiFi.hostByName(ntpHost, ntpServerIP); + Serial.print(FPSTR(ntpServerName[i])); + Serial.write(':'); + Serial.println(ntpServerIP); + + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, 48); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + udp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123 + udp.write(packetBuffer, 48); + udp.endPacket(); + + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = udp.parsePacket(); + if (size >= 48) { + Serial.println("Receive NTP Response"); + udp.read(packetBuffer, 48); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + _now = secsSince1900 - 2208988800UL; // + timeZone * 3600; + break; + } + }// return 0 if unable to get the time if(_now) break; //成功后立即退出 + else Serial.println("No NTP Response :-("); yield(); } if(_now){ diff --git a/examples/ex04_wifi/4_wifi_text_show/4_wifi_text_show.ino b/examples/ex04_wifi/4_wifi_text_show/4_wifi_text_show.ino index 115ad25..d9cd0aa 100644 --- a/examples/ex04_wifi/4_wifi_text_show/4_wifi_text_show.ino +++ b/examples/ex04_wifi/4_wifi_text_show/4_wifi_text_show.ino @@ -103,7 +103,7 @@ void textShow(server_t sv){ //点击链接即可出现发送文本框, 点击发 } void textShowGet(server_t sv){ //注册Web服务函数回调 (就是显示接口) const String ok_str_1=F(""); //网页前半部分 - const String ok_str_2=F("重新传文字"); //网页后半部分 + const String ok_str_2=F("
重新传文字"); //网页后半部分 if(sv->hasArg("txt")){ //检查请求的报文 是否包含键值txt (详见前面的网页声明) String txt=sv->arg("txt"); //找到字段 //-----------------showTextEpd(txt)------------------ //显示到墨水屏幕上 @@ -114,7 +114,7 @@ void textShowGet(server_t sv){ //注册Web服务函数回调 (就是显示接口 Serial.println("Arg width == 0."); //字符串为空 或者总宽度为零 return; } - sv->send(200, String(F("text/html")), ok_str_1+"文字显示完成: "+txt+ok_str_2); //报告显示完成 + sv->send(200, String(F("text/html")), ok_str_1+"文字显示完成:
"+txt+ok_str_2); //报告显示完成 float tsize = ((float)guy.width())/twidth; //计算字体大小, 此大小的目的是填满屏幕 float fsize = ((float)guy.height())/guy.fontHeight(); //计算垂直方向的字体大小, 制定合适的显示方法 if(tsize>fsize){ //字符太短, 字体大小取决于屏幕垂直高度 diff --git a/extra/ctg_timelib/TimeLib.h b/extra/ctg_timelib/TimeLib.h new file mode 100644 index 0000000..40062e5 --- /dev/null +++ b/extra/ctg_timelib/TimeLib.h @@ -0,0 +1,9 @@ +#ifndef _TIMELIB_H_FILE +#define _TIMELIB_H_FILE + +#ifdef __cplusplus +#include "ctg_timelib.hpp" +using namespace CTG_TimeLib; +#endif + +#endif \ No newline at end of file diff --git a/extra/ctg_timelib/ctg_timelib.cpp b/extra/ctg_timelib/ctg_timelib.cpp new file mode 100644 index 0000000..c9ed720 --- /dev/null +++ b/extra/ctg_timelib/ctg_timelib.cpp @@ -0,0 +1,104 @@ +#include "ctg_timelib.hpp" + +namespace CTG_TimeLib { + +time_t tOffset = 0; +struct tm m; + +#if (defined ( CTG_USE_tmElements_t ) || defined (CTG_USE_ALL_TimeLib_features)) + +void setTime(int16_t hr,int16_t min,int16_t sec,int16_t dy, int16_t mnth, int16_t yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + tmElements_t tem; + if( yr >= 1900) yr -= 1970; + else yr += 30; + tem.Year = yr; + tem.Month = mnth; + tem.Day = dy; + tem.Wday = 0; + tem.Hour = hr; + tem.Minute = min; + tem.Second = sec; + setTime(makeTime(tem)); +} + +#define LEAP_YEAR(Y) ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) ) +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + // API starts months from 1, this array starts from 0 + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + monthLength = monthDays[month]; + if(LEAP_YEAR(year) && month==1) monthLength++;// february + if (time >= monthLength) + time -= monthLength; + else break; + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(const tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(86400 * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += 86400; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += 86400 * 29; + } else { + seconds += 86400 * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * 86400; + seconds+= tm.Hour * 3600; + seconds+= tm.Minute * 60; + seconds+= tm.Second; + return (time_t)seconds; +} +#endif // CTG_USE_tmElements_t + +} //namespace \ No newline at end of file diff --git a/extra/ctg_timelib/ctg_timelib.hpp b/extra/ctg_timelib/ctg_timelib.hpp new file mode 100644 index 0000000..ed45553 --- /dev/null +++ b/extra/ctg_timelib/ctg_timelib.hpp @@ -0,0 +1,158 @@ +/** + * @file ctg_timelib.h + * @author FriendshipEnder (Q:3253342798) + * @brief 时间兼容库文件. 旨在替代原有的, 过时的 TimeLib.h 文件(用于AVR设备) + * 本库依赖 GLibC 的 time.h 文件. + * 本库兼容 TimeLib.h 文件 + * 为了确保名称无冲突, 使用名称空间命名避免函数名冲突 + * @version 1.0.0 + * @date 2024-11-16 + * + * @copyright Copyright (c) 2024 + * + */ +#ifndef _CTG_TimeLib_H_FILE +#define _CTG_TimeLib_H_FILE + +#define CTG_DEFAULT_LOCAL_TIMEZONE ("CST-8") //默认时区 +#define CTG_DEFAULT_LOCAL_OFFSET (8*3600) //默认时区偏移值 +#define CTG_USE_tmElements_t //使用 tmElements_t (强兼容, 但是没什么用) + +#include +#include +#include +#include //settimeofday + +namespace CTG_TimeLib { + +extern time_t tOffset; +extern struct tm m; + +/// @brief return the current local time as seconds since Jan 1 1970 +inline time_t now() {return time(NULL)+tOffset;} +/// @brief the hour for the given time +inline int hour(time_t t){gmtime_r(&t, &m);return m.tm_hour;} +/// @brief the hour now +inline int hour(){return hour(now());} +/// @brief the minute for the given time +inline int minute(time_t t){gmtime_r(&t, &m);return m.tm_min;} +/// @brief the minute now +inline int minute(){return minute(now());} +/// @brief the second for the given time +inline int second(time_t t){gmtime_r(&t, &m);return m.tm_sec;} +/// @brief the second now +inline int second(){return second(now());} +/// @brief the day for the given time +inline int day(time_t t){gmtime_r(&t, &m);return m.tm_mday;} +/// @brief the day now +inline int day(){return day(now());} +/// @brief the weekday for the given time (Sunday is day 1) +inline int weekday(time_t t){gmtime_r(&t, &m);return m.tm_wday+1;} +/// @brief the weekday now (Sunday is day 1) +inline int weekday(){return weekday(now());} +/// @brief the month for the given time (Jan is month 1) +inline int month(time_t t){gmtime_r(&t, &m);return m.tm_mon+1;} +/// @brief the month now (Jan is month 1) +inline int month(){return month(now());} +/// @brief the year for the given time +inline int year(time_t t){gmtime_r(&t, &m);return m.tm_year+1900;} +/// @brief the full four digit year: (2009, 2010 etc) +inline int year(){return year(now());} +/// @brief set the time-zone +/// @param z (string) timezone name (like 'CST-8', 'EST+5', etc) +/// @param offset (time_t) how many seconds offset (+8 == 8*3600) +inline void setTimezone(const char *z, time_t offset){ + tOffset = offset; + setenv("TZ", z, 1); //设置时区变量 (当前设置为北京时间) + tzset(); +} + +/// @brief set the time (input UTC time,not local time) +/// @param t (time_t): correct UTC time +/// @param fit_zone_cn (bool): Set timezone to UTC+8 (Beijing) +inline void setUTCTime(time_t t, bool fit_local = true){ + if(tOffset != CTG_DEFAULT_LOCAL_OFFSET && fit_local) + setTimezone(CTG_DEFAULT_LOCAL_TIMEZONE, CTG_DEFAULT_LOCAL_OFFSET); + timeval tm_now={t, 0}; + settimeofday(&tm_now,nullptr);// need #include +} + +/// @brief set the time (input local time) +/// @param t (time_t): correct local time (UTC+8 = Beijing) +inline void setTime(time_t t){ + if(tOffset != CTG_DEFAULT_LOCAL_OFFSET) + setTimezone(CTG_DEFAULT_LOCAL_TIMEZONE, CTG_DEFAULT_LOCAL_OFFSET); + timeval tm_now={t-CTG_DEFAULT_LOCAL_OFFSET, 0}; + settimeofday(&tm_now,nullptr);// need #include +} + +#ifdef CTG_USE_tmElements_t +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + int16_t Year; // offset from 1970; +} tmElements_t; + +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +/* Useful Constants */ +#define SECS_PER_MIN ((time_t)(60UL)) +#define SECS_PER_HOUR ((time_t)(3600UL)) +#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) +#define DAYS_PER_WEEK ((time_t)(7UL)) +#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) +#define SECS_PER_YEAR ((time_t)(SECS_PER_DAY * 365UL)) // TODO: ought to handle leap years +#define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) ((_time_) % SECS_PER_MIN) +#define numberOfMinutes(_time_) (((_time_) / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (((_time_) % SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((((_time_) / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ((_time_) / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) ((_time_) % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before settting alarms +#define previousMidnight(_time_) (((_time_) / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) (previousMidnight(_time_) + SECS_PER_DAY) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY)) // note that week starts on day 1 +#define previousSunday(_time_) ((_time_) - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) (previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/// @brief set the time (input local time) +/// @param hr,min,sec,day,month,yr: correct local time (UTC+8 = Beijing) +void setTime(int16_t hr,int16_t min,int16_t sec,int16_t day,int16_t month,int16_t yr); + +/// @brief break time_t into elements +void breakTime(time_t time, tmElements_t &tm); + +/// @brief convert time elements into time_t +time_t makeTime(const tmElements_t &tm); + +#endif // CTG_USE_tmElements_t +} //namespace +#endif // _CTG_TimeLib_H_FILE \ No newline at end of file diff --git a/library.json b/library.json index c23439a..bba434d 100644 --- a/library.json +++ b/library.json @@ -11,7 +11,7 @@ "type": "git", "url": "https://github.com/fsender/readguy" }, - "version": "1.3.7", + "version": "1.4.0", "frameworks": "arduino", "platforms": ["espressif32", "espressif8266"], "headers": "readguy.h", diff --git a/library.properties b/library.properties index e3f2063..3977b3f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=readguy -version=1.3.7 +version=1.4.0 author=fsender maintainer=fsender sentence=A free E-paper display driver library supports 16-level greyscale. diff --git a/src/guy_button.cpp b/src/guy_button.cpp index c740288..b759786 100644 --- a/src/guy_button.cpp +++ b/src/guy_button.cpp @@ -59,6 +59,7 @@ SOFTWARE. */ #include "guy_button.h" +#include "guy_driver_config.h" // initalize static counter @@ -75,11 +76,11 @@ void guy_button::begin(uint8_t _pin, std_U8_function_U8 f, bool activeLow/*=true prev_state = state ; get_state_cb = f; pin = _pin; - state = get_state_cb(pin); - min_debounce =25; //去抖时间 - long_press_ms =300; //长按持续时间 - double_press_ms =300; //双击识别间隔最大时间 - long_repeat_ms =200; //长按连按间隔时间 + state = get_state_cb(pin, activeLow); + min_debounce =READGUY_DEFAULT_MIN_DEBOUNCE_MS; //去抖时间 + long_press_ms =READGUY_LONG_PRESS_MS; //长按持续时间 + double_press_ms =READGUY_DOUBLE_PRESS_MS; //双击识别间隔最大时间 + long_repeat_ms =READGUY_LONG_REPEAT_MS; //长按连按间隔时间 scanDT =1; // =1识别双击或三击, =0则不识别双击或三击等需要延时返回的情况 lk=0; } @@ -87,7 +88,7 @@ bool guy_button::isPressedRaw() { int mi=millis(); while(lk) if(millis()-mi>GUYBTN_READ_TIMEOUT) return 0; //等待数据读完 lk=3; - bool willreturn = (get_state_cb(pin) == _pressedState); + bool willreturn = (get_state_cb(pin, !_pressedState) == _pressedState); lk=0; return willreturn; } @@ -116,7 +117,7 @@ void guy_button::loop() { lk=1; unsigned long now = millis(); prev_state = state; - state = get_state_cb(pin); + state = get_state_cb(pin, !_pressedState); // is button pressed? if (state == _pressedState) { diff --git a/src/guy_button.h b/src/guy_button.h index 30fbd6d..c103b4d 100644 --- a/src/guy_button.h +++ b/src/guy_button.h @@ -109,7 +109,7 @@ class guy_button{ bool pressed_triggered = false; bool trig_mode = false; //长按连按触发模式 - typedef uint8_t (*std_U8_function_U8)(uint8_t); + typedef uint8_t (*std_U8_function_U8)(uint8_t, bool); std_U8_function_U8 get_state_cb = NULL; public: @@ -118,7 +118,7 @@ class guy_button{ /// @param _pin 引脚ID. 传入的引脚在内部将会使用digitalRead函数实现 /// @param activeLow 设置为true时, 当读取到低电平视为按下 void begin(uint8_t _pin, bool activeLow = true){ - begin(_pin,[](uint8_t p)->uint8_t { return digitalRead(p); },activeLow); + begin(_pin,[](uint8_t p, bool)->uint8_t { return digitalRead(p); },activeLow); } /// @brief 初始化 /// @param _pin 引脚ID. 传入的引脚在内部将会使用digitalRead函数实现 diff --git a/src/guy_driver_config.h b/src/guy_driver_config.h index 24334a7..395a8ac 100644 --- a/src/guy_driver_config.h +++ b/src/guy_driver_config.h @@ -52,9 +52,10 @@ //对于甘草酸不酸的新版本板子: //busy 4 rst 2 dc 0 cs 15 sck / mosi / sdcs 5 btnL rx(3) btnM 0 -//对于四个按钮的4.2屏开发板: +//对于四个按钮的4.2屏开发板: @十里画廊 +//(https://oshwhub.com/shilihualang/han-jia-qian-gai-zhuang-ji-jian-kai-fa-ban) //clk 14 cs 15 mosi 13 dc 27 rst 26 busy 25 -//sd cs 4 miso 12 (共线) 按键 k1 5 k2 17 k3 16 k4 0 前置光 19/18/23 +//sd cs 4 miso 12 (共线) 按键 k1 5 k2 17 k3 16 k4 0 前置光 19/18/23 (RGB) //对于lilygo t-watch epaper 开发板: //cs 5 dc 19 rst 27 busy 38 clk 18 mosi 23 @@ -64,30 +65,65 @@ // ------------------ definations - 定义 - -/// @brief 使用静态的数据 !!!注意:注释此选项编写的程序是不支持跨平台运行的!!! -/// @note 相比于禁用WiFi配网功能, 禁用此功能减少的flash并不多, 为保证程序可在不同屏幕上运行, 请不要注释此选项 -// 关闭此选项自动禁用wifi功能. 如需wifi功能需要自己在程序里加. +/** @brief 使用静态的数据 !!!注意:注释此选项编写的程序是不支持跨平台运行的!!! +/ @note 相比于禁用WiFi配网功能, 禁用此功能减少的flash并不多, 为保证程序可在不同屏幕上运行, 请不要注释此选项 +/ 关闭此选项自动禁用wifi功能. 如需wifi功能需要自己在程序里加. 但也可以大幅减少flash的占用 */ #define DYNAMIC_PIN_SETTINGS -/// @brief 启用WIFI配网功能.必须先启用 #define DYNAMIC_PIN_SETTINGS. 此选项对 ESP32xx 会减少大量flash. +/// @brief 启用WIFI配网功能.必须先启用 #define DYNAMIC_PIN_SETTINGS. 此选项对 ESP32xx 会减少大量可用flash. #define READGUY_ENABLE_WIFI -/// @brief 启用I2C功能. 可用于联网时钟, 温度计, 陀螺仪等外设. 目前暂不支持库内使用类似函数. 仅可以提供引脚定义 -//#define READGUY_ENABLE_I2C -/// @note 现在库不提供任何I2C驱动, 只提供引脚定义的存储和读取, 这几乎不增加多少代码. 因此本宏不再使用 +/** @brief 启用I2C功能. 可用于联网时钟, 温度计, 陀螺仪等外设. 目前暂不支持库内使用类似函数. 仅可以提供引脚定义 +/ @note 现在库提供了获取已存的I2C引脚的接口, 使用时请使用 getI2cSda() 和 getI2cScl() 函数获取I2C的引脚. +/ 本库内不提供任何I2C驱动, 只提供引脚定义的存储和读取, 这几乎不增加多少代码. 因此本宏不再使用 */ +//#define READGUY_ENABLE_I2C /** @brief 启用SD卡功能. 开启此功能将会使用内置SD卡管理功能. 关闭后仅可保存SD卡用到的引脚. - @note 会破坏兼容性. 若没有启用通用的SD卡驱动程序, 那么那些跨屏台编译的程序将无法用guyFS读取到SD卡. - 若用户程序希望能从外部加载SD卡, 可以使用getSdMiso()等函数获取SD卡的Miso等引脚, 再由用户程序初始化SD卡. */ +/ @note 会破坏兼容性. 若没有启用通用的SD卡驱动程序, 那么那些跨屏台编译的程序将无法用guyFS读取到SD卡. +/ 若用户程序希望能从外部加载SD卡, 可以使用getSdMiso()等函数获取SD卡的Miso等引脚, 再由用户程序初始化SD卡. */ #define READGUY_ENABLE_SD /// @brief 使用LittleFS作为片上文件系统, 注释此行则用SPIFFS(功能少, 不好用) #define READGUY_USE_LITTLEFS 1 -/// @brief 使用esp8266时, EEPROM(类似NVS)的存储位置起点 (从起点开始的40字节被readguy所使用) 可选值: 0~4045 -/// @note 对于单一项目来说, 此选项不建议更改, 请在项目初期就确定此变量的值. +/** @brief 使用esp8266时, EEPROM(类似NVS)的存储位置起点 (从起点开始的40字节被readguy所使用) 可选值: 0~4045 +/ @note 对于单一项目来说, 此选项不建议更改, 请在项目初期就确定此变量的值. */ #define READGUY_ESP8266_EEPROM_OFFSET 2 +/** @brief (实验性) 允许 EPD_DC 引脚复用作按键引脚. 当DC引脚直连主控且直连按键时 (按下按键后DC引脚电平被锁定) +/ 启用此项可以允许这样的按键复用 (如用于早期版本甘草酸不酸2.9阅读器板子) 但复用的引脚作为按钮长按时体验会变差 +/ @note 即使注释此选项依旧可以允许DC引脚复用, 但需要在主控直连屏幕DC引脚, 并在按键一端和主控引脚之间接入一个 +/ 220Ω~1kΩ的电阻 (推荐470Ω), 按键另一端接地, 这样才可以. 注释该选项后, 即使按键和DC引脚复用了, 也不会影响功 +/ 能和长按体验. 注意如果按键有内阻(不按时按键两端电阻为∞, 按下后按键电阻较高) 可以酌情减小一些按键接入电阻. +/ 对于新版的甘草酸不酸2.9阅读器板子就可以注释此选项. +/ 没有需要兼容「复用DC引脚的硬件」的需求的用户, 建议注释此项. 一般仅建议ESP8266启用此选项*/ +//#define READGUY_ALLOW_DC_AS_BUTTON + +/** @brief (实验性) 允许复用 EPD 的 CS 引脚作为按钮. 这会让读取按钮电平的速度些许减慢. +/ @note 需要在按键一端和主控引脚之间接入一个220Ω~1kΩ的电阻 (推荐470Ω), 并要求EPD的CS引脚直连主控对应引脚. */ +//#define READGUY_ALLOW_EPDCS_AS_BUTTON + +/** @brief (不建议, 实验性) 允许复用 SD 卡的 CS 引脚作为按钮. +/ @details 这不仅会减慢读按钮速度, 而且十分不建议对「确定」按钮 (通常为按钮2) 启用此功能函数. 因为一些程序会 +/ 在你按下「确定」后立即读取 SD 卡 (与按键循环读取功能冲突) 导致总线紊乱! 在ESP32上会引起无法预知的崩溃复位! +/ @attention 启用后每次读写SD卡时,都必须调用 setSDbusy(1) 来锁定引脚, 屏蔽按键读取, 调用 setSDbusy(0)解锁. +/ @note 需要在按键一端和主控引脚之间接入一个220Ω~1kΩ的电阻 (推荐470Ω), 并要求SD卡的CS引脚直连主控对应引脚. +/ 所有示例程序未针对此选项重写, 建议重新规划硬件再使用示例. 若硬件无法更改, 请手动对需要用 ReadGuy 库未提供的 +/ SD卡IO操作的部分, 手动添加 setSDbusy() 函数. 由 ReadGuy 提供的函数已适配好了, 如ReadGuy::Screenshot() */ +//#define READGUY_ALLOW_SDCS_AS_BUTTON + +/// @brief 按键去抖时间 +#define READGUY_DEFAULT_MIN_DEBOUNCE_MS 25 + +/// @brief 按键长按持续时间 +#define READGUY_LONG_PRESS_MS 300 + +/// @brief 按键双击识别间隔最大时间 +#define READGUY_DOUBLE_PRESS_MS 300 + +/// @brief 按键长按连按间隔时间 +#define READGUY_LONG_REPEAT_MS 200 + /// @brief ESP32按键服务任务的栈空间大小, 不建议普通用户更改. 默认值1024字节. 小于此大小会使程序栈溢出. #ifdef CONFIG_IDF_TARGET_ESP32S3 #define BTN_LOOPTASK_STACK 1536 @@ -114,7 +150,10 @@ #define ESP32_SD_SPI_FREQUENCY 20000000 /// @brief debug专用, 请保持处于注释状态. 正常开机从NVS读取引脚配置数据, 取消注释则每次开机需要重新配置 -//#define INDEV_DEBUG 1 +//#define READGUY_INDEV_DEBUG 1 + +/// @brief 串口显示刷屏功能等的信息. 建议开启, 如果是对flash要求十分敏感 可以关闭 +#define READGUY_SERIAL_DEBUG #ifndef DYNAMIC_PIN_SETTINGS #ifdef ESP8266 @@ -136,12 +175,23 @@ #define READGUY_i2c_scl -1 // 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效 //按键驱动部分, 为负代表高触发, 否则低触发, //注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发 -#define READGUY_btn1 ( 5+1) //按键1,注意需要+1,这里示例已经加了 设置为负的来允许高电平触发 +#define READGUY_btn1 ( 5+1) //按键1,注意需要+1,这里示例已经加了 设置为负的来允许高电平触发 如 (-(5+1)) #define READGUY_btn2 (12+1) //按键2,注意需要+1 #define READGUY_btn3 ( 2+1) //按键3,注意需要+1 #define READGUY_bl_pin 4 //前置光接口引脚IO -#else //对于非ESP8266 +#define READGUY_user1 -1 //useless +#define READGUY_user2 -1 //useless +#define READGUY_user3 -1 //useless +#define READGUY_user4 -1 //useless +#define READGUY_user5 -1 //useless +#define READGUY_user6 -1 //useless +#define READGUY_user7 -1 //useless +#define READGUY_user8 -1 //useless +#define READGUY_user9 -1 //useless +#define READGUY_user10 -1 //useless + +#else //对于非ESP8266 (如 ESP32xx) #define READGUY_shareSpi 0 //EPD和SD卡是否共享SPI, 此处不共享 #define READGUY_epd_type READGUY_DEV_154B // 对应的epd驱动程序代号, -1为未指定 @@ -167,9 +217,20 @@ #define READGUY_btn2 -(32+1) //按键2,注意需要+1,此处前面的负号表示允许高电平触发 #define READGUY_btn3 0 //按键3,注意需要+1, 不用的按钮应当设置为0 #define READGUY_bl_pin 12 //前置光接口引脚IO + +#define READGUY_user1 -1 //useful with ESP32S3 SDMMC sd mode DAT2 pin +#define READGUY_user2 -1 //useful with ESP32S3 SDMMC sd mode DAT3 pin +#define READGUY_user3 -1 //useless +#define READGUY_user4 -1 //useless +#define READGUY_user5 -1 //useless +#define READGUY_user6 -1 //useless +#define READGUY_user7 -1 //useless +#define READGUY_user8 -1 //useless +#define READGUY_user9 -1 //useless +#define READGUY_user10 -1 //useless #endif -#define READGUY_rtc_type 0 //使用的RTC型号(待定, 还没用上) +#define READGUY_rtc_type 0 //使用的RTC型号. 现已弃用 RTC 功能. 保留是为了兼容性 让代码更简单维护 #elif defined(READGUY_ENABLE_WIFI) #define READGUY_ESP_ENABLE_WIFI //使用WIFI进行配网等功能 #endif diff --git a/src/guy_epaper/guy_154b_270b_290b/guy_154b_270b_290b.cpp b/src/guy_epaper/guy_154b_270b_290b/guy_154b_270b_290b.cpp index ac89aa6..8c464f7 100644 --- a/src/guy_epaper/guy_154b_270b_290b/guy_154b_270b_290b.cpp +++ b/src/guy_epaper/guy_154b_270b_290b/guy_154b_270b_290b.cpp @@ -145,7 +145,7 @@ void drvSSD168x::drv_init(){ } void drvSSD168x::drv_fullpart(bool part){ //切换慢刷/快刷功能 if(lastRefresh) return; - if(!epd_PowerOn) part=0; //未上电 无法局刷 + //if(!epd_PowerOn) part=0; //未上电 无法局刷 if(!part) { iLut=15; greyScaling=0; } _part=part; } diff --git a/src/guy_epaper/guy_epaper_config.h b/src/guy_epaper/guy_epaper_config.h index 84f1e8c..a492ae4 100644 --- a/src/guy_epaper/guy_epaper_config.h +++ b/src/guy_epaper/guy_epaper_config.h @@ -59,7 +59,7 @@ //#define READGUY_DEV_154C 14 //(即将推出) 1.54寸M09墨水屏 (M5Stack Core-Ink 同款; GDEW0154M09) //#define READGUY_DEV_370B 15 //(即将推出) 3.7寸低DPI墨水屏, 分辨率416*240, b站 @叫我武哒哒 的项目用 //#define READGUY_DEV_426A 16 //(即将推出) 4.26寸高分辨率墨水屏, 800*480. GDEQ0426T82 支持硬件四灰 -#define READGUY_DEV_583A 17 //(即将推出) 5.83寸墨水屏幕, 分辨率为600*448. 有黑白有三色 +#define READGUY_DEV_583A 17 // 5.83寸墨水屏幕, 分辨率为600*448. 有黑白有三色 //#define READGUY_DEV_583B 18 //(即将推出) 5.83寸高分辨率, 640*480. GDEQ0583T31 只有黑白 //#define READGUY_DEV_750A 19 //(即将推出) 7.5 寸墨水屏幕, 800*480. 只有三色(买不到黑白) //#define READGUY_DEV_1020A 20 //(即将推出) 10.2寸墨水屏GDEQ102T90, 芯片SSD1677. 黑白色分辨率960*640 @@ -68,7 +68,7 @@ #define EPD_DRIVERS_NUM_MAX 21 //此选项请不要取消注释掉, 有几个屏幕就写多少. -#define READGUY_583A_DUAL_BUFFER //对于单缓存的5.83屏幕, 启用双缓存支持 +#define READGUY_583A_DUAL_BUFFER //对于单缓存的5.83屏幕,启用双缓存支持. 相当不建议注释掉,否则不能刷白色 #endif /* END OF FILE. ReadGuy project. Copyright (C) 2023 FriendshipEnder. */ \ No newline at end of file diff --git a/src/guy_epaper/lcdDebug/ctg_stack_c_defines.h b/src/guy_epaper/lcdDebug/ctg_stack_c_defines.h index bb598b6..b52410e 100644 --- a/src/guy_epaper/lcdDebug/ctg_stack_c_defines.h +++ b/src/guy_epaper/lcdDebug/ctg_stack_c_defines.h @@ -73,7 +73,11 @@ //#define _DEFINA_SD_CS_PIN 0 // * for NodeMcu ctg stack LCF board -#define WHITE_GAP 2 +#define WHITE_GAP 2 //白边像素数 +#define SIMULATE_BLINK 1 //额外的闪屏次数 +#define SIMULATE_SLOW_REFRESH_DELAY 250 //慢刷延时 +#define SIMULATE_FAST_REFRESH_DELAY 50 //快刷延时 +#define SIMULATE_GREYSCALE_COLOUR //仿真灰度时, 读取屏幕像素决定色彩 (更逼真但更慢) #ifdef ESP8266 #define DISPLAY_TYPE_ST7789_240320 //2.0寸的ST7789 IPS TFT模块 diff --git a/src/guy_epaper/lcdDebug/lcdDebug.cpp b/src/guy_epaper/lcdDebug/lcdDebug.cpp index 0d2fa84..c9631d0 100644 --- a/src/guy_epaper/lcdDebug/lcdDebug.cpp +++ b/src/guy_epaper/lcdDebug/lcdDebug.cpp @@ -45,25 +45,42 @@ void drv::drv_dispWriter(std::function f,uint8_t m){ //单色刷 if(!(m&1)) return; //stage 1 uint16_t dat[8]; unsigned short xbits=(drv_width()+7)/8; + ips.startWrite(); if(partMode==0){ ips.invertDisplay(1); - DelayMs(250); + DelayMs(SIMULATE_SLOW_REFRESH_DELAY); +#if ((SIMULATE_BLINK) > 1) ips.invertDisplay(0); - DelayMs(250); + DelayMs(SIMULATE_SLOW_REFRESH_DELAY); ips.invertDisplay(1); - DelayMs(250); + DelayMs(SIMULATE_SLOW_REFRESH_DELAY); +#endif +#if ((SIMULATE_BLINK) > 0) + ips.invertDisplay(0); + DelayMs(SIMULATE_SLOW_REFRESH_DELAY); + ips.invertDisplay(1); + DelayMs(SIMULATE_SLOW_REFRESH_DELAY); +#endif ips.invertDisplay(0); } for(int j=0;j f,uint8_t m){ //单色刷 } for(int k=0;k<8;k++){ if(i==xbits-1 && i*8+k>=drv_width()) break; +#ifdef SIMULATE_GREYSCALE_COLOUR if((readf&(0x80>>k))) ips.drawPixel(WHITE_GAP+i*8+k,WHITE_GAP+j,0xffff); else if((dat[k]&0x1f)==0x1f) ips.drawPixel(WHITE_GAP+i*8+k,WHITE_GAP+j,0x1082*(15-depth)); +#else + if(!(readf&(0x80>>k))) { + ips.drawPixel(WHITE_GAP+i*8+k,WHITE_GAP+j,0x1082*(15-depth)); + } +#endif } } } yield(); } - delay(50); + ips.endWrite(); + DelayMs(SIMULATE_FAST_REFRESH_DELAY); } void drv::drv_sleep() {} } diff --git a/src/guy_version.h b/src/guy_version.h index 15b39fb..296c7e7 100644 --- a/src/guy_version.h +++ b/src/guy_version.h @@ -40,10 +40,10 @@ //务必保证这些版本号是一致的. //另外, 在提交新版本之前, 不要忘记在github上创建release, 否则Arduino IDE会读不到 #define READGUY_V_MAJOR 1 -#define READGUY_V_MINOR 3 -#define READGUY_V_PATCH 7 +#define READGUY_V_MINOR 4 +#define READGUY_V_PATCH 0 #define READGUY_VERSION_VAL (READGUY_V_MAJOR*1000+READGUY_V_MINOR*100+READGUY_V_PATCH*10) -#define READGUY_VERSION "1.3.7" +#define READGUY_VERSION "1.4.0" #ifdef ESP8266 #define _READGUY_PLATFORM "ESP8266" @@ -61,8 +61,8 @@ #endif #endif -#define _GITHUB_LINK "github.com/fsender/readguy" -#define _BILIBILI_LINK "www.bilibili.com/video/BV1f94y187wz" +#define _GITHUB_LINK "https://github.com/fsender/readguy" +#define _BILIBILI_LINK "https://www.bilibili.com/video/BV1f94y187wz" #endif /* END OF FILE. ReadGuy project. diff --git a/src/guy_wireless.cpp b/src/guy_wireless.cpp index 55dade9..496fe3f 100644 --- a/src/guy_wireless.cpp +++ b/src/guy_wireless.cpp @@ -28,11 +28,20 @@ */ #include "readguy.h" +/* #if (!defined(ESP8266)) //for ESP32, ESP32S2, ESP32S3, ESP32C3 +#include "esp_flash.h" +#endif */ #ifdef READGUY_ESP_ENABLE_WIFI static const PROGMEM char NOT_SUPPORTED[] = "(不支持此屏幕)"; static const PROGMEM char TEXT_HTML[] = "text/html"; static const PROGMEM char TEXT_PLAIN [] = "text/plain"; +static const PROGMEM char text_http_methods[8][8]={ + "UPLOAD", "404", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" +}; +static const HTTPMethod val_http_methods[6]={ + HTTP_HEAD, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS +}; static const PROGMEM char args_name[24][8]={ "share","epdtype","EpdMOSI","EpdSCLK","Epd_CS","Epd_DC","Epd_RST","EpdBusy", "SD_MISO","SD_MOSI","SD_SCLK","SD_CS","I2C_SDA","I2C_SCL", @@ -187,7 +196,9 @@ void ReadguyDriver::ap_setup(const char *ssid, const char *pass, int m){ IPAddress subnet(255,255,255,0); WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAP(ssid,pass); +#ifdef READGUY_SERIAL_DEBUG Serial.printf_P(PSTR("[Guy AP] ap_setup SSID: %s, Pass: %s\n"),ssid,pass); +#endif } void ReadguyDriver::server_setup(const String ¬ify, const serveFunc *serveFuncs, int funcs){ //启动WiFi服务器端, 这样就可以进行配网工作 @@ -198,6 +209,7 @@ void ReadguyDriver::server_setup(const String ¬ify, const serveFunc *serveFun sv.on("/pinsetup", HTTP_GET, std::bind(&ReadguyDriver::handlePinSetup ,this)); sv.on("/final", HTTP_POST, std::bind(&ReadguyDriver::handleFinalPost,this)); //此时验证已经正确 //sv.on("/wifi", HTTP_GET, std::bind(&ReadguyDriver::handleWiFi ,this)); //此时验证已经正确 + sv.onNotFound(std::bind(&ReadguyDriver::handleNotFound,this)); //处理404的情况 guy_notify=notify; sfuncs=funcs; //设置服务函数列表 if(sfnames!=nullptr) { delete [] sfnames; delete [] sfevents; } //严防内存泄露 @@ -205,7 +217,14 @@ void ReadguyDriver::server_setup(const String ¬ify, const serveFunc *serveFun sfnames=new String[sfuncs]; sfevents=new String[sfuncs]; for(int i=0;i1) sv.on(serveFuncs[i].event,val_http_methods[spec-2],std::bind(serveFuncs[i].func,&sv)); + else if(spec==1) sv.onNotFound(std::bind(serveFuncs[i].func,&sv)); //404 + else if(spec==0) sv.onFileUpload(std::bind(serveFuncs[i].func,&sv)); //文件上传 + else sv.on(serveFuncs[i].event,HTTP_GET,std::bind(serveFuncs[i].func,&sv)); sfnames[i] = serveFuncs[i].linkname; sfevents[i] = serveFuncs[i].event; } @@ -220,13 +239,14 @@ void ReadguyDriver::server_setup(const String ¬ify, const serveFunc *serveFun "Connection: close\r\n\r\n"),89); sv.client().write_P((const char *)faviconData,sizeof(faviconData)); });*/ - sv.onNotFound(std::bind(&ReadguyDriver::handleNotFound,this)); //处理404的情况 sv.begin(); MDNS.begin("readguy"); //MDNS.addService("http","tcp",80); +#ifdef READGUY_SERIAL_DEBUG Serial.print(F("[Guy server] Done! visit ")); if(WiFi.getMode() == WIFI_AP) Serial.println(F("192.168.4.1")); else Serial.println(WiFi.localIP()); +#endif } bool ReadguyDriver::server_loop(){ //此时等待网页操作完成响应... sv.handleClient(); @@ -251,10 +271,16 @@ bool ReadguyDriver::server_loop(){ //此时等待网页操作完成响应... refFlag=3; } if(refFlag!=127) { - Serial.printf("randch: %d %c\n",randomch[refFlag],(char)(randomch[refFlag])); +#ifdef READGUY_SERIAL_DEBUG + Serial.printf_P(PSTR("randch: %d %c\n"),randomch[refFlag],(char)(randomch[refFlag])); +#endif drawChar((guy_dev->drv_width()>>1)-46+refFlag*24,(guy_dev->drv_height()>>1)-14,randomch[refFlag],true,false,4); + refresh_begin(0); guy_dev->drv_fullpart(1); guy_dev->_display((const uint8_t*)getBuffer()); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif } } delay(1); //防止触发看门狗 @@ -279,7 +305,9 @@ void ReadguyDriver::handleInitPost(){ // 此时返回一个文本输入框, 定位到 handleFinalPost 函数 uint8_t btn_count_=0; if(READGUY_cali){ //再次初始化已经初始化的东西, 此时需要关闭一些外设什么的 +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy Pin] Reconfig pins and hardwares...")); +#endif READGUY_cali=0; READGUY_sd_ok=0; #if defined(ESP8266) @@ -301,13 +329,19 @@ void ReadguyDriver::handleInitPost(){ } config_data[0]=1; //默认只要运行到此处, 就已经初始化好了的 for(int i=0;i<33;i++){ +#ifdef READGUY_SERIAL_DEBUG Serial.print(F("Argument ")); +#endif String a_name = String(FPSTR(args_name[23])) + (i-22); if(i<=22) a_name = FPSTR(args_name[i]); +#ifdef READGUY_SERIAL_DEBUG Serial.print(a_name); Serial.write(':'); +#endif if(sv.hasArg(a_name)) { +#ifdef READGUY_SERIAL_DEBUG Serial.println(sv.arg(a_name)); +#endif if(i<14){ //这12个引脚是不可以重复的, 如果有重复, config_data[0]设为0 config_data[i+1] = sv.arg(FPSTR(args_name[i])).toInt(); } @@ -319,13 +353,15 @@ void ReadguyDriver::handleInitPost(){ else if(i==19&&btn_count_>2) config_data[17]=sv.arg(FPSTR(args_name[19])).toInt()+1; else if(i==20&&btn_count_>2) config_data[17]=-config_data[17]; else if(i==21) config_data[18] = sv.arg(FPSTR(args_name[21])).toInt(); - else if(i==22) config_data[19] = sv.arg(FPSTR(args_name[22])).toInt(); //保留RTC功能 + else if(i==22) config_data[19] = sv.arg(FPSTR(args_name[22])).toInt(); //现已弃用 RTC 功能. else if(i>22){ //用户数据 config_data[i-1] = sv.arg(a_name).toInt(); } } else { +#ifdef READGUY_SERIAL_DEBUG Serial.write('\n'); +#endif if(i==0) READGUY_shareSpi = 0; //有的html响应是没有的.共享SPI默认值为true. else if(i<14) config_data[i+1] = -1;//这12个引脚是不可以重复的, 如果有重复, config_data[0]设为0 } @@ -350,7 +386,9 @@ void ReadguyDriver::handleInitPost(){ uint8_t ck=checkEpdDriver(); if(btn_count_<2) config_data[16]=0; if(btn_count_<3) config_data[17]=0; +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy Pin] Config OK. Now init devices.")); +#endif if(ck>=125) { const char *pNotify[3]={ PSTR("Necessary pin NOT connected."),\ PSTR("Pin conflicted."),PSTR("Display not supported.") }; @@ -374,12 +412,16 @@ void ReadguyDriver::handleInitPost(){ randomch[1] = 48+((rdm>>12)%10);//R2CHAR((rdm>>12)%10); randomch[2] = 48+((rdm>> 6)%10);//R2CHAR((rdm>> 6)%10); randomch[3] = 48+((rdm )%10);//R2CHAR((rdm )%10); +#ifdef READGUY_SERIAL_DEBUG Serial.print(F("[Guy] rand string: ")); for(int i=0;i<4;i++) Serial.write(randomch[i]); Serial.write('\n'); Serial.println(F("[Guy] Init EPD...")); //此时引脚io数据已经录入, 如果没有问题, 此处屏幕应当可以显示 +#endif setEpdDriver(); //尝试初始化屏幕 +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy] Init details...")); +#endif setTextSize(1); drawCenterString(setSDcardDriver()?"SD Init OK!":"SD Init failed!", guy_dev->drv_width()>>1,(guy_dev->drv_height()>>1)+20); @@ -389,11 +431,15 @@ void ReadguyDriver::handleInitPost(){ drawRect((guy_dev->drv_width()>>1)-46+24,(guy_dev->drv_height()>>1)-14,20,28,0); drawRect((guy_dev->drv_width()>>1)-46+48,(guy_dev->drv_height()>>1)-14,20,28,0); drawRect((guy_dev->drv_width()>>1)-46+72,(guy_dev->drv_height()>>1)-14,20,28,0); - spibz++; + refresh_begin(0); guy_dev->drv_fullpart(1); guy_dev->_display((const uint8_t*)getBuffer()); - spibz--; +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy] Display done!")); +#endif READGUY_cali=1; //显示初始化完成 } void ReadguyDriver::handlePinSetup(){ @@ -408,7 +454,7 @@ void ReadguyDriver::handlePinSetup(){ args_name[15],args_name[17],args_name[19],args_name[21],args_name[12],args_name[13] }; static const PROGMEM int dem_args_val[DRIVER_TEMPLATE_N][DRIVER_TEMPLATE_ARRAY_L]={ - { 6,15, 0, 2, 4, 5, 2, 0, 3,-1,-1,13,14}, + { 6,15, 0, 2, 4, 5, 2, 3, 0,-1,-1,13,14}, //{ 0,10, 9, 8, 7, 4, 3, 2, 3, 5, 6,SDA,SCL}, //{ 0,15, 4, 2, 5,-1, 1, 0,-1,-1,-1,-1,-1} //微雪官方例程板子不支持SD卡, 也不支持I2C. 按钮为boot按钮 }; @@ -593,7 +639,12 @@ void ReadguyDriver::handleFinal(){ s += F("
"); if(sfuncs>0){ s+=F("以下链接来自应用程序
"); //换行 - for(int i=0;i"); @@ -635,7 +686,7 @@ void ReadguyDriver::handleFinal(){ #endif #endif s+=F("当前WiFi模式: "); - s+=(WiFi.getMode()==WIFI_STA)?F("正常联网模式"):F("AP配网模式"); + s+=(WiFi.getMode()==WIFI_AP_STA)?F("热点联网模式"):((WiFi.getMode()==WIFI_STA)?F("正常联网模式"):F("热点配置模式")); s+=F(", IP地址: "); s+=WiFi.localIP().toString(); s+=F("
芯片型号: "); @@ -682,7 +733,9 @@ void ReadguyDriver::handleFinal(){ //s+=F("
"); //换行 sv.send_P(200, TEXT_HTML, (s+FPSTR(end_html)).c_str()); if(READGUY_cali == 63){ +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy NVS] Data saved to NVS.")); +#endif READGUY_cali = 127; nvs_init(); nvs_write(); diff --git a/src/readguy.cpp b/src/readguy.cpp index 107b9e5..eeeaaf5 100644 --- a/src/readguy.cpp +++ b/src/readguy.cpp @@ -28,12 +28,26 @@ */ #include "readguy.h" -#if (!defined(INPUT_PULLDOWN)) +/* #if (!defined(ESP8266)) //for ESP32, ESP32S2, ESP32S3, ESP32C3 +#include "esp32-hal.h" +#endif */ +#if (!defined(INPUT_PULLDOWN)) //not supported pinMode #define INPUT_PULLDOWN INPUT #endif guy_button ReadguyDriver::btn_rd[3]; -int8_t ReadguyDriver::pin_cmx=-1; +int8_t ReadguyDriver::pin_cmx=-1; +#ifdef READGUY_ALLOW_DC_AS_BUTTON +bool ReadguyDriver::refresh_state=1; //1: free; 0: busy(refreshing) +uint8_t ReadguyDriver::refresh_press=0x7f; //0x7f: no button pressed other:button pressed pin +#endif +#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON +uint8_t ReadguyDriver::static_epd_cs=0x7f; +#endif +#ifdef READGUY_ALLOW_SDCS_AS_BUTTON +uint8_t ReadguyDriver::static_sd_cs=0x7f; +volatile uint8_t ReadguyDriver::sd_cs_busy=0; +#endif const PROGMEM char ReadguyDriver::projname[8] = "readguy"; const PROGMEM char ReadguyDriver::tagname[7] = "hwconf"; @@ -63,7 +77,7 @@ const int8_t ReadguyDriver::config_data[32] = { READGUY_btn2 , READGUY_btn3 , READGUY_bl_pin ,//前置光接口引脚IO - READGUY_rtc_type ,//使用的RTC型号(待定, 还没用上) + READGUY_rtc_type ,//使用的RTC型号. 现已弃用 RTC 功能. 保留是为了兼容性 让代码更简单维护 0 ,//READGUY_sd_ok SD卡已经成功初始化 0 ,//READGUY_buttons 按钮个数, 0-3都有可能 -1, //用户自定义变量 同时用于esp32s3使用SDIO卡数据的DAT1 为-1代表不使用SDIO @@ -79,16 +93,18 @@ TaskHandle_t ReadguyDriver::btn_handle; ReadguyDriver::ReadguyDriver(){ READGUY_cali = 0; // config_data[0] 的初始值为0 +#ifdef DYNAMIC_PIN_SETTINGS for(unsigned int i=1;i=2) WiFi.begin(); //连接到上次存储在flash NVS中的WiFi. - else if(WiFiSet==1) ap_setup(); + else +#endif + if(WiFiSet==1) ap_setup(); if(checkEpdDriver()!=127) setEpdDriver(initepd/* ,g_width,g_height */); //初始化屏幕 else for(;;); //此处可能添加程序rollback等功能操作(比如返回加载上一个程序) if(initSD) setSDcardDriver(); @@ -119,6 +147,7 @@ uint8_t ReadguyDriver::init(uint8_t WiFiSet, bool initepd, bool initSD){ #endif nvs_deinit(); #else + (void)WiFiSet; //avoid warning nvs_init(); if(checkEpdDriver()!=127) setEpdDriver(initepd/* ,g_width,g_height */); //初始化屏幕 else for(;;); //此处可能添加程序rollback等功能操作(比如返回加载上一个程序) @@ -129,7 +158,9 @@ uint8_t ReadguyDriver::init(uint8_t WiFiSet, bool initepd, bool initSD){ } nvs_deinit(); #endif +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy init] init done.")); +#endif READGUY_cali=127; return READGUY_sd_ok; } @@ -140,7 +171,9 @@ uint8_t ReadguyDriver::checkEpdDriver(){ #else #define TEST_ONLY_VALUE 3 #endif +#ifdef READGUY_SERIAL_DEBUG Serial.printf_P(PSTR("[Guy SPI] shareSpi? %d\n"),READGUY_shareSpi); +#endif for(int i=TEST_ONLY_VALUE;i<8;i++){ if(i<7 && config_data[i]<0) return 125;//必要的引脚没连接 for(int j=1;j<=8-i;j++) @@ -218,7 +251,9 @@ uint8_t ReadguyDriver::checkEpdDriver(){ #endif //添加新屏幕型号 add displays here default: - Serial.println(F("[GUY ERR] EPD DRIVER IC NOT SUPPORTED!\n")); +#ifdef READGUY_SERIAL_DEBUG + Serial.println(F("[Guy Error] EPD DRIVER IC NOT SUPPORTED!\n")); +#endif return 127; } #else @@ -285,7 +320,9 @@ uint8_t ReadguyDriver::checkEpdDriver(){ epd_spi->begin(READGUY_epd_sclk,READGUY_shareSpi?READGUY_sd_miso:-1,READGUY_epd_mosi); guy_dev->IfInit(*epd_spi, READGUY_epd_cs, READGUY_epd_dc, READGUY_epd_rst, READGUY_epd_busy); #endif +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy SPI] drvBase Init OK")); +#endif return READGUY_epd_type; } void ReadguyDriver::setEpdDriver(bool initepd, bool initGFX){ @@ -311,7 +348,9 @@ void ReadguyDriver::setEpdDriver(bool initepd, bool initGFX){ setTextColor(0); fillScreen(1); //开始先全屏白色 } +#ifdef READGUY_SERIAL_DEBUG Serial.printf_P(PSTR("[Guy EPD] EPD init OK(%d): w: %d, h: %d\n"),guy_dev->drv_ID(),guy_dev->drv_width(),guy_dev->drv_height()); +#endif } #ifdef READGUY_ENABLE_SD bool ReadguyDriver::setSDcardDriver(){ @@ -326,8 +365,11 @@ bool ReadguyDriver::setSDcardDriver(){ #endif && READGUY_sd_cs!=READGUY_epd_cs && READGUY_sd_cs!=READGUY_epd_dc && READGUY_sd_cs!=READGUY_epd_rst && READGUY_sd_cs!=READGUY_epd_busy ){ //SD卡的CS检测程序和按键检测程序冲突, 故删掉 (可能引发引脚无冲突但是显示冲突的bug) -#if defined(ESP8266) - //Esp8266无视SPI的设定, 固定为唯一的硬件SPI (D5=SCK, D6=MISO, D7=MOSI) +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON)) + setSDbusy(1); +#endif +#if defined(ESP8266) //Esp8266无视SPI的设定, 固定为唯一的硬件SPI (D5=SCK, D6=MISO, D7=MOSI) + SDFS.end(); SDFS.setConfig(SDFSConfig(READGUY_sd_cs)); READGUY_sd_ok = SDFS.begin(); #else @@ -340,11 +382,16 @@ bool ReadguyDriver::setSDcardDriver(){ sd_spi->begin(READGUY_sd_sclk,READGUY_sd_miso,READGUY_sd_mosi); //初始化SPI } READGUY_sd_ok = SD.begin(READGUY_sd_cs,*sd_spi,ESP32_SD_SPI_FREQUENCY); //初始化频率为20MHz +#endif +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON)) + setSDbusy(0); #endif } else READGUY_sd_ok=0; //引脚不符合规则,或冲突或不可用 if(!READGUY_sd_ok){ +#ifdef READGUY_SERIAL_DEBUG Serial.println(F("[Guy SD] SD Init Failed!")); +#endif //guyFS().begin(); //初始化内部FS #ifdef READGUY_USE_LITTLEFS LittleFS.begin(); @@ -367,7 +414,7 @@ bool ReadguyDriver::setSDcardDriver(){ #endif void ReadguyDriver::setButtonDriver(){ if(READGUY_btn1) { //初始化按键. 注意高电平触发的引脚在初始化时要设置为下拉 - int8_t btn_pin=abs(READGUY_btn1)-1; + int8_t btn_pin=abs((int)READGUY_btn1)-1; #if defined(ESP8266) //只有ESP8266是支持16引脚pulldown功能的, 而不支持pullup if(btn_pin == 16) pinMode(16,(READGUY_btn1>0)?INPUT:INPUT_PULLDOWN_16); else if(btn_pin < 16 && btn_pin != D5 && btn_pin != D6 && btn_pin != D7) @@ -404,6 +451,16 @@ void ReadguyDriver::setButtonDriver(){ || abs(READGUY_btn3)-1==READGUY_epd_dc ) { pin_cmx=READGUY_epd_dc; //DC引脚复用 } +#if ((defined (READGUY_ALLOW_EPDCS_AS_BUTTON)) || ((defined(READGUY_ALLOW_SDCS_AS_BUTTON)) && (defined(READGUY_ENABLE_SD)))) + for(int j=15;j<=17;j++){ //btn1~3 +#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON + if(READGUY_epd_cs == abs(config_data[j])-1) static_epd_cs = READGUY_epd_cs; +#endif +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON) && defined(READGUY_ENABLE_SD)) + if(READGUY_sd_cs == abs(config_data[j])-1 && READGUY_sd_cs!=-1) static_sd_cs = READGUY_sd_cs; +#endif + } +#endif //初始化按钮, 原计划要使用Button2库, 后来发现实在是太浪费内存, 于是决定自己写 if(READGUY_btn1) btn_rd[0].begin(abs(READGUY_btn1)-1,rd_btn_f,(READGUY_btn1>0)); if(READGUY_btn2) btn_rd[1].begin(abs(READGUY_btn2)-1,rd_btn_f,(READGUY_btn2>0)); @@ -519,56 +576,105 @@ void ReadguyDriver::display(uint8_t part){ //......可惜'dynamic_cast' not permitted with -fno-rtti // static bool _part = 0; 记忆上次到底是full还是part, 注意启动时默认为full if(READGUY_cali==127){ + part = refresh_begin(part); //in_press(); //暂停, 然后读取按键状态 spibz guy_dev->drv_fullpart(part&1); guy_dev->_display((const uint8_t*)getBuffer(),((part>>1)?part>>1:3)); //in_release(); //恢复 +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif } } void ReadguyDriver::displayBuffer(const uint8_t *buf, uint8_t part){ if(READGUY_cali==127){ + part = refresh_begin(part); //in_press(); //暂停, 然后读取按键状态 spibz guy_dev->drv_fullpart(part&1); + epdPartRefresh++; guy_dev->_display(buf,((part>>1)?part>>1:3)); //in_release(); //恢复 +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif } } void ReadguyDriver::display(std::function f, uint8_t part){ if(READGUY_cali==127){ + part = refresh_begin(part); //in_press(); //暂停, 然后读取按键状态 spibz guy_dev->drv_fullpart(part&1); guy_dev->drv_dispWriter(f,((part>>1)?part>>1:3)); //in_release(); //恢复 +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif } } void ReadguyDriver::drawImage(LGFX_Sprite &base, LGFX_Sprite &spr,int32_t x,int32_t y,int32_t zoomw, int32_t zoomh) { - if(READGUY_cali==127) guy_dev->drv_drawImage(base, spr, x, y, 0, zoomw, zoomh); + if(READGUY_cali==127) { + refresh_begin(0); + guy_dev->drv_drawImage(base, spr, x, y, 0, zoomw, zoomh); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif + } } void ReadguyDriver::drawImageStage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,int32_t x,int32_t y,uint8_t stage, uint8_t totalstage,int32_t zoomw,int32_t zoomh) { if(READGUY_cali!=127 || stage>=totalstage) return; - //Serial.printf("stage: %d/%d\n",stage+1,totalstage); +#ifdef READGUY_SERIAL_DEBUG + Serial.printf_P(PSTR("[Guy Draw] stage: %d/%d\n"),stage+1,totalstage); +#endif + refresh_begin(0); guy_dev->drv_drawImage(sprbase, spr, x, y, (totalstage<=1)?0:(stage==0?1:(stage==(totalstage-1)?3:2)),zoomw,zoomh); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif } void ReadguyDriver::setDepth(uint8_t d){ - if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling()) guy_dev->drv_setDepth(d); + if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling()) { +// refresh_begin(0); + if(d==0 || d>15) { d=15; } //invalid. set to default value 15. + guy_dev->drv_setDepth(d); + current_depth = d; +//#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) +// refresh_end(); +//#endif + } } void ReadguyDriver::draw16grey(LGFX_Sprite &spr,int32_t x,int32_t y,int32_t zoomw,int32_t zoomh){ if(READGUY_cali!=127) return; + refresh_begin(0); if(guy_dev->drv_supportGreyscaling() && (spr.getColorDepth()&0xff)>1) return guy_dev->drv_draw16grey(*this,spr,x,y,zoomw,zoomh); guy_dev->drv_drawImage(*this, spr, x, y, 0, zoomw, zoomh); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + refresh_end(); +#endif } void ReadguyDriver::draw16greyStep(int step){ if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling() && step>0 && step<16 ){ - if(step==1) guy_dev->drv_fullpart(1); + if(step==1) { + refresh_begin(0); + guy_dev->drv_fullpart(1); + } guy_dev->drv_draw16grey_step((const uint8_t *)this->getBuffer(),step); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + if(step>=15) refresh_end(); +#endif } } void ReadguyDriver::draw16greyStep(std::function f, int step){ if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling() && step>0 && step<16 ){ - if(step==1) guy_dev->drv_fullpart(1); + if(step==1) { + refresh_begin(0); + guy_dev->drv_fullpart(1); + } guy_dev->drv_draw16grey_step(f,step); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + if(step>=15) refresh_end(); +#endif } } void ReadguyDriver::invertDisplay(){ @@ -579,10 +685,100 @@ void ReadguyDriver::invertDisplay(){ } } void ReadguyDriver::sleepEPD(){ - if(READGUY_cali==127) guy_dev->drv_sleep(); + if(READGUY_cali==127) { + if(READGUY_bl_pin>=0) digitalWrite(READGUY_bl_pin, LOW); //关闭背光灯, 节省电量 + guy_dev->drv_sleep(); + } } - -#if (defined(INDEV_DEBUG)) +uint8_t ReadguyDriver::refresh_begin(uint8_t freshType){ +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + if(refresh_state){ + refresh_state=0; + refresh_press = 0x7f; //clear state + for(uint8_t i=0;i<3;i++){ //修复老旧硬件 按键和DC引脚冲突 + bool flag = 0; + if(config_data[15+i] == 0) break; + int ipin = abs((int)config_data[15+i])-1; + if(ipin == (int)READGUY_epd_dc){ //旨在解决部分老旧硬件共用DC引脚 + while(btn_rd[i].isPressedRaw() == ((int)config_data[15+i] > 0)) { //这里需要按下的状态 + flag = 1; + yield(); + } + if(flag) { + #ifdef READGUY_SERIAL_DEBUG + Serial.printf_P(PSTR("[Guy Pin] refresh_begin pin %d mode output\n"), READGUY_epd_dc); + #endif //等待恢复到按键空闲电平 btn>0 代表低电平按下, 此时右边为1 btn<0 代表高电平按下 + delay(btn_rd[i].min_debounce); //去抖动 +//#ifdef ESP8266 +// btnTask.detach();//暂时关闭任务, 避免引脚被设置为非法模式 +//#else +// vTaskSuspend(btn_handle);//暂时关闭任务, 避免引脚被设置为非法模式 +//#endif + spibz|=0x40; //set bit at 0x40 + refresh_press = i; + //pinMode((uint8_t)READGUY_epd_dc, OUTPUT); + } + } + } + } +#endif + if((freshType&1)==1){ //隶属于快刷的范畴, 计数+1 + if(epdPartRefresh >= epdForceFull) { + epdPartRefresh = 0; + return (freshType & 0xfe); + } + else { + if(!epdPartRefresh) guy_dev->drv_setDepth(current_depth); //慢刷完成后的首次快刷, 保存上次的刷新颜色深度 + if(epdPartRefresh<0x7ffe) epdPartRefresh++; + } + } + else epdPartRefresh = 0; //全刷, 重置计数器 + return freshType; +} +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) +void ReadguyDriver::refresh_end(){ +#ifdef READGUY_ALLOW_DC_AS_BUTTON + //for(uint8_t i=15;i<=17;i++){ //修复老旧硬件 按键和DC引脚冲突 + // if(config_data[i] == 0) break; + // int ipin = abs((int)config_data[i])-1; + if(refresh_press != 0x7f && !refresh_state){ //旨在解决部分老旧硬件共用DC引脚的bug +#ifdef READGUY_SERIAL_DEBUG + Serial.printf_P(PSTR("[Guy Pin] refresh_end pin %d\n"), READGUY_epd_dc); +#endif //pinMode((uint8_t)READGUY_epd_dc, INPUT_PULLUP); + spibz&=0x3f; //reset bit at 0x40 +//#ifdef ESP8266 +// btnTask.attach_ms(BTN_LOOPTASK_DELAY,looptask); +//#else +// vTaskResume(btn_handle); +//#endif //开启任务后, 延时确保按键任务可以活跃而不是一直处于被暂停又刷屏的无限循环 + delay((BTN_LOOPTASK_DELAY+btn_rd[refresh_press].min_debounce)*2); + } + refresh_state = 1; +#endif +} +#endif +void ReadguyDriver::setAutoFullRefresh(int16_t frames){ + //epdPartRefresh = frames<0?-frames:0; + if(frames<0) epdPartRefresh = -frames; + if(frames) { + epdForceFull = (frames<0?-frames:frames); + } + else { + epdForceFull = 0x7fff; + } +} +#ifdef ESP8266 +void ReadguyDriver::recoverI2C(){ + if(READGUY_cali!=127) return; + for(int i=13;i<=14;i++){ // READGUY_i2c_scl == config_data[14]; READGUY_i2c_sda == config_data[13]; + if(config_data[i] == 12 || config_data[i] == 13 || config_data[i] == 14) pinMode(config_data[i], SPECIAL); + for(int j=15;j<=17;j++){ // READGUY_btn1 == config_data[15]; b2 == config_data[15]; b3== config_data[17]; + if(config_data[i] == abs((int)config_data[j])-1) pinMode(config_data[i], config_data[j]>0?INPUT_PULLUP:INPUT_PULLDOWN); + } + } +} +#endif +#if (defined(READGUY_INDEV_DEBUG)) void ReadguyDriver::nvs_init(){ } void ReadguyDriver::nvs_deinit(){ @@ -614,7 +810,9 @@ bool ReadguyDriver::nvs_read(){ #endif s[i]=(char)rd; } - Serial.printf("[Guy NVS] Get EEPROM...%d\n", config_data[0]); +#ifdef READGUY_SERIAL_DEBUG + Serial.printf_P(PSTR("[Guy NVS] ReadGuy Ver " READGUY_VERSION " on " _READGUY_PLATFORM " Get EEPROM...%d\n"), config_data[0]); +#endif return !(strcmp_P(s,projname)); } void ReadguyDriver::nvs_write(){ @@ -725,19 +923,109 @@ void ReadguyDriver::looptask(){ //均为类内静态数据 btn_rd[1].loop(); btn_rd[2].loop(); } +bool ReadguyDriver::screenshot(const char *filename){ +#ifdef READGUY_ENABLE_SD + if(!SDinside(false)) return 0; + uint16_t ibytes = ((width()+31)>>3)&0x7ffcu; //必须是4的倍数 + uint16_t bmpHeader[] = { //0x3e == 62 字节 + 0x4d42, //[ 0]字符BM, 固定文件头 + 0x0f7e,0,0,0, //[ 1]File Size 文件总大小 文件大小0x0f7e == 3966; 位图数据大小32*122 == 3904 + 0x003e,0, //[ 5]Bitmap Data Offset (BDO) 信息头到数据段的字节数 (索引到0x3e后的第一个数据就是位图字节数据) + 0x0028,0, //[ 7]Bitmap Header Size (BHS) 信息头长度 + 0x00fa,0, //[ 9]宽度 0xfa == 250 + 0x007a,0, //[11]高度 0x7a == 122 + 0x01, //[13]Planes 位面数(图层数) 锁定为1 + 0x01, //[14]Bits Per Pixel (BPP) 每像素位数 (1bit/pixel) + 0,0, //[15]Compression 压缩方法 压缩说明:0-无压缩 + 0x0f40,0, //[17]Bitmap Data Size 位图数据的字节数。该数必须是4的倍数 0x0f40 == 3904 == 32*122 + 0x0ec4,0, //[19]Horizontal Resolution 水平分辨率 0x0ec4==3780 (pixel/metre)==96.012 (pixel/inch) + 0x0ec4,0, //[21]Vertical Resolution 水平分辨率 0x0ec4==3780 (pixel/metre)==96.012 (pixel/inch) + 0,0, //[23]Colours 位图使用的所有颜色数。为0表示使用 2^比特数 种颜色 如8-比特/像素表示为100h或者 256. + 0,0, //[25]Important Colours 指定重要的色彩数。当该域的值等于色彩数或者等于0时,表示所有色彩都一样重要 + 0,0, //[27]重要颜色 0: 黑色 (#000000) R0 G0 B0 X0 + 0xffff,0xff //[29]重要颜色 1: 白色 (#ffffff) R255 G255 B255 X0 + }; +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON)) + setSDbusy(1); +#endif + File bmpf = guyFS().open(filename,"w"); + if(!bmpf) { +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON)) + setSDbusy(0); +#endif + return 0; + } + uint32_t datsz = ibytes * height(); + bmpHeader[ 1] = (datsz + sizeof(bmpHeader)) & 0xffffu; + bmpHeader[ 2] = (datsz + sizeof(bmpHeader)) >> 16; + bmpHeader[ 9] = width(); + bmpHeader[11] = height(); + bmpHeader[17] = datsz & 0xffffu; + bmpHeader[18] = datsz >> 16; + bmpf.write((uint8_t*)bmpHeader,sizeof(bmpHeader)); + uint8_t *byteWrite = new uint8_t [ibytes]; + for(int h=bmpHeader[11]-1;h>=0;h--){ //bmpHeader[11] == tft->height() + for(uint16_t i=0;iwidth()+7)>>3 + for(uint16_t w=0;w<=bmpHeader[9];w++){ //bmpHeader[ 9] == tft->width() + byteWrite[w>>3] |= !!(readPixel(w,h))<<((w&7)^7); + } + bmpf.write(byteWrite,ibytes); + yield(); + } + bmpf.close(); +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON)) + setSDbusy(0); +#endif + delete [] byteWrite; //释放内存 + //bmpf = guyFS().open(filename,"r"); + //if(!bmpf || bmpf.size()!=datsz + sizeof(bmpHeader) || bmpf.peek() != 0x42) return 0; //检查 + //bmpf.close(); + return 1; +#else + return 0; +#endif +} -uint8_t ReadguyDriver::rd_btn_f(uint8_t btn){ +uint8_t ReadguyDriver::rd_btn_f(uint8_t btn, bool activeLow){ static uint8_t lstate=0; //上次从dc引脚读到的电平 #ifdef ESP8266 - if(btn==ReadguyDriver::pin_cmx && spibz) return lstate; - if(btn==D5||btn==D6||btn==D7||btn==ReadguyDriver::pin_cmx) - pinMode(btn,INPUT_PULLUP);//针对那些复用引脚做出的优化 + if(btn==ReadguyDriver::pin_cmx && ReadguyDriver::spibz) return lstate; +#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON + static uint8_t epdcsstate=0; //上次从epd_cs引脚读到的电平 + if(btn==ReadguyDriver::static_epd_cs && ReadguyDriver::spibz) return epdcsstate; +#endif +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON) && defined(READGUY_ENABLE_SD)) + static uint8_t sdcsstate=0; //上次从sd_cs引脚读到的电平 + if(btn==ReadguyDriver::static_sd_cs && ReadguyDriver::sd_cs_busy) return sdcsstate; +#endif + if(btn==D5||btn==D6||btn==D7||btn==ReadguyDriver::pin_cmx +#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON + || btn==ReadguyDriver::static_epd_cs +#endif +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON) && defined(READGUY_ENABLE_SD)) + || btn==ReadguyDriver::static_sd_cs +#endif + ) + pinMode(btn,activeLow?INPUT_PULLUP:INPUT_PULLDOWN);//针对那些复用引脚做出的优化 uint8_t readb = digitalRead(btn); - if(btn==ReadguyDriver::pin_cmx) { + if(btn==ReadguyDriver::pin_cmx +#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON + || btn==ReadguyDriver::static_epd_cs +#endif +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON) && defined(READGUY_ENABLE_SD)) + || btn==ReadguyDriver::static_sd_cs +#endif + ) { //Serial.printf("rd D1.. %d\n",spibz); pinMode(btn,OUTPUT); //如果有复用引脚, 它们的一部分默认需要保持输出状态, 比如复用的DC引脚 digitalWrite(btn,HIGH); //这些引脚的默认电平都是高电平 - lstate = readb; + if(btn==ReadguyDriver::pin_cmx) lstate = readb; +#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON + else if(btn==ReadguyDriver::static_epd_cs) epdcsstate = readb; +#endif +#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON) && defined(READGUY_ENABLE_SD)) + else if(btn==ReadguyDriver::static_sd_cs) sdcsstate = readb; +#endif } else if(btn==D5||btn==D6||btn==D7) pinMode(btn,SPECIAL); //针对SPI引脚进行专门的优化 return readb; @@ -745,7 +1033,7 @@ uint8_t ReadguyDriver::rd_btn_f(uint8_t btn){ if(btn!=ReadguyDriver::pin_cmx) return digitalRead(btn); if(spibz) return lstate; - pinMode(btn,INPUT_PULLUP); + pinMode(btn,activeLow?INPUT_PULLUP:INPUT_PULLDOWN); uint8_t readb = digitalRead(btn); pinMode(btn,OUTPUT); digitalWrite(btn,HIGH); diff --git a/src/readguy.h b/src/readguy.h index e6a686d..b6abe78 100644 --- a/src/readguy.h +++ b/src/readguy.h @@ -151,7 +151,7 @@ #define READGUY_btn2 (config_data[16]) #define READGUY_btn3 (config_data[17]) #define READGUY_bl_pin (config_data[18])//前置光接口引脚IO -#define READGUY_rtc_type (config_data[19])//使用的RTC型号(待定, 还没用上) +#define READGUY_rtc_type (config_data[19])//现已弃用 RTC 功能. 保留是为了兼容性 让代码更简单维护 #define READGUY_sd_ok (config_data[20]) //SD卡已经成功初始化 #define READGUY_buttons (config_data[21]) //按钮个数, 0-3都有可能 @@ -235,6 +235,8 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类 void drawImage(LGFX_Sprite &base,LGFX_Sprite &spr,int32_t x,int32_t y,int32_t zoomw=0,int32_t zoomh=0); /// @brief 设置显示对比度(灰度) void setDepth(uint8_t d); + /// @brief 返回上次设置的显示对比度(灰度) + int16_t getDepth() const {return current_depth;} /** @brief 返回目标屏幕是否支持16级灰度 返回非0代表支持. * @note 返回负整数则代表调用draw16greyStep需要从深色到浅色刷新, 而不是从浅色到深色刷新 */ int supportGreyscaling() const{return READGUY_cali==127?guy_dev->drv_supportGreyscaling():0;} @@ -249,16 +251,21 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类 * @param step 步骤代号. 从1开始到15,依次调用此函数来自定义的灰度显示显存内容. 没有0和16. * @note 必须按照 "慢刷全屏->绘图->设置参数1->绘图->设置参数2... 调用15次 来完成一次自定义灰度刷屏 * 连续调用多次此函数之间, 可以修改显存内的像素颜色, 但只能从白色改为黑色. - * @attention 需要先调用 supportGreyscaling() 来确定是否支持灰度分步刷新.为负数时需要从深到浅刷新. 参见示例. - */ + * @attention 先调用 supportGreyscaling() 来确定是否支持灰度分步刷新. 为负时需要从深到浅刷新. 参见示例. + * 方法较为复杂用法参见示例. */ void draw16greyStep(int step); /** @brief 分步刷新显示灰度, 详见 display(f,part) 和 draw16grey(spr,x,y) 的注释. - * @note 此函数不读取显存,而是通过调用该函数来确定像素颜色. */ + * @note 此函数不读取显存,而是通过调用该函数来确定像素颜色, 不建议新手使用该函数. */ void draw16greyStep(std::function f, int step); /// @brief 对缓冲区内所有像素进行反色.只对现在的缓冲区有效,之后的颜色该怎样就怎样. 灰度信息会被扔掉. void invertDisplay(); /// @brief 进入EPD的低功耗模式 void sleepEPD(void); + /// @brief 设置自动全刷慢刷(连续快刷一定次数之后自动调用慢刷, 保护屏幕 + /// @param frames 连续快刷多少次之后自动慢刷, 传入0来禁用自动慢刷 + void setAutoFullRefresh(int16_t frames); + /// @brief 截屏并保存到SD卡上. 不支持灰度. 当SD卡不可用或者截屏失败时返回false + bool screenshot(const char *filename); #ifdef READGUY_ESP_ENABLE_WIFI /// @brief ap配网设置页面 typedef struct { @@ -286,9 +293,10 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类 } serveFunc; /// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接 void ap_setup(){} - void ap_setup(const char *ssid, const char *pass, int m=2){} + void ap_setup(const char *ssid, const char *pass, int m=2){(void)ssid; (void)pass; (void)m;} //avoid warning /// @brief 初始化服务器模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接 - void server_setup(const String ¬ify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0){} + void server_setup(const String ¬ify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0) + {(void)notify;(void)serveFuncs;(void)funcs;} //avoid warning bool server_loop(){ return true; } void server_end(){} #endif @@ -306,16 +314,48 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类 /** @brief 检查SD卡是否插入 * @param check 为true时, 如果SD卡不可用则初始化SD卡. 为false时, 当SD卡不可用则返回LittleFS. */ bool SDinside(bool check=true) { return check?setSDcardDriver():READGUY_sd_ok; }; + void SDdeinit() { READGUY_sd_ok = 0; }; //当SD卡被检测到拔出或不可用时, 调用此函数标记 /// @brief 检查按钮. 当配置未完成时,按钮不可用, 返回0. uint8_t getBtn() { return (READGUY_cali==127)?getBtn_impl():0; } /// @brief [此函数已弃用 非常不建议使用] 根据按钮ID来检查按钮. 注意这里如果按下返回0, 没按下或者按钮无效返回1 //uint8_t getBtn(int btnID){return btnIDbeginTransaction(SPISettings(ESP32_DISP_FREQUENCY, MSBFIRST, SPI_MODE0)); + if(!spibz) +#endif +#ifdef ESP8266 + SPI.beginTransaction(SPISettings(ESP8266_SPI_FREQUENCY, MSBFIRST, SPI_MODE0)); +#else + epd_spi->beginTransaction(SPISettings(ESP32_DISP_FREQUENCY, MSBFIRST, SPI_MODE0)); #endif spibz ++; } static void in_release(){//SPI结束传输屏幕数据 spibz --; -#ifdef ESP8266 - if(!spibz) SPI.endTransaction(); +#if (defined(READGUY_ALLOW_DC_AS_BUTTON)) + if(!(spibz&0x3f)) #else - if(!spibz) epd_spi->endTransaction(); + if(!spibz) +#endif +#ifdef ESP8266 + SPI.endTransaction(); +#else + epd_spi->endTransaction(); #endif } public: //增加了一些返回系统状态变量的函数, 它们是静态的, 而且不会对程序造成任何影响. @@ -433,11 +499,14 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类 constexpr int getI2cScl () const { return READGUY_i2c_scl; }// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效 //按键驱动部分, 为负代表高触发, 否则低触发, //注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发 - constexpr int getBtn1Pin () const { return READGUY_btn1; } - constexpr int getBtn2Pin () const { return READGUY_btn2; } - constexpr int getBtn3Pin () const { return READGUY_btn3; } + constexpr int getBtn1Pin () const { return abs((int)READGUY_btn1)-1; } + constexpr int getBtn2Pin () const { return abs((int)READGUY_btn2)-1; } + constexpr int getBtn3Pin () const { return abs((int)READGUY_btn3)-1; } + constexpr int getBtn1Info() const { return READGUY_btn1; } + constexpr int getBtn2Info() const { return READGUY_btn2; } + constexpr int getBtn3Info() const { return READGUY_btn3; } constexpr int getBlPin () const { return READGUY_bl_pin; } //前置光接口引脚IO - constexpr int getRtcType () const { return READGUY_rtc_type; } //使用的RTC型号(待定, 还没用上) + constexpr int getRtcType () const { return READGUY_rtc_type; } //现已弃用 RTC 功能. 保留是为了兼容性 让代码更简单维护 constexpr int getButtonsCount() const { return READGUY_buttons; } //按钮个数, 0-3都有可能 constexpr int getReadguy_user1 () const { return READGUY_user1; } //用户变量 constexpr int getReadguy_user2 () const { return READGUY_user2; } //用户变量