20 Commits
1.3.3 ... main

Author SHA1 Message Date
fsender
742e3ca1bc update button logic 2025-04-08 01:24:09 +08:00
fsender
f1a61272a9 1.5.0 rewrite several (Pre 2.0 update) 2025-04-07 16:46:04 +08:00
fsender
b8b74b2b8d fix configuring 154B bugs 2025-03-23 14:01:06 +08:00
fsender
25b9e62f5f feat: initialize epd with custom slow-refresh 2025-03-13 08:36:29 +08:00
fsender
86e1022ffa feat: new device pre-support 2025-03-13 00:04:09 +08:00
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
fsender
b8d7310236 feat: user pin-config data & button lib 2024-10-28 05:42:15 +08:00
fsender
86de2fb12a ver 1.3.6: fix post-release bugs 2024-03-12 00:58:08 +08:00
fsender
558b847a3d ver 1.3.6 buttons driver update 2024-03-11 21:54:20 +08:00
fsender
7bb2ba77f0 1.3.5: add send-text example&fix bugs 2024-03-04 13:53:35 +08:00
fsender
8e825c862a 1.3.5: add more examples&fix bugs 2024-03-04 13:48:01 +08:00
fsender
d88f1a3c5c feat: add 5.83 inch support 2023-11-24 22:38:28 +08:00
fsender
771ac6db80 feat: initSD option. init without SD is fine 2023-11-12 07:10:23 +08:00
fsender
c4e848730c perf: better button preformance 2023-11-12 06:19:31 +08:00
fsender
78438e36a7 fix: pin preset order 2023-11-12 05:01:10 +08:00
54 changed files with 37466 additions and 667 deletions

View File

@@ -1,3 +1,105 @@
# 1.5.0
## Release 1.5.0 - 2025/4/7
1. 重构较多代码, 为2.0做准备. 现在可以通过包含文件来自定义配置了
2. 修复bug
## Release 1.4.3 - 2025/4/3
1. 加入了更多可选配置项, 如 AP 配网地址等
2. 为ESP32S3优先选择调用 SDIO_1Bit 来驱动SD卡
4. 修复bug
## 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卡用的)
2. 增加 `guy_button` 按键库功能的接口函数
3. 修复部分bug
## Release 1.3.6 - 2024/3/11
1. 按键功能: 正式更新特殊操作按法, 此按法可用于切换输入法或菜单定位等功能. 同时更新ex03演示, 演示更清晰.
2. 修复其他bug, 比如示例wifi-text-show中, 中文网页无法在windows系统上呈现等...
## Release 1.3.5 - 2024/3/4
1. 2024/2/25更新: 修复了按键bug, 双按键有时候识别不灵, 三按键不支持移植到拨轮硬件操作, 现在全修好了
2. 2024/2/26更新: 增加了新环境 : 针对无串口芯片的 ESP32C3 新增的选项
3. 2024/2/28更新: 增加了新的示例: WiFi传文本
4. 优化ESP32C3的配置体验
## Release 1.3.4 - 2023/11/24
1. 添加5.83寸屏幕驱动. 默认开启, 嫌flash占用大的可以手动关
2. 修复别的一些bug
## Release 1.3.3 - 2023/11/12
1. 添加了2.66寸三色和黑白的不同驱动程序.

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

104
README.md
View File

@@ -2,29 +2,48 @@
[![arduino-library-badge](https://www.ardu-badge.com/badge/readguy.svg?)](https://www.ardu-badge.com/readguy)
<img src="extra/artset/readguy_theme3.png" width="30%" height="auto">
## ReadGuy 2.0 预告
**版本1.3.3正式发布欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~
1. 内置驱动,无其他 不跨平台 多平台
2. 自定义脚本驱动 不跨平台 多平台
3. 内置 不自定义 带脚本 跨平台 数据存eeprom或者nvs. 支持从文件系统加载
4. 文件系统接口优化
5. 灰度显示优化
6. 按键逻辑优化: 双按键:按键1点按下一个 长按上一个 长按过程中点按2则功能5 按键2点按确定 长按取消
三按键 左键点按松开时候结算: 上一个, 左键长按也是上一个 右键点按长按都是下一个 按住左键按中间功能5 中键点按确定 长按取消
7. 支持pre_init post_init 等设备限定的脚本,在初始化设备时和设备休眠时都可以执行类似功能. 此外readguy2.0的二进制是兼容1.x的
8. 摒弃默认刷屏方式使用dispWriter函数 速度太慢, 改为使用两种示例, 正常读取显示和函数读取显示都支持调用
敬请期待
<img src="https://github.com/fsender/readguy/blob/1.4.2/extra/artset/readguy_theme3.png?raw=true" width="30%" height="auto">
**版本1.5.0正式发布欢迎分享、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级灰度**内容!
### 一次编译即可使程序兼容多种屏幕!
<img src="extra/artset/readguy_girl.png" width="25%" height="auto">
<img src="https://github.com/fsender/readguy/blob/1.4.2/extra/artset/readguy_girl.png?raw=true" width="25%" height="auto">
<img src="extra/artset/view.jpg" width="auto" height="auto">
<img src="https://github.com/fsender/readguy/blob/1.4.2/extra/artset/view.jpg?raw=true" width="auto" height="auto">
#### 目前支持ESP芯片平台、多种屏幕型号、13个按钮操控、SD卡、前置光亮度控制
@@ -70,7 +89,7 @@ Supported displays: 1.54-inch, 2.13-inch, 2.66-inch, 2.9-inch, 4.2-inch.
|14 |M5 Core.Ink 1.54寸 |即将支持|200*200|/|/|/|/|
|15 |3.7寸低DPI版墨水屏 |即将支持|416*240|/|/|/|/|
|16 |4.26寸高分辨率墨水屏|即将支持|800*480|/|/|/|/|
|17 |5.83寸墨水屏幕 |即将支持|600*448|/|/|/|/|
|17 |5.83寸墨水屏幕 |原创自研|600*448| 16阶 | 支持 | 1.7s | 0.8s |
|18 |5.83寸GDEQ0583T31 |即将支持|640*480|/|/|/|/|
|19 |7.5寸三色墨水屏幕 |即将支持|800*480|/|/|/|/|
|20 |10.2寸GDEQ102T90 |即将支持|960*640|/|/|/|/|
@@ -113,6 +132,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 +170,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. 关于菜单控件: 在菜单模式下若处于最后一个菜单选项,再按 向右/向下 功能键则会进入滑动条(选择菜单选项).
@@ -209,9 +246,9 @@ firmware.bin 0x10000
**如果想要使用纯arduino环境, 需要更改platformio.ini, 并将framework更改为`arduino`. 此时可以跳过 5~6 步骤**.
5. 编译目标是`ESP32`时, 需要单独安装[`LittleFS`](https://github.com/joltwallet/esp_littlefs)库来实现相关功能. 在项目内新建文件夹`components`, 在`components`文件夹内放入刚刚克隆的`LittleFS`库. *详见下图**上**方红框*. (使用纯arduino时跳过此步骤)
5. 编译目标是`ESP32`且使用`ESP-IDF arduino component`时, 需要单独安装[`LittleFS`](https://github.com/joltwallet/esp_littlefs)库来实现相关功能. 在项目内新建文件夹`components`, 在`components`文件夹内放入刚刚克隆的`LittleFS`库. *详见下图**上**方红框*. (使用纯arduino时跳过此步骤)
<img src="extra/artset/build_dir_structure.png" width="20%" height="auto">
<img src="https://github.com/fsender/readguy/blob/1.4.2/extra/artset/build_dir_structure.png?raw=true" width="20%" height="auto">
6.`readguy``extra`文件夹内的`platformio`文件夹内所有文件都放在当前目录. 这些文件包含推荐的ESP32 sdkconfig, flash分区表等文件. 使用`pio run -t menuconfig`可以更改项目的menuconfig数据. (使用纯arduino时跳过此步骤)
@@ -235,17 +272,38 @@ firmware.bin 0x10000
其中的WiFi功能, 其实是可以禁掉的. 只要你提前配置成功, 那么就可以摆脱WiFi配网配引脚功能.
使用方法: 打开文件[guy_driver_config.h](src/guy_driver_config.h), 随后便根据注释来选择性的开启或关闭一些系统功能.
使用方法: 打开文件[`guy_driver_config.h`](src/guy_driver_config.h), 随后便根据注释来选择性的开启或关闭一些系统功能.
**不推荐的做法!**: 其中有些屏幕用不到, 也可以通过`guy_epaper_config`来配置.
使用方法: 打开文件[guy_epaper_config.h](src/guy_epaper/guy_epaper_config.h), 随后便可以设置不加载哪些屏幕的驱动程序.
使用方法: 打开文件[`guy_epaper_config.h`](src/guy_epaper/guy_epaper_config.h), 随后便可以设置不加载哪些屏幕的驱动程序.
此操作可以节约flash和RAM消耗, **但是实际上此操作并不能节省太多的flash.** 为确保编译获得的程序兼容性, 应尽量不要更改这个文件.
- 还有一部分功能可以通过更改这两个文件来进行编辑 (如你想自己实现一个SD卡驱动)
- 但是如果自己更改了WiFi配置引脚的功能, ***这样编译出的程序就不能实现跨硬件运行了***
*有一些宏定义的组合是没有检验是否能够通过编译的. 如果有问题请提issue或者群里反馈*
## ESP32 项目配置 (使用 PlatformIO + Arduino as ESP-IDF component 环境)
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的许可才能商用
版权声明:需要经过作者@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(),(int)sp.width(),(int)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

@@ -6,9 +6,15 @@
*
* @file ex03_buttons.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-10-20
* @version 1.2 增加了新的手势功能
*
* @date created: 2023-10-20 modify: 2024-02-25 last modify: 2024-03-11
* @brief ReadGuy 按键功能演示. ReadGuy自带的按键驱动程序是非常好用的
以下内容为按键个数与手势操作的对应关系.
//行为 下一个 上一个 确定 返回/退出 特殊操作(如切换输入法)
//1个按键 返回 1=点按 2=双击 4=长按 8=三击 3=点按后接长按
//2个按键 返回 1=左键点按 2=左键长按 4=右键点按 8=右键长按 3=按住左键点按右键
//3个按键 返回 1=右键点按 2=左键点按 4=中键点按 8=中键长按 3=中间按键双击(需手动开启)
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
@@ -34,48 +40,63 @@
#include "readguy.h" //包含readguy_driver 基础驱动库
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
int c=1;
void setup(){
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库. 尽管初始化过程会刷屏, 但此示例不会用到屏幕.
//if(guy.width()<guy.height()) guy.setRotation(1); //横向
if(guy.width()<200) guy.setRotation(1); //对于小屏幕, 设置横向
c = guy.getButtonsCount(); //此函数用于返回设备有多少个按键.
Serial.println(F("[readguy] Button demo")); //显示文本 默认是不支持中文显示的.
guy.println("Button demo"); //显示文本 默认是不支持中文显示的.
guy.println(F("Button demo."));
guy.println(F("Press the buttons in any way!"));
guy.println(F("Information will show"));
guy.println(F("on the EPD display."));
guy.println();
guy.print(F("Your device supports "));
guy.printf_P(PSTR("%d button%c.\r\n"),c,c==1?' ':'s'); //显示文本
guy.display();//刷新墨水屏.
//guy.setButtonSpecial(true); //对于拨轮, 无法做到按住按钮1按下按钮2
}
void loop(){
int val = guy.getBtn(); //此函数用于获取按键状态 没有按键按下时 返回0.
//1个按键 返回 1=点按 2=双击 3=长按 4=三击
//2个按键 返回 1=左键点按 2=左键长按 3=右键点按 4=右键长按
//3个按键 返回 1=左键点按 2=右键点按 3=中键点按 4=中键长按
auto val = guy.getBtn(); //此函数用于获取按键状态 没有按键按下时 返回0.
if(val>0){
int c = guy.getButtonsCount(); //此函数用于返回设备有多少个按键. [最近更新的函数]
if(val!=guyBNone){
guy.fillRect(0,guy.height()-10, guy.width(),10,1);
guy.setCursor(2,guy.height()-10);
switch (val){
case 1:
if(c==1) guy.println("key single clicked!");
else if(c==2) guy.println("Left key clicked!");
else if(c==3) guy.println("Left key clicked!");
case guyBNext: //下一个 手势
if(c==1) guy.println(F("key single clicked!"));
else if(c==2) guy.println(F("Left key clicked!"));
else if(c==3) guy.println(F("Right key clicked!"));
break;
case 2:
if(c==1) guy.println("key double clicked!");
else if(c==2) guy.println("Left key long pressed!");
else if(c==3) guy.println("Right key clicked!");
case guyBPrev: //上一个 手势
if(c==1) guy.println(F("key long pressed!"));
else if(c==2) guy.println(F("Left key long pressed!"));
else if(c==3) guy.println(F("Left key clicked!"));
break;
case 4:
if(c==1) guy.println("key long pressed!");
else if(c==2) guy.println("Right key clicked!");
else if(c==3) guy.println("Centre key clicked!");
case guyBSpecial: //特殊 手势
if(c==1) guy.println(F("key clicked and pressed!"));
else if(c==2) guy.println(F("Right clicked at left pressing!"));
else if(c==3) guy.println(F("Centre key double clicked!"));
break;
case 8:
if(c==1) guy.println("key triple clicked!");
else if(c==2) guy.println("Right key long pressed!");
else if(c==3) guy.println("Centre key long pressed!");
case guyBOK: //确定 手势
if(c==1) guy.println(F("key double clicked!"));
else if(c==2) guy.println(F("Right key clicked!"));
else if(c==3) guy.println(F("Centre key clicked!"));
break;
case guyBCancel: //返回 手势
if(c==1) guy.println(F("key triple clicked!"));
else if(c==2) guy.println(F("Right key long pressed!"));
else if(c==3) guy.println(F("Centre key long pressed!"));
break;
default: //未知手势
guy.println(F("Unknown gesture!"));
break;
}
guy.display();
guy.scroll(0,-8);
}
delay(10);
}/* END OF FILE. ReadGuy project.

View File

@@ -6,10 +6,13 @@
*
* @file 2_wifi_config.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-10-14
* @version 1.1
* @date create: 2023-10-14 last modify: 2024-02-26
* @note 本版本主要更新了NTP对时机制, 以及扫描wifi时可以在屏幕上显示到底扫描了多少wifi
* @brief ReadGuy配网服务器 配置并连接附近的WiFi网络演示程序.
编译烧录后, 本程序将使用AP方式配网并在连接到网络时访问NTP服务器来在墨水屏上显示时间.
*** 推荐文章 解决2038千年虫: (本程序未使用该文章内容)
*** https://blog.csdn.net/qdlyd/article/details/131199628
同时开启在STA上的服务器, 供这个WiFi上的用户访问此墨水屏阅读器.
// 注意, 为了避免此项目占用的flash空间过大, 故库内中不再提供配网的相关功能函数.
@@ -40,6 +43,7 @@
#include <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
#include <lwip/apps/sntp.h> // settimeofday 函数 需要
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
@@ -48,7 +52,9 @@ typedef ReadguyDriver::serveFunc event_t ; //存储一个WiFi功能事
void f1(server_t sv); //服务器响应回调函数. 当启动AP配网服务器时, 这些函数将会被调用
void f2(server_t sv);
time_t getNTPTime(); //NTP获取时间的函数
/// @brief NTP获取时间的函数, 必须联网才能调用
time_t getNTPTime();
int conf_status = 0; //标记WiFi配网状态: 当此值为1时, 说明配网程序收到了WiFi SSID和密码信息, 尝试连接.
//此变量为2 说明配网成功了. 连接到了WiFi并显示当前时间.
@@ -81,8 +87,8 @@ void setup(){
scanres = WiFi.scanNetworks(); //开始扫描网络
Serial.println("[readguy] WiFi Scan OK."); //关闭服务器, 尝试连接, 连接成功之后将会在屏幕上显示
guy.println("WiFi Scan OK."); //连接失败则会重新进入循环
Serial.printf("[readguy] WiFi Scan %d OK.\n",scanres); //关闭服务器, 尝试连接, 连接成功之后将会在屏幕上显示
guy.printf("WiFi Scan %d OK.\n",scanres); //连接失败则会重新进入循环
guy.display();
IPAddress local_IP(192,168,4,1); //设置本地AP的IP地址, 网关和子网掩码.
@@ -129,15 +135,21 @@ 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时间
guy.display();
time_t now = getNTPTime(); //下方的函数演示了如何使用NTP来对时
guy.println(ctime(&now));
Serial.println(ctime(&now));
time_t now = getNTPTime(); //下方的函数演示了如何使用NTP来对时. 此函数必须连接上wifi才能调用
now=time(nullptr); //通过Unix API获取时间
struct tm now_tm;
gmtime_r(&now,&now_tm); //转换为GMT时间
guy.println(asctime(&now_tm));
Serial.println(asctime(&now_tm));
localtime_r(&now,&now_tm); //转换为本地时间(包含了时区数据的)
guy.println(asctime(&now_tm));
Serial.println(asctime(&now_tm));
guy.display();
guy.server_setup("现在是联网的STA模式."); //如果没有调用server_end函数 连续调用server_setup将自动结束之前的服务器
@@ -151,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'>"
@@ -216,80 +233,84 @@ void f2(server_t sv){
PSTR("<html><body><meta charset=\"utf-8\">配置失败,缺少信息</body></html>"));
}
/*----------------- 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);
if(_now) break; //成功后立即退出
yield();
Serial.print("Local port: ");
Serial.println(localPort);
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){
if(time(nullptr) < 1577836800){ //时区未设置 (比较时间为2020年1月1日 00:00:00)
setenv("TZ", "CST-8", 1); //设置时区变量 (当前设置为北京时间)
tzset();
}
return _now;
timeval tm_now={_now, 0};
settimeofday(&tm_now,nullptr);
}
return _now;
}
/* END OF FILE. ReadGuy project.

View File

@@ -0,0 +1,138 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file 4_wifi_text_show.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2024-02-28
* @brief ReadGuy通过wifi传输文本并显示.
// 注意, 为了避免此项目占用的flash空间过大, 故库内中不再提供配网的相关功能函数.
// 此示例程序提供了文本传输的功能, 可以通过网页端输入文本并显示到墨水屏上. 自适应字体大小.
// ******** 在进行此示例之前, 不要将 DYNAMIC_PIN_SETTINGS 和 READGUY_ENABLE_WIFI 注释掉. ********
// ******************************** 此示例需要用到 WiFi 的特性. ********************************
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
* Apache License, Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//在这里包含程序需要用到的库函数
#include <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
#include "ctg_u8g2_wqy12.h" //中文字体库
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
//extern const uint8_t ctg_u8g2_wqy12_chinese1[]; //声明中文字体文件, 本字体只包含很少的字, 不建议用
extern const uint8_t ctg_u8g2_wqy12_gb2312[]; //声明中文字体文件, 本字体包含常用字, 体积较大
//extern const uint8_t ctg_u8g2_wqy12[]; //声明中文字体文件, 本字体包含绝大多数的字, 但体积也更大
const lgfx::U8g2font cn_font(ctg_u8g2_wqy12_gb2312); //U8G2格式中文字体转化为LGFX格式字体
typedef ReadguyDriver::ReadguyWebServer* server_t; //类型名太长太繁琐, 使用typedef减短
typedef ReadguyDriver::serveFunc event_t ; //存储一个WiFi功能事件.
const PROGMEM char textShowHtml[]= R"EOF(<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>WiFi传文字</title>
</head>
<body>
<h1>WiFi传文字 </h1>
<p><br /></p>
<form action="/showtext" method="GET"><input type='text' name='txt' placeholder="ReadGuy" maxlength="63" />
<br /><input type='submit' value='!' /><br /></form><br />
<p>ReadGuy<br />Copyright © FriendshipEnder
<a href="https://github.com/fsender/readguy">GitHub</a>
<a href="https://space.bilibili.com/180327370/">Bilibili</a></p>
</body>
</html>
)EOF"; //网页文本
void textShow(server_t sv); //服务器响应回调函数. 当启动AP配网服务器时, 这些函数将会被调用
void textShowGet(server_t sv);
void setup(){
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库. 尽管初始化过程会刷屏, 但此示例不会用到屏幕.
if(guy.width()<guy.height()) guy.setRotation(1);
guy.setFont(&cn_font);
guy.println("Web一键传文本 正在启用热点...");
guy.display();
event_t server_event[2]={
{"一键传文字","/textshow",textShow},
{"","/showtext",textShowGet},
};
guy.ap_setup(); //初始化WiFi AP模式 (可以理解为路由器模式)
guy.server_setup(String(F("配网服务器演示:可以放置自己的链接和回调函数")),server_event,2); //初始化服务器.
//这些服务器响应回调函数会打包进入初始化参数列表中.
//上方的字符串可以在用户访问主页时, 显示在主页的第二行.(作为通知显示, 但并不是通知)
guy.println("名称:readguy 密码:12345678");
guy.println("连接后浏览器访问: 192.168.4.1");
guy.display();
}
void loop(){
guy.server_loop(); //让服务器一直运行
}
// 以下演示了如何向配网服务器添加回调函数.
//其中, sv 参数指向了一个服务器类型的变量. 当有来自客户端的请求时, 需要通过sv来发送响应消息.
void textShow(server_t sv){ //点击链接即可出现发送文本框, 点击发送按钮即可将输入的文本显示到屏幕上
sv->send_P(200, PSTR("text/html"), textShowHtml);
}
void textShowGet(server_t sv){ //注册Web服务函数回调 (就是显示接口)
const String ok_str_1=F("<html><body><meta charset=\"utf-8\">"); //网页前半部分
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)------------------ //显示到墨水屏幕上
guy.setTextSize(1); //先设置为默认字体大小, 方便后续计算
int twidth = guy.textWidth(txt); //获取字符串在当前字体的宽度
if(!twidth) { //宽度数值必须为非0
sv->send(200, String(F("text/html")), ok_str_1+"只包含空格, 不显示. "+ok_str_2);
Serial.println("Arg width == 0."); //字符串为空 或者总宽度为零
return;
}
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){ //字符太短, 字体大小取决于屏幕垂直高度
guy.setTextSize(fsize);
}
else{ //字符可以顶到宽度
guy.setTextSize(tsize, std::max(1.0f,tsize)); //显示的字体大小会根据文本动态变化
} //水平方向太小的话, 垂直方向大小设置为1.0倍(字体原高度)
guy.fillScreen(1);//清屏
guy.setTextDatum(MC_DATUM); //居中显示
guy.drawString(txt,guy.width()/2,guy.height()/2);//居中显示
guy.display(READGUY_SLOW); //慢刷
Serial.print("Show successful:"); //显示成功
Serial.println(txt);
}
else{
Serial.println("No arg."); //找不到txt字段参数
sv->send(200, String(F("text/html")), ok_str_1+"显示失败:缺少参数 "+ok_str_2);
}
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file ctg_u8g2_wqy12.h
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-09-24
* @brief ReadGuy示例字体文件
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
* Apache License, Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef _CTG_U8G2_WQY12_H_FILE
#define _CTG_U8G2_WQY12_H_FILE
#include <Arduino.h>
#include <pgmspace.h>
#ifdef __cplusplus
extern "C" {
#endif
extern const uint8_t ctg_u8g2_wqy12_chinese1[14241] ;
extern const uint8_t ctg_u8g2_wqy12_gb2312[208522] ;
extern const uint8_t ctg_u8g2_wqy12[626234] ;
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -55,9 +55,9 @@
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
extern const uint8_t ctg_wqy9pt_chinese1[]; //声明中文字体文件
extern const uint8_t ctg_u8g2_wqy12_gb2312[]; //声明中文字体文件
const lgfx::U8g2font cn_font(ctg_wqy9pt_chinese1); //U8G2格式中文字体转化为LGFX格式字体
const lgfx::U8g2font cn_font(ctg_u8g2_wqy12_gb2312); //U8G2格式中文字体转化为LGFX格式字体
void setup(){
@@ -69,7 +69,7 @@ void setup(){
guy.setCursor(10,10);
guy.print("中文");
guy.print("你好, 中文");
guy.display();
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@
extern "C" {
#endif
extern const uint8_t ctg_u8g2_wqy12_chinese1[14241] ;
extern const uint8_t ctg_u8g2_wqy12_gb2312[208522] ;
extern const uint8_t ctg_u8g2_wqy12[626234] ;
#ifdef __cplusplus
}

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,32 +64,28 @@
#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){}
readguyImage(ReadguyDriver &_guy):guy(&_guy){
w=guy->width(); h=guy->height();
}
/** @brief 显示图像. use16grey: 为true则使用16灰度,false则不用
* @note 需要提前设置绘制参数, 直接在类的外部 设置此类的成员函数即可.
* 对于不会C++的朋友们, 可以看示例.
* @note 需要提前设置绘制参数, 直接在类的外部 设置此类的成员函数即可. 对于不会C++的朋友们, 可以看示例.
* 注意该函数会调用 guy.display 来刷屏, 因此
* @param baseFs, filename 文件系统和文件名. 必须指定.
* @param x, y 显示在屏幕的什么地方
*
* @param use16grey 0: 使用单色抖动 1:使用16灰度 2:使用16灰度但不抖动
* @param use16grey 0: 使用单色抖动 1:使用16灰度 (如果要开关抖动则需要用setGreyQuality设置)
* */
void drawImageFile(bool use16grey = 0);
/** @brief 将图像绘制到系统缓存内. 必须预分配内存, 并且需要知道分配的大小.
* @note 需要提前设置绘制参数, 直接在类的外部 设置此类的成员函数即可.
* 对于不会C++的朋友们, 可以看示例.
* @note 需要提前设置绘制参数, 直接在类的外部 设置此类的成员函数即可. 对于不会C++的朋友们, 可以看示例.
* 注意该函数会调用 guy.display 来刷屏
* @param baseFs, filename 文件系统和文件名. 必须指定.
* @param x, y 显示在屏幕缓存的什么地方
* @param w, h 开辟的缓存宽度和高度. 最好是和图片的大小相匹配.
@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

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

@@ -40,17 +40,24 @@ namespace guydev_template{
void drv::drv_init(){ //初始化屏幕
//add driver code...
}
void drv::drv_fullpart(bool part){ //初始化慢刷功能
void drv::drv_fullpart(bool part){ //初始化慢刷/快刷功能
if(lastRefresh) return;
//add driver code...
}
void drv::drv_setDepth(uint8_t i){
epdFull=0; iLut = i?(i>15?15:i):15; //如果需要, 改成自己的代码
}
/* 关于这里的f函数指针: f(n)代表访问屏幕缓存的第n字节
若设N=(((屏幕宽度+7)/8)*屏幕高度), 则n的取值范围为 0<=n<N .
比如一个缓存buffer, 有N字节, 那么可以用f(n)=buffer[n]
函数语法为 drv_dispWriter([&](int n)->uint8_t{return buffer[n];},3);
呃 你就把里面的f(n)理解为buffer[n]就行.
*/
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -43,20 +43,45 @@ constexpr int fastRefTime =500; //驱动屏幕快刷时间, 单位毫秒
class drv : public readguyEpdBase {
public:
/** @brief 返回驱动程序ID. 此函数不需要在 cpp 文件内重写
* @return int 直接返回对应宏定义就可以 */
int drv_ID() const { return READGUY_DEV_template; }
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3); //按照函数刷新
void drv_sleep() ; //开始屏幕睡眠
int drv_width() const { return GUY_D_WIDTH; }; //返回显示区域宽度
//int drv_panelwidth() const { return GUY_D_WIDTH; }; //返回缓存的数据宽度
int drv_height() const{ return GUY_D_HEIGHT; }; //返回显示区域高度
void drv_setDepth(uint8_t i); //设置显示颜色深度
/// @brief 初始化屏幕 不过大多数时候此函数只需要初始化启动变量就行
// 比如将模式设为慢刷, 设置为未上电状态 这样下次刷新必为全屏慢刷
void drv_init();
/// @brief 切换慢刷/快刷功能
/// @param part 为1则为快刷, 为0则为慢刷
void drv_fullpart(bool part);
/** @brief 刷屏函数. 程序接口按照此函数刷新
/ @param f 读取像素数据的函数. 这个函数用于替代屏幕缓存数组.
/ 因为有时候屏幕缓存数组不能满足一些显示场景, 比如存储空间复用, 缩放显示等
/ @param m 刷新模式:
/ 1-仅执行前半部分 执行前半部分之后将会向屏幕发送数据后立即退出. (不等busy信号)
/ 2-仅执行后半部分 执行后半部分之后会进行屏幕刷新完之后该执行的操作
/ 3-完整刷屏: 执行1部分->等待busy信号->执行2部分 */
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3);
/// @brief 开始屏幕睡眠/低功耗模式
void drv_sleep() ;
/// @brief 返回显示区域宽度
int drv_width() const { return GUY_D_WIDTH; };
/// @brief 返回显示区域高度
int drv_height() const{ return GUY_D_HEIGHT; };
/** @brief 设置显示颜色深度. 只有在受支持 屏幕上才可以设置灰度
/ @param i 有效值 1~16 0必须为无效 */
void drv_setDepth(uint8_t i);
/** @brief 设置屏幕是否支持连续灰度刷新.
/ @return 设置为 0 不支持灰度 16 支持灰度 -16 支持连续刷新灰度
/ 连续刷新灰度: 先刷深色部分 再刷浅色部分, 原来的深色部分每次刷新都会逐渐越来越深色.
/ 如果不提供连续刷新灰度接口 则使用setDepth函数 先刷浅色部分 再刷深色部分
/ 可以在支持连续刷新的屏幕上烧录范例程序查看效果. 通常都是好于非连续刷新的灰度 */
int drv_supportGreyscaling() const { return 16; }
// 在支持连续灰度刷新的屏幕上 还要额外实现一个函数用于连续刷新接口
/// @brief 设置连续刷新功能函数. 范例可以看guy_420a文件内的示例,分步执行连续刷灰度
//void drv_draw16grey_step(std::function<uint8_t(int)> f, int step);
private:
uint8_t epd_PowerOn=1; //是否上电. 睡眠则设为0
uint8_t epdFull=0; //是partical模式/快速刷新模式 0快刷, 1慢刷
uint8_t iLut=15; //颜色深度
uint8_t iLut=15; //颜色深度 1-15均为有效. 慢刷模式中 此数值为15.
};
}
#endif /* END OF FILE. ReadGuy project.

View File

@@ -45,9 +45,6 @@ default_envs = nodemcuv2
board_build.filesystem = littlefs ; SPIFFS mode
upload_speed = 921600 ; If using USB-JTAG, this selection is dummy
monitor_speed = 115200
build_flags =
-Wall
-Wextra
[env:esp32dev] ; 适用于ESP32的项目配置方案 注意是经典的ESP32...
platform = espressif32
@@ -60,6 +57,8 @@ framework = espidf, arduino
monitor_filters = esp32_exception_decoder
;build_type = debug
build_flags =
-Wall
-Wextra
; -DCORE_DEBUG_LEVEL=4
[env:nodemcuv2] ; 适用于ESP8266的项目配置方案
@@ -72,6 +71,8 @@ monitor_filters = esp8266_exception_decoder
;build_type = debug
build_flags =
-Wall
-Wextra
; -DNON32XFER_HANDLER ;不需要PROGMEM保留字也可以访问flash中的内容
; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;增大可用的HEAP内存
; -fstack-protector ;打开栈溢出保护器
@@ -90,7 +91,9 @@ board_build.partitions = readguy_4MB.csv ; defined
monitor_filters = esp32_exception_decoder
;build_type = debug
build_flags =
build_flags =
-Wall
-Wextra
;-DARDUINO_USB_MODE=1
;-DARDUINO_USB_CDC_ON_BOOT=1 ; 是否需要使用USB串口调试如果需要调试则打开否则禁用
; 如果打开了这个选项但是不连接串口在有串口输出的地方会卡顿1秒左右
@@ -117,7 +120,9 @@ board_build.flash_mode = dio
board_build.partitions = readguy_16MB.csv
monitor_filters = esp32_exception_decoder
build_flags =
build_flags =
-Wall
-Wextra
;-DARDUINO_USB_MODE=1
;-DARDUINO_USB_CDC_ON_BOOT=1 ; 是否需要使用USB串口调试如果需要调试则打开否则禁用
; 如果打开了这个选项但是不连接串口在有串口输出的地方会卡顿1秒左右
@@ -142,6 +147,31 @@ board_build.f_flash = 80000000L
board_build.flash_mode = dio
board_build.partitions = readguy_4MB.csv ; 2MB的芯片就选readguy_2MB_noOTA.csv
build_flags =
-Wall
-Wextra
;-DARDUINO_USB_MODE=1
;-DARDUINO_USB_CDC_ON_BOOT=1 ; 是否需要使用USB串口调试如果需要调试则打开否则禁用
; 如果打开了这个选项但是不连接串口在有串口输出的地方会卡顿1秒左右
; 合宙无串口开发板请选择此选项为1.
-DCORE_DEBUG_LEVEL=1 ; None 0, Error 1, Warn 2, Info 3, Debug 4, Verbose 5
[env:esp32c3_no_uart] ;适用于ESP32C3 的项目配置方案.
platform = espressif32 ;注意在使用不带串口芯片的ESP32C3时, 尽量不要使用引脚18和19.
board = esp32-c3-devkitm-1 ;那俩是连接的板载USB串口 (USB-CDC, 可以下载程序或是当免驱串口)
framework = espidf, arduino ;合宙你真该死啊出这种没串口芯片的ESP32C3 甚至旧版本arduino无法编程!
board_build.f_cpu = 160000000L ;芯片速率默认160MHz, 不支持高频240MHz.
;board_build.flash_size=2MB ;2MB的芯片就选readguy_2MB_noOTA.csv
board_build.flash_size=4MB ;根据你自己的改, 不得小于4MB. 2MB的芯片就选readguy_2MB_noOTA.csv
board_build.f_flash = 80000000L
board_build.flash_mode = dio
board_build.partitions = readguy_4MB.csv ; 2MB的芯片就选readguy_2MB_noOTA.csv
build_flags =
-Wall
-Wextra
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; 是否需要使用USB串口调试如果需要调试则打开否则禁用
; 如果打开了这个选项但是不连接串口在有串口输出的地方会卡顿1秒左右
; 合宙无串口开发板请选择此选项为1.
-DCORE_DEBUG_LEVEL=1 ; None 0, Error 1, Warn 2, Info 3, Debug 4, Verbose 5
@@ -149,10 +179,14 @@ build_flags =
platform = espressif32
board = nodemcu-32s2
framework = espidf, arduino
board_build.f_cpu = 240000000L
build_type = debug
board_build.f_cpu = 160000000L
board_build.flash_size=4MB ;根据你自己的改, 不得小于4MB
board_build.f_flash = 80000000L
board_build.flash_mode = dio
board_build.partitions = readguy_4MB.csv ; defined
build_flags =
-Wall
-Wextra
-DCORE_DEBUG_LEVEL=1 ; None 0, Error 1, Warn 2, Info 3, Debug 4, Verbose 5
monitor_filters = esp32_exception_decoder

View File

@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1E0000,
app1, app, ota_1, 0x1F0000,0x1E0000,
spiffs, data, spiffs, 0x3D0000,0x20000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1E0000
5 app1 app ota_1 0x1F0000 0x1E0000
6 spiffs data spiffs 0x3D0000 0x20000
7 coredump data coredump 0x3F0000 0x10000

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
"type": "git",
"url": "https://github.com/fsender/readguy"
},
"version": "1.3.3",
"version": "1.5.0",
"frameworks": "arduino",
"platforms": ["espressif32", "espressif8266"],
"headers": "readguy.h",
@@ -19,7 +19,7 @@
"libArchive": false
},
"license": "Apache-2.0",
"homepage": "b站视频",
"homepage": "https://www.bilibili.com/video/BV1f94y187wz/",
"dependencies": {
"name": "lovyan03/LovyanGFX",
"version": ">=1.1.9"

View File

@@ -1,5 +1,5 @@
name=readguy
version=1.3.3
version=1.5.0
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_config_host.h"
// initalize static counter
@@ -67,7 +68,7 @@ guy_button::guy_button() {
//id = _nextID++;
}
void guy_button::begin(uint8_t _pin, std_U8_function_U8 f, bool activeLow /* = true */) {
void guy_button::begin(uint8_t _pin, std_U8_function_U8 f, bool activeLow/*=true*/) {
//pin = attachTo;
//id = _nextID++;
_pressedState = activeLow ? LOW : HIGH;
@@ -75,18 +76,19 @@ void guy_button::begin(uint8_t _pin, std_U8_function_U8 f, bool activeLow /* = t
prev_state = state ;
get_state_cb = f;
pin = _pin;
state = get_state_cb(pin);
min_debounce =25; //去抖时间
long_press_ms =300; //长按持续时间+双击识别间隔最大时间
long_repeat_ms =200; //长按连按间隔时间
multibtn =0;
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;
}
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;
}
@@ -115,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) {
@@ -144,7 +146,7 @@ void guy_button::loop() {
longclick_detected = true;
}
// is the button released and the time has passed for multiple clicks?
} else if (now - click_ms > (multibtn?min_debounce:long_press_ms)) {
} else if (now - click_ms > (scanDT?double_press_ms:min_debounce)) {
// was there a longclick?
if (longclick_detected) {
// was it part of a combination?
@@ -157,17 +159,14 @@ void guy_button::loop() {
// determine the number of single clicks
} else if (click_count > 0) {
switch (click_count) {
case 1:
last_click_type = GUYBUTTON_single_click;
break;
case 2:
last_click_type = GUYBUTTON_double_click;
break;
case 3:
last_click_type = GUYBUTTON_triple_click;
break;
if(scanDT){
switch (click_count) {
case 1: last_click_type = GUYBUTTON_single_click; break;
case 2: last_click_type = GUYBUTTON_double_click; break;
case 3: last_click_type = GUYBUTTON_triple_click;
}
}
else last_click_type = GUYBUTTON_single_click; //此时若click_count>1 视为抖动
was_pressed = true;
}
// clean up

View File

@@ -63,28 +63,25 @@ SOFTWARE.
#include "Arduino.h"
#include <stdint.h>
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP8266)
#include <functional>
#endif
#include <Arduino.h>
/////////////////////////////////////////////////////////////////
#define GUYBUTTON_empty 0
#define GUYBUTTON_single_click 1
#define GUYBUTTON_double_click 2
#define GUYBUTTON_triple_click 3
#define GUYBUTTON_long_click 4
#define GUYBUTTON_xlong_click 5
#define GUYBUTTON_xxlong_click 6
#define GUYBTN_READ_TIMEOUT 100
#define GUYBTN_LOOP_TIMEOUT 10
#define GUYBUTTON_empty 0 //没按下
#define GUYBUTTON_single_click 1 //单击
#define GUYBUTTON_double_click 2 //双击
#define GUYBUTTON_triple_click 3 //三击
#define GUYBUTTON_long_click 4 //长按
#define GUYBUTTON_xlong_click 5 //点击后接长按
#define GUYBUTTON_xxlong_click 6 //双击后接长按
#define GUYBTN_READ_TIMEOUT 100 //读取延时
#define GUYBTN_LOOP_TIMEOUT 10 //循环扫描延时
class guy_button{
public:
uint16_t min_debounce ; //去抖时间
uint16_t long_press_ms ; //长按持续时间+双击识别间隔最大时间
uint16_t long_press_ms ; //长按持续时间
uint16_t double_press_ms ; //双击识别间隔最大时间
uint16_t long_repeat_ms ; //长按连按间隔时间
protected:
uint8_t pin = 255; //未定义引脚
@@ -92,7 +89,7 @@ class guy_button{
uint8_t prev_state;
uint8_t click_count = 0;
uint8_t _pressedState;
uint8_t multibtn; //是否为多个按钮, 可自己设置
uint8_t scanDT; //是否为多个按钮, 可自己设置
uint8_t last_click_type = GUYBUTTON_empty;
volatile uint8_t lk = 255;
//int id;
@@ -108,22 +105,45 @@ 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:
guy_button();
/// @brief 初始化
/// @param _pin 引脚ID. 传入的引脚在内部将会使用digitalRead函数实现
/// @param activeLow 设置为true时, 当读取到低电平视为按下
void begin(uint8_t _pin, bool activeLow = true){
begin(_pin,[](uint8_t p, bool)->uint8_t { return digitalRead(p); },activeLow);
}
/// @brief 初始化
/// @param _pin 引脚ID. 传入的引脚在内部将会使用digitalRead函数实现
/// @param f 触发函数: 当activeLow为false时, 返回1表示按下, 0表示没按下 为true时相反
/// @note 默认的 f 是 匿名函数 [](uint8_t p)->uint8_t { return digitalRead(p); }
/// 如果自己指定了函数而且没有用到内置的参数, 那么 _pin 参数可能是没有任何用处的
void begin(uint8_t _pin, std_U8_function_U8 f, bool activeLow = true);
/// @brief 设置长按连按触发模式
/// @param trigMode 0:单次长按 1:连续长按
void setLongRepeatMode(bool trigMode) { trig_mode = trigMode; }
/// @brief 长按了多久按钮
unsigned int wasPressedFor() const { return down_time_ms; }
/// @brief 返回是否处于被按下的状态 受loop扫描的限制, 如果没loop扫描则可以先手动调用扫描后使用
bool isPressed() const { return (state == _pressedState); }
/// @brief 读取原始的按钮状态 (不去抖动), 此函数不受loop扫描的限制
bool isPressedRaw(); // { return (get_state_cb(pin) == _pressedState); }
/// @brief 曾经按下的状态 是否是点击后立即松开
bool wasPressed(){ if(was_pressed){ was_pressed = false; return true; } return false; }
/// @brief 连击了几下
uint8_t getNumberOfClicks() const{ return click_count;}
/// @brief [已经弃用] 获取上次按钮的按下数据. 返回按钮状态(按钮状态参考read函数的说明)
uint8_t getType() const { return last_click_type; }
/// @brief 读取按钮的按下数据. 返回按钮状态 0没按 1单击 2双击 3三击 4长按 5点击后长按 6双击后长按
uint8_t read();
/// @brief 连续循环扫描按钮. 必须多次反复调用, 最好是单独开一个task来实现
void loop();
void setMultiBtn(uint8_t btns) { multibtn = btns; }
/// @brief 设置是否识别双击 三连击等高级手势
/// @param scan =1识别双击或三击, =0则不识别双击或三击等需要延时返回的情况
void enScanDT(uint8_t scan) { scanDT = scan; }
/* void setMinDebounce(short n) { min_debounce =n;} //去抖时间
void setLongPressMs(short n) { long_press_ms =n;} //长按持续时间+双击识别间隔最大时间
void setLongRepeat(short n) { long_repeat_ms =n;} //长按连按间隔时间

View File

@@ -3,14 +3,15 @@
* extra/artset/reward , Bilibili .
* QQ群: 926824162 ()
* : .
* @file guy_epaper_config.h
* @file guy_config_host.h
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief EPD驱动基础配置文件. .
* @brief readguy . guy_driver_config.h
* @version 1.0
* @date 2023-09-21
* @date create: 2025/4/7
* last modify: 2025/4/7
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
* Copyright (c) 2022-2025 FriendshipEnder
*
* Apache License, Version 2.0
*
@@ -26,20 +27,113 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _READGUY_CONFIG_HOST_H_FILE
#define _READGUY_CONFIG_HOST_H_FILE
#ifndef _GUY_E_PAPER_CONFIG_H_FILE
#define _GUY_E_PAPER_CONFIG_H_FILE
#if __has_include("readguy_user_config.h")
#undef DYNAMIC_PIN_SETTINGS
#undef READGUY_NVS_PROJECTNAME
#undef READGUY_NVS_CONFIGKEY
#undef READGUY_NVS_DRIVERKEY
#undef READGUY_ENABLE_WIFI
#undef READGUY_ESP_ENABLE_WIFI
#undef READGUY_CONF_AP_SSID
#undef READGUY_CONF_AP_PASS
#undef READGUY_USE_DEFAULT_ICON
#undef READGUY_MDNS_SERVICE
#undef READGUY_UPDATE_SERVER
#undef READGUY_ENABLE_I2C
#undef READGUY_ENABLE_SD
#undef READGUY_USE_LITTLEFS
#undef READGUY_ESP8266_EEPROM_OFFSET
#undef READGUY_ALLOW_DC_AS_BUTTON
#undef READGUY_ALLOW_EPDCS_AS_BUTTON
#undef READGUY_ALLOW_SDCS_AS_BUTTON
#undef READGUY_DEFAULT_MIN_DEBOUNCE_MS
#undef READGUY_LONG_PRESS_MS
#undef READGUY_DOUBLE_PRESS_MS
#undef READGUY_LONG_REPEAT_MS
#undef BTN_LOOPTASK_STACK
#undef BTN_LOOPTASK_CORE_ID
#undef BTN_LOOPTASK_PRIORITY
#undef BTN_LOOPTASK_DELAY
#undef ESP8266_SPI_FREQUENCY
#undef ESP32_DISP_FREQUENCY
#undef ESP32_SD_SPI_FREQUENCY
#undef ESP32_SD_MMC_FREQUENCY
#undef READGUY_SERIAL_DEBUG
#undef READGUY_shareSpi
#undef READGUY_epd_type
#undef READGUY_epd_mosi
#undef READGUY_epd_sclk
#undef READGUY_epd_cs
#undef READGUY_epd_dc
#undef READGUY_epd_rst
#undef READGUY_epd_busy
#undef READGUY_sd_miso
#undef READGUY_sd_mosi
#undef READGUY_sd_sclk
#undef READGUY_sd_cs
#undef READGUY_i2c_sda
#undef READGUY_i2c_scl
#undef READGUY_btn1
#undef READGUY_btn2
#undef READGUY_btn3
#undef READGUY_bl_pin
#undef READGUY_user1
#undef READGUY_user2
#undef READGUY_user3
#undef READGUY_user4
#undef READGUY_user5
#undef READGUY_user6
#undef READGUY_user7
#undef READGUY_user8
#undef READGUY_user9
#undef READGUY_user10
#undef READGUY_rtc_type
#undef MEPD_DEBUG_WAVE
#undef MEPD_DEBUG_WAITTIME
#undef FLOYD_STEINBERG_DITHERING
#undef FLOYD_DITHERING_16GREY
#undef READGUY_583A_DUAL_BUFFER
#include "readguy_user_config.h"
#else
#include "guy_driver_config.h"
#endif
#ifdef ESP8266 //应用于
#define _READGUY_PLATFORM "ESP8266"
//#define READGUY_IDF_TARGET_WITHOUT_FSPI //该器件不能使用fspi
#else
#ifdef CONFIG_IDF_TARGET_ESP32
#define _READGUY_PLATFORM "ESP32"
#define READGUY_IDF_TARGET_WITH_VSPI //该器件拥有vspi
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define _READGUY_PLATFORM "ESP32S2"
#define READGUY_IDF_TARGET_MATRIX_SDIO //该器件支持矩阵SDIO
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#define _READGUY_PLATFORM "ESP32S3"
#define READGUY_IDF_TARGET_MATRIX_SDIO //该器件支持矩阵SDIO
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#define _READGUY_PLATFORM "ESP32C3"
#define READGUY_IDF_TARGET_MATRIX_SDIO //该器件支持矩阵SDIO
#elif defined(CONFIG_IDF_TARGET_ESP32C6) //新增 ESP32C6 (暂未测试)
#define _READGUY_PLATFORM "ESP32C6"
#define READGUY_IDF_TARGET_MATRIX_SDIO //该器件支持矩阵SDIO
#else
#define _READGUY_PLATFORM "Unknown" //Prepare for ESP32-C6, H7, etc.
#warning Unknown platform! Readguy will run with unexpected (maybe hardware) errors!
#endif
#endif
//#define MEPD_DEBUG_WAVE 16 //取消此行注释来监视SPI的通信数据 (用于debug), 可以查看主控和屏幕的通信数据
//#define MEPD_DEBUG_WAITTIME //显示墨水屏的刷新时间, 单位是毫秒
#define FLOYD_STEINBERG_DITHERING //默认使用更好的floyd steinberg抖动算法,取消注释则用bayer图案抖动算法
#define FLOYD_DITHERING_16GREY //使用更好的floyd steinberg抖动算法显示16阶灰度,取消注释则使用阈值填充
// ***********************************************************************
//设备设置: 取消对应设备就可以直接禁用掉对应设备
//非常不建议用户在应用程序中禁用掉设备. 实测根本不会节省很多的空间. 如果要节省代码占用的flash空间,
//请在字库上下功夫.
//要想省空间, 请关闭wifi配置功能并避免使用wifi, 或者在程序中 (尤其是图片素材和字库素材上) 下功夫.
//如果程序不想支持此型号(为了节省flash),请注释掉这一行, 实测每个设备只能节省100字节左右的flash空间
#define READGUY_DEV_154A 0 //1.54寸标准, 甘草酸不酸使用的1.54默认屏幕型号. 1.54汉朔价签也选这个
#define READGUY_DEV_154B 1 //1.54寸Lilygo, lilygo的1.54触摸和1.54背光墨水屏. GDEH0154D67
@@ -59,7 +153,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
@@ -69,4 +163,4 @@
#define EPD_DRIVERS_NUM_MAX 21 //此选项请不要取消注释掉, 有几个屏幕就写多少.
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */
Copyright (C) 2025 FriendshipEnder. */

View File

@@ -7,15 +7,17 @@
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief readguy 基础配置文件. 用户可以根据自己对库的编译需求来修改此文件.
*
* 1.5.0 新增!
* 在项目目录里拷贝本文件并重命名为 "readguy_user_config.h" 即可使本配置文件仅在当前项目有效
*
* 如果你希望在程序代码内包含引脚定义, 请参考此文件下方的注释来确定并编译
* 这样就可以只针对你设计的这一个硬件来设定引脚功能.
* 关于屏幕配置, 请参考 guy_epaper/guy_epaper_config.h 文件来配置到底哪些屏幕型号的屏幕会被包含进来
* 关于屏幕配置, 请参考最下方(284行到文件末尾)来配置到底哪些屏幕型号的屏幕会被包含进来
* 可以禁用WiFi功能来减少程序的flash消耗
* @version 1.1
* @date create: 2023-09-21
* last modify: 2023-11-11
* last modify: 2025/4/7
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
@@ -39,22 +41,26 @@
// *** Do not include any file here!
/*
#define _EPD_CS_PIN 15 //D8, 显示屏的片选引脚
#define _EPD_DC_PIN 5 //D1, 显示屏的DC引脚
#define _EPD_RST_PIN -1 //reserved 保留的重置引脚
#define _EPD_BUSY_PIN 4 //D2 显示屏的Busy引脚
#define _SD_CS_PIN 0 //D3, SD卡的片选引脚
#define _BtnL 5 //D1, 左键
#define _BtnM 12 //中间键 D6引脚
#define _BtnR 2 //D4 右键
/**************************** 请不要取消注释这里! *****************************
********* 这里的设置项是无效的, 仅仅用于标注一些开发板的引脚预定义数据. *********
********** 部分热门的, 创新的带墨水屏开发板的引脚定义可能会罗列在这里. **********
*****************************************************************************
(无效设置项) #define _EPD_CS_PIN 15 //D8, 显示屏的片选引脚
(无效设置项) #define _EPD_DC_PIN 5 //D1, 显示屏的DC引脚
(无效设置项) #define _EPD_RST_PIN -1 //reserved 保留的重置引脚
(无效设置项) #define _EPD_BUSY_PIN 4 //D2 显示屏的Busy引脚
(无效设置项) #define _SD_CS_PIN 0 //D3, SD卡的片选引脚
(无效设置项) #define _BtnL 5 //D1, 左键
(无效设置项) #define _BtnM 12 //中间键 D6引脚
(无效设置项) #define _BtnR 2 //D4 右键
//对于甘草酸不酸的新版本板子:
//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,28 +70,102 @@
// ------------------ 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 NVS存储配置数据时用的项目名. 必须小于8个字符, 为节省内存, 项目依赖此处的内存块仅分配了8字节.
#define READGUY_NVS_PROJECTNAME "readguy"
/// @brief NVS存储配置数据时用的配置数据键名. 必须小于8个字符, 为节省内存, 项目依赖此处的内存块仅分配了8字节.
#define READGUY_NVS_CONFIGKEY "hwconf"
/// @brief 当使用自定义驱动 (customDriver) 时会通过NVS访问驱动代码. 必须小于8个字符.
/// @note 有关自定义驱动, 可以参考 readme 中提到的在 2.0 版本即将到来的特性.
#define READGUY_NVS_DRIVERKEY "driver"
/// @brief 启用WIFI配网功能.必须先启用 #define DYNAMIC_PIN_SETTINGS. 此选项对 ESP32xx 会减少大量可用flash.
/// @note 注释掉它会破坏跨平台特性
#define READGUY_ENABLE_WIFI
/// @brief 启用I2C功能. 可用于联网时钟, 温度计, 陀螺仪等外设. 目前暂不支持库内使用类似函数. 仅可以提供引脚定义
#define READGUY_ENABLE_I2C
/// @brief AP配网热点的名称和密码. 不可注释
#define READGUY_CONF_AP_SSID "readguy"
#define READGUY_CONF_AP_PASS "12345678"
/** @brief (即将推出) 启用SD卡功能. 开启此功能将会使用内置SD卡管理功能. 关闭后仅可保存SD卡用到的引脚.
@note 会破坏兼容性. 若没有启用通用的SD卡驱动程序, 那么那些跨屏台编译的程序将无法用guyFS读取到SD卡.
若用户程序希望能从外部加载SD卡, 可以使用getSdMiso()等函数获取SD卡的Miso等引脚, 再由用户程序初始化SD卡. */
/** @brief 使用服务器或者WIFI配网时, 在网页上显示项目默认的图标, 默认图标会额外占用1150字节的flash空间
/ 如果想用自己的图标数据库, 请参考配网示例, 在配置时候加入下方的代码. 此处提供了代码.
``` C++
typedef ReadguyDriver::ReadguyWebServer* server_t; //类型名太长太繁琐, 使用typedef减短
typedef ReadguyDriver::serveFunc event_t ; //存储一个WiFi功能事件.
const uint8_t icon[] = {}; //你的图标数据. 可直接把 *.ico 格式图标文件转化为字节数组, 拷贝到此处
void sendIcon(server_t sv){ sv->send_P(200, "image/x-icon", (const char *)icon,sizeof(icon)); }
event_t server_event={"","/favicon",sendIcon};
guy.server_setup(String("Custom Icon"),&server_event,1); //初始化服务器.
``` */
//#define READGUY_USE_DEFAULT_ICON
/** @brief 使用MDNS域名服务. 在设备联网或者打开热点之后, 可以通过域名 readguy.local 来访问配网主页
/ 关闭本功能可节省少许flash. 如需自定义服务名称, 则您可更改此处的字符串, 访问 <自定义名称>.local */
//#define READGUY_MDNS_SERVICE "readguy"
/// @brief 使用固件更新服务. 关闭本功能可节省少许flash. 如需自定义链接名称, 则您可更改此处的字符串.
//#define READGUY_UPDATE_SERVER "固件更新"
/** @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卡. */
#define READGUY_ENABLE_SD
/// @brief 使用LittleFS作为片上文件系统, 注释此行则用SPIFFS(功能少, 不好用)
#define READGUY_USE_LITTLEFS 1
/** @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 1280
#define BTN_LOOPTASK_STACK 1536
#else
#define BTN_LOOPTASK_STACK 1024
#endif
@@ -107,9 +187,28 @@
/// @brief ESP32驱动SD卡的速率. 当ESP32在与SD卡共享SPI时, 屏幕依据此处的速率.
#define ESP32_SD_SPI_FREQUENCY 20000000
#define ESP32_SD_MMC_FREQUENCY 40000
/// @brief debug专用, 请保持处于注释状态. 正常开机从NVS读取引脚配置数据, 取消注释则每次开机需要重新配置
//#define INDEV_DEBUG 1
//#define READGUY_INDEV_DEBUG 1
/// @brief 串口显示刷屏功能等的信息. 如果是对flash大小要求十分敏感, 或者希望减少串口数据, 可以关闭
#define READGUY_SERIAL_DEBUG
/// @brief 对于单缓存的5.83屏幕,启用双缓存支持. 相当不建议注释掉,否则不能刷白色
#define READGUY_583A_DUAL_BUFFER
/// @brief 取消此行注释来监视SPI的通信数据 (用于debug), 可以查看主控和屏幕的通信数据
//#define MEPD_DEBUG_WAVE 16
/// @brief 显示墨水屏的刷新时间, 单位是毫秒
//#define MEPD_DEBUG_WAITTIME
/// @brief 默认使用更好的floyd steinberg抖动算法,取消注释则用bayer图案抖动算法
#define FLOYD_STEINBERG_DITHERING
/// @brief 使用更好的floyd steinberg抖动算法显示16阶灰度,取消注释则使用阈值填充
#define FLOYD_DITHERING_16GREY
#ifndef DYNAMIC_PIN_SETTINGS
#ifdef ESP8266
@@ -131,12 +230,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为未指定
@@ -162,10 +272,25 @@
#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型号(待定, 还没用上)
#elif defined(READGUY_ENABLE_WIFI)
#define READGUY_rtc_type 0 //使用的RTC型号. RTC 将在2.0实装 保留是为了兼容性 让代码更简单维护
// ******************************************************************
// ********************** 以下内容不建议用户更改 **********************
// ******************************************************************
#elif defined(READGUY_ENABLE_WIFI) // READGUY_ENABLE_WIFI 依赖 DYNAMIC_PIN_SETTINGS
#define READGUY_ESP_ENABLE_WIFI //使用WIFI进行配网等功能
#endif
#undef READGUY_ENABLE_WIFI

View File

@@ -51,6 +51,7 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -46,6 +46,7 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -96,30 +96,34 @@ const unsigned char drvSSD168x::WS_20_30[42] =
};
const unsigned char drvSSD168x::VSH_table[32]=
{/*0x00,0xbc,0xc1,0xc6,0xcb,0x23,0x26,0x28,0x2a,0x2c,0x2e,0x32,0x35,0x39,0x3d,0x41,*/
0x00,0x24,0x28,0x2c,0x30,0x33,0x36,0x38,0x3a,0x3c,0x3e,0x40,0x42,0x44,0x45,0x46,
0x00,0xac,0xb0,0xb6,0xbd,0xc5,0xce,0x26,0x2c,0x33,0x3a,0x42,0x4a,0x4a,0x4a,0x4a,
0x00,0x41,0x3c,0x37,0x32,0x2d,0x28,0x23,0xcb,0xc6,0xc1,0xbc,0xb7,0xb2,0xb0,0xae};
//以下代码均为我 FriendshipEnder 原创, 呵呵哒~~
void drvSSD168x::SetLut() {
static const unsigned char VSH_repeat[16]={
0,1,1,1,1,1,1,1,1,1,1,1,1,2,3,4
};
unsigned int i;
guy_epdCmd(0x3f);
guy_epdParam(greyScaling?0x07:0x22);
guy_epdParam(greyScaling?0x07:0x02); //greyScaling?0x07:0x22
guy_epdCmd(0x03); // gate voltage
guy_epdParam(0x17);
guy_epdParam(0x17); //0x17 orig
guy_epdCmd(0x04); // source voltage
guy_epdParam(greyScaling?pgm_read_byte(VSH_table+16+iLut):pgm_read_byte(VSH_table+iLut));
guy_epdParam(0xA8); // VSH2
guy_epdParam(0x32); // VSL
guy_epdCmd(0x2c); // VCOM
guy_epdParam(0x28);
unsigned char i;
guy_epdParam(0x20); //0x28 orig
guy_epdCmd(0x32);
if(_part){
for(i=0;i<5;i++){
guy_epdParam(i==1?0x80:(i==(greyScaling?0:2)?0x40:0x00));
for(int j=0;j<11;j++) guy_epdParam(0);
}
guy_epdParam(greyScaling?1:iLut);
//guy_epdParam(greyScaling?1:iLut);
guy_epdParam(greyScaling ? 1 : pgm_read_byte(VSH_repeat + iLut));
for(i=0;i<83;i++) guy_epdParam(0);
guy_epdParam(0x22);
for(i=0;i<8;i++) guy_epdParam(0);
@@ -145,7 +149,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;
}
@@ -160,6 +164,8 @@ void drvSSD168x::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单
for(int i=0;i<10;i++) guy_epdParam(i==5?0x40:0x00);
guy_epdCmd(0x3C); //BorderWavefrom
guy_epdParam(0x80);
guy_epdCmd(0x18); //BorderWavefrom
guy_epdParam(0x80);
guy_epdCmd(0x22);
guy_epdParam(0xC0);
@@ -172,9 +178,10 @@ void drvSSD168x::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单
epd_PowerOn=1;
_part=0;
iLut=15;
guy_epdBusy(20);
}
guy_epdCmd(0x12); //SWRESET
guy_epdBusy(10);
guy_epdBusy(20);
guy_epdCmd(0x01); //Driver output control
guy_epdParam((epdHeight-1)&0xff);

View File

@@ -51,6 +51,7 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -51,6 +51,7 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -35,34 +35,380 @@
#include "guy_583A.h"
#ifdef READGUY_DEV_583A
namespace guydev_583A{
//以下代码均为我 FriendshipEnder 原创, 呵呵哒~~
/*LUT格式
重复次数xnnnxnnn...(000=0,001=VSH,010=VSL,011=VSH_LV,100=VSL_LV,101=VSH_LVX,110=VSL_LVX,111=浮动),持续时间.......
0x01, 0x00, 0x00, 0x1B, 0x10, 0x0F, 0x0A, 0x0A, 0x0F, 0x10, 0x1B
0x01, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x01, 0x21, 0x21, 0x21, 0x21, 0x1B, 0x10, 0x0F, 0x0A, 0x0A, 0x0F, 0x10, 0x1B
0x01, 0x12, 0x12, 0x12, 0x10, 0x14, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x00
0x01, 0x11, 0x21, 0x21, 0x22, 0x1B, 0x10, 0x0F, 0x0A, 0x0A, 0x0F, 0x10, 0x1B
0x01, 0x21, 0x21, 0x21, 0x20, 0x14, 0x01, 0x04, 0x01, 0x04, 0x01, 0x04, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x01, 0x12, 0x12, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02
0x01, 0x21, 0x21, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
*/
const PROGMEM unsigned char drv::lut_20_LUTC[] =
{ 0x01, 0x00, 0x00, 0x1B, 0x10, 0x0F, 0x0A, 0x0A, 0x0F, 0x10, 0x1B};
const PROGMEM unsigned char drv::lut_20_LUTCFAST[] =
{ 0x01, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const PROGMEM unsigned char drv::lut_21_LUTB[] =//000
{ 0x01, 0x21, 0x21, 0x21, 0x21, 0x1B, 0x14, 0x13, 0x12, 0x00, 0x00, 0x10, 0x1E};
const PROGMEM unsigned char drv::lut_21_LUTBFAST[] =
{ 0x01, 0x12, 0x12, 0x12, 0x10, 0x1f, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x00};
const PROGMEM unsigned char drv::lut_22_LUTW[] =//011
{ 0x01, 0x11, 0x21, 0x21, 0x22, 0x1B, 0x14, 0x13, 0x12, 0x00, 0x00, 0x10, 0x1E};
const PROGMEM unsigned char drv::lut_22_LUTWFAST[] =
{ 0x01, 0x21, 0x21, 0x21, 0x20, 0x1f, 0x01, 0x04, 0x01, 0x04, 0x01, 0x04, 0x00};
const PROGMEM unsigned char drv::lut_23_LUTG1[] =//001
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const PROGMEM unsigned char drv::lut_24_LUTG2[] =//010
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const PROGMEM unsigned char drv::lut_25_LUTR0[] =//100//WW
{ 0x01, 0x12, 0x12, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03};
const PROGMEM unsigned char drv::lut_26_LUTR1[] =//101//BB
{ 0x00/*0x01*/, 0x21, 0x21, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02};
const PROGMEM unsigned char drv::lut_27_LUTR2[] =//110
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const PROGMEM unsigned char drv::lut_28_LUTR3[] =//111//局刷
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const PROGMEM unsigned char drv::lut_29_LUTXON[] =
{ 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const PROGMEM unsigned char drv::customGrey[] =
{ 0x00, 0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0a, 0x0c, 0x0e, 0x11, 0x14, 0x17, 0x1a, 0x1e,
0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
const PROGMEM unsigned char *drv::luts[10]={
lut_20_LUTC,lut_21_LUTB,lut_22_LUTW,lut_23_LUTG1,lut_24_LUTG2,
lut_25_LUTR0,lut_26_LUTR1,lut_27_LUTR2,lut_28_LUTR3,lut_29_LUTXON
};
const unsigned char *drv::luts_fast[10]={
lut_20_LUTCFAST,lut_21_LUTBFAST,lut_22_LUTWFAST,lut_23_LUTG1,lut_24_LUTG2,
lut_25_LUTR0,lut_26_LUTR1,lut_27_LUTR2,lut_28_LUTR3,lut_29_LUTXON
};
void drv::drv_init(){ //初始化屏幕
//add driver code...
#ifdef READGUY_583A_DUAL_BUFFER
drv::drv(){ //构造时, 分配内存.
buf_2nd=(uint8_t *)malloc(GUY_D_WIDTH*GUY_D_HEIGHT/8);
if(buf_2nd) {
Serial.println(F("[GUY MEM] allocate 33600B for 5.83' EPD."));
//memset(buf_2nd,0,GUY_D_WIDTH*GUY_D_HEIGHT/8);
}
}
void drv::drv_fullpart(bool part){ //初始化慢刷功能
drv::~drv(){ //构造时, 分配内存.
if(buf_2nd) free(buf_2nd);
Serial.println(F("[GUY MEM] unallocate 33600B."));
}
#endif
void drv::sendLut(int lutid){
int lutlen=13,lutlen2=247;
if(lutid==0){lutlen=11;lutlen2=209;}
else if(lutid==9){lutlen=10;lutlen2=190;}
guy_epdCmd(0x20+lutid);
if(epdFull){
for(int i=0;i<lutlen;i++){
guy_epdParam(pgm_read_byte(luts[lutid]+i));
}
}
else{
for(int i=0;i<lutlen;i++){
if(lutid==1 && i==5) guy_epdParam(pgm_read_byte(customGrey+iLut+greyscalingHighQuality));
else
guy_epdParam(pgm_read_byte(luts_fast[lutid]+i));
}
}
for(int i=0;i<lutlen2;i++){
guy_epdParam(0);
}
}
void drv::epd_init(){
if(!epd_PowerOn) Reset(); //设定为已上电
guy_epdCmd(0x01);
guy_epdParam(0x37);
guy_epdParam(0x00);
guy_epdParam(0x07);
guy_epdParam(0x07);
guy_epdCmd(0x00);
guy_epdParam(0xcf);
guy_epdParam(0x88);
guy_epdCmd(0x06);
guy_epdParam(0xc7);
guy_epdParam(0xcc);
guy_epdParam(0x28);
guy_epdCmd(0x30);
guy_epdParam(0x3a);
guy_epdCmd(0x41);
guy_epdParam(0x00);
guy_epdCmd(0x50);
guy_epdParam(0x77);
guy_epdCmd(0x60);
guy_epdParam(0x22);
guy_epdCmd(0x61);
guy_epdParam(0x02);
guy_epdParam(0x58);
guy_epdParam(0x01);
guy_epdParam(0xc0);
guy_epdCmd(0x82);
guy_epdParam(0x28);
for(int i=0;i<10;i++){
sendLut(i);
}
guy_epdCmd(0xe5);
guy_epdParam(0x03);
}
void drv::epd_sendZoneInfo(){
guy_epdCmd(0x91);
//send area info
guy_epdCmd(0x90);
guy_epdParam(0x00);
guy_epdParam(0x00);
guy_epdParam(0x02);
guy_epdParam(0x57);
guy_epdParam(0x00);
guy_epdParam(0x00);
guy_epdParam(0x01);
guy_epdParam(0xbf);
guy_epdParam(0x01);
}
void drv::epd_sendWriter(std::function<uint8_t(int)> f){
guy_epdCmd(0x10); //Then write image data 显示数据
#ifdef READGUY_583A_DUAL_BUFFER
if(epdFull || buf_2nd==nullptr || greyscalingHighQuality){
#endif
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){ //fill 0x03 for test
uint8_t dat=0,dat_r=f(i);
dat |= ((dat_r&0x80)?0x30:0);
dat |= ((dat_r&0x40)?0x03:0);
guy_epdParam(dat);
dat&=0;
dat |= ((dat_r&0x20)?0x30:0);
dat |= ((dat_r&0x10)?0x03:0);
guy_epdParam(dat);
dat&=0;
dat |= ((dat_r&0x08)?0x30:0);
dat |= ((dat_r&0x04)?0x03:0);
guy_epdParam(dat);
dat&=0;
dat |= ((dat_r&0x02)?0x30:0);
dat |= ((dat_r&0x01)?0x03:0);
guy_epdParam(dat);
#ifdef READGUY_583A_DUAL_BUFFER
buf_2nd[i]=dat_r;
}
}
else{
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){ //fill 0x03 for test
//guy_epdParam(0x03); //for data test
uint8_t dat=0,dat_r=f(i);
dat |= ((dat_r&0x80)?((buf_2nd[i]&0x80)?0x40:0x30):((buf_2nd[i]&0x80)?0x00:0x50));
//0x3?黑->白色, 0x0?白->黑色 0x4? 白->白色 0x5? 黑->黑色
dat |= ((dat_r&0x40)?((buf_2nd[i]&0x40)?0x4:0x3):((buf_2nd[i]&0x40)?0x0:0x5));
guy_epdParam(dat);
dat&=0;
dat |= ((dat_r&0x20)?((buf_2nd[i]&0x20)?0x40:0x30):((buf_2nd[i]&0x20)?0x00:0x50));
dat |= ((dat_r&0x10)?((buf_2nd[i]&0x10)?0x4:0x3):((buf_2nd[i]&0x10)?0x0:0x5));
guy_epdParam(dat);
dat&=0;
dat |= ((dat_r&0x8)?((buf_2nd[i]&0x8)?0x40:0x30):((buf_2nd[i]&0x8)?0x00:0x50));
dat |= ((dat_r&0x4)?((buf_2nd[i]&0x4)?0x4:0x3):((buf_2nd[i]&0x4)?0x0:0x5));
guy_epdParam(dat);
dat&=0;
dat |= ((dat_r&0x2)?((buf_2nd[i]&0x2)?0x40:0x30):((buf_2nd[i]&0x2)?0x00:0x50));
dat |= ((dat_r&0x1)?((buf_2nd[i]&0x1)?0x4:0x3):((buf_2nd[i]&0x1)?0x0:0x5));
guy_epdParam(dat);
buf_2nd[i]=dat_r;
}
#endif
}
}
void drv::drv_init(){ //初始化屏幕
epdFull=1;
epd_PowerOn=0;
}
void drv::drv_fullpart(bool part){ //初始化慢刷/快刷功能
if(lastRefresh) return;
//add driver code...
if(!epd_PowerOn) part=0;
epdFull=!part;
if(epdFull) iLut=15;
}
void drv::drv_setDepth(uint8_t i){
if(!epd_PowerOn) return; //不支持切换到快刷的情形
epdFull=0; iLut = i?(i>15?15:i):15; //如果需要, 改成自己的代码
}
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
BeginTransfer();
if(epdFull){
if(!epd_PowerOn){
epdFull=0; //发送快刷的lut
epd_init();
epdFull=1; //发送快刷的lut
guy_epdCmd(0x04); //power on
EndTransfer();
guy_epdBusy(-60);
BeginTransfer();
guy_epdCmd(0x10);
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x33
guy_epdParam(0x33);
}
epd_PowerOn=1; //设定为已上电
}
else{ //full refresh need power on
guy_epdCmd(0x04); //power on
EndTransfer();
guy_epdBusy(-60);
BeginTransfer();
}
epd_sendZoneInfo();
guy_epdCmd(0x10); //Then write image data
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x03 for test
guy_epdParam(0x33); //for test
}
guy_epdCmd(0x92);
epd_init();
//epd_init(); //repeat call in GxEPD2
epd_sendWriter(f); //write image data
}
else{
epd_init();
//Power on fx
guy_epdCmd(0x04);
EndTransfer();
guy_epdBusy(-60);
BeginTransfer();
epd_sendZoneInfo();
epd_sendWriter(f);
guy_epdCmd(0x92);
epd_sendZoneInfo();
}
guy_epdCmd(0x12); //开始刷屏
EndTransfer();
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
uint32_t u=epdFull?slowRefTime:fastRefTime; //全刷:局刷 busy时间
if(ms<u) guy_epdBusy(u-ms); //对于busy电平为低电平忙碌,高电平正常的屏幕则改为ms-u
int ms=millis()-lastRefresh;
Serial.printf("epdFull? %d, epd_PowerOn? %d \n",epdFull,epd_PowerOn);
int u=epdFull?slowRefTime:fastRefTime; //全刷:局刷 busy时间
if(ms<u) guy_epdBusy(ms-u); //对于busy电平为低电平忙碌,高电平正常的屏幕则改为ms-u
lastRefresh=0;
//add driver code...
BeginTransfer();
if(epdFull){
epdFull=0; //发送快刷的lut
epd_init(); //repeat init in part mode
epdFull=1; //发送快刷的lut
epd_sendZoneInfo();
guy_epdCmd(0x10); //Then write image data
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x03 for test
guy_epdParam(0x33); //for test
}
guy_epdCmd(0x02); //power off for test...
//guy_epdBusy(-30);
}
else{
guy_epdCmd(0x92);
epd_sendZoneInfo();
guy_epdCmd(0x10); //Then write image data 显示数据
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x03 for test
guy_epdParam(0x33); //for test:全白
}
guy_epdCmd(0x92);
}
EndTransfer();
}
/* test code , with stageless ------------------------------------------------------
if(epdFull){ //慢刷
BeginTransfer();
if(!epd_PowerOn){
epdFull=0; //发送快刷的lut
epd_init();
epdFull=1; //发送快刷的lut
guy_epdCmd(0x04); //power on
EndTransfer();
guy_epdBusy(-60);
BeginTransfer();
guy_epdCmd(0x10);
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x33
guy_epdParam(0x33);
}
epd_PowerOn=1; //设定为已上电
}
else{ //full refresh need power on
guy_epdCmd(0x04); //power on
EndTransfer();
guy_epdBusy(-60);
BeginTransfer();
}
epd_sendZoneInfo();
guy_epdCmd(0x10); //Then write image data
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x03 for test
guy_epdParam(0x33); //for test
}
guy_epdCmd(0x92);
epd_init();
//epd_init(); //repeat call in GxEPD2
epd_sendWriter(f); //write image data
guy_epdCmd(0x12);
EndTransfer();
guy_epdBusy(-4000);
BeginTransfer();
epdFull=0; //发送快刷的lut
epd_init(); //repeat init in part mode
epdFull=1; //发送快刷的lut
epd_sendZoneInfo();
guy_epdCmd(0x10); //Then write image data
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x03 for test
guy_epdParam(0x33); //for test
}
guy_epdCmd(0x02); //power off for test...
EndTransfer();
//guy_epdBusy(-30);
}
else{ //partmode
BeginTransfer();
epd_init();
//Power on fx
guy_epdCmd(0x04);
EndTransfer();
guy_epdBusy(-60);
BeginTransfer();
epd_sendZoneInfo();
epd_sendWriter(f);
guy_epdCmd(0x92);
epd_sendZoneInfo();
guy_epdCmd(0x12); //快刷
EndTransfer();
guy_epdBusy(-1000); //1秒钟刷屏时间
BeginTransfer();
guy_epdCmd(0x92);
epd_sendZoneInfo();
guy_epdCmd(0x10); //Then write image data 显示数据
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/2;i++){ //fill 0x03 for test
guy_epdParam(0x33); //for test:全白
}
guy_epdCmd(0x92);
EndTransfer();
}
// test code , with stageless ------------------------------------------------------ */
}
void drv::drv_draw16grey_step(std::function<uint8_t(int)> f, int step){
if(_quality&1) return readguyEpdBase::drv_draw16grey_step(f,step);
if(step==1) drv_fullpart(1);//初始阶段,完成准备工作 //设置为快刷模式
greyscalingHighQuality=16; //开启高品质灰度模式
iLut=step;
drv_dispWriter(f);
//DelayMs(10); //未知原因()
if(step==15) greyscalingHighQuality=0;//初始阶段,完成准备工作 //设置为快刷模式
}
void drv::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0){ //RST_PIN<0 无法唤醒
//add driver code...
guy_epdCmd(0x07); // deep sleep
guy_epdParam(0xA5); // check code
}
epd_PowerOn = 0;
epdFull = 1; //强制设置为慢刷新模式

View File

@@ -36,13 +36,17 @@
#define _GUY_EPD583A_H_FILE
namespace guydev_583A{
constexpr int GUY_D_WIDTH =200; //驱动屏幕宽度
constexpr int GUY_D_HEIGHT =200; //驱动屏幕高度
constexpr int slowRefTime =2000; //驱动屏幕慢刷时间, 单位毫秒
constexpr int fastRefTime =500; //驱动屏幕快刷时间, 单位毫秒
constexpr int GUY_D_WIDTH =600; //驱动屏幕宽度
constexpr int GUY_D_HEIGHT =448; //驱动屏幕高度
constexpr int slowRefTime =1600; //驱动屏幕慢刷时间, 单位毫秒
constexpr int fastRefTime =560; //驱动屏幕快刷时间, 单位毫秒
class drv : public readguyEpdBase {
public:
#ifdef READGUY_583A_DUAL_BUFFER
drv();
virtual ~drv();
#endif
int drv_ID() const { return READGUY_DEV_583A; }
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
@@ -52,11 +56,36 @@ public:
//int drv_panelwidth() const { return GUY_D_WIDTH; }; //返回缓存的数据宽度
int drv_height() const{ return GUY_D_HEIGHT; }; //返回显示区域高度
void drv_setDepth(uint8_t i); //设置显示颜色深度
int drv_supportGreyscaling() const { return 16; }
int drv_supportGreyscaling() const { return (_quality&1)?16:-16; } //暂不支持灰度功能移植
void drv_draw16grey_step(std::function<uint8_t(int)> f, int step);
private:
uint8_t epd_PowerOn=1; //是否上电. 睡眠则设为0
uint8_t epdFull=0; //是partical模式/快速刷新模式 0快刷, 1慢刷
uint8_t iLut=15; //颜色深度
uint8_t greyscalingHighQuality = 0;
static const unsigned char *luts[10];
static const unsigned char *luts_fast[10];
static const unsigned char lut_20_LUTC[];
static const unsigned char lut_20_LUTCFAST[];
static const unsigned char lut_21_LUTB[];
static const unsigned char lut_21_LUTBFAST[];
static const unsigned char lut_22_LUTW[];
static const unsigned char lut_22_LUTWFAST[];
static const unsigned char lut_23_LUTG1[];
static const unsigned char lut_24_LUTG2[];
static const unsigned char lut_25_LUTR0[];
static const unsigned char lut_26_LUTR1[];
static const unsigned char lut_27_LUTR2[];
static const unsigned char lut_28_LUTR3[];
static const unsigned char lut_29_LUTXON[];
static const unsigned char customGrey[];
void sendLut(int lutid);
void epd_init();
void epd_sendZoneInfo();
void epd_sendWriter(std::function<uint8_t(int)> f);
#ifdef READGUY_583A_DUAL_BUFFER
uint8_t *buf_2nd=nullptr; //second Buffer for luts
#endif
};
}
#endif /* END OF FILE. ReadGuy project.

View File

@@ -51,6 +51,7 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -51,6 +51,7 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
//add driver code...
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;

View File

@@ -160,8 +160,8 @@ void readguyEpdBase::Reset(uint32_t minTime)
}
//void readguyEpdBase::drv_draw16grey(const uint8_t *d16bit){ //不支持的话什么都不做
void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,int o,
uint16_t fw, uint16_t fh){
void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,int32_t x,int32_t y,int o,
int32_t fw0, int32_t fh){
static const PROGMEM uint8_t bayer_tab [64]={
0, 32, 8, 40, 2, 34, 10, 42,
48, 16, 56, 24, 50, 18, 58, 26,
@@ -172,9 +172,11 @@ void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_
15, 47, 7, 39, 13, 45, 5, 37,
63, 31, 55, 23, 61, 29, 53, 21
};
if(!fw) fw=spr.width();
if(!fh) fh=spr.height();
if((!fw) || (!fh)) return;
if(!fw0) fw0=spr.width();
if(!fh) fh=spr.height();
if((!fw0) || (!fh)) return;
int32_t fw=(fw0>0?std::min(fw0,sprbase.width()):-fw0);
fw0=std::abs(fw0); //无视缩放优化, 0~3:常规的三种渲染模式, 4~7: 无视缩放优化
if(o==0 || o==1){
readBuff = new uint16_t[spr.width()];
floyd_tab[0] = new int16_t [fw];
@@ -188,7 +190,7 @@ void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_
spr.readRect(0,(i-y)*spr.height()/fh,spr.width(),1,readBuff);
if(_quality&2){
for(int32_t j=0;j<fw;j++){
int gv=greysc(readBuff[j*spr.width()/fw]);
int gv=greysc(readBuff[j*spr.width()/fw0]);
int32_t flodelta = floyd_tab[i&1][j]+(int32_t)((gv<<8)|gv);
if(flodelta>=0x8000) {
//spr.drawPixel(j,i,1);
@@ -221,7 +223,7 @@ void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_
buff8bit=0;
for(uint_fast8_t b=0;b<8;b++)
buff8bit |= ((pgm_read_byte(bayer_tab+((b<<3)|(i&7)))<<2)+2
<greysc(readBuff[((j<<3)+b)*spr.width()/fw]))<<(7-b);
<greysc(readBuff[((j<<3)+b)*spr.width()/fw0]))<<(7-b);
writeBuff[j]=buff8bit;
}
}
@@ -236,12 +238,14 @@ void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_
}
}
//不支持的话使用单色抖动刷屏
void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,
uint16_t fw, uint16_t fh){
void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,int32_t x,int32_t y,
int32_t fw0, int32_t fh){
//Serial.println("drv_draw16grey fx");
if(!fw) fw=spr.width();
if(!fh) fh=spr.height();
if((!fw) || (!fh)) return;
if(!fw0) fw0=spr.width();
if(!fh) fh=spr.height();
if((!fw0) || (!fh)) return;
int32_t fw=(fw0>0?std::min(fw0,sprbase.width()):-fw0);
fw0=std::abs(fw0); //无视缩放优化, 0~3:常规的三种渲染模式, 4~7: 无视缩放优化
readBuff = new uint16_t[spr.width()];
if(_quality&2){
floyd_tab[0] = new int16_t [fw];
@@ -263,7 +267,7 @@ void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16
//for(uint_fast8_t b=0;b<8;b++){
uint_fast8_t cg=0;
if(_quality&2){
int gv=greysc(readBuff[j*spr.width()/fw]);
int gv=greysc(readBuff[j*spr.width()/fw0]);
int32_t fd = floyd_tab[i&1][j]+((gv<<8)|gv);
while(fd>=0x800) {
cg++;
@@ -275,8 +279,8 @@ void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16
{ floyd_tab[!(i&1)][j ] += (fd*5)>>4; }
if(j!=fw-1) { floyd_tab[!(i&1)][j+1] += (fd )>>4; }
}
else{ cg=greysc(readBuff[j*spr.width()/fw])>>4; }
//uint_fast8_t cg=greysc(readBuff[j*spr.width()/fw])>>4;
else{ cg=greysc(readBuff[j*spr.width()/fw0])>>4; }
//uint_fast8_t cg=greysc(readBuff[j*spr.width()/fw0])>>4;
if(negativeOrder)
buff8bit |= (cg<k)<<((~j)&7);
else{
@@ -288,7 +292,7 @@ void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16
buff8bit=0;
}
//}
//sprbase.drawPixel(x+j,i,(greysc(readBuff[j*spr.width()/fw])/16)==(15-k));
//sprbase.drawPixel(x+j,i,(greysc(readBuff[j*spr.width()/fw0])/16)==(15-k));
}
if(_quality&2) for(int floi=0;floi<fw;floi++) floyd_tab[i&1][floi]=0;
sprbase.drawBitmap(x,i,writeBuff,fw,1,1,0);

View File

@@ -35,7 +35,8 @@
#include <FS.h>
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include "guy_epaper_config.h"
#include "../guy_config_host.h"
#include <functional>
#define FILL_WHITE ([](int)->uint8_t{return 0xff;})
#define FILL_BLACK ([](int)->uint8_t{return 0x00;})
@@ -48,9 +49,9 @@ protected:
int8_t CS_PIN ;
int8_t BUSY_PIN;
uint8_t in_trans=0;
uint8_t _quality=2; //灰度显示品质 0(默认)-高品质 1-低品质 部分屏幕支持高品质的连续刷灰度.
uint8_t _quality=2;//灰度显示品质 默认2 0,2-高品质 1,3-低品质高兼容性. 0,1使用bayer灰度二值表 2,3使用floyd算法
#ifdef MEPD_DEBUG_WAVE
uint16_t dat_combo = 0; //dc引脚状态 0 command, 1 data
int dat_combo = 0; //dc引脚状态 0 command, 1 data
#endif
uint16_t *readBuff;// = new uint16_t[spr.width()];
uint8_t *writeBuff;// = new uint8_t[w];
@@ -88,17 +89,19 @@ public:
virtual int drv_height()const=0; //返回显示区域高度, 即使旋转了也不能影响此函数输出
virtual int drv_supportGreyscaling() const { return 0; }
virtual void drv_setDepth(uint8_t i){} //设置显示颜色深度, 不支持的话什么都不做
virtual void drv_setDepth(uint8_t i){ (void)i; } //设置显示颜色深度, 不支持的话什么都不做
/** @brief 获取某一像素颜色, 并转化为256阶灰度
* @param x, y 坐标
* @param gamma_on 是否对灰度值进行gamma校正(速度慢)
* @return uint32_t 颜色的灰度值
*/
IRAM_ATTR static int greysc(int c){return(((c>>3)&0x1F)*79+(((c<<3)+(c>>13))&0x3F)*76+((c>>8)&0x1F)*30)>>5;}
void drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,int o=0,
uint16_t fw=0, uint16_t fh=0); //分步完成灰度刷新
void drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,
uint16_t fw=0, uint16_t fh=0);//省内存方式
/// @brief 显示sprite图像, 使用floyd算法
/// @param o 无视缩放优化, 0~3:分步的三种渲染模式, 0完整 1开始 2中间 3结束
void drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,int32_t x,int32_t y,int o=0,
int32_t fw0=0, int32_t fh=0); //分步完成灰度刷新
void drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,int32_t x,int32_t y,
int32_t fw0=0, int32_t fh=0);//省内存方式
void drv_draw16grey_step(const uint8_t *buf, int step){ //分步完成灰度刷新
drv_draw16grey_step([&](int n)->uint8_t{return buf[n];},step);
}

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模块
@@ -124,12 +128,16 @@ public:
auto cfg = _bus_instance.config(); // 获取总线配置的结构。
#ifndef ESP8266
#ifdef CONFIG_IDF_TARGET_ESP32
cfg.spi_host = SPI3_HOST; // 选择要使用的 SPI (VSPI_HOST or HSPI_HOST)
cfg.spi_host = VSPI_HOST; // 选择要使用的 SPI (VSPI_HOST or HSPI_HOST)
#else
cfg.spi_host = SPI1_HOST; // 选择要使用的 SPI (VSPI_HOST or HSPI_HOST)
#endif
cfg.use_lock = true; // 使用锁时设置为 True
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
cfg.dma_channel = SPI_DMA_CH_AUTO; // Set the DMA channel (1 or 2. 0=disable) 设置要打开的 DMA 通道 (0=DMA关闭)
#else
cfg.dma_channel = 0; // Auto DMA channel isn't supported. Don't use DMA by default.
#endif
#endif
cfg.spi_mode = 0; // SPI设置通讯模式 (0 ~ 3)
#if (defined(DISPLAY_TYPE_ST7789_240320) || defined(DISPLAY_TYPE_ST7789_240240) || defined(DISPLAY_TYPE_ST7789_135240))

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,14 +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();
}
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,29 +40,13 @@
//务必保证这些版本号是一致的.
//另外, 在提交新版本之前, 不要忘记在github上创建release, 否则Arduino IDE会读不到
#define READGUY_V_MAJOR 1
#define READGUY_V_MINOR 3
#define READGUY_V_PATCH 3
#define READGUY_V_MINOR 5
#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.3"
#define READGUY_VERSION "1.5.0"
#ifdef ESP8266
#define _READGUY_PLATFORM "ESP8266"
#else
#ifdef CONFIG_IDF_TARGET_ESP32
#define _READGUY_PLATFORM "ESP32"
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define _READGUY_PLATFORM "ESP32S2"
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#define _READGUY_PLATFORM "ESP32S3"
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#define _READGUY_PLATFORM "ESP32C3"
#else
#define _READGUY_PLATFORM "Unknown"
#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,15 +28,26 @@
*/
#include "readguy.h"
#if (!defined(ESP8266)) //for ESP32, ESP32S2, ESP32S3, ESP32C3
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
#include "esp_flash.h"
#endif
#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 args_name[23][8]={
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",
"btn_cnt","btn1","btn1c","btn2","btn2c","btn3","btn3c","bklight","rtc"
"btn_cnt","btn1","btn1c","btn2","btn2c","btn3","btn3c","bklight","rtc","user"
};
#ifdef READGUY_DEV_154A
static const PROGMEM char NAME_guyDev154[]="1.54寸标准";
@@ -177,24 +188,32 @@ const char *ReadguyDriver::epd_drivers_list[EPD_DRIVERS_NUM_MAX]={
//x==62 -> _
//#define R2CHAR(x) (((x)==63)?42:(((x)==62)?95:(((x)>=36)?((x)+61):(((x)>=10)?((x)+55):((x)+48)))))
void ReadguyDriver::ap_setup(){
return ap_setup(READGUY_CONF_AP_SSID,READGUY_CONF_AP_PASS);
}
void ReadguyDriver::ap_setup(const char *ssid, const char *pass, int m){
//初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
WiFi.mode(WIFI_AP);
if(m>=0 && m<=3) WiFi.mode((WiFiMode_t)m); //有时候还需要STA+AP或者是一开始就确定好了wifi模式
IPAddress local_IP(192,168,4,1);
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);
WiFi.softAPConfig(local_IP, gateway, subnet);
WiFi.softAP("readguy","12345678");
Serial.println(F("[Guy AP] ap_setup SSID: readguy, Pass: 12345678"));
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服务器端, 这样就可以进行配网工作
if(sfuncs!=-1) server_end(); //避免重复服务器setup
#ifdef READGUY_UPDATE_SERVER
httpUpdater.setup(&sv);
#endif
sv.on("/", HTTP_GET, std::bind(&ReadguyDriver::handleInit ,this));
sv.on("/verify", HTTP_POST, std::bind(&ReadguyDriver::handleInitPost ,this)); //此时已经完成了引脚初始化
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; } //严防内存泄露
@@ -202,28 +221,35 @@ 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;
}
}
else { sfnames=nullptr; sfevents=nullptr; }
/*sv.on("/favicon.ico", HTTP_GET, [&](){
sv.client().write_P(PSTR("HTTP/1.1 200 OK\r\n"
"Content-Type: image/x-icon\r\n"
"Content-Length: 1150\r\n"
"Connection: close\r\n\r\n"),89);
sv.client().write_P((const char *)faviconData,sizeof(faviconData));
});*/
sv.onNotFound(std::bind(&ReadguyDriver::handleNotFound,this)); //处理404的情况
#ifdef READGUY_USE_DEFAULT_ICON
sv.on("/favicon.ico", HTTP_GET, [&](){
sv.send_P(200,"image/x-icon",(const char *)faviconData,sizeof(faviconData));
});
#endif
sv.begin();
MDNS.begin("readguy");
//MDNS.addService("http","tcp",80);
#ifdef READGUY_MDNS_SERVICE
MDNS.begin(READGUY_MDNS_SERVICE);
MDNS.addService("http","tcp",80);
#endif
#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();
@@ -248,10 +274,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); //防止触发看门狗
@@ -259,7 +291,9 @@ bool ReadguyDriver::server_loop(){ //此时等待网页操作完成响应...
}
void ReadguyDriver::server_end(){
sv.stop();
#ifdef READGUY_MDNS_SERVICE
MDNS.end();
#endif
sfuncs=-1;
delete [] sfnames;
delete [] sfevents;
@@ -276,7 +310,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)
@@ -287,22 +323,36 @@ void ReadguyDriver::handleInitPost(){
btnTask.detach();
#else
#ifdef READGUY_ENABLE_SD
#if (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
if(READGUY_shareSpi) SD.end(); else SD_MMC.end();
#else
SD.end();//关闭SD卡
#endif
#ifdef READGUY_IDF_TARGET_WITH_VSPI
if(sd_spi != epd_spi) { //共线时, 不要删除SD
delete sd_spi;
sd_spi=nullptr;
}
#endif
#endif
vTaskDelete(btn_handle);
#endif
}
config_data[0]=1; //默认只要运行到此处, 就已经初始化好了的
for(int i=0;i<23;i++){
for(int i=0;i<33;i++){
#ifdef READGUY_SERIAL_DEBUG
Serial.print(F("Argument "));
Serial.print(FPSTR(args_name[i]));
#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(':');
if(sv.hasArg(FPSTR(args_name[i]))) {
Serial.println(sv.arg(FPSTR(args_name[i])));
#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();
}
@@ -314,25 +364,30 @@ 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 将在2.0实装
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
}
}
//尝试初始化各个硬件, 可能失败, 然后显示一些东西
#if (defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3))
#if (defined(ESP8266))
if(config_data[3]==-1) config_data[3] = config_data[10];
else config_data[10] = config_data[3];
if(config_data[4]==-1) config_data[4] = config_data[11];
else config_data[11] = config_data[4];
READGUY_shareSpi = true;
#else
if(config_data[10] == config_data[3] && config_data[11] == config_data[4]) //检测到SPI共线
if(config_data[10] == config_data[3] || config_data[11] == config_data[4]) //检测到SPI共线
READGUY_shareSpi = true;
else if(READGUY_shareSpi){
if(READGUY_shareSpi){
if(config_data[3]==-1) config_data[3] = config_data[10];
else config_data[10] = config_data[3];
if(config_data[4]==-1) config_data[4] = config_data[11];
@@ -342,7 +397,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.") };
@@ -366,13 +423,20 @@ 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);
#if (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
SD_MMC.end();
#endif
drawCenterString(setSDcardDriver()?"SD Init OK!":"SD Init failed!",
guy_dev->drv_width()>>1,(guy_dev->drv_height()>>1)+20);
setButtonDriver(); //初始化按钮..
@@ -381,11 +445,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++;
guy_dev->drv_fullpart(1);
refresh_begin(0);
guy_dev->drv_fullpart(0);
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(){
@@ -516,18 +584,23 @@ void ReadguyDriver::handlePinSetup(){
#if defined(ESP8266)
for(int i=2;i<12;i++){
if(i>=6 && i<=8) continue;
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
for(int i=2;i<12;i++){
s += F("<br/>");
#else
for(int i=0;i<12;i++){
#endif
s += F("<br/>");
#if (!(defined(READGUY_IDF_TARGET_WITH_VSPI) || defined(READGUY_IDF_TARGET_MATRIX_SDIO)))
if(i==7) {
i+=2; //优化ESP32C3的SPI配置体验 (C3只能共线)
s += F("(" _READGUY_PLATFORM "不支持SD卡独立SPI总线! SD_MOSI和SD_SCLK沿用EPDMOSI和EPDSCLK)<br/>");
}
#endif
#endif
s += FPSTR(args_name[i+2]);
s += F("<input type=\"number\" id=\"");
s += FPSTR(args_name[i+2]);
s += FPSTR(index_cn_html3);
s += FPSTR(args_name[i+2]);
s += ("\" min=\"-1\" max=\"99\" step=\"1\" value=\"");
s += F("\" min=\"-1\" max=\"99\" step=\"1\" value=\"");
s += (READGUY_cali?(int)config_data[i+3] :-1);
s += F("\"/>");
}
@@ -554,6 +627,17 @@ void ReadguyDriver::handlePinSetup(){
if(i==3) s += (READGUY_cali?(int)READGUY_bl_pin :-1);
else s += ((READGUY_cali && config_data[15+i])?(int)abs(config_data[15+i])-1:-1);
}
for(int i=0;i<10;i++){
s += F("\"/><br/>用户数据 ");
s += (i+1);
s += F("<input type=\"number\" id=\"user");
s += (i+1);
s += FPSTR(index_cn_html3);
s += ("user");
s += (i+1);
s += F("\" min=\"-1\" max=\"99\" step=\"1\" value=\"");
s += (READGUY_cali?(int)config_data[i+22] :-1);
} //---------------------------------此部分代码需要配合硬件测试 + 查看网页源代码 才可以实现
s += FPSTR(index_cn_html16); //s += (READGUY_cali?(int)0 :-1);
sv.send_P(200, TEXT_HTML, (s+FPSTR(end_html)).c_str());
}
@@ -569,7 +653,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("\">");
@@ -596,22 +685,38 @@ void ReadguyDriver::handleFinal(){
else s+=F("SD卡已插入.<br/>"); //对于大容量(>2GB)卡, SDFS.info64(*sdInfo)函数调用速度太慢(17秒)
#else
else{
auto cardType = SD.cardType();
uint32_t sz;
uint32_t usesz;
sdcard_type_t cardType;
#if (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
if(READGUY_shareSpi){
#endif
sz=(uint32_t)(SD.cardSize()/1024);
usesz=(uint32_t)(SD.usedBytes()/1024);
cardType=SD.cardType();
#if (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
}
else{
sz=(uint32_t)(SD_MMC.cardSize()/1024);
usesz=(uint32_t)(SD_MMC.usedBytes()/1024);
cardType=SD_MMC.cardType();
}
#endif
s+=F("SD Card Type: ");
if(cardType == CARD_MMC) s+=F("MMC");
else if(cardType == CARD_SD) s+=F("SDSC");
else if(cardType == CARD_SDHC) s+=F("SDHC");
else s+=F("UNKNOWN");
s+=F(", SD card size: ");
s+=(uint32_t)(SD.cardSize()/1024);
s+=sz;
s+=F(", used size: ");
s+=(uint32_t)(SD.usedBytes()/1024);
s+=usesz;
s+=F("KB.<br/>");
}
#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/>芯片型号: ");
@@ -626,10 +731,17 @@ void ReadguyDriver::handleFinal(){
char cbuf[20]="";
#if (defined(ESP8266))
sprintf(cbuf, "0x%08x", ESP.getChipId());
#else
#elif (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0))
uint64_t gotID;
esp_flash_read_unique_chip_id(esp_flash_default_chip,&gotID);
sprintf(cbuf, "%016llx", gotID);
//sprintf(cbuf, "%016llx", gotID);
sprintf(cbuf, "%08x%08x",(unsigned int)(gotID>>32),(unsigned int)(gotID&0xffffffffu));
#else
uint32_t chipId = 0;
for(int i=0; i<17; i=i+8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
sprintf(cbuf, "0x%08x",chipId);
#endif
s+=cbuf;
s+=F("<br/>闪存容量: ");
@@ -658,7 +770,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();
@@ -693,7 +807,7 @@ const PROGMEM char ReadguyDriver::index_cn_html[] = // then write EpdMOSI pin
"将引脚配置输入到框内, 即可成功点亮屏幕.</p><hr/><h2>引脚定义设定</h2><form "
"name=\"input\" action=\"/verify\" method=\"POST\">";
const PROGMEM char ReadguyDriver::index_cn_html2[] =
#if (!defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3))
#if (defined(READGUY_IDF_TARGET_WITH_VSPI) || defined(READGUY_IDF_TARGET_MATRIX_SDIO))
"<input type=\"checkbox\" name=\"share\" value=\"1\">墨水屏和SD卡共享SPI<br/>"
#endif
"E-paper 型号<select id=\"et\" onchange=\"ct()\" name=\"epdtype\">";
@@ -707,21 +821,25 @@ const PROGMEM char ReadguyDriver::verify_html[] =
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][200] = {
"一个按键, 操作可能比较繁琐, 但功能还都可以的.<br/>"
"点按: 下一个/向下翻页<br/>双击: 确定/选择<br/>三连击: 返回/切换输入法<br/>长按: 上一个/向上翻页",
"个按键, 操作可以满足需求.<br/>"
"按键1点按: 下一个/向下翻页<br/>按键1长按: 上一个/向上翻页<br/>按键2点按: 确定/选择<br/>按键2长按: 返回/切换输入法",
"三个按键, 操作非常流畅.<br/>"
"按键1: 上一个/向上翻页<br/>按键2点按: 确定/选择<br/>按键2长按: 返回/切换输入法<br/>按键3: 下一个/向下翻页"
};
const PROGMEM char *ReadguyDriver::verifybtn_html[3] = {verifybtn_html1,verifybtn_html2,verifybtn_html3};
const PROGMEM char ReadguyDriver::verifybtn_html1[] =
"个按键, 功能全保留, 操作可能比较繁琐.<br/>点按:下一个/向下翻页<br/>双击:确定/选择<br/>三连击:返回/退格<br/>"
"长按半秒:上一个/向上翻页<br/>点按紧接着长按: 特殊操作";
const PROGMEM char ReadguyDriver::verifybtn_html2[] =
"两个按键, 操作可以满足需求.<br/>按键1点按:下一个/向下翻页<br/>按键1长按:上一个/向上翻页<br/>按键2点按:确定/选"
"择<br/>按键2长按:返回/退格<br/>按住按键1点按2:特殊操作";
const PROGMEM char ReadguyDriver::verifybtn_html3[] =
"三个按键, 操作非常方便流畅.<br/>按键1:上一个/向上翻页<br/>按键2点按:确定/选择<br/>按键2长按: 返回/退格<br/>按"
"键3:下一个/向下翻页<br/>双击点按2:切换输入法等特殊操作";
const PROGMEM char ReadguyDriver::final_html[] =
"欢迎使用 readguy</title></head><body><h1>readguy ";
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\' "
@@ -730,10 +848,14 @@ 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>";
/*const PROGMEM uint8_t ReadguyDriver::faviconData[1150]={
"<p>ReadGuy on device " _READGUY_PLATFORM " <a href=\"/pinsetup\">重新配置引脚</a>"
#ifdef READGUY_UPDATE_SERVER
" <a href=\"/update\">" READGUY_UPDATE_SERVER "</a>"
#endif
"<br/>Copyright © FriendshipEnder <a href=\"" _GITHUB_LINK "\">GitHub</a> <a href=\"" _BILIBILI_LINK "\">"
"Bilibili</a><br/>版本: " READGUY_VERSION " ,编译日期: " __DATE__ " " __TIME__ "</p></body></html>";
#ifdef READGUY_USE_DEFAULT_ICON
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,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x9d,0xaa,0xe8,0xff,0x74,0x73,0x77,0xff,0x74,0x73,
@@ -782,6 +904,7 @@ _GITHUB_LINK "\">GitHub</a> <a href=\"" _BILIBILI_LINK "\">Bilibili</"
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x3,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,
0x0,0x0,0x0,0x1,0x0,0x0,0x80,0x1,0x0,0x0,0x80,0x0,0x0,0x0,0x80,0x1,0x0,0x0,0x80,0x1,0x0,0x0,0xc0,0x1,
0x0,0x0,0xc0,0x3,0x0,0x0,0xe0,0x3,0x0,0x0,0xf0,0x7,0x0,0x0,0xfc,0x1f,0x0,0x0,0xf0,0xff,0x0,0x0
};*/
};
#endif
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -7,7 +7,7 @@
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief readguy 基础功能源代码文件.
* @version 1.0
* @date 2023-09-21
* @date created: 2023-09-21 last modify: 2024-03-11
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
@@ -28,18 +28,33 @@
*/
#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";
const PROGMEM char ReadguyDriver::projname[8] = READGUY_NVS_PROJECTNAME;
const PROGMEM char ReadguyDriver::tagname[8] = READGUY_NVS_CONFIGKEY;
volatile uint8_t ReadguyDriver::spibz=0;
#ifndef DYNAMIC_PIN_SETTINGS
const int8_t ReadguyDriver::config_data[22] = {
const int8_t ReadguyDriver::config_data[32] = {
127 , //READGUY_cali
READGUY_shareSpi ,
READGUY_epd_type ,// 对应的epd驱动程序代号, -1为未指定
@@ -51,10 +66,10 @@ const int8_t ReadguyDriver::config_data[22] = {
READGUY_epd_rst ,// 目标显示器的 RST 引脚
READGUY_epd_busy ,// 目标显示器的 BUSY 引脚
//sd卡驱动部分, 默认使用hspi (sd卡建议用hspi)
READGUY_sd_miso ,// 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
READGUY_sd_mosi ,// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
READGUY_sd_sclk ,// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
READGUY_sd_cs ,// 目标sd卡的 CS 引脚.
READGUY_sd_miso ,// 目标sd卡的 MISO 引脚, 或esp32s3使用SDIO的 DAT0 引脚, sd_share_spi == 1 时无效
READGUY_sd_mosi ,// 目标sd卡的 MOSI 引脚, 或esp32s3使用SDIO的 CMD 引脚, sd_share_spi == 1 时无效
READGUY_sd_sclk ,// 目标sd卡的 SCLK 引脚, 或esp32s3使用SDIO的 CLK 引脚, sd_share_spi == 1 时无效
READGUY_sd_cs ,// 目标sd卡的 CS 引脚, 或esp32s3使用SDIO的 DAT3 引脚,
READGUY_i2c_sda ,// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
READGUY_i2c_scl ,// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
@@ -63,28 +78,36 @@ const int8_t ReadguyDriver::config_data[22] = {
READGUY_btn2 ,
READGUY_btn3 ,
READGUY_bl_pin ,//前置光接口引脚IO
READGUY_rtc_type ,//使用的RTC型号(待定, 还没用上)
READGUY_rtc_type ,//使用的RTC型号. RTC 将在2.0实装 保留是为了兼容性 让代码更简单维护
0 ,//READGUY_sd_ok SD卡已经成功初始化
0 //READGUY_buttons 按钮个数, 0-3都有可能
0 ,//READGUY_buttons 按钮个数, 0-3都有可能
-1, //用户自定义变量 同时用于esp32s3使用SDIO卡数据的DAT1 为-1代表不使用SDIO
-1, //用户自定义变量 同时用于esp32s3使用SDIO卡数据的DAT2
-1,-1,-1,-1,-1,-1,-1,-1 //user data 区域, 此功能没啥用就暂时全设定为-1了
};
#endif
#ifndef ESP8266
#ifdef READGUY_IDF_TARGET_WITH_VSPI
SPIClass *ReadguyDriver::sd_spi =nullptr;
#endif
SPIClass *ReadguyDriver::epd_spi=nullptr;
TaskHandle_t ReadguyDriver::btn_handle;
#endif
ReadguyDriver::ReadguyDriver(){
READGUY_cali = 0; // config_data[0] 的初始值为0
#ifdef DYNAMIC_PIN_SETTINGS
for(unsigned int i=1;i<sizeof(config_data);i++) config_data[i] = -1;
#endif
READGUY_sd_ok = 0; //初始默认SD卡未成功初始化
READGUY_buttons = 0; //初始情况下没有按钮
} //WiFiSet: 是否保持AP服务器一直处于打开状态
uint8_t ReadguyDriver::init(uint8_t WiFiSet, bool initepd){
uint8_t ReadguyDriver::init(uint8_t WiFiSet, bool initepd, bool initSD){
if(READGUY_cali==127) //已经初始化过了一次了, 为了防止里面一些volatile的东西出现问题....还是退出吧
return 0;
return READGUY_sd_ok;
#ifdef DYNAMIC_PIN_SETTINGS
nvs_init();
#if (!defined(INDEV_DEBUG))
#if (!defined(READGUY_INDEV_DEBUG))
if(!nvs_read()){ //如果NVS没有录入数据, 需要打开WiFiAP模式初始化录入引脚数据
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
@@ -101,31 +124,46 @@ uint8_t ReadguyDriver::init(uint8_t WiFiSet, bool initepd){
server_end();
if(!WiFiSet) WiFi.mode(WIFI_OFF);
fillScreen(1);
#else //DYNAMIC_PIN_SETTINGS 已定义 and READGUY_ESP_ENABLE_WIFI 已定义 but NVS无数据, 此时无法配置引脚. 视为halt
(void)WiFiSet; //avoid warning
nvs_deinit(); //invalid
Serial.printf_P(PSTR("[Guy NVS] INVALID pin settings on " _READGUY_PLATFORM " (Readguy lib version " READGUY_VERSION ")!\n"\
"Please open guy_driver_config.h and uncomment #define READGUY_ESP_ENABLE_WIFI!\n"\
"You can also flash example binary with Wi-Fi, configure pins and re-flash this program.\n"\
"System will restart in 5 seconds..."));
delay(5000);
ESP.restart();
#endif
#if (!defined(INDEV_DEBUG))
#if (!defined(READGUY_INDEV_DEBUG))
}
else{ //看来NVS有数据, //从NVS加载数据, 哪怕前面的数据刚刚写入, 还没读取
#ifdef READGUY_ESP_ENABLE_WIFI
if(WiFiSet>=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等功能操作(比如返回加载上一个程序)
setSDcardDriver();
if(initSD) setSDcardDriver();
setButtonDriver();
}
#endif
nvs_deinit();
#else
(void)WiFiSet; //avoid warning
nvs_init();
if(checkEpdDriver()!=127) setEpdDriver(initepd/* ,g_width,g_height */); //初始化屏幕
else for(;;); //此处可能添加程序rollback等功能操作(比如返回加载上一个程序)
setSDcardDriver();
if(initSD) setSDcardDriver();
setButtonDriver();
if(!nvs_read()){
nvs_write(); //全部成功之后, 写入引脚信息到NVS.
}
nvs_deinit();
#endif
#ifdef READGUY_SERIAL_DEBUG
Serial.println(F("[Guy init] init done."));
#endif
READGUY_cali=127;
return READGUY_sd_ok;
}
@@ -136,7 +174,9 @@ uint8_t ReadguyDriver::checkEpdDriver(){
#else
#define TEST_ONLY_VALUE 3
#endif
Serial.printf_P(PSTR("[Guy SPI] shareSpi? %d\n"),READGUY_shareSpi);
#ifdef READGUY_SERIAL_DEBUG
if(READGUY_shareSpi) Serial.println(F("[Guy SPI] share EPD and SD SPI."));
#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++)
@@ -214,7 +254,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
@@ -273,21 +315,25 @@ uint8_t ReadguyDriver::checkEpdDriver(){
else epd_spi->end();
//epd_spi ->setFrequency(ESP32_DISP_FREQUENCY);
//Serial.println("deleting guy_dev");
#ifdef READGUY_IDF_TARGET_WITH_VSPI
if(READGUY_shareSpi) sd_spi = epd_spi;
else {
if(sd_spi!=nullptr && sd_spi!=&SPI) { sd_spi->end(); delete sd_spi; } //防止SPI被delete掉
sd_spi=nullptr;
}
#endif
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){
guy_dev->spi_tr_release = in_release;
guy_dev->spi_tr_press = in_press;
if(initepd) guy_dev->drv_init(); //初始化epd驱动层
guy_dev->drv_init(); //初始化epd驱动层
//if(g_width) guy_width = g_width;
//else guy_width = guy_dev->drv_width(); //宽度必须是8的倍数, 但这个可以由GFX自动计算
//if(g_height) guy_height = g_height;
@@ -307,7 +353,10 @@ 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
if(initepd) display(READGUY_SLOW); //25/3/13:feat: 如果initepd为true说明墨水屏需要从上电or休眠初始化, 必定慢刷
}
#ifdef READGUY_ENABLE_SD
bool ReadguyDriver::setSDcardDriver(){
@@ -315,35 +364,70 @@ bool ReadguyDriver::setSDcardDriver(){
对于esp32也要注意这个引脚是否是一个合法的引脚
对于esp8266真的要重写, 比如esp8266需要允许某些引脚是可以复用的
此处的函数必须是可以反复调用的, 即 "可重入函数" 而不会造成任何线程危险 */
if(READGUY_sd_cs>=0
#ifndef ESP8266
&&READGUY_sd_miso!=READGUY_sd_mosi&&READGUY_sd_miso!=READGUY_sd_sclk&&READGUY_sd_miso!=READGUY_sd_cs&&READGUY_sd_mosi!=READGUY_sd_sclk
#ifdef ESP8266
if( READGUY_sd_cs>=0 && READGUY_sd_cs!=READGUY_epd_cs && READGUY_sd_cs!=READGUY_epd_dc\
&& READGUY_sd_cs!=READGUY_epd_rst && READGUY_sd_cs!=READGUY_epd_busy )
#else
if(READGUY_sd_miso!=READGUY_sd_mosi&&READGUY_sd_miso!=READGUY_sd_sclk&&READGUY_sd_miso!=READGUY_sd_cs&&READGUY_sd_mosi!=READGUY_sd_sclk
&& READGUY_sd_mosi!=READGUY_sd_cs && READGUY_sd_sclk!=READGUY_sd_cs && READGUY_sd_miso>=0 && READGUY_sd_mosi>=0 && READGUY_sd_sclk>=0
#if (!defined(READGUY_IDF_TARGET_MATRIX_SDIO))
&& READGUY_sd_cs>=0 && READGUY_sd_cs!=READGUY_epd_cs && READGUY_sd_cs!=READGUY_epd_dc && READGUY_sd_cs!=READGUY_epd_rst && READGUY_sd_cs!=READGUY_epd_busy
#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)
)
#endif
{ //SD卡的CS检测程序和按键检测程序冲突, 故删掉 (可能引发引脚无冲突但是显示冲突的bug)
#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
if(sd_spi == nullptr) {
#if (defined(CONFIG_IDF_TARGET_ESP32))
sd_spi = new SPIClass(VSPI);
#else
sd_spi = new SPIClass(FSPI); //ESP32S2和S3和C3 不支持VSPI, C3不支持FSPI
#if (defined(READGUY_IDF_TARGET_WITH_VSPI))
if(READGUY_shareSpi) sd_spi = epd_spi;
else{
#ifdef READGUY_SERIAL_DEBUG
Serial.printf_P(PSTR("[Guy SD] SD-VSPI sclk%3d|mosi%3d|miso%3d|ss%5d\n"),\
READGUY_sd_sclk,READGUY_sd_mosi,READGUY_sd_miso,READGUY_sd_cs);
#endif
if(sd_spi == nullptr) sd_spi = new SPIClass(VSPI);
else sd_spi->end();
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
READGUY_sd_ok = SD.begin(READGUY_sd_cs,*(READGUY_shareSpi?epd_spi:sd_spi),ESP32_SD_SPI_FREQUENCY);
#elif (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
if(READGUY_shareSpi){
READGUY_sd_ok = SD.begin(READGUY_sd_cs,*epd_spi,ESP32_SD_SPI_FREQUENCY);
}
else{
//if(getReadguyUseSdio()){ //4-wire SDIO
#ifdef READGUY_SERIAL_DEBUG
Serial.printf_P(PSTR("[Guy SD] SDMMC clk%3d|cmd%3d|d0 %3d|d1 %3d|d2 %3d|d3 %3d\n"),\
READGUY_sd_sclk,READGUY_sd_mosi,READGUY_sd_miso,READGUY_user1,READGUY_user2,READGUY_sd_cs);
#endif
if(READGUY_sd_cs>=0) pinMode(READGUY_sd_cs, INPUT_PULLUP); //默认上拉
SD_MMC.setPins(READGUY_sd_sclk,READGUY_sd_mosi,READGUY_sd_miso,READGUY_user1,READGUY_user2,READGUY_sd_cs);
//#else //不支持独立总线, 所以不会执行
// sd_spi = new SPIClass(FSPI); //ESP32S2和S3和C3 不支持VSPI, C3不支持FSPI
// sd_spi->begin(READGUY_sd_sclk,READGUY_sd_miso,READGUY_sd_mosi); //初始化SPI
READGUY_sd_ok = SD_MMC.begin("/sd",!getReadguyUseSdio(),false,ESP32_SD_MMC_FREQUENCY); //初始化频率为20MHz
}
#endif
#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();
if(!LittleFS.begin())
LittleFS.format(); //初始化失败时, 格式化文件系统
#else
SPIFFS.begin();
#endif
@@ -354,7 +438,8 @@ bool ReadguyDriver::setSDcardDriver(){
bool ReadguyDriver::setSDcardDriver(){
READGUY_sd_ok=0;
#ifdef READGUY_USE_LITTLEFS
LittleFS.begin();
if(!LittleFS.begin())
LittleFS.format(); //初始化失败时, 格式化文件系统
#else
SPIFFS.begin();
#endif
@@ -363,7 +448,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)
@@ -400,6 +485,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));
@@ -408,18 +503,23 @@ void ReadguyDriver::setButtonDriver(){
btn_rd[0].setLongRepeatMode(1); //允许连按
//}
if(READGUY_buttons==2){
btn_rd[0].setMultiBtn(1); //设置为多个按钮,不识别双击或三击
//btn_rd[0].setLongRepeatMode(1);
btn_rd[1].setMultiBtn(1); //设置为多个按钮,不识别双击或三击
btn_rd[1].setLongRepeatMode(0);
btn_rd[0].enScanDT(0); //不识别双击或三击
//btn_rd[0].setLongRepeatMode(1); //双按键 选择按键 设置为允许连按
btn_rd[1].enScanDT(0); //不识别双击或三击
btn_rd[1].setLongRepeatMode(0); //双按键 确定按键 设置为不允许连按
}
else if(READGUY_buttons==3){
btn_rd[0].long_press_ms = 50; //不识别双击三击, 只有按一下或者长按, 并且开启连按
//btn_rd[0].setLongRepeatMode(1);
btn_rd[1].setMultiBtn(1); //设置为多个按钮,不识别双击或三击
btn_rd[1].setLongRepeatMode(0);
btn_rd[2].long_press_ms = 50; //不识别双击三击, 只有按一下或者长按, 并且开启连按
btn_rd[2].setLongRepeatMode(1);
//btn_rd[0].long_press_ms = 20; //只有长按, 按一下也是长按, 2025/4/7更新: 优化识别逻辑, 这样不会变慢了
btn_rd[0].double_press_ms = 20; //不识别双击三击,
btn_rd[0].setLongRepeatMode(1); //并且开启连按
//2024/2/25更新:需要支持连按适配拨轮
//2025/3/12更新:默认开启识别, 但按下确定键的响应会稍微变慢
//2025/4/7更新: 优化识别逻辑, 这样不会变慢了
btn_rd[1].enScanDT(0); //识别双击或三击(默认)
btn_rd[1].setLongRepeatMode(0); //三按键 确定按键 设置为不允许连按
btn_rd[2].long_press_ms = 20; //只有长按, 按一下也是长按, 并且开启连按
btn_rd[2].double_press_ms = 20; //不识别双击三击,
btn_rd[2].setLongRepeatMode(1); //并且开启连按
}
#ifdef ESP8266 //对于esp8266, 需要注册到ticker. 这是因为没freertos.
btnTask.attach_ms(BTN_LOOPTASK_DELAY,looptask);
@@ -434,7 +534,7 @@ void ReadguyDriver::setButtonDriver(){
btn_rd[0].setLongPressMs(1); //不识别双击三击, 只有按一下或者长按, 并且开启连按
btn_rd[0].begin(33,rd_btn_f);
btn_rd[0].setLongRepeatMode(1);
btn_rd[1].setMultiBtn(1); //设置为多个按钮,不识别双击或三击
btn_rd[1].enScanDT(1); //设置为多个按钮,不识别双击或三击
btn_rd[1].begin(32,rd_btn_f);
btn_rd[1].setLongRepeatMode(0);
btn_rd[2].setLongPressMs(1); //不识别双击三击, 只有按一下或者长按, 并且开启连按
@@ -456,14 +556,22 @@ void ReadguyDriver::setButtonDriver(){
analogWriteFreq(400); //8266不宜太快
analogWriteResolution(8); //其实完全可以再快点的
analogWrite(READGUY_bl_pin,currentBright);
#else
#if (defined ( ESP_IDF_VERSION_VAL ) && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)))
//ledcAttach(READGUY_bl_pin,8000,8);
analogWrite(READGUY_bl_pin, currentBright);
analogWriteFrequency(READGUY_bl_pin, 8000); //esp32宜快一些, 减少频闪影响
analogWriteResolution(READGUY_bl_pin, 8); //其实完全可以再快点的
#else
ledcSetup(0, 8000, 8);
ledcAttachPin(READGUY_bl_pin, 0);//稍后加入PWM驱动...
ledcWrite(0, currentBright);
#endif
#endif
}
else {
currentBright=-1; //引脚不支持PWM,设置为亮起
pinMode(READGUY_bl_pin, OUTPUT);
digitalWrite(READGUY_bl_pin,HIGH);
}
} //关于按键策略, 我们在此使用多个Button2的类, 然后在一个task共享变量来确定上一个按键状态
@@ -475,6 +583,9 @@ fs::FS &ReadguyDriver::guyFS(uint8_t initSD){
#ifdef ESP8266
return SDFS;
#else
#if (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
if(!READGUY_shareSpi) return SD_MMC;
#endif
return SD;
#endif
}
@@ -497,14 +608,21 @@ fs::FS &ReadguyDriver::guyFS(uint8_t initSD){
void ReadguyDriver::setBright(int d){
if(currentBright>=0 && d>=0 && d<=255){
currentBright=d;
#ifdef ESP8266
#if ((defined (ESP8266)))
analogWrite(READGUY_bl_pin,d);
#else
#if (defined ( ESP_IDF_VERSION_VAL ) )
#if ( (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)))
analogWrite(READGUY_bl_pin,d);
#else
ledcWrite(0, d);
#endif
#endif
#endif
}
else if(currentBright>=-2 && currentBright<0){ //-1为不支持PWM的亮起,-2为不支持PWM的熄灭
currentBright=d?-1:-2;
pinMode(READGUY_bl_pin, OUTPUT);
digitalWrite(READGUY_bl_pin,d?HIGH:LOW);
}
}
@@ -513,56 +631,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<uint8_t(int)> 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,uint16_t x,uint16_t y,uint16_t zoomw, uint16_t zoomh) {
if(READGUY_cali==127) guy_dev->drv_drawImage(base, spr, x, y, 0, zoomw, zoomh);
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) {
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,uint16_t x,uint16_t y,uint8_t stage,
uint8_t totalstage,uint16_t zoomw,uint16_t zoomh) {
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,uint16_t x,uint16_t y,uint16_t zoomw,uint16_t zoomh){
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<uint8_t(int)> 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(){
@@ -573,10 +740,103 @@ void ReadguyDriver::invertDisplay(){
}
}
void ReadguyDriver::sleepEPD(){
if(READGUY_cali==127) guy_dev->drv_sleep();
if(READGUY_cali==127) {
if(READGUY_bl_pin>=0) {
pinMode(READGUY_bl_pin, OUTPUT);
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(){
@@ -588,7 +848,7 @@ void ReadguyDriver::nvs_write(){
}
#elif (defined(ESP8266))
void ReadguyDriver::nvs_init(){
EEPROM.begin(32);
EEPROM.begin(sizeof(config_data)+8+READGUY_ESP8266_EEPROM_OFFSET);
}
void ReadguyDriver::nvs_deinit(){
EEPROM.commit();
@@ -601,19 +861,21 @@ bool ReadguyDriver::nvs_read(){
sizeof(config_data)+
#endif
8;i++){
int8_t rd=(int8_t)EEPROM.read(2+i);
int8_t rd=(int8_t)EEPROM.read(READGUY_ESP8266_EEPROM_OFFSET+i);
#ifdef DYNAMIC_PIN_SETTINGS
if(i>=8) config_data[i-8] = rd;
else
#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(){
for(unsigned int i=0;i<sizeof(config_data)+8;i++){
EEPROM.write(2+i,(uint8_t)(i<8?pgm_read_byte(projname+i):config_data[i-8]));
EEPROM.write(READGUY_ESP8266_EEPROM_OFFSET+i,(uint8_t)(i<8?pgm_read_byte(projname+i):config_data[i-8]));
}
}
#else
@@ -646,36 +908,63 @@ void ReadguyDriver::nvs_write(){
#endif
uint8_t ReadguyDriver::getBtn_impl(){ //按钮不可用, 返回0.
static uint32_t last=0;
uint8_t res1,res2,res3,res4=0;
switch(READGUY_buttons){
case 1:
static unsigned long last=0;
static unsigned long last2=0;
uint8_t res1,res2,res3=0,res4=0;
if(READGUY_buttons == 1){
res1=btn_rd[0].read();
if(res1 == 1) res4 |= 1; //点按
else if(res1 == 2) res4 |= 4; //双击-确定
else if(res1 == 3) res4 |= 8; //三击-返回
else if(res1 == 4) res4 |= 2; //长按-向上翻页
else if(res1 == 5) res4 |= 3; //单击后长按-新增操作(可以连按)
break;
case 2:
res1=btn_rd[0].read(); //两个按钮引脚都读取
res2=btn_rd[1].read();
if(millis()-last>500){
if(res1 == 1) res4 |= 1; //左键点按-向下翻页
else if(res1 == 4) {
res4 |= 2; //左键长按-向上翻页
//if(btn_rd[1].isPressedRaw()) res4 |= 1;
else if(res1 == 5) res4 |= 16; //单击后长按-新增操作(可以连按)
}
else{ // case 2: //3
res1=btn_rd[0].read(); //选项上下键 两个按钮引脚都读取
res2=btn_rd[1].read(); //确定/返回键
if(READGUY_buttons>=3) res3=btn_rd[2].read(); //确定/返回键
//#if 1
bool newval=btn_rd[0].isPressedRaw();
if(newval && last2>1) last2=0;
else if(!(newval || last2>1)) last2=millis(); //捕获按钮松开的行为
//if(newval && btn_rd[1].isPressedRaw()) last2 = 1;
if((res1||res3) && (millis()-last>=btn_rd[1].long_press_ms) && (!btn_rd[1].isPressedRaw())){
//Serial.printf("[%9d] res 1 state: %d %d\n",millis(),longpresstest,pressedRawtest);
if(READGUY_buttons>=3) {
if(res1) res4 |= 2; //点按
else if(res3) res4 |= 1; //点按
}
else res4 = (res1 == 1)?1:2; //左键点按-向下翻页
if(last2 == 1) res4 = 0;//本次触发过3事件
}
if(btn_rd[0].isPressedRaw() && res2){
res4 |= 3; //右键点按-确定
last=millis();
//#endif
/*
uint32_t nowm = millis();
if(res1 && nowm-last >= btn_rd[1].long_press_ms && (!btn_rd[1].isPressedRaw())){
res4 = (res1 == 1)?1:2; //左键点按-向下翻页
last=nowm;
}
else{
if(res2 == 1) res4 |= 4; //右键点按-确定
if(res2) {
if(btn_rd[0].isPressedRaw()) res4 |= 16; //避免GCC警告(我常年喜欢-Werror=all
else if(res2 == 1 && nowm>last) res4 |= 4; //右键点按-确定
else if(res2 == 4 && nowm>last) res4 |= 8; //右键长按-返回
last=nowm;
}
*/
if(res2) {
unsigned long ts=millis();
//Serial.printf("[%9lu] now last2: %lu, threshold %lu\n",ts,last2,ts-last2);
if(newval || ts-last2<=20) { //2024.2.25新增: 20毫秒的去抖时间 防误判
res4 |= 16; //避免GCC警告(我常年喜欢-Werror=all
last2 = 1;
}
else if(res2 == 1) res4 |= 4; //右键点按-确定
else if(res2 == 2) res4 |= 16; //新增: 双击进入操作5
else if(res2 == 4) res4 |= 8; //右键长按-返回
last=ts;
}
if(res4==5 || res4==6) res4=3;
if(res4==5 || res4==6) res4=16;
#if 0
break;
case 3:
res1=btn_rd[0].read();
@@ -686,10 +975,22 @@ uint8_t ReadguyDriver::getBtn_impl(){ //按钮不可用, 返回0.
res4 |= ((btn_rd[0].isPressedRaw()<<1)|1);
last=millis();
}
//if(res3 && ((millis()-last)<btn_rd[0].long_repeat_ms)) res4 |=3;
if(res2 == 1) res4 |= 4;
else if(res2 == 4) res4 |= 8;
//if((res2||res3) && ((millis()-last)<btn_rd[0].long_repeat_ms)) res4 |=3;
if(res2) {
unsigned long ts=millis();
//Serial.printf("[%9lu] now last2: %lu, threshold %lu\n",ts,last2,ts-last2);
if(btn_rd[0].isPressedRaw() || ts-last2<=20) { //2024.2.25新增: 20毫秒的去抖时间 防误判
res4 |= 16; //避免GCC警告(我常年喜欢-Werror=all
}
else if(res2 == 1) res4 |= 4; //右键点按-确定
else if(res2 == 4) res4 |= 8; //右键长按-返回
last=ts;
}
//if(res2 == 1) res4 |= 4;
//else if(res2 == 4) res4 |= 8;
//else if((res2 == 2) || ) res4 |= 16; //新增: 双击进入操作5
break;
#endif
}
return res4;
}
@@ -698,19 +999,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;i<ibytes;i++) byteWrite[i] = 0; //ibytes == (tft->width()+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;
@@ -718,7 +1109,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);

View File

@@ -35,6 +35,7 @@
#include <Arduino.h>
#include <SPI.h>
#include <FS.h>
#include <functional>
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
@@ -91,7 +92,7 @@
#include "guy_button.h" //改自Button2精简而来
#include "guy_version.h"
#include "guy_driver_config.h" //config
#include "guy_config_host.h" //config
#ifdef READGUY_USE_LITTLEFS
#include <LittleFS.h>
#else
@@ -105,9 +106,13 @@
#ifdef READGUY_ESP_ENABLE_WIFI
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#ifdef READGUY_MDNS_SERVICE
#include <ESP8266mDNS.h>
#endif
#ifdef READGUY_UPDATE_SERVER
#include "ESP8266HTTPUpdateServer.h"
#endif
#endif
#ifdef READGUY_ENABLE_SD
#include <SDFS.h>
#endif
@@ -117,10 +122,17 @@
#ifdef READGUY_ESP_ENABLE_WIFI
#include <WiFi.h>
#include <WebServer.h>
#ifdef READGUY_MDNS_SERVICE
#include <ESPmDNS.h>
#endif
#ifdef READGUY_UPDATE_SERVER
#include "HTTPUpdateServer.h"
#endif
#endif
#ifdef READGUY_ENABLE_SD
#if (defined(READGUY_IDF_TARGET_MATRIX_SDIO))
#include <SD_MMC.h>
#endif
#include <SD.h>
#endif
#include <freertos/FreeRTOS.h>
@@ -139,10 +151,10 @@
#define READGUY_epd_rst (config_data[7]) // 目标显示器的 RST 引脚
#define READGUY_epd_busy (config_data[8]) // 目标显示器的 BUSY 引脚
//sd卡驱动部分, 默认使用hspi (sd卡建议用hspi)
#define READGUY_sd_miso (config_data[9]) // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_mosi (config_data[10])// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_sclk (config_data[11])// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_cs (config_data[12])// 目标sd卡的 CS 引脚.
#define READGUY_sd_miso (config_data[9]) // 目标sd卡的 MISO 引脚, 或esp32s3使用SDIO的 DAT0 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_mosi (config_data[10])// 目标sd卡的 MOSI 引脚, 或esp32s3使用SDIO的 CMD 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_sclk (config_data[11])// 目标sd卡的 SCLK 引脚, 或esp32s3使用SDIO的 CLK 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_cs (config_data[12])// 目标sd卡的 CS 引脚, 或esp32s3使用SDIO的 DAT3 引脚
#define READGUY_i2c_sda (config_data[13])// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
#define READGUY_i2c_scl (config_data[14])// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
@@ -151,9 +163,20 @@
#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 将在2.0实装 保留是为了兼容性 让代码更简单维护
#define READGUY_sd_ok (config_data[20]) //SD卡已经成功初始化
#define READGUY_buttons (config_data[21]) //按钮个数, 0-3都有可能
#define READGUY_user1 (config_data[22]) //用户自定义变量 同时用于esp32s3使用SDIO卡数据的DAT1 为-1代表不使用SDIO
#define READGUY_user2 (config_data[23]) //用户自定义变量 同时用于esp32s3使用SDIO卡数据的DAT2
#define READGUY_user3 (config_data[24]) //用户自定义变量
#define READGUY_user4 (config_data[25]) //用户自定义变量
#define READGUY_user5 (config_data[26]) //用户自定义变量
#define READGUY_user6 (config_data[27]) //用户自定义变量
#define READGUY_user7 (config_data[28]) //用户自定义变量
#define READGUY_user8 (config_data[29]) //用户自定义变量
#define READGUY_user9 (config_data[30]) //用户自定义变量
#define READGUY_user10 (config_data[31]) //用户自定义变量
#endif
#define READGUY_SLOW 0
@@ -163,15 +186,48 @@
#define READGUY_SLOW_END 4
#define READGUY_FAST_END 5
//按键行为 下一个 上一个 确定 返回/退出 特殊操作(如切换输入法)
//1个按键 返回 1=点按 2=双击 4=长按 8=三击 3=点按后接长按
//2个按键 返回 1=左键点按 2=左键长按 4=右键点按 8=右键长按 3=按住左键点按右键
//3个按键 返回 1=右键点按 2=左键点按 4=中键点按 8=中键长按 3=中间按键双击(需手动开启)
#define GUY_BTN_PREV 1 //上一个
#define GUY_BTN_NEXT 2 //下一个
#define GUY_BTN_SPECIAL 3 //确定 选择
#define GUY_BTN_OK 4 //返回/退出
#define GUY_BTN_BACK 8 //特殊操作(如切换输入法)
#if __cplusplus >= 201304L
#define RG_IL_CONEXP constexpr inline
#else
#define RG_IL_CONEXP inline
#endif
enum ReadguyButtonGesture: uint8_t{
guyBNone =0,
guyBNext =1,
guyBRight =1,
guyBPrev =2,
guyBLeft =2,
guyBSelect =4,
guyBOK =4,
guyBCancel =8,
guyBBack =8,
guyBSpecial =16
};
class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
public:
#ifdef READGUY_ESP_ENABLE_WIFI
#ifdef ESP8266
typedef ESP8266WebServer ReadguyWebServer;
#ifdef READGUY_UPDATE_SERVER
typedef ESP8266HTTPUpdateServer ReadguyUpdateServer;
#endif
#else
typedef WebServer ReadguyWebServer;
#ifdef READGUY_UPDATE_SERVER
typedef HTTPUpdateServer ReadguyUpdateServer;
#endif
#endif
#endif
ReadguyDriver();
@@ -179,9 +235,10 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
* @param WiFiSet 是否保持AP模式关闭. 0:配网完成自动关WiFi, 1:需要手动调用 WiFi.mode(WIFI_OFF) 关闭WiFi.
* 2:自动连接到已存的WiFi, 但不等待连接成功
* @param initepd 是否初始化墨水屏. 初始化后的首次刷屏必为慢刷. 如果是不断电复位, 可以不初始化墨水屏直接刷屏
* @param initSD 是否初始化文件系统. 选是-初始化SD失败则初始化LittleFs; 选否-不初始化SD也不初始化littlefs.
* @return SD卡是否就绪
*/
uint8_t init(uint8_t WiFiSet = 0, bool initepd = 1);
uint8_t init(uint8_t WiFiSet = 0, bool initepd = 1, bool initSD = 1);
/// @brief 设置显示亮度
void setBright(int d);
/// @brief 返回显示亮度
@@ -206,13 +263,15 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
*/
void display(std::function<uint8_t(int)> f, uint8_t part);
/// @brief 显示图片, 使用抖动算法. 可以用省内存的方法显示, 可以缩放到指定的宽度和高度
void drawImage(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint16_t zoomw=0, uint16_t zoomh=0){
void drawImage(LGFX_Sprite &spr,int32_t x,int32_t y,int32_t zoomw=0, int32_t zoomh=0){
if(READGUY_cali==127) drawImage(*this,spr,x,y,zoomw,zoomh);
}
/// @brief 显示图片, 将图片(任意颜色格式)显示到一个黑白色的sprite(必须是黑白二值型)上 (未经测试)
void drawImage(LGFX_Sprite &base,LGFX_Sprite &spr,uint16_t x,uint16_t y,uint16_t zoomw=0,uint16_t zoomh=0);
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;}
@@ -222,21 +281,26 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
* 2-关闭连续刷屏 关闭16阶灰度抖动 3-开启连续刷屏 关闭16阶灰度抖动 */
void setGreyQuality(uint8_t q) { if(READGUY_cali==127) guy_dev->setGreyQuality(q); }
/// @brief 显示灰度图片,如果支持,否则就不显示灰度图片了. 可以用省内存的方法显示
void draw16grey(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint16_t zoomw=0,uint16_t zoomh=0);
void draw16grey(LGFX_Sprite &spr,int32_t x,int32_t y,int32_t zoomw=0,int32_t zoomh=0);
/** @brief 按照自定义分步显示灰度图片,如果支持,否则就不显示灰度图片了. 可以用省内存的方法显示
* @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 {
@@ -246,6 +310,11 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
} serveFunc;
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void ap_setup();
/*** @brief 初始化WiFi AP模式, 用于将来的连接WiFi 可以自定义模式切换的函数
* @param ssid 设置ap的名称
* @param pass 设置ap的密码
* @param m WiFi模式 */
void ap_setup(const char *ssid, const char *pass, int m=(int)WIFI_AP);
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void server_setup(const String &notify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0);
bool server_loop();
@@ -259,8 +328,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)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
@@ -278,36 +349,76 @@ 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; }
ReadguyButtonGesture getBtn() { return (READGUY_cali==127)?(ReadguyButtonGesture)getBtn_impl():guyBNone; }
/// @brief [此函数已弃用 非常不建议使用] 根据按钮ID来检查按钮. 注意这里如果按下返回0, 没按下或者按钮无效返回1
//uint8_t getBtn(unsigned int btnID){return btnID<getButtonsCount()?(!(btn_rd[0].isPressedRaw())):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!
//添加屏幕驱动范例: 直接添加对应屏幕的类就可以用了
static const char projname[8];
static const char tagname[7];
static const char tagname[8];
//uint8_t config_wifi=0; //是否强行在初始化期间设置WiFi.
void nvs_init(); //初始化持久存储器.
void nvs_deinit();//保存持久存储器的内容
bool nvs_read(); //从持久存储器读取, 返回是否读取成功
void nvs_write(); //写入到持久存储器
#ifdef DYNAMIC_PIN_SETTINGS//数据是否已经校准
int8_t config_data[22];
int8_t config_data[32];
char randomch[4]; //校验用字符串
#else
static const int8_t config_data[22];
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;
@@ -328,7 +439,9 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
ReadguyWebServer sv;
#ifdef READGUY_UPDATE_SERVER
ReadguyUpdateServer httpUpdater;
#endif
String guy_notify=emptyString; //嵌入在网页中的自定义标语
int sfuncs=-1;
String* sfnames=nullptr;
@@ -345,9 +458,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];
@@ -358,59 +481,109 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
static const PROGMEM char index_cn_html16[];
static const PROGMEM char verify_html[];
static const PROGMEM char verify2_html[];
static const PROGMEM char verifybtn_html[3][200];
static const PROGMEM char *verifybtn_html[3];
static const PROGMEM char verifybtn_html1[];
static const PROGMEM char verifybtn_html2[];
static const PROGMEM char verifybtn_html3[];
static const PROGMEM char final_html[];
static const PROGMEM char afterConfig_html[];
static const PROGMEM char home_html[];
static const PROGMEM char end_html[];
//static const PROGMEM uint8_t faviconData[1150];
#ifdef READGUY_USE_DEFAULT_ICON
static const PROGMEM uint8_t faviconData[1150];
#endif
#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
spibz ++;
#ifdef ESP8266
SPI.beginTransaction(SPISettings(ESP8266_SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
#else
epd_spi->beginTransaction(SPISettings(ESP32_DISP_FREQUENCY, MSBFIRST, SPI_MODE0));
#endif
spibz +=1;
}
static void in_release(){//SPI结束传输屏幕数据
spibz --;
#ifdef ESP8266
if(!spibz) SPI.endTransaction();
spibz -=1;
#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: //增加了一些返回系统状态变量的函数, 它们是静态的, 而且不会对程序造成任何影响.
constexpr int getShareSpi() const { return READGUY_shareSpi; }
constexpr int getEpdType () const { return READGUY_epd_type; } // 对应的epd驱动程序代号, -1为未指定
RG_IL_CONEXP int getShareSpi() const { return READGUY_shareSpi; }
RG_IL_CONEXP int getEpdType () const { return READGUY_epd_type; } // 对应的epd驱动程序代号, -1为未指定
//显示驱动部分, 显示默认使用vspi (vspi也是默认SPI库的通道)
constexpr int getEpdMosi () const { return READGUY_epd_mosi; } // 目标显示器的 MOSI 引脚
constexpr int getEpdSclk () const { return READGUY_epd_sclk; } // 目标显示器的 SCLK 引脚
constexpr int getEpdCs () const { return READGUY_epd_cs; } // 目标显示器的 CS 引脚
constexpr int getEpdDc () const { return READGUY_epd_dc; } // 目标显示器的 DC 引脚
constexpr int getEpdRst () const { return READGUY_epd_rst; } // 目标显示器的 RST 引脚
constexpr int getEpdBusy () const { return READGUY_epd_busy; } // 目标显示器的 BUSY 引脚
RG_IL_CONEXP int getEpdMosi () const { return READGUY_epd_mosi; } // 目标显示器的 MOSI 引脚
RG_IL_CONEXP int getEpdSclk () const { return READGUY_epd_sclk; } // 目标显示器的 SCLK 引脚
RG_IL_CONEXP int getEpdCs () const { return READGUY_epd_cs; } // 目标显示器的 CS 引脚
RG_IL_CONEXP int getEpdDc () const { return READGUY_epd_dc; } // 目标显示器的 DC 引脚
RG_IL_CONEXP int getEpdRst () const { return READGUY_epd_rst; } // 目标显示器的 RST 引脚
RG_IL_CONEXP int getEpdBusy () const { return READGUY_epd_busy; } // 目标显示器的 BUSY 引脚
//sd卡驱动部分, 默认使用hspi (sd卡建议用hspi)
constexpr int getSdMiso () const { return READGUY_sd_miso; } // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
constexpr int getSdMosi () const { return READGUY_sd_mosi; }// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
constexpr int getSdSclk () const { return READGUY_sd_sclk; }// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
constexpr int getSdCs () const { return READGUY_sd_cs; }// 目标sd卡的CS引脚. 对ESP32S3, 返回127代表使用SDMMC
constexpr int getI2cSda () const { return READGUY_i2c_sda; }// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
constexpr int getI2cScl () const { return READGUY_i2c_scl; }// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
RG_IL_CONEXP int getSdMiso () const { return READGUY_sd_miso; } // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
RG_IL_CONEXP int getSdMosi () const { return READGUY_sd_mosi; }// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
RG_IL_CONEXP int getSdSclk () const { return READGUY_sd_sclk; }// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
RG_IL_CONEXP int getSdCs () const { return READGUY_sd_cs; }// 目标sd卡的CS引脚. 对ESP32S3, 返回127代表使用SDMMC
RG_IL_CONEXP int getI2cSda () const { return READGUY_i2c_sda; }// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
RG_IL_CONEXP 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 getBlPin () const { return READGUY_bl_pin; } //前置光接口引脚IO
constexpr int getRtcType () const { return READGUY_rtc_type; } //使用的RTC型号(待定, 还没用上)
constexpr int getButtonsCount() const { return READGUY_buttons; } //按钮个数, 0-3都有可能
//constexpr int memWidth () const { return guy_width ; } //返回显存宽度(不是画幅宽度),不会随着画布旋转改变
//constexpr int memHeight () const { return guy_height ; } //返回显存高度(不是画幅高度),不会随着画布旋转改变
RG_IL_CONEXP int getBtn1Pin () const { return abs((int)READGUY_btn1)-1; }
RG_IL_CONEXP int getBtn2Pin () const { return abs((int)READGUY_btn2)-1; }
RG_IL_CONEXP int getBtn3Pin () const { return abs((int)READGUY_btn3)-1; }
RG_IL_CONEXP int getBtn1Info() const { return READGUY_btn1; }
RG_IL_CONEXP int getBtn2Info() const { return READGUY_btn2; }
RG_IL_CONEXP int getBtn3Info() const { return READGUY_btn3; }
RG_IL_CONEXP int getBlPin () const { return READGUY_bl_pin; } //前置光接口引脚IO
RG_IL_CONEXP int getRtcType () const { return READGUY_rtc_type; } //RTC 将在2.0实装 保留是为了兼容性 让代码更简单维护
RG_IL_CONEXP int getButtonsCount() const { return READGUY_buttons; } //按钮个数, 0-3都有可能
RG_IL_CONEXP int getReadguy_user1 () const { return READGUY_user1; } //用户变量
RG_IL_CONEXP int getReadguy_user2 () const { return READGUY_user2; } //用户变量
RG_IL_CONEXP int getReadguy_user3 () const { return READGUY_user3; } //用户变量
RG_IL_CONEXP int getReadguy_user4 () const { return READGUY_user4; } //用户变量
RG_IL_CONEXP int getReadguy_user5 () const { return READGUY_user5; } //用户变量
RG_IL_CONEXP int getReadguy_user6 () const { return READGUY_user6; } //用户变量
RG_IL_CONEXP int getReadguy_user7 () const { return READGUY_user7; } //用户变量
RG_IL_CONEXP int getReadguy_user8 () const { return READGUY_user8; } //用户变量
RG_IL_CONEXP int getReadguy_user9 () const { return READGUY_user9; } //用户变量
RG_IL_CONEXP int getReadguy_user10() const { return READGUY_user10;} //用户变量
RG_IL_CONEXP bool getReadguyUseSdio() const { //返回程序调用SD卡时 是否使用了SDIO
#ifdef CONFIG_IDF_TARGET_ESP32S3 //仅对ESP32S3可用
return (READGUY_user1 != -1) && (READGUY_user2 != -1) && (READGUY_sd_cs != -1);
#else
return 0; //非ESP32S3平台不可用SDIO
#endif
} //用于esp32s3使用SDIO卡数据的DAT2
#ifdef CONFIG_IDF_TARGET_ESP32S3 //仅对ESP32S3可用
RG_IL_CONEXP int getSdio_dat0 () const { return getReadguyUseSdio()?READGUY_sd_miso:-1; } //用于esp32s3使用SDIO卡数据的DAT0
RG_IL_CONEXP int getSdio_dat1 () const { return getReadguyUseSdio()?READGUY_user1 :-1; } //用于esp32s3使用SDIO卡数据的DAT1
RG_IL_CONEXP int getSdio_dat2 () const { return getReadguyUseSdio()?READGUY_user2 :-1; } //用于esp32s3使用SDIO卡数据的DAT2
RG_IL_CONEXP int getSdio_dat3 () const { return getReadguyUseSdio()?READGUY_sd_cs :-1; } //用于esp32s3使用SDIO卡数据的DAT3
RG_IL_CONEXP int getSdio_clk () const { return getReadguyUseSdio()?READGUY_sd_sclk:-1; } //用于esp32s3使用SDIO卡数据的CLK
RG_IL_CONEXP int getSdio_cmd () const { return getReadguyUseSdio()?READGUY_sd_mosi:-1; } //用于esp32s3使用SDIO卡数据的CMD
#else
RG_IL_CONEXP int getSdio_dat0 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT0
RG_IL_CONEXP int getSdio_dat1 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT1
RG_IL_CONEXP int getSdio_dat2 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT2
RG_IL_CONEXP int getSdio_dat3 () const { return -1; } //用于esp32s3使用SDIO卡数据的DAT3
RG_IL_CONEXP int getSdio_clk () const { return -1; } //用于esp32s3使用SDIO卡数据的CLK
RG_IL_CONEXP int getSdio_cmd () const { return -1; } //用于esp32s3使用SDIO卡数据的CMD
#endif
//RG_IL_CONEXP int memWidth () const { return guy_width ; } //返回显存宽度(不是画幅宽度),不会随着画布旋转改变
//RG_IL_CONEXP int memHeight () const { return guy_height ; } //返回显存高度(不是画幅高度),不会随着画布旋转改变
int drvWidth () const { return READGUY_cali==127?guy_dev->drv_width():0; } //返回显示屏硬件宽度(不是画幅宽度)
int drvHeight() const { return READGUY_cali==127?guy_dev->drv_height():0; } //返回显示屏硬件高度(不是画幅高度)
//int width () const { return (getRotation()&1)?drvHeight():drvWidth(); }
@@ -426,10 +599,11 @@ class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
void implBeginTransfer() { guy_dev->BeginTransfer(); } //此函数用于开启SPI传输, 只能在自定义刷屏函数中使用!!
void implEndTransfer() { guy_dev->EndTransfer(); } //此函数用于开启SPI传输, 只能在自定义刷屏函数中使用!!
/// @brief 分阶段显示图片, 使用抖动算法. 更加的省内存.目前函数
void drawImageStage(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint8_t stage,uint8_t totalstage,
uint16_t zoomw=0,uint16_t zoomh=0){ drawImageStage(*this,spr,x,y,stage,totalstage,zoomw,zoomh); }
void drawImageStage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,
uint8_t stage,uint8_t totalstage,uint16_t zoomw=0,uint16_t zoomh=0);
void drawImageStage(LGFX_Sprite &spr,int32_t x,int32_t y,uint8_t stage,uint8_t totalstage,
int32_t zoomw=0,int32_t zoomh=0){ drawImageStage(*this,spr,x,y,stage,totalstage,zoomw,zoomh); }
void drawImageStage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,int32_t x,int32_t y,
uint8_t stage,uint8_t totalstage,int32_t zoomw=0,int32_t zoomh=0);
};
#undef RG_IL_CONEXP
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */