6 Commits
1.3.7 ... 1.4.2

Author SHA1 Message Date
fsender
20ba78ea52 1.4.2: fix build for arduino-esp32 3.x 2025-03-11 18:36:59 +08:00
fsender
247e5e0b94 update api for arduino-esp32 3.x 2025-03-08 05:14:09 +08:00
fsender
02174694d2 fix: can't run when disable DYNAMIC_PIN_SETTINGS 2024-12-02 22:00:54 +08:00
fsender
6fe0269ccf update changelog 2024-12-01 19:59:50 +08:00
fsender
f289938a65 Bump to 1.4.0 with many new feats 2024-12-01 19:57:36 +08:00
fsender
a69456a09c fix: uninitialized variable & HTML errors 2024-10-28 06:30:05 +08:00
25 changed files with 2097 additions and 1044 deletions

View File

@@ -1,3 +1,60 @@
1.4.2
## Release 1.4.2 - 2025/3/11
1. 现在程序可以在Arduino-ESP32的版本高于3.0.0的Arduino集成环境和ESP-IDF (仍需依赖Arduino component, 但不依赖PlatformIO) 中编译成功
2. 示例程序中添加了更多的说明文本
## Release 1.4.1 - 2024/12/2
1. 紧急修复了在禁用 `DYNAMIC_PIN_SETTINGS` 之后点不亮屏幕的bug
## Release 1.4.0 - 2024/12/1
### 新增内容
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卡用的)

46
CMakeLists.txt Normal file
View File

@@ -0,0 +1,46 @@
# Esp-idf component requires this file.
# Even Arduino IDE will ignore it.
file(GLOB READGUY_SRCS
src/*.cpp
src/guy_epaper/*.cpp
src/guy_epaper/guy_154a_290a/*.cpp
src/guy_epaper/guy_154b_270b_290b/*.cpp
src/guy_epaper/guy_154C/*.cpp
src/guy_epaper/guy_213a/*.cpp
src/guy_epaper/guy_213b_266a/*.cpp
src/guy_epaper/guy_370a/*.cpp
src/guy_epaper/guy_370B/*.cpp
src/guy_epaper/guy_420a/*.cpp
src/guy_epaper/guy_420b/*.cpp
src/guy_epaper/guy_426A/*.cpp
src/guy_epaper/guy_583A/*.cpp
src/guy_epaper/guy_583B/*.cpp
src/guy_epaper/guy_750A/*.cpp
src/guy_epaper/guy_1020A/*.cpp
src/guy_epaper/lcdDebug/*.cpp
)
set(READGUY_DIRS
src
src/guy_epaper
src/guy_epaper/guy_154a_290a
src/guy_epaper/guy_154b_270b_290b
src/guy_epaper/guy_154C
src/guy_epaper/guy_213a
src/guy_epaper/guy_213b_266a
src/guy_epaper/guy_370a
src/guy_epaper/guy_370B
src/guy_epaper/guy_420a
src/guy_epaper/guy_420b
src/guy_epaper/guy_426A
src/guy_epaper/guy_583A
src/guy_epaper/guy_583B
src/guy_epaper/guy_750A
src/guy_epaper/guy_1020A
src/guy_epaper/lcdDebug
../arduino-esp32/libraries/HTTPUpdateServer/src
)
idf_component_register(SRCS ${READGUY_SRCS}
INCLUDE_DIRS "." ${READGUY_DIRS}
REQUIRES arduino-esp32 LovyanGFX spi_flash
)
#spi flash for read chip

View File

@@ -4,19 +4,17 @@
<img src="extra/artset/readguy_theme3.png" width="30%" height="auto">
**版本1.3.7正式发布欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~
**版本1.4.2正式发布欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~
**即将发布7个全新的屏幕驱动: 欢迎支持! (详见后面的驱动表格)**
**发布好的全新驱动程序版本号将会是2.0.0!**
欢迎克隆, 项目交流QQ群: 926824162 (萌新可以进来问问题的哟), 项目的 Bilibili 主页: [BV1f94y187wz](https://www.bilibili.com/video/BV1f94y187wz/) 记得三连+关注我这个宝藏up主哦~
注意, 有问题一定要先加群问, 先不要提issue, 提了小影 *(也就是作者FriendshipEnder)* 也不会看的.
注意, 有问题一定要先加群问, 先不要提issue, 提了作者 FriendshipEnder 也不会看的.
**依赖的库: [LovyanGFX](https://github.com/lovyan03/LovyanGFX)**
*依赖的环境: Arduino-ESP8266 或者 Arduino-ESP32.*
*依赖的环境: Arduino-ESP8266 或者 Arduino-ESP32. 现已支持使用最新版本的Arduino-ESP32库(2.x和3.x)编译!*
## 在**所有**受支持屏幕上都显示**16级灰度**内容!
@@ -113,6 +111,24 @@ Light-brightness-control is optional.
8. sd卡固件更新/切换
---
## 配置方法-使用网页配网配置引脚
1. 首次烧录完成后, 按下板子的复位按键.
2. 打开手机或者电脑的WiFi, 然后找到名称为 `readguy` 的网络, 连接. 密码为12345678.
3. 连接完成后, 打开该手机的浏览器, 访问192.168.4.1 如果手机提示该网络无法访问因特网, 则选择保持连接.
4. 加载完成之后, 根据你的硬件配置引脚. 网页会提供输入各个引脚的输入框, 根据自己的硬件情况输入引脚即可. 如果你正在使用甘草和半糖的开发板, 点击最上方的自定义线序, 可以快捷设置引脚.
5. 点击下方的OK!来确定配置, 直到屏幕闪烁并显示〝口口口口〞后, 即可进入下一步
6. 根据网页的按键说明提示, 依次按下按键, 直到屏幕刷新出4位数字验证码后, 将验证码填写到网页上, 最后点击确定!即可完成配置.
7. 如果刚才的引脚配置有误 (如按下按键无响应, 或者屏幕不显示), 则需要从该步骤的第一步重新开始.
## 使用方法-按钮操控方式:
1. 烧录成功后即刻出现一个wifi热点“readguy”wifi密码为12345678
@@ -133,23 +149,23 @@ Light-brightness-control is optional.
5. 双按键操作说明:
按键1单击向右移动
- 按键1单击向右移动
按键1长按向左移动
- 按键1长按向左移动
按键2单击确定
- 按键2单击确定
按键2长按半秒界面内返回键盘输入状态下可以快速切换英文大小写
- 按键2长按半秒界面内返回键盘输入状态下可以快速切换英文大小写
6. 三按键操作说明:(只不过, GUI功能还在研发编写中...)
按键1向左/向上,
- 按键1向左/向上,
按键2单击确定
- 按键2单击确定
按键3向右/向下。
- 按键3向右/向下。
按键2长按半秒界面内返回键盘输入状态下可以快速切换英文大小写
- 按键2长按半秒界面内返回键盘输入状态下可以快速切换英文大小写
7. 关于菜单控件: 在菜单模式下若处于最后一个菜单选项,再按 向右/向下 功能键则会进入滑动条(选择菜单选项).
@@ -253,17 +269,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询问商用事宜

View File

@@ -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 绘制纵向线段

View File

@@ -43,7 +43,7 @@
#include <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
#include <lwip/apps/sntp.h>
#include <lwip/apps/sntp.h> // 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(
"<html><body><meta charset=\"utf-8\">已连接, 不需要再配网了。</body></html>"));
return;
}
String webpage_html = F(
"<!DOCTYPE html>"
"<html lang='zh-cn'>"
@@ -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<NTP_SERVERS;i++){//最多尝试10次对时请求
_now=0;
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[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){

View File

@@ -103,7 +103,7 @@ void textShow(server_t sv){ //点击链接即可出现发送文本框, 点击发
}
void textShowGet(server_t sv){ //注册Web服务函数回调 (就是显示接口)
const String ok_str_1=F("<html><body><meta charset=\"utf-8\">"); //网页前半部分
const String ok_str_2=F("<a href=\"/textshow\">重新传文字</a></body></html>"); //网页后半部分
const String ok_str_2=F("<br/><a href=\"/textshow\">重新传文字</a></body></html>"); //网页后半部分
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+"文字显示完成:<br/>"+txt+ok_str_2); //报告显示完成
float tsize = ((float)guy.width())/twidth; //计算字体大小, 此大小的目的是填满屏幕
float fsize = ((float)guy.height())/guy.fontHeight(); //计算垂直方向的字体大小, 制定合适的显示方法
if(tsize>fsize){ //字符太短, 字体大小取决于屏幕垂直高度

View File

@@ -8,9 +8,43 @@
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date create: 2023-11-01
* last modify: 2023-11-06
* @brief ReadGuy 图片显示功能演示.
* last modify: 2024-12-04
*
* @note 重要消息: 这是一个实验性功能. 可能你所使用的LGFX库版本较旧而无法通过编译.
*
* (ESPxxxx系列可无视此行) 对于不支持fs::FS的设备 (如PC) 来说, 请前往 guy_image.h 文件并更改第34行的注释
*
* 如果你的项目中无法成功编译源码中的setBuffer, 请更改LovyanGFX库的函数!
* 位于文件 LovyanGFX/src/lgfx/v1/LGFX_Sprite.hpp
* 第155行 void setBuffer 函数:
* 添加为如下内容并保存 (不建议修改库里原有的函数, 保证库的兼容性)
*
``` C++
void setBuffer(void* buffer, int32_t w, int32_t h, color_depth_t bpp)
{
deleteSprite();
if (bpp != 0) {
_write_conv.setColorDepth(bpp);
_read_conv = _write_conv;
_panel_sprite.setColorDepth(bpp);
}
_panel_sprite.setBuffer(buffer, w, h, &_write_conv);
_img = _panel_sprite.getBuffer();
_sw = w;
_clip_r = w - 1;
_xpivot = w >> 1;
_sh = h;
_clip_b = h - 1;
_ypivot = h >> 1;
}
```
* 完成后请再次尝试编译
* [已经向lovyan03/LovyanGFX发布issue, 等待解决]
*
* @brief ReadGuy 图片显示功能演示.
* - 演示如何将比较大的图片通过多种方法显示到屏幕上.
* - 运行的会很缓慢, 因为示例的图片文件比较大.
* 1. 在运行过ex01或者ex02的开发板上 编译烧录本程序.
@@ -106,37 +140,6 @@
* 为了简化程序调用过程并提高调用速度, 此处的像素坐标位置参数为一个整数(而不是两个)
* 至于该怎么调用这个函数, 并不是你需要了解的事情.
*
* @note 重要消息: 这是一个实验性功能. 可能你所使用的LGFX库版本较旧而无法通过编译.
* 如果你的项目中无法成功编译源码中的setBuffer, 请更改LovyanGFX库的函数!
* 位于文件 LovyanGFX/src/lgfx/v1/LGFX_Sprite.hpp
* 第155行 void setBuffer 函数:
* 修改为如下内容并保存
*
``` C++
void setBuffer(void* buffer, int32_t w, int32_t h, color_depth_t bpp = rgb565_2Byte)
{
deleteSprite();
if (bpp != 0) {
_write_conv.setColorDepth(bpp);
_read_conv = _write_conv;
_panel_sprite.setColorDepth(bpp);
}
_panel_sprite.setBuffer(buffer, w, h, &_write_conv);
_img = _panel_sprite.getBuffer();
_sw = w;
_clip_r = w - 1;
_xpivot = w >> 1;
_sh = h;
_clip_b = h - 1;
_ypivot = h >> 1;
}
```
* 完成后请再次尝试编译
* [已经向lovyan03/LovyanGFX发布issue, 等待解决]
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
@@ -263,7 +266,9 @@ void setup(){
readguyImage im(guy); //定义一个绘制器, 此类中的函数用于绘制图片.
//所有的绘制图片的参数均需要放入此结构内
//直接更改im内的数据即可设置绘制参数. (就像结构体一样用它)
#ifdef FS_POINTER //对于不支持fs::FS的设备来说, 请前往 guy_image.h 文件并更改第34行的注释
im.baseFs=&guy.guyFS(); //在此处就是设置文件系统.
#endif
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.

View File

@@ -9,6 +9,40 @@
* @version 1.0
* @date 2023-11-01
* @note 重要消息: 这是一个实验性功能. 可能你所使用的LGFX库版本较旧而无法通过编译.
*
* (ESPxxxx系列可无视此行) 对于不支持fs::FS的设备 (如PC) 来说, 请前往 guy_image.h 文件并更改第34行的注释
*
* 如果你的项目中无法成功编译源码中的setBuffer, 请更改LovyanGFX库的函数!
* 位于文件 LovyanGFX/src/lgfx/v1/LGFX_Sprite.hpp
* 第155行 void setBuffer 函数:
* 添加为如下内容并保存 (不建议修改库里原有的函数, 保证库的兼容性)
*
``` C++
void setBuffer(void* buffer, int32_t w, int32_t h, color_depth_t bpp)
{
deleteSprite();
if (bpp != 0) {
_write_conv.setColorDepth(bpp);
_read_conv = _write_conv;
_panel_sprite.setColorDepth(bpp);
}
_panel_sprite.setBuffer(buffer, w, h, &_write_conv);
_img = _panel_sprite.getBuffer();
_sw = w;
_clip_r = w - 1;
_xpivot = w >> 1;
_sh = h;
_clip_b = h - 1;
_ypivot = h >> 1;
}
```
* 完成后请再次尝试编译
* [已经向lovyan03/LovyanGFX发布issue, 等待解决]
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
@@ -78,15 +112,33 @@ uint8_t readguyImage::drawImgHandler(int r, LGFX_Sprite *spr){
spr->setRotation(rot);
switch(format&3){
case 1:
spr->drawBmpFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
spr->drawBmpFile(
#ifdef FS_POINTER
*baseFs
#else
guy->guyFS()
#endif
,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
#ifndef ESP8266
case 2:
spr->drawPngFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
spr->drawPngFile(
#ifdef FS_POINTER
*baseFs
#else
guy->guyFS()
#endif
,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
#endif
case 3:
spr->drawJpgFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
spr->drawJpgFile(
#ifdef FS_POINTER
*baseFs
#else
guy->guyFS()
#endif
,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
}
spr->setRotation(0);
@@ -219,7 +271,11 @@ uint8_t readguyImage::getExName(const char* fname, char* exname, size_t exname_l
/// @brief 显示图像
void readguyImage::drawImageFile(bool use16grey){
if(filename == nullptr || filename[0] == 0 || !(baseFs->exists(filename))) return; //文件不存在
if(filename == nullptr || filename[0] == 0
#ifdef FS_POINTER
|| !(baseFs->exists(filename))
#endif
) return; //文件不存在
char ex[8]; //保存文件的扩展名
getExName(filename,ex,7); //获取文件的扩展名.最后一个参数用于防止数组越界
format = 0; //16灰度模式
@@ -243,6 +299,7 @@ void readguyImage::drawImageFile(bool use16grey){
LGFX_Sprite bmpspr;
//首先, 需要获取到内部显存的地址, 用于建立图片分块绘制缓存.
//获取屏幕缓存, 随后分配图片解码所需的内存.
//此处如果出现 convert from `lgfx::v1::color_depth_t` to `uint8_t` 警告(或错误) 请查看本文开头重要消息
bmpspr.setBuffer(_pool,w,_h,lgfx::v1::color_depth_t::grayscale_8bit);
//bmpspr.createSprite(guy_width,(guy_height+7)&0x7ffffff8);
@@ -286,7 +343,11 @@ void readguyImage::drawImageFile(bool use16grey){
}
uint8_t readguyImage::drawImageToBuffer(){
if(filename == nullptr || filename[0] == 0 || !(baseFs->exists(filename))) return 1; //文件不存在
if(filename == nullptr || filename[0] == 0
#ifdef FS_POINTER
|| !(baseFs->exists(filename))
#endif
) return 1; //文件不存在
if( w==0 || h==0 || w>=0x8000 || h>=0x8000 || exPool==nullptr || exPoolSize<1024) return 3; //内存不足
char ex[8]; //保存文件的扩展名
format = 0; //16灰度模式
@@ -309,20 +370,39 @@ uint8_t readguyImage::drawImageToBuffer(){
if(_h==0 || GUY_STAGES>8) return 3; //内存不足以在显示8次之内就... 总之就是内存不够
LGFX_Sprite spr;
//此处如果出现 convert from `lgfx::v1::color_depth_t` to `uint8_t` 警告(或错误) 请查看本文开头重要消息
spr.setBuffer(_pool,w,_h,lgfx::v1::color_depth_t::grayscale_8bit);
for(int i=0;i<GUY_STAGES;i++){
spr.fillScreen(background?0xffff:0);
switch(format&3){
case 1:
spr.drawBmpFile(*baseFs,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
spr.drawBmpFile(
#ifdef FS_POINTER
*baseFs
#else
guy->guyFS()
#endif
,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
break;
#ifndef ESP8266
case 2:
spr.drawPngFile(*baseFs,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
spr.drawPngFile(
#ifdef FS_POINTER
*baseFs
#else
guy->guyFS()
#endif
,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
break;
#endif
case 3:
spr.drawJpgFile(*baseFs,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
spr.drawJpgFile(
#ifdef FS_POINTER
*baseFs
#else
guy->guyFS()
#endif
,filename,0,0,0,0,offsetx,offsety+_h*i,scalex,scaley);
break;
}
guy->drawImageStage(spr,x,y+_h*i,i,GUY_STAGES);

View File

@@ -9,6 +9,40 @@
* @version 1.0
* @date 2023-11-01
* @note 重要消息: 这是一个实验性功能. 可能你所使用的LGFX库版本较旧而无法通过编译.
*
* (ESPxxxx系列可无视此行) 对于不支持fs::FS的设备 (如PC) 来说, 请前往 guy_image.h 文件并更改第34行的注释
*
* 如果你的项目中无法成功编译源码中的setBuffer, 请更改LovyanGFX库的函数!
* 位于文件 LovyanGFX/src/lgfx/v1/LGFX_Sprite.hpp
* 第155行 void setBuffer 函数:
* 添加为如下内容并保存 (不建议修改库里原有的函数, 保证库的兼容性)
*
``` C++
void setBuffer(void* buffer, int32_t w, int32_t h, color_depth_t bpp)
{
deleteSprite();
if (bpp != 0) {
_write_conv.setColorDepth(bpp);
_read_conv = _write_conv;
_panel_sprite.setColorDepth(bpp);
}
_panel_sprite.setBuffer(buffer, w, h, &_write_conv);
_img = _panel_sprite.getBuffer();
_sw = w;
_clip_r = w - 1;
_xpivot = w >> 1;
_sh = h;
_clip_b = h - 1;
_ypivot = h >> 1;
}
```
* 完成后请再次尝试编译
* [已经向lovyan03/LovyanGFX发布issue, 等待解决]
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
@@ -30,15 +64,11 @@
#ifndef _GUY_IMAGE_H_FILE
#define _GUY_IMAGE_H_FILE
#define FS_POINTER //如果你的系统不是ESP系列, 没有FS.h文件也没有File类则注释掉它
#include <Arduino.h>
#include <SPI.h>
#include <FS.h>
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include "readguy.h"
class readguyImage{
public:
readguyImage(ReadguyDriver &_guy):guy(&_guy){
@@ -67,7 +97,9 @@ class readguyImage{
/// @brief 获取文件的扩展名.
static uint8_t getExName(const char* fname, char* exname, size_t exname_len);
#ifdef FS_POINTER
fs::FS *baseFs = nullptr;/// // / //要绘制的图片所属的文件系统
#endif
const char *filename = nullptr; // / //要绘制的图片的文件名和文件路径
int32_t x = 0; //// // / //绘制位置坐标X
int32_t y = 0; //// // / //绘制位置坐标Y

View File

@@ -0,0 +1,9 @@
#ifndef _TIMELIB_H_FILE
#define _TIMELIB_H_FILE
#ifdef __cplusplus
#include "ctg_timelib.hpp"
using namespace CTG_TimeLib;
#endif
#endif

View File

@@ -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

View File

@@ -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 <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <lwip/apps/sntp.h> //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 <lwip/apps/sntp.h>
}
/// @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 <lwip/apps/sntp.h>
}
#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

View File

@@ -11,7 +11,7 @@
"type": "git",
"url": "https://github.com/fsender/readguy"
},
"version": "1.3.7",
"version": "1.4.2",
"frameworks": "arduino",
"platforms": ["espressif32", "espressif8266"],
"headers": "readguy.h",

View File

@@ -1,5 +1,5 @@
name=readguy
version=1.3.7
version=1.4.2
author=fsender <f_ender@163.com>
maintainer=fsender <f_ender@163.com>
sentence=A free E-paper display driver library supports 16-level greyscale.

View File

@@ -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) {

View File

@@ -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函数实现

View File

@@ -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.
#define READGUY_ENABLE_WIFI
/// @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

View File

@@ -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;
}

View File

@@ -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. */

View File

@@ -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模块

View File

@@ -45,25 +45,42 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> 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<drv_height();j++){
for(int i=0;i<xbits;i++){
uint_fast8_t readf=f(j*xbits+i);
#ifdef SIMULATE_GREYSCALE_COLOUR
if(readf == 0xff && i!=xbits-1)
#endif
ips.drawFastHLine(WHITE_GAP+i*8,WHITE_GAP+j,8,0xffff);
else {
#ifdef SIMULATE_GREYSCALE_COLOUR
else
#endif
{
int lineOK=0;
#ifdef SIMULATE_GREYSCALE_COLOUR
if(partMode)//注意这里 readrect函数已经自动化实现边界处理了
ips.readRect(WHITE_GAP+i*8,WHITE_GAP+j,8,1,dat);
else memset(dat,0xff,sizeof(dat));
else
#endif
memset(dat,0xff,sizeof(dat));
if(readf == 0x00 && i!=xbits-1){
for(int k=0;k<8;k++)
if((dat[k]&0x1f)==0x1f) lineOK++;
@@ -74,16 +91,23 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> 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() {}
}

View File

@@ -6,7 +6,7 @@
* @file guy_version.h
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief readguy 版本控制文件.
* @version 1.3.2
* @version 1.4.2
* @date 2023-09-21
* @attention
@@ -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 2
#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.2"
#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.

View File

@@ -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 &notify, const serveFunc *serveFuncs, int funcs){
//启动WiFi服务器端, 这样就可以进行配网工作
@@ -198,6 +209,7 @@ void ReadguyDriver::server_setup(const String &notify, 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 &notify, const serveFunc *serveFun
sfnames=new String[sfuncs];
sfevents=new String[sfuncs];
for(int i=0;i<sfuncs;i++){ //set-up 第三方库内容, 初始化后即可使用
sv.on(serveFuncs[i].event,HTTP_GET,std::bind(serveFuncs[i].func,&sv));
int spec = -1;
for(int ij=0;ij<8;ij++){ //处理一些HTTP不同类型请求.
if(!strcmp_P(serveFuncs[i].linkname.c_str(),text_http_methods[ij])) spec = ij;
}
if(spec>1) 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 &notify, 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("</h3><hr/>");
if(sfuncs>0){
s+=F("以下链接来自<b>应用程序</b><br/>"); //换行
for(int i=0;i<sfuncs && sfnames[i]!=emptyString;i++){
for(int i=0;i<sfuncs;i++){
int spec = -1;
for(int ij=0;ij<8;ij++){
if(!strcmp_P(sfnames[i].c_str(),text_http_methods[ij])) spec = ij;
}
if(spec != -1 || sfnames[i]==emptyString) continue;
s+=F("<a href=\"");
s+=sfevents[i];
s+=F("\">");
@@ -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("<br/>芯片型号: ");
@@ -682,7 +733,9 @@ void ReadguyDriver::handleFinal(){
//s+=F("<br/>"); //换行
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();
@@ -732,12 +785,12 @@ const PROGMEM char ReadguyDriver::verify2_html[] =
"<br/><hr/>完成上述4个操作之后屏幕上将会展现出验证码,输入验证码即可完成硬件配置.<br/></p><form action=\"/fin"
"al\" method=\"POST\"><input type=\'text\' name=\'t_verify\' maxlength=\"6";
const PROGMEM char ReadguyDriver::verifybtn_html[3][224] = {
"一个按键, 功能全保留, 操作可能比较繁琐.<br/>"
"点按:下一个/向下翻页<br/>双击:确定/选择<br/>三连击:返回/退格<br/>长按半秒:上一个/向上翻页<br/>点按紧接着长按: 特殊操作",
"两个按键, 操作可以满足需求.<br/>"
"按键1点按:下一个/向下翻页<br/>按键1长按:上一个/向上翻页<br/>按键2点按:确定/选择<br/>按键2长按:返回/退格<br/>按住按键1点按2:特殊操作",
"三个按键, 操作非常方便流畅.<br/>"
"按键1:上一个/向上翻页<br/>按键2点按:确定/选择<br/>按键2长按: 返回/退格<br/>按键3:下一个/向下翻页<br/>双击点按2:切换输入法等特殊操作"
"一个按键, 功能全保留, 操作可能比较繁琐.<br/>点按:下一个/向下翻页<br/>双击:确定/选择<br/>三连击:返回/退格<br/>"
"长按半秒:上一个/向上翻页<br/>点按紧接着长按: 特殊操作",
"两个按键, 操作可以满足需求.<br/>按键1点按:下一个/向下翻页<br/>按键1长按:上一个/向上翻页<br/>按键2点按:确定/选"
"择<br/>按键2长按:返回/退格<br/>按住按键1点按2:特殊操作",
"三个按键, 操作非常方便流畅.<br/>按键1:上一个/向上翻页<br/>按键2点按:确定/选择<br/>按键2长按: 返回/退格<br/>按"
"键3:下一个/向下翻页<br/>双击点按2:切换输入法等特殊操作"
};
const PROGMEM char ReadguyDriver::final_html[] =
"欢迎使用 readguy</title></head><body><h1>readguy ";
@@ -745,7 +798,7 @@ const PROGMEM char ReadguyDriver::afterConfig_html[] =
"配置完成</h1><p>您已完成了初始化工作.现在可以配置WiFi和天气密钥相关内容.<br/></p>"
"返回<a href=\"/pinsetup\">引脚设置</a><h3>";
const PROGMEM char ReadguyDriver::home_html[]=
"欢迎页面</h1>在这里您可以配置属于应用app的内容. <a href=\"/pinsetup\">重新配置引脚</a><h3>";
"欢迎页面</h1>在这里您可以配置属于应用app的内容.<h3>";
/*
const PROGMEM char ReadguyDriver::final2_html[] =
"<form action=\"/wifi\" method=\"POST\">WiFi 名称<input type=\'text\' name=\'ssid\' "
@@ -754,9 +807,9 @@ const PROGMEM char ReadguyDriver::final2_html[] =
"钥<input type=\'text\' name=\'psk\' maxlength=\"63";
*/
const PROGMEM char ReadguyDriver::end_html[] =
"<p>ReadGuy on device " _READGUY_PLATFORM "<br/>Copyright © FriendshipEnder <a href=\""
_GITHUB_LINK "\">GitHub</a> <a href=\"" _BILIBILI_LINK "\">Bilibili</"
"a><br/>版本: " READGUY_VERSION " ,编译日期: " __DATE__ " " __TIME__ "</p></body></html>";
"<p>ReadGuy on device " _READGUY_PLATFORM " <a href=\"/pinsetup\">重新配置引脚</a> <a href=\"/update\">固件更新"
"</a><br/>Copyright © FriendshipEnder <a href=\"" _GITHUB_LINK "\">GitHub</a> <a href=\"" _BILIBILI_LINK "\">"
"Bilibili</a><br/>版本: " READGUY_VERSION " ,编译日期: " __DATE__ " " __TIME__ "</p></body></html>";
/*const PROGMEM uint8_t ReadguyDriver::faviconData[1150]={
0x0,0x0,0x1,0x0,0x1,0x0,0x10,0x10,0x0,0x0,0x1,0x0,0x20,0x0,0x68,0x4,0x0,0x0,0x16,0x0,0x0,0x0,0x28,0x0,
0x0,0x0,0x10,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x1,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x40,0x4,0x0,0x0,0x0,0x0,

File diff suppressed because it is too large Load Diff

View File

@@ -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<uint8_t(int)> 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 &notify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0){}
void server_setup(const String &notify=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 btnID<getButtonsCount()?(!(btn_rd[btnID].isPressedRaw())):1;}
/// @brief 该函数用于设置按键是否允许扫描连按
void setButtonSpecial(bool en = 1) { if(READGUY_buttons==3) btn_rd[1].enScanDT(en); }
/** @brief 返回可用的文件系统. 当SD卡可用时, 返回SD卡. 否则根据情况返回最近的可用文件系统
* @param initSD 2:总是重新初始化SD卡; 1:若SD卡不可用则初始化; 0:SD卡不可用则返回LittleFS. */
fs::FS &guyFS(uint8_t initSD = 0);
#ifdef ESP8266
/// @brief 恢复I2C复用SPI时的引脚功能, 仅ESP8266可用
void recoverI2C();
#else
/// @brief 恢复I2C引脚功能
void recoverI2C(){}
#endif
#if (defined(READGUY_ALLOW_SDCS_AS_BUTTON) && defined(READGUY_ENABLE_SD))
/// @brief 设置SD卡为忙状态或空闲状态
void setSDbusy(bool busy){ if(static_sd_cs!=0x7f) sd_cs_busy += (busy?1:(sd_cs_busy?-1:0)); }
#else
void setSDbusy(bool busy){ (void)busy; } //sd_cs as a btn pin
#endif
#if 0 // disabled because of useless
/// @brief 暂停按键扫描 (比如即将要开启中断或者中断, 定时器等硬件外设资源不足)
void stopKeyScan(){
#ifdef ESP8266
btnTask.detach();//暂时关闭任务, 避免引脚被设置为非法模式
#else
vTaskSuspend(btn_handle);//暂时关闭任务, 避免引脚被设置为非法模式
#endif
}
/// @brief 恢复按键扫描 (中断...等需要定时器的硬件外设用完了)
void keyScan(){
#ifdef ESP8266
btnTask.attach_ms(BTN_LOOPTASK_DELAY,looptask);
#else
vTaskResume(btn_handle); //开启任务后, 延时确保按键任务可以活跃而不是一直处于被暂停又刷屏的无限循环
#endif
}
#endif
//friend class EpdIf; //这样EpdIf就可以随意操作这个类的成员了
private:
//以下是支持的所有屏幕型号 Add devices here!
@@ -333,11 +373,17 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
#else
static const int8_t config_data[32];
int8_t READGUY_sd_ok = 0;
int8_t READGUY_cali = 0;
int8_t READGUY_cali = 127;
int8_t READGUY_buttons = 0; //按钮个数, 0-3都有可能
#endif
int epd_OK=0; //墨水屏可用
int currentBright = -3; //初始亮度
int16_t epdPartRefresh = 0; //连续快刷次数
int16_t epdForceFull = 0x7fff; //连续快刷达到指定次数后, 强制全刷, 内部变量
int16_t currentBright = -3; //初始亮度
int16_t current_depth = 15; //初始灰度
uint8_t refresh_begin(uint8_t freshType); //设置快速刷新 频率调控
#if (defined(READGUY_ALLOW_DC_AS_BUTTON))
void refresh_end();
#endif
//LGFX_Sprite gfx; // 图形引擎类指针, 可以用这个指针去操作屏幕缓冲区
readguyEpdBase *guy_dev = nullptr;
@@ -375,9 +421,19 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
static guy_button btn_rd[3];
/// @brief 复用输出引脚1: 适用于按键引脚与屏幕DC引脚复用的情形
/// @note 只能解决屏幕DC引脚复用的情况, 其他引脚最好不要复用, 复用了我也解决不了
static int8_t pin_cmx;
static int8_t pin_cmx;
#ifdef READGUY_ALLOW_DC_AS_BUTTON
static bool refresh_state; //1: free; 0: busy(refreshing)
static uint8_t refresh_press; //0x7f: epd_dc btn released; others: epd_dc btn pressed
#endif
#ifdef READGUY_ALLOW_EPDCS_AS_BUTTON
static uint8_t static_epd_cs; //epd_cs as a btn pin
#endif
#ifdef READGUY_ALLOW_SDCS_AS_BUTTON
static uint8_t static_sd_cs; //sd_cs as a btn pin
static volatile uint8_t sd_cs_busy; //sd_cs as a btn pin
#endif
static volatile uint8_t spibz;
private:
#ifdef READGUY_ESP_ENABLE_WIFI
//static constexpr size_t EPD_DRIVERS_NUM_MAX = READGUY_SUPPORT_DEVICES;
static const char *epd_drivers_list[EPD_DRIVERS_NUM_MAX];
@@ -396,22 +452,32 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
//static const PROGMEM uint8_t faviconData[1150];
#endif
static void looptask(); //按键服务函数
static uint8_t rd_btn_f(uint8_t btn);
static uint8_t rd_btn_f(uint8_t btn, bool activeLow);
uint8_t getBtn_impl(); //按钮不可用, 返回0.
static void in_press(){ //SPI开始传输屏幕数据
#ifdef ESP8266
if(!spibz) SPI.beginTransaction(SPISettings(ESP8266_SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
#if (defined(READGUY_ALLOW_DC_AS_BUTTON))
if(!(spibz&0x3f))
#else
if(!spibz) epd_spi->beginTransaction(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; } //用户变量
@@ -449,19 +518,28 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
constexpr int getReadguy_user8 () const { return READGUY_user8; } //用户变量
constexpr int getReadguy_user9 () const { return READGUY_user9; } //用户变量
constexpr int getReadguy_user10() const { return READGUY_user10;} //用户变量
constexpr int getReadguyUseSdio () { //返回程序调用SD卡时 是否使用了SDIO
#ifdef CONFIG_IDF_TARGET_ESP32S3 //仅对ESP32S3可用
constexpr int getReadguyUseSdio() const { //返回程序调用SD卡时 是否使用了SDIO
#ifdef CONFIG_IDF_TARGET_ESP32S3 //仅对ESP32S3可用
return (READGUY_user1 != -1) && (READGUY_user2 != -1);
#else
return 0; //非ESP32S3平台不可用SDIO
return 0; //非ESP32S3平台不可用SDIO
#endif
} //用于esp32s3使用SDIO卡数据的DAT2
constexpr int getSdio_dat0 () { return getReadguyUseSdio()?READGUY_sd_miso:-1; } //用于esp32s3使用SDIO卡数据的DAT0
constexpr int getSdio_dat1 () { return getReadguyUseSdio()?READGUY_user1:-1; } //用于esp32s3使用SDIO卡数据的DAT1
constexpr int getSdio_dat2 () { return getReadguyUseSdio()?READGUY_user2:-1; } //用于esp32s3使用SDIO卡数据的DAT2
constexpr int getSdio_dat3 () { return getReadguyUseSdio()?READGUY_sd_cs:-1; } //用于esp32s3使用SDIO卡数据的DAT3
constexpr int getSdio_clk () { return getReadguyUseSdio()?READGUY_sd_sclk:-1; } //用于esp32s3使用SDIO卡数据的CLK
constexpr int getSdio_cmd () { return getReadguyUseSdio()?READGUY_sd_mosi:-1; } //用于esp32s3使用SDIO卡数据的CMD
#ifdef CONFIG_IDF_TARGET_ESP32S3 //仅对ESP32S3可用
constexpr int getSdio_dat0 () const { return getReadguyUseSdio()?READGUY_sd_miso:-1; } //用于esp32s3使用SDIO卡数据的DAT0
constexpr int getSdio_dat1 () const { return getReadguyUseSdio()?READGUY_user1 :-1; } //用于esp32s3使用SDIO卡数据的DAT1
constexpr int getSdio_dat2 () const { return getReadguyUseSdio()?READGUY_user2 :-1; } //用于esp32s3使用SDIO卡数据的DAT2
constexpr int getSdio_dat3 () const { return getReadguyUseSdio()?READGUY_sd_cs :-1; } //用于esp32s3使用SDIO卡数据的DAT3
constexpr int getSdio_clk () const { return getReadguyUseSdio()?READGUY_sd_sclk:-1; } //用于esp32s3使用SDIO卡数据的CLK
constexpr int getSdio_cmd () const { return getReadguyUseSdio()?READGUY_sd_mosi:-1; } //用于esp32s3使用SDIO卡数据的CMD
#else
constexpr int getSdio_dat0 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT0
constexpr int getSdio_dat1 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT1
constexpr int getSdio_dat2 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT2
constexpr int getSdio_dat3 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT3
constexpr int getSdio_clk () const { return -1; } //用于esp32s3使用SDIO卡数据的CLK
constexpr int getSdio_cmd () const { return -1; } //用于esp32s3使用SDIO卡数据的CMD
#endif
//constexpr int memWidth () const { return guy_width ; } //返回显存宽度(不是画幅宽度),不会随着画布旋转改变
//constexpr int memHeight () const { return guy_height ; } //返回显存高度(不是画幅高度),不会随着画布旋转改变
int drvWidth () const { return READGUY_cali==127?guy_dev->drv_width():0; } //返回显示屏硬件宽度(不是画幅宽度)