5 Commits
1.0.2 ... 1.3.1

Author SHA1 Message Date
fsender
85a785eabd update to 1.3.1 ver 2023-11-07 16:41:37 +08:00
fsender
a8b2540468 update to 1.3.0 version 2023-11-06 15:39:13 +08:00
fsender
b5d77bc054 update rotation support 2023-11-03 21:00:36 +08:00
fsender
6fef19e3e0 update to 1.2.0 ver 2023-11-03 18:48:56 +08:00
fsender
26a041c3b9 update to 1.1.1 version 2023-10-20 18:41:20 +08:00
46 changed files with 23240 additions and 1139 deletions

View File

@@ -1,10 +1,88 @@
## 即将发布: Release 1.1.0 - 2023/09/25
## Release 1.3.1 - 2023/11/7
即将发布: 1. 可以配网了 也可以在线更新固件了. 注意在2MB flash的硬件上是无法通过在线更新固件的. 现在还需要你通过示例来进行配网!
1. 增加了分步绘制的支持. 可以使用 READGUY_FAST_START 等绘制选项来控制绘制过程:
- READGUY_SLOW 慢刷, 完整的进行一次慢刷, 等于连续执行慢刷开始和慢刷结束.
即将发布: 2. 增加了更多示例程序.
- READGUY_FAST 快刷, 完整的进行一次快刷, 需要等待刷完才能继续执行代码.
即将发布: 3. 修复了一些bug
- READGUY_SLOW_START 慢刷开始, 此过程不会进行等待, 发送完要刷新的缓存之后立刻返回
- READGUY_FAST_START 快刷开始.
- READGUY_SLOW_END 慢刷结束, 调用慢刷开始之后才能执行. 会等待到屏幕刷完再执行后续操作.
- READGUY_FAST_END 快刷结束, 如果两次调用间隔时间屏幕已经刷完, 那么该函数会完成刷屏后续操作之后立刻返回.
2. 修复了首次配置驱动时, 墨水屏显示页面错位的bug (现在对于新设备, 配置起来会像以前一样丝滑)
3. 修复若干其他bug
## Release 1.3.0 - 2023/11/6
1. 增加了真.保姆级的教程 (详细到注释比代码多很多倍)
2. 目前所有支持的屏幕设备都可以在主控复位之后, 不需要调用init函数即可直接快刷(也不会变成慢刷). 为此, 调用init函数之后, 会让屏幕的下一次刷新变为慢刷.
3. 优化了所有的驱动程序. 包括灰度效果, 内存占用等.
4. 增加了更多的图片显示示例程序. 现在支持多种图片显示方式:
4.1. 在内存中开辟新画布(sprite). 支持颜色格式有1,2,4,8bit灰度(greyscale), 1,2,4,8bit调色板(palette), 8bit-RGB332, 16bit-RGB565, 24bit-RGB888.
4.2. 画布可以用抖动算法在屏幕上显示为二值图.
4.3. 画布可以在屏幕上显示为16级灰度图.
4.4. 画布可以显示在屏幕缓存中, 此过程不刷新屏幕. 下次刷新屏幕时, 画布内容将呈现在屏幕缓存中.
4.5. 将画布缩放到指定的宽度和高度后显示.
4.6. 显示BMP, JPG, PNG(仅限ESP32系列)图片文件到画布上.
4.7. 直接将图片文件刷新到屏幕上, 无需画布(sprite).
4.8. 将图片文件显示到屏幕缓存内, (不显示).
4.9. 将图片文件以16级灰度显示到屏幕上, 无需画布(sprite).
4.10. 缩放显示图片文件, 需要指定缩放比例. >1.0为放大, <1.0为缩小. (float scale_x, float scale_y).
5. 修复若干bug.
## Release 1.2.0 - 2023/11/3
1. 添加了图片demo, 和 可选关闭的WiFi 的示例程序。其中图片相关功能相当节省内存, 还请大胆使用。
2. 新增了接口函数 getReadguy...(), guyMemoryWidth(), guyMemoryHeight(), guyDriverWidth(), guyDriverHeight() 等函数, 可以在程序内获取到一些硬件相关信息。
3. 增加了保姆级教程, 比现有教程更加简单易用。
4. 修复了一些bug。现在4.2寸HINK屏幕的16灰度刷新不会导致内存溢出了.
## Release 1.1.1 - 2023/10/20
1. 引脚配置页更新,内置预设开发板的引脚预定义。此选项的内置定义可以被用户更改成自己的板子预定义。
2. 可以配置wifi并对时。
3. 支持在库内设置i2c总线。实际i2c功能需要自己编写程序本库只提供了i2c引脚的定义接口。此次更新之后设备需要重新配网。
4. 支持16级灰度的抖动算法并可在编译时切换显示算法。
5. 添加了更多例程,例程注释更简洁易用。比如 WiFi获取时间demo, 按键demo, 文本和字体demo, 使用静态引脚定义的demo.
6. 修复了一些bug.
## Release 1.1.0 - 2023/09/25
1. 可以配网了 也可以在线更新固件了. 注意在2MB flash的硬件上是无法通过在线更新固件的. 现在还需要你通过示例来进行配网!
2. 增加了更多示例程序.
3. 修复了一些bug
4. 适配了16级灰度的floyd steinberg抖动算法, 提升了灰度的画质 (显示灰度渐变色, 则不会产生色块, 而是自然的像素抖动)
### Release 1.0.2 - 2023/09/24

View File

@@ -1,10 +1,12 @@
# readguy 自由的墨水屏阅读器驱动库
[![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">
**版本1.0.2正式发布欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~
**版本1.3.1正式发布欢迎分享、star和fork~** 上面的图是项目看板娘, 盖. 可爱的盖姐在等你哟~
欢迎克隆, 项目交流QQ群: 926824162 (萌新可以进来问问题的哟), 项目的 Bilibili 主页:
欢迎克隆, 项目交流QQ群: 926824162 (萌新可以进来问问题的哟), 项目的 Bilibili 主页: [BV1f94y187wz](https://www.bilibili.com/video/BV1f94y187wz/) 记得三连+关注我这个宝藏up主哦~
注意, 有问题一定要先加群问, 先不要提issue, 提了小影 *(也就是作者FriendshipEnder)* 也不会看的.
@@ -30,6 +32,12 @@
#### 可以使用wifi配网来配置硬件信息。
有不少人吐槽我这个库的WiFi功能可不可以关掉 就是把WiFi独立出来 其实是可以的, 教程如下
其中的WiFi功能, 其实是可以禁掉的. 只要你提前配置成功, 那么就可以摆脱WiFi配网配引脚功能.
使用方法: 打开文件[guy_driver_config.h](src/guy_driver_config.h), 随后便根据注释来选择性的开启或关闭一些系统功能.
----
## 支持芯片esp8266/esp32/esp32s3/esp32s2/esp32c3

View File

@@ -6,8 +6,9 @@
*
* @file ex01_helloWorld.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-09-19
* @version 1.1
* @date create: 2023-09-19
* last modify: 2023-11-06
* @brief ReadGuy最基础的HelloWorld显示.
*
* @note 食用方法:
@@ -27,6 +28,79 @@
* 你需要知道你的哪个引脚对应哪个GPIO, 才能使用这个库 (带来的不便请谅解nia~)
* 配置好了, 代码就开始正常运行了.
*
* 以下是看不懂代码的解决方法. 仔细看就行了
* {0} 代码食用注意事项:
* 这一部分的代码很难读, 或者按维莫斯小姐的说法, 很 "抽象" .
* 如果你要开始一行一行读取下去, 不妨先在这里停顿一下, 看一下一些需要你提前知道的东西.
*
* {1} 画布, 或者叫sprite. 是一块专门存储图像的数据结构.
* 画布需要大量的内存来存放图像的像素, 但是你可以对画布进行任何图像操作, 就像是在墨水屏上能进行的操作一样多.
* 画布类型的数据结构至少需要包含3个要素: 宽度, 高度, 每像素需要的字节数.
* 画布需要的内存为 宽度*高度*每像素需要的字节数. (1bit图像需要8分之一字节)
* 在画布上可以进行几乎任何图像操作, 就像是在墨水屏上能进行的操作一样多.
* 而且你还可以获取像素的颜色 (这和你创建的画布的颜色格式有关).
* 画布可以随时被快速的显示到墨水屏上, 显示完了之后再使用display方法, 你刚才创建的画布就显示出来了.
*
* {2} 屏幕缓存. 你就从来没好奇过, 墨水屏上怎么呈现图像的? 我好奇过.
* 我翻看了里面那些错综复杂又是深奥的C++骚操作又是类继承又是虚函数指针...反正就是抽象而且难懂的的源代码,
* 简而言之, 墨水屏显示的原理就是墨水屏驱动里面也有个画布, 不过这个画布是1bit的(每8像素消耗1字节内存).
* 所有对墨水屏进行的操作其实都是对那个内部的画布进行的操作.
* 在你使用墨水屏的display方法时, 呃, 墨水屏的驱动程序可以读取到这个屏幕缓存的所有字节.
* 并且根据驱动手册的方法将这个画布上的字节发送到屏幕上.
* 发送完成之后再通过一些代码, 墨水屏就会刷屏, 将这个屏幕缓存里的内容显示出来了.
* 维莫斯小姐: 苏滴嘶内~
* 下面的库其实用了一些更加骚气的方法, 直接读取了这块画布, 从而能允许你在ESP8266上也可以...显示不小的图片
*
* {3} 灰度显示. ReadGuy这个库的一大特色就是支持灰度显示.
* 欸, 可是屏幕缓存画布只有1bit啊, 怎么显示灰度啊?
* 不用急, 原理也不难.
* 16级灰度颜色的刷屏分为15个步骤. 因为白色不需要刷.
* 在开始刷屏之前, 务必进行一次慢刷, 确保所有的白色像素都足够白.
* 第一次刷屏, 通常会刷最浅的灰色.
* 此时, 首先将屏幕缓存内所有需要显示为这个颜色的像素全写入为黑色(只是名义上的黑色, 实际上是设定的颜色).
* 再初始化墨水屏的刷屏功能.
* - 此处需要额外设定刷屏的颜色的参数. 此参数设定是可以由驱动程序自动完成的, 不过也可以通过你调用函数完成.
* - 用户只需要用简单的setDepth函数设置颜色即可. 用户设定的颜色深度通常用于显示字符串或者形状等GUI控件.
* - 刷图程序的灰度图片刷新功能会自动控制刷屏颜色, 不需要手动调用.
* 最后刷屏. 因为之前刷图程序已经设定过刷的颜色了, 所以此时刷屏呈现的颜色就是之前设定的颜色.
* 第2-15次刷屏原理类似.
* 每次追加黑色像素, 追加的像素就是来显示灰度的像素. 而原本的黑色像素就是之前那些灰度颜色的像素.
*
* \*(^_^)*/ /** 我相信你应该还不懂. 我就举个例子吧.
* 比如要显示一个渐变色, 从左到右为白色→黑色的渐变图.
* 首先进行一次慢刷, 确保要刷图的地方都是纯洁的白色.
* ≡=-(1)-=≡
* 筛选出最浅的灰色设为灰1, (灰X代表灰度颜色, 灰15代表黑色)
* 再把图片内所有颜色和灰1最接近的颜色写入到屏幕缓存内.
* 此时屏幕缓存内的"黑色"部分都是与灰1最接近的颜色, 其他颜色均为"白色"部分.
* 刷好了之后, 进行一次刷屏. (系统自动调用setDepth(1)来保证接下来图片的黑色部分其实是灰1).
* ≡=-(2)-=≡
* 筛选出比最浅的灰色深一点的颜色设为灰2, 把图片内和灰2最接近的颜色写入屏幕缓存, 但保持原本代表灰1的"黑色"不变.
* (也就是说, 把灰2的像素写入之后, 屏幕缓存的黑色像素只多不少. 写入过程中, 只能是白→黑, 不存在黑→白)
* 此时屏幕缓存内的"黑色"部分包含了与灰2最接近的颜色, 和刚才刷完的与灰1最接近的颜色. 其他颜色均为"白色"部分.
* 刷好了之后, 进行一次刷屏. (系统自动调用setDepth(2)来保证接下来图片的黑色部分其实是灰2)
* 注意屏幕显示, 原本的灰1并没有颜色变化, 变化的都是本次追加的"灰2"代表的"黑色".
* 追加的像素会显示为新设定的颜色.
* ≡=-(3)-=≡
* 对于灰3, 和灰2一样, 追加显示到屏幕缓存内.
* (也就是说, 屏幕缓存的黑色像素还是只多不少. 写入过程中, 只能是白→黑, 不存在黑→白)
* 此时屏幕缓存内的"黑色"部分包含了与灰3, 灰2和灰1最接近的颜色. 其他颜色均为"白色"部分.
* 设置颜色深度, 刷屏.
* 对于灰4~灰15, 都这么做
* ≡=-(5)-=≡
* 刷完灰15之后, 灰度图就刷完了.
*
* {4} 自定义刷屏函数. 到底是什么决定了刷屏时的每一像素是什么颜色? 是黑是白?
* 其实刷屏的本质就是通过调用一个外部函数.
* 该函数可以根据输入的像素坐标位置来决定输出的像素颜色.
* 为了简化程序调用过程并提高调用速度, 此处的像素坐标位置参数为一个整数(而不是两个)
* 至于该怎么调用这个函数, 并不是你需要了解的事情.
*
* 关于图形函数:
* 用过 LovyanGFX / Adafruit GFX / GxEPD2 / TFT_eSPI 库的用户, 可以直接套用他们的接口函数
* 用过 U8G2 的用户, 接口函数和U8G2不一样的,
* 请参考上述库的示例程序.
*
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
@@ -46,14 +120,17 @@
*/
//在这里包含程序需要用到的库函数
//就像是你在C语言里面总是用的 #include <stdio.h> 一样,
//为了能在这里使用readguy库, 你需要在这里调用 #include "readguy.h" 来包含此库
//在platformio中, 还要额外 #include <Arduino.h> 来确保你用的是Arduino环境.
#include <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
#include "readguy.h" //包含readguy_driver 基础驱动库
readguy_driver guy;//新建一个readguy对象, 用于显示驱动.
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
//所有对墨水屏的操作都是对guy这个对象进行的操作.
void drawLines(); //声明一个函数, 用于显示一些线条. 此函数在后面的程序中会用到的
void setup(){
void setup(){ //Arduino的setup函数. 这个函数在上电之后仅执行一次.
// --------------------- 1 - 初始化和启动ReadGuy -------<
Serial.begin(115200); //初始化串口
@@ -64,19 +141,24 @@ void setup(){
guy.init(); //初始化readguy_driver 基础驱动库.
//首次初始化完成之后, 以后再初始化就不需要配网了, 除非你抹除了芯片的flash
//完成之后会全屏刷新一次
//完成之后会让下一次刷屏 全屏慢速刷新一次, 之后的刷屏即可自由定义是全刷还是局刷.
guy.setFont(&FreeMonoBold9pt7b); //设置显示的字体
//字体可以参考LovyanGFX的示例程序.
guy.setTextColor(0,1); //设置显示的颜色. 0代表黑色, 1代表白色
//此函数的作用是设置显示颜色. 左边的0代表前景色(文字颜色: 黑色), 右边的1代表背景色(白色)
//类似于 guy.setTextColor(0) 的用法说明此颜色为透明背景.
guy.drawString("Hello Readguy!",10,10); //用此函数将字符串显示到屏幕缓存内
//调用此函数并不会立即刷屏, 而是会将文本字符串写入到屏幕缓存, 在下一次调用display()函数之后就会显示出来.
//也可以用print函数来显示.
//guy.setCursor(10,10); //设置显示的坐标
//guy.print("Hello Readguy!"); //使用这个函数也能显示出字符串, 但是需要提前使用setCursor确定显示坐标
guy.display(true); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上
//guy.display(false); // 慢速刷新.
guy.display(READGUY_FAST); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上. 可简写为 guy.display(), 效果一样.
//guy.display(READGUY_SLOW); // 慢速刷新.
//想知道更多内容, 欢迎移步到其他示例.
}
@@ -85,6 +167,7 @@ void loop(){
//什么也不做, 毕竟刷新墨水屏要消耗墨水屏的阳寿.
//盖姐说它们也是有阳寿的. 刷多了会老化.
delay(1);
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -11,6 +11,25 @@
* @brief ReadGuy功能演示.
* 将根目录下的data文件夹 上传到LittleFS之后运行效果更佳
* 或者可以准备一张SD卡,并准备在卡的根目录下放置data文件夹内的文件.
* 就是SD卡内放data文件夹内的所有文件, 不能额外套文件夹.
* 如果你的SD卡插入电脑上显示为可移动磁盘, 那么双击SD卡目录就要看到这个文件夹里的文件
*
* 默认的文件系统为SD卡. 当没有插入SD卡时, 会读取LittleFS文件系统.
* 没有条件准备SD卡的, 可以烧录LittleFS文件系统.
*
* 对于ESP8266 Arduino 用户, 在项目草图文件夹内新建一个data文件夹, 并放入文件 (示例已提供data文件夹)
* 再在 arduinoIDE 的工具选项里选择 ESP8266 LittleFS Data Upload.
* 没有这个选项的需要参考以下文档安装ESP8266 Sketch upload tool
* https://randomnerdtutorials.com/install-esp8266-nodemcu-littlefs-arduino/
*
* 对于ESP32 Arduino 用户, 也要在项目草图文件夹内放一个data文件夹, 并把文件放入其中 (示例已提供data文件夹)
* 再在 arduinoIDE 的工具选项里选择 ESP32 Sketch data upload, 最后选择LittleFS.
* 没有这个选项的需要参考以下文档安装ESP32 LittleFS upload tool
* https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
*
* 对于PlatformIO 用户, 需要进入platformIO扩展界面, 选择Upload Filesystem Image, 上传项目文件.
* ESP8266和ESP32都要用这种方法.
*
* 用于演示BMP格式图片灰度显示.
*
* @note 食用方法:
@@ -51,7 +70,7 @@
#include <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
readguy_driver guy;//新建一个readguy对象, 用于显示驱动.
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
void drawLines(); //声明一个函数, 用于显示一些线条. 此函数在后面的程序中会用到的
@@ -66,7 +85,6 @@ void setup(){
guy.init(); //初始化readguy_driver 基础驱动库.
//首次初始化完成之后, 以后再初始化就不需要配网了, 除非你抹除了芯片的flash
//完成之后会全屏刷新一次
// ------------------- 2 - 使用ReadGuy来显示字符串 ------<<
@@ -77,14 +95,14 @@ void setup(){
guy.drawString("Hello Readguy!",10,10); //用此函数将字符串显示到屏幕缓存内
//guy.print("Hello Readguy!"); //使用这个函数也能显示出字符串, 但是需要提前使用setCursor确定显示坐标
guy.display(true); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上
//guy.display(false); // 慢速刷新.
guy.display(READGUY_FAST); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上
//guy.display(READGUY_SLOW); // 慢速刷新.
guy.setCursor(10,30); //设置显示的坐标
guy.print("Hello~"); //或者用print函数在屏幕上打印字符串, 数值, 字符等等... 两种函数都行
guy.display(false); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影
guy.display(READGUY_SLOW); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影
guy.drawString(guy.SDinside()?"SD card OK.":"No SD card!",10,50); //检查readguy是否插入了SD卡
@@ -135,7 +153,7 @@ void setup(){
guy.fillScreen(1);
guy.display(false); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影
guy.display(READGUY_SLOW); // 慢速刷新. 慢刷的对比度显著高于快速刷新, 而且可以消除残影
for(int i=1;i<16;i++){ //灰度测试, 循环设置不同灰度
@@ -180,11 +198,17 @@ void setup(){
guy.drawImage(sp,10,10); //使用抖动像素的方式显示图片(不是灰度, 只有黑点和白点的那种显示效果)
guy.display(); //自从1.2.0更新之后, drawImage不再刷屏, 此处需要额外调用display函数刷屏
Serial.printf("[%lu] drawn dithering bmp.\n",millis()); //显示信息
delay(2000);
guy.setGreyQuality(1); //设置灰度刷新方式. 对于支持连续刷灰度的屏幕才有效.
// 1(默认)为连续刷新, 0为循环调用 setDepth+display 来刷新 (可能会有白边)
//如果连续刷新效果不好, 请将此处改为0再试一次.
guy.draw16grey(sp,10,10); //使用16级灰度的方式显示图片 需要的时间比较长
sp.deleteSprite(); //关闭图片文件, 释放图片占用的大量内存
@@ -198,7 +222,7 @@ void setup(){
guy.setFont(&FreeMonoBold9pt7b); //设置文本字体
guy.setTextColor(0); //设置文本颜色
guy.fillScreen(1); //用白色清屏.
guy.display(false); //慢刷. 注意, 进行慢刷操作之后, 所有之前显示的灰度内容均会被重新刷成纯黑色
guy.display(READGUY_SLOW); //慢刷. 注意, 进行慢刷操作之后, 所有之前显示的灰度内容均会被重新刷成纯黑色
//不管是浅灰色还是深灰色, 进行慢刷之后只有黑白色. 原来的非白色像素(浅灰色,深灰色和黑色等) 会全刷成白色.
guy.drawString("Rotation 0",10,12); //默认旋转方向为0. 实际的默认方向取决于屏幕IC. 大多数屏幕IC是竖屏.
@@ -234,13 +258,15 @@ void setup(){
guy.setTextColor(1); //设置文本颜色为白色,因为被反色的屏幕的当前像素颜色以黑色像素为主
guy.drawString("Wake Up! ~\\(^_^)/~",10,50); //退出睡眠状态
guy.display(false); //使用慢刷 来唤醒处于低功耗状态下的屏幕.
guy.display(READGUY_SLOW); //使用慢刷 来唤醒处于低功耗状态下的屏幕.
// ------------------ 6 - 可以利用灰度来达到的一些显示效果 --<<<<<<
guy.fillScreen(1); //清屏
guy.display(false); //慢刷清屏
guy.display(FILL_WHITE,READGUY_SLOW); //慢刷清屏. 左侧的FILL_WHITE表示 不写入屏幕缓存, 直接刷全白
//可以改为FILL_BLACK来设置写入缓存全黑.
//以上的方式均不会修改屏幕缓存中的内容. 右侧的false表示全屏慢刷.
guy.setTextColor(0); //设置显示的颜色. 0代表黑色, 一个参数代表黑白显示.
//注意这个函数不是设定显示灰度的函数!

View File

@@ -0,0 +1,82 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file ex03_buttons.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-10-20
* @brief ReadGuy 按键功能演示. 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.
*/
//在这里包含程序需要用到的库函数
#include <Arduino.h> //arduino功能基础库. 在platformIO平台上此语句不可或缺
#include "readguy.h" //包含readguy_driver 基础驱动库
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
void setup(){
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库. 尽管初始化过程会刷屏, 但此示例不会用到屏幕.
//if(guy.width()<guy.height()) guy.setRotation(1); //横向
Serial.println(F("[readguy] Button demo")); //显示文本 默认是不支持中文显示的.
guy.println("Button demo"); //显示文本 默认是不支持中文显示的.
guy.display();//刷新墨水屏.
}
void loop(){
int val = guy.getBtn(); //此函数用于获取按键状态 没有按键按下时 返回0.
//1个按键 返回 1=点按 2=双击 3=长按 4=三击
//2个按键 返回 1=左键点按 2=左键长按 3=右键点按 4=右键长按
//3个按键 返回 1=左键点按 2=右键点按 3=中键点按 4=中键长按
if(val>0){
int c = guy.getButtonsCount(); //此函数用于返回设备有多少个按键. [最近更新的函数]
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!");
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!");
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!");
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!");
break;
}
guy.display();
}
delay(10);
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -0,0 +1,88 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file 1_wifi_ap_server.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-09-29
* @brief ReadGuy配网服务器演示.
// 注意, 为了避免此项目占用的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 基础驱动库
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
typedef ReadguyDriver::ReadguyWebServer* server_t; //类型名太长太繁琐, 使用typedef减短
typedef ReadguyDriver::serveFunc event_t ; //存储一个WiFi功能事件.
void f1(server_t sv); //服务器响应回调函数. 当启动AP配网服务器时, 这些函数将会被调用
void f2(server_t sv);
void f3(server_t sv);
void setup(){
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库. 尽管初始化过程会刷屏, 但此示例不会用到屏幕.
event_t server_event[3]={ //设置一个服务器响应回调函数的容器.
{"按钮1","/btn1",f1}, //需要向容器的第一个元素中添加链接名称, 链接响应和链接回调函数
{"按钮2","/btn2",f2}, //链接响应必须以斜线字符开头
{"","/btn3",f3} //将HTML响应的事件名改为空白字符串, 就不会显示进入此链接的按钮, 只能通过其他页面跳转
}; //这里不太好理解, 不过看完示例应该就会了
guy.ap_setup(); //初始化WiFi AP模式 (可以理解为路由器模式)
guy.server_setup(String(F("配网服务器演示:可以放置自己的链接和回调函数")),server_event,3); //初始化服务器.
//这些服务器响应回调函数会打包进入初始化参数列表中.
//上方的字符串可以在用户访问主页时, 显示在主页的第二行.(作为通知显示, 但并不是通知)
}
void loop(){
guy.server_loop(); //让服务器一直运行
}
// 以下演示了如何向配网服务器添加回调函数.
//其中, sv 参数指向了一个服务器类型的变量. 当有来自客户端的请求时, 需要通过sv来发送响应消息.
void f1(server_t sv){ //使用PSTR来减少对内存的消耗(不加PSTR()或者F()则字符串会存到.rodata,占用宝贵的内存)
sv->send_P(200, PSTR("text/html"), PSTR("<html><body><meta charset=\"utf-8\">按钮1服务函数</body></html>"));
} //于此相应, 使用send_P函数而不是send函数来发送数据
void f2(server_t sv){ //使用 R"EOF()EOF" 括起来的字符串, (不含括号本身) 其中的字符不需要转义, 包括回车换行
sv->send_P(200, PSTR("text/html"), PSTR(R"EOF(<html>
<body>
<meta charset="utf-8">2 <a href="/btn3">3</a></body></html>)EOF"));
} //即使是缩进的空格也会被包含到字符串内, 此外, 引号等符号也不需要转义符.
void f3(server_t sv){ //此函数不会直接在首页中显示链接, 只能通过btn2中的链接跳转到本页面.
sv->send_P(200, PSTR("text/html"), PSTR("<html><body><meta charset=\"utf-8\">按钮3服务函数</body></html>"));
}
/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -0,0 +1,296 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file 2_wifi_config.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-10-14
* @brief ReadGuy配网服务器 配置并连接附近的WiFi网络演示程序.
编译烧录后, 本程序将使用AP方式配网并在连接到网络时访问NTP服务器来在墨水屏上显示时间.
同时开启在STA上的服务器, 供这个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 基础驱动库
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
typedef ReadguyDriver::ReadguyWebServer* server_t; //类型名太长太繁琐, 使用typedef减短
typedef ReadguyDriver::serveFunc event_t ; //存储一个WiFi功能事件.
void f1(server_t sv); //服务器响应回调函数. 当启动AP配网服务器时, 这些函数将会被调用
void f2(server_t sv);
time_t getNTPTime(); //NTP获取时间的函数
int conf_status = 0; //标记WiFi配网状态: 当此值为1时, 说明配网程序收到了WiFi SSID和密码信息, 尝试连接.
//此变量为2 说明配网成功了. 连接到了WiFi并显示当前时间.
int scanres = 0; //WiFi扫描结果数量
String recv_ssid = "";//接收到的WiFi SSID
String recv_psk = ""; //接收到的WiFi 密码
void setup(){
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库. 尽管初始化过程会刷屏, 但此示例不会用到屏幕.
Serial.println(F("[readguy] WiFi Configure demo")); //显示文本 默认是不支持中文显示的.
guy.println(F("WiFi Configure demo")); //显示文本 默认是不支持中文显示的.
guy.display();//刷新墨水屏.
event_t server_event[3]={ //设置一个服务器响应回调函数的容器. 使用方法详见wifi_ap_server示例
{"配置WiFi","/wificfg",f1}, //配置WiFi的入口链接. 在AP模式下点击此链接将会进入配网功能页面.
{"","/wifiok",f2} //收到wifi连接请求后 发送的响应. 此函数调用之后会尝试切换到STA模式, 连接到WiFi.
}; //本驱动库不会单独保存用户的WiFi SSID和WiFi密码信息
WiFi.mode(WIFI_AP_STA); //扫描网络需要切换到STA模式进行扫描. 扫描完成之后将会切换到AP模式
guy.server_setup(String(F("WiFi配网示例")),server_event,2); //初始化服务器.
//这些服务器响应回调函数会打包进入初始化参数列表中.
//上方的字符串可以在用户访问主页时, 显示在主页的第二行.(作为通知显示, 但并不是通知)
do{ //没有连接到目标SSID时 一直循环连接.
conf_status=0; //重置配置状态变量
scanres = WiFi.scanNetworks(); //开始扫描网络
Serial.println("[readguy] WiFi Scan OK."); //关闭服务器, 尝试连接, 连接成功之后将会在屏幕上显示
guy.println("WiFi Scan OK."); //连接失败则会重新进入循环
guy.display();
IPAddress local_IP(192,168,4,1); //设置本地AP的IP地址, 网关和子网掩码.
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);
WiFi.softAPConfig(local_IP, gateway, subnet); //注册一个WiFi AP(类比无线路由器), 可以被手机等设备连接
WiFi.softAP("readguy","12345678"); //初始化WiFi AP模式 (可以理解为路由器模式)
while(conf_status==0){
guy.server_loop(); //让服务器一直运行 此函数总是返回true. 因此配网何时完成, 应在程序里设定, 适可而止
}
//guy.server_end(); //看来是接收到啊WiFi名称和密码消息了, 现在关闭服务器, 尝试连接到WiFi...
Serial.println("[readguy] received SSID and PSK info"); //关闭服务器, 尝试连接, 连接成功之后将会在屏幕上显示
guy.println("received SSID and PSK info"); //连接失败则会重新进入循环
guy.display(); //显示到墨水屏
WiFi.scanDelete(); //删除WiFi扫描数据
scanres = 0; //设置扫描到的网络个数为0
if(recv_ssid != "-"){ //检测SSID是否为空. 一般的网络SSID应该不会是这个名称吧...
//WiFi.mode(WIFI_STA);
WiFi.begin(recv_ssid,recv_psk); //启动STA模式, 然后尝试连接网络(当前维WIFI_AP_STA模式, 可以连接到网络)
for(int i=0;i<1500;i++){ //等待15秒, 没连上就退出.
if(WiFi.status() == WL_CONNECTED){ //一旦连接成功就退出循环
conf_status=2; //标记为连接成功
guy.print("Connected! IP:"); //连接成功! 显示消息然后退出循环
guy.println(WiFi.localIP());
Serial.print("[readguy] Connected!IP:"); //连接成功! 显示消息然后退出循环
Serial.println(WiFi.localIP());
guy.display();
break;
}
delay(10);
}
}
if(conf_status!=2){ //经过循环之后发现wifi并没连接上 显示连接失败的信息.
guy.println("WiFi failed! AP and server restarted."); //连接成功! 显示消息然后退出循环
Serial.println("[readguy] WiFi failed! AP and server restarted."); //连接成功! 显示消息然后退出循环
guy.display();
}
}while(conf_status!=2); // conf_status==2说明连接上了
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));
guy.display();
guy.server_setup("现在是联网的STA模式."); //如果没有调用server_end函数 连续调用server_setup将自动结束之前的服务器
}
void loop(){
guy.server_loop();
}
// 以下演示了如何向配网服务器添加回调函数.
//其中, sv 参数指向了一个服务器类型的变量. 当有来自客户端的请求时, 需要通过sv来发送响应消息.
void f1(server_t sv){ //使用PSTR来减少对内存的消耗(不加PSTR()或者F()则字符串会存到.rodata,占用宝贵的内存)
String webpage_html = F(
"<!DOCTYPE html>"
"<html lang='zh-cn'>"
"<head>"
"<meta charset='UTF-8'>"
"<title>配置WiFi连接</title>"
"</head>"
"<body>"
"<form name='input' action='/wifiok' method='GET'>"
"<h2>wifi配置页面</h2>"
"如需刷新WiFi列表, 请选择选项``重新扫描WiFi``并点击保存, 而不是刷新此页, 刷新此页不会刷新WiFi列表<hr/>"
"wifi名称:<br/>"
// "<input type='text' name='ssid'>"
"<select name='ssid'>"
// "<option value='0'>测试WiFi名称</option>" //在此放置你扫描到的所有WiFi名称
// "<option value='1'>1.54寸Lilygo</option>"
);
String webpage_html2 = F(
"</select>"
"<br/>"
"wifi密码:<br/>"
"<input type='text' name='psk'><br/>"
"<input type='submit' value='保存'>"
"</form>"
"</body>"
"</html>");
for(int i=0;i<=scanres;i++){
webpage_html += "<option value='";
webpage_html += i;
webpage_html += "'>";
if(i<scanres) webpage_html += WiFi.SSID(i);
else webpage_html += "重新扫描WiFi";
webpage_html += "</option>";
}
sv->send_P(200, PSTR("text/html"), (webpage_html+webpage_html2).c_str());
} //于此相应, 使用send_P函数而不是send函数来发送数据
void f2(server_t sv){
if(sv->hasArg("ssid")) {
if((sv->arg("ssid")).toInt() == scanres){
recv_ssid = "-"; //空白字符串 退出 重新扫描
}
else recv_ssid=WiFi.SSID((sv->arg("ssid")).toInt());
if(sv->hasArg("psk")) {
recv_psk=sv->arg("psk");
if(recv_ssid!="") conf_status=1; //接收到了WiFi SSID信息和密码信息
}
}
if(recv_ssid == "-"){
sv->send_P(200, PSTR("text/html"), PSTR("<html><body><meta charset=\"utf-8\" http-equiv=\"refresh\""
" content=\"8;url=/wificfg\">正在扫描WiFi, 8秒后自动跳转.</body></html>"));
}
else if(conf_status>=1){
String s=F("<html><body><meta charset=\"utf-8\">配置成功,正在连接...<br/>WiFi名称:");
s += recv_ssid;
s += F("<br/>WiFi密码:");
s += recv_psk;
s += F("<br/>连接成功的消息会显示在墨水屏上.</body></html>");
sv->send_P(200, PSTR("text/html"), s.c_str());
}
else sv->send_P(200, PSTR("text/html"), //没有给定SSID, 无法连接到WiFi.
PSTR("<html><body><meta charset=\"utf-8\">配置失败,缺少信息</body></html>"));
}
/*----------------- NTP code ------------------*/
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] = {
"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();
}
return _now;
}
/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -0,0 +1,85 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file 3_build_without_wifi.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-11-06
* @brief ReadGuy最基础的HelloWorld显示, 但不编译WiFi库.
*
* @note 打开方式:
* 1. 深入库文件夹, 找到guy_driver_config.h文件
* 2. 找到宏定义 READGUY_ENABLE_WIFI
* 3. 注释掉这一行, 就像这样
* ```C++
* /// 启用WIFI配网功能.必须先启用 #define DYNAMIC_PIN_SETTINGS. 此选项对 ESP32xx 会减少大量flash.
* //#define READGUY_ENABLE_WIFI
* ```
* 4. 回到这里编译运行.
* 5. 其实那里的很多功能都能被禁用掉, 直接注释对应的功能定义即可
* 但我懒得测试哪些注释掉之后会导致编译不过去, 或是, 鬼找上门.
* 6. 运行完本示例之后想要跑其他示例, 记得注释回去, 免得有些功能用不了
*
* @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 基础驱动库
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
void setup(){
// --------------------- 1 - 初始化和启动ReadGuy -------<
Serial.begin(115200); //初始化串口
//注意: 首次烧录此程序时, 这一步需要你打开手机联esp8266/32的 WiFi, 用于配网.
//名称是 readguy 密码 12345678. 连接后访问 192.168.4.1 再在网页中完成后续步骤
//后续启动就可以不用这个配置了
guy.init(); //初始化readguy_driver 基础驱动库.
//首次初始化完成之后, 以后再初始化就不需要配网了, 除非你抹除了芯片的flash
guy.setFont(&FreeMonoBold9pt7b); //设置显示的字体
guy.setTextColor(0,1); //设置显示的颜色. 0代表黑色, 1代表白色
guy.drawString("Hello Readguy!",10,10); //用此函数将字符串显示到屏幕缓存内
guy.drawString("Without WiFi!",10,30); //用此函数将字符串显示到屏幕缓存内
//guy.setCursor(10,10); //设置显示的坐标
//guy.print("Hello Readguy!"); //使用这个函数也能显示出字符串, 但是需要提前使用setCursor确定显示坐标
guy.display(READGUY_FAST); // 快速刷新. 将屏幕缓存内的内容显示到墨水屏幕上
//guy.display(READGUY_SLOW); // 慢速刷新.
//想知道更多内容, 欢迎移步到其他示例.
}
void loop(){
//什么也不做, 毕竟刷新墨水屏要消耗墨水屏的阳寿.
//盖姐说它们也是有阳寿的. 刷多了会老化.
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -4,7 +4,7 @@
* QQ群: 926824162 ()
* : .
*
* @file ex03_1_u8g2font.ino
* @file 1_u8g2font.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date 2023-09-19
@@ -53,7 +53,7 @@
#include "ctg_u8g2_wqy12.h" //中文字体库
readguy_driver guy;//新建一个readguy对象, 用于显示驱动.
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
extern const uint8_t ctg_wqy9pt_chinese1[]; //声明中文字体文件

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
/******************** 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[626234] ;
#ifdef __cplusplus
}
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View File

@@ -0,0 +1,411 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
*
* @file ex06_Image.ino
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @version 1.0
* @date create: 2023-11-01
* last modify: 2023-11-06
* @brief ReadGuy 图片显示功能演示.
*
* - 演示如何将比较大的图片通过多种方法显示到屏幕上.
* - 运行的会很缓慢, 因为示例的图片文件比较大.
* 1. 在运行过ex01或者ex02的开发板上 编译烧录本程序.
* 2. 将该项目data文件夹内的所有文件放置于SD卡的根目录上.
* 就是SD卡内放data文件夹内的所有文件, 不能额外套文件夹.
* 如果你的SD卡插入电脑上显示为可移动磁盘, 那么双击SD卡目录就要看到这个文件夹里的文件
*
* 将根目录下的data文件夹 上传到LittleFS之后运行效果更佳
* 或者可以准备一张SD卡,并准备在卡的根目录下放置data文件夹内的文件.
* 就是SD卡内放data文件夹内的所有文件, 不能额外套文件夹.
* 如果你的SD卡插入电脑上显示为可移动磁盘, 那么双击SD卡目录就要看到这个文件夹里的文件
*
* 默认的文件系统为SD卡. 当没有插入SD卡时, 会读取LittleFS文件系统.
* 没有条件准备SD卡的, 可以烧录LittleFS文件系统.
*
* 对于ESP8266 Arduino 用户, 在项目草图文件夹内新建一个data文件夹, 并放入文件 (示例已提供data文件夹)
* 再在 arduinoIDE 的工具选项里选择 ESP8266 LittleFS Data Upload.
* 没有这个选项的需要参考以下文档安装ESP8266 Sketch upload tool
* https://randomnerdtutorials.com/install-esp8266-nodemcu-littlefs-arduino/
*
* 对于ESP32 Arduino 用户, 也要在项目草图文件夹内放一个data文件夹, 并把文件放入其中 (示例已提供data文件夹)
* 再在 arduinoIDE 的工具选项里选择 ESP32 Sketch data upload, 最后选择LittleFS.
* 没有这个选项的需要参考以下文档安装ESP32 LittleFS upload tool
* https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
*
* 对于PlatformIO 用户, 需要进入platformIO扩展界面, 选择Upload Filesystem Image, 上传项目文件.
* ESP8266和ESP32都要用这种方法.
*
* {0} 代码食用注意事项:
* 这一部分的代码很难读, 或者按维莫斯小姐的说法, 很 "抽象" .
* 如果你要开始一行一行读取下去, 不妨先在这里停顿一下, 看一下一些需要你提前知道的东西.
*
* {1} 画布, 或者叫sprite. 是一块专门存储图像的数据结构.
* 画布需要大量的内存来存放图像的像素, 但是你可以对画布进行任何图像操作, 就像是在墨水屏上能进行的操作一样多.
* 画布类型的数据结构至少需要包含3个要素: 宽度, 高度, 每像素需要的字节数.
* 画布需要的内存为 宽度*高度*每像素需要的字节数. (1bit图像需要8分之一字节)
* 在画布上可以进行几乎任何图像操作, 就像是在墨水屏上能进行的操作一样多.
* 而且你还可以获取像素的颜色 (这和你创建的画布的颜色格式有关).
* 画布可以随时被快速的显示到墨水屏上, 显示完了之后再使用display方法, 你刚才创建的画布就显示出来了.
*
* {2} 屏幕缓存. 你就从来没好奇过, 墨水屏上怎么呈现图像的? 我好奇过.
* 我翻看了里面那些错综复杂又是深奥的C++骚操作又是类继承又是虚函数指针...反正就是抽象而且难懂的的源代码,
* 简而言之, 墨水屏显示的原理就是墨水屏驱动里面也有个画布, 不过这个画布是1bit的(每8像素消耗1字节内存).
* 所有对墨水屏进行的操作其实都是对那个内部的画布进行的操作.
* 在你使用墨水屏的display方法时, 呃, 墨水屏的驱动程序可以读取到这个屏幕缓存的所有字节.
* 并且根据驱动手册的方法将这个画布上的字节发送到屏幕上.
* 发送完成之后再通过一些代码, 墨水屏就会刷屏, 将这个屏幕缓存里的内容显示出来了.
* 维莫斯小姐: 苏滴嘶内~
* 下面的库其实用了一些更加骚气的方法, 直接读取了这块画布, 从而能允许你在ESP8266上也可以...显示不小的图片
*
* {3} 灰度显示. ReadGuy这个库的一大特色就是支持灰度显示.
* 欸, 可是屏幕缓存画布只有1bit啊, 怎么显示灰度啊?
* 不用急, 原理也不难.
* 16级灰度颜色的刷屏分为15个步骤. 因为白色不需要刷.
* 在开始刷屏之前, 务必进行一次慢刷, 确保所有的白色像素都足够白.
* 第一次刷屏, 通常会刷最浅的灰色.
* 此时, 首先将屏幕缓存内所有需要显示为这个颜色的像素全写入为黑色(只是名义上的黑色, 实际上是设定的颜色).
* 再初始化墨水屏的刷屏功能.
* - 此处需要额外设定刷屏的颜色的参数. 此参数设定是可以由驱动程序自动完成的, 不过也可以通过你调用函数完成.
* - 用户只需要用简单的setDepth函数设置颜色即可. 用户设定的颜色深度通常用于显示字符串或者形状等GUI控件.
* - 刷图程序的灰度图片刷新功能会自动控制刷屏颜色, 不需要手动调用.
* 最后刷屏. 因为之前刷图程序已经设定过刷的颜色了, 所以此时刷屏呈现的颜色就是之前设定的颜色.
* 第2-15次刷屏原理类似.
* 每次追加黑色像素, 追加的像素就是来显示灰度的像素. 而原本的黑色像素就是之前那些灰度颜色的像素.
*
* \*(^_^)*/ /** 我相信你应该还不懂. 我就举个例子吧.
* 比如要显示一个渐变色, 从左到右为白色→黑色的渐变图.
* 首先进行一次慢刷, 确保要刷图的地方都是纯洁的白色.
* ≡=-(1)-=≡
* 筛选出最浅的灰色设为灰1, (灰X代表灰度颜色, 灰15代表黑色)
* 再把图片内所有颜色和灰1最接近的颜色写入到屏幕缓存内.
* 此时屏幕缓存内的"黑色"部分都是与灰1最接近的颜色, 其他颜色均为"白色"部分.
* 刷好了之后, 进行一次刷屏. (系统自动调用setDepth(1)来保证接下来图片的黑色部分其实是灰1).
* ≡=-(2)-=≡
* 筛选出比最浅的灰色深一点的颜色设为灰2, 把图片内和灰2最接近的颜色写入屏幕缓存, 但保持原本代表灰1的"黑色"不变.
* (也就是说, 把灰2的像素写入之后, 屏幕缓存的黑色像素只多不少. 写入过程中, 只能是白→黑, 不存在黑→白)
* 此时屏幕缓存内的"黑色"部分包含了与灰2最接近的颜色, 和刚才刷完的与灰1最接近的颜色. 其他颜色均为"白色"部分.
* 刷好了之后, 进行一次刷屏. (系统自动调用setDepth(2)来保证接下来图片的黑色部分其实是灰2)
* 注意屏幕显示, 原本的灰1并没有颜色变化, 变化的都是本次追加的"灰2"代表的"黑色".
* 追加的像素会显示为新设定的颜色.
* ≡=-(3)-=≡
* 对于灰3, 和灰2一样, 追加显示到屏幕缓存内.
* (也就是说, 屏幕缓存的黑色像素还是只多不少. 写入过程中, 只能是白→黑, 不存在黑→白)
* 此时屏幕缓存内的"黑色"部分包含了与灰3, 灰2和灰1最接近的颜色. 其他颜色均为"白色"部分.
* 设置颜色深度, 刷屏.
* 对于灰4~灰15, 都这么做
* ≡=-(5)-=≡
* 刷完灰15之后, 灰度图就刷完了.
*
* {4} 自定义刷屏函数. 到底是什么决定了刷屏时的每一像素是什么颜色? 是黑是白?
* 其实刷屏的本质就是通过调用一个外部函数.
* 该函数可以根据输入的像素坐标位置来决定输出的像素颜色.
* 为了简化程序调用过程并提高调用速度, 此处的像素坐标位置参数为一个整数(而不是两个)
* 至于该怎么调用这个函数, 并不是你需要了解的事情.
*
* @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
*
* 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 "guy_image.h" //包含 readguy_image 图片功能库. 此库文件放在项目文件夹内.
constexpr const char * BMP_FILE = "/bmp.bmp"; //在此更改你的图片文件路径名
constexpr const char * JPG_FILE = "/jpg.jpg"; //在此更改你的图片文件路径名
constexpr const char * PNG_FILE = "/png.png"; //在此更改你的图片文件路径名
ReadguyDriver guy;//新建一个readguy对象, 用于显示驱动.
void setup(){
// 1 - 初始化和启动ReadGuy --------------------- 1 - 初始化和启动ReadGuy -------<
Serial.begin(115200); //初始化串口
guy.init(); //初始化readguy_driver 基础驱动库.
guy.setRotation(3); //设置旋转方向. 因为此库的功能不随旋转而发生变化.
// 2 - 显示图片 方式1 --------------------- 2 - 显示图片 方式1:使用readguy提供的drawImage方法. ------<<
//此功能可以在 画布{1}(Sprite) 绘制图片, 再使用内置的drawImage方法来显示出这个sprite.
//此方法消耗的内存颇多, 需要为整个sprite分配所有内存才能成功绘制.
//由于目前版本LovyanGFX的限制, 不能分配4bit的画布, 只能分配大小至少为8bit的画布.
guy.fillScreen(1); //清屏
//guy.display(); //显示白屏,用于将来显示图片.
LGFX_Sprite sp(&guy); //创建一个 画布(Sprite) (可以存储一些像素, 快速读写)
sp.setColorDepth(lgfx::v1::color_depth_t::grayscale_8bit); //设置画布sprite的色彩深度
sp.createSprite(80,80); //为刚刚创建的画布对象分配内存 (像素宽度*像素高度). 此处为80(宽)*80(高)像素
sp.drawBmpFile(guy.guyFS(),BMP_FILE,0,0); //从文件创建BMP图像信息. 文件系统内必须要有这个BMP图像.
//默认的文件系统为SD卡. 当没有插入SD卡时, 会读取LittleFS文件系统.
//没有条件准备文件系统的, 可以烧录文件系统.
Serial.printf("[%lu] sp.w: %d, h: %d, res: %im.\n",millis(),sp.width(),sp.height(),ESP.getFreeHeap());
guy.drawImage(sp,9,11); //使用抖动像素的方式显示图片(不是灰度, 只有黑点和白点的那种显示效果)
//sp为要显示的画布, 后面的9 11都是要显示的目标坐标位置. 此处代表将画布显示在 屏幕缓存{2} 的第9列, 第11行.
Serial.printf("[%lu] drawn dithering bmp.\n",millis()); //显示信息
guy.display(); //1.2.0版本之后, drawImage不再刷屏, 需要额外调用display函数刷屏.
delay(2000);
guy.display(FILL_WHITE,READGUY_FAST); //在保持屏幕缓存不变的时候快速刷新白屏.
guy.drawImage(sp,0,0,guy.width(),guy.height()); //绘画的画布可以被放大或者缩小到任意宽度和高度.
//此处的参数调用表示将会在屏幕坐标(0,0)开始显示, 显示的画布宽度缩放到屏幕宽度, 画布高度缩放到屏幕高度.
guy.display(); //调用display函数刷屏.
delay(2000);
guy.display(FILL_WHITE,READGUY_FAST); //在保持屏幕缓存不变的时候快速刷新白屏
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.drawImage(sp,10,10,65,50); //缩放: 缩小到65X50
guy.display(); //调用display函数刷屏.
delay(2000);
//以下函数可以允许你用灰度显示画布.
//在刷灰度之前, 该函数可以对刷屏区域自动慢刷白屏.
//0-关闭连续刷屏 开启16阶灰度抖动. 连续刷屏并非所有的屏幕都支持. 所以此处使用了一个函数检验.
if(guy.supportGreyscaling()==-16){ //此函数返回-16代表支持连续刷屏. 返回16代表支持灰度, 返回0代表仅黑白.
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(0); //设置刷屏方式:0-关闭连续刷屏, 开启16阶灰度抖动.
guy.draw16grey(sp,9,9); //使用16级灰度的方式显示图片 需要的时间比较长
delay(2000);
}
//1-开启连续刷屏 开启16阶灰度抖动 (默认值)
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(1); //设置刷屏方式:1-开启连续刷屏, 开启16阶灰度抖动. 在初始化时, 这是默认值.
guy.draw16grey(sp,12,12,120,120); //使用16级灰度的方式显示图片. 后两个参数120代表缩放之后的宽度高度
delay(2000);
//2-关闭连续刷屏 关闭16阶灰度抖动. 连续刷屏并非所有的屏幕都支持. 所以此处使用了一个函数检验.
if(guy.supportGreyscaling()==-16){ //此函数返回-16代表支持连续刷屏. 返回16代表支持灰度, 返回0代表仅黑白.
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(2); //2-关闭连续刷屏, 关闭16阶灰度抖动
guy.draw16grey(sp,9,9,50,100); //使用16级灰度的方式显示图片. 参数50和100分别代表宽度缩放和高度缩放
delay(2000);
}
//3-开启连续刷屏 关闭16阶灰度抖动
guy.fillScreen(1); //白屏清屏(清屏幕缓存)
guy.setGreyQuality(3); //3-打开连续刷屏, 关闭16阶灰度抖动
guy.draw16grey(sp,3,3,100,50); //使用16级灰度的方式显示图片 参数100和50分别代表宽度缩放和高度缩放
delay(2000);
//灰度显示画布到此显示完了
sp.deleteSprite(); //关闭画布, 释放图片占用的大量内存
Serial.printf("[%lu] drawn 16-layer greyscale bmp.\n",millis()); //显示信息
delay(2000);
// 3 - 显示图片 方式2 --------------------- 3 - 显示图片 方式2:使用readguyImage类. -----<<<
//readguy驱动程序的内部有一个屏幕缓存, 可以用于存储显示引擎显示的内容, 在刷屏时 直接发送此缓存的内容.
//显示引擎会通过显示函数, 来改写屏幕缓存的内容. (显示就是这样显示的. 类似于print, drawPixel, fillRect...)
//此绘制方式可以做到几乎不消耗额外的内存, 仅需要屏幕缓存即可显示图片.
//适合用在esp8266或者消耗的内存较多的项目上.
//显示原理比较复杂, 因为显示函数的限制, 图片不能直接显示到屏幕缓存中.
//大体上说就是用屏幕缓存作为显示图片的中间内存, 再分阶段解码图片, 最后通过自定义刷屏函数, 完成刷新图片
//使用此方法, 因为屏幕缓存被复用作图片解码中间内存, 屏幕缓存上原本的内容会因为图片解码而发生改变.
//不需要指定额外的外部内存.
//显示BMP格式
readguyImage im(guy); //定义一个绘制器, 此类中的函数用于绘制图片.
//所有的绘制图片的参数均需要放入此结构内
//直接更改im内的数据即可设置绘制参数. (就像结构体一样用它)
im.baseFs=&guy.guyFS(); //在此处就是设置文件系统.
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
guy.display(FILL_WHITE,READGUY_SLOW); //将屏幕全刷成白屏. 为了即将的图片刷新.
//建议在使用drawImageFile函数之前, 使用慢刷刷白屏, 可以保证显示效果清晰可见.
im.drawImageFile(); //显示BMP格式.图片. im会自动识别文件扩展名并绘制.
//此处不再需要调用 guy.display() 函数即可显示.
//此时显示缓存已经清空(因为显示缓存内原本是用来解码图片用的)
//此时直接使用guy.display()将刷白屏.
delay(2000);
//显示JPG格式
im.filename=JPG_FILE; //在此直接设置文件路径和文件名.
im.scalex=0.3333333f; //设置横坐标图片缩放
im.scaley=0.3333333f; //设置纵坐标图片缩放.
im.offsetx=70; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
im.offsety=40; //设置显示偏移Y轴方向.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
guy.display(FILL_WHITE,READGUY_SLOW); //将屏幕全刷成白屏. 为了即将的图片刷新.
im.drawImageFile(); //显示JPG格式.图片. im会自动识别文件扩展名并绘制.
delay(2000);
#ifndef ESP8266 //显示PNG格式. ESP8266不支持PNG格式.
im.filename=PNG_FILE; //PNG图片. ESP8266的内存不够, 所以不能显示PNG格式~(T_T)~
im.x=10; //设置在哪里绘制图片. 在此编辑图片绘制起始点的 X, Y 坐标
im.y=5 ; //
im.scalex=400.0f/1280.0f;
im.scaley=300.0f/576.0f;
guy.display(FILL_WHITE, READGUY_SLOW);//显示. 此处的功能就是将显示缓存输出到屏幕上
im.drawImageFile(); //显示PNG格式.图片. ESP8266可能不会绘制.
delay(2000);
#endif
//开启灰度显示, 然后显示BMP格式
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
im.scalex=0.75f; //设置横坐标图片缩放
im.scaley=0.5625f; //设置纵坐标图片缩放.
im.offsetx=0; //设置显示偏移. 此处的70说明 JPG图像应该从图片文件的(70,40)坐标处开始解码
im.offsety=0; //设置显示偏移Y轴方向.
im.background=1; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
//预分配内存, 可以用作显示缓存.
#ifdef ESP8266
#define MEM_POOL 10000
#else
#define MEM_POOL 60000
#endif
uint8_t *buf = new uint8_t [MEM_POOL]; //分配缓存
im.exPool=buf; //设置外部缓存内存地址
im.exPoolSize=MEM_POOL; //设置外部缓存内存大小
guy.setGreyQuality(1); //设置灰度模式为默认灰度显示模式
guy.display(FILL_WHITE,READGUY_SLOW); //将屏幕全刷成白屏. 为了即将显示灰度图.
im.enableFloyd=0; //禁用掉抖动算法.
im.drawImageFile(1); //显示JPG格式.图片.
delay(2000);
guy.display(FILL_WHITE,READGUY_SLOW); //将屏幕全刷成白屏. 为了即将显示灰度图.
im.enableFloyd=1; // 重新启用抖动算法.
im.drawImageFile(1); //显示JPG格式.图片.
delay(5000);
delete [] buf; //缓存用完了, 释放内存.
im.exPool=nullptr; //用完之后将这个内存地址置为null, 避免重复使用导致内存不可访问.
im.exPoolSize=0;
// --------------------- 4 - 显示图片 使用readguyImage类的externalBuffer绘制到主屏幕缓存. -----<<<
//实现原理: 通过一个额外的缓存, 显示引擎可以分多次将图片显示在这个额外的内存中, 再将这块内存写入到缓存内.
//由于用了外部内存, 所以可以分多次显示一张大图片, 相比于分配整张大图片的内存, 要更节省内存.代价是速度更慢
//readguy驱动程序的内部有一个屏幕缓存, 如果使用这个缓存来显示图片, 屏幕上原本的东西也会变得混乱.(上个示例)
//因此, readguyImage类提供了使用外部内存的显示功能.
//分为多次, 每次将图片的一小部分显示到外部内存中, 再写入到屏幕缓存内.
//需要提供额外分配好的内存, 但此函数不会更改显示区域之外的显示缓存.
guy.fillScreen(1); //将屏幕全刷成白屏. 为了即将的图片刷新.
for(int i=1;i<10;i++){ //在屏幕上绘画线段. 本质是更改显示缓存.
guy.drawLine(i*guy.width()/10,0,0,i*guy.height()/10,0);
guy.drawLine(i*guy.width()/10,guy.height()-1,guy.width()-1,i*guy.height()/10,0);
}
guy.drawLine(guy.width(),0,0,guy.height(),0);
guy.display(READGUY_SLOW); //刷新屏幕, 显示绘画的线段
//im.baseFs=&guy.guyFS(); //直接更改im内的数据即可设置绘制参数. 在此处就是设置文件系统.
im.filename=BMP_FILE; //在此直接设置文件路径和文件名.
im.background=0; //设置背景颜色, 0黑1白, 此处设为背景色为黑色.
im.x=15; //设置将要显示的横坐标
im.y=15; //设置将要显示的纵坐标
im.w=180; //设置将要显示区域的宽度
im.h=270; //设置将要显示区域的高度
im.offsetx=0; //如果不从左上角开始解码图片, 从哪个坐标开始显示/解码图片?
im.offsety=0;
im.scalex=0.5625f; //设置横坐标缩放.
im.scaley=0.5625f; //设置纵坐标缩放. 为0代表与横坐标缩放相同.
buf = new uint8_t [ 60*270 ]; //分配缓存
im.exPool=buf; //设置外部缓存内存地址
im.exPoolSize=60*270; //设置外部缓存内存大小
im.drawImageToBuffer(); //将图片写入到屏幕缓存. im会自动识别文件扩展名并绘制.
guy.display(); //显示缓存.此时应当能看到图片.
delete [] buf; //缓存用完了, 释放内存.
im.exPool=nullptr; //用完之后将这个内存地址置为null, 避免重复使用导致内存不可访问.
im.exPoolSize=0;
}
void loop(){
delay(1);
}/* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -0,0 +1,332 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
* @file guy_image.cpp
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief guy_image 基础功能 源代码文件.
* @version 1.0
* @date 2023-11-01
* @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 "guy_image.h"
#include "readguy.h"
#define GUY_STAGES ((h+_h-1)/_h)
/// @brief 此函数为自定义图片显示程序
uint8_t readguyImage::drawImgHandler(int r, LGFX_Sprite *spr){
int widthDiv8=spr->width()>>3; //宽度的1/8. 注意此处宽度一定是8的倍数.
int stage = widthDiv8*_h;
//uint32_t colors[8]={0x101010,0x303030,0x505050,0x707070,0x909090,0xb0b0b0,0xd0d0d0,0xf0f0f0};//for debug
if(r%stage==0){ //分为8个阶段绘制图像, 每个阶段开始时, 打开图片文件尝试绘制.
guy->implEndTransfer();
Serial.printf("filename: %s(%d) Stage %d: Pixels: w=%d, h=%d\n",filename,format,r/stage,spr->width(),_h);
spr->fillScreen(background?0xffff:0); //背景色填充白色
//spr->drawBmp(*baseFs,filename,x,y,std::min(w,spr->width()),std::min(h,_h),
//offsetx,offsety,scalex,scaley);
int _x=0,_y=0,xd=0,yd=0;
uint8_t rot = guy->getRotation();
//uint8_t td=lgfx::v1::TL_DATUM; //top left for default
switch(rot){
case 0:
_x=x; xd=0;
_y=y-r/stage*_h;
if(_y<0){ yd=-_y; _y=0; }
break;
case 1:
//旋转之后, sprite为竖条. spr的width为_h. height为widthDiv8<<3(原sprite宽度)
//输入的x位置: 转化为_y位置: (widthDiv8<<3)-x-1
//输入的y位置: 转化为_x位置: _x=y. 限制: 分8块 分块: _x=y-r/stage*_h;
//_x=y-r/stage*_h; yd=0;
//_y=x;//(widthDiv8<<3)-x-1;
_x=x-r/stage*_h;
_y=y; yd=0;
if(_x<0){ xd=-_x; _x=0; }
break;
case 2:
_x=x; xd=0;
_y=y-(GUY_STAGES-r/stage-1)*_h;
if(_y<0){ yd=-_y; _y=0; }
break;
case 3:
_x=x-(GUY_STAGES-r/stage-1)*_h;
_y=y; yd=0;
if(_x<0){ xd=-_x; _x=0; }
break;
}
spr->setRotation(rot);
switch(format&3){
case 1:
spr->drawBmpFile(*baseFs,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);
break;
#endif
case 3:
spr->drawJpgFile(*baseFs,filename,_x,_y,0,0,offsetx+xd,offsety+yd,scalex,scaley,datum);
break;
}
spr->setRotation(0);
//spr->setTextColor(0u,colors[r/stage]);//for debug
//spr->drawString("Hello",0,0);//for debug
/*此函数将会把图片文件绘制到这个灰度图里面.
for(int j=0;j<spr->height();j++){
for(int i=0;i<spr->width();i++){
Serial.print((spr->readPixelRGB(i,j).R8())&0xff,HEX);
Serial.write(' ');
}
Serial.print("\r\n\r\n");
}*/
delay(1);
guy->implBeginTransfer();
}
// *
if(r%widthDiv8==0){ //开始显示新的一行像素, 读取到buff内
guy->implEndTransfer();
int w=spr->width();
//Serial.printf("line: %d %d, readStart: %d, readEnd: %d\n",r/w,r/widthDiv8,((r%stage)<<3), ((r%stage)<<3)+w);
if(r==0){
memset(floyd,0,w<<1);
//Serial.printf("floyd buff = %d:\n",w);
//for(int j=0;j<(w<<1);j++){
// Serial.printf("%d ",floyd[j]);
//}
//Serial.println();
}
memcpy_P(readBuff,((uint8_t*)spr->getBuffer())+((r%stage)<<3),w);
uint_fast8_t buff8bit=0;
for(int32_t j=0;j<w;j++){
int32_t flodelta = floyd[((r/widthDiv8)&1)*w+j]+(int32_t)((readBuff[j]<<8)|readBuff[j]);
if(format>>2){ //是灰度模式
uint_fast8_t cg=0;
if(enableFloyd){
while(flodelta>=0x800) {
cg++;
if(flodelta>=0) flodelta -= 0x1000;
}
if(flodelta<0) flodelta++;
}
else{ cg=readBuff[j]>>4; }
if(format>>6){
if(cg<15) //白色不考虑
buff8bit |= (cg>=((~(format>>2))&0xf))<<((~j)&7);
}
else{
buff8bit |= (cg<(format>>2))<<((~j)&7);
}
}
else if(flodelta>=0x8000) {
//spr.drawPixel(j,i,1);
buff8bit |= 1<<((~j)&7);
flodelta -= 0xffff;
}
if((j&7)==7 || j==(w-1)){
writeBuff[j>>3]=buff8bit^((format>>2)?0xff:0);
buff8bit=0;
}
//计算出读取到的颜色, 然后与128比较, 如果小于128, 显示黑色,否则显示白色
//else { spr.drawPixel(j,i,0); }
//if(j!=(int32_t)spr.width()-1) {
// floyd[ i&1 ][j+1] += (flodelta*7)>>4;
// floyd[!(i&1)][j+1] += (flodelta )>>4;
//}
//if(j) { floyd[!(i&1)][j-1] += (flodelta*3)>>4; }
// { floyd[!(i&1)][j ] += (flodelta*5)>>4; }
if(j!=w-1) { floyd[ ((r/widthDiv8)&1) *w+j+1] += (flodelta*7)>>4; }
if(j) { floyd[(!((r/widthDiv8)&1))*w+j-1] += (flodelta*3)>>4; }
{ floyd[(!((r/widthDiv8)&1))*w+j ] += (flodelta*5)>>4; }
if(j!=w-1) { floyd[(!((r/widthDiv8)&1))*w+j+1] += (flodelta )>>4; }
}
for(int floi=0;floi<w;floi++) floyd[((r/widthDiv8)&1)*w+floi]=0;
guy->implBeginTransfer();
}
return writeBuff[r%widthDiv8];
/*
//根据r的值返回对应位置上的的颜色. 一次读取8个像素
uint_fast8_t colorBase = 0; //即将发送出去的像素数据
//根据r的值返回对应位置上的的颜色. 一次读取8个像素
for(uint_fast8_t j=0;j<8;j++){
uint_fast8_t grey = *(((uint8_t *)spr->getBuffer())+((r%stage)<<3)+j); //获取对应位置的像素点的灰度值
//计算灰度数值, 并使用floyd算法转化为位图.
//if(readguyEpdBase::greysc(grey)>=128) colorBase|=(1<<(7-j));
if(grey>=128) colorBase|=(1<<(7-j));
}
return colorBase;
*/
}
/// @brief 获取文件的扩展名, 最大长度为 exname_len
uint8_t readguyImage::getExName(const char* fname, char* exname, size_t exname_len){
const char * dataex[12] = {
"txt","bmp","jpg","png","mp3","wav","aac","flac","json","bin","html","js"
};
char dbuff[6];
uint8_t foundLastName = 0;
int fnamelen = strlen(fname);
//Serial.print("test file name hook: ");
//Serial.println(fname);
if(fname[fnamelen-1] == '/') {
//fname[fnamelen-1] = '\0'; //删除文件夹标记辅助位
exname[0] = '/';
exname[1] = '\0';
return 2; //文件夹类型
}
for(int i=0;i<5;i++){
if(fnamelen-1-i>=0) dbuff[i] = fname[fnamelen-1-i];
else dbuff[i] = 0;
if(dbuff[i] == '.'){
dbuff[i] = 0;
foundLastName = strlen(dbuff); //标记找到了扩展名
break;
}
}
if(foundLastName>exname_len) foundLastName = exname_len;
for(int i=0;i<foundLastName;i++){
exname[i] = dbuff[foundLastName-1-i];
}
exname[foundLastName] = 0;
//Serial.print("test exname hook: ");
//Serial.println(exname);
if(exname[0] == 0) return 0; //无类型
for(int i=0;i<12;i++){
if(strcmp(exname,dataex[i]) == 0) return i+3;
}
return 1; //未知类型
}
/// @brief 显示图像
void readguyImage::drawImageFile(bool use16grey){
if(filename == nullptr || filename[0] == 0 || !(baseFs->exists(filename))) return; //文件不存在
char ex[8]; //保存文件的扩展名
getExName(filename,ex,7); //获取文件的扩展名.最后一个参数用于防止数组越界
format = 0; //16灰度模式
//Serial.printf("filename: %s, exname: %s\n",filename,ex);
//图片将会分割成8个部分, 分块绘制, 节省内存.
w=(guy->drvWidth()+7)&0x7ffffff8; //guy->guyMemoryWidth() 返回不随旋转参数而改变的显示内存宽度
if(!w) return; //保证宽度>0
h=guy->drvHeight();
if(exPoolSize>guy->bufferLength()){ //当外部缓存的像素超过屏幕缓存时,使用外部缓存作为主缓冲区
_h=exPoolSize/w;
_pool=exPool;
}
if(_pool==nullptr) {
_h=(h+7)>>3; //设置缓存区的高度. 更多内存将可以更快显示
_pool=(uint8_t *)guy->getBuffer();
}
//(guy->guyMemoryHeight()+7)>>3 返回高度,并补齐后右移三位 (等效于除以2³, 分成8份)
//最后一个参数代表bpp,也就是有多少位, 在这里需要使用8位灰度.
LGFX_Sprite bmpspr;
//首先, 需要获取到内部显存的地址, 用于建立图片分块绘制缓存.
//获取屏幕缓存, 随后分配图片解码所需的内存.
bmpspr.setBuffer(_pool,w,_h,lgfx::v1::color_depth_t::grayscale_8bit);
//bmpspr.createSprite(guy_width,(guy_height+7)&0x7ffffff8);
//必须在此处转化为8bit灰度 (256等阶)
//bmpspr.setColorDepth(lgfx::v1::grayscale_8bit); //因为LGFX的限制, 图片经过解码之后不能直接转换为单色/16色
//随后打开图片进行解码. 可选显示的位置和宽度高度参数, 屏幕上的其他部分则会变成白色.
if(strcmp(ex,"bmp") == 0 || strcmp(ex,"BMP") == 0) //BMP格式, 绘制BMP图片
format|=1; //BMP格式
#ifndef ESP8266
else if(strcmp(ex,"png") == 0 || strcmp(ex,"PNG") == 0) //PNG格式, 绘制PNG图片
format|=2; //PNG格式
#endif
else if(strcmp(ex,"jpg") == 0 || strcmp(ex,"JPG") == 0 || strcmp(ex,"jpeg") == 0 || strcmp(ex,"JPEG") == 0)
format|=3; //JPG格式
else return; //未知格式
//bmpspr.drawBmp(*baseFs,filename,x,y,w,h,offsetx,offsety,scalex,scaley);
floyd = new int16_t[w<<1];
readBuff = new uint8_t[w];
writeBuff = new uint8_t[w>>3];
//guy->display([](int)->uint8_t{ return 0xff; },true);
if(use16grey){
for(int i=1;i<16;i++){
format |= (i<<2);
if(guy->supportGreyscaling()==16) format|=64;
guy->draw16greyStep(std::bind(&readguyImage::drawImgHandler,this,std::placeholders::_1,&bmpspr),i);
format &= 0x03;
}
}
else{
// ************* 提示: 编写此示例时的最新版本LovyanGFX库不提供此函数. 请看ex06_Image.ino文件开头的解决方法!
guy->display(std::bind(&readguyImage::drawImgHandler,this,std::placeholders::_1,&bmpspr));
// 此函数过不了编译 需要改库.
}
delete []floyd;
delete []readBuff;
delete []writeBuff;
guy->fillScreen(0xff); //清空乱码
}
uint8_t readguyImage::drawImageToBuffer(){
if(filename == nullptr || filename[0] == 0 || !(baseFs->exists(filename))) return 1; //文件不存在
if( w==0 || h==0 || w>=0x8000 || h>=0x8000 || exPool==nullptr || exPoolSize<1024) return 3; //内存不足
char ex[8]; //保存文件的扩展名
format = 0; //16灰度模式
getExName(filename,ex,7); //获取文件的扩展名.最后一个参数用于防止数组越界
Serial.printf("filename: %s, exname: %s\n",filename,ex);
if(strcmp(ex,"bmp") == 0 || strcmp(ex,"BMP") == 0) //BMP格式, 绘制BMP图片
format|=1; //BMP格式
#ifndef ESP8266
else if(strcmp(ex,"png") == 0 || strcmp(ex,"PNG") == 0) //PNG格式, 绘制PNG图片
format|=2; //PNG格式
#endif
else if(strcmp(ex,"jpg") == 0 || strcmp(ex,"JPG") == 0 || strcmp(ex,"jpeg") == 0 || strcmp(ex,"JPEG") == 0)
format|=3; //JPG格式
else return 2; //未知格式
_h=exPoolSize/w;
if(_h>h) _h=h; //分配的内存更多了, 直接使用
_pool=exPool;
if(_h==0 || GUY_STAGES>8) return 3; //内存不足以在显示8次之内就... 总之就是内存不够
LGFX_Sprite spr;
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);
break;
#ifndef ESP8266
case 2:
spr.drawPngFile(*baseFs,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);
break;
}
guy->drawImageStage(spr,x,y+_h*i,i,GUY_STAGES);
}
return 0;
} /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -0,0 +1,101 @@
/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
* @file guy_image.h
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief guy_image 基础功能 头文件.
* @version 1.0
* @date 2023-11-01
* @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 _GUY_IMAGE_H_FILE
#define _GUY_IMAGE_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){}
/** @brief 显示图像. use16grey: 为true则使用16灰度,false则不用
* @note 需要提前设置绘制参数, 直接在类的外部 设置此类的成员函数即可.
* 对于不会C++的朋友们, 可以看示例.
* @param baseFs, filename 文件系统和文件名. 必须指定.
* @param x, y 显示在屏幕的什么地方
*
* @param use16grey 0: 使用单色抖动 1:使用16灰度 2:使用16灰度但不抖动
* */
void drawImageFile(bool use16grey = 0);
/** @brief 将图像绘制到系统缓存内. 必须预分配内存, 并且需要知道分配的大小.
* @note 需要提前设置绘制参数, 直接在类的外部 设置此类的成员函数即可.
* 对于不会C++的朋友们, 可以看示例.
* @param baseFs, filename 文件系统和文件名. 必须指定.
* @param x, y 显示在屏幕缓存的什么地方
* @param w, h 开辟的缓存宽度和高度. 最好是和图片的大小相匹配.
* @param exPool 外部内存位置, 必须指定. 此处的pool为1字节=1像素(8bit灰度)
* @param exPoolSize 外部内存大小, 必须指定. 太小会无法显示.
* @return 0:正常显示, 1:文件打不开 2:文件格式不支持 3:内存不足 4:其他问题 */
uint8_t drawImageToBuffer();
/// @brief 获取文件的扩展名.
static uint8_t getExName(const char* fname, char* exname, size_t exname_len);
fs::FS *baseFs = nullptr;/// // / //要绘制的图片所属的文件系统
const char *filename = nullptr; // / //要绘制的图片的文件名和文件路径
int32_t x = 0; //// // / //绘制位置坐标X
int32_t y = 0; //// // / //绘制位置坐标Y
int32_t w = 0x7fffffff; // / //绘制图片的最大宽度, 无需更改
int32_t h = 0x7fffffff; // / //绘制图片的最大高度, 无需更改
int32_t offsetx = 0; // / //从图片的哪个坐标点开始绘制图片,一般无需更改
int32_t offsety = 0; // / //从图片的哪个坐标点开始绘制图片, 一般无需更改
float scalex = 1.0f; // / //横坐标缩放水平. >1为放大, <1为缩小
float scaley = 0.0f; // / //纵坐标缩放水平. 0代表与scalex相同.
lgfx::v1::textdatum::textdatum_t datum=lgfx::v1::textdatum::top_left;
uint8_t background = 1; // //背景颜色. 0黑 1白, 默认白色.
uint8_t enableFloyd = 1; //是否开启floyd抖动算法. (抗锯齿) 默认开启
uint8_t *exPool = nullptr;//额外的内存池, 外部缓存
uint32_t exPoolSize = 0; //额外的内存池 外部缓存的字节数
private: // //私有部分. 以下函数和成员只能是类内成员能调用
ReadguyDriver *guy;// //readguy类型指针. 用于沟通硬件.
/// @brief 设置绘制函数. 此函数不建议单独调用.
uint8_t drawImgHandler(int r, LGFX_Sprite *spr);
uint8_t format = 0; //图片格式
int32_t _h = 0; //内存池的像素高度 可能是内部内存池也可能是外部的
//分多次完成显示, 次数为 h/_h.
uint8_t *_pool = nullptr; //内存池, 可能是内部内存池也可能是外部的
int16_t *floyd = nullptr; //用于显示图片
uint8_t *readBuff = nullptr;
uint8_t *writeBuff = nullptr;
};
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -7,20 +7,21 @@
</head>
<body>
<h1>readguy 欢迎页面</h1>
<p>您已完成了初始化工作.现在可以配置WiFi和聚合密钥相关内容.<br />
</p>返回<a href="/pinsetup">设置页面</a><br />前往<a href="/edit">SD卡文件管理器页面</a>
<br />SD card size: 2967040KB, used size: 28224KB.<br />
当前WiFi模式: AP配网模式, IP地址: (IP unset)<br />
芯片型号: ESP-12E or 12F, 芯片闪存ID: 0x00000000<br />
闪存容量: 4096KB, MAC地址: 48:3F:DA:76:24:46<br />
<b>引脚定义:</b><br />EpdMOSI:-1EpdSCLK:-1Epd_CS:15Epd_DC:5Epd_RST:-1
EpdBusy:0SD_MISO:-1SD_MOSI:-1SD_SCLK:-1SD_CS:0<br />按键定义:btn1:5btn2:12btn3:2<br />
<form action="/wifi" method="POST">WiFi 名称<input type='text' name='ssid' maxlength="31" /><br />WiFi 密码<input
type='text' name='psk' maxlength="31" /><br />天气所在地<input type='text' name='ssid'
maxlength="31" /><br />聚合数据API密钥<input type='text' name='psk' maxlength="63" /><br /></form><br />
<p>ReadGuy for device ESP8266<br />Copyright © FriendshipEnder <a href="https://github.com/fsender/readguy">GitHub</a>
<a href="https://space.bilibili.com/180327370/">Bilibili</a><br />版本: 1.0.0 ,编译日期: Sep 20 202318:17:07</p>
<h1>readguy 欢迎页面</h1>返回<a href="/pinsetup">设置页面</a>
<br />前往<a href="/edit">SD卡文件管理器页面</a><br
/>SD Card Type: SDSC, SD card size: 997888, used size: 997888KB.<br
/>当前WiFi模式: AP配网模式, IP地址: 0.0.0.0<br
/>芯片型号: ESP32-S2 Rev0, 芯片闪存ID: ffffffffffffffff<br
/>闪存容量: 4096KB, MAC地址: 7C:DF:A1:32:BD:78, 当前可用内存: 105324<br
/>ESP-IDF版本: 4.4.5<br /><b>引脚定义:</b><br
/>EpdMOSI:3 EpdSCLK:2 Epd_CS:6 Epd_DC:5 Epd_RST:4 EpdBusy:7 SD_MISO:41<br
/>SD_MOSI:40 SD_SCLK:39 SD_CS:42 I2C_SDA:-1 I2C_SCL:-1 <br
/>按键定义:btn1:19 btn2:20 <br />
<form action="/customName"><input type='submit' value='确定!' /><br /></form><br />
<p>ReadGuy on device ESP32S2<br
/>Copyright © FriendshipEnder <a href="https://github.com/fsender/readguy">GitHub</a>
<a href="https://space.bilibili.com/180327370">Bilibili</a><br
/>版本: 1.0.2 ,编译日期: Sep 27 2023 20:32:42</p>
</body>
</html>

View File

@@ -8,38 +8,166 @@
<body>
<h1>readguy 设置页面</h1>
<p>感谢您使用ReadGuy. 现在将引脚配置输入到框内, 即可成功点亮屏幕.<br />点击<a href="/wifi">配置WiFi</a><br />点击<a href="/api">配置聚合数据API密钥</a><br /></p>
<p>感谢您使用ReadGuy. 现在将引脚配置输入到框内, 即可成功点亮屏幕.<br />点击<a href="/wifi">配置WiFi</a>
<br />点击<a href="/api">配置聚合数据API密钥</a><br />
</p>
<h2>引脚定义设定</h2>
<form name="input" action="/verify" method="POST">
<h2>引脚定义设定</h2><input type="checkbox" name="share" value="1">墨水屏和SD卡共享SPI<br />E-paper 型号<select name="epdtype">
<option value="0" selected>1.54寸标准</option>
<script>
function bt() {
var a = document.getElementById("e").value;
if (a == 1) {
document.getElementById("et").value = 0;
document.getElementById("EpdMOSI").value = 23;
document.getElementById("EpdSCLK").value = 18;
document.getElementById("Epd_CS").value = 19;
document.getElementById("Epd_DC").value = 16;
document.getElementById("Epd_RST").value = 4;
document.getElementById("EpdBusy").value = 13;
document.getElementById("SD_MISO").value = 2;
document.getElementById("SD_MOSI").value = 15;
document.getElementById("SD_SCLK").value = 14;
document.getElementById("SD_CS").value = 26;
document.getElementById("btn_cnt").value = 3;
document.getElementById("btn1").value = 32;
document.getElementById("btn2").value = 33;
document.getElementById("btn3").value = 25;
document.getElementById("bklight").value = -1;
document.getElementById("I2C_SDA").value = 21;
document.getElementById("I2C_SCL").value = 22;
}
if (a == 2) {
document.getElementById("et").value = 11;
document.getElementById("EpdMOSI").value = 23;
document.getElementById("EpdSCLK").value = 18;
document.getElementById("Epd_CS").value = 14;
document.getElementById("Epd_DC").value = 27;
document.getElementById("Epd_RST").value = 33;
document.getElementById("EpdBusy").value = -1;
document.getElementById("SD_MISO").value = 19;
document.getElementById("SD_MOSI").value = 23;
document.getElementById("SD_SCLK").value = 18;
document.getElementById("SD_CS").value = 4;
document.getElementById("btn_cnt").value = 3;
document.getElementById("btn1").value = 39;
document.getElementById("btn2").value = 38;
document.getElementById("btn3").value = 37;
document.getElementById("bklight").value = 32;
document.getElementById("I2C_SDA").value = 21;
document.getElementById("I2C_SCL").value = 22;
}
if (a == 3) {
document.getElementById("et").value = 0;
document.getElementById("EpdMOSI").value = 14;
document.getElementById("EpdSCLK").value = 13;
document.getElementById("Epd_CS").value = 15;
document.getElementById("Epd_DC").value = 27;
document.getElementById("Epd_RST").value = 26;
document.getElementById("EpdBusy").value = 25;
document.getElementById("SD_MISO").value = -1;
document.getElementById("SD_MOSI").value = -1;
document.getElementById("SD_SCLK").value = -1;
document.getElementById("SD_CS").value = -1;
document.getElementById("btn_cnt").value = 1;
document.getElementById("btn1").value = 0;
document.getElementById("btn2").value = -1;
document.getElementById("btn3").value = -1;
document.getElementById("bklight").value = -1;
document.getElementById("I2C_SDA").value = -1;
document.getElementById("I2C_SCL").value = -1;
}
if (a == 4) {
document.getElementById("et").value = 1;
document.getElementById("EpdMOSI").value = 23;
document.getElementById("EpdSCLK").value = 18;
document.getElementById("Epd_CS").value = 5;
document.getElementById("Epd_DC").value = 19;
document.getElementById("Epd_RST").value = 27;
document.getElementById("EpdBusy").value = 38;
document.getElementById("SD_MISO").value = 2;
document.getElementById("SD_MOSI").value = 15;
document.getElementById("SD_SCLK").value = 14;
document.getElementById("SD_CS").value = 13;
document.getElementById("btn_cnt").value = 2;
document.getElementById("btn1").value = 32;
document.getElementById("btn2").value = 36;
document.getElementById("btn3").value = -1;
document.getElementById("bklight").value = 12;
document.getElementById("I2C_SDA").value = 25;
document.getElementById("I2C_SCL").value = 26;
}
if (a == 5) {
document.getElementById("et").value = 5;
document.getElementById("EpdMOSI").value = 23;
document.getElementById("EpdSCLK").value = 18;
document.getElementById("Epd_CS").value = 5;
document.getElementById("Epd_DC").value = 17;
document.getElementById("Epd_RST").value = 16;
document.getElementById("EpdBusy").value = 4;
document.getElementById("SD_MISO").value = 2;
document.getElementById("SD_MOSI").value = 15;
document.getElementById("SD_SCLK").value = 14;
document.getElementById("SD_CS").value = 13;
document.getElementById("btn_cnt").value = 3;
document.getElementById("btn1").value = 37;
document.getElementById("btn2").value = 38;
document.getElementById("btn3").value = 39;
document.getElementById("bklight").value = -1;
document.getElementById("I2C_SDA").value = -1;
document.getElementById("I2C_SCL").value = -1;
}
}
function ct() {
document.getElementById("e").value = 0;
}
</script>
<select id="e" name="epdTemplate" onchange="bt()">
<option value="0" selected>自定义线序</option>
<option value="1">甘草MP3播放器</option>
<option value="2">M5Stack-Core</option>
<option value="3">微雪官方开发板</option>
<option value="4">LilyGo T-Watch墨水屏</option>
<option value="5">LilyGo T5开发板</option>
</select><br /><input type="checkbox" name="share" value="1">墨水屏和SD卡共享SPI<br
/>E-paper 型号<select id="et" onchange="ct()" name="epdtype">
<option value="0">1.54寸标准</option>
<option value="1">1.54寸Lilygo</option>
<option value="2">2.13寸汉朔价签</option>
<option value="3">2.66寸Vusion价签</option>
<option value="4">2.13寸三色</option>
<option value="5">2.9寸A01</option>
<option value="6">2.9寸T94背光</option>
<option value="7">4.2寸WF丝印</option>
<option value="8">4.2寸HINK黑白价签</option>
<option value="9">3.7寸高DPI墨水屏</option>
<option value="10">测试用LCD屏 请勿选此项</option>
<option value="11">2.7寸佳显触摸墨水屏</option>
</select><br />Epd_CS<input type="number" name="Epd_CS" min="-1" max="100" step="1" value="15" /><br />
Epd_DC<input type="number" name="Epd_DC" min="-1" max="100" step="1" value="5" /><br />
Epd_RST<input type="number" name="Epd_RST" min="-1" max="100" step="1" value="-1" /><br />
EpdBusy<input type="number" name="EpdBusy" min="-1" max="100" step="1" value="4" /><br />
SD_CS<input type="number" name="SD_CS" min="-1" max="100" step="1" value="0" /><br />
几个按键?<br /><input type="number" name="btn_cnt" min="1" max="3" step="1" value="3" /><br />
按键 1 引脚<input type="number" name="btn1" min="-1" max="100" step="1" value="5" /><br />
<input type="checkbox" name="btn1c" value="1">高电平触发<br />
按键 2 引脚<input type="number" name="btn2" min="-1" max="100" step="1" value="12" /><br />
<input type="checkbox" name="btn2c" value="1">高电平触发<br />
按键 3 引脚<input type="number" name="btn3" min="-1" max="100" step="1" value="2" /><br />
<input type="checkbox" name="btn3c" value="1">高电平触发<br />
背光 PWM 引脚<input type="number" name="bklight" min="-1" max="100" step="1" value="-1" /><br />
<input type='submit' value='确定!' /><br />
<option value="3">2.13寸三色</option>
<option value="4">2.66寸Vusion价签</option>
<option value="5">2.7寸佳显触摸墨水屏</option>
<option value="6">2.9寸A01</option>
<option value="7">2.9寸T94背光</option>
<option value="8">3.7寸高DPI墨水屏</option>
<option value="9">4.2寸HINK黑白价签</option>
<option value="10">4.2寸WF丝印</option>
<option value="11">测试用LCD屏 请勿选此项</option>
</select><br
/>EpdMOSI<input type="number" id="EpdMOSI" onchange="ct()" name="EpdMOSI" min="-1" max="99" step="1" value="-1" /><br
/>EpdSCLK<input type="number" id="EpdSCLK" onchange="ct()" name="EpdSCLK" min="-1" max="99" step="1" value="-1" /><br
/>Epd_CS<input type="number" id="Epd_CS" onchange="ct()" name="Epd_CS" min="-1" max="99" step="1" value="-1" /><br
/>Epd_DC<input type="number" id="Epd_DC" onchange="ct()" name="Epd_DC" min="-1" max="99" step="1" value="-1" /><br
/>Epd_RST<input type="number" id="Epd_RST" onchange="ct()" name="Epd_RST" min="-1" max="99" step="1" value="-1" /><br
/>EpdBusy<input type="number" id="EpdBusy" onchange="ct()" name="EpdBusy" min="-1" max="99" step="1" value="-1" /><br
/>SD_MISO<input type="number" id="SD_MISO" onchange="ct()" name="SD_MISO" min="-1" max="99" step="1" value="-1" /><br
/>SD_MOSI<input type="number" id="SD_MOSI" onchange="ct()" name="SD_MOSI" min="-1" max="99" step="1" value="-1" /><br
/>SD_SCLK<input type="number" id="SD_SCLK" onchange="ct()" name="SD_SCLK" min="-1" max="99" step="1" value="-1" /><br
/>SD_CS<input type="number" id="SD_CS" onchange="ct()" name="SD_CS" min="-1" max="99" step="1" value="-1" /><br
/>I2C_SDA<input type="number" id="I2C_SDA" onchange="ct()" name="I2C_SDA" min="-1" max="99" step="1" value="-1" /><br
/>I2C_SCL<input type="number" id="I2C_SCL" onchange="ct()" name="I2C_SCL" min="-1" max="99" step="1" value="-1" /><br
/>几个按键?<input type="number" id="btn_cnt" onchange="ct()" name="btn_cnt" min="1" max="3" step="1" value="1" /><br
/>按键 1 引脚<input type="number" id="btn1" onchange="ct()" name="btn1" min="-1" max="99" step="1" value="-1" />
<input type="checkbox" onchange="ct()" name="btn1c" value="1">高电平触发<br
/>按键 2 引脚<input type="number" id="btn2" onchange="ct()" name="btn2" min="-1" max="99" step="1" value="-1" />
<input type="checkbox" onchange="ct()" name="btn2c" value="1">高电平触发<br
/>按键 3 引脚<input type="number" id="btn3" onchange="ct()" name="btn3" min="-1" max="99" step="1" value="-1" />
<input type="checkbox" onchange="ct()" name="btn3c" value="1">高电平触发<br
/>背光 PWM 引脚<input type="number" id="bklight" onchange="ct()" name="bklight" min="-1" max="99" step="1" value="-1" /><br
/><input type='submit' value='OK!' /><br />
</form><br />
<p>ReadGuy on device ESP8266<br />Copyright © FriendshipEnder <a href="https://github.com/fsender/readguy">GitHub</a>
<a href="https://space.bilibili.com/180327370/">Bilibili</a><br />版本: 1.0.0 ,编译日期: Sep 20 202318:17:07</p>
<p>ReadGuy on device ESP32<br />Copyright © FriendshipEnder <a
href="https://github.com/fsender/readguy">GitHub</a> <a
href="https://space.bilibili.com/180327370">Bilibili</a><br />版本: 1.2.0 ,编译日期: Sep 28 2023 23:37:58</p>
</body>
</html>

View File

@@ -11,13 +11,15 @@
"type": "git",
"url": "https://github.com/fsender/readguy"
},
"version": "1.1.0",
"version": "1.3.1",
"frameworks": "arduino",
"platforms": ["espressif32", "espressif8266"],
"headers": "readguy.h",
"build": {
"libArchive": false
},
"license": "Apache-2.0",
"homepage": "b站视频",
"dependencies": {
"name": "lovyan03/LovyanGFX",
"version": ">=1.1.9"

View File

@@ -1,5 +1,5 @@
name=readguy
version=1.1.0
version=1.3.1
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,17 +59,42 @@
// ------------------ definations - 定义 -
//#define INDEV_DEBUG 1 //debug专用, 正常开机从NVS读取引脚配置数据, 取消注释则每次开机需要重新配置
#define DYNAMIC_PIN_SETTINGS //使用静态的数据 !!!注意:注释此选项编写的程序是不支持跨平台运行的!!!
#define READGUY_ENABLE_WIFI //启用WIFI配网功能.必须先启用 #define DYNAMIC_PIN_SETTINGS
#define READGUY_USE_LITTLEFS 1 //取消注释以使用LittleFS,注释则用SPIFFS
#define BTN_LOOPTASK_STACK 1024 //ESP32按键服务任务的栈空间大小
#define BTN_LOOPTASK_CORE_ID 0 //ESP32按键服务任务运行在哪个核心
#define BTN_LOOPTASK_PRIORITY 4 //ESP32按键服务任务的优先级
#define BTN_LOOPTASK_DELAY 10 //按键任务间隔多久调用一次,单位毫秒
#define ESP8266_SPI_FREQUENCY 20000000 //使用ESP8266时, SPI速率
#define ESP32_DISP_FREQUENCY 20000000 //使用ESP32且屏幕与SD卡用不同的SPI总线时,屏幕速率
#define ESP32_SD_SPI_FREQUENCY 20000000 //注意ESP32在与SD卡共享SPI时,屏幕依据SD_SPI的速率
/// @brief 使用静态的数据 !!!注意:注释此选项编写的程序是不支持跨平台运行的!!!
/// @note 相比于禁用WiFi配网功能, 禁用此功能减少的flash并不多, 为保证程序可在不同屏幕上运行, 请不要注释此选项
#define DYNAMIC_PIN_SETTINGS
/// @brief 启用WIFI配网功能.必须先启用 #define DYNAMIC_PIN_SETTINGS. 此选项对 ESP32xx 会减少大量flash.
#define READGUY_ENABLE_WIFI
/// @brief 启用I2C功能. 可用于联网时钟, 温度计, 陀螺仪等外设. 目前暂不支持库内使用类似函数. 仅可以提供引脚定义
#define READGUY_ENABLE_I2C
/// @brief 使用LittleFS作为片上文件系统, 注释此行则用SPIFFS(功能少, 不好用)
#define READGUY_USE_LITTLEFS 1
/// @brief ESP32按键服务任务的栈空间大小, 不建议普通用户更改. 默认值1024字节. 小于此大小会使程序栈溢出.
#define BTN_LOOPTASK_STACK 1024
/// @brief ESP32按键服务任务运行在哪个核心. 通常运行在核心0上.
#define BTN_LOOPTASK_CORE_ID 0
/// @brief ESP32按键服务任务的优先级. 高于arduino默认任务即可
#define BTN_LOOPTASK_PRIORITY 3
/// @brief 按键任务间隔多久调用一次, 默认为20毫秒. 对于需要精确读取按钮的程序, 请酌情减小此数值
#define BTN_LOOPTASK_DELAY 20
/// @brief 使用ESP8266时, 屏幕和SD卡的SPI速率. 默认为20MHz.
#define ESP8266_SPI_FREQUENCY 20000000
/// @brief 使用ESP32且屏幕与SD卡用不同的SPI总线时, 驱动屏幕的SPI速率
#define ESP32_DISP_FREQUENCY 20000000
/// @brief ESP32驱动SD卡的速率. 当ESP32在与SD卡共享SPI时, 屏幕依据此处的速率.
#define ESP32_SD_SPI_FREQUENCY 20000000
/// @brief debug专用, 请保持处于注释状态. 正常开机从NVS读取引脚配置数据, 取消注释则每次开机需要重新配置
//#define INDEV_DEBUG 1
#ifndef DYNAMIC_PIN_SETTINGS
#ifdef ESP8266
@@ -86,8 +111,9 @@
#define READGUY_sd_miso -1 // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_mosi -1 // 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_sclk -1 // 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_cs 0 // 目标sd卡的 CS 引脚
#define READGUY_i2c_sda -1 // 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
#define READGUY_i2c_scl -1 // 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
//注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发
#define READGUY_btn1 ( 5+1) //按键1,注意需要+1,这里示例已经加了 设置为负的来允许高电平触发
@@ -112,6 +138,8 @@
#define READGUY_sd_mosi 15 // 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_sclk 14 // 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_cs 13 // 目标sd卡的 CS 引脚
#define READGUY_i2c_sda -1 // 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
#define READGUY_i2c_scl -1 // 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
//注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发

View File

@@ -56,16 +56,7 @@ int drvBase::Init(const unsigned char* lut) {
guy_epdParam((iLut==1)?0x02:((iLut==3 || iLut==5)?0x05:0x08)); // 2us per line
guy_epdCmd(0x11);
guy_epdParam(0x03); // X increment; Y increment
SetLut(this->lut);
// * EPD hardware init end /
EndTransfer();
return 0;
}
void drvBase::SetLut(const unsigned char* lut) {
this->lut = lut;
guy_epdCmd(0x32);
// * the length of look-up table is 30 bytes /
for (int i = 0; i < 30; i++) {
if(iLut>0 && iLut<15 && i>19 && i<23){
guy_epdParam(pgm_read_byte(lut_grey_update+iLut*2+i-(i==20?22:23)));
@@ -73,26 +64,8 @@ void drvBase::SetLut(const unsigned char* lut) {
else
guy_epdParam(pgm_read_byte(this->lut+i));
}
}
void drvBase::SetFrameWriter(std::function<uint8_t(int)> f,uint8_t _extra) {
guy_epdCmd(0x44);
guy_epdParam((0 >> 3) & 0xFF);
guy_epdParam(((epdWidth-1) >> 3) & 0xFF);
guy_epdCmd(0x45);
guy_epdParam(0 & 0xFF);
guy_epdParam((0 >> 8) & 0xFF);
guy_epdParam((epdHeight-1) & 0xFF);
guy_epdParam(((epdHeight-1) >> 8) & 0xFF);
guy_epdCmd(0x4e);
guy_epdParam((0 >> 3) & 0xFF);
guy_epdCmd(0x4f);
guy_epdParam(0 & 0xFF);
guy_epdParam((0 >> 8) & 0xFF);
guy_epdCmd(_extra);
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
guy_epdParam(f(i));
EndTransfer();
return 0;
}
const PROGMEM unsigned char lut_slow[] =
@@ -119,35 +92,55 @@ const PROGMEM unsigned char lut_grey_update[]={ //从上到下是依次加深
0x12, 0x44, 0x13, 0x44
};
void drvBase::drv_init(){
void drvBase::drv_init(){ //new init method can init without freshing.
iLut = 15; //新的初始化方式可以允许不初始化直接显示
sleeping=1;
//drv_color(0xffu); //睡眠模式下始终需要慢刷
}
void drvBase::drv_fullpart(bool part){ //切换慢刷/快刷功能
if(!part) iLut=15;
if(sleeping) iLut=15;
else Init(part?lut_fast:lut_slow);
}
/*void drvBase::drv_init(){
Init(lut_slow);
drv_color(0xffu); //睡眠模式下始终需要慢刷
}
void drvBase::drv_fullpart(bool part){ //切换慢刷/快刷功能
if(!part) iLut=15; //恢复默认的灰度模式
Init(part?lut_fast:lut_slow);
}
void drvBase::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新等功能
}*/
void drvBase::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新等功能
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
if(sleeping) Init(lut_slow);
BeginTransfer();
SetMemory(); // bit set = white, bit reset = black
guy_epdBusy(100);
guy_epdCmd(0x24); /* will send the color data */
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
guy_epdParam(f(i));
SpiTransfer(f(i));
guy_epdCmd(0x22);
guy_epdParam(0xC4);
guy_epdCmd(0x20);
guy_epdCmd(0xff);
EndTransfer();
guy_epdBusy((this->lut == (const uint8_t*)lut_fast)?idleFastRf:idleSlowRf);
BeginTransfer();
SetMemory(); // bit set = white, bit reset = black
guy_epdBusy(90);
guy_epdCmd(0x26); /* will send the color data */
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
guy_epdParam(f(i));
EndTransfer();
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
uint32_t u=(this->lut == (const uint8_t*)lut_fast)?idleFastRf:idleSlowRf;
if(ms<u) guy_epdBusy(u-ms);
lastRefresh=0;
BeginTransfer();
SetMemory(); // bit set = white, bit reset = black
guy_epdBusy(90);
guy_epdCmd(0x26); /* will send the color data */
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
SpiTransfer(f(i));
EndTransfer();
}
}
void drvBase::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0) { //未定义RST_PIN时无法唤醒
@@ -156,8 +149,8 @@ void drvBase::drv_sleep() { //开始屏幕睡眠
EndTransfer();
guy_epdBusy(150);
DigitalWrite(RST_PIN, LOW);
sleeping=1;
}
sleeping=1;
}
void drvBase::drv_setDepth(uint8_t i){
iLut = i?(i>15?15:i):15;

View File

@@ -42,7 +42,7 @@ public:
virtual int drv_ID() const=0;
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3); //按照函数刷新
void drv_sleep() ; //开始屏幕睡眠
int drv_width() const { return epdWidth; }; //返回显示区域宽度
int drv_height() const{ return epdHeight; }; //返回显示区域高度
@@ -55,12 +55,9 @@ protected:
int idleFastRf;
private:
int Init(const unsigned char* lut);
void SetFrameWriter(std::function<uint8_t(int)> f,uint8_t _extra);
const unsigned char* lut;
uint8_t iLut = 15;
uint8_t sleeping=1;
void SetLut(const unsigned char* lut);
uint8_t sleeping=0;
};
#ifdef READGUY_DEV_154A

View File

@@ -53,7 +53,7 @@ namespace guydev_154B_270B_290B{
0x22,0x17,0x41,0xB0,0x32,0x28
};*/
const unsigned char drvSSD168x::WS_20_30[48] =
const unsigned char drvSSD168x::WS_20_30[42] =
{ /*
0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
@@ -93,65 +93,25 @@ const unsigned char drvSSD168x::WS_20_30[48] =
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, */
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
0x22, 0x17, 0x41, 0xA8, 0x32, 0x28
};
const unsigned char drvSSD168x::VSH_table[32]=
{0x00,0x24,0x24,0x24,0x25,0x26,0x27,0x28,0x2a,0x2c,0x2e,0x32,0x35,0x39,0x3d,0x41,
0x46,0x45,0x44,0x42,0x41,0x40,0x3f,0x3f,0x3c,0x39,0x36,0x34,0x32,0x31,0x30,0x2f};
{/*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,0x41,0x3c,0x37,0x32,0x2d,0x28,0x23,0xcb,0xc6,0xc1,0xbc,0xb7,0xb2,0xb0,0xae};
//以下代码均为我 FriendshipEnder 原创, 呵呵哒~~
void drvSSD168x::epd_init() {
if(epd_PowerOn==0) {
Reset();
epd_PowerOn=1;
_part=0;
iLut=15;
}
guy_epdCmd(0x12); //SWRESET
guy_epdBusy(10);
guy_epdCmd(0x01); //Driver output control
guy_epdParam((epdHeight-1)&0xff);
guy_epdParam(((epdHeight-1)>>8)&0xff);
guy_epdParam(0x00);
guy_epdCmd(0x11); //data entry mode
guy_epdParam(0x03);
SetMemory();
SetLut();
}
void drvSSD168x::SetFrameWriter(std::function<uint8_t(int)> f) {
if(_part && epd_PowerOn){
//Reset();
SetLut();
guy_epdCmd(0x37);
for(int i=0;i<10;i++) guy_epdParam(i==5?0x40:0x00);
guy_epdCmd(0x3C); //BorderWavefrom
guy_epdParam(0x80);
guy_epdCmd(0x22);
guy_epdParam(0xC0);
guy_epdCmd(0x20);
guy_epdBusy(140);
}
else epd_init();
SetMemory();
guy_epdCmd(0x24);
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
guy_epdParam(f(i));
if(!_part){
guy_epdCmd(0x26);
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
guy_epdParam(f(i));
}
}
void drvSSD168x::DisplayFrame(void) {
guy_epdCmd(0x22);
guy_epdParam(_part?0x0f:0xc7);
guy_epdCmd(0x20);
}
void drvSSD168x::SetLut() {
guy_epdCmd(0x3f);
guy_epdParam(greyScaling?0x07:0x22);
guy_epdCmd(0x03); // gate voltage
guy_epdParam(0x17);
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_epdCmd(0x32);
if(_part){
@@ -159,10 +119,10 @@ void drvSSD168x::SetLut() {
guy_epdParam(i==1?0x80:(i==(greyScaling?0:2)?0x40:0x00));
for(int j=0;j<11;j++) guy_epdParam(0);
}
guy_epdParam(greyScaling?(iLut<5?4-((iLut+1)>>1):1):iLut);
guy_epdParam(greyScaling?1:iLut);
for(i=0;i<83;i++) guy_epdParam(0);
for(i=0;i<6;i++) guy_epdParam(0x22);
for(i=0;i<3;i++) guy_epdParam(0);
guy_epdParam(0x22);
for(i=0;i<8;i++) guy_epdParam(0);
}
else{
//for(i=0; i<153; i++) guy_epdParam(lut[i]==0xff?iLut:lut[i]);
@@ -176,32 +136,77 @@ void drvSSD168x::SetLut() {
for(i=0;i<66;i++) guy_epdParam(0);
for(i=0;i<9;i++) guy_epdParam(pgm_read_byte(WS_20_30+i+33));
}
guy_epdCmd(0x3f);
guy_epdParam(pgm_read_byte(WS_20_30+42));
guy_epdCmd(0x03); // gate voltage
guy_epdParam(pgm_read_byte(WS_20_30+43));
guy_epdCmd(0x04); // source voltage
guy_epdParam(greyScaling?pgm_read_byte(VSH_table+16+iLut):pgm_read_byte(VSH_table+iLut));
guy_epdParam(pgm_read_byte(WS_20_30+45)); // VSH2
guy_epdParam(pgm_read_byte(WS_20_30+46)); // VSL
guy_epdCmd(0x2c); // VCOM
guy_epdParam(pgm_read_byte(WS_20_30+47));
}
void drvSSD168x::drv_init(){
_part=0;
drv_color(0xffu);
epd_PowerOn=0;
//drv_color(0xffu);
}
void drvSSD168x::drv_fullpart(bool part){ //切换慢刷/快刷功能
if(!epd_PowerOn) part=0; //未上电 无法局刷
if(!part) { iLut=15; greyScaling=0; }
_part=part;
}
void drvSSD168x::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
void drvSSD168x::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
BeginTransfer();
SetFrameWriter(f);
DisplayFrame();
if(_part){
//Reset();
SetLut();
guy_epdCmd(0x37);
for(int i=0;i<10;i++) guy_epdParam(i==5?0x40:0x00);
guy_epdCmd(0x3C); //BorderWavefrom
guy_epdParam(0x80);
guy_epdCmd(0x22);
guy_epdParam(0xC0);
guy_epdCmd(0x20);
guy_epdBusy(140);
}
else{
if(epd_PowerOn==0) {
Reset();
epd_PowerOn=1;
_part=0;
iLut=15;
}
guy_epdCmd(0x12); //SWRESET
guy_epdBusy(10);
guy_epdCmd(0x01); //Driver output control
guy_epdParam((epdHeight-1)&0xff);
guy_epdParam(((epdHeight-1)>>8)&0xff);
guy_epdParam(0x00);
guy_epdCmd(0x11); //data entry mode
guy_epdParam(0x03);
SetMemory();
SetLut();
}
SetMemory();
guy_epdCmd(0x24);
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
SpiTransfer(f(i));
if(!_part){
guy_epdCmd(0x26);
for (int i = 0; i < epdHeight*epdWidth / 8; i++)
SpiTransfer(f(i));
}
guy_epdCmd(0x22);
guy_epdParam(_part?0x0f:0xc7);
guy_epdCmd(0x20);
EndTransfer();
guy_epdBusy(_part?600:2300);
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
uint32_t u=_part?600:2300;
if(ms<u) guy_epdBusy(u-ms);
lastRefresh=0;
}
//guy_epdBusy(_part?600:2300);
}
void drvSSD168x::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0){ //无法唤醒
@@ -209,8 +214,8 @@ void drvSSD168x::drv_sleep() { //开始屏幕睡眠
guy_epdCmd(0x10);
guy_epdParam(0x01);
EndTransfer();
epd_PowerOn=0;
}
epd_PowerOn=0;
}
void drvSSD168x::drv_setDepth(uint8_t i){ //设置显示颜色深度, 不支持的话什么都不做
if(i>0 && i<16) {
@@ -220,7 +225,7 @@ void drvSSD168x::drv_setDepth(uint8_t i){ //设置显示颜色深度, 不支持
else iLut=15;
}
void drvSSD168x::drv_draw16grey_step(std::function<uint8_t(int)> f, int step){
if(_quality) return readguyEpdBase::drv_draw16grey_step(f,step);
if(_quality&1) return readguyEpdBase::drv_draw16grey_step(f,step);
if(step==1){
drv_fullpart(1);
greyScaling=1;

View File

@@ -40,11 +40,11 @@ public:
virtual int drv_ID() const =0;
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3); //按照函数刷新
void drv_sleep() ; //开始屏幕睡眠
int drv_width() const { return epdWidth; }; //返回显示区域宽度
int drv_height() const{ return epdHeight; }; //返回显示区域高度
int drv_supportGreyscaling() const { return _quality?16:-16; }
int drv_supportGreyscaling() const { return (_quality&1)?16:-16; }
void drv_setDepth(uint8_t i); //设置显示颜色深度, 不支持的话什么都不做
void drv_draw16grey_step(std::function<uint8_t(int)> f, int step);
protected:
@@ -52,19 +52,14 @@ protected:
int epdWidth;
int epdHeight;
private:
uint8_t _part;
uint8_t epd_PowerOn = 0;
uint8_t _part=1;
uint8_t epd_PowerOn = 1;
uint8_t iLut=15;
uint8_t greyScaling=0;
//static const unsigned char _WF_PARTIAL_2IN9[48] ;
static const unsigned char WS_20_30[48] ;
static const unsigned char WS_20_30[42] ;
static const unsigned char VSH_table[32];
void epd_init();
void SetFrameWriter(std::function<uint8_t(int)> f);
void DisplayFrame(void);
void SetLut();
//void SetLut_by_host(const unsigned char *lut);
};
#ifdef READGUY_DEV_154B

View File

@@ -34,7 +34,7 @@ namespace guydev_213A{
const PROGMEM uint8_t drv::_ed_lut_full[] = { // command //慢刷lut
0x22, 0x55, 0xaa, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x13, 0x13, 0x13, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
0x00, 0x13, 0x16, 0x16, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const PROGMEM uint8_t drv::_ed_lut_part[] = { // command //快刷lut
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -106,18 +106,22 @@ void drv::epd_Init(void){
}
void drv::drv_init(){ //初始化屏幕
epdFull = 1;
BeginTransfer();
epd_PowerOn = 0;
/*BeginTransfer();
epd_Init();
EndTransfer();
drv_color(0xff);
EndTransfer();*/
//drv_color(0xff);
}
void drv::drv_fullpart(bool part){ //初始化慢刷功能
if(!epd_PowerOn) part=0; //未上电 无法局刷
//if(part==epdFull) return;
if(!part) iLut=15; //恢复默认的灰度模式
epdFull = !part;
//epd_Init();
}
void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
BeginTransfer();
if(epdFull) { //当刷新模式从快刷切换为慢刷时, 需要发送一次init
epdFull=0;
@@ -128,37 +132,46 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
// for (int k = 0; k < GUY_D_WIDTH/8; k++)
// guy_epdParam(f(j*(GUY_D_WIDTH/8)+k)); //按照给定的RAM写入数据
for (int i = 0; i < GUY_D_WIDTH/8*GUY_D_HEIGHT; i++)
guy_epdParam(f(i));
SpiTransfer(f(i));
}
guy_epdCmd(0x24);
//for (int j = GUY_D_HEIGHT-1; j >= 0; j--)
// for (int k = 0; k < GUY_D_WIDTH/8; k++)
// guy_epdParam(f(j*(GUY_D_WIDTH/8)+k)); //按照给定的RAM写入数据
for (int i = 0; i < GUY_D_WIDTH/8*GUY_D_HEIGHT; i++)
guy_epdParam(f(i));
SpiTransfer(f(i));
epd_Init();
guy_epdCmd(0x22);
guy_epdParam(epdFull?0xc4:0x04);
guy_epdCmd(0x20);
EndTransfer();
guy_epdBusy(epdFull?1600:310);
lastRefresh=millis();
}
if(m&2){//stage 2
lastRefresh=0;
uint32_t ms=millis()-lastRefresh;
uint32_t u=epdFull?1600:310;
if(ms<u) guy_epdBusy(u-ms);
}
//guy_epdBusy(epdFull?1600:310);
}
void drv::drv_sleep() { //开始屏幕睡眠
if(RST_PIN<0) return; //无法唤醒
BeginTransfer();
if(epd_PowerOn){
guy_epdCmd(0x22);
guy_epdParam(0xc3);
guy_epdCmd(0x20);
guy_epdBusy(200);
epd_PowerOn = 0;
epdFull = 1; //强制设置为慢刷新模式
if(RST_PIN>=0){ //RST_PIN<0 无法唤醒
BeginTransfer();
if(epd_PowerOn){
guy_epdCmd(0x22);
guy_epdParam(0xc3);
guy_epdCmd(0x20);
guy_epdBusy(200);
}
guy_epdCmd(0x10); //enter deep sleep
guy_epdParam(0x01);
EndTransfer();
DelayMs(200);
DigitalWrite(RST_PIN, LOW);
}
guy_epdCmd(0x10); //enter deep sleep
guy_epdParam(0x01);
EndTransfer();
DelayMs(200);
DigitalWrite(RST_PIN, LOW);
epd_PowerOn = 0;
epdFull = 1; //强制设置为慢刷新模式
}
}
#endif /* END OF FILE. ReadGuy project.

View File

@@ -47,7 +47,7 @@ public:
int drv_ID() const { return READGUY_DEV_213A; }
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3); //按照函数刷新
void drv_sleep() ; //开始屏幕睡眠
int drv_width() const { return EPD_REAL_WIDTH; }; //返回显示区域宽度
//int drv_panelwidth() const { return GUY_D_WIDTH; }; //返回缓存的数据宽度
@@ -56,8 +56,8 @@ public:
int drv_supportGreyscaling() const { return 16; }
private:
void epd_Init(void);
uint8_t epdFull; //是partical模式/快速刷新模式 0快刷, 1慢刷
uint8_t epd_PowerOn = 0; //是否上电
uint8_t epdFull=0; //是partical模式/快速刷新模式 0快刷, 1慢刷
uint8_t epd_PowerOn = 1; //是否上电
uint8_t iLut = 15; //颜色深度(灰度模式用)
// uint8_t dc_d = 0; //dc引脚状态 0 command, 1 data
static const PROGMEM uint8_t _ed_lut_full[];

View File

@@ -76,13 +76,8 @@ drv_base::drv_base(){
guy_lutArray[4] = lutSlow_b_b;
guy_lutArray[5] = lutFast_;
}
void drv_base::pre(){
guy_epdCmd(0x91);
send_zoneInfo();
guy_epdCmd(0x13);
}
void drv_base::epd_init(){
if(!Power_is_on) Reset();
//if(!Power_is_on) Reset();
guy_epdCmd(0x01);
guy_epdParam(0x03);
guy_epdParam(0x00);
@@ -110,11 +105,11 @@ void drv_base::epd_init(){
void drv_base::send_zoneInfo(){
guy_epdCmd(0x90);
guy_epdParam(0x00);
guy_epdParam(0x97);
guy_epdParam(epdWidth-1);
guy_epdParam(0x00);
guy_epdParam(0x00);
guy_epdParam(0x01);
guy_epdParam(0x27);
guy_epdParam((epdHeight-1)>>8);
guy_epdParam((epdHeight-1)&0xff);
guy_epdParam(0x00);
}
void drv_base::SendLuts(bool part_lut){
@@ -134,17 +129,19 @@ void drv_base::SendLuts(bool part_lut){
}
}
}
if(!Power_is_on){
if(!Power_is_on || Power_is_on==2){
guy_epdCmd(0x04);
guy_epdBusy(-60);
Power_is_on = 1;
}
}
void drv_base::drv_init(){
part_mode=0;
drv_color(0xff);
part_mode = 0;
Power_is_on = 0; //初始为未上电
//drv_color(0xff);
}
void drv_base::drv_fullpart(bool part){ //切换慢刷/快刷功能
if(!Power_is_on) part=0;
if(!part) greyLut=15; //恢复默认的灰度模式
part_mode = part;
}
@@ -155,80 +152,69 @@ void drv_base::drv_setDepth(uint8_t i){
SendLuts(1);
EndTransfer();
}
void drv_base::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
void drv_base::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
BeginTransfer();
pre();
//send pixel data -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
//Total 5624 data written.
//for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++)
// guy_epdParam(c);
epd_init();
SendLuts(part_mode);
guy_epdCmd(0x91);
send_zoneInfo();
guy_epdCmd(0x13);
for (int i = 0; i < epdHeight*epdWidth/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
SpiTransfer(f(i)); //按照给定的RAM写入数据
guy_epdCmd(0x92);
if(part_mode){
guy_epdCmd(0x30);
guy_epdParam(0x3a); //0x3a:100Hz, 0x29:150Hz
//[EPDrg_BW<>] refresh fx
send_zoneInfo();
guy_epdCmd(0x12);
EndTransfer();
//[EPDrg_EPD] wait_until_idle fx: 1300
guy_epdBusy(-200);
//[EPDrg_BW<>] writeImageAgain fx
//guy_epdCmd(0x91);
//send_zoneInfo();
//guy_epdCmd(0x13);
//send image data -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
//Total 5624 data written.
//guy_epdCmd(0x92);
}
else{
epd_init();
SendLuts(0);
guy_epdCmd(0x12);
//[EPDrg_EPD] wait_until_idle fx: 1600
EndTransfer();
guy_epdBusy(-2000);
//[EPDrg_BW<>] writeImageAgain fx
BeginTransfer();
epd_init();
SendLuts(1);
pre();
//send image data -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
//Total 5624 data written.
for (int i = 0; i < epdHeight*epdWidth/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
guy_epdCmd(0x92);
EndTransfer();
//[EPDrg_BW<>] powerOff fx
//guy_epdCmd(0x02);
//[EPDrg_EPD] wait_until_idle fx: 20
//guy_epdBusy(-20);
}
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
if(part_mode){
if(ms<200) guy_epdBusy(ms-200);
//guy_epdBusy(-200);
}
else{
if(ms<2000) guy_epdBusy(ms-2000);
//guy_epdBusy(-2000);
BeginTransfer();
epd_init();
SendLuts(1);
guy_epdCmd(0x92);
EndTransfer();
}
lastRefresh=0;
}
}
void drv_base::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0) { //未定义RST_PIN时无法唤醒
part_mode = 0;
BeginTransfer();
guy_epdCmd(0x02); // power off
guy_epdBusy(-20);
guy_epdCmd(0X10);
guy_epdParam(0x01);
EndTransfer();
Power_is_on = 0;
}
//if(RST_PIN>=0) { //未定义RST_PIN时无法唤醒
BeginTransfer();
guy_epdCmd(0x02); // power off
guy_epdBusy(-20);
guy_epdCmd(0X10);
guy_epdParam(0x01);
EndTransfer();
//}
part_mode = 0;
Power_is_on = 0;
}
void drv_base::drv_draw16grey_step(std::function<uint8_t(int)> f, int step){
if(_quality) return readguyEpdBase::drv_draw16grey_step(f,step);
if(_quality&1) return readguyEpdBase::drv_draw16grey_step(f,step);
if(step==1){
greyHQ=3;
drv_setDepth(3);
drv_fullpart(1); //开始
drv_fullpart(1); //开始
}
if(step==3) drv_setDepth(2);
if(step==5) drv_setDepth(1);

View File

@@ -39,23 +39,22 @@ public:
virtual int drv_ID() const=0;
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3); //按照函数刷新
void drv_sleep() ; //开始屏幕睡眠
int drv_width() const { return epdWidth; }; //返回显示区域宽度
int drv_height() const{ return epdHeight; }; //返回显示区域高度
void drv_setDepth(uint8_t i); //设置显示颜色深度
int drv_supportGreyscaling() const { return _quality?16:-16; }
int drv_supportGreyscaling() const { return (_quality&1)?16:-16; }
void drv_draw16grey_step(std::function<uint8_t(int)> f, int step);
protected:
int epdWidth;
int epdHeight;
private:
void pre();
void epd_init();
void send_zoneInfo();
void SendLuts(bool part_lut);
uint8_t part_mode = 0;
uint8_t Power_is_on = 0; //初始为未上电
uint8_t part_mode = 1;
uint8_t Power_is_on = 2; //初始为未上电
uint8_t greyLut=15;
uint8_t greyHQ=4; // greyHQ==3 时 为高品质刷新灰度 否则为常规方式刷新灰度
const uint8_t *guy_lutArray[6];

View File

@@ -31,7 +31,7 @@
#include "guy_370a.h"
#ifdef READGUY_DEV_370A
namespace guydev_370A{
static const PROGMEM unsigned char lut_1Gray_GC[] ={
static const PROGMEM unsigned char lut_1Grey_GC[] ={
0x2A,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//1
0x05,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//2
0x2A,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//3
@@ -39,7 +39,7 @@ static const PROGMEM unsigned char lut_1Gray_GC[] ={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//5
0x00,0x02,0x03,0x0A,0x00,0x02,0x06,0x0A,0x05,0x00
};
static const PROGMEM unsigned char lut_1Gray_A2[] ={
static const PROGMEM unsigned char lut_1Grey_A2[] ={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //1
0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //2
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //3
@@ -88,12 +88,12 @@ void drv::Load_LUT(unsigned char mode) {
guy_epdCmd(0x32);
for (i = 0; i < 60; i++) {
if(mode == 1)
guy_epdParam(pgm_read_byte(lut_1Gray_GC+i));
guy_epdParam(pgm_read_byte(lut_1Grey_GC+i));
else if(mode == 0){
if(20==i && greyScaling<8) guy_epdParam(0x03);
else if(53==i && greyScaling<3) guy_epdParam(greyScaling);
else if(53==i && greyScaling==15) guy_epdParam(5);
else guy_epdParam(pgm_read_byte(lut_1Gray_A2+i));
else guy_epdParam(pgm_read_byte(lut_1Grey_A2+i));
}
}
for (i = 0; i < 45; i++) {
@@ -103,10 +103,14 @@ void drv::Load_LUT(unsigned char mode) {
}
void drv::drv_init(){ //初始化屏幕
sleeping = 1;
Init();
drv_color(0xff);
sleeping = 1;
part_mode=0;
//drv_color(0xff);
}
void drv::drv_fullpart(bool part){ //切换慢刷/快刷功能
if(sleeping) return;
if(!part) {
greyScaling=15; //恢复默认的灰度模式
BeginTransfer();
@@ -116,7 +120,9 @@ void drv::drv_fullpart(bool part){ //切换慢刷/快刷功能
}
part_mode=part;
}
void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
if(sleeping) Init();
BeginTransfer();
guy_epdCmd(0x4E); guy_epdParam(0x00); guy_epdParam(0x00);
@@ -124,17 +130,25 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
guy_epdCmd(0x24);
for (int i = 0; i < GUY_D_HEIGHT*GUY_D_WIDTH/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
SpiTransfer(f(i)); //按照给定的RAM写入数据
if(!part_mode) {
guy_epdCmd(0x26);
for (int i = 0; i < GUY_D_HEIGHT*GUY_D_WIDTH/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
SpiTransfer(f(i)); //按照给定的RAM写入数据
}
Load_LUT(!part_mode);
guy_epdCmd(0x20);
EndTransfer();
guy_epdBusy(part_mode?500:1300);
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
uint32_t u=part_mode?500:1300;
if(ms<u) guy_epdBusy(u-ms);
lastRefresh=0;
}
//guy_epdBusy(part_mode?500:1300);
}
void drv::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0){
@@ -142,8 +156,9 @@ void drv::drv_sleep() { //开始屏幕睡眠
guy_epdCmd(0X10); //deep sleep
guy_epdParam(0x03);
EndTransfer();
sleeping = true;
}
sleeping = true;
part_mode=0;
}
void drv::drv_setDepth(uint8_t i){ //设置显示颜色深度, 不支持的话什么都不做

View File

@@ -40,16 +40,16 @@ public:
int drv_ID() const { return READGUY_DEV_370A; }
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
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_height() const{ return GUY_D_HEIGHT; }; //返回显示区域高度
int drv_supportGreyscaling() const { return 16; }
void drv_setDepth(uint8_t i); //设置显示颜色深度, 不支持的话什么都不做
private:
uint8_t part_mode;
uint8_t part_mode=1;
uint8_t greyScaling = 15;
uint8_t sleeping = true;
uint8_t sleeping = false;
int Init(void);
void Load_LUT(unsigned char mode);
};

View File

@@ -100,6 +100,7 @@ const PROGMEM unsigned char drv::lut_213_B72_16grey[]={
void drv::epd_Init(void){
const uint16_t GreyDrvFrameFreq[]={
0x1108, /* 75Hz */
0x1108, /* 75Hz */
0x180c, /* 35Hz */
0x2c0a, /* 50Hz */
@@ -115,7 +116,6 @@ void drv::epd_Init(void){
0x1804, /* 140Hz */
0x0d04, /* 145Hz */
0x0304, /* 150Hz */
//0x0304, /* 150Hz */
};
//_InitDisplay fx
if(epdFull==2) {
@@ -159,16 +159,6 @@ void drv::epd_Init(void){
guy_epdCmd(0x11); //Data Entry mode
guy_epdParam(0x03); //0x03 or 0x01
}
void drv::power_up(){
if(!Power_is_on){
//Power is not On
guy_epdCmd(0x22);
guy_epdParam(0xc0);
guy_epdCmd(0x20);
guy_epdBusy(120);
Power_is_on=1;
}
}
void drv::power_down(){
Power_is_on=0;
BeginTransfer();
@@ -187,36 +177,43 @@ void drv::SetLut(const unsigned char* lut){
}
}
void drv::drv_init(){ //初始化屏幕
epdFull = 2;
drv_color(0xff);
Power_is_on = 0; //初始为未上电
epdFull = 2; //初始设为正在休眠
//drv_color(0xff);
}
void drv::drv_fullpart(bool part){ //初始化慢刷功能
if(!part) GreyScaling=0;
if(epdFull<=1) epdFull = !part; //epdFull==2代表睡眠中, 不能快刷
if(epdFull) GreyScaling=0;
}
void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
BeginTransfer();
epd_Init();
SetMemory();
power_up();
if(!Power_is_on){ //Power is not On, Power up.
guy_epdCmd(0x22);
guy_epdParam(0xc0);
guy_epdCmd(0x20);
guy_epdBusy(120);
Power_is_on=1;
}
SetMemory();
if(epdFull){ //慢刷
guy_epdCmd(0x26);
for (int i = 0; i < GUY_D_HEIGHT*GUY_D_WIDTH/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
SpiTransfer(f(i)); //按照给定的RAM写入数据
}
guy_epdCmd(0x24);
for (int i = 0; i < GUY_D_HEIGHT*GUY_D_WIDTH/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
SpiTransfer(f(i)); //按照给定的RAM写入数据
if(epdFull){ //慢刷
epd_Init();
SetMemory();
SetLut(lut_213_B72_Full);
guy_epdCmd(0x22);
guy_epdParam(0xc4);
guy_epdCmd(0x20);
EndTransfer();
guy_epdBusy(1600); //等待刷完
//guy_epdBusy(1600); //等待刷完
}
else{ //快刷
guy_epdCmd(0x2c); //may a mistake? 此处不需要设置vcom
@@ -224,20 +221,27 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
SetLut(GreyScalingHighQuality?lut_213_B72_16grey:lut_213_B72);
guy_epdCmd(0x22);
guy_epdParam(0x04);
guy_epdCmd(0x20);
EndTransfer();
guy_epdBusy(260); //等待屏幕刷新完成
}
BeginTransfer(); //write again
SetMemory();
guy_epdCmd(0x26);
for (int i = 0; i < GUY_D_HEIGHT*GUY_D_WIDTH/8; i++)
guy_epdParam(f(i)); //按照给定的RAM写入数据
guy_epdCmd(0x20);
EndTransfer();
if(epdFull) power_down();
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
uint32_t u=epdFull?1600:260;
if(ms<u) guy_epdBusy(u-ms); //等待屏幕刷新完成
lastRefresh=0;
BeginTransfer(); //write again
SetMemory();
guy_epdCmd(0x26);
for (int i = 0; i < GUY_D_HEIGHT*GUY_D_WIDTH/8; i++)
SpiTransfer(f(i)); //按照给定的RAM写入数据
EndTransfer();
if(epdFull) power_down();
}
}
void drv::drv_draw16grey_step(std::function<uint8_t(int)> f, int step){
if(_quality) return readguyEpdBase::drv_draw16grey_step(f,step);
if(_quality&1) return readguyEpdBase::drv_draw16grey_step(f,step);
if(step==1) drv_fullpart(1);//初始阶段,完成准备工作 //设置为快刷模式
GreyScalingHighQuality=step; //开启高品质灰度模式
drv_dispWriter(f);
@@ -260,13 +264,14 @@ void drv::drv_draw16grey_step(std::function<uint8_t(int)> f, int step){
}*/
void drv::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0) { //未定义RST_PIN时无法唤醒
epdFull=2; //睡眠
power_down();
BeginTransfer();
guy_epdCmd(0x10); // deep sleep mode
guy_epdParam(0x01); // enter deep sleep
EndTransfer();
}
epdFull=2; //睡眠
Power_is_on=0;
}
}
#endif /* END OF FILE. ReadGuy project.

View File

@@ -41,26 +41,25 @@ public:
int drv_ID() const { return READGUY_DEV_420A; }
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
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_height() const{ return GUY_D_HEIGHT; }; //返回显示区域高度
void drv_setDepth(uint8_t i) { epdFull=0; GreyScaling = i>15?15:i; if(!i) GreyScaling=15;}
void drv_draw16grey_step(std::function<uint8_t(int)> f, int step);
int drv_supportGreyscaling() const { return _quality?16:-16; }
int drv_supportGreyscaling() const { return (_quality&1)?16:-16; }
private:
//void Lut(unsigned char* lut);
void epd_Init(void);
void SetLut(const unsigned char* lut);
uint8_t Power_is_on = 0; //初始为未上电
uint8_t epdFull = 2; //是partical模式/快速刷新模式 0快刷, 1慢刷
uint8_t epdFull = 1; //是partical模式/快速刷新模式 0快刷, 1慢刷
uint8_t GreyScaling = 0; //是否正在灰度显示
uint8_t GreyScalingHighQuality = 0; //是否正在16阶高品质灰度显示
static const PROGMEM unsigned char epd42_lut_full[];
static const PROGMEM unsigned char lut_213_B72[];
static const PROGMEM unsigned char lut_213_B72_Full[];
static const PROGMEM unsigned char lut_213_B72_16grey[];
void power_up();
void power_down();
//std::function<void(int)> send; //此处不能用 void (*send)(int); 是因为lambda函数是std的
};

View File

@@ -49,35 +49,55 @@ void drv::Init(uint8_t pt) {
Reset();
guy_epdCmd(0x00); //Software reset
guy_epdParam(0x00);
delayMicroseconds(40);
guy_epdCmd(0x00);
guy_epdParam(0x1f);
guy_epdParam(0x0d);
delayMicroseconds(40);
guy_epdCmd(0x50);
guy_epdParam(0x97);
delayMicroseconds(40);
guy_epdCmd(0x01);
guy_epdParam(0x03);
guy_epdParam(0x00);
guy_epdParam(0x2b);
guy_epdParam(0x2b);
delayMicroseconds(40);
guy_epdCmd(0x06);
guy_epdParam(0x17);
guy_epdParam(0x17);
guy_epdParam(0x17);
delayMicroseconds(40);
guy_epdCmd(0x00);
guy_epdParam(0x3f);
delayMicroseconds(40);
guy_epdCmd(0x30);
guy_epdParam(0x3a);
delayMicroseconds(40);
guy_epdCmd(0x61);
guy_epdParam(0x01);
guy_epdParam(0x90);
guy_epdParam(0x01);
guy_epdParam(0x2c);
delayMicroseconds(40);
guy_epdCmd(0x82); //vcom setting: 0x00:-0.1V, 0x3a: -3.0V
guy_epdParam(0x1a);
delayMicroseconds(40);
guy_epdCmd(0x50);
guy_epdParam(0xd7);
SendLuts(pt?1:0); //不知为何, 此处需要快刷lut
delayMicroseconds(40);
//不知为何, 此处需要快刷lut
for(uint8_t i=0;i<=4;i++){ //for(uint8_t i=0;i<=(lutOption==2?5:4);i++){
guy_epdCmd(i+0x20);
for(int j=0;j<(pt?6:(i+pt==0?44:42));j++){
if(j==2 && customGreyscale) guy_epdParam(63);
else if(j==3 && i!=2) guy_epdParam(customLut);
else guy_epdParam(j<18?pgm_read_byte(guy_lutArray[i+(pt?1:0)*5]+j):0x00);
delayMicroseconds(40);
}
}
if(pt!=2) guy_epdCmd(0x04);
delayMicroseconds(40);
}
void drv::sendArea(){
@@ -96,18 +116,6 @@ void drv::sendAreaRaw(){
guy_epdParam(0x2b);
guy_epdParam(0x01);
}
void drv::SendLuts(uint8_t lutOption){ // -------- 在此发送你的Lut数据 --------<<<<
//Serial.printf("SendLuts fx: %d\n",lutOption);
for(uint8_t i=0;i<=4;i++){ //for(uint8_t i=0;i<=(lutOption==2?5:4);i++){
guy_epdCmd(i+0x20);
//Serial.printf("%d th lut loaded.\n",i+lutOption*5);
for(int j=0;j<(lutOption==1?6:(i+lutOption==0?44:42));j++){
if(j==2 && customGreyscale) guy_epdParam(63);
else if(j==3 && i!=2) guy_epdParam(customLut);
else guy_epdParam(j<18?pgm_read_byte(guy_lutArray[i+lutOption*5]+j):0x00);
}
}
}
const PROGMEM unsigned char drv::lut_vcom0[] ={
0x00, 0x08, 0x08, 0x00, 0x00, 0x02,
0x00, 0x0F, 0x0F, 0x00, 0x00, 0x01,
@@ -138,31 +146,32 @@ const PROGMEM unsigned char drv::lutFast_w_w[] ={ 0x02,2,0,48,0,1 };
const PROGMEM unsigned char drv::lutFast_b_w[] ={ 0x5a,2,0,63,0,1 };
const PROGMEM unsigned char drv::lutFast_w_b[] ={ 0x84,2,0,48,0,1 };
const PROGMEM unsigned char drv::lutFast_b_b[] ={ 0x01,2,0,48,0,1 };
void drv::epd_display(){
//void drv::epd_display(){
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
BeginTransfer();
Init(part_mode);
if(part_mode){
sendArea();
guy_epdCmd(0x13);
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){
send(i); //guy_epdParam(d[i]);
}
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++)
SpiTransfer(f(i));
//data
guy_epdCmd(0x92);
sendAreaRaw();
//[EPDrg_BW<>] refresh fx
}
else{
Power_is_on=1;
guy_epdCmd(0x10);
//刷新数据
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){
send(i); //guy_epdParam(d[i]);
}
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++)
SpiTransfer(f(i));
guy_epdCmd(0x13);
//刷新数据
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){
send(i); //guy_epdParam(d[i]);
}
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++)
SpiTransfer(f(i));
guy_epdCmd(0x92);
//[EPDrg_BW<>] refresh fx
guy_epdCmd(0x00);
@@ -174,26 +183,30 @@ void drv::epd_display(){
}
guy_epdCmd(0x12);
EndTransfer();
guy_epdBusy(part_mode?-800:-3600);
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
uint32_t u=part_mode?800:3600;
if(ms<u) guy_epdBusy(ms-u);
lastRefresh=0;
BeginTransfer();
if(part_mode){
sendArea();
guy_epdCmd(0x13);
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){
send(i); //guy_epdParam(d[i]);
}
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++)
SpiTransfer(f(i));
guy_epdCmd(0x92);
EndTransfer();
}
else{
Init(2);
sendArea();
guy_epdCmd(0x13);
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++){
send(i); //guy_epdParam(d[i]);
}
for(int i=0;i<GUY_D_WIDTH*GUY_D_HEIGHT/8;i++)
SpiTransfer(f(i));
guy_epdCmd(0x92);
guy_epdCmd(0x02);
}
EndTransfer();
guy_epdBusy(-20);
}
@@ -202,19 +215,20 @@ const PROGMEM uint8_t drv::greyTable[16]={
32,23,18,15,12,10,9,8,
7,6,5,5,5,5,5,0
};
void drv::drv_init(){
//Init(0);
drv_color(0xffu);
part_mode=0;
Power_is_on=0;
BeginTransfer();
Init(0);
EndTransfer();
//drv_color(0xffu);
}
void drv::drv_fullpart(bool part){ //切换慢刷/快刷功能
if(!part) customLut = CUSTOM_LUT_DISABLE;
part_mode = part;
//Init(part);
}
void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
send = [&](int i){ guy_epdParam(f(i)); };
epd_display();
if(Power_is_on) {
if(!part) customLut = CUSTOM_LUT_DISABLE;
part_mode = part;
//Init(part);
}
}
void drv::drv_sleep() { //开始屏幕睡眠
if(RST_PIN>=0){ //未定义RST_PIN时无法唤醒
@@ -227,6 +241,8 @@ void drv::drv_sleep() { //开始屏幕睡眠
guy_epdParam(0xA5);
EndTransfer();
}
Power_is_on=0;
part_mode=0;
}
}
#endif /* END OF FILE. ReadGuy project.

View File

@@ -44,7 +44,7 @@ public:
void drv_init(); //初始化屏幕
//void drv_draw16grey(uint8_t *d16bit);
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //按照函数刷新
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_height() const{ return GUY_D_HEIGHT; }; //返回显示区域高度
@@ -56,8 +56,8 @@ public:
int drv_supportGreyscaling() const { return 16; }
private:
uint8_t part_mode = 0;
//uint8_t Power_is_on = 0; //初始为未上电
uint8_t part_mode = 1;
uint8_t Power_is_on = 1; //初始为未上电
static const PROGMEM unsigned char lut_vcom0[];
static const PROGMEM unsigned char lut_ww[];
@@ -76,12 +76,7 @@ private:
void Init(uint8_t pt);
void sendArea();
void sendAreaRaw();
void SendLuts(uint8_t lutOption); //0:慢刷, 1:快刷, 2:四阶灰度
uint8_t customLut, customGreyscale; //customLut 是灰度刷新时的自定义lut
void epd_display();
//void (*send)(int);
std::function<void(int)> send; //此处不能用 void (*send)(int); 是因为lambda函数是std的
static const PROGMEM uint8_t greyTable[16];
};
}

View File

@@ -32,24 +32,27 @@
//#define MEPD_DEBUG_WAVE 16 //取消此行注释来监视SPI的通信数据 (用于debug), 可以查看主控和屏幕的通信数据
//#define MEPD_DEBUG_WAITTIME //显示墨水屏的刷新时间, 单位是毫秒
#define FLOYD_STEINBERG_DITHERING //默认使用性能更好的floyd steinberg抖动算法, 取消注释则使用bayer图案抖动算法
#define FLOYD_STEINBERG_DITHERING //默认使用更好的floyd steinberg抖动算法,取消注释则用bayer图案抖动算法
#define FLOYD_DITHERING_16GREY //使用更好的floyd steinberg抖动算法显示16阶灰度,取消注释则使用阈值填充
// ***********************************************************************
//设备设置: 取消对应设备就可以直接禁用掉对应设备
//如果程序不想支持此型号(为了节省flash),请注释掉这一行, 实测根本不会节省多少空间
#define READGUY_DEV_154A 0
#define READGUY_DEV_154B 1
#define READGUY_DEV_213A 2
#define READGUY_DEV_213B 3
#define READGUY_DEV_266A 4
#define READGUY_DEV_270B 5
#define READGUY_DEV_290A 6
#define READGUY_DEV_290B 7
#define READGUY_DEV_370A 8
#define READGUY_DEV_420A 9
#define READGUY_DEV_420B 10
#define MEPD_DEBUG_DISPLAY 11 //使用LCD显示屏幕来debug
//非常不建议用户在应用程序中禁用掉设备. 实测根本不会节省很多的空间. 如果要节省代码占用的flash空间,
//请在字库上下功夫.
//如果程序不想支持此型号(为了节省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
#define READGUY_DEV_213A 2 //2.13寸汉朔价签, 价格便宜, 兼容性好. 夏襄居士单词卡用 GDE0213B1
#define READGUY_DEV_213B 3 //2.13寸三色, 分辨率低212x104, 部分价签也用这个屏 易老化
#define READGUY_DEV_266A 4 //2.66寸Vusion价签, 黑白双色.
#define READGUY_DEV_270B 5 //2.7寸佳显触摸墨水屏, 佳显官方店有售: 2.7寸触摸墨水屏.
#define READGUY_DEV_290A 6 //2.9寸A01, 甘草酸不酸2.9寸A01型号. 显示性能一般
#define READGUY_DEV_290B 7 //2.9寸T94背光, 甘草酸不酸2.9寸T94型号. 显示性能更好, 支持前置光
#define READGUY_DEV_370A 8 //3.7寸高DPI墨水屏, 480x280分辨率, 显示效果非常好
#define READGUY_DEV_420A 9 //4.2寸HINK黑白价签, 汉朔价签, 汉朔三色价签通用. 速度快, 但效果一般
#define READGUY_DEV_420B 10 //4.2寸WF丝印, 部分价签也用此型号屏, 排线有WF开头的丝印
#define MEPD_DEBUG_DISPLAY 11 //使用LCD显示屏幕来debug. 前往ctg_stack_c_defines.h修改兼容的屏幕.
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -29,27 +29,19 @@
#include "guy_epdbase.h"
#include <spi.h>
#pragma GCC optimize ("O3")
readguyEpdBase::readguyEpdBase() {
};
readguyEpdBase::~readguyEpdBase() {
};
void readguyEpdBase::DigitalWrite(int pin, int value) {
if(pin>=0) digitalWrite(pin, value);
}
int readguyEpdBase::DigitalRead(int pin) {
return (pin>=0)?digitalRead(pin):1;
}
void readguyEpdBase::DelayMs(unsigned int delaytime) {
delay(delaytime);
}
void readguyEpdBase::BeginTransfer(){
if(!in_trans && CS_PIN>=0) {
digitalWrite(CS_PIN, LOW);
DigitalWrite(CS_PIN, LOW);
if(spi_tr_press!=nullptr) spi_tr_press();
}
in_trans++;
@@ -57,11 +49,11 @@ void readguyEpdBase::BeginTransfer(){
void readguyEpdBase::EndTransfer(){
if(in_trans) in_trans--;
if(!in_trans && CS_PIN>=0) {
digitalWrite(CS_PIN, HIGH);
DigitalWrite(CS_PIN, HIGH);
if(spi_tr_release!=nullptr) spi_tr_release();
}
}
void readguyEpdBase::SpiTransfer(unsigned char data) {
/*IRAM_ATTR void readguyEpdBase::SpiTransfer(unsigned char data) {
if(in_trans) {
_spi->transfer(data);
return;
@@ -69,7 +61,7 @@ void readguyEpdBase::SpiTransfer(unsigned char data) {
if(CS_PIN>=0) digitalWrite(CS_PIN, LOW);
_spi->transfer(data);
if(CS_PIN>=0) digitalWrite(CS_PIN, HIGH);
}
}*/
int readguyEpdBase::IfInit(SPIClass &c,int8_t cs,int8_t dc,int8_t rst,int8_t busy) {
//static uint8_t IfHadInit=0;
//if(IfHadInit) return;
@@ -78,11 +70,14 @@ int readguyEpdBase::IfInit(SPIClass &c,int8_t cs,int8_t dc,int8_t rst,int8_t bus
RST_PIN = rst;
BUSY_PIN = busy;
if(CS_PIN>=0) pinMode(CS_PIN , OUTPUT);
DigitalWrite(CS_PIN,HIGH);
if(RST_PIN>=0) pinMode(RST_PIN , OUTPUT);
DigitalWrite(RST_PIN,HIGH);
if(DC_PIN>=0) pinMode(DC_PIN , OUTPUT);
DigitalWrite(DC_PIN,HIGH);
if(BUSY_PIN>=0) pinMode(BUSY_PIN, INPUT);
_spi = &c;
lastRefresh=0;
//_spi->begin();
//_spi->beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
@@ -165,9 +160,9 @@ 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){
#ifndef FLOYD_STEINBERG_DITHERING
static const uint8_t bayer_tab [64]={
void readguyEpdBase::drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,int o,
uint16_t fw, uint16_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,
12, 44, 4, 36, 14, 46, 6, 38,
@@ -177,102 +172,134 @@ 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
};
#endif
const uint_fast16_t w = (spr.width()+7)>>3;
if((!w) || (!spr.height())) return;
uint16_t *readBuff = new uint16_t[spr.width()];
uint8_t *writeBuff = new uint8_t[w];
#ifdef FLOYD_STEINBERG_DITHERING
int16_t *floyd_tab[2];
floyd_tab[0] = new int16_t [spr.width()];
floyd_tab[1] = new int16_t [spr.width()];
for(int j=0;j<spr.width();j++){ floyd_tab[0][j] = 0; floyd_tab[1][j] = 0; }
#endif
sprbase.fillRect(x,y,spr.width(),spr.height(),1);
for(int_fast16_t i=0;i<(int_fast16_t)spr.height();i++){
spr.readRect(0,i,spr.width(),1,readBuff);
#ifdef FLOYD_STEINBERG_DITHERING
if(!fw) fw=spr.width();
if(!fh) fh=spr.height();
if((!fw) || (!fh)) return;
if(o==0 || o==1){
readBuff = new uint16_t[spr.width()];
floyd_tab[0] = new int16_t [fw];
floyd_tab[1] = new int16_t [fw];
for(int j=0;j<fw;j++){ floyd_tab[0][j] = 0; floyd_tab[1][j] = 0; }
writeBuff = new uint8_t[(fw+7)>>3];
}
sprbase.fillRect(x,y,fw,fh,1);
for(int32_t i=y;i<(int32_t)fh+y;i++){
uint_fast8_t buff8bit=0;
for(int_fast16_t j=0;j<(int_fast16_t)spr.width();j++){
int_fast16_t flodelta = floyd_tab[i&1][j]+(int_fast16_t)(greysc(readBuff[j])<<4);
if(flodelta>=2048) {
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]);
int32_t flodelta = floyd_tab[i&1][j]+(int32_t)((gv<<8)|gv);
if(flodelta>=0x8000) {
//spr.drawPixel(j,i,1);
buff8bit |= 1<<((~j)&7);
flodelta -= 4095;
flodelta -= 0xffff;
}
if((j&7)==7 || j==((int_fast16_t)spr.width()-1)){
if((j&7)==7 || j==((int32_t)fw-1)){
writeBuff[j>>3]=buff8bit;
buff8bit=0;
}
//计算出读取到的颜色, 然后与128比较, 如果小于128, 显示黑色,否则显示白色
//else { spr.drawPixel(j,i,0); }
/*if(j!=(int_fast16_t)spr.width()-1) {
/*if(j!=(int32_t)spr.width()-1) {
floyd_tab[ i&1 ][j+1] += (flodelta*7)>>4;
floyd_tab[!(i&1)][j+1] += (flodelta )>>4;
}
if(j) { floyd_tab[!(i&1)][j-1] += (flodelta*3)>>4; }
{ floyd_tab[!(i&1)][j ] += (flodelta*5)>>4; }*/
if(j!=(int_fast16_t)spr.width()-1)
if(j!=fw-1)
{ floyd_tab[i&1] [j+1] += (flodelta*7)>>4; }
if(j) { floyd_tab[!(i&1)][j-1] += (flodelta*3)>>4; }
{ floyd_tab[!(i&1)][j ] += (flodelta*5)>>4; }
if(j!=(int_fast16_t)spr.width()-1)
if(j!=fw-1)
{ floyd_tab[!(i&1)][j+1] += (flodelta )>>4; }
}
for(int floi=0;floi<spr.width();floi++) floyd_tab[i&1][floi]=0;
#else
for(int_fast16_t j=0;j<w;j++){
uint_fast8_t buff8bit=0;
for(uint_fast8_t b=0;b<8;b++)
buff8bit |= (bayer_tab[(b<<3)|(i&7)]<(greysc(readBuff[(j<<3)+b])>>2))<<(7-b);
writeBuff[j]=buff8bit;
for(int floi=0;floi<fw;floi++) floyd_tab[i&1][floi]=0;
}
#endif
sprbase.drawBitmap(x,y+i,writeBuff,spr.width(),1,1,0);
else{
for(int32_t j=0;j<((fw+7)>>3);j++){
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);
writeBuff[j]=buff8bit;
}
}
sprbase.drawBitmap(x,i,writeBuff,fw,1,1,0);
}
//_display((const uint8_t*)sprbase.getBuffer()); //显示
if(o==0 || o==3){
delete []readBuff;
delete []writeBuff;
delete [] floyd_tab[0] ;
delete [] floyd_tab[1] ;
}
_display((const uint8_t*)sprbase.getBuffer());
delete []readBuff;
delete []writeBuff;
#ifdef FLOYD_STEINBERG_DITHERING
delete [] floyd_tab[0] ;
delete [] floyd_tab[1] ;
#endif
}
//不支持的话使用单色抖动刷屏
void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y){
void readguyEpdBase::drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y,
uint16_t fw, uint16_t fh){
//Serial.println("drv_draw16grey fx");
const uint_fast16_t w = (spr.width()+7)>>3;
if((!w) || (!spr.height())) return;
uint16_t *readBuff = new uint16_t[spr.width()];
uint8_t *writeBuff = new uint8_t[w];
sprbase.fillRect(x,y,spr.width(),spr.height(),1);
if(!fw) fw=spr.width();
if(!fh) fh=spr.height();
if((!fw) || (!fh)) return;
readBuff = new uint16_t[spr.width()];
if(_quality&2){
floyd_tab[0] = new int16_t [fw];
floyd_tab[1] = new int16_t [fw];
}
writeBuff = new uint8_t[(fw+7)>>3];
sprbase.fillRect(x,y,fw,fh,1);
bool negativeOrder=(drv_supportGreyscaling()==-16);
drv_fullpart(0);
_display((const uint8_t*)sprbase.getBuffer()); //先对区域慢刷白屏确保颜色正确
//_display((const uint8_t*)sprbase.getBuffer()); //先对区域慢刷白屏确保颜色正确
drv_dispWriter(FILL_WHITE);
drv_fullpart(1);
for(uint_fast8_t k=1;k<16;k++){ //亮度为15的不用绘制,因为本来就是白色
for(int i=0;i<spr.height();i++){
spr.readRect(0,i,spr.width(),1,readBuff);
for(uint_fast16_t j=0;j<w;j++){
uint_fast8_t buff8bit=0;
for(uint_fast8_t b=0;b<8;b++){
//uint_fast8_t cg=greysc(readBuff[j*8+b])/16
uint_fast8_t cg=greysc(readBuff[(j<<3)+b])>>4;
if(negativeOrder)
buff8bit |= (cg<k)<<((~b)&7);
else{
if(cg==15) continue; //白色不考虑
buff8bit |= (cg>=((~k)&15))<<((~b)&7);
if(_quality&2) for(int j=0;j<fw;j++){ floyd_tab[0][j] = 0; floyd_tab[1][j] = 0; }
for(int i=y;i<(int32_t)fh+y;i++){
uint_fast8_t buff8bit=0;
spr.readRect(0,(i-y)*spr.height()/fh,spr.width(),1,readBuff);
for(int32_t j=0;j<fw;j++){
//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]);
int32_t fd = floyd_tab[i&1][j]+((gv<<8)|gv);
while(fd>=0x800) {
cg++;
if(fd>=0) fd -= 0x1000;
}
if(fd<0) fd++;
if(j!=fw-1) { floyd_tab[i&1] [j+1] += (fd*7)>>4; }
if(j) { floyd_tab[!(i&1)][j-1] += (fd*3)>>4; }
{ floyd_tab[!(i&1)][j ] += (fd*5)>>4; }
if(j!=fw-1) { floyd_tab[!(i&1)][j+1] += (fd )>>4; }
}
//sprbase.drawPixel(x+j,y+i,(greysc(readBuff[j])/16)==(15-k));
writeBuff[j]=buff8bit^0xff;
else{ cg=greysc(readBuff[j*spr.width()/fw])>>4; }
//uint_fast8_t cg=greysc(readBuff[j*spr.width()/fw])>>4;
if(negativeOrder)
buff8bit |= (cg<k)<<((~j)&7);
else{
if(cg<15) //白色不考虑
buff8bit |= (cg>=((~k)&15))<<((~j)&7);
}
if((j&7)==7 || j==((int32_t)fw-1)){
writeBuff[j>>3]=buff8bit^0xff;
buff8bit=0;
}
//}
//sprbase.drawPixel(x+j,i,(greysc(readBuff[j*spr.width()/fw])/16)==(15-k));
}
sprbase.drawBitmap(x,y+i,writeBuff,spr.width(),1,1,0);
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);
}
drv_draw16grey_step((const uint8_t*)sprbase.getBuffer(),k); //使用灰度显示函数
}
delete []readBuff;
delete []writeBuff;
if(_quality&2){
delete [] floyd_tab[0] ;
delete [] floyd_tab[1] ;
}
} /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */

View File

@@ -38,6 +38,8 @@
#include "guy_epaper_config.h"
#define EPD_DRIVERS_NUM_MAX 12 //此选项请不要取消注释掉
#define FILL_WHITE ([](int)->uint8_t{return 0xff;})
#define FILL_BLACK ([](int)->uint8_t{return 0x00;})
class readguyEpdBase {
protected:
SPIClass *_spi;
@@ -47,24 +49,31 @@ protected:
int8_t CS_PIN ;
int8_t BUSY_PIN;
uint8_t in_trans=0;
uint8_t _quality=0; //灰度显示品质 0(默认)-高品质 1-低品质 部分屏幕支持高品质的连续刷灰度.
uint8_t _quality=2; //灰度显示品质 0(默认)-高品质 1-低品质 部分屏幕支持高品质的连续刷灰度.
#ifdef MEPD_DEBUG_WAVE
uint16_t dat_combo = 0; //dc引脚状态 0 command, 1 data
#endif
uint16_t *readBuff;// = new uint16_t[spr.width()];
uint8_t *writeBuff;// = new uint8_t[w];
int16_t *floyd_tab[2];
uint32_t lastRefresh;
public:
readguyEpdBase(void);
virtual ~readguyEpdBase(void);
int IfInit(SPIClass &c,int8_t cs,int8_t dc,int8_t rst,int8_t busy);
void DigitalWrite(int pin, int value);
int DigitalRead(int pin);
IRAM_ATTR void DigitalWrite(int pin, int value) { if(pin>=0) digitalWrite(pin, value); }
IRAM_ATTR int DigitalRead(int pin) { return (pin>=0)?digitalRead(pin):1; }
void DelayMs(unsigned int delaytime);
void BeginTransfer();
void EndTransfer();
void SpiTransfer(unsigned char data);
void SpiTransfer(unsigned char data){
if(in_trans)
_spi->transfer(data);
}
//basic I/O operation
void guy_epdCmd(unsigned char command);
void guy_epdParam(unsigned char data);
void guy_epdParam(unsigned char data); //发送数据, 注意此方式速度会比较慢 大量数据发送请直接用SpiTransfer()
void guy_epdBusy(int32_t maxt);
void Reset(uint32_t minTime = 20);
void SetMemory(); //集成了0x44 0x45 0x4e 0x4f指令的函数. 此函数用于设置墨水屏内存写入方式
@@ -72,9 +81,9 @@ public:
virtual int drv_ID() const =0; //返回驱动代号
virtual void drv_init()=0; //初始化屏幕
virtual void drv_fullpart(bool part)=0; //初始化慢刷功能
void _display(const uint8_t *d){ drv_dispWriter([&](int n)->uint8_t{return d[n];}); }
virtual void drv_dispWriter(std::function<uint8_t(int)>)=0; //按照显示函数刷新
void drv_color(uint8_t c){ drv_dispWriter([=](int)->uint8_t{return c;}); } //单色刷新
void _display(const uint8_t *d,uint8_t m=3){ drv_dispWriter([&](int n)->uint8_t{return d[n];},m); }
virtual void drv_dispWriter(std::function<uint8_t(int)>,uint8_t m=3)=0; //按照显示函数刷新
void drv_color(uint8_t c,uint8_t m=3){ drv_dispWriter([=](int)->uint8_t{return c;},m); } //单色刷新
virtual void drv_sleep() =0; //开始屏幕睡眠
virtual int drv_width() const=0; //返回显示区域宽度, 即使旋转了也不能影响此函数输出
virtual int drv_height()const=0; //返回显示区域高度, 即使旋转了也不能影响此函数输出
@@ -86,9 +95,11 @@ public:
* @param gamma_on 是否对灰度值进行gamma校正(速度慢)
* @return uint32_t 颜色的灰度值
*/
static int greysc(int got){ return (int)((got&0xf8)*299+(((got>>11)&0x1c)|((got<<5)&0xe0))*587+((got&0x1f00)>>5)*114)/978; }
void drv_drawImage(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y); //分步完成灰度刷新
void drv_draw16grey(LGFX_Sprite &sprbase,LGFX_Sprite &spr,uint16_t x,uint16_t y);//省内存方式
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);//省内存方式
void drv_draw16grey_step(const uint8_t *buf, int step){ //分步完成灰度刷新
drv_draw16grey_step([&](int n)->uint8_t{return buf[n];},step);
}
@@ -96,12 +107,15 @@ public:
drv_setDepth(step);
drv_dispWriter(f);
}
void setGreyQuality(bool q) { _quality=!q; } //设置灰度的渲染画质. 高画质模式在某些屏幕某些情况下可能表现不好.
void setGreyQuality(uint8_t q){_quality=q^3;} //设置灰度的渲染画质. 高画质模式在某些屏幕某些情况下可能表现不好.
void (*spi_tr_release)(void);
void (*spi_tr_press)(void);
friend class ReadguyDriver;
#ifdef MEPD_DEBUG_DISPLAY
friend class LGFX;
#endif
private:
int16_t guy_width=0,guy_height=0;
};
#endif /* END OF FILE. ReadGuy project.

View File

@@ -73,9 +73,11 @@
//#define _DEFINA_SD_CS_PIN 0
// * for NodeMcu ctg stack LCF board
#define WHITE_GAP 2
#ifdef ESP8266
#define DISPLAY_TYPE_ST7789_240320 //2.0寸的ST7789 IPS TFT模块
#define _DEFINA_IPS_CS_PIN 15
#define _DEFINA_IPS_CS_PIN 15 //lcdDebug是不支持自定义引脚的
#define _DEFINA_IPS_DC_PIN 5
#define _DEFINA_IPS_RST_PIN -1

View File

@@ -40,7 +40,9 @@ void drv::drv_init(){
void drv::drv_fullpart(bool part){
partMode=part;
}
void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
void drv::drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m){ //单色刷新
if(m&1){//stage 1
if(lastRefresh) drv_dispWriter(f,2);
uint16_t dat[8];
unsigned short xbits=(drv_width()+7)/8;
if(partMode==0){
@@ -54,12 +56,15 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
}
for(int j=0;j<drv_height();j++){
for(int i=0;i<xbits;i++){
if(f(j*xbits+i) == 0xff && i!=xbits-1)
uint_fast8_t readf=f(j*xbits+i);
if(readf == 0xff && i!=xbits-1)
ips.drawFastHLine(WHITE_GAP+i*8,WHITE_GAP+j,8,0xffff);
else {
int lineOK=0;
ips.readRect(WHITE_GAP+i*8,WHITE_GAP+j,8,1,dat); //注意这里 readrect函数已经自动化实现边界处理了
if(f(j*xbits+i) == 0x00 && i!=xbits-1){
if(partMode)//注意这里 readrect函数已经自动化实现边界处理了
ips.readRect(WHITE_GAP+i*8,WHITE_GAP+j,8,1,dat);
else memset(dat,0xff,sizeof(dat));
if(readf == 0x00 && i!=xbits-1){
for(int k=0;k<8;k++)
if((dat[k]&0x1f)==0x1f) lineOK++;
if(lineOK==8) {
@@ -69,17 +74,21 @@ void drv::drv_dispWriter(std::function<uint8_t(int)> f){ //单色刷新
}
for(int k=0;k<8;k++){
if(i==xbits-1 && i*8+k>=drv_width()) break;
if((f(j*xbits+i)&(0x80>>k)))
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));
}
}
}
yield();
}
//ips.readPixel(i,j);
DelayMs(150);
lastRefresh=millis();
}
if(m&2){//stage 2
uint32_t ms=millis()-lastRefresh;
if(ms<150) DelayMs(150-ms);
lastRefresh=0;
}
}
void drv::drv_sleep() {}
}

View File

@@ -34,7 +34,6 @@
#ifdef MEPD_DEBUG_DISPLAY
#include "ctg_stack_c_defines.h"
#define WHITE_GAP 4
namespace EpdLcdDebug{
class drv : public readguyEpdBase {
@@ -42,7 +41,7 @@ public:
int drv_ID() const { return MEPD_DEBUG_DISPLAY; }
void drv_init(); //初始化屏幕
void drv_fullpart(bool part); //切换慢刷/快刷功能
void drv_dispWriter(std::function<uint8_t(int)> f); //单色刷新
void drv_dispWriter(std::function<uint8_t(int)> f,uint8_t m=3); //单色刷新
void drv_sleep() ; //开始屏幕睡眠
int drv_width() const { return ips.width()-2*WHITE_GAP; } //返回显示区域宽度
int drv_height() const{ return ips.height()-2*WHITE_GAP; } //返回显示区域高度

View File

@@ -30,11 +30,20 @@
#ifndef _READGUY_VERSION_H_FILE
#define _READGUY_VERSION_H_FILE
//在进行版本更迭之前, 需要检查以下文件的版本号是否为最新
//1. ChangeLog.md
//2. library.json
//3. library.properities
//4. README.md
//5. git commit 信息
//6. 下面的三行 以及下面的这个字符串
//务必保证这些版本号是一致的.
//另外, 在提交新版本之前, 不要忘记在github上创建release, 否则Arduino IDE会读不到
#define READGUY_V_MAJOR 1
#define READGUY_V_MINOR 0
#define READGUY_V_PATCH 2
#define READGUY_V_MINOR 3
#define READGUY_V_PATCH 1
#define READGUY_VERSION_VAL (READGUY_V_MAJOR*1000+READGUY_V_MINOR*100+READGUY_V_PATCH*10)
#define READGUY_VERSION "1.0.2"
#define READGUY_VERSION "1.3.1"
#ifdef ESP8266
#define _READGUY_PLATFORM "ESP8266"
@@ -52,8 +61,8 @@
#endif
#endif
#define _GITHUB_LINK "https://github.com/fsender/readguy"
#define _BILIBILI_LINK "https://space.bilibili.com/180327370"
#define _GITHUB_LINK "github.com/fsender/readguy"
#define _BILIBILI_LINK "www.bilibili.com/video/BV1f94y187wz"
#endif /* END OF FILE. ReadGuy project.

File diff suppressed because it is too large Load Diff

View File

@@ -32,37 +32,35 @@
#define INPUT_PULLDOWN INPUT
#endif
guy_button readguy_driver::btn_rd[3];
int8_t readguy_driver::pin_cmx=-1;
guy_button ReadguyDriver::btn_rd[3];
int8_t ReadguyDriver::pin_cmx=-1;
const PROGMEM char readguy_driver::projname[8] = "readguy";
const PROGMEM char readguy_driver::tagname[9] = "hwconfig";
volatile uint8_t readguy_driver::spibz=0;
const PROGMEM char ReadguyDriver::projname[8] = "readguy";
const PROGMEM char ReadguyDriver::tagname[7] = "hwconf";
volatile uint8_t ReadguyDriver::spibz=0;
#ifndef ESP8266
SPIClass *readguy_driver::sd_spi =nullptr;
SPIClass *readguy_driver::epd_spi=nullptr;
TaskHandle_t readguy_driver::btn_handle;
SPIClass *ReadguyDriver::sd_spi =nullptr;
SPIClass *ReadguyDriver::epd_spi=nullptr;
TaskHandle_t ReadguyDriver::btn_handle;
#endif
readguy_driver::readguy_driver(){
ReadguyDriver::ReadguyDriver(){
READGUY_cali = 0; // config_data[0] 的初始值为0
READGUY_sd_ok = 0; //初始默认SD卡未成功初始化
READGUY_buttons = 0; //初始情况下没有按钮
}
uint8_t readguy_driver::init(){
} //WiFiSet: 是否保持AP服务器一直处于打开状态
uint8_t ReadguyDriver::init(uint8_t WiFiSet,bool initepd/* ,int g_width,int g_height */){
if(READGUY_cali==127) //已经初始化过了一次了, 为了防止里面一些volatile的东西出现问题....还是退出吧
return 0;
#ifdef DYNAMIC_PIN_SETTINGS
//char config_data[18];
nvs_init();
#if (!defined(INDEV_DEBUG))
if(!nvs_read()){ //如果NVS没有录入数据, 需要打开WiFiAP模式初始化录入引脚数据
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
//开启WiFi和服务器, 然后网页获取数据
//以下代码仅供测试
ap_setup();
server_setup();
ap_setup(); //开启WiFi和服务器, 然后网页获取数据. 如果保持WiFi AP模式打开, 则显示此标语.
if(WiFiSet) server_setup(F("引脚配置完成。请稍后访问<a href=\"/\">此页面</a>浏览更多内容。"));
else server_setup(F("引脚配置完成, WiFi即将关闭。")); //直接进入主页.
for(uint32_t i=UINT32_MAX;millis()<i;){
if(server_loop()){
if(i==UINT32_MAX) i=millis()+500;
@@ -71,16 +69,15 @@ uint8_t readguy_driver::init(){
}
//delay(300); //等待网页加载完再关掉WiFi. (有没有用存疑)
server_end();
WiFi.mode(WIFI_OFF);
if(!WiFiSet) WiFi.mode(WIFI_OFF);
fillScreen(1);
#endif
#if (!defined(INDEV_DEBUG))
}
else{ //看来NVS有数据, //从NVS加载数据, 哪怕前面的数据刚刚写入, 还没读取
//for(unsigned int i=0;i<sizeof(config_data);i++){
// Serial.printf_P(PSTR("data %u: %d\n"),i,config_data[i]);
//}
if(checkEpdDriver()!=127) setEpdDriver(); //初始化屏幕
if(WiFiSet>=2) WiFi.begin(); //连接到上次存储在flash NVS中的WiFi.
else if(WiFiSet==1) ap_setup();
if(checkEpdDriver()!=127) setEpdDriver(initepd/* ,g_width,g_height */); //初始化屏幕
else for(;;); //此处可能添加程序rollback等功能操作(比如返回加载上一个程序)
setSDcardDriver();
setButtonDriver();
@@ -88,23 +85,23 @@ uint8_t readguy_driver::init(){
#endif
nvs_deinit();
#else
if(checkEpdDriver()!=127) setEpdDriver(); //初始化屏幕
if(checkEpdDriver()!=127) setEpdDriver(initepd/* ,g_width,g_height */); //初始化屏幕
else for(;;); //此处可能添加程序rollback等功能操作(比如返回加载上一个程序)
setSDcardDriver();
setButtonDriver();
#endif
Serial.println(F("init done."));
Serial.println(F("[Guy init] init done."));
READGUY_cali=127;
return READGUY_sd_ok;
}
uint8_t readguy_driver::checkEpdDriver(){
uint8_t ReadguyDriver::checkEpdDriver(){
#ifdef DYNAMIC_PIN_SETTINGS
#ifdef ESP8266
#define TEST_ONLY_VALUE 5
#else
#define TEST_ONLY_VALUE 3
#endif
Serial.printf_P(PSTR("READGUY_shareSpi? %d\n"),READGUY_shareSpi);
Serial.printf_P(PSTR("[Guy SPI] shareSpi? %d\n"),READGUY_shareSpi);
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++)
@@ -152,10 +149,9 @@ uint8_t readguy_driver::checkEpdDriver(){
case READGUY_DEV_270B: guy_dev = new guydev_154B_270B_290B ::dev270B; break;
#endif
default:
Serial.println(F("[ERR] EPD DRIVER IC NOT SUPPORTED!\n"));
Serial.println(F("[GUY ERR] EPD DRIVER IC NOT SUPPORTED!\n"));
return 127;
}
// this calls the peripheral hardware interface, see epdif 初始化硬件SPI层(HAL层)
#if (defined(ESP8266))
SPI.begin();
SPI.setFrequency(ESP8266_SPI_FREQUENCY); ///< 1MHz
@@ -173,21 +169,26 @@ uint8_t readguy_driver::checkEpdDriver(){
epd_spi->begin(READGUY_epd_sclk,READGUY_shareSpi?READGUY_sd_miso:-1,READGUY_epd_mosi);
guy_dev->IfInit(*epd_spi, READGUY_epd_cs, READGUY_epd_dc, READGUY_epd_rst, READGUY_epd_busy);
#endif
Serial.println(F("IfInit OK"));
Serial.println(F("[Guy SPI] drvBase Init OK"));
return READGUY_epd_type;
}
void readguy_driver::setEpdDriver(){
void ReadguyDriver::setEpdDriver(bool initepd/* ,int g_width,int g_height */){
guy_dev->spi_tr_release = in_release;
guy_dev->spi_tr_press = in_press;
guy_dev->drv_init(); //初始化epd驱动层
Serial.println(F("EPD init OK"));
if(initepd) 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;
//else guy_height = guy_dev->drv_height();
//以下依赖于你的图形驱动
setColorDepth(1); //单色模式
createPalette(); //初始化颜色系统
Serial.printf_P(PSTR("mono set: w: %d, h: %d\n"),guy_dev->drv_width(),guy_dev->drv_height());
Serial.printf_P(PSTR("[Guy EPD] EPD init OK: w: %d, h: %d\n"),guy_dev->drv_width(),guy_dev->drv_height());
//创建画布. 根据LovyanGFX的特性, 如果以前有画布会自动重新生成新画布
//此外, 即使画布宽度不是8的倍数(如2.13寸单色),也支持自动补全8的倍数 ( 250x122 => 250x128 )
createSprite(guy_dev->drv_width(),guy_dev->drv_height());
//为了保证图片显示功能的正常使用, 高度也必须是8的倍数.
createSprite(guy_dev->drv_width(),(guy_dev->drv_height()+7)&0x7ffffff8);
//这里发现如果用自定义的内存分配方式会更好一些. 不会导致返回的height不对. 但是因为LovyanGFX库未更新 暂时不能这么用.
//setRotation(1); //旋转之后操作更方便
setRotation(0);
setFont(&fonts::Font0);
@@ -195,7 +196,7 @@ void readguy_driver::setEpdDriver(){
setTextColor(0);
fillScreen(1); //开始先全屏白色
}
bool readguy_driver::setSDcardDriver(){
bool ReadguyDriver::setSDcardDriver(){
/*重要信息: 有些引脚冲突是难以避免的, 比如8266 尤其需要重写这部分代码
对于esp32也要注意这个引脚是否是一个合法的引脚
对于esp8266真的要重写, 比如esp8266需要允许某些引脚是可以复用的
@@ -213,7 +214,6 @@ bool readguy_driver::setSDcardDriver(){
READGUY_sd_ok = SDFS.begin();
#else
if(sd_spi == nullptr) {
//sd_spi = new SPIClass(HSPI);
#if (defined(CONFIG_IDF_TARGET_ESP32))
sd_spi = new SPIClass(VSPI);
#else
@@ -226,6 +226,7 @@ bool readguy_driver::setSDcardDriver(){
}
else READGUY_sd_ok=0; //引脚不符合规则,或冲突或不可用
if(!READGUY_sd_ok){
Serial.println(F("[Guy SD] SD Init Failed!"));
//guyFS().begin(); //初始化内部FS
#ifdef READGUY_USE_LITTLEFS
LittleFS.begin();
@@ -235,7 +236,7 @@ bool readguy_driver::setSDcardDriver(){
}
return READGUY_sd_ok;
}
void readguy_driver::setButtonDriver(){
void ReadguyDriver::setButtonDriver(){
if(READGUY_btn1) { //初始化按键. 注意高电平触发的引脚在初始化时要设置为下拉
int8_t btn_pin=abs(READGUY_btn1)-1;
#if defined(ESP8266) //只有ESP8266是支持16引脚pulldown功能的, 而不支持pullup
@@ -339,7 +340,7 @@ void readguy_driver::setButtonDriver(){
}
} //关于按键策略, 我们在此使用多个Button2的类, 然后在一个task共享变量来确定上一个按键状态
}
fs::FS &readguy_driver::guyFS(uint8_t initSD){
fs::FS &ReadguyDriver::guyFS(uint8_t initSD){
if(initSD==2 || (!READGUY_sd_ok && initSD)) setSDcardDriver();
if(READGUY_sd_ok){
#ifdef ESP8266
@@ -354,7 +355,7 @@ fs::FS &readguy_driver::guyFS(uint8_t initSD){
return SPIFFS;
#endif
}
void readguy_driver::setBright(int d){
void ReadguyDriver::setBright(int d){
if(currentBright>=0 && d>=0 && d<=255){
currentBright=d;
#ifdef ESP8266
@@ -368,125 +369,150 @@ void readguy_driver::setBright(int d){
digitalWrite(READGUY_bl_pin,d?HIGH:LOW);
}
}
void readguy_driver::display(bool part){
void ReadguyDriver::display(uint8_t part){
//真的是我c++的盲区了啊....搜索了半天才找到可以这么玩的
//......可惜'dynamic_cast' not permitted with -fno-rtti
// static bool _part = 0; 记忆上次到底是full还是part, 注意启动时默认为full
if(READGUY_cali==127){
//in_press(); //暂停, 然后读取按键状态 spibz
guy_dev->drv_fullpart(part);
guy_dev->_display((const uint8_t*)getBuffer());
guy_dev->drv_fullpart(part&1);
guy_dev->_display((const uint8_t*)getBuffer(),((part>>1)?part>>1:3));
//in_release(); //恢复
}
}
void readguy_driver::display(std::function<uint8_t(int)> f, bool part){
void ReadguyDriver::display(const uint8_t *buf, uint8_t part){
if(READGUY_cali==127){
//in_press(); //暂停, 然后读取按键状态 spibz
guy_dev->drv_fullpart(part);
guy_dev->drv_dispWriter(f);
guy_dev->drv_fullpart(part&1);
guy_dev->_display(buf,((part>>1)?part>>1:3));
//in_release(); //恢复
}
}
void readguy_driver::drawImage(LGFX_Sprite &spr,uint16_t x,uint16_t y) {
if(READGUY_cali==127) guy_dev->drv_drawImage(*this, spr, x, y);
void ReadguyDriver::display(std::function<uint8_t(int)> f, uint8_t part){
if(READGUY_cali==127){
//in_press(); //暂停, 然后读取按键状态 spibz
guy_dev->drv_fullpart(part&1);
guy_dev->drv_dispWriter(f,((part>>1)?part>>1:3));
//in_release(); //恢复
}
}
void readguy_driver::setDepth(uint8_t d){
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::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) {
if(READGUY_cali!=127 || stage>=totalstage) return;
//Serial.printf("stage: %d/%d\n",stage+1,totalstage);
guy_dev->drv_drawImage(sprbase, spr, x, y, (totalstage<=1)?0:(stage==0?1:(stage==(totalstage-1)?3:2)),zoomw,zoomh);
}
void ReadguyDriver::setDepth(uint8_t d){
if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling()) guy_dev->drv_setDepth(d);
}
void readguy_driver::draw16grey(LGFX_Sprite &spr,uint16_t x,uint16_t y){
void ReadguyDriver::draw16grey(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint16_t zoomw,uint16_t zoomh){
if(READGUY_cali!=127) return;
if(guy_dev->drv_supportGreyscaling() && (spr.getColorDepth()&0xff)>1)
return guy_dev->drv_draw16grey(*this,spr,x,y);
guy_dev->drv_drawImage(*this, spr, x, y);
return guy_dev->drv_draw16grey(*this,spr,x,y,zoomw,zoomh);
guy_dev->drv_drawImage(*this, spr, x, y, 0, zoomw, zoomh);
}
void readguy_driver::draw16greyStep(int step){
if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling())
return guy_dev->drv_draw16grey_step((const uint8_t *)this->getBuffer(),step);
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);
guy_dev->drv_draw16grey_step((const uint8_t *)this->getBuffer(),step);
}
}
void readguy_driver::draw16greyStep(std::function<uint8_t(int)> f, int step){
if(READGUY_cali==127 && guy_dev->drv_supportGreyscaling())
return guy_dev->drv_draw16grey_step(f,step);
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);
guy_dev->drv_draw16grey_step(f,step);
}
}
void readguy_driver::invertDisplay(){
void ReadguyDriver::invertDisplay(){
if(READGUY_cali==127){
const int pixels=((guy_dev->drv_width()+7)>>3)*guy_dev->drv_height();
for(int i=0;i<pixels;i++)
((uint8_t*)(getBuffer()))[i]=uint8_t(~(((uint8_t*)(getBuffer()))[i]));
}
}
void readguy_driver::sleepEPD(){
void ReadguyDriver::sleepEPD(){
if(READGUY_cali==127) guy_dev->drv_sleep();
}
#if (!defined(DYNAMIC_PIN_SETTINGS)) //do nothing here.
#elif (defined(INDEV_DEBUG))
void readguy_driver::nvs_init(){
void ReadguyDriver::nvs_init(){
}
void readguy_driver::nvs_deinit(){
void ReadguyDriver::nvs_deinit(){
}
bool readguy_driver::nvs_read(){
bool ReadguyDriver::nvs_read(){
return 1;
}
void readguy_driver::nvs_write(){
void ReadguyDriver::nvs_write(){
}
#elif (defined(ESP8266))
void readguy_driver::nvs_init(){
EEPROM.begin(128);
void ReadguyDriver::nvs_init(){
EEPROM.begin(32);
}
void readguy_driver::nvs_deinit(){
void ReadguyDriver::nvs_deinit(){
EEPROM.commit();
EEPROM.end();
}
bool readguy_driver::nvs_read(){
bool ReadguyDriver::nvs_read(){
char s[8];
for(unsigned int i=0;i<sizeof(config_data)+8;i++){
int8_t rd=(int8_t)EEPROM.read(100+i);
int8_t rd=(int8_t)EEPROM.read(2+i);
if(i>=8) config_data[i-8] = rd;
else s[i]=(char)rd;
}
Serial.printf("Get NVS...%d\n", config_data[0]);
Serial.printf("[Guy NVS] Get NVS...%d\n", config_data[0]);
return !(strcmp_P(s,projname));
}
void readguy_driver::nvs_write(){
void ReadguyDriver::nvs_write(){
for(unsigned int i=0;i<sizeof(config_data)+8;i++){
EEPROM.write(100+i,(uint8_t)(i<8?pgm_read_byte(projname+i):config_data[i-8]));
EEPROM.write(2+i,(uint8_t)(i<8?pgm_read_byte(projname+i):config_data[i-8]));
}
}
#else
void readguy_driver::nvs_init(){
void ReadguyDriver::nvs_init(){
nvsData.begin(projname); //初始化NVS
}
void readguy_driver::nvs_deinit(){
void ReadguyDriver::nvs_deinit(){
nvsData.end(); //用完NVS记得关闭, 省内存
}
bool readguy_driver::nvs_read(){
bool suc = nvsData.isKey(tagname);
if(suc) nvsData.getBytes(tagname,config_data,sizeof(config_data));
return suc;
bool ReadguyDriver::nvs_read(){ //此处需要处理一些有关I2C的内容
if(!nvsData.isKey(tagname)) return 0; //没有这个键值
size_t len=nvsData.getBytes(tagname,config_data,sizeof(config_data)); //读取的数据长度
/*if(len<sizeof(config_data)){ //旧版本格式无法获取I2C相关数据, 设置为-1.
for(int i=sizeof(config_data)-1;i>=15;i--) //使用新版本格式来存储相关数据
config_data[i]=config_data[i-2];
READGUY_i2c_sda=-1;
READGUY_i2c_scl=-1;
nvsData.putBytes(tagname,config_data,sizeof(config_data)); //用新版本格式保存
}*/
return len==sizeof(config_data);
}
void readguy_driver::nvs_write(){
void ReadguyDriver::nvs_write(){
if(nvsData.isKey(tagname)) nvsData.remove(tagname);
nvsData.putBytes(tagname,config_data,sizeof(config_data)); //正式写入NVS
}
#endif
uint8_t readguy_driver::getBtn_impl(){ //按钮不可用, 返回0.
uint8_t ReadguyDriver::getBtn_impl(){ //按钮不可用, 返回0.
uint8_t res1,res2,res3,res4=0;
switch(READGUY_buttons){
case 1:
res1=btn_rd[0].read();
if(res1 == 1) res4 |= 1;
else if(res1 == 2) res4 |= 2;
else if(res1 == 4) res4 |= 4;
else if(res1 == 3) res4 |= 8;
if(res1 == 1) res4 |= 1; //点按
else if(res1 == 2) res4 |= 2; //双击
else if(res1 == 4) res4 |= 4; //长按-确定
else if(res1 == 3) res4 |= 8; //三击-返回
break;
case 2:
res1=btn_rd[0].read();
res1=btn_rd[0].read(); //两个按钮引脚都读取
res2=btn_rd[1].read();
if(res1 == 1) res4 |= 1;
else if(res1 == 4) res4 |= 2;
if(res2 == 1) res4 |= 4;
else if(res2 == 4) res4 |= 8;
if(res1 == 1) res4 |= 1; //左键点按-向下翻页
else if(res1 == 4) res4 |= 2; //左键长按-向上翻页
if(res2 == 1) res4 |= 4; //右键点按-确定
else if(res2 == 4) res4 |= 8; //右键长按-返回
break;
case 3:
res1=btn_rd[0].read();
@@ -500,20 +526,20 @@ uint8_t readguy_driver::getBtn_impl(){ //按钮不可用, 返回0.
}
return res4;
}
void readguy_driver::looptask(){ //均为类内静态数据
void ReadguyDriver::looptask(){ //均为类内静态数据
btn_rd[0].loop();
btn_rd[1].loop();
btn_rd[2].loop();
}
uint8_t readguy_driver::rd_btn_f(uint8_t btn){
uint8_t ReadguyDriver::rd_btn_f(uint8_t btn){
static uint8_t lstate=0; //上次从dc引脚读到的电平
#ifdef ESP8266
if(btn==readguy_driver::pin_cmx && spibz) return lstate;
if(btn==D5||btn==D6||btn==D7||btn==readguy_driver::pin_cmx)
if(btn==ReadguyDriver::pin_cmx && spibz) return lstate;
if(btn==D5||btn==D6||btn==D7||btn==ReadguyDriver::pin_cmx)
pinMode(btn,INPUT_PULLUP);//针对那些复用引脚做出的优化
uint8_t readb = digitalRead(btn);
if(btn==readguy_driver::pin_cmx) {
if(btn==ReadguyDriver::pin_cmx) {
//Serial.printf("rd D1.. %d\n",spibz);
pinMode(btn,OUTPUT); //如果有复用引脚, 它们的一部分默认需要保持输出状态, 比如复用的DC引脚
digitalWrite(btn,HIGH); //这些引脚的默认电平都是高电平
@@ -522,7 +548,7 @@ uint8_t readguy_driver::rd_btn_f(uint8_t btn){
else if(btn==D5||btn==D6||btn==D7) pinMode(btn,SPECIAL); //针对SPI引脚进行专门的优化
return readb;
#else //ESP32不再允许SPI相关引脚复用
if(btn!=readguy_driver::pin_cmx)
if(btn!=ReadguyDriver::pin_cmx)
return digitalRead(btn);
if(spibz) return lstate;
pinMode(btn,INPUT_PULLUP);

View File

@@ -36,7 +36,6 @@
#include <SPI.h>
#include <FS.h>
//#include <esp-fs-webserver.h>
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
@@ -120,31 +119,52 @@
#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_i2c_sda (config_data[13])// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
#define READGUY_i2c_scl (config_data[14])// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
//注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发
#define READGUY_btn1 (config_data[13])
#define READGUY_btn2 (config_data[14])
#define READGUY_btn3 (config_data[15])
#define READGUY_bl_pin (config_data[16])//前置光接口引脚IO
#define READGUY_rtc_type (config_data[17])//使用的RTC型号(待定, 还没用上)
#define READGUY_sd_ok (config_data[18]) //SD卡已经成功初始化
#define READGUY_buttons (config_data[19]) //按钮个数, 0-3都有可能
#define READGUY_btn1 (config_data[15])
#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_sd_ok (config_data[20]) //SD卡已经成功初始化
#define READGUY_buttons (config_data[21]) //按钮个数, 0-3都有可能
#endif
class readguy_driver: public LGFX_Sprite{ // readguy 基础类
#define READGUY_SLOW 0
#define READGUY_FAST 1
#define READGUY_SLOW_START 2
#define READGUY_FAST_START 3
#define READGUY_SLOW_END 4
#define READGUY_FAST_END 5
class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
public:
readguy_driver();
#ifdef READGUY_ESP_ENABLE_WIFI
#ifdef ESP8266
typedef ESP8266WebServer ReadguyWebServer;
typedef ESP8266HTTPUpdateServer ReadguyUpdateServer;
#else
typedef WebServer ReadguyWebServer;
typedef HTTPUpdateServer ReadguyUpdateServer;
#endif
#endif
ReadguyDriver();
/** @brief 初始化readguy
* @param WiFiSet 是否保持AP模式关闭. 0:配网完成自动关WiFi, 1:需要手动调用 WiFi.mode(WIFI_OFF) 关闭WiFi.
* 2:自动连接到已存的WiFi, 但不等待连接成功
* @return SD卡是否就绪
*/
uint8_t init();
//LGFX_Sprite g() const { return gfx; }; //使用 gfx 绘制内容.
uint8_t init(uint8_t WiFiSet = 0,bool initepd = 1/* ,int g_width = 0,int g_height = 0 */);
/// @brief 设置显示亮度
void setBright(int d);
/// @brief 返回显示亮度
int getBright() const { return currentBright; }
/// @brief 刷新显示到屏幕上
void display(bool part = true);
void display(uint8_t part = READGUY_FAST);
/// @brief 刷新显示到屏幕上
void display(const uint8_t *buf, uint8_t part = READGUY_FAST);
/** @brief 刷新显示到屏幕上, 可以自定义读取指定位置像素的函数
* @param f 自定义的函数. 此函数将在读取像素并输出到墨水屏时被调用.
* 每次调用需要返回 "参数对应位置" 的8个像素的颜色信息(凑成一字节). 其中左侧应在高位,右侧应在低位.
@@ -159,23 +179,30 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
* @endcode
* 该函数会将参数从0开始,每次逐渐增加1的顺序来被调用. 即先调用f(0),再f(1),f(2),f(3)... 以此类推.
*/
void display(std::function<uint8_t(int)> f, bool part = true);
/// @brief 显示图片, 使用抖动算法. 可以用省内存的方法显示
void drawImage(LGFX_Sprite &spr,uint16_t x,uint16_t y);
void display(std::function<uint8_t(int)> f, uint8_t part = READGUY_FAST);
/// @brief 显示图片, 使用抖动算法. 可以用省内存的方法显示, 可以缩放到指定的宽度和高度
void drawImage(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint16_t zoomw=0, uint16_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);
/// @brief 设置显示对比度(灰度)
void setDepth(uint8_t d);
/** @brief 返回目标屏幕是否支持16级灰度 返回非0代表支持.
* @note 返回负整数则代表调用draw16greyStep需要从深色到浅色刷新, 而不是从浅色到深色刷新 */
int supportGreyscaling() const{return READGUY_cali==127?guy_dev->drv_supportGreyscaling():0;}
/// @brief 设置灰度的渲染画质. 高画质模式在某些屏幕某些情况下可能表现不好.
void setGreyQuality(bool q) { if(READGUY_cali==127) guy_dev->setGreyQuality(q); }
/** @brief 设置灰度的渲染画质. 高画质模式在某些屏幕某些情况下可能表现不好.
* @param q 0-关闭连续刷屏 开启16阶灰度抖动 1-开启连续刷屏 开启16阶灰度抖动
* 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);
void draw16grey(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint16_t zoomw=0,uint16_t zoomh=0);
/** @brief 按照自定义分步显示灰度图片,如果支持,否则就不显示灰度图片了. 可以用省内存的方法显示
* @param step 步骤代号. 从1开始到15,依次调用此函数来自定义的灰度显示显存内容. 没有16.
* @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) 的注释.
@@ -187,29 +214,31 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
void sleepEPD(void);
/// @brief ap配网设置页面
typedef struct {
String event;
int method; //其实这里的method的类型应当为HTTPMethod, 但是为了兼容无wifi的功能, 使用了int格式
std::function<void()> func;
String linkname;
String event; //链接名称 事件URI
std::function<void(ReadguyWebServer*)> func; //触发时执行的函数
} serveFunc;
#ifdef READGUY_ESP_ENABLE_WIFI
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void ap_setup();
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void server_setup(const serveFunc *serveFuncs = nullptr, int funcs = 0);
void server_setup(const String &notify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0);
bool server_loop();
void server_end() { sv.stop(); MDNS.end(); }
void server_end();
#else
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void ap_setup(){}
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void server_setup(const serveFunc *serveFuncs = nullptr, int funcs = 0){}
/// @brief 初始化服务器模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void server_setup(const String &notify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0){}
bool server_loop(){ return true; }
void server_end(){}
#endif
/// @brief 检查初始化屏幕硬件, 若检查失败返回0,否则返回硬件代号
uint8_t checkEpdDriver();
/// @brief 初始化屏幕, 设置驱动代号, 引脚排列顺序. 过程会检验引脚可用性.
void setEpdDriver();
/** @brief 初始化屏幕, 设置驱动代号, 引脚排列顺序. 过程会检验引脚可用性.
* @param g_width, g_height 显示区域的宽度和高度. 为0表示直接使用屏幕的宽度和高度
* @note 这两个参数转专为指定分辨率的程序画面设计, 其他分辨率的画面会自动拉伸. [1.2新增] */
void setEpdDriver(bool initepd = 1/* ,int g_width = 0,int g_height = 0 */);
/** @brief 初始化SD卡, 设置驱动代号, 引脚排列顺序. 过程会检验引脚可用性.
* @return SD卡初始化成功与否 */
bool setSDcardDriver();
@@ -229,10 +258,10 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
//以下是支持的所有屏幕型号 Add devices here!
//添加屏幕驱动范例: 直接添加对应屏幕的类就可以用了
static const char projname[8];
static const char tagname[9];
//数据是否已经校准
#ifdef DYNAMIC_PIN_SETTINGS
int8_t config_data[20];
static const char tagname[7];
//uint8_t config_wifi=0; //是否强行在初始化期间设置WiFi.
#ifdef DYNAMIC_PIN_SETTINGS//数据是否已经校准
int8_t config_data[22];
char randomch[4]; //校验用字符串
void nvs_init(); //初始化持久存储器.
void nvs_deinit();//保存持久存储器的内容
@@ -245,7 +274,6 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
#endif
int epd_OK=0; //墨水屏可用
int currentBright = -3; //初始亮度
//.........敬请期待更多屏幕ic...........
//LGFX_Sprite gfx; // 图形引擎类指针, 可以用这个指针去操作屏幕缓冲区
readguyEpdBase *guy_dev = nullptr;
@@ -255,19 +283,9 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
//template <class T> void t_display(T t);
#if defined(ESP8266)
#ifdef READGUY_ESP_ENABLE_WIFI
ESP8266WebServer sv;
ESP8266HTTPUpdateServer httpUpdater;
String w_ssid;
String w_psk;
#endif
//对于esp8266, 需要注册到ticker
Ticker btnTask;
#else
#ifdef READGUY_ESP_ENABLE_WIFI
WebServer sv;
HTTPUpdateServer httpUpdater;
#endif
#ifdef DYNAMIC_PIN_SETTINGS
//NVS数据操作函数, 无NVS的使用EEProm的最后几个字节块
Preferences nvsData;
@@ -277,12 +295,18 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
static TaskHandle_t btn_handle;
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
ReadguyWebServer sv;
ReadguyUpdateServer httpUpdater;
String guy_notify=emptyString; //嵌入在网页中的自定义标语
int sfuncs=-1;
String* sfnames=nullptr;
String* sfevents=nullptr;
void handleInit(); //服务器初始化系统(初次访问时, 跳转至引脚设定函数)
void handleInitPost(); //服务器响应初始化请求
void handlePinSetup(); //服务器-引脚设定函数
void handleFinal(); //服务器-校验屏幕是否正常
void handleFinalPost(); //服务器-校验屏幕是否正常回调函数
void handleWiFiPost(); //服务器-处理WiFi连接相关内容和API接口密钥功能
//void handleWiFi();//[已弃用]服务器-处理WiFi连接相关内容和API接口密钥功能
void handleNotFound(); //服务器-404响应
#endif
//按键驱动部分
@@ -295,28 +319,17 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
#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];
static const PROGMEM char html_header[]; //HTML头的数据. 省内存, 能省一点是一点
static const PROGMEM char index_cn_html[];
/*static const PROGMEM char index_cn_html1[];
static const PROGMEM char index_cn_html2[];
static const PROGMEM char index_cn_html3[];
static const PROGMEM char index_cn_html4[];
static const PROGMEM char index_cn_html5[];
static const PROGMEM char index_cn_html6[];
static const PROGMEM char index_cn_html7[];
static const PROGMEM char index_cn_html8[];
static const PROGMEM char index_cn_html9[];
static const PROGMEM char index_cn_html10[];
static const PROGMEM char index_cn_html11[];
static const PROGMEM char index_cn_html12[];
static const PROGMEM char index_cn_html13[];
static const PROGMEM char index_cn_html14[];
static const PROGMEM char index_cn_html15[]; */
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 final_html[];
static const PROGMEM char final2_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];
#endif
@@ -335,6 +348,45 @@ class readguy_driver: public LGFX_Sprite{ // readguy 基础类
if(!spibz) epd_spi->endTransaction();
#endif
}
public: //增加了一些返回系统状态变量的函数, 它们是静态的, 而且不会对程序造成任何影响.
constexpr int getShareSpi() const { return config_data[1]; }
constexpr int getEpdType () const { return config_data[2]; } // 对应的epd驱动程序代号, -1为未指定
//显示驱动部分, 显示默认使用vspi (vspi也是默认SPI库的通道)
constexpr int getEpdMosi () const { return config_data[3]; } // 目标显示器的 MOSI 引脚
constexpr int getEpdSclk () const { return config_data[4]; } // 目标显示器的 SCLK 引脚
constexpr int getEpdCs () const { return config_data[5]; } // 目标显示器的 CS 引脚
constexpr int getEpdDc () const { return config_data[6]; } // 目标显示器的 DC 引脚
constexpr int getEpdRst () const { return config_data[7]; } // 目标显示器的 RST 引脚
constexpr int getEpdBusy () const { return config_data[8]; } // 目标显示器的 BUSY 引脚
//sd卡驱动部分, 默认使用hspi (sd卡建议用hspi)
constexpr int getSdMiso () const { return config_data[9]; } // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
constexpr int getSdMosi () const { return config_data[10]; }// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
constexpr int getSdSclk () const { return config_data[11]; }// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
constexpr int getSdCs () const { return config_data[12]; }// 目标sd卡的 CS 引脚.
constexpr int getI2cSda () const { return config_data[13]; }// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
constexpr int getI2cScl () const { return config_data[14]; }// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
//注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发
constexpr int getBtn1Pin () const { return config_data[15]; }
constexpr int getBtn2Pin () const { return config_data[16]; }
constexpr int getBtn3Pin () const { return config_data[17]; }
constexpr int getBlPin () const { return config_data[18]; } //前置光接口引脚IO
constexpr int getRtcType () const { return config_data[19]; } //使用的RTC型号(待定, 还没用上)
constexpr int getButtonsCount() const { return config_data[21]; } //按钮个数, 0-3都有可能
//constexpr int memWidth () const { return guy_width ; } //返回显存宽度(不是画幅宽度),不会随着画布旋转改变
//constexpr int memHeight () const { return guy_height ; } //返回显存高度(不是画幅高度),不会随着画布旋转改变
int drvWidth () const { return READGUY_cali==127?guy_dev->drv_width():0; } //返回显示屏硬件宽度(不是画幅宽度)
int drvHeight() const { return READGUY_cali==127?guy_dev->drv_height():0; } //返回显示屏硬件高度(不是画幅高度)
int width () const { return (getRotation()&1)?drvHeight():drvWidth(); }
int height() const { return (getRotation()&1)?drvWidth():drvHeight(); }
// private:
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);
};
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */