├── README.md ├── img ├── demo-mondstadt-yjsnpi.gif ├── fpc_cable_with_temt6000_welded_01.jpg ├── fpc_cable_with_temt6000_welded_02.jpg ├── liyue_asm.jpg ├── liyue_charger_ferrite_size.jpg ├── liyue_charger_install.jpg ├── liyue_charger_install_taped.jpg ├── liyue_full_circuit.jpg ├── liyue_glass_charger.jpg ├── liyue_glass_install.jpg ├── liyue_glass_install_taped.jpg ├── liyue_painting.jpg ├── liyue_sensor_taped.jpg ├── liyue_sensor_taped_Aside.jpg ├── liyue_v2.1_Qi_coil.jpg ├── liyue_v2.1_Qi_coil_with_ferrite_sheet.jpg ├── liyue_v2.1_Qi_coil_with_two_layers_ferrite_sheet_and_module.jpg ├── mondstadt_Geo_test.mp4 ├── mondstadt_bare_with_glass.jpg ├── mondstadt_bare_with_glass_and_glue.jpg ├── mondstadt_fpc_90degree.jpg ├── mondstadt_glass_glue.jpg ├── mondstadt_glass_glued_taped.jpg ├── mondstadt_main_painting.jpg ├── mondstadt_pcb_taped.jpg ├── mondstadt_pcb_welding_01.jpg ├── mondstadt_pcb_welding_02.jpg ├── mondstadt_pcb_welding_03.jpg ├── mondstadt_pcb_with_screen.jpg ├── mondstadt_sensor_Aside_glue_filled.jpg ├── mondstadt_sensor_Aside_glue_filled_focus.jpg ├── mondstadt_sensor_position.jpg └── mondstadt_sensor_position_taped.jpg ├── pcb ├── esp32_vision_v3.2_gc9a01 │ ├── Gerber_PCB_esp_vision_v3.2_gc9a01_rel.zip │ ├── README.md │ └── Schematic_esp32_vision_v3.2_gc9a01_rel.svg └── esp32_vision_v3.2_st7789_liyuev3 │ ├── BOM_PCB_esp32-vision-v3.2-liyue-v3_2022-02-08.csv │ ├── DFM_Report.pdf │ ├── Gerber_PCB_esp32-vision-v3.2-liyue-v3.zip │ ├── README.md │ └── Schematic_esp32_vision_v3.2_st7789_rel_2022-02-28.svg ├── src ├── ble_itag_demo │ └── ble_itag_demo.ino ├── vision_SPIFFS_GIF_video │ ├── Data │ │ └── loop.gif │ ├── README.md │ ├── gifdec.cpp │ ├── gifdec.h │ ├── medikarasenpai-240p-5s-15fps.gif │ └── vision_SPIFFS_GIF_video.ino └── vision_sdcard_mjpeg │ ├── MjpegClass.78b3749.h │ ├── MjpegClass.h │ ├── README.md │ ├── electro5.mjpeg │ ├── electro_unaligned_new │ ├── a_loop.mjpeg │ ├── a_setup.mjpeg │ ├── b_loop.mjpeg │ └── b_setup.mjpeg │ ├── html │ ├── edit.htm │ ├── index.htm │ └── ota.htm │ ├── hydro_unaligned │ ├── a_loop.mjpeg │ ├── a_setup.mjpeg │ ├── b_loop.mjpeg │ └── b_setup.mjpeg │ ├── vision_sdcard_mjpeg.ino │ └── vision_sdcard_mjpeg.no_ble.78b3749.ino └── stl ├── inazuma ├── README.md ├── inazuma-bigcap-v1.1.STL ├── inazuma-main-v1.1.STL └── inazuma-v1.0-bare-220401.STL ├── liyue ├── README.md ├── liyue-cap-v2.1.STL ├── liyue-main-v2.0.STL ├── liyue-v2.2-cap.STL └── liyue-v2.2-main.STL ├── mondstadt ├── README.md ├── mondstadt-cap-bigbattery-v2.3.STL ├── mondstadt-cap-v2.1.STL ├── mondstadt-cap-v2.3.STL ├── mondstadt-main-v2.0.STL └── mondstadt-main-v2.3.1.STL └── sumeru └── sumeru-v1.0.STL /README.md: -------------------------------------------------------------------------------- 1 | 本项目已停止更新,已停止任何形式的技术支持。但您仍然可以查看全部开源资料,并在遵守开源协议的情况下使用。 2 | 如果您遇到任何问题,可以学习那位的美德后自行解决,也可以在教令院找阿帽。阿帽不好听吗? 3 | 4 | --- 5 | 6 | # ESP32 神之眼 Genshin Impact Vision 7 | 8 | [![Inazuma_Electro_Demo](https://res.cloudinary.com/marcomontalbano/image/upload/v1656770471/video_to_markdown/images/youtube--uiCPUxYVVak-c05b58ac6eb4c4700831b2b3070cd403.jpg)](https://www.youtube.com/watch?v=uiCPUxYVVak "Inazuma_Electro_Demo") 9 | 10 | [Bilibili BV1Fr4y1z7yS: 下北泽元素力测试](https://www.bilibili.com/video/BV1Fr4y1z7yS) 11 | [Bilibili BV1ir4y1r7ZS: 给刻师傅做个神之眼](https://www.bilibili.com/video/BV1ir4y1r7ZS) 12 | [Bilibili BV1hS4y1D7fo:蒙德外壳+圆版 测试](https://www.bilibili.com/video/BV1hS4y1D7fo) 13 | [Bilibili BV1a34y1s7b5:稻妻外壳+圆版 测试](https://www.bilibili.com/video/BV1a34y1s7b5) 14 | [Bilibili BV13S4y1e7Yu:蓝牙切换视频测试](https://www.bilibili.com/video/BV13S4y1e7Yu) 15 | [Bilibili BV13S4y1e7Yu:须弥外壳](https://www.bilibili.com/video/BV1ma41197kk) 16 | 17 | 18 | **警告:如果你没有能够完全理解整个设计的能力,那就不推荐做。** 因为我也不知道又会遇到什么坑 19 | 20 | **这里不是 B 站 up [渣渣一块钱4个](https://space.bilibili.com/14958846) 的代码仓库**,别做错了。详情见下方 [消歧义补丁](https://github.com/libc0607/esp32-vision#%E6%B6%88%E6%AD%A7%E4%B9%89%E8%A1%A5%E4%B8%81) 一节 21 | 22 | 如果你是第一次尝试电子制作,或是不确定能否在缺乏教程的情况下自行完成以 0402 元件为主的中高密度 PCB 焊接及调试,那么推荐你从 [渣渣一块钱4个:电子版璃月神之眼制作教程](https://www.bilibili.com/video/BV1HS4y1b7tQ) 做起,他的教程十分详细,难度和成本都比较低,可以快速做出一个能用的成品。 23 | 24 | 另外我也有一个基于小渣渣的设计缩水的版本,使用他修改的外壳,基于 GPL 3.0 开源,对量产成本做了优化; 25 | 见 [libc0607/vision-c3-youth](https://github.com/libc0607/vision-c3-youth) 26 | 但我没有在量产,在卖的都不是我,所以我不是客服,遇到问题你自己看看源码( 27 | 28 | ## 我想自己做一个,且不怕翻车,该从哪开始? 29 | 但是真的很容易翻车,如果你真的想好了……。 30 | 你可以先看 [渣渣一块钱4个:电子版璃月神之眼制作教程](https://www.bilibili.com/video/BV1HS4y1b7tQ),大体流程是相同的 31 | 32 | - 参考 [PCB 设计](https://github.com/libc0607/esp32-vision#pcb-%E8%AE%BE%E8%AE%A1) 一节中的链接,在 LCEDA 中[导出 Gerber](https://docs.lceda.cn/cn/PCB/Gerber-Generate/index.html),送到嘉立创下单([PCB 下单指引](https://www.jlc.com/portal/server_guide_35092.html)) 33 | - 如果你对自己的焊接水平没有自信,需要 SMT,还需要[导出 BOM](https://docs.lceda.cn/cn/Schematic/Export-BOM/index.html) 以及[贴片坐标](https://docs.lceda.cn/cn/PCB/Export-Coordinate/index.html),参照[嘉立创的下单教程](https://www.jlc.com/portal/server_guide_35102.html)进行 SMT 下单 34 | - 拿到 PCB 后完成全部元件焊接,确保硬件没有问题 35 | - 在 [Arduino IDE](https://www.arduino.cc/en/software) 中装好 [arduino-esp32](https://github.com/espressif/arduino-esp32) 环境,并按照代码前的注释信息安装好其他依赖 36 | - 按照代码前的注释信息,编译并上传程序至板载的 ESP32 中,测试基本功能是否正常;你应该使用带有 EN 和 IO0 接口的 ESP32 下载器 37 | - 下载[外壳的 STL 文件](https://github.com/libc0607/esp32-vision/tree/main/stl),通过 3D 打印制作外壳;你也许会想要在 [三维猴](https://www.sanweihou.com/) 或 [未来工厂](https://www.wenext.cn/) 下单 38 | - 将打印好的外壳进行打磨及喷漆,可以参考 [渣渣一块钱4个:外壳喷漆教程](https://www.bilibili.com/video/BV1cY4y1a7CQ) 39 | - 按照对应[外壳文件夹](https://github.com/libc0607/esp32-vision/tree/main/stl)下的 README 进行外壳及整机的组装 40 | - 按照 [代码 README](https://github.com/libc0607/esp32-vision/tree/main/src/vision_sdcard_mjpeg) 下的指引,上传文件至内部存储中,并进行参数配置 41 | - Enjoy! 42 | 43 | 如果你想在 渣渣一块钱四个 的设计中,移植使用蓝牙 iTag 进行遥控的代码,这里有一个我整理出来的最小示例大概能帮到你:[ble_itag_demo](https://github.com/libc0607/esp32-vision/blob/main/src/ble_itag_demo/ble_itag_demo.ino) 44 | 45 | ## 硬件特性 46 | 以下描述的是 V3.3 版本的特性: 47 | - 1.54 寸 LCD(方版)或 1.28 寸 LCD (圆版) 48 | - ESP32-PICO-D4 核心,可以 Wi-Fi 上传 49 | - 内部显示的内容作为图片或者视频存在,可以在 SPIFFS 或 SD Nand 中存放图片/视频,理论上可以 128M ~ 8G 50 | - 内置 LIS2DW12 加速度传感器,支持敲击识别,重力感应功能觉得有点鸡肋就删了 51 | - 外壳预留 TEMT6000 环境光强度传感器位置,12-bit(大约) PWM 调光 52 | - 锂电池充电管理使用 CN3165,带有 NTC 保护,通过电阻配置来支持 4.2V/4.35V/4.4V 53 | - 圆版电池 R40350 3.85V/435mAh,方版电池 403035 3.7V/450mAh 54 | - 低功耗 DC-DC 降压 ETA3425,低电量时 100% 导通,即断电电压由锂电池保护板决定 55 | - 用了一个 SY6280AAC 让屏和 SD Nand 支持断电 56 | - 支持且仅支持无线充电,需要外接无线充电模块并将线圈贴在外壳上;配套使用 华为 GT2 Pro 的无线充电座即可充电,淘宝大概卖 29 57 | - 本体带有一个物理按键,暂时用于切换显示模式 58 | - 支持将 iTag 蓝牙防丢器作为按钮控制视频切换 59 | 60 | 本设计的整体思路是尽量省电,虽然 V3.3 增加了电池容量(…),但受体积限制和屏幕拖累,续航还是尿崩 61 | 你可以通过修改配置文件,在显示效果和续航间选择合适的平衡;详见 [vision_sdcard_mjpeg](https://github.com/libc0607/esp32-vision/tree/main/src/vision_sdcard_mjpeg) 62 | 63 | 由于 2022 年 6 月的某个时候嘉立创将 ESP32-PICO-D4 踢出了经济型 SMT,考虑到少量自制的可制造性,未来本设计很可能会因此做出一些调整;但 V3.3 已经是一个相对稳定的版本,如果你等不及了可以做或是自己修改。 64 | 根据目前(2022.7)的[信息](https://t.me/avocado_leaks/4980)来看,须弥也是圆的,我猜电路大概是还能用的(吧…?),所以应该不用再大改了 65 | 66 | ## PCB 设计 67 | PCB 圆版: 68 | V3.3: [OSHWHub: libc0607/esp32_vision_v3-3_gc9a01_rel](https://oshwhub.com/libc0607/esp32_vision_v3-3_gc9a01_rel) 69 | 70 | ![image](https://user-images.githubusercontent.com/8705034/158412090-078fbdf3-1522-4b3c-a5c7-30ed71a7bd47.png) 71 | 72 | PCB 方版: 73 | V3.3: [OSHWHub: libc0607/esp32_vision_v3-3_st7789_rel](https://oshwhub.com/libc0607/esp32_vision_v3-3_st7789_rel) 74 | 注:1.54 寸 ST7789 屏幕有很多厂家生产外形和引脚定义兼容的型号,但有一部分型号屏幕的侧面及背面漏光。 75 | 由于把漆喷到一点都不漏光这件事不是很有操作性,所以推荐选择不漏光的版本;你可能需要问问卖家 76 | 注2:屏幕可能存在色差,不同的屏色差不同,这个emmm就没什么低成本且可操作性强的解决方法,推荐把播放的视频用 AE 自己调个色 77 | 78 | ![image](https://user-images.githubusercontent.com/8705034/161293365-5aa8db52-e6ec-49e8-a091-8577e36fbef4.png) 79 | 80 | 81 | 关于本设计在 2022 年 6 月之前的物料成本,仅供参考:以每批次做 5 套为例,约为 120 ~ 140 元/套。 82 | 该价格:不含消耗品及工具等;不考虑良率;不含邮费;PCB 白嫖嘉立创;SMT 贴基础库; 83 | 其中由于屏幕价格,方版整体会比圆版便宜;整体数值可能随元器件价格波动(不过大概只会高吧x 84 | 懒人包:648做5个,做不出来再补一单328(?) 85 | 86 | 2022 年 7 月更新:由于 JLC 把 ESP32-PICO-D4 移出了经济版 SMT 可贴列表,单次贴片的工程费由原本的 50 元提高到了 150+50 元。(我这邮寄的还没用完呢你就涨价草( 87 | 除非你自己搞得定 ESP32-PICO-D4 的手工焊接,否则这会带来很高的成本提升。 88 | 89 | ## 软件 90 | 目前做了两版测试代码,可以实现基本功能,但都不算正式 Release。有待填坑; 91 | 92 | [vision_SPIFFS_GIF_video](https://github.com/libc0607/esp32-vision/tree/main/src/vision_SPIFFS_GIF_video): 不贴 SD Nand,仅使用 ESP32 内部的 SPIFFS 作为存储的版本 93 | [vision_sdcard_mjpeg](https://github.com/libc0607/esp32-vision/tree/main/src/vision_sdcard_mjpeg): 使用 SD Nand 作为存储,支持 Wi-Fi 上传,支持 OTA,支持蓝牙的版本 94 | 95 | vision_SPIFFS_GIF_video 为早期版本的产物,没有维护,不能保证运行在新版硬件上。 96 | 后续主要更新会集中在 vision_sdcard_mjpeg 上 97 | 98 | ## 外壳 99 | 由于本人非专业,只在大二上过一晚上的 SolidWorks 课,原始工程的 SLDPRT 已经变成屎山,本着要脸的原则没有放出。 100 | 但这里提供了 STL 格式的文件,可以直接拿去下单 3D 打印,想修改的话也可以逆向一下,或是使用如 C4D 等可以修改的软件自行修改。 101 | 102 | 另,现版本由于个人不算满意,可能会继续改动,持续到我删掉这句话为止;请自行考虑风险。 103 | 104 | [蒙德版外壳在这](https://github.com/libc0607/esp32-vision/tree/main/stl/mondstadt) 105 | 106 | [璃月版外壳在这](https://github.com/libc0607/esp32-vision/tree/main/stl/liyue) 107 | 108 | [稻妻的](https://github.com/libc0607/esp32-vision/tree/main/stl/inazuma) 109 | 110 | ![image](https://user-images.githubusercontent.com/8705034/155986652-94c0bdcc-bc52-475f-8d96-ee1dc8aed9e1.png) 111 | 112 | ![image](https://user-images.githubusercontent.com/8705034/155986832-7f6c0eb7-d1e6-46ee-b782-bc37fff176d0.png) 113 | 114 | ![image](https://user-images.githubusercontent.com/8705034/161298378-ef804b8e-395f-4212-8504-cf7b54752a0f.png) 115 | 116 | ## 参考 & 引用 117 | 两个软件例子都参考了 [moononournation/RGB565_video](https://github.com/moononournation/RGB565_video) 118 | 以及 DFRobot_LIS 的 tapInterrupt 示例、ESP32 Arduino 中的 SDWebServer 和 OTAWebUpdater 示例 119 | 蓝牙 iTag 部分参考自 [100-x-arduino.blogspot.com](http://100-x-arduino.blogspot.com/) 120 | 元素图标来自 [Bilibili: 鱼翅翅Kira](https://space.bilibili.com/2292091) 121 | 外壳的处理方法是从 @云梦泽创意空间 的群文件中学到的 122 | 野兽先辈忘了从哪搞来的了。。 123 | 如果还有遗漏请指出谢谢茄子 124 | 125 | ## 消歧义补丁 126 | **这里不是 B 站 up [渣渣一块钱4个](https://space.bilibili.com/14958846) 的代码仓库**,别做错了。 127 | 128 | 我的设计和他的设计是**两套独立的设计,代码不通用,硬件不通用,外壳不通用**,开源协议也不同,只是碰巧外观都是神之眼。。 129 | 如果你只是想要一颗挂件,被神明注视一下下,我推荐优先考虑他的设计,因为他的教程详细丰富,群里活人多,翻车严重的话还能买个他的核心板用; 130 | 我的这边只有 Github 这点儿文档,还总是不更新,代码还有 bug,做了会被神明注释掉的( 131 | 132 | ### A. [渣渣一块钱4个](https://space.bilibili.com/14958846) 的设计资料汇总 133 | 我这里仅作为参考,不对内容做保证,不保证完整性,以原作者为准,请自行检查是否有最新版。 134 | 别做错了,别做混了,别问我哪里和哪里能不能通用。。。 135 | 圆版 PCB:[OSHWHub: 神之眼挂件V1.1](https://oshwhub.com/Myzhazha/esp32_pico-d4-video) 136 | 方版 PCB:[OSHWHub: 璃月神之眼挂件](https://oshwhub.com/Myzhazha/li-yue-shen-zhi-yan-gua-jian) 137 | 方版教程:[渣渣一块钱4个:电子版璃月神之眼制作教程](https://www.bilibili.com/video/BV1HS4y1b7tQ) 138 | 方版效果:[渣渣一块钱4个:璃月电子版神之眼](https://www.bilibili.com/video/BV1DY4y1b7GN) 139 | 圆版教程:[渣渣一块钱4个:电子版神之眼(蒙德)开源说明](https://www.bilibili.com/read/cv15460352) 140 | 圆版安装:[渣渣一块钱4个:神之眼圆形核心板安装教程](https://www.bilibili.com/video/BV1KB4y1m7H2) 141 | 圆版效果:[渣渣一块钱4个:稻妻和蒙德电子版神之眼](https://www.bilibili.com/video/BV1sF411g7tc) 142 | 外壳下单:[渣渣一块钱4个:神之眼外壳3D打印平台打印教程](https://www.bilibili.com/read/cv16117622) 143 | 外壳喷漆:[渣渣一块钱4个:外壳喷漆教程](https://www.bilibili.com/video/BV1cY4y1a7CQ) 144 | 视频格式转换:[渣渣一块钱4个:使用FFmpeg转换mjpeg格式](https://www.bilibili.com/read/cv15713513) 145 | 146 | 他的 QQ 群:636426429 147 | 他后续会制作大尺寸(大约你家墙上86盒那么大)的神之眼,而我暂时没有计划,如果需要可以关注他。 148 | 149 | ### B. [我](https://space.bilibili.com/6509521) 的设计资料汇总 150 | ……就 全都在这 没别的了( 151 | 152 | ### 我的设计和他的设计的关系 153 | 154 | - 电路部分各做各的,不通用,不过也有相互参考 155 | - 程序部分各做各的,不通用 156 | - 他的蒙德外壳和璃月外壳基于我的修改,但不通用 157 | - 他卖核心板(不含外壳),我不对外卖,所以想买的去找他 158 | - 他的外壳为 CC BY-NC-SA 协议,其余部分为 GPL 协议;我的全部设计为 CC BY-NC-SA 协议 159 | 160 | 161 | 162 | ## 许可 163 | 本设计采用 [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0) 许可。(OSHWHub 上只能选 3.0 ..) 164 | 我不卖成品,也不以任何形式卖套件,没精力搞。。 165 | 但作为开源项目,你当然可以在遵守开源协议的情况下自己做一个玩;NC 是为了防止潜在的mhy法务部警告(?)。 166 | 如果你还是充满好奇,可以看看 [《原神同人周边大陆地区正式运行指引V1.2》](https://weibo.com/ttarticle/p/show?id=2309404707028085113324)(不保证这里链接是最新版,请自己确认) 167 | 168 | ## 免责声明 169 | 这东西仅为个人 DIY 项目,作者不保证所公开的内容绝对可用,也不可能解决所有出现的问题。 170 | 本设计的全部设计资料都已开源,这里的内容足够从零开始制作一个完全可用的成品,没有那种祖传的隐藏细节。 171 | 如果你对组装细节有不清楚的地方,可以联系我补全,但我只会直接补全到 Github 上,以便他人参考。 172 | 你应该考虑到所有可能存在的风险,包括但不限于各种可能的人身伤害、金钱损失和 Emotional Damage。 173 | 滥用本项目造成的一切负面效果与作者无关,最终解释权归作者所有。 174 | 175 | 说真的,不推荐做,又费钱又费时间又容易翻车,还容易因为自己太菜而受到精神打击( 176 | 177 | -------------------------------------------------------------------------------- /img/demo-mondstadt-yjsnpi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/demo-mondstadt-yjsnpi.gif -------------------------------------------------------------------------------- /img/fpc_cable_with_temt6000_welded_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/fpc_cable_with_temt6000_welded_01.jpg -------------------------------------------------------------------------------- /img/fpc_cable_with_temt6000_welded_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/fpc_cable_with_temt6000_welded_02.jpg -------------------------------------------------------------------------------- /img/liyue_asm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_asm.jpg -------------------------------------------------------------------------------- /img/liyue_charger_ferrite_size.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_charger_ferrite_size.jpg -------------------------------------------------------------------------------- /img/liyue_charger_install.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_charger_install.jpg -------------------------------------------------------------------------------- /img/liyue_charger_install_taped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_charger_install_taped.jpg -------------------------------------------------------------------------------- /img/liyue_full_circuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_full_circuit.jpg -------------------------------------------------------------------------------- /img/liyue_glass_charger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_glass_charger.jpg -------------------------------------------------------------------------------- /img/liyue_glass_install.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_glass_install.jpg -------------------------------------------------------------------------------- /img/liyue_glass_install_taped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_glass_install_taped.jpg -------------------------------------------------------------------------------- /img/liyue_painting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_painting.jpg -------------------------------------------------------------------------------- /img/liyue_sensor_taped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_sensor_taped.jpg -------------------------------------------------------------------------------- /img/liyue_sensor_taped_Aside.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_sensor_taped_Aside.jpg -------------------------------------------------------------------------------- /img/liyue_v2.1_Qi_coil.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_v2.1_Qi_coil.jpg -------------------------------------------------------------------------------- /img/liyue_v2.1_Qi_coil_with_ferrite_sheet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_v2.1_Qi_coil_with_ferrite_sheet.jpg -------------------------------------------------------------------------------- /img/liyue_v2.1_Qi_coil_with_two_layers_ferrite_sheet_and_module.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/liyue_v2.1_Qi_coil_with_two_layers_ferrite_sheet_and_module.jpg -------------------------------------------------------------------------------- /img/mondstadt_Geo_test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_Geo_test.mp4 -------------------------------------------------------------------------------- /img/mondstadt_bare_with_glass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_bare_with_glass.jpg -------------------------------------------------------------------------------- /img/mondstadt_bare_with_glass_and_glue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_bare_with_glass_and_glue.jpg -------------------------------------------------------------------------------- /img/mondstadt_fpc_90degree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_fpc_90degree.jpg -------------------------------------------------------------------------------- /img/mondstadt_glass_glue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_glass_glue.jpg -------------------------------------------------------------------------------- /img/mondstadt_glass_glued_taped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_glass_glued_taped.jpg -------------------------------------------------------------------------------- /img/mondstadt_main_painting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_main_painting.jpg -------------------------------------------------------------------------------- /img/mondstadt_pcb_taped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_pcb_taped.jpg -------------------------------------------------------------------------------- /img/mondstadt_pcb_welding_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_pcb_welding_01.jpg -------------------------------------------------------------------------------- /img/mondstadt_pcb_welding_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_pcb_welding_02.jpg -------------------------------------------------------------------------------- /img/mondstadt_pcb_welding_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_pcb_welding_03.jpg -------------------------------------------------------------------------------- /img/mondstadt_pcb_with_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_pcb_with_screen.jpg -------------------------------------------------------------------------------- /img/mondstadt_sensor_Aside_glue_filled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_sensor_Aside_glue_filled.jpg -------------------------------------------------------------------------------- /img/mondstadt_sensor_Aside_glue_filled_focus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_sensor_Aside_glue_filled_focus.jpg -------------------------------------------------------------------------------- /img/mondstadt_sensor_position.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_sensor_position.jpg -------------------------------------------------------------------------------- /img/mondstadt_sensor_position_taped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/img/mondstadt_sensor_position_taped.jpg -------------------------------------------------------------------------------- /pcb/esp32_vision_v3.2_gc9a01/Gerber_PCB_esp_vision_v3.2_gc9a01_rel.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/pcb/esp32_vision_v3.2_gc9a01/Gerber_PCB_esp_vision_v3.2_gc9a01_rel.zip -------------------------------------------------------------------------------- /pcb/esp32_vision_v3.2_gc9a01/README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | [OSHWHub: libc0607/esp32_vision_v3-2_gc9a01_rel](https://oshwhub.com/libc0607/esp32_vision_v3-2_gc9a01_rel) -------------------------------------------------------------------------------- /pcb/esp32_vision_v3.2_st7789_liyuev3/BOM_PCB_esp32-vision-v3.2-liyue-v3_2022-02-08.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/pcb/esp32_vision_v3.2_st7789_liyuev3/BOM_PCB_esp32-vision-v3.2-liyue-v3_2022-02-08.csv -------------------------------------------------------------------------------- /pcb/esp32_vision_v3.2_st7789_liyuev3/DFM_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/pcb/esp32_vision_v3.2_st7789_liyuev3/DFM_Report.pdf -------------------------------------------------------------------------------- /pcb/esp32_vision_v3.2_st7789_liyuev3/Gerber_PCB_esp32-vision-v3.2-liyue-v3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/pcb/esp32_vision_v3.2_st7789_liyuev3/Gerber_PCB_esp32-vision-v3.2-liyue-v3.zip -------------------------------------------------------------------------------- /pcb/esp32_vision_v3.2_st7789_liyuev3/README.md: -------------------------------------------------------------------------------- 1 | [OSHWHub: libc0607/esp32_vision_v3-2_liyuev3_st7789_rel](https://oshwhub.com/libc0607/esp32_vision_v3-2_liyuev3_st7789_rel) 2 | -------------------------------------------------------------------------------- /src/ble_itag_demo/ble_itag_demo.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple BLE iTag example 3 | * 4 | * tested on: 5 | * ESP32 Arduino V1.0.6 6 | * Widora AIR (ESP32) 7 | * 8 | * You should change pServerAddress to your iTag's MAC address 9 | * (nRF Connect may help) 10 | * 11 | * Original from http://100-x-arduino.blogspot.com/ 12 | * modified by @libc0607 13 | */ 14 | 15 | /* 16 | * 一个简单的将蓝牙 iTag 用作按键输入的示例 17 | * 每次按下 iTag 上的按钮,都会在串口打印 FreeHeap 18 | * 19 | * 在 20 | * 软件:ESP32 Arduino V1.0.6 21 | * 硬件:Widora AIR (ESP32) 22 | * 上测试通过 23 | * 24 | * 需要根据你的硬件修改 iTag 的蓝牙 MAC 地址 pServerAddress 25 | * (可以用 nRF Connect 查看 26 | * 按下按钮后代码会执行到 notifyCallback 中 27 | * 蓝牙连接状态由 ble_task_loop 任务维护 28 | * 29 | * 大部分参考自 http://100-x-arduino.blogspot.com/ 30 | * 做了一点点修补,瞎改,混乱 31 | */ 32 | 33 | #include "BLEDevice.h" 34 | 35 | // config 36 | static BLEAddress *pServerAddress = new BLEAddress("ff:ff:10:0c:e6:49"); 37 | 38 | static BLEUUID serviceUUID("0000ffe0-0000-1000-8000-00805f9b34fb"); 39 | static BLEUUID charUUID("0000ffe1-0000-1000-8000-00805f9b34fb"); 40 | static bool ble_doconnect = false; 41 | static bool ble_connected = false; 42 | static BLERemoteCharacteristic* pRemoteCharacteristic; 43 | static BLEClient* pClient; 44 | static bool deviceBleConnected = false; 45 | TaskHandle_t BLE_TaskHandle; 46 | 47 | class MyClientCallbacks: public BLEClientCallbacks { 48 | void onConnect(BLEClient *pClient) { 49 | deviceBleConnected = true; 50 | Serial.println("BLE: connected to my server"); 51 | }; 52 | void onDisconnect(BLEClient *pClient) { 53 | pClient->disconnect(); 54 | deviceBleConnected = false; 55 | Serial.println("BLE: disconnected from my server"); 56 | ble_connected = false; 57 | } 58 | }; 59 | 60 | MyClientCallbacks* callbacks = new MyClientCallbacks(); 61 | 62 | static void notifyCallback( 63 | BLERemoteCharacteristic* pBLERemoteCharacteristic, 64 | uint8_t* pData, size_t length, bool isNotify) { 65 | Serial.print("BLE: Notify from iTag, FreeHeap = "); Serial.println(ESP.getFreeHeap()); 66 | } 67 | 68 | bool connectToServer(BLEAddress pAddress) { 69 | if (pClient != nullptr) { 70 | delete(pClient); 71 | } 72 | pClient = BLEDevice::createClient(); 73 | pClient->setClientCallbacks(callbacks); 74 | pClient->connect(pAddress); 75 | if (!pClient->isConnected()) { 76 | return false; 77 | } 78 | BLERemoteService* pRemoteService = pClient->getService(serviceUUID); 79 | if (pRemoteService == nullptr) { 80 | Serial.print("BLE: Failed to find our service UUID"); 81 | return false; 82 | } 83 | Serial.println("BLE: " + String(pRemoteService->toString().c_str())); 84 | 85 | pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); 86 | if (pRemoteCharacteristic == nullptr) { 87 | Serial.println("BLE: Failed to find our characteristic UUID"); 88 | return false; 89 | } 90 | Serial.println("BLE: " + String(pRemoteCharacteristic->toString().c_str())); 91 | 92 | const uint8_t notificationOn[] = {0x1, 0x0}; 93 | pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); 94 | pRemoteCharacteristic->registerForNotify(notifyCallback); 95 | Serial.println("BLE: Notification ON"); 96 | return true; 97 | } 98 | 99 | void ble_task_loop(void * par) { 100 | ble_connected = false; 101 | deviceBleConnected = false; 102 | String newValue = "T"; // i don't know why, but it works 103 | while(1) { 104 | if (ble_connected == false) { 105 | delay(500); // wait for iTag init 106 | if (connectToServer(*pServerAddress)) { 107 | ble_connected = true; 108 | Serial.println("BLE: Server UP"); 109 | } else { 110 | Serial.println("BLE: Server DOWN"); 111 | deviceBleConnected = false; 112 | } 113 | } 114 | if (deviceBleConnected) { 115 | pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); 116 | } 117 | delay(500); 118 | } 119 | } 120 | 121 | void setup() { 122 | Serial.begin(115200); 123 | 124 | ble_connected = false; 125 | deviceBleConnected = false; 126 | BLEDevice::init(""); 127 | xTaskCreatePinnedToCore( 128 | ble_task_loop, "BLE_task", 129 | 8192, NULL, 1, 130 | &BLE_TaskHandle, 0 131 | ); 132 | } 133 | 134 | 135 | void loop() { 136 | delay(500); // go on other tasks 137 | } 138 | -------------------------------------------------------------------------------- /src/vision_SPIFFS_GIF_video/Data/loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_SPIFFS_GIF_video/Data/loop.gif -------------------------------------------------------------------------------- /src/vision_SPIFFS_GIF_video/README.md: -------------------------------------------------------------------------------- 1 | # vision_SPIFFS_GIF_video 2 | 这里是 ESP32 Vision 的测试代码之一。 3 | 如果你只是想要一个可以简单地显示元素图标或者野兽仙贝,且不需要经常更改显示内容的钥匙链,那这个 Demo 适合你。 4 | 5 | ## 测试效果 6 | ![image](https://user-images.githubusercontent.com/8705034/155977538-5bb3ef90-7baa-4e10-b06e-492b431bec85.png) 7 | 8 | 见 [Bilibili BV1Fr4y1z7yS: 下北泽元素力测试 - bcccc23333](https://www.bilibili.com/video/BV1Fr4y1z7yS) 9 | 10 | ## 硬件 11 | 目前版本的代码可配合 [方版(ST7789)](https://oshwhub.com/libc0607/esp32_vision_v3-2_liyuev3_st7789_rel) 及 [圆版(GC9A01)](https://oshwhub.com/libc0607/esp32_vision_v3-2_gc9a01_rel) 硬件版本 V3.2 使用。 12 | 13 | 这个例子没有使用到板上的 SD Nand 和那个 0402 封装的 NTC,如果你想用这个例子那就可以不焊。(SD Nand 一片也十多块呢x) 14 | 这个例子通过修改代码的方式同时支持 方版(ST7789)和 圆版(GC9A01),修改方式见下文。 15 | 16 | ## 功能 17 | - 上电后从 SPIFFS 读取 GIF 图片 18 | - 播放完成后进入深度睡眠 19 | - 通过双击唤醒或是定时器唤醒(代码中默认 30s),再播放一次 GIF 图片,如此循环 20 | - 如果电量低,它就不亮了 (?废话) 21 | - 每次唤醒时根据环境光强度调节屏幕亮度 22 | 23 | ## 编译 & 上传 24 | 25 | 1. 安装 Arduino 26 | 2. 安装 [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) (仅在 V1.0.6 下测试过;高版本和下面某个库的兼容不好) 27 | 3. 安装 [moononournation/Arduino_GFX](https://github.com/moononournation/Arduino_GFX) 28 | 4. 安装 [bitbank2/JPEGDEC](https://github.com/bitbank2/JPEGDEC) 29 | 5. 安装 [DFRobot/DFRobot_LIS](https://github.com/DFRobot/DFRobot_LIS) 30 | 6. 安装 [me-no-dev/arduino-esp32fs-plugin](https://github.com/me-no-dev/arduino-esp32fs-plugin) 31 | 7. 下载这坨源码,打开 32 | 8. 选择:ESP32 Dev Module, 240MHz CPU, 80MHz Flash, DIO, 4MB(32Mb), No OTA (1M APP/3M SPIFFS), PSRAM Disable 33 | 9. 上传 34 | 10. 将你需要的 GIF 文件通过 ffmpeg 或是其他的什么方式转为 240x240 @15fps,命名为 loop.gif ,放入 Data 文件夹中;这里也提供了两个示例 GIF 35 | 11. "ESP32 Sketch Data upload" 写入 SPIFFS 36 | 12. 重启,你就获得了一颗下北泽神之眼 37 | 38 | ## 自定义配置 39 | 代码中有几个可以配置的地方: 40 | ``` 41 | #define GIF_FILENAME "/loop.gif" // SPIFFS 中的动图名,要以 / 开头 42 | 43 | 44 | #define DEEP_SLEEP_TIME_S 30 // 深度睡眠后,定时器唤醒的时间,单位是秒 45 | 46 | 47 | #define BAT_ADC_THRESH_LOW 1700 // 低电压保护。当每一轮工作开始时,如果 ADC 读取到的电压读数低于这个值,就不会播放 GIF,直接进入下一轮睡眠 48 | // 由于 ESP32 的 ADC 质量不咋样,所以没有换算成电压。你可以根据自己板子的情况尝试调节这个值。 49 | 50 | 51 | //Arduino_GC9A01 *gfx = new Arduino_GC9A01(bus, PIN_TFT_RST, 2, true); 52 | Arduino_ST7789 *gfx = new Arduino_ST7789(bus, PIN_TFT_RST, 0, true, 240, 240, 0, 0); 53 | // 在这里选择你的屏幕种类 54 | 55 | ``` 56 | ## 参考 57 | [moononournation/RGB565_video](https://github.com/moononournation/RGB565_video) 58 | 雷元素图标来自 [Bilibili: 鱼翅翅Kira](https://space.bilibili.com/2292091) 59 | 60 | ## 免责声明 61 | 这段代码仅用作测试,由于滥用造成的一切不好的后果和作者无关。 62 | 如果其中的图片素材涉及侵权,请联系我删除。 63 | -------------------------------------------------------------------------------- /src/vision_SPIFFS_GIF_video/gifdec.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * rewrite from: https://github.com/BasementCat/arduino-tft-gif 3 | */ 4 | #include "gifdec.h" 5 | #include 6 | #include 7 | 8 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 9 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 10 | 11 | int gif_buf_last_idx, gif_buf_idx, file_pos; 12 | uint8_t gif_buf[GIF_BUF_SIZE]; 13 | 14 | static bool gif_buf_seek(File *fd, int len) 15 | { 16 | if (len > (gif_buf_last_idx - gif_buf_idx)) 17 | { 18 | fd->seek(len - (gif_buf_last_idx - gif_buf_idx), SeekCur); 19 | gif_buf_idx = gif_buf_last_idx; 20 | } 21 | else 22 | { 23 | gif_buf_idx += len; 24 | } 25 | return true; 26 | } 27 | 28 | static int gif_buf_read(File *fd, uint8_t *dest, int len) 29 | { 30 | while (len--) 31 | { 32 | if (gif_buf_idx == gif_buf_last_idx) 33 | { 34 | gif_buf_last_idx = fd->read(gif_buf, GIF_BUF_SIZE); 35 | gif_buf_idx = 0; 36 | } 37 | 38 | file_pos++; 39 | *(dest++) = gif_buf[gif_buf_idx++]; 40 | } 41 | return len; 42 | } 43 | 44 | static uint8_t gif_buf_read(File *fd) 45 | { 46 | if (gif_buf_idx == gif_buf_last_idx) 47 | { 48 | gif_buf_last_idx = fd->read(gif_buf, GIF_BUF_SIZE); 49 | gif_buf_idx = 0; 50 | } 51 | 52 | file_pos++; 53 | return gif_buf[gif_buf_idx++]; 54 | } 55 | 56 | static uint16_t gif_buf_read16(File *fd) 57 | { 58 | return gif_buf_read(fd) + (((uint16_t)gif_buf_read(fd)) << 8); 59 | } 60 | 61 | static void read_palette(File *fd, gd_Palette *dest, int32_t num_colors) 62 | { 63 | uint8_t r, g, b; 64 | dest->size = num_colors; 65 | for (int32_t i = 0; i < num_colors; i++) 66 | { 67 | r = gif_buf_read(fd); 68 | g = gif_buf_read(fd); 69 | b = gif_buf_read(fd); 70 | dest->colors[i] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3); 71 | } 72 | } 73 | 74 | gd_GIF *gd_open_gif(File *fd) 75 | { 76 | uint8_t sigver[3]; 77 | uint16_t width, height, depth; 78 | uint8_t fdsz, bgidx, aspect; 79 | int32_t gct_sz; 80 | gd_GIF *gif; 81 | 82 | // init global variables 83 | gif_buf_last_idx = GIF_BUF_SIZE; 84 | gif_buf_idx = gif_buf_last_idx; // no buffer yet 85 | file_pos = 0; 86 | 87 | /* Header */ 88 | gif_buf_read(fd, sigver, 3); 89 | if (memcmp(sigver, "GIF", 3) != 0) 90 | { 91 | Serial.println(F("invalid signature")); 92 | return NULL; 93 | } 94 | /* Version */ 95 | gif_buf_read(fd, sigver, 3); 96 | if (memcmp(sigver, "89a", 3) != 0) 97 | { 98 | Serial.println(F("invalid version")); 99 | return NULL; 100 | } 101 | /* Width x Height */ 102 | width = gif_buf_read16(fd); 103 | height = gif_buf_read16(fd); 104 | /* FDSZ */ 105 | gif_buf_read(fd, &fdsz, 1); 106 | /* Presence of GCT */ 107 | if (!(fdsz & 0x80)) 108 | { 109 | Serial.println(F("no global color table")); 110 | return NULL; 111 | } 112 | /* Color Space's Depth */ 113 | depth = ((fdsz >> 4) & 7) + 1; 114 | /* Ignore Sort Flag. */ 115 | /* GCT Size */ 116 | gct_sz = 1 << ((fdsz & 0x07) + 1); 117 | /* Background Color Index */ 118 | gif_buf_read(fd, &bgidx, 1); 119 | /* Aspect Ratio */ 120 | gif_buf_read(fd, &aspect, 1); 121 | /* Create gd_GIF Structure. */ 122 | gif = (gd_GIF *)calloc(1, sizeof(*gif)); 123 | gif->fd = fd; 124 | gif->width = width; 125 | gif->height = height; 126 | gif->depth = depth; 127 | /* Read GCT */ 128 | read_palette(fd, &gif->gct, gct_sz); 129 | gif->palette = &gif->gct; 130 | gif->bgindex = bgidx; 131 | gif->anim_start = file_pos; // fd->position(); 132 | gif->table = new_table(); 133 | return gif; 134 | } 135 | 136 | static void discard_sub_blocks(gd_GIF *gif) 137 | { 138 | uint8_t size; 139 | 140 | do 141 | { 142 | gif_buf_read(gif->fd, &size, 1); 143 | gif_buf_seek(gif->fd, size); 144 | } while (size); 145 | } 146 | 147 | static void read_plain_text_ext(gd_GIF *gif) 148 | { 149 | if (gif->plain_text) 150 | { 151 | uint16_t tx, ty, tw, th; 152 | uint8_t cw, ch, fg, bg; 153 | off_t sub_block; 154 | gif_buf_seek(gif->fd, 1); /* block size = 12 */ 155 | tx = gif_buf_read16(gif->fd); 156 | ty = gif_buf_read16(gif->fd); 157 | tw = gif_buf_read16(gif->fd); 158 | th = gif_buf_read16(gif->fd); 159 | cw = gif_buf_read(gif->fd); 160 | ch = gif_buf_read(gif->fd); 161 | fg = gif_buf_read(gif->fd); 162 | bg = gif_buf_read(gif->fd); 163 | gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); 164 | } 165 | else 166 | { 167 | /* Discard plain text metadata. */ 168 | gif_buf_seek(gif->fd, 13); 169 | } 170 | /* Discard plain text sub-blocks. */ 171 | discard_sub_blocks(gif); 172 | } 173 | 174 | static void read_graphic_control_ext(gd_GIF *gif) 175 | { 176 | uint8_t rdit; 177 | 178 | /* Discard block size (always 0x04). */ 179 | gif_buf_seek(gif->fd, 1); 180 | gif_buf_read(gif->fd, &rdit, 1); 181 | gif->gce.disposal = (rdit >> 2) & 3; 182 | gif->gce.input = rdit & 2; 183 | gif->gce.transparency = rdit & 1; 184 | gif->gce.delay = gif_buf_read16(gif->fd); 185 | gif_buf_read(gif->fd, &gif->gce.tindex, 1); 186 | /* Skip block terminator. */ 187 | gif_buf_seek(gif->fd, 1); 188 | } 189 | 190 | static void read_comment_ext(gd_GIF *gif) 191 | { 192 | if (gif->comment) 193 | { 194 | gif->comment(gif); 195 | } 196 | /* Discard comment sub-blocks. */ 197 | discard_sub_blocks(gif); 198 | } 199 | 200 | static void read_application_ext(gd_GIF *gif) 201 | { 202 | char app_id[8]; 203 | char app_auth_code[3]; 204 | 205 | /* Discard block size (always 0x0B). */ 206 | gif_buf_seek(gif->fd, 1); 207 | /* Application Identifier. */ 208 | gif_buf_read(gif->fd, (uint8_t *)app_id, 8); 209 | /* Application Authentication Code. */ 210 | gif_buf_read(gif->fd, (uint8_t *)app_auth_code, 3); 211 | if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) 212 | { 213 | /* Discard block size (0x03) and constant byte (0x01). */ 214 | gif_buf_seek(gif->fd, 2); 215 | gif->loop_count = gif_buf_read16(gif->fd); 216 | /* Skip block terminator. */ 217 | gif_buf_seek(gif->fd, 1); 218 | } 219 | else if (gif->application) 220 | { 221 | gif->application(gif, app_id, app_auth_code); 222 | discard_sub_blocks(gif); 223 | } 224 | else 225 | { 226 | discard_sub_blocks(gif); 227 | } 228 | } 229 | 230 | static void read_ext(gd_GIF *gif) 231 | { 232 | uint8_t label; 233 | 234 | gif_buf_read(gif->fd, &label, 1); 235 | switch (label) 236 | { 237 | case 0x01: 238 | read_plain_text_ext(gif); 239 | break; 240 | case 0xF9: 241 | read_graphic_control_ext(gif); 242 | break; 243 | case 0xFE: 244 | read_comment_ext(gif); 245 | break; 246 | case 0xFF: 247 | read_application_ext(gif); 248 | break; 249 | default: 250 | Serial.print("unknown extension: "); 251 | Serial.println(label, HEX); 252 | } 253 | } 254 | 255 | static gd_Table *new_table() 256 | { 257 | // int key; 258 | // int init_bulk = MAX(1 << (key_size + 1), 0x100); 259 | // Table *table = (Table*) malloc(sizeof(*table) + sizeof(Entry) * init_bulk); 260 | // if (table) { 261 | // table->bulk = init_bulk; 262 | // table->nentries = (1 << key_size) + 2; 263 | // table->entries = (Entry *) &table[1]; 264 | // for (key = 0; key < (1 << key_size); key++) 265 | // table->entries[key] = (Entry) {1, 0xFFF, key}; 266 | // } 267 | // return table; 268 | int s = sizeof(gd_Table) + (sizeof(gd_Entry) * 4096); 269 | gd_Table *table = (gd_Table *)malloc(s); 270 | if (table) 271 | { 272 | Serial.printf("new_table() malloc %d.\n", s); 273 | } 274 | else 275 | { 276 | Serial.printf("new_table() malloc %d failed!\n", s); 277 | } 278 | table->entries = (gd_Entry *)&table[1]; 279 | return table; 280 | } 281 | 282 | static void reset_table(gd_Table *table, int32_t key_size) 283 | { 284 | table->nentries = (1 << key_size) + 2; 285 | for (int32_t key = 0; key < (1 << key_size); key++) 286 | { 287 | table->entries[key] = (gd_Entry){1, 0xFFF, key}; 288 | } 289 | } 290 | 291 | /* Add table entry. Return value: 292 | * 0 on success 293 | * +1 if key size must be incremented after this addition 294 | * -1 if could not realloc table */ 295 | static int32_t add_entry(gd_Table *table, int32_t length, uint16_t prefix, uint8_t suffix) 296 | { 297 | // Table *table = *tablep; 298 | // if (table->nentries == table->bulk) { 299 | // table->bulk *= 2; 300 | // table = (Table*) realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); 301 | // if (!table) return -1; 302 | // table->entries = (Entry *) &table[1]; 303 | // *tablep = table; 304 | // } 305 | table->entries[table->nentries] = (gd_Entry){length, prefix, suffix}; 306 | table->nentries++; 307 | if ((table->nentries & (table->nentries - 1)) == 0) 308 | return 1; 309 | return 0; 310 | } 311 | 312 | static uint16_t get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) 313 | { 314 | int bits_read; 315 | int rpad; 316 | int frag_size; 317 | uint16_t key; 318 | 319 | key = 0; 320 | for (bits_read = 0; bits_read < key_size; bits_read += frag_size) 321 | { 322 | rpad = (*shift + bits_read) % 8; 323 | if (rpad == 0) 324 | { 325 | /* Update byte. */ 326 | if (*sub_len == 0) 327 | gif_buf_read(gif->fd, sub_len, 1); /* Must be nonzero! */ 328 | gif_buf_read(gif->fd, byte, 1); 329 | (*sub_len)--; 330 | } 331 | frag_size = MIN(key_size - bits_read, 8 - rpad); 332 | key |= ((uint16_t)((*byte) >> rpad)) << bits_read; 333 | } 334 | /* Clear extra bits to the left. */ 335 | key &= (1 << key_size) - 1; 336 | *shift = (*shift + key_size) % 8; 337 | return key; 338 | } 339 | 340 | /* Compute output index of y-th input line, in frame of height h. */ 341 | static int interlaced_line_index(int h, int y) 342 | { 343 | int p; /* number of lines in current pass */ 344 | 345 | p = (h - 1) / 8 + 1; 346 | if (y < p) /* pass 1 */ 347 | return y * 8; 348 | y -= p; 349 | p = (h - 5) / 8 + 1; 350 | if (y < p) /* pass 2 */ 351 | return y * 8 + 4; 352 | y -= p; 353 | p = (h - 3) / 4 + 1; 354 | if (y < p) /* pass 3 */ 355 | return y * 4 + 2; 356 | y -= p; 357 | /* pass 4 */ 358 | return y * 2 + 1; 359 | } 360 | 361 | /* Decompress image pixels. 362 | * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ 363 | static int32_t read_image_data(gd_GIF *gif, int interlace, uint8_t *frame) 364 | { 365 | uint8_t sub_len, shift, byte; 366 | int init_key_size, key_size, table_is_full; 367 | int frm_off, str_len, p, x, y; 368 | uint16_t key, clear, stop; 369 | int32_t ret; 370 | gd_Entry entry; 371 | off_t start, end; 372 | 373 | // Serial.println("Read key size"); 374 | gif_buf_read(gif->fd, &byte, 1); 375 | key_size = (int)byte; 376 | // Serial.println("Set pos, discard sub blocks"); 377 | // start = gif->fd->position(); 378 | // discard_sub_blocks(gif); 379 | // end = gif->fd->position(); 380 | // gif_buf_seek(gif->fd, start, SeekSet); 381 | clear = 1 << key_size; 382 | stop = clear + 1; 383 | // Serial.println("New LZW table"); 384 | // table = new_table(key_size); 385 | reset_table(gif->table, key_size); 386 | key_size++; 387 | init_key_size = key_size; 388 | sub_len = shift = 0; 389 | // Serial.println("Get init key"); 390 | key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ 391 | frm_off = 0; 392 | ret = 0; 393 | while (1) 394 | { 395 | if (key == clear) 396 | { 397 | // Serial.println("Clear key, reset nentries"); 398 | key_size = init_key_size; 399 | gif->table->nentries = (1 << (key_size - 1)) + 2; 400 | table_is_full = 0; 401 | } 402 | else if (!table_is_full) 403 | { 404 | // Serial.println("Add entry to table"); 405 | ret = add_entry(gif->table, str_len + 1, key, entry.suffix); 406 | // if (ret == -1) { 407 | // // Serial.println("Table entry add failure"); 408 | // free(table); 409 | // return -1; 410 | // } 411 | if (gif->table->nentries == 0x1000) 412 | { 413 | // Serial.println("Table is full"); 414 | ret = 0; 415 | table_is_full = 1; 416 | } 417 | } 418 | // Serial.println("Get key"); 419 | key = get_key(gif, key_size, &sub_len, &shift, &byte); 420 | if (key == clear) 421 | continue; 422 | if (key == stop) 423 | break; 424 | if (ret == 1) 425 | key_size++; 426 | entry = gif->table->entries[key]; 427 | str_len = entry.length; 428 | uint8_t tindex = gif->gce.tindex; 429 | // Serial.println("Interpret key"); 430 | while (1) 431 | { 432 | p = frm_off + entry.length - 1; 433 | x = p % gif->fw; 434 | y = p / gif->fw; 435 | if (interlace) 436 | { 437 | y = interlaced_line_index((int)gif->fh, y); 438 | } 439 | if (tindex != entry.suffix) 440 | { 441 | frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; 442 | } 443 | if (entry.prefix == 0xFFF) 444 | break; 445 | else 446 | entry = gif->table->entries[entry.prefix]; 447 | } 448 | frm_off += str_len; 449 | if (key < gif->table->nentries - 1 && !table_is_full) 450 | gif->table->entries[gif->table->nentries - 1].suffix = entry.suffix; 451 | } 452 | // Serial.println("Done w/ img data, free table and seek to end"); 453 | // free(table); 454 | gif_buf_read(gif->fd, &sub_len, 1); /* Must be zero! */ 455 | // gif_buf_seek(gif->fd, end, SeekSet); 456 | return 0; 457 | } 458 | 459 | /* Read image. 460 | * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ 461 | static int32_t read_image(gd_GIF *gif, uint8_t *frame) 462 | { 463 | uint8_t fisrz; 464 | int interlace; 465 | 466 | /* Image Descriptor. */ 467 | // Serial.println("Read image descriptor"); 468 | gif->fx = gif_buf_read16(gif->fd); 469 | gif->fy = gif_buf_read16(gif->fd); 470 | gif->fw = gif_buf_read16(gif->fd); 471 | gif->fh = gif_buf_read16(gif->fd); 472 | // Serial.println("Read fisrz?"); 473 | gif_buf_read(gif->fd, &fisrz, 1); 474 | interlace = fisrz & 0x40; 475 | /* Ignore Sort Flag. */ 476 | /* Local Color Table? */ 477 | if (fisrz & 0x80) 478 | { 479 | /* Read LCT */ 480 | // Serial.println("Read LCT"); 481 | read_palette(gif->fd, &gif->lct, 1 << ((fisrz & 0x07) + 1)); 482 | gif->palette = &gif->lct; 483 | } 484 | else 485 | { 486 | gif->palette = &gif->gct; 487 | } 488 | /* Image Data. */ 489 | // Serial.println("Read image data"); 490 | return read_image_data(gif, interlace, frame); 491 | } 492 | 493 | static void render_frame_rect(gd_GIF *gif, uint16_t *buffer, uint8_t *frame) 494 | { 495 | int i, j, k; 496 | uint8_t index, *color; 497 | i = gif->fy * gif->width + gif->fx; 498 | for (j = 0; j < gif->fh; j++) 499 | { 500 | for (k = 0; k < gif->fw; k++) 501 | { 502 | index = frame[(gif->fy + j) * gif->width + gif->fx + k]; 503 | // color = &gif->palette->colors[index*2]; 504 | if (!gif->gce.transparency || index != gif->gce.tindex) 505 | buffer[(i + k)] = gif->palette->colors[index]; 506 | // memcpy(&buffer[(i+k)*2], color, 2); 507 | } 508 | i += gif->width; 509 | } 510 | } 511 | 512 | /* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ 513 | int32_t gd_get_frame(gd_GIF *gif, uint8_t *frame) 514 | { 515 | char sep; 516 | 517 | while (1) 518 | { 519 | gif_buf_read(gif->fd, (uint8_t *)&sep, 1); 520 | if (sep == 0) 521 | { 522 | gif_buf_read(gif->fd, (uint8_t *)&sep, 1); 523 | } 524 | if (sep == ',') 525 | { 526 | break; 527 | } 528 | if (sep == ';') 529 | { 530 | return 0; 531 | } 532 | if (sep == '!') 533 | { 534 | read_ext(gif); 535 | } 536 | else 537 | { 538 | Serial.printf("Read sep: [%d].\n", sep); 539 | return -1; 540 | } 541 | } 542 | // Serial.println("Do read image"); 543 | if (read_image(gif, frame) == -1) 544 | return -1; 545 | return 1; 546 | } 547 | 548 | void gd_rewind(gd_GIF *gif) 549 | { 550 | gif->fd->seek(gif->anim_start, SeekSet); 551 | file_pos = gif->anim_start; 552 | gif_buf_idx = gif_buf_last_idx; // reset buffer 553 | } 554 | 555 | void gd_close_gif(gd_GIF *gif) 556 | { 557 | gif->fd->close(); 558 | free(gif->table); 559 | free(gif); 560 | } 561 | -------------------------------------------------------------------------------- /src/vision_SPIFFS_GIF_video/gifdec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * rewrite from: https://github.com/BasementCat/arduino-tft-gif 3 | */ 4 | #ifndef _GIFDEC_H_ 5 | #define _GIFDEC_H_ 6 | 7 | #include 8 | 9 | #define GIF_BUF_SIZE 1024 10 | 11 | typedef struct gd_Palette { 12 | int size; 13 | uint16_t colors[256]; 14 | } gd_Palette; 15 | 16 | typedef struct gd_GCE { 17 | uint16_t delay; 18 | uint8_t tindex; 19 | uint8_t disposal; 20 | int input; 21 | int transparency; 22 | } gd_GCE; 23 | 24 | typedef struct gd_Entry { 25 | int32_t length; 26 | uint16_t prefix; 27 | uint8_t suffix; 28 | } gd_Entry; 29 | 30 | typedef struct gd_Table { 31 | int bulk; 32 | int nentries; 33 | gd_Entry *entries; 34 | } gd_Table; 35 | 36 | typedef struct gd_GIF { 37 | File* fd; 38 | off_t anim_start; 39 | uint16_t width, height; 40 | uint16_t depth; 41 | uint16_t loop_count; 42 | gd_GCE gce; 43 | gd_Palette *palette; 44 | gd_Palette lct, gct; 45 | void (*plain_text)( 46 | struct gd_GIF *gif, uint16_t tx, uint16_t ty, 47 | uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch, 48 | uint8_t fg, uint8_t bg 49 | ); 50 | void (*comment)(struct gd_GIF *gif); 51 | void (*application)(struct gd_GIF *gif, char id[8], char auth[3]); 52 | uint16_t fx, fy, fw, fh; 53 | uint8_t bgindex; 54 | gd_Table* table; 55 | } gd_GIF; 56 | 57 | gd_GIF *gd_open_gif(File* fd); 58 | static gd_Table * new_table(); 59 | static void reset_table(gd_Table* table, int key_size); 60 | // int32_t add_entry(gd_Table* table, int32_t length, uint16_t prefix, uint8_t suffix) 61 | int32_t gd_get_frame(gd_GIF *gif, uint8_t *frame); 62 | void gd_rewind(gd_GIF *gif); 63 | void gd_close_gif(gd_GIF *gif); 64 | 65 | #endif /* _GIFDEC_H_ */ 66 | -------------------------------------------------------------------------------- /src/vision_SPIFFS_GIF_video/medikarasenpai-240p-5s-15fps.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_SPIFFS_GIF_video/medikarasenpai-240p-5s-15fps.gif -------------------------------------------------------------------------------- /src/vision_SPIFFS_GIF_video/vision_SPIFFS_GIF_video.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Vision V3.2 SPIFFS GIF DEMO 3 | Github: libc0607/esp32-lcd-vision 4 | 5 | ref: https://github.com/moononournation/RGB565_video 6 | 7 | About this demo: 8 | - play loop.gif once, and then go into deep sleep; 9 | - wake by tapping or timer; 10 | 11 | How-to in Arduino IDE: 12 | Choose: 13 | ESP32 Dev Module, 240MHz CPU, 80MHz Flash, DIO, 4MB(32Mb), 14 | No OTA (1M APP/3M SPIFFS), PSRAM Disable; 15 | check /data/loop.gif; (should less than ~3MB 16 | check DEEP_SLEEP_TIME_S below; 17 | upload; 18 | "ESP32 Sketch Data upload"; 19 | 20 | Dependencies: 21 | Install espressif/arduino-esp32 (V1.0.6) first 22 | (using V2.0+ together with DFRobot_LIS will cause reset on every i2c.write now) 23 | moononournation/Arduino_GFX 24 | bitbank2/JPEGDEC 25 | DFRobot/DFRobot_LIS (see tapInterrupt example) 26 | me-no-dev/arduino-esp32fs-plugin (update test file to spiffs) 27 | 28 | This is just an early DEMO to verify all parts of the 29 | hardware can work properly. 30 | Many bugs. No guarantee for the performance of this code. 31 | 32 | */ 33 | 34 | #define GIF_FILENAME "/loop.gif" // /data/loop.gif 35 | #define DEEP_SLEEP_TIME_S 30 // wakeup by timer every 30 seconds 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "gifdec.h" 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #define PIN_MISO 2 // sdcard, not used (in this demo) 52 | #define PIN_SCK 14 // sdcard, not used (in this demo) 53 | #define PIN_MOSI 15 // sdcard, not used (in this demo) 54 | #define PIN_SD_CS 13 // sdcard, not used (in this demo) 55 | #define PIN_TFT_CS 5 // LCD 56 | #define PIN_TFT_BL 22 // LCD 57 | #define PIN_TFT_DC 27 // LCD 58 | #define PIN_TFT_RST 33 // LCD 59 | #define PIN_LCD_PWR_EN 19 // LCD, SDcard & temt6000 3v3 power (SY6280AAC on board) 60 | #define PIN_ADC_BAT 35 // battery voltage, with 1M/1M resistor dividor on hardware 61 | #define PIN_TEMP_ADC 36 // not used (in this demo) 62 | #define PIN_BAT_STDBY 37 // cn3165 standby, low == charge finish, ext. pull-up 63 | #define PIN_BAT_CHRG 38 // cn3165 chrg, low == charging, ext. pull-up 64 | #define PIN_ACC_INT1 39 // from accelerometer, push-pull 65 | #define PIN_ACC_INT2 32 // not used 66 | #define PIN_SENSOR_ADC 34 // light sensor (temt6000), to GND if not connected 67 | #define PIN_I2C_SCL 25 // to accelerometer 68 | #define PIN_I2C_SDA 26 // to accelerometer 69 | 70 | #define uS_TO_S_FACTOR 1000000ULL 71 | #define BAT_ADC_THRESH_LOW 1700 // analogRead( 1/2*Vbat ) 72 | 73 | Arduino_DataBus *bus = new Arduino_ESP32SPI(PIN_TFT_DC/* DC */, PIN_TFT_CS /* CS */, PIN_SCK, PIN_MOSI, PIN_MISO, VSPI ); 74 | //Arduino_GC9A01 *gfx = new Arduino_GC9A01(bus, PIN_TFT_RST, 2, true); 75 | Arduino_ST7789 *gfx = new Arduino_ST7789(bus, PIN_TFT_RST, 0, true, 240, 240, 0, 0); 76 | RTC_DATA_ATTR int bootCount = 0; 77 | 78 | DFRobot_LIS2DW12_I2C acce(&Wire, 0x19); // sdo/sa0 internal pull-up 79 | 80 | void set_io_before_deep_sleep() 81 | { 82 | pinMode(PIN_TFT_BL, INPUT); 83 | pinMode(PIN_TFT_CS, INPUT); 84 | pinMode(PIN_TFT_RST, INPUT); 85 | pinMode(PIN_MISO, INPUT); 86 | pinMode(PIN_SCK, INPUT); 87 | pinMode(PIN_MOSI, INPUT); 88 | pinMode(PIN_SD_CS, INPUT); 89 | pinMode(PIN_TFT_DC, INPUT); 90 | pinMode(1, INPUT); 91 | pinMode(3, INPUT); 92 | pinMode(PIN_I2C_SCL, INPUT); 93 | pinMode(PIN_I2C_SDA, INPUT); 94 | pinMode(PIN_ACC_INT2, INPUT); 95 | pinMode(PIN_ADC_BAT, INPUT); 96 | pinMode(PIN_TEMP_ADC, INPUT); 97 | pinMode(PIN_SENSOR_ADC, INPUT); 98 | 99 | pinMode(PIN_ACC_INT1, INPUT); 100 | pinMode(PIN_BAT_STDBY, INPUT); 101 | pinMode(PIN_BAT_CHRG, INPUT); 102 | 103 | digitalWrite(PIN_LCD_PWR_EN, LOW); 104 | gpio_hold_en(( gpio_num_t ) PIN_LCD_PWR_EN ); 105 | gpio_deep_sleep_hold_en(); 106 | esp_wifi_stop(); 107 | 108 | } 109 | 110 | void setup() 111 | { 112 | bootCount++; 113 | 114 | WiFi.mode(WIFI_OFF); 115 | Serial.begin(115200); 116 | Serial.print("Vision v3.2, SPIFFS GIF demo; bootcounter: "); 117 | Serial.println(bootCount); 118 | 119 | gpio_deep_sleep_hold_dis(); 120 | gpio_hold_dis(( gpio_num_t ) PIN_LCD_PWR_EN ); 121 | pinMode(PIN_LCD_PWR_EN, OUTPUT); 122 | 123 | Wire.setPins(PIN_I2C_SDA, PIN_I2C_SCL); 124 | Serial.print("finding acce. chip"); 125 | while (!acce.begin()) { 126 | delay(50); 127 | Serial.print("."); 128 | } 129 | Serial.println("!"); 130 | Serial.print("acce. chip id : "); 131 | Serial.println(acce.getID(), HEX); 132 | 133 | pinMode(PIN_ACC_INT1, INPUT); 134 | pinMode(PIN_BAT_STDBY, INPUT); 135 | pinMode(PIN_BAT_CHRG, INPUT); 136 | esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_ACC_INT1 , HIGH); 137 | esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME_S * uS_TO_S_FACTOR); 138 | 139 | // find out why we are working 140 | RESET_REASON rst_reason = rtc_get_reset_reason(0); 141 | esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); 142 | 143 | if (rst_reason == DEEPSLEEP_RESET) { 144 | // wakeup by acce. 145 | if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) { 146 | Serial.println("wakeup by ext0"); 147 | } else if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) { 148 | Serial.println("wakeup by timer"); 149 | } else { 150 | Serial.println("wakeup by yjsnpi"); 151 | } 152 | 153 | } else { 154 | Serial.println("bootup, init acce"); 155 | 156 | acce.setRange(DFRobot_LIS2DW12::e2_g); 157 | acce.setPowerMode(DFRobot_LIS2DW12::eContLowPwrLowNoise1_12bit); 158 | acce.setDataRate(DFRobot_LIS2DW12::eRate_100hz); 159 | acce.enableTapDetectionOnZ(true); 160 | acce.enableTapDetectionOnY(true); 161 | acce.setTapThresholdOnY(0.8); 162 | acce.setTapThresholdOnZ(0.8); 163 | acce.setTapDur(3); 164 | acce.setTapMode(DFRobot_LIS2DW12::eBothSingleDouble); 165 | acce.setInt1Event(DFRobot_LIS2DW12::eDoubleTap); 166 | 167 | } 168 | 169 | uint32_t battery_voltage_adc = analogRead(PIN_ADC_BAT); 170 | 171 | 172 | Serial.print("battery_adc: "); Serial.println(battery_voltage_adc); 173 | if (battery_voltage_adc < BAT_ADC_THRESH_LOW) { 174 | Serial.println("battery low, deep sleep start"); 175 | set_io_before_deep_sleep(); 176 | esp_deep_sleep_start(); 177 | } else { 178 | digitalWrite(PIN_LCD_PWR_EN, HIGH); 179 | delay(50); 180 | } 181 | 182 | // init lcd 183 | gfx->begin(); 184 | gfx->fillScreen(BLACK); 185 | delay(50); 186 | 187 | // light sensor -> lcd brightness 188 | float light_sens = analogRead(PIN_SENSOR_ADC); 189 | float light_sens_norm = light_sens/4096.0; 190 | light_sens_norm = pow(light_sens_norm, 2.0); 191 | ledcAttachPin(PIN_TFT_BL, 1); // assign TFT_BL pin to channel 1 192 | ledcSetup(1, 12000, 8); // 12 kHz PWM, 8-bit resolution 193 | uint8_t lcd_brightness = int(light_sens_norm * 224.0) + 31; // minimum 194 | ledcWrite(1, lcd_brightness); // brightness 195 | Serial.print("lcd_brightness: "); 196 | Serial.println(lcd_brightness); 197 | 198 | // charger state 199 | if (digitalRead(PIN_BAT_CHRG) == LOW) { 200 | Serial.println("battery: charging"); 201 | } else if (digitalRead(PIN_BAT_STDBY) == LOW) { 202 | Serial.println("battery: full"); 203 | } else { 204 | Serial.println("battery: not charging"); 205 | } 206 | 207 | // Init SPIFFS & play GIF 208 | if (!SPIFFS.begin(true)) { 209 | Serial.println(F("ERROR: SPIFFS mount failed!")); 210 | gfx->println(F("ERROR: SPIFFS mount failed!")); 211 | } else { 212 | File vFile = SPIFFS.open(GIF_FILENAME); 213 | if (!vFile || vFile.isDirectory()) { 214 | Serial.println(F("ERROR: Failed to open "GIF_FILENAME" file for reading")); 215 | gfx->println(F("ERROR: Failed to open "GIF_FILENAME" file for reading")); 216 | } else { 217 | gd_GIF *gif = gd_open_gif(&vFile); 218 | if (!gif) { 219 | Serial.println(F("gd_open_gif() failed!")); 220 | } else { 221 | int32_t s = gif->width * gif->height; 222 | uint8_t *buf = (uint8_t *)malloc(s); 223 | if (!buf) { 224 | Serial.println(F("buf malloc failed!")); 225 | } else { 226 | Serial.println(F("GIF video start")); 227 | gfx->setAddrWindow((gfx->width() - gif->width) / 2, (gfx->height() - gif->height) / 2, gif->width, gif->height); 228 | int t_fstart, t_delay = 0, t_real_delay, res, delay_until; 229 | int duration = 0, remain = 0; 230 | 231 | while (1) { 232 | t_fstart = millis(); 233 | t_delay = gif->gce.delay * 10; 234 | res = gd_get_frame(gif, buf); 235 | if (res < 0) { 236 | Serial.println(F("ERROR: gd_get_frame() failed!")); 237 | break; 238 | } else if (res == 0) { 239 | //Serial.printf("rewind, duration: %d, remain: %d (%0.1f %%)\n", duration, remain, 100.0 * remain / duration); 240 | //duration = 0; 241 | //remain = 0; 242 | //gd_rewind(gif); 243 | //continue; 244 | Serial.println("gif: break;"); 245 | break; 246 | } 247 | 248 | gfx->startWrite(); 249 | gfx->writeIndexedPixels(buf, gif->palette->colors, s); 250 | gfx->endWrite(); 251 | 252 | t_real_delay = t_delay - (millis() - t_fstart); 253 | duration += t_delay; 254 | remain += t_real_delay; 255 | delay_until = millis() + t_real_delay; 256 | do { 257 | delay(1); 258 | } while (millis() < delay_until); 259 | } 260 | 261 | Serial.println(F("GIF video end")); 262 | Serial.printf("duration: %d, remain: %d (%0.1f %%)\n", duration, remain, 100.0 * remain / duration); 263 | gd_close_gif(gif); 264 | } 265 | } 266 | } 267 | } 268 | 269 | 270 | Serial.println(F("deep sleep")); 271 | ledcWrite(1, 0); 272 | delay(20); 273 | ledcDetachPin(PIN_TFT_BL); 274 | gfx->displayOff(); 275 | 276 | set_io_before_deep_sleep(); 277 | esp_deep_sleep_start(); 278 | } 279 | 280 | void loop() 281 | { 282 | } 283 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/MjpegClass.78b3749.h: -------------------------------------------------------------------------------- 1 | #ifndef _MJPEGCLASS_H_ 2 | #define _MJPEGCLASS_H_ 3 | 4 | #define READ_BUFFER_SIZE 1024 5 | #define MAXOUTPUTSIZE 8 6 | #define NUMBER_OF_DRAW_BUFFER 4 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef struct 18 | { 19 | JPEG_DRAW_CALLBACK *drawFunc; 20 | } paramDrawTask; 21 | 22 | static xQueueHandle xqh = 0; 23 | static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER]; 24 | static int queue_cnt, draw_cnt; 25 | 26 | static int queueDrawMCU(JPEGDRAW *pDraw) 27 | { 28 | ++queue_cnt; 29 | while ((queue_cnt - draw_cnt) > NUMBER_OF_DRAW_BUFFER) 30 | { 31 | delay(1); 32 | } 33 | 34 | int len = pDraw->iWidth * pDraw->iHeight * 2; 35 | JPEGDRAW *j = &jpegdraws[queue_cnt % NUMBER_OF_DRAW_BUFFER]; 36 | j->x = pDraw->x; 37 | j->y = pDraw->y; 38 | j->iWidth = pDraw->iWidth; 39 | j->iHeight = pDraw->iHeight; 40 | memcpy(j->pPixels, pDraw->pPixels, len); 41 | 42 | xQueueSend(xqh, &j, 0); 43 | return 1; 44 | } 45 | 46 | static void drawTask(void *arg) 47 | { 48 | paramDrawTask *p = (paramDrawTask *)arg; 49 | for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++) 50 | { 51 | jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA); 52 | Serial.printf("#%d draw buffer allocated\n", i); 53 | } 54 | JPEGDRAW *pDraw; 55 | Serial.println("drawTask start"); 56 | while (xQueueReceive(xqh, &pDraw, portMAX_DELAY)) 57 | { 58 | // Serial.printf("task work: x: %d, y: %d, iWidth: %d, iHeight: %d\r\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 59 | p->drawFunc(pDraw); 60 | // Serial.println("task work done"); 61 | ++draw_cnt; 62 | } 63 | vQueueDelete(xqh); 64 | Serial.println("drawTask end"); 65 | vTaskDelete(NULL); 66 | } 67 | 68 | class MjpegClass 69 | { 70 | public: 71 | bool setup(Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool enableMultiTask, bool useBigEndian) 72 | { 73 | _input = input; 74 | _mjpeg_buf = mjpeg_buf; 75 | _pfnDraw = pfnDraw; 76 | _enableMultiTask = enableMultiTask; 77 | _useBigEndian = useBigEndian; 78 | 79 | _mjpeg_buf_offset = 0; 80 | _inputindex = 0; 81 | _remain = 0; 82 | 83 | queue_cnt = 0; 84 | draw_cnt = 0; 85 | 86 | if (!_read_buf) 87 | { 88 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 89 | } 90 | 91 | if (_enableMultiTask) 92 | { 93 | if (!xqh) 94 | { 95 | TaskHandle_t task; 96 | _p.drawFunc = pfnDraw; 97 | xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW)); 98 | xTaskCreatePinnedToCore(drawTask, "drawTask", 1600, &_p, 1, &task, 0); 99 | } 100 | } 101 | 102 | return true; 103 | } 104 | 105 | bool readMjpegBuf() 106 | { 107 | if (_inputindex == 0) 108 | { 109 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 110 | _inputindex += _buf_read; 111 | } 112 | _mjpeg_buf_offset = 0; 113 | int i = 0; 114 | bool found_FFD8 = false; 115 | while ((_buf_read > 0) && (!found_FFD8)) 116 | { 117 | i = 0; 118 | while ((i < _buf_read) && (!found_FFD8)) 119 | { 120 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 121 | { 122 | // Serial.printf("Found FFD8 at: %d.\n", i); 123 | found_FFD8 = true; 124 | } 125 | ++i; 126 | } 127 | if (found_FFD8) 128 | { 129 | --i; 130 | } 131 | else 132 | { 133 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 134 | } 135 | } 136 | uint8_t *_p = _read_buf + i; 137 | _buf_read -= i; 138 | bool found_FFD9 = false; 139 | if (_buf_read > 0) 140 | { 141 | i = 3; 142 | while ((_buf_read > 0) && (!found_FFD9)) 143 | { 144 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 145 | { 146 | // Serial.printf("Found FFD9 at: %d.\n", i); 147 | found_FFD9 = true; 148 | } 149 | else 150 | { 151 | while ((i < _buf_read) && (!found_FFD9)) 152 | { 153 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 154 | { 155 | found_FFD9 = true; 156 | ++i; 157 | } 158 | ++i; 159 | } 160 | } 161 | 162 | // Serial.printf("i: %d\n", i); 163 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 164 | _mjpeg_buf_offset += i; 165 | size_t o = _buf_read - i; 166 | if (o > 0) 167 | { 168 | // Serial.printf("o: %d\n", o); 169 | memcpy(_read_buf, _p + i, o); 170 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 171 | _p = _read_buf; 172 | _inputindex += _buf_read; 173 | _buf_read += o; 174 | // Serial.printf("_buf_read: %d\n", _buf_read); 175 | } 176 | else 177 | { 178 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 179 | _p = _read_buf; 180 | _inputindex += _buf_read; 181 | } 182 | i = 0; 183 | } 184 | if (found_FFD9) 185 | { 186 | return true; 187 | } 188 | } 189 | 190 | return false; 191 | } 192 | 193 | int getWidth() 194 | { 195 | return _jpeg.getWidth(); 196 | } 197 | 198 | int getHeight() 199 | { 200 | return _jpeg.getHeight(); 201 | } 202 | 203 | bool drawJpg() 204 | { 205 | _remain = _mjpeg_buf_offset; 206 | 207 | if (_enableMultiTask) 208 | { 209 | _jpeg.openRAM(_mjpeg_buf, _remain, queueDrawMCU); 210 | } 211 | else 212 | { 213 | _jpeg.openRAM(_mjpeg_buf, _remain, _pfnDraw); 214 | } 215 | 216 | _jpeg.setMaxOutputSize(MAXOUTPUTSIZE); 217 | if (_useBigEndian) 218 | { 219 | _jpeg.setPixelType(RGB565_BIG_ENDIAN); 220 | } 221 | _jpeg.decode(0, 0, 0); 222 | _jpeg.close(); 223 | 224 | return true; 225 | } 226 | 227 | private: 228 | Stream *_input; 229 | uint8_t *_mjpeg_buf; 230 | JPEG_DRAW_CALLBACK *_pfnDraw; 231 | bool _enableMultiTask; 232 | bool _useBigEndian; 233 | 234 | uint8_t *_read_buf; 235 | int32_t _mjpeg_buf_offset; 236 | 237 | JPEGDEC _jpeg; 238 | paramDrawTask _p; 239 | 240 | int32_t _inputindex; 241 | int32_t _buf_read; 242 | int32_t _remain; 243 | }; 244 | 245 | #endif // _MJPEGCLASS_H_ 246 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/MjpegClass.h: -------------------------------------------------------------------------------- 1 | #ifndef _MJPEGCLASS_H_ 2 | #define _MJPEGCLASS_H_ 3 | 4 | #define READ_BUFFER_SIZE 1024 5 | #define MAXOUTPUTSIZE 8 6 | #define NUMBER_OF_DRAW_BUFFER 4 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef struct 18 | { 19 | JPEG_DRAW_CALLBACK *drawFunc; 20 | } paramDrawTask; 21 | 22 | static xQueueHandle xqh = 0; 23 | static JPEGDRAW jpegdraws[NUMBER_OF_DRAW_BUFFER]; 24 | static int queue_cnt, draw_cnt; 25 | 26 | static int queueDrawMCU(JPEGDRAW *pDraw) 27 | { 28 | ++queue_cnt; 29 | while ((queue_cnt - draw_cnt) > NUMBER_OF_DRAW_BUFFER) 30 | { 31 | delay(1); 32 | } 33 | 34 | int len = pDraw->iWidth * pDraw->iHeight * 2; 35 | JPEGDRAW *j = &jpegdraws[queue_cnt % NUMBER_OF_DRAW_BUFFER]; 36 | j->x = pDraw->x; 37 | j->y = pDraw->y; 38 | j->iWidth = pDraw->iWidth; 39 | j->iHeight = pDraw->iHeight; 40 | memcpy(j->pPixels, pDraw->pPixels, len); 41 | 42 | xQueueSend(xqh, &j, 0); 43 | return 1; 44 | } 45 | 46 | static void drawTask(void *arg) 47 | { 48 | paramDrawTask *p = (paramDrawTask *)arg; 49 | for (int i = 0; i < NUMBER_OF_DRAW_BUFFER; i++) 50 | { 51 | jpegdraws[i].pPixels = (uint16_t *)heap_caps_malloc(MAXOUTPUTSIZE * 16 * 16 * 2, MALLOC_CAP_DMA); 52 | Serial.printf("#%d draw buffer allocated\n", i); 53 | } 54 | JPEGDRAW *pDraw; 55 | Serial.println("drawTask start"); 56 | while (xQueueReceive(xqh, &pDraw, portMAX_DELAY)) 57 | { 58 | // Serial.printf("task work: x: %d, y: %d, iWidth: %d, iHeight: %d\r\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 59 | p->drawFunc(pDraw); 60 | // Serial.println("task work done"); 61 | ++draw_cnt; 62 | } 63 | vQueueDelete(xqh); 64 | Serial.println("drawTask end"); 65 | vTaskDelete(NULL); 66 | } 67 | 68 | class MjpegClass 69 | { 70 | public: 71 | bool setup(Stream *input, uint8_t *mjpeg_buf, JPEG_DRAW_CALLBACK *pfnDraw, bool enableMultiTask, bool useBigEndian) 72 | { 73 | _input = input; 74 | _mjpeg_buf = mjpeg_buf; 75 | _pfnDraw = pfnDraw; 76 | _enableMultiTask = enableMultiTask; 77 | _useBigEndian = useBigEndian; 78 | 79 | _mjpeg_buf_offset = 0; 80 | _inputindex = 0; 81 | _remain = 0; 82 | 83 | queue_cnt = 0; 84 | draw_cnt = 0; 85 | 86 | if (!_read_buf) 87 | { 88 | _read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE); 89 | } 90 | 91 | if (_enableMultiTask) 92 | { 93 | if (!xqh) 94 | { 95 | TaskHandle_t task; 96 | _p.drawFunc = pfnDraw; 97 | xqh = xQueueCreate(NUMBER_OF_DRAW_BUFFER, sizeof(JPEGDRAW)); 98 | xTaskCreatePinnedToCore(drawTask, "drawTask", 1600, &_p, 1, &task, 0); 99 | } 100 | } 101 | 102 | return true; 103 | } 104 | 105 | bool updateFilePointer(Stream *input) 106 | { 107 | _input = input; 108 | _mjpeg_buf_offset = 0; 109 | _inputindex = 0; 110 | _remain = 0; 111 | 112 | queue_cnt = 0; 113 | draw_cnt = 0; 114 | 115 | return true; 116 | } 117 | 118 | bool readMjpegBuf() 119 | { 120 | if (_inputindex == 0) 121 | { 122 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 123 | _inputindex += _buf_read; 124 | } 125 | _mjpeg_buf_offset = 0; 126 | int i = 0; 127 | bool found_FFD8 = false; 128 | while ((_buf_read > 0) && (!found_FFD8)) 129 | { 130 | i = 0; 131 | while ((i < _buf_read) && (!found_FFD8)) 132 | { 133 | if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header 134 | { 135 | // Serial.printf("Found FFD8 at: %d.\n", i); 136 | found_FFD8 = true; 137 | } 138 | ++i; 139 | } 140 | if (found_FFD8) 141 | { 142 | --i; 143 | } 144 | else 145 | { 146 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 147 | } 148 | } 149 | uint8_t *_p = _read_buf + i; 150 | _buf_read -= i; 151 | bool found_FFD9 = false; 152 | if (_buf_read > 0) 153 | { 154 | i = 3; 155 | while ((_buf_read > 0) && (!found_FFD9)) 156 | { 157 | if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer 158 | { 159 | // Serial.printf("Found FFD9 at: %d.\n", i); 160 | found_FFD9 = true; 161 | } 162 | else 163 | { 164 | while ((i < _buf_read) && (!found_FFD9)) 165 | { 166 | if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer 167 | { 168 | found_FFD9 = true; 169 | ++i; 170 | } 171 | ++i; 172 | } 173 | } 174 | 175 | // Serial.printf("i: %d\n", i); 176 | memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i); 177 | _mjpeg_buf_offset += i; 178 | size_t o = _buf_read - i; 179 | if (o > 0) 180 | { 181 | // Serial.printf("o: %d\n", o); 182 | memcpy(_read_buf, _p + i, o); 183 | _buf_read = _input->readBytes(_read_buf + o, READ_BUFFER_SIZE - o); 184 | _p = _read_buf; 185 | _inputindex += _buf_read; 186 | _buf_read += o; 187 | // Serial.printf("_buf_read: %d\n", _buf_read); 188 | } 189 | else 190 | { 191 | _buf_read = _input->readBytes(_read_buf, READ_BUFFER_SIZE); 192 | _p = _read_buf; 193 | _inputindex += _buf_read; 194 | } 195 | i = 0; 196 | } 197 | if (found_FFD9) 198 | { 199 | return true; 200 | } 201 | } 202 | 203 | return false; 204 | } 205 | 206 | int getWidth() 207 | { 208 | return _jpeg.getWidth(); 209 | } 210 | 211 | int getHeight() 212 | { 213 | return _jpeg.getHeight(); 214 | } 215 | 216 | bool drawJpg() 217 | { 218 | _remain = _mjpeg_buf_offset; 219 | 220 | if (_enableMultiTask) 221 | { 222 | _jpeg.openRAM(_mjpeg_buf, _remain, queueDrawMCU); 223 | } 224 | else 225 | { 226 | _jpeg.openRAM(_mjpeg_buf, _remain, _pfnDraw); 227 | } 228 | 229 | _jpeg.setMaxOutputSize(MAXOUTPUTSIZE); 230 | if (_useBigEndian) 231 | { 232 | _jpeg.setPixelType(RGB565_BIG_ENDIAN); 233 | } 234 | _jpeg.decode(0, 0, 0); 235 | _jpeg.close(); 236 | 237 | return true; 238 | } 239 | 240 | private: 241 | Stream *_input; 242 | uint8_t *_mjpeg_buf; 243 | JPEG_DRAW_CALLBACK *_pfnDraw; 244 | bool _enableMultiTask; 245 | bool _useBigEndian; 246 | 247 | uint8_t *_read_buf; 248 | int32_t _mjpeg_buf_offset; 249 | 250 | JPEGDEC _jpeg; 251 | paramDrawTask _p; 252 | 253 | int32_t _inputindex; 254 | int32_t _buf_read; 255 | int32_t _remain; 256 | }; 257 | 258 | #endif // _MJPEGCLASS_H_ 259 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/README.md: -------------------------------------------------------------------------------- 1 | # vision_sdcard_mjpeg 2 | 这里是 ESP32 Vision 的测试代码之一。 3 | 由于 SPIFFS 的容量只能够存十秒上下的 GIF,很多花活可能整不出来;这个 Demo 使用了板上容量可高达 8GB 的 SD Nand 来支持整活。 4 | 注:由于加入蓝牙时这堆代码已经变成屎山了,目前的蓝牙部分还有各种各样的 Bug,emmmm…… 5 | 6 | ## 测试效果 7 | [Bilibili BV13S4y1e7Yu](https://www.bilibili.com/video/BV13S4y1e7Yu) 8 | 这里用于演示的视频片段见 [hydro_unaligned](https://github.com/libc0607/esp32-vision/tree/main/src/vision_sdcard_mjpeg/hydro_unaligned) 目录下 9 | 10 | ## 硬件 11 | 目前版本的代码可配合 硬件版本 V3.3 使用。 12 | 蓝牙按钮使用一种叫做 iTag 的防丢器,它的协议相比模拟 HID 键盘的蓝牙按钮要简单很多,比如淘宝上的 [这个](https://item.taobao.com/item.htm?id=556798481873) 13 | 关于如何将 iTag 作为 ESP32 的输入按钮使用,这里有一个最小化的例程: [ble_itag_demo](https://github.com/libc0607/esp32-vision/tree/main/src/ble_itag_demo) 14 | 15 | 这个例子没有使用到板上那个 0402 封装的 NTC;其他的电路,包括外接的环境光传感器以及 NTC 电阻都需要焊接才能正常工作。 16 | 这个例子通过修改代码的方式同时支持 方版(ST7789)和 圆版(GC9A01),修改方式见下文。 17 | 18 | ## 功能 19 | - 冷启动后,屏幕变灰,尝试连接名为 Celestia,密码为 mimitomo (默认,可在源码中更改)的 Wi-Fi 20 | - 如果超时未连接成功则进入正常工作周期;短按按键超过 250ms 可以跳过这个超时过程 21 | - 如果连接成功,屏幕变蓝,同时开启一个 WebServer,通过浏览器 http 访问该设备,可以上传 mjpeg 视频到 SD 卡中,可以修改配置文件 22 | - 上传过程中屏幕会变成橘色,LED 闪烁,上传结束后变回蓝色 23 | - 短按按键退出上传模式 24 | - 播放视频时可以按下按键暂停,短按暂停则进入浅度睡眠并关闭屏幕和背光;按下超过一秒(默认)则保持当前帧,开最大背光,进入浅度睡眠;再次按下会恢复播放 25 | - 循环模式:可以选择循环播放(常亮),或是播放视频一次后进入深度睡眠 26 | - 深度睡眠时,通过双击唤醒或者定时器唤醒(按键由于不是 RTC IO,无法用于深睡唤醒) 27 | - 如果近期使用过双击唤醒或是按下过按键,则接下来的几次定时器唤醒会较快,反之亦然 28 | - 如果电量低,它就也不亮了 (?又废话) 29 | - 支持动态亮度调节 30 | - 支持通过配置文件选择播放的 mjpeg,支持在网页中预览 mjpeg 文件(…的第一帧) 31 | - 支持通过配置文件设置屏幕方向 32 | - 支持在网页中 OTA 升级固件 33 | - 播放掉帧,性能堪忧( 34 | - 任何时候长按超过三秒都会被断电或是上电 35 | - 蓝牙启用后,可以使用蓝牙连接 iTag 来控制在两段视频间平滑切换(可能仍存在 bug( 36 | 37 | ## 编译 & 上传 & 初始化设置 38 | 39 | 1. 安装 Arduino 40 | 2. 安装 [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) (仅在 V1.0.6 下测试过;高版本和下面某个库的兼容不好) 41 | 3. 安装 [moononournation/Arduino_GFX](https://github.com/moononournation/Arduino_GFX) 42 | 4. 安装 [bitbank2/JPEGDEC](https://github.com/bitbank2/JPEGDEC) 43 | 5. 安装 [DFRobot/DFRobot_LIS](https://github.com/DFRobot/DFRobot_LIS) 44 | 6. 安装 [stevemarple/IniFile](https://github.com/stevemarple/IniFile) 45 | 7. 下载这坨源码,打开 46 | 8. 选择:ESP32 Dev Module, 240MHz CPU, 80MHz Flash, DIO, 4MB(32Mb), (1.9M/190k), PSRAM Disable 47 | 9. 上传 48 | 10. 建立名为 Celestia, 密码为 mimitomo 的 2.4GHz Wi-Fi,该网络中需要提供 DHCP 服务;(就是用电脑或者手机开热点的意思;并且由于需要加载 js,这个热点需要能访问互联网) 49 | 11. 给板子上电 50 | 12. 当看到板子连入该 Wi-Fi 后,在 DHCP 地址池中找到该设备,记录下其 IP 地址 51 | 13. 使用浏览器 http 打开其 IP 地址,下文以 192.168.1.200 为例;如果你的网络中支持 MDNS 服务也可以打开 http://vision.local 52 | 14. 使用 curl 或是其他什么你熟悉的东西上传 index.htm, edit.htm, ota.htm 三个网页文件:curl -X POST -F "file=@index.htm" http://192.168.1.200/edit 53 | 15. 将你需要播放的东西通过 ffmpeg 或是其他的什么方式转为 240x240,大约 24fps 的 mjpeg 格式文件 (参考 [RGB565_video](https://github.com/moononournation/RGB565_video) 中的示例) 54 | 16. 使用浏览器打开 http://192.168.1.200 或 http://vision.local ,选择 “文件”,并使用上方的按钮上传 mjpeg 文件到 SD Nand 根目录 55 | 17. 在左侧列表中找到 config.txt 并编辑,设置 video 为需要播放的文件名,以 / 开头,例如 video=/loop.mjpeg, Ctrl+S 保存;该文件的语法参考 [stevemarple/IniFile](https://github.com/stevemarple/IniFile) 56 | 18. 短按按键,看到屏幕一绿即为配置完成 57 | 19. 你就又获得了一颗下北泽神之眼 58 | 20. 如果想要再次更改其中内容,只需要将其断电(板上开关)等个十几秒后再接通让它冷启动,即可重复上述相关步骤。 59 | 60 | ## 自定义配置 61 | 62 | 配置文件 /config.txt 中的可自定义内容: 63 | 64 | ``` 65 | [vision] 66 | video=/loop.mjpeg # 要播放的文件名。需要以 / 开头,并且文件名不要超过 8 个字符(短文件名),不然会出bug 67 | lcd_rotation=0 # LCD 的方向。取值范围 0~3,对应 0°, 90°, 180°, 270°,根据实际情况修改 68 | loop_mode=false # 循环开关 69 | abloop_en=false # 使能两段视频切换 70 | ble_mac=00:12:34:56:78:9a # 你的 iTag 的 MAC 地址。可以通过例如 nRF Connect 这样的应用查看 71 | ``` 72 | 目前的设定中,有 3 种工作模式: 73 | (a). 循环播放两段视频,通过 iTag 或双击来控制两段视频的切换 74 | 该模式下需要剪出 4 段视频,两个循环片段 a_loop 及 b_loop,以及两个过渡片段 a_setup 和 b_setup; 75 | - loop_mode = true 76 | - abloop_en = true 77 | - ble_mac = \ 78 | - 上传 /a_setup.mjpeg, /a_loop.mjpeg, /b_setup.mjpeg, /b_loop.mjpeg 79 | 80 | Light Sleep后蓝牙连接会断开,需要等几秒才会重新连接 81 | LED 会指示当前正在播放的是 A 视频还是 B 视频; 82 | 83 | (b). 循环单个视频 84 | 该模式需要一段视频 85 | - 上传视频 (例如 hydro.mjpeg) 86 | - loop_mode = true 87 | - abloop_en = false 88 | - video = hydro.mjpeg 89 | 90 | (c). 单次播放后进入睡眠,通过双击唤醒 91 | 该模式需要一段视频 92 | - 上传视频 (例如 hydro.mjpeg) 93 | - loop_mode = false 94 | - video = hydro.mjpeg 95 | 96 | 如果你想恢复默认值,只需要删除 /config.txt 并重启即可,会生成一个新的默认配置的文件。 97 | 按键功能仅在 (a) (b) 下生效 98 | 99 | 代码中有几个可以配置的地方(其中一部分可能在日后(如果有,咕)的版本中移入配置文件): 100 | ``` 101 | 102 | // 系统配置 103 | #define CONFIG_FILENAME "/config.txt" // SD Nand 根目录中的配置文件的名称。该文件如果不存在会自动生成。 104 | #define DEEP_SLEEP_SHORT_S 6 // 在最近一次敲击唤醒之后的 DEEP_SLEEP_SHORT_CNT 次睡眠前,会设置定时器使用 DEEP_SLEEP_SHORT_S 作为唤醒延时;否则使用 DEEP_SLEEP_LONG_S 105 | #define DEEP_SLEEP_SHORT_CNT 6 // 每次检测到敲击唤醒后重置计数器 106 | #define DEEP_SLEEP_LONG_S 30 // DEEP_SLEEP_LONG_S 和 DEEP_SLEEP_SHORT_S 是定时器唤醒的时间,单位是秒 107 | #define BAT_ADC_THRESH_LOW 1700 // 低电压保护。当每一轮工作开始时,如果 ADC 读取到的电压读数低于这个值,就不会播放 GIF,直接进入下一轮睡眠 108 | // 由于 ESP32 的 ADC 质量不咋样,并且电池的截止电压也各自不同,所以没有换算成电压。 109 | // 你可以根据自己板子的情况尝试调节这个值。 110 | 111 | // 屏幕配置 112 | //Arduino_GC9A01 *gfx = new Arduino_GC9A01(bus, PIN_TFT_RST, 2, true); 113 | Arduino_ST7789 *gfx = new Arduino_ST7789(bus, PIN_TFT_RST, 0, true, 240, 240, 0, 0, 0, 80); 114 | // 在这里选择你的屏幕种类 115 | #define LCD_BACKLIGHT_MIN_8B 16 // 屏幕背光的最低亮度,范围为 8bit(即最大值为 255)。 116 | // 每次运行时,程序将从环境光传感器那里得到的光强度 Map 到 LCD_BACKLIGHT_MIN_8B ~ 255 区间内,作为背光 PWM 的占空比。 117 | #define LCD_PWM_FIR_LEN 32 // 让背光的动态调节不要那么突然 118 | 119 | // 网络设置 120 | const char* wifi_ssid = "Celestia"; // 冷启动时尝试连接的 Wi-Fi 名称和密码 121 | const char* wifi_pwd = "mimitomo"; 122 | #define WIFI_CONNECT_TIMEOUT_S 20 // 冷启动时尝试连接 Wi-Fi 的时间上限,单位为秒。 123 | // 超过这个时间还没有连接成功则直接进入正常工作,也可以用按键跳过 124 | const char* wifi_host = "vision"; // MDNS 主机名。这样设置得到的结果形如 vision.local 125 | 126 | ``` 127 | ## 早期版本 128 | 由于早期使用的分区是默认的 1.2M/1.2M/1.5M,加入 BLE 后空间不够了。。所以 BLE 版本改了分区 129 | 加入 BLE 前的最后一个版本为 [78b3749](https://github.com/libc0607/esp32-vision/commit/78b3749) 130 | 131 | ## TO-DOs 132 | 本来想用 ESP-NOW(例如 [Picoclick](https://github.com/makermoekoe/Picoclick-C3)),但国内好像没有那种买来就能用的 133 | iTag 虽然使人逻辑混乱,但有商品卖,且足够便宜,先凑合用着 134 | 135 | ## 参考 136 | [moononournation/RGB565_video](https://github.com/moononournation/RGB565_video) 137 | ESP32 Arduino 中的 SDWebServer 和 OTAWebUpdater 示例 138 | 雷元素图标来自 [Bilibili: 鱼翅翅Kira](https://space.bilibili.com/2292091) 139 | BLE iTag 相关代码抄自 [100-x-arduino.blogspot.com](http://100-x-arduino.blogspot.com/2018/05/itag-i-arduino-ide-czy-esp32-otworzy.html) 140 | 141 | ## 免责声明 142 | 这段代码仅用作测试,由于滥用造成的一切不好的后果和作者无关。 143 | 如果其中的图片素材涉及侵权,请联系我删除。 144 | 这段代码仅调通了功能,结构十分混乱,如有引发不适与我无关。 145 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/electro5.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/electro5.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/electro_unaligned_new/a_loop.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/electro_unaligned_new/a_loop.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/electro_unaligned_new/a_setup.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/electro_unaligned_new/a_setup.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/electro_unaligned_new/b_loop.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/electro_unaligned_new/b_loop.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/electro_unaligned_new/b_setup.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/electro_unaligned_new/b_setup.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/html/edit.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SD Editor 5 | 162 | 727 | 728 | 729 | 730 |
731 |
732 |
733 | 734 | 735 | 736 | 737 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/html/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP32Vision 6 | 7 | 8 | 35 | 36 | 37 | 38 |
39 |

ESP32Vision

40 | 43 |
44 |

System status

45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 | 文件 53 |
54 |
55 |
56 |
57 | 更新 58 |
59 |
60 |
61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/html/ota.htm: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ESP32Vision 6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 |

ESP32Vision OTA Update

18 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/hydro_unaligned/a_loop.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/hydro_unaligned/a_loop.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/hydro_unaligned/a_setup.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/hydro_unaligned/a_setup.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/hydro_unaligned/b_loop.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/hydro_unaligned/b_loop.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/hydro_unaligned/b_setup.mjpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/src/vision_sdcard_mjpeg/hydro_unaligned/b_setup.mjpeg -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/vision_sdcard_mjpeg.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Vision V3.3 SD MJPEG WebServer OTA BLE DEMO 3 | Github: libc0607/esp32-vision 4 | 5 | ref: 6 | https://github.com/moononournation/RGB565_video 7 | examples/SDWebServer 8 | examples/OTAWebUpdater 9 | 10 | WebServer bring-up: 11 | curl -X POST -F "file=@ota.htm" http://vision.local/edit 12 | curl -X POST -F "file=@index.htm" http://vision.local/edit 13 | curl -X POST -F "file=@edit.htm" http://vision.local/edit 14 | 15 | How-to in Arduino IDE: 16 | Choose: 17 | ESP32 Dev Module, 240MHz CPU, 80MHz Flash, DIO, 4MB(32Mb), 18 | Minimal SPIFFS (1.9MB with OTA/190kB SPIFFS), PSRAM Disable; 19 | upload; 20 | 21 | Dependencies: 22 | Install espressif/arduino-esp32 (V1.0.6, due to compatibility issue) first 23 | moononournation/Arduino_GFX 24 | bitbank2/JPEGDEC 25 | DFRobot/DFRobot_LIS (see tapInterrupt example) 26 | stevemarple/IniFile 27 | 28 | Config ini example: 29 | 30 | [vision] 31 | video=/loop.mjpeg 32 | lcd_rotation=0 33 | loop_mode=false 34 | abloop_en=false 35 | ble_mac=00:12:34:56:78:9a 36 | 37 | There are 3 working modes: 38 | a. Loop video A or B, controlled by iTag or double tap; 39 | you should: 40 | set loop_mode = true 41 | set abloop_en = true 42 | if u have an iTag, set ble_mac to your iTag MAC address; else, delete ble_mac 43 | upload /a_setup.mjpeg, /a_loop.mjpeg, /b_setup.mjpeg, /b_loop.mjpeg 44 | b. Loop one video; 45 | u should: 46 | upload video (e.g. hydro.mjpeg) 47 | set loop_mode = true 48 | set abloop_en = false 49 | set video = hydro.mjpeg 50 | c. play video once, then deep sleep; wakeup by double tap; 51 | u should: 52 | upload video (e.g. hydro.mjpeg) 53 | set loop_mode = false 54 | set video = hydro.mjpeg 55 | 56 | This is just an EARLY DEMO to verify all parts of the 57 | hardware can work properly. 58 | Many bugs. No guarantee for the performance of this code. 59 | 就是抄了一大堆例程,并小心地让它们能一起工作而已。很烂,轻喷 60 | 61 | */ 62 | #define CONFIG_FILENAME "/config.txt" 63 | const char* wifi_ssid = "Celestia"; 64 | const char* wifi_pwd = "mimitomo"; 65 | const char* wifi_host = "vision"; 66 | #define CONF_GIFNAME_DEFAULT "/loop.mjpeg" 67 | #define CONF_TARGET_FPS_DEFAULT 15 68 | //#define CONF_GRAVITY_DEFAULT true 69 | #define CONF_LCD_ROTATION_DEFAULT 0 70 | #define CONF_LOOP_MODE_DEFAULT true 71 | #define DEEP_SLEEP_LONG_S 30 72 | #define DEEP_SLEEP_SHORT_S 2 73 | #define DEEP_SLEEP_SHORT_CNT 6 74 | #define TEMP_PROTECT_HIGH_THRESH_12B 1840 // ~60°C @10k,B3380 75 | #define LCD_BACKLIGHT_MIN_12B 640 76 | #define LCD_PWM_FIR_LEN 32 77 | #define LCD_PWM_CHANGE_MAX_STEP 32 78 | #define VIDEO_A_SETUP "/a_setup.mjpeg" 79 | #define VIDEO_B_SETUP "/b_setup.mjpeg" 80 | #define VIDEO_A_LOOP "/a_loop.mjpeg" 81 | #define VIDEO_B_LOOP "/b_loop.mjpeg" 82 | #define LOCK_SCREEN_TIME_THRESH_MS 1000 83 | #define BL_LEVEL_MAX 4095 84 | 85 | #define FILE_SYSTEM SD 86 | 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | #include 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include "BLEDevice.h" 108 | 109 | 110 | #define PIN_MISO 2 // sdcard 111 | #define PIN_SCK 14 // sdcard 112 | #define PIN_MOSI 15 // sdcard 113 | #define PIN_SD_CS 13 // sdcard 114 | #define PIN_TFT_CS 5 // LCD 115 | #define PIN_TFT_BL 22 // LCD 116 | #define PIN_TFT_DC 27 // LCD 117 | #define PIN_TFT_RST 33 // LCD 118 | #define PIN_LCD_PWR_EN 19 // LCD, SDcard & temt6000 3v3 power (SY6280AAC on board) 119 | #define PIN_ADC_BAT 35 // battery voltage, with 1M/1M resistor dividor on hardware 120 | #define PIN_TEMP_ADC 36 // 121 | #define PIN_BAT_STDBY 37 // cn3165 standby, low == charge finish, ext. pull-up 122 | #define PIN_BAT_CHRG 38 // cn3165 chrg, low == charging, ext. pull-up 123 | #define PIN_ACC_INT1 39 // from accelerometer, push-pull 124 | #define PIN_ACC_INT2 32 // not used 125 | #define PIN_SENSOR_ADC 34 // light sensor (temt6000), to GND if not connected 126 | #define PIN_I2C_SCL 25 // to accelerometer 127 | #define PIN_I2C_SDA 26 // to accelerometer 128 | #define PIN_LED 23 // ext. led 129 | #define PIN_KEY 18 // ext. key 130 | 131 | #define WIFI_CONNECT_TIMEOUT_S 20 132 | #define uS_TO_S_FACTOR (1000000ULL) 133 | //#define BAT_ADC_THRESH_LOW (1700) // analogRead( 1/2*Vbat ) 134 | #define BAT_ADC_THRESH_LOW_MV (2900) // analogReadMilliVolts(Vbat) 135 | //#define BAT_ADC_THRESH_WARN (1880) // analogRead( 1/2*Vbat ) 136 | #define MJPEG_BUFFER_SIZE (240 * 240 * 2 / 4) 137 | 138 | Arduino_DataBus *bus = new Arduino_ESP32SPI(PIN_TFT_DC/* DC */, PIN_TFT_CS /* CS */, PIN_SCK, PIN_MOSI, PIN_MISO, VSPI, true ); 139 | Arduino_ST7789 *gfx = new Arduino_ST7789(bus, PIN_TFT_RST, 0, true, 240, 240, 0, 0, 0, 80); 140 | //Arduino_GC9A01 *gfx = new Arduino_GC9A01(bus, PIN_TFT_RST, 2, true); 141 | 142 | DFRobot_LIS2DW12_I2C acce(&Wire, 0x19); // sdo/sa0 internal pull-up 143 | 144 | #include "MjpegClass.h" 145 | static MjpegClass mjpeg; 146 | uint8_t *mjpeg_buf; 147 | 148 | unsigned long lightsleep_ms; 149 | TaskHandle_t LCD_BL_TaskHandle; 150 | xQueueHandle lcd_bl_queue; 151 | 152 | WebServer server(80); 153 | static bool hasSD = false; 154 | File uploadFile; 155 | int led_status; 156 | 157 | RTC_DATA_ATTR int bootCount = 0; 158 | RTC_DATA_ATTR int lastTappedCount = 0; 159 | 160 | /* BLE Connection to iTag. 161 | * 162 | * BUG: cause reset when connect itag at the second time, and i don't know why 163 | * however the first connection is ok, if u keep that connection it's still usable 164 | * 165 | * This bunch of sh*t logic is designed to smoothly switching 166 | * between two video parts A & B when BLE got notified 167 | * 168 | * to enable this feature, you need 4 pieces of video: 169 | * Length: |--------------|--------------|--------------|--------------| 170 | * Part: | A setup | A Loop | B Setup | B Loop | 171 | * 172 | * the BLE part: i just copied his code, no optimization 173 | * See http://100-x-arduino.blogspot.com/2018/05/itag-i-arduino-ide-czy-esp32-otworzy.html 174 | * 175 | * About how to fix the memory leak of the iTag example, see 176 | * https://github.com/nkolban/esp32-snippets/issues/1006 177 | * https://github.com/nkolban/esp32-snippets/issues/786 178 | * 179 | */ 180 | static BLEUUID serviceUUID("0000ffe0-0000-1000-8000-00805f9b34fb"); 181 | static BLEUUID charUUID("0000ffe1-0000-1000-8000-00805f9b34fb"); 182 | static BLEAddress *pServerAddress; 183 | static boolean ble_doconnect = false; 184 | static boolean ble_connected = false; 185 | static BLERemoteCharacteristic* pRemoteCharacteristic; 186 | static BLEClient* pClient; 187 | bool deviceBleConnected = false; 188 | bool ble_key_pressed = false; // when ble got notify 189 | TaskHandle_t BLE_TaskHandle; 190 | 191 | // led indicator 192 | TaskHandle_t LED_TaskHandle; 193 | xQueueHandle led_queue; 194 | 195 | class MyClientCallbacks: public BLEClientCallbacks { 196 | void onConnect(BLEClient *pClient) { 197 | deviceBleConnected = true; 198 | Serial.println("BLE: connected to my server"); 199 | }; 200 | void onDisconnect(BLEClient *pClient) { 201 | pClient->disconnect(); 202 | deviceBleConnected = false; 203 | Serial.println("BLE: disconnected from my server"); 204 | ble_connected = false; 205 | } 206 | }; 207 | 208 | MyClientCallbacks* callbacks = new MyClientCallbacks(); 209 | 210 | static void notifyCallback( 211 | BLERemoteCharacteristic* pBLERemoteCharacteristic, 212 | uint8_t* pData, size_t length, bool isNotify) { 213 | Serial.println("BLE: Notify from iTAG"); 214 | ble_key_pressed = true; 215 | } 216 | 217 | bool connectToServer(BLEAddress pAddress) { 218 | if (pClient != nullptr) { 219 | delete(pClient); 220 | } 221 | pClient = BLEDevice::createClient(); 222 | pClient->setClientCallbacks(callbacks); 223 | pClient->connect(pAddress); 224 | if (!pClient->isConnected()) 225 | return false; 226 | BLERemoteService* pRemoteService = pClient->getService(serviceUUID); 227 | if (pRemoteService == nullptr) { 228 | Serial.print("BLE: Failed to find our service UUID "); 229 | return false; 230 | } 231 | Serial.println("BLE: Found service " + String(pRemoteService->toString().c_str())); 232 | 233 | pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); 234 | if (pRemoteCharacteristic == nullptr) { 235 | Serial.println("BLE: Failed to find our characteristic UUID "); 236 | return false; 237 | } 238 | Serial.println("BLE: Found characteristic" + String(pRemoteCharacteristic->toString().c_str())); 239 | 240 | //std::string value = pRemoteCharacteristic->readValue(); 241 | //Serial.print("BLE: Read characteristic value: "); Serial.println(value.c_str()); 242 | 243 | //const uint8_t bothOff[] = {0x0, 0x0}; 244 | const uint8_t notificationOn[] = {0x1, 0x0}; 245 | //const uint8_t indicationOn[] = {0x2, 0x0}; 246 | //const uint8_t bothOn[] = {0x3, 0x0}; 247 | 248 | pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); 249 | Serial.println("BLE: Notification ON "); 250 | pRemoteCharacteristic->registerForNotify(notifyCallback); // Callback function when notification 251 | Serial.println("BLE: Notification Callback "); 252 | return true; 253 | } 254 | 255 | // pixel drawing callback 256 | static int drawMCU(JPEGDRAW *pDraw) { 257 | // Serial.printf("Draw pos = %d,%d. size = %d x %d\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 258 | gfx->draw16bitBeRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); 259 | return 1; 260 | } /* drawMCU() */ 261 | 262 | void set_io_before_deep_sleep() { 263 | 264 | pinMode(PIN_TFT_BL, INPUT); 265 | pinMode(PIN_TFT_CS, INPUT); 266 | pinMode(PIN_TFT_RST, INPUT); 267 | pinMode(PIN_MISO, INPUT); 268 | pinMode(PIN_SCK, INPUT); 269 | pinMode(PIN_MOSI, INPUT); 270 | pinMode(PIN_SD_CS, INPUT); 271 | pinMode(PIN_TFT_DC, INPUT); 272 | pinMode(1, INPUT); 273 | pinMode(3, INPUT); 274 | pinMode(PIN_I2C_SCL, INPUT); 275 | pinMode(PIN_I2C_SDA, INPUT); 276 | pinMode(PIN_ACC_INT2, INPUT); 277 | pinMode(PIN_ADC_BAT, INPUT); 278 | pinMode(PIN_TEMP_ADC, INPUT); 279 | pinMode(PIN_SENSOR_ADC, INPUT); 280 | 281 | pinMode(PIN_ACC_INT1, INPUT); 282 | pinMode(PIN_BAT_STDBY, INPUT); 283 | pinMode(PIN_BAT_CHRG, INPUT); 284 | 285 | pinMode(PIN_KEY, INPUT_PULLUP); 286 | pinMode(PIN_LED, INPUT); 287 | 288 | esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_ACC_INT1 , HIGH); 289 | //esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BAT_STDBY , LOW); 290 | //esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BAT_CHRG , LOW); 291 | 292 | digitalWrite(PIN_LCD_PWR_EN, LOW); 293 | gpio_hold_en(( gpio_num_t ) PIN_LCD_PWR_EN ); 294 | gpio_deep_sleep_hold_en(); 295 | esp_wifi_stop(); 296 | //adc_power_release(); 297 | } 298 | 299 | void returnOK() { 300 | server.send(200, "text/plain", ""); 301 | } 302 | 303 | void returnFail(String msg) { 304 | server.send(500, "text/plain", msg + "\r\n"); 305 | } 306 | 307 | bool loadFromSdCard(String path) { 308 | //bool is_gzipped = false; 309 | 310 | String dataType = "text/plain"; 311 | if (path.endsWith("/")) { 312 | path += "index.htm"; 313 | } 314 | 315 | if (path.endsWith(".src")) { 316 | path = path.substring(0, path.lastIndexOf(".")); 317 | } else if (path.endsWith(".htm")) { 318 | dataType = "text/html"; 319 | } else if (path.endsWith(".css")) { 320 | dataType = "text/css"; 321 | } else if (path.endsWith(".js")) { 322 | dataType = "application/javascript"; 323 | } else if (path.endsWith(".png")) { 324 | dataType = "image/png"; 325 | } else if (path.endsWith(".gif")) { 326 | dataType = "image/gif"; 327 | } else if (path.endsWith(".jpg")) { 328 | dataType = "image/jpeg"; 329 | } else if (path.endsWith(".ico")) { 330 | dataType = "image/x-icon"; 331 | } else if (path.endsWith(".xml")) { 332 | dataType = "text/xml"; 333 | } else if (path.endsWith(".pdf")) { 334 | dataType = "application/pdf"; 335 | } else if (path.endsWith(".zip")) { 336 | dataType = "application/zip"; 337 | } 338 | 339 | File dataFile = FILE_SYSTEM.open(path.c_str()); 340 | if (dataFile.isDirectory()) { 341 | path += "/index.htm"; 342 | dataType = "text/html"; 343 | dataFile = FILE_SYSTEM.open(path.c_str()); 344 | } 345 | 346 | if (!dataFile) { 347 | return false; 348 | } 349 | 350 | if (server.hasArg("download")) { 351 | dataType = "application/octet-stream"; 352 | } 353 | 354 | // for ffmpeg.wasm 355 | // 试过了 这不太现实 并且太慢 算了 356 | //server.sendHeader("Cross-Origin-Opener-Policy", "same-origin"); 357 | //server.sendHeader("Cross-Origin-Embedder-Policy", "require-corp"); 358 | 359 | if (server.streamFile(dataFile, dataType) != dataFile.size()) { 360 | Serial.println("Sent less data than expected!"); 361 | } 362 | 363 | dataFile.close(); 364 | return true; 365 | } 366 | 367 | void handleFileUpload() { 368 | if (server.uri() != "/edit") { 369 | return; 370 | } 371 | HTTPUpload& upload = server.upload(); 372 | String filename = upload.filename; 373 | if (!filename.startsWith("/")) 374 | filename = "/" + filename; 375 | if (upload.status == UPLOAD_FILE_START) { 376 | gfx->fillScreen(ORANGE);SD.begin(PIN_SD_CS); 377 | if (FILE_SYSTEM.exists(filename)) { 378 | FILE_SYSTEM.remove(filename); 379 | } 380 | uploadFile = FILE_SYSTEM.open(filename, FILE_WRITE); 381 | Serial.print("Upload: START, filename: "); Serial.println(filename); 382 | } else if (upload.status == UPLOAD_FILE_WRITE) { 383 | led_status = (led_status == HIGH)? LOW: HIGH; 384 | digitalWrite(PIN_LED, led_status); 385 | if (uploadFile) { 386 | uploadFile.write(upload.buf, upload.currentSize); 387 | } 388 | Serial.print("Upload: WRITE, Bytes: "); Serial.println(upload.currentSize); 389 | } else if (upload.status == UPLOAD_FILE_END) { 390 | if (uploadFile) { 391 | uploadFile.close(); 392 | } 393 | gfx->fillScreen(BLUE);SD.begin(PIN_SD_CS); 394 | Serial.print("Upload: END, Size: "); Serial.println(upload.totalSize); 395 | } 396 | } 397 | 398 | void deleteRecursive(String path) { 399 | File file = FILE_SYSTEM.open((char *)path.c_str()); 400 | if (!file.isDirectory()) { 401 | file.close(); 402 | FILE_SYSTEM.remove((char *)path.c_str()); 403 | return; 404 | } 405 | 406 | file.rewindDirectory(); 407 | while (true) { 408 | File entry = file.openNextFile(); 409 | if (!entry) { 410 | break; 411 | } 412 | String entryPath = path + "/" + entry.name(); 413 | if (entry.isDirectory()) { 414 | entry.close(); 415 | deleteRecursive(entryPath); 416 | } else { 417 | entry.close(); 418 | FILE_SYSTEM.remove((char *)entryPath.c_str()); 419 | } 420 | yield(); 421 | } 422 | 423 | FILE_SYSTEM.rmdir((char *)path.c_str()); 424 | file.close(); 425 | } 426 | 427 | void handleDelete() { 428 | if (server.args() == 0) { 429 | return returnFail("BAD ARGS"); 430 | } 431 | String path = server.arg(0); 432 | if (path == "/" || !FILE_SYSTEM.exists((char *)path.c_str())) { 433 | returnFail("BAD PATH"); 434 | return; 435 | } 436 | deleteRecursive(path); 437 | returnOK(); 438 | } 439 | 440 | void handleCreate() { 441 | if (server.args() == 0) { 442 | return returnFail("BAD ARGS"); 443 | } 444 | String path = server.arg(0); 445 | if (path == "/" || FILE_SYSTEM.exists((char *)path.c_str())) { 446 | returnFail("BAD PATH"); 447 | return; 448 | } 449 | 450 | if (path.indexOf('.') > 0) { 451 | File file = FILE_SYSTEM.open((char *)path.c_str(), FILE_WRITE); 452 | if (file) { 453 | file.write(0); 454 | file.close(); 455 | } 456 | } else { 457 | FILE_SYSTEM.mkdir((char *)path.c_str()); 458 | } 459 | returnOK(); 460 | } 461 | 462 | void getStatus() { 463 | 464 | // A human-readable system status API 465 | 466 | String stat = ""; 467 | 468 | // card type 469 | //stat += "SD_Type,"; 470 | uint8_t cardType = FILE_SYSTEM.cardType(); 471 | if (cardType == CARD_NONE) { 472 | stat += "No_Card"; 473 | } else if (cardType == CARD_MMC) { 474 | stat += "SD_MMC"; 475 | } else if (cardType == CARD_SD) { 476 | stat += "SD_SC"; 477 | } else if (cardType == CARD_SDHC) { 478 | stat += "SD_HC"; 479 | } else { 480 | stat += "Card_UNKNOWN"; 481 | } 482 | stat += ", "; 483 | 484 | // card space 485 | stat += String(int(FILE_SYSTEM.usedBytes() / (1024 * 1024))); 486 | stat += "MB/"; 487 | stat += String(int(FILE_SYSTEM.totalBytes() / (1024 * 1024))); 488 | stat += "MB, "; 489 | // Battery info 490 | //stat += "Battery_ADC,"; 491 | //stat += analogRead(PIN_ADC_BAT); 492 | stat += (2*analogReadMilliVolts(PIN_ADC_BAT)); stat += "mV"; 493 | //stat += ",Charger,"; 494 | stat += ", "; 495 | if (digitalRead(PIN_BAT_CHRG) == LOW) { 496 | stat += "Charging, "; 497 | } else if (digitalRead(PIN_BAT_STDBY) == LOW) { 498 | stat += "Full, "; 499 | } else { 500 | stat += "NotCharging, "; 501 | } 502 | 503 | // light sensor info 504 | //stat += "Light_ADC,"; 505 | stat += analogRead(PIN_SENSOR_ADC); 506 | stat += "/4095, "; 507 | 508 | // acce. 509 | stat += "X, "; 510 | stat += acce.readAccX(); 511 | stat += "mg, Y, "; 512 | stat += acce.readAccY(); 513 | stat += "mg, Z, "; 514 | stat += acce.readAccZ(); 515 | stat += "mg, "; 516 | 517 | // firmware info 518 | stat += __DATE__ ; 519 | stat += " "; 520 | stat += __TIME__ ; 521 | stat += "\r\n"; 522 | server.send(200, "text/plain", stat); 523 | } 524 | 525 | void printDirectory() { 526 | if (!server.hasArg("dir")) { 527 | return returnFail("BAD ARGS"); 528 | } 529 | String path = server.arg("dir"); 530 | //if (path != "/" && !SD.exists((char *)path.c_str())) { 531 | if (path != "/" && !FILE_SYSTEM.exists((char *)path.c_str())) { 532 | return returnFail("BAD PATH"); 533 | } 534 | //File dir = SD.open((char *)path.c_str()); 535 | File dir = FILE_SYSTEM.open((char *)path.c_str()); 536 | path = String(); 537 | if (!dir.isDirectory()) { 538 | dir.close(); 539 | return returnFail("NOT DIR"); 540 | } 541 | dir.rewindDirectory(); 542 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 543 | server.send(200, "text/json", ""); 544 | WiFiClient client = server.client(); 545 | 546 | server.sendContent("["); 547 | for (int cnt = 0; true; ++cnt) { 548 | File entry = dir.openNextFile(); 549 | if (!entry) { 550 | break; 551 | } 552 | 553 | String output; 554 | if (cnt > 0) { 555 | output = ','; 556 | } 557 | 558 | output += "{\"type\":\""; 559 | output += (entry.isDirectory()) ? "dir" : "file"; 560 | output += "\",\"name\":\""; 561 | output += entry.name(); 562 | output += "\",\"size\":\""; 563 | output += entry.size(); 564 | output += "\""; 565 | output += "}"; 566 | server.sendContent(output); 567 | entry.close(); 568 | } 569 | server.sendContent("]"); 570 | dir.close(); 571 | } 572 | 573 | void handleNotFound() { 574 | if (hasSD && loadFromSdCard(server.uri())) { 575 | return; 576 | } 577 | String message = "SDCARD Not Detected\n\n"; 578 | message += "URI: "; 579 | message += server.uri(); 580 | message += "\nMethod: "; 581 | message += (server.method() == HTTP_GET) ? "GET" : "POST"; 582 | message += "\nArguments: "; 583 | message += server.args(); 584 | message += "\n"; 585 | for (uint8_t i = 0; i < server.args(); i++) { 586 | message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; 587 | } 588 | server.send(404, "text/plain", message); 589 | Serial.print(message); 590 | } 591 | 592 | void handleOTAresult() { 593 | server.sendHeader("Connection", "close"); 594 | server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); 595 | Serial.println("OTA update success, rebooting..."); 596 | gfx->begin(); 597 | gfx->fillScreen(GREEN); 598 | delay(200); 599 | ESP.restart(); 600 | } 601 | 602 | void handleOTA() { 603 | HTTPUpload& upload = server.upload(); 604 | if (upload.status == UPLOAD_FILE_START) { 605 | gfx->begin(); 606 | gfx->fillScreen(RED); 607 | //Serial.println("Start OTA Update"); 608 | Serial.setDebugOutput(true); 609 | Serial.printf("Update: %s\n", upload.filename.c_str()); 610 | if (!Update.begin()) { //start with max available size 611 | Update.printError(Serial); 612 | } 613 | } else if (upload.status == UPLOAD_FILE_WRITE) { 614 | led_status = (led_status == HIGH)? LOW: HIGH; 615 | digitalWrite(PIN_LED, led_status); 616 | Serial.print("."); 617 | if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 618 | Update.printError(Serial); 619 | } 620 | } else if (upload.status == UPLOAD_FILE_END) { 621 | if (Update.end(true)) { //true to set the size to the current progress 622 | Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); 623 | } else { 624 | Update.printError(Serial); 625 | } 626 | Serial.setDebugOutput(false); 627 | } else { 628 | Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status); 629 | } 630 | } 631 | 632 | void testWriteFile(fs::FS &fs, const char *path, uint8_t *buf, int len) { 633 | unsigned long start_time = millis(); 634 | Serial.printf("Test write %s\n", path); 635 | 636 | File file = fs.open(path, FILE_WRITE); 637 | if (!file) { 638 | Serial.println("Failed to open file for writing"); 639 | return; 640 | } 641 | int loop = 65536 / len; 642 | while (loop--) 643 | { 644 | if (!file.write(buf, len)) { 645 | Serial.println("Write failed"); 646 | return; 647 | } 648 | } 649 | file.flush(); 650 | file.close(); 651 | unsigned long time_used = millis() - start_time; 652 | Serial.printf("Write file used: %d ms, %f KB/s\n", time_used, (float)65536 / time_used); 653 | } 654 | 655 | void testReadFile(fs::FS &fs, const char *path, uint8_t *buf, int len) { 656 | unsigned long start_time = millis(); 657 | Serial.printf("Test read %s\n", path); 658 | 659 | File file = fs.open(path); 660 | if (!file) { 661 | Serial.println("Failed to open file for reading"); 662 | return; 663 | } 664 | int loop = 65536 / len; 665 | while (loop--) 666 | { 667 | if (!file.read(buf, len)) { 668 | Serial.println("Read failed"); 669 | return; 670 | } 671 | } 672 | file.close(); 673 | unsigned long time_used = millis() - start_time; 674 | Serial.printf("Read file used: %d ms, %f KB/s\n", time_used, (float)65536 / time_used); 675 | } 676 | 677 | void testIO(fs::FS &fs) { 678 | /* malloc will not reset all bytes to zero, so it is a random data */ 679 | uint8_t *buf = (uint8_t*)malloc(64 * 1024); 680 | testWriteFile(fs, "/test_1k.bin", buf, 1024); 681 | testReadFile(fs, "/test_1k.bin", buf, 1024); 682 | free(buf); 683 | } 684 | 685 | void printErrorMessage(uint8_t e, bool eol = true) { 686 | switch (e) { 687 | case IniFile::errorNoError: 688 | Serial.print("no error"); 689 | break; 690 | case IniFile::errorFileNotFound: 691 | Serial.print("file not found"); 692 | break; 693 | case IniFile::errorFileNotOpen: 694 | Serial.print("file not open"); 695 | break; 696 | case IniFile::errorBufferTooSmall: 697 | Serial.print("buffer too small"); 698 | break; 699 | case IniFile::errorSeekError: 700 | Serial.print("seek error"); 701 | break; 702 | case IniFile::errorSectionNotFound: 703 | Serial.print("section not found"); 704 | break; 705 | case IniFile::errorKeyNotFound: 706 | Serial.print("key not found"); 707 | break; 708 | case IniFile::errorEndOfFile: 709 | Serial.print("end of file"); 710 | break; 711 | case IniFile::errorUnknownError: 712 | Serial.print("unknown error"); 713 | break; 714 | default: 715 | Serial.print("unknown error value"); 716 | break; 717 | } 718 | if (eol) 719 | Serial.println(); 720 | } 721 | 722 | // LCD Backlight refresh 723 | void lcd_bl_task(void * par) { 724 | int lcd_enable = 0; 725 | int i; 726 | BaseType_t x_stat; 727 | TickType_t x_timeout = pdMS_TO_TICKS(10); 728 | //uint16_t bl; 729 | uint16_t lcd_pwm_filter[LCD_PWM_FIR_LEN]; 730 | uint16_t p_lcd_pwm_filter; 731 | uint32_t sum = 0; 732 | uint16_t avg_map, last_val; 733 | 734 | for (i=0; i 0) { 746 | lcd_pwm_filter[p_lcd_pwm_filter] = analogRead(PIN_SENSOR_ADC); 747 | p_lcd_pwm_filter++; 748 | if (p_lcd_pwm_filter == LCD_PWM_FIR_LEN) { 749 | p_lcd_pwm_filter = 0; 750 | } 751 | sum = 0; 752 | for (i=0; i LCD_PWM_CHANGE_MAX_STEP) { 759 | ledcWrite(1, last_val + LCD_PWM_CHANGE_MAX_STEP); 760 | } else if (last_val - avg_map > LCD_PWM_CHANGE_MAX_STEP) { 761 | ledcWrite(1, last_val - LCD_PWM_CHANGE_MAX_STEP); 762 | } else { 763 | ledcWrite(1, avg_map); 764 | } 765 | last_val = avg_map; 766 | //Serial.println(avg_map); 767 | } else { 768 | ledcWrite(1, 0); 769 | } 770 | vTaskDelay(pdMS_TO_TICKS(16)); // ~60Hz 771 | } 772 | } 773 | 774 | void led_blink_task(void * par) { 775 | int led_blink_cnt = 1; 776 | int i = 0; 777 | BaseType_t x_stat; 778 | TickType_t x_timeout = pdMS_TO_TICKS(100); 779 | 780 | while(1) { 781 | //Serial.println(ESP.getFreeHeap()); 782 | 783 | x_stat = xQueueReceive(led_queue, &led_blink_cnt, x_timeout); 784 | //if (x_stat != pdPASS) { 785 | // led_blink_cnt = 1; 786 | //} 787 | for (i=0; iwriteValue(newValue.c_str(), newValue.length()); 828 | //} 829 | } 830 | 831 | delay(500); 832 | } 833 | 834 | // 由于我也不知道是itag的状态机bug 还是抄来的代码bug 还是我写的bug, 835 | // itag断连后重连就会崩 836 | // 所以就暂且让它能够在每次开机的第一次连接成功。。屎山上打补丁 837 | while(1) { 838 | Serial.println("BLE: unexpected error, needs reset"); 839 | delay(10000); // hot glue for BLE connection bug 840 | } 841 | } 842 | 843 | void lock_screen_handler(bool reset_ble) { 844 | int lcd_bl_en = 0; 845 | uint64_t ts_down, ts_up; 846 | 847 | ts_down = millis(); 848 | while(digitalRead(PIN_KEY) == LOW); 849 | ts_up = millis(); 850 | if (ts_up - ts_down > LOCK_SCREEN_TIME_THRESH_MS) { 851 | lcd_bl_en = BL_LEVEL_MAX; 852 | Serial.println("Light sleep start, but keep LCD on"); 853 | } else { 854 | Serial.println("Light sleep start, turn off LCD"); 855 | } 856 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 857 | digitalWrite(PIN_LED, LOW); delay(50); digitalWrite(PIN_LED, HIGH); 858 | if (lcd_bl_en == 0) { 859 | gfx->displayOff(); 860 | } 861 | lightsleep_ms = millis(); 862 | gpio_wakeup_enable((gpio_num_t)PIN_KEY, GPIO_INTR_LOW_LEVEL); 863 | gpio_hold_en(( gpio_num_t ) PIN_LCD_PWR_EN ); 864 | gpio_hold_en(( gpio_num_t ) PIN_TFT_BL ); 865 | esp_sleep_enable_gpio_wakeup(); 866 | 867 | esp_light_sleep_start(); //============================ 868 | 869 | lightsleep_ms = millis() - lightsleep_ms; 870 | Serial.print("Wakeup from light sleep, "); 871 | Serial.print(lightsleep_ms); Serial.println("ms"); 872 | gpio_hold_dis(( gpio_num_t ) PIN_LCD_PWR_EN ); 873 | gpio_hold_dis(( gpio_num_t ) PIN_TFT_BL ); 874 | if (lcd_bl_en == 0) { 875 | gfx->begin(); 876 | } 877 | if (reset_ble) { 878 | vTaskDelete(BLE_TaskHandle); 879 | BLEDevice::init(""); 880 | xTaskCreatePinnedToCore( 881 | ble_task_loop, "BLE_task", 882 | 8192, NULL, 1, 883 | &BLE_TaskHandle, 0); 884 | } 885 | while(digitalRead(PIN_KEY) == LOW); // wait for key release 886 | delay(50); // ~20fps, 1 frame 887 | lcd_bl_en = 1; 888 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 889 | } 890 | 891 | void check_battery_then_deep_sleep() { 892 | if (analogReadMilliVolts(PIN_ADC_BAT) * 2 < BAT_ADC_THRESH_LOW_MV) { 893 | // just go sleep if battery low 894 | Serial.println("battery low, deep sleep start"); 895 | esp_sleep_enable_timer_wakeup(DEEP_SLEEP_LONG_S * uS_TO_S_FACTOR); 896 | set_io_before_deep_sleep(); 897 | esp_deep_sleep_start(); 898 | } 899 | } 900 | 901 | void setup() 902 | { 903 | int i, k; 904 | String conf_gifname = CONF_GIFNAME_DEFAULT; 905 | // bool conf_gravity = CONF_GRAVITY_DEFAULT; 906 | int conf_lcd_rotation = CONF_LCD_ROTATION_DEFAULT; 907 | bool conf_loop_mode = CONF_LOOP_MODE_DEFAULT; 908 | bool conf_abloop_en = false; 909 | bool conf_ble_en = false; 910 | bool video_part_a = true; // true=partA, false=partB 911 | int last_key_state = HIGH; 912 | int key_state = HIGH; 913 | File * pvFile; 914 | File vFiles[4]; 915 | int led_blink_cnt = 0; 916 | int lcd_bl_en = 0; 917 | int last_acc_int1 = LOW, acc_int1 = LOW; 918 | 919 | WiFi.mode(WIFI_OFF); 920 | bootCount++; 921 | Serial.begin(115200); 922 | Serial.print("Vision v3.3, SD MJPEG WebServer OTA demo; bootcounter: "); Serial.print(bootCount); 923 | Serial.print(", lastTapped: "); Serial.println(lastTappedCount); 924 | Serial.println("fw: "__DATE__" "__TIME__); 925 | 926 | // disable all PAD HOLD 927 | gpio_deep_sleep_hold_dis(); 928 | gpio_hold_dis(( gpio_num_t ) PIN_LCD_PWR_EN ); 929 | pinMode(PIN_LCD_PWR_EN, OUTPUT); 930 | pinMode(PIN_KEY, INPUT_PULLUP); 931 | pinMode(PIN_LED, OUTPUT); 932 | digitalWrite(PIN_LED, LOW); // startup blink 933 | 934 | // find out why we are working 935 | RESET_REASON rst_reason = rtc_get_reset_reason(0); 936 | esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); 937 | 938 | // init accel. 939 | Wire.setPins(PIN_I2C_SDA, PIN_I2C_SCL); 940 | while (!acce.begin()) { 941 | delay(50); 942 | } 943 | 944 | //Serial.print("accel. chip id : "); Serial.println(acce.getID(), HEX); 945 | //Serial.println("Init accel."); 946 | acce.setRange(DFRobot_LIS2DW12::e2_g); 947 | acce.setPowerMode(DFRobot_LIS2DW12::eContLowPwrLowNoise1_12bit); 948 | acce.setDataRate(DFRobot_LIS2DW12::eRate_100hz); 949 | acce.enableTapDetectionOnZ(true); 950 | acce.enableTapDetectionOnY(true); 951 | //acce.enableTapDetectionOnX(true); 952 | acce.setTapThresholdOnY(0.8); 953 | acce.setTapThresholdOnZ(0.8); 954 | //acce.setTapThresholdOnX(0.8); 955 | acce.setTapDur(3); 956 | acce.setTapMode(DFRobot_LIS2DW12::eBothSingleDouble); 957 | acce.setInt1Event(DFRobot_LIS2DW12::eDoubleTap); 958 | DFRobot_LIS2DW12:: eTap_t tapEvent = acce.tapDetect(); 959 | 960 | // set wakeup by accel. 961 | pinMode(PIN_ACC_INT1, INPUT); 962 | pinMode(PIN_BAT_STDBY, INPUT); 963 | pinMode(PIN_BAT_CHRG, INPUT); 964 | 965 | check_battery_then_deep_sleep(); 966 | 967 | // enable 3v3b & init spi for sdcard 968 | digitalWrite(PIN_LCD_PWR_EN, HIGH); 969 | delay(20); 970 | 971 | gfx->begin(); 972 | gfx->fillScreen(BLACK); 973 | ledcAttachPin(PIN_TFT_BL, 1); 974 | ledcSetup(1, 18000, 12); 975 | ledcWrite(1, 0); 976 | 977 | SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SD_CS); 978 | if (!SD.begin(PIN_SD_CS) && !SD.begin(PIN_SD_CS)) { 979 | Serial.println(F("ERROR: SD card mount failed!")); 980 | } else { 981 | hasSD = true; 982 | //Serial.println(F("SD card mounted")); 983 | uint8_t cardType = FILE_SYSTEM.cardType(); 984 | if (cardType == CARD_NONE) { 985 | Serial.println("No SD_MMC card attached"); 986 | } 987 | } 988 | 989 | 990 | // check config file & generate default if not exist 991 | const size_t buf_len = 128; 992 | char conf_buf[buf_len]; 993 | if (!FILE_SYSTEM.exists(CONFIG_FILENAME)) { 994 | Serial.println("Config file not found, use default"); 995 | File conf_file = FILE_SYSTEM.open(CONFIG_FILENAME, FILE_WRITE); 996 | if (conf_file) { 997 | conf_file.println("[vision]"); 998 | conf_file.print("video="); conf_file.println(CONF_GIFNAME_DEFAULT); 999 | // conf_file.print("gravity="); conf_file.println(CONF_GRAVITY_DEFAULT? "true": "false"); 1000 | conf_file.print("lcd_rotation="); conf_file.println(CONF_LCD_ROTATION_DEFAULT); 1001 | conf_file.print("loop_mode="); conf_file.println(CONF_LOOP_MODE_DEFAULT? "true": "false"); 1002 | conf_file.println("abloop_en=false"); 1003 | conf_file.println("ble_mac=00:12:34:56:78:9a"); 1004 | conf_file.println("# when enable abloop, rename video to "VIDEO_A_SETUP", "VIDEO_B_SETUP", "VIDEO_A_LOOP", "VIDEO_A_LOOP";"); 1005 | conf_file.close(); 1006 | } 1007 | } 1008 | 1009 | IniFile ini(CONFIG_FILENAME); 1010 | if (!ini.open()) { 1011 | Serial.println("config file open error"); 1012 | } 1013 | if (!ini.validate(conf_buf, buf_len)) { 1014 | Serial.println("config file not valid"); 1015 | printErrorMessage(ini.getError()); 1016 | } 1017 | //Serial.println("Reading config file: "); 1018 | if (ini.getValue("vision", "video", conf_buf, buf_len)) { 1019 | conf_gifname = String(conf_buf); 1020 | //Serial.print("vision.video="); 1021 | //Serial.println(conf_gifname); 1022 | } else { 1023 | printErrorMessage(ini.getError()); 1024 | Serial.print(" not found; use vision.video.default="); 1025 | Serial.println(conf_gifname); 1026 | } 1027 | if (ini.getValue("vision", "abloop_en", conf_buf, buf_len, conf_abloop_en)) { 1028 | //Serial.print("vision.abloop_en="); 1029 | //Serial.println(conf_abloop_en ? "true" : "false"); 1030 | } else { 1031 | printErrorMessage(ini.getError()); 1032 | Serial.println(" not found; use vision.abloop_en.default=false"); 1033 | } 1034 | if (conf_abloop_en) { 1035 | if (ini.getValue("vision", "ble_mac", conf_buf, buf_len)) { 1036 | pServerAddress = new BLEAddress(String(conf_buf).c_str()); 1037 | //Serial.print("vision.ble_mac="); 1038 | //Serial.println(String(conf_buf)); 1039 | conf_ble_en = true; 1040 | } else { 1041 | printErrorMessage(ini.getError()); 1042 | Serial.println(" not found."); 1043 | //conf_abloop_en = false; 1044 | } 1045 | } 1046 | /* 1047 | if (ini.getValue("vision", "gravity", conf_buf, buf_len, conf_gravity)) { 1048 | Serial.print("vision.gravity="); 1049 | Serial.println(conf_gravity ? "true" : "false"); 1050 | } else { 1051 | printErrorMessage(ini.getError()); 1052 | Serial.print(" not found; use vision.gravity.default="); 1053 | Serial.println(conf_gravity ? "true" : "false"); 1054 | }*/ 1055 | if (ini.getValue("vision", "loop_mode", conf_buf, buf_len, conf_loop_mode)) { 1056 | //Serial.print("vision.loop_mode="); 1057 | //Serial.println(conf_loop_mode ? "true" : "false"); 1058 | } else { 1059 | printErrorMessage(ini.getError()); 1060 | Serial.print(" not found; use vision.loop_mode.default="); 1061 | Serial.println(conf_loop_mode ? "true" : "false"); 1062 | } 1063 | conf_abloop_en = conf_loop_mode? conf_abloop_en: false; 1064 | if (ini.getValue("vision", "lcd_rotation", conf_buf, buf_len)) { 1065 | conf_lcd_rotation = String(conf_buf).toInt(); 1066 | //Serial.print("vision.lcd_rotation="); 1067 | //Serial.println(conf_lcd_rotation); 1068 | } else { 1069 | printErrorMessage(ini.getError()); 1070 | Serial.print(" not found; use vision.lcd_rotation.default="); 1071 | Serial.println(conf_lcd_rotation); 1072 | } 1073 | 1074 | digitalWrite(PIN_LED, HIGH); // startup blink 1075 | // set lcd backlight task 1076 | lcd_bl_queue = xQueueCreate(8, sizeof(int)); 1077 | xTaskCreatePinnedToCore( 1078 | lcd_bl_task, "LCD_BL_task", 1079 | 4096, NULL, 1, 1080 | &LCD_BL_TaskHandle, 0); 1081 | 1082 | if (rst_reason == DEEPSLEEP_RESET) { 1083 | 1084 | gfx->setRotation(conf_lcd_rotation); 1085 | 1086 | // init ble 1087 | if (conf_ble_en) { 1088 | BLEDevice::init(""); 1089 | BLEDevice::setPower(ESP_PWR_LVL_P3); 1090 | xTaskCreatePinnedToCore( 1091 | ble_task_loop, "BLE_task", 1092 | 8192, NULL, 1, 1093 | &BLE_TaskHandle, 0); 1094 | } 1095 | 1096 | // set led task 1097 | led_queue = xQueueCreate(8, sizeof(int)); 1098 | xTaskCreatePinnedToCore( 1099 | led_blink_task, "LED_task", 1100 | 4096, NULL, 1, 1101 | &LED_TaskHandle, 0); 1102 | 1103 | lcd_bl_en = 1; 1104 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 1105 | 1106 | if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) { 1107 | lastTappedCount = bootCount; 1108 | } 1109 | 1110 | mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 1111 | if (!mjpeg_buf) { 1112 | Serial.println(F("mjpeg_buf malloc failed!")); 1113 | } 1114 | if (conf_abloop_en) { 1115 | vFiles[0] = FILE_SYSTEM.open(VIDEO_A_SETUP); 1116 | vFiles[1] = FILE_SYSTEM.open(VIDEO_A_LOOP); 1117 | vFiles[2] = FILE_SYSTEM.open(VIDEO_B_SETUP); 1118 | vFiles[3] = FILE_SYSTEM.open(VIDEO_B_LOOP); 1119 | pvFile = &vFiles[1]; 1120 | for (i=0; i<4; i++) { 1121 | if (!vFiles[i]) { 1122 | conf_abloop_en = false; 1123 | Serial.println("abloop file error, disable"); 1124 | } 1125 | } 1126 | } 1127 | if (!conf_abloop_en) { 1128 | vFiles[0] = FILE_SYSTEM.open(conf_gifname); 1129 | pvFile = &vFiles[0]; 1130 | } 1131 | if (!(*pvFile) || pvFile->isDirectory()) { 1132 | Serial.println(F("ERROR: Failed to open GIF file for reading")); 1133 | } else { 1134 | mjpeg.setup(pvFile, mjpeg_buf, drawMCU, false, true); 1135 | Serial.println("MJPEG video start"); 1136 | 1137 | if (conf_loop_mode) { 1138 | while (1) { 1139 | mjpeg.readMjpegBuf(); // 1. read, decode & render jpeg frame; 1140 | mjpeg.drawJpg(); 1141 | //Serial.print("."); 1142 | 1143 | last_acc_int1 = acc_int1; 1144 | acc_int1 = digitalRead(PIN_ACC_INT1); 1145 | if (ble_key_pressed // 2. check BLE itag & acce status 1146 | || (acc_int1 == HIGH && last_acc_int1 == LOW)) { 1147 | ble_key_pressed = false; 1148 | tapEvent = acce.tapDetect(); 1149 | if (pvFile == &vFiles[1] || pvFile == &vFiles[3]) { 1150 | // only accept switch a/b when looping 1151 | video_part_a = !video_part_a; 1152 | //Serial.print("ble video switch "); 1153 | Serial.println(video_part_a? "part a": "part b"); 1154 | led_blink_cnt = (video_part_a)? 1: 2; 1155 | xQueueSendToFront(led_queue, &led_blink_cnt, pdMS_TO_TICKS(1)); 1156 | } 1157 | } 1158 | 1159 | check_battery_then_deep_sleep(); // 3. check battery voltage 1160 | 1161 | last_key_state = key_state; // 4. check key 'lock screen' 1162 | key_state = digitalRead(PIN_KEY); 1163 | if (last_key_state == LOW && key_state == LOW) { 1164 | lastTappedCount = bootCount; 1165 | //lock_screen_handler(conf_abloop_en); 1166 | lock_screen_handler(false); 1167 | } 1168 | 1169 | SD.begin(PIN_SD_CS); // 5. hardware magics 1170 | 1171 | // 6. set file position 1172 | if (conf_abloop_en) { 1173 | if (video_part_a) { 1174 | // 6.1 needs to switch to video A 1175 | // 6.1.1 it's looping A now, then just loop A 1176 | if (pvFile == &vFiles[1] && (!pvFile->available()) ) { 1177 | mjpeg.updateFilePointer(pvFile); 1178 | pvFile->seek(0); 1179 | //Serial.println("from A loop end to A loop"); 1180 | } 1181 | // 6.1.2 it's looping B, when it ends(not available), jump to A setup 1182 | if (pvFile == &vFiles[3] && (!pvFile->available()) ) { 1183 | pvFile = &vFiles[0]; 1184 | mjpeg.updateFilePointer(pvFile); 1185 | pvFile->seek(0); 1186 | //Serial.println("from B loop end to A setup"); 1187 | } 1188 | } else { 1189 | // 6.2 needs to switch to video B 1190 | // 6.2.1 it's looping B now, keep it 1191 | if (pvFile == &vFiles[3] && (!pvFile->available()) ) { 1192 | mjpeg.updateFilePointer(pvFile); 1193 | pvFile->seek(0); 1194 | //Serial.println("from B loop to B loop"); 1195 | } 1196 | // 6.2.2 or "when A loop ends, jump to B setup" 1197 | if (pvFile == &vFiles[1] && (!pvFile->available()) ) { 1198 | pvFile = &vFiles[2]; 1199 | mjpeg.updateFilePointer(pvFile); 1200 | pvFile->seek(0); 1201 | //Serial.println("from A loop to B setup"); 1202 | } 1203 | } 1204 | // and, play loop after setup 1205 | if (pvFile == &vFiles[0] && (!pvFile->available()) ) { 1206 | pvFile = &vFiles[1]; 1207 | pvFile->seek(0); 1208 | mjpeg.updateFilePointer(pvFile); 1209 | //Serial.println("from A setup to A loop"); 1210 | } 1211 | if (pvFile == &vFiles[2] && (!pvFile->available()) ) { 1212 | pvFile = &vFiles[3]; 1213 | pvFile->seek(0); 1214 | mjpeg.updateFilePointer(pvFile); 1215 | //Serial.println("from B setup to B loop"); 1216 | } 1217 | 1218 | } else { 1219 | if (!pvFile->available()) { 1220 | pvFile->seek(0); // ble disable, simple loop 1221 | mjpeg.updateFilePointer(pvFile); 1222 | } 1223 | } 1224 | } 1225 | } else { 1226 | // loop_mode=false 1227 | while (pvFile->available()) { 1228 | mjpeg.readMjpegBuf(); 1229 | mjpeg.drawJpg(); 1230 | last_key_state = key_state; 1231 | key_state = digitalRead(PIN_KEY); 1232 | if (last_key_state == LOW && key_state == LOW) { 1233 | lastTappedCount = bootCount; 1234 | } 1235 | SD.begin(PIN_SD_CS); 1236 | } 1237 | }// loop_mode=false 1238 | Serial.println("MJPEG video end"); 1239 | //vFile.close(); 1240 | pvFile = NULL; 1241 | for (k=0; k<4; k++) { 1242 | if (!vFiles[k]) { 1243 | vFiles[k].close(); 1244 | } 1245 | } 1246 | } 1247 | lcd_bl_en = 0; 1248 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 1249 | ledcDetachPin(PIN_TFT_BL); 1250 | digitalWrite(PIN_LED, HIGH); 1251 | Serial.println(F("deep sleep")); 1252 | 1253 | if (bootCount - lastTappedCount < DEEP_SLEEP_SHORT_CNT) { 1254 | esp_sleep_enable_timer_wakeup(DEEP_SLEEP_SHORT_S * uS_TO_S_FACTOR); 1255 | } else { 1256 | esp_sleep_enable_timer_wakeup(DEEP_SLEEP_LONG_S * uS_TO_S_FACTOR); 1257 | } 1258 | gfx->displayOff(); 1259 | set_io_before_deep_sleep(); 1260 | esp_deep_sleep_start(); 1261 | 1262 | } else { 1263 | gfx->begin(); 1264 | gfx->fillScreen(LIGHTGREY);SD.begin(PIN_SD_CS); 1265 | // poweron reset, start wi-fi ap 1266 | Serial.println("Power on reset (maybe); "); 1267 | Serial.print("Start "); 1268 | Serial.println(wifi_ssid); 1269 | WiFi.mode(WIFI_STA); 1270 | WiFi.setTxPower(WIFI_POWER_MINUS_1dBm); 1271 | WiFi.begin(wifi_ssid, wifi_pwd); 1272 | lcd_bl_en = 1; 1273 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 1274 | 1275 | int wifi_cnt = 0; 1276 | while (WiFi.status() != WL_CONNECTED ) { 1277 | digitalWrite(PIN_LED, led_status); 1278 | led_status = (led_status == HIGH)? LOW: HIGH; 1279 | delay(250); 1280 | Serial.print("."); 1281 | wifi_cnt++; 1282 | if (wifi_cnt > 4 * WIFI_CONNECT_TIMEOUT_S) { 1283 | Serial.println("WiFi timeout"); 1284 | break; 1285 | } 1286 | if (digitalRead(PIN_KEY) == LOW) { 1287 | delay(20); 1288 | if (digitalRead(PIN_KEY) == LOW) { 1289 | Serial.println("Wi-Fi skipped by key"); 1290 | break; 1291 | } 1292 | } 1293 | } 1294 | digitalWrite(PIN_LED, HIGH); 1295 | server.on("/status", HTTP_GET, getStatus); 1296 | server.on("/list", HTTP_GET, printDirectory); 1297 | server.on("/edit", HTTP_DELETE, handleDelete); 1298 | server.on("/edit", HTTP_PUT, handleCreate); 1299 | server.on("/edit", HTTP_POST, []() { 1300 | returnOK(); 1301 | }, handleFileUpload); 1302 | server.onNotFound(handleNotFound); 1303 | server.on("/update", HTTP_POST, handleOTAresult, handleOTA); 1304 | 1305 | if (WiFi.status() == WL_CONNECTED) { 1306 | Serial.println("WiFi connected"); 1307 | Serial.println("IP address: "); 1308 | Serial.println(WiFi.localIP()); 1309 | server.begin(); 1310 | if (!MDNS.begin(wifi_host)) { 1311 | Serial.println("Error setting up MDNS responder!"); 1312 | } 1313 | MDNS.addService("http", "tcp", 80); 1314 | 1315 | gfx->fillScreen(BLUE); SD.begin(PIN_SD_CS); 1316 | } else { 1317 | // wifi not connected 1318 | } 1319 | } 1320 | } 1321 | 1322 | // 这个setup好tm长啊 1323 | void loop() 1324 | { 1325 | bool exit_loop = false; 1326 | int lcd_bl_en; 1327 | 1328 | if (WiFi.status() == WL_CONNECTED) { 1329 | server.handleClient(); 1330 | delay(2);//allow the cpu to switch to other tasks 1331 | 1332 | lcd_bl_en = 1; 1333 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 1334 | 1335 | // 退出webserver进入深睡条件:按下按键 1336 | if (digitalRead(PIN_KEY) == LOW) { 1337 | delay(20); 1338 | if (digitalRead(PIN_KEY) == LOW) { 1339 | exit_loop = true; 1340 | } 1341 | } 1342 | } else { 1343 | // 如果没有连接就进到loop,说明 a.前面尝试连接wifi炒屎了 或 b.wifi断开了 1344 | // 就直接睡眠了准备正常工作 1345 | exit_loop = true; 1346 | } 1347 | 1348 | if (exit_loop) { 1349 | Serial.println(F("init done, deep sleep now")); 1350 | gfx->begin(); 1351 | lcd_bl_en = 1; 1352 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 1353 | gfx->fillScreen(GREEN); 1354 | delay(500); 1355 | gfx->fillScreen(BLACK); 1356 | lcd_bl_en = 0; 1357 | xQueueSendToFront(lcd_bl_queue, &lcd_bl_en, pdMS_TO_TICKS(1)); 1358 | ledcDetachPin(PIN_TFT_BL); 1359 | delay(5); 1360 | gfx->displayOff(); 1361 | esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR); 1362 | set_io_before_deep_sleep(); 1363 | esp_deep_sleep_start(); 1364 | } 1365 | 1366 | } 1367 | -------------------------------------------------------------------------------- /src/vision_sdcard_mjpeg/vision_sdcard_mjpeg.no_ble.78b3749.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Vision V3.3 SD MJPEG WebServer OTA DEMO 3 | Github: libc0607/esp32-vision 4 | 5 | ref: 6 | https://github.com/moononournation/RGB565_video 7 | examples/SDWebServer 8 | examples/OTAWebUpdater 9 | 10 | WebServer bring-up: 11 | curl -X POST -F "file=@ota.htm" http://vision.local/edit 12 | curl -X POST -F "file=@index.htm" http://vision.local/edit 13 | curl -X POST -F "file=@edit.htm" http://vision.local/edit 14 | 15 | How-to in Arduino IDE: 16 | Choose: 17 | ESP32 Dev Module, 240MHz CPU, 80MHz Flash, DIO, 4MB(32Mb), 18 | default with spiffs, PSRAM Disable; 19 | upload; 20 | 21 | Dependencies: 22 | Install espressif/arduino-esp32 (V1.0.6, due to compatibility issue) first 23 | moononournation/Arduino_GFX 24 | bitbank2/JPEGDEC 25 | DFRobot/DFRobot_LIS (see tapInterrupt example) 26 | stevemarple/IniFile 27 | 28 | Config ini example: 29 | 30 | [vision] 31 | video=/loop.mjpeg 32 | lcd_rotation=0 33 | loop_mode=false 34 | 35 | This is just an EARLY DEMO to verify all parts of the 36 | hardware can work properly. 37 | Many bugs. No guarantee for the performance of this code. 38 | 就是抄了一大堆例程,并小心地让它们能一起工作而已。很烂,轻喷 39 | 40 | */ 41 | 42 | #define CONFIG_FILENAME "/config.txt" 43 | const char* wifi_ssid = "Celestia"; 44 | const char* wifi_pwd = "mimitomo"; 45 | const char* wifi_host = "vision"; 46 | #define CONF_GIFNAME_DEFAULT "/loop.mjpeg" 47 | #define CONF_TARGET_FPS_DEFAULT 15 48 | //#define CONF_GRAVITY_DEFAULT true 49 | #define CONF_LCD_ROTATION_DEFAULT 0 50 | #define CONF_LOOP_MODE_DEFAULT true 51 | #define DEEP_SLEEP_LONG_S 30 52 | #define DEEP_SLEEP_SHORT_S 2 53 | #define DEEP_SLEEP_SHORT_CNT 6 54 | #define TEMP_PROTECT_HIGH_THRESH_12B 1840 // ~60°C @10k,B3380 55 | #define LCD_BACKLIGHT_MIN_8B 16 56 | #define LCD_PWM_FIR_LEN 32 57 | 58 | 59 | #define FILE_SYSTEM SD 60 | 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | 82 | 83 | 84 | #define PIN_MISO 2 // sdcard 85 | #define PIN_SCK 14 // sdcard 86 | #define PIN_MOSI 15 // sdcard 87 | #define PIN_SD_CS 13 // sdcard 88 | #define PIN_TFT_CS 5 // LCD 89 | #define PIN_TFT_BL 22 // LCD 90 | #define PIN_TFT_DC 27 // LCD 91 | #define PIN_TFT_RST 33 // LCD 92 | #define PIN_LCD_PWR_EN 19 // LCD, SDcard & temt6000 3v3 power (SY6280AAC on board) 93 | #define PIN_ADC_BAT 35 // battery voltage, with 1M/1M resistor dividor on hardware 94 | #define PIN_TEMP_ADC 36 // 95 | #define PIN_BAT_STDBY 37 // cn3165 standby, low == charge finish, ext. pull-up 96 | #define PIN_BAT_CHRG 38 // cn3165 chrg, low == charging, ext. pull-up 97 | #define PIN_ACC_INT1 39 // from accelerometer, push-pull 98 | #define PIN_ACC_INT2 32 // not used 99 | #define PIN_SENSOR_ADC 34 // light sensor (temt6000), to GND if not connected 100 | #define PIN_I2C_SCL 25 // to accelerometer 101 | #define PIN_I2C_SDA 26 // to accelerometer 102 | #define PIN_LED 23 // ext. led 103 | #define PIN_KEY 18 // ext. key 104 | 105 | #define WIFI_CONNECT_TIMEOUT_S 20 106 | #define uS_TO_S_FACTOR (1000000ULL) 107 | #define BAT_ADC_THRESH_LOW (1700) // analogRead( 1/2*Vbat ) 108 | #define BAT_ADC_THRESH_WARN (1880) // analogRead( 1/2*Vbat ) 109 | #define SENSOR_DISWIFI_THRESH_8B 10 110 | #define ACCE_DISWIFI_Z_THRESH 850 // while g~980 111 | #define ACCE_ORIENTATION_THRESH 850 112 | //#define FPS 24 // but still lots of frame skipped. haiyaa why r u so weak 113 | #define MJPEG_BUFFER_SIZE (240 * 240 * 2 / 4) 114 | 115 | Arduino_DataBus *bus = new Arduino_ESP32SPI(PIN_TFT_DC/* DC */, PIN_TFT_CS /* CS */, PIN_SCK, PIN_MOSI, PIN_MISO, VSPI, true ); 116 | Arduino_GC9A01 *gfx = new Arduino_GC9A01(bus, PIN_TFT_RST, 2, true); 117 | //Arduino_ST7789 *gfx = new Arduino_ST7789(bus, PIN_TFT_RST, 0, true, 240, 240, 0, 0, 0, 80); 118 | 119 | DFRobot_LIS2DW12_I2C acce(&Wire, 0x19); // sdo/sa0 internal pull-up 120 | 121 | 122 | #include "MjpegClass.h" 123 | static MjpegClass mjpeg; 124 | uint8_t *mjpeg_buf; 125 | 126 | unsigned long lightsleep_ms; 127 | uint8_t lcd_pwm_filter[LCD_PWM_FIR_LEN] = {LCD_BACKLIGHT_MIN_8B}; 128 | uint8_t p_lcd_pwm_filter; 129 | 130 | WebServer server(80); 131 | static bool hasSD = false; 132 | File uploadFile; 133 | uint8_t lcd_brightness; 134 | int led_status; 135 | 136 | RTC_DATA_ATTR int bootCount = 0; 137 | RTC_DATA_ATTR int lastTappedCount = 0; 138 | RTC_DATA_ATTR uint8_t lcd_brightness_by_key = LCD_BACKLIGHT_MIN_8B; 139 | 140 | RTC_DATA_ATTR int loop_mode_coldboot = 0; 141 | #define COLDBOOT_Y_NOT 0 142 | #define COLDBOOT_Y_UP 1 143 | #define COLDBOOT_Y_DOWN 2 144 | 145 | // pixel drawing callback 146 | static int drawMCU(JPEGDRAW *pDraw) 147 | { 148 | // Serial.printf("Draw pos = %d,%d. size = %d x %d\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); 149 | gfx->draw16bitBeRGBBitmap(pDraw->x, pDraw->y, pDraw->pPixels, pDraw->iWidth, pDraw->iHeight); 150 | return 1; 151 | } /* drawMCU() */ 152 | 153 | void set_io_before_deep_sleep() { 154 | 155 | pinMode(PIN_TFT_BL, INPUT); 156 | pinMode(PIN_TFT_CS, INPUT); 157 | pinMode(PIN_TFT_RST, INPUT); 158 | pinMode(PIN_MISO, INPUT); 159 | pinMode(PIN_SCK, INPUT); 160 | pinMode(PIN_MOSI, INPUT); 161 | pinMode(PIN_SD_CS, INPUT); 162 | pinMode(PIN_TFT_DC, INPUT); 163 | pinMode(1, INPUT); 164 | pinMode(3, INPUT); 165 | pinMode(PIN_I2C_SCL, INPUT); 166 | pinMode(PIN_I2C_SDA, INPUT); 167 | pinMode(PIN_ACC_INT2, INPUT); 168 | pinMode(PIN_ADC_BAT, INPUT); 169 | pinMode(PIN_TEMP_ADC, INPUT); 170 | pinMode(PIN_SENSOR_ADC, INPUT); 171 | 172 | pinMode(PIN_ACC_INT1, INPUT); 173 | pinMode(PIN_BAT_STDBY, INPUT); 174 | pinMode(PIN_BAT_CHRG, INPUT); 175 | 176 | pinMode(PIN_KEY, INPUT_PULLUP); 177 | pinMode(PIN_LED, INPUT); 178 | 179 | esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_ACC_INT1 , HIGH); 180 | //esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BAT_STDBY , LOW); 181 | //esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BAT_CHRG , LOW); 182 | 183 | digitalWrite(PIN_LCD_PWR_EN, LOW); 184 | gpio_hold_en(( gpio_num_t ) PIN_LCD_PWR_EN ); 185 | gpio_deep_sleep_hold_en(); 186 | esp_wifi_stop(); 187 | //adc_power_release(); 188 | } 189 | 190 | void returnOK() { 191 | server.send(200, "text/plain", ""); 192 | } 193 | 194 | void returnFail(String msg) { 195 | server.send(500, "text/plain", msg + "\r\n"); 196 | } 197 | 198 | bool loadFromSdCard(String path) { 199 | String dataType = "text/plain"; 200 | if (path.endsWith("/")) { 201 | path += "index.htm"; 202 | } 203 | 204 | if (path.endsWith(".src")) { 205 | path = path.substring(0, path.lastIndexOf(".")); 206 | } else if (path.endsWith(".htm")) { 207 | dataType = "text/html"; 208 | } else if (path.endsWith(".css")) { 209 | dataType = "text/css"; 210 | } else if (path.endsWith(".js")) { 211 | dataType = "application/javascript"; 212 | } else if (path.endsWith(".png")) { 213 | dataType = "image/png"; 214 | } else if (path.endsWith(".gif")) { 215 | dataType = "image/gif"; 216 | } else if (path.endsWith(".jpg")) { 217 | dataType = "image/jpeg"; 218 | } else if (path.endsWith(".ico")) { 219 | dataType = "image/x-icon"; 220 | } else if (path.endsWith(".xml")) { 221 | dataType = "text/xml"; 222 | } else if (path.endsWith(".pdf")) { 223 | dataType = "application/pdf"; 224 | } else if (path.endsWith(".zip")) { 225 | dataType = "application/zip"; 226 | } 227 | 228 | File dataFile = FILE_SYSTEM.open(path.c_str()); 229 | if (dataFile.isDirectory()) { 230 | path += "/index.htm"; 231 | dataType = "text/html"; 232 | dataFile = FILE_SYSTEM.open(path.c_str()); 233 | } 234 | 235 | if (!dataFile) { 236 | return false; 237 | } 238 | 239 | if (server.hasArg("download")) { 240 | dataType = "application/octet-stream"; 241 | } 242 | 243 | if (server.streamFile(dataFile, dataType) != dataFile.size()) { 244 | Serial.println("Sent less data than expected!"); 245 | } 246 | 247 | dataFile.close(); 248 | return true; 249 | } 250 | 251 | void handleFileUpload() { 252 | if (server.uri() != "/edit") { 253 | return; 254 | } 255 | HTTPUpload& upload = server.upload(); 256 | String filename = upload.filename; 257 | if (!filename.startsWith("/")) 258 | filename = "/" + filename; 259 | if (upload.status == UPLOAD_FILE_START) { 260 | gfx->fillScreen(ORANGE);SD.begin(PIN_SD_CS); 261 | if (FILE_SYSTEM.exists(filename)) { 262 | FILE_SYSTEM.remove(filename); 263 | } 264 | uploadFile = FILE_SYSTEM.open(filename, FILE_WRITE); 265 | Serial.print("Upload: START, filename: "); Serial.println(filename); 266 | } else if (upload.status == UPLOAD_FILE_WRITE) { 267 | led_status = (led_status == HIGH)? LOW: HIGH; 268 | digitalWrite(PIN_LED, led_status); 269 | if (uploadFile) { 270 | uploadFile.write(upload.buf, upload.currentSize); 271 | } 272 | Serial.print("Upload: WRITE, Bytes: "); Serial.println(upload.currentSize); 273 | } else if (upload.status == UPLOAD_FILE_END) { 274 | if (uploadFile) { 275 | uploadFile.close(); 276 | } 277 | gfx->fillScreen(BLUE);SD.begin(PIN_SD_CS); 278 | Serial.print("Upload: END, Size: "); Serial.println(upload.totalSize); 279 | } 280 | } 281 | 282 | void deleteRecursive(String path) { 283 | File file = FILE_SYSTEM.open((char *)path.c_str()); 284 | if (!file.isDirectory()) { 285 | file.close(); 286 | FILE_SYSTEM.remove((char *)path.c_str()); 287 | return; 288 | } 289 | 290 | file.rewindDirectory(); 291 | while (true) { 292 | File entry = file.openNextFile(); 293 | if (!entry) { 294 | break; 295 | } 296 | String entryPath = path + "/" + entry.name(); 297 | if (entry.isDirectory()) { 298 | entry.close(); 299 | deleteRecursive(entryPath); 300 | } else { 301 | entry.close(); 302 | FILE_SYSTEM.remove((char *)entryPath.c_str()); 303 | } 304 | yield(); 305 | } 306 | 307 | FILE_SYSTEM.rmdir((char *)path.c_str()); 308 | file.close(); 309 | } 310 | 311 | void handleDelete() { 312 | if (server.args() == 0) { 313 | return returnFail("BAD ARGS"); 314 | } 315 | String path = server.arg(0); 316 | if (path == "/" || !FILE_SYSTEM.exists((char *)path.c_str())) { 317 | returnFail("BAD PATH"); 318 | return; 319 | } 320 | deleteRecursive(path); 321 | returnOK(); 322 | } 323 | 324 | void handleCreate() { 325 | if (server.args() == 0) { 326 | return returnFail("BAD ARGS"); 327 | } 328 | String path = server.arg(0); 329 | if (path == "/" || FILE_SYSTEM.exists((char *)path.c_str())) { 330 | returnFail("BAD PATH"); 331 | return; 332 | } 333 | 334 | if (path.indexOf('.') > 0) { 335 | File file = FILE_SYSTEM.open((char *)path.c_str(), FILE_WRITE); 336 | if (file) { 337 | file.write(0); 338 | file.close(); 339 | } 340 | } else { 341 | FILE_SYSTEM.mkdir((char *)path.c_str()); 342 | } 343 | returnOK(); 344 | } 345 | 346 | void getStatus() { 347 | 348 | // A human-readable system status API 349 | 350 | String stat = ""; 351 | 352 | // card type 353 | stat += "SD_Type,"; 354 | uint8_t cardType = FILE_SYSTEM.cardType(); 355 | if (cardType == CARD_NONE) { 356 | stat += "No_Card"; 357 | } else if (cardType == CARD_MMC) { 358 | stat += "SD_MMC"; 359 | } else if (cardType == CARD_SD) { 360 | stat += "SD_SC"; 361 | } else if (cardType == CARD_SDHC) { 362 | stat += "SD_HC"; 363 | } else { 364 | stat += "Card_UNKNOWN"; 365 | } 366 | stat += ","; 367 | 368 | // card space 369 | stat += "Total_MB,"; 370 | stat += String(int(FILE_SYSTEM.totalBytes() / (1024 * 1024))); 371 | stat += "MB,Used_MB,"; 372 | stat += String(int(FILE_SYSTEM.usedBytes() / (1024 * 1024))); 373 | stat += "MB,"; 374 | // Battery info 375 | stat += "Battery_ADC,"; 376 | stat += analogRead(PIN_ADC_BAT); 377 | stat += ",Charger,"; 378 | if (digitalRead(PIN_BAT_CHRG) == LOW) { 379 | stat += "charging,"; 380 | } else if (digitalRead(PIN_BAT_STDBY) == LOW) { 381 | stat += "full,"; 382 | } else { 383 | stat += "not_charging,"; 384 | } 385 | 386 | // light sensor info 387 | stat += "Light_ADC,"; 388 | stat += analogRead(PIN_SENSOR_ADC); 389 | 390 | // acce. 391 | stat += ",Acc_X,"; 392 | stat += acce.readAccX(); 393 | stat += ",Acc_Y,"; 394 | stat += acce.readAccY(); 395 | stat += ",Acc_Z,"; 396 | stat += acce.readAccZ(); 397 | 398 | stat += ",\r\n"; 399 | server.send(200, "text/plain", stat); 400 | } 401 | 402 | void printDirectory() { 403 | if (!server.hasArg("dir")) { 404 | return returnFail("BAD ARGS"); 405 | } 406 | String path = server.arg("dir"); 407 | //if (path != "/" && !SD.exists((char *)path.c_str())) { 408 | if (path != "/" && !FILE_SYSTEM.exists((char *)path.c_str())) { 409 | return returnFail("BAD PATH"); 410 | } 411 | //File dir = SD.open((char *)path.c_str()); 412 | File dir = FILE_SYSTEM.open((char *)path.c_str()); 413 | path = String(); 414 | if (!dir.isDirectory()) { 415 | dir.close(); 416 | return returnFail("NOT DIR"); 417 | } 418 | dir.rewindDirectory(); 419 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 420 | server.send(200, "text/json", ""); 421 | WiFiClient client = server.client(); 422 | 423 | server.sendContent("["); 424 | for (int cnt = 0; true; ++cnt) { 425 | File entry = dir.openNextFile(); 426 | if (!entry) { 427 | break; 428 | } 429 | 430 | String output; 431 | if (cnt > 0) { 432 | output = ','; 433 | } 434 | 435 | output += "{\"type\":\""; 436 | output += (entry.isDirectory()) ? "dir" : "file"; 437 | output += "\",\"name\":\""; 438 | output += entry.name(); 439 | output += "\",\"size\":\""; 440 | output += entry.size(); 441 | output += "\""; 442 | output += "}"; 443 | server.sendContent(output); 444 | entry.close(); 445 | } 446 | server.sendContent("]"); 447 | dir.close(); 448 | } 449 | 450 | void handleNotFound() { 451 | if (hasSD && loadFromSdCard(server.uri())) { 452 | return; 453 | } 454 | String message = "SDCARD Not Detected\n\n"; 455 | message += "URI: "; 456 | message += server.uri(); 457 | message += "\nMethod: "; 458 | message += (server.method() == HTTP_GET) ? "GET" : "POST"; 459 | message += "\nArguments: "; 460 | message += server.args(); 461 | message += "\n"; 462 | for (uint8_t i = 0; i < server.args(); i++) { 463 | message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; 464 | } 465 | server.send(404, "text/plain", message); 466 | Serial.print(message); 467 | } 468 | 469 | void handleOTAresult() { 470 | server.sendHeader("Connection", "close"); 471 | server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); 472 | Serial.println("OTA update success, rebooting..."); 473 | gfx->begin(); 474 | gfx->fillScreen(GREEN); 475 | delay(200); 476 | ESP.restart(); 477 | } 478 | 479 | void handleOTA() { 480 | HTTPUpload& upload = server.upload(); 481 | if (upload.status == UPLOAD_FILE_START) { 482 | gfx->begin(); 483 | gfx->fillScreen(RED); 484 | //Serial.println("Start OTA Update"); 485 | Serial.setDebugOutput(true); 486 | Serial.printf("Update: %s\n", upload.filename.c_str()); 487 | if (!Update.begin()) { //start with max available size 488 | Update.printError(Serial); 489 | } 490 | } else if (upload.status == UPLOAD_FILE_WRITE) { 491 | led_status = (led_status == HIGH)? LOW: HIGH; 492 | digitalWrite(PIN_LED, led_status); 493 | Serial.print("."); 494 | if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 495 | Update.printError(Serial); 496 | } 497 | } else if (upload.status == UPLOAD_FILE_END) { 498 | if (Update.end(true)) { //true to set the size to the current progress 499 | Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); 500 | } else { 501 | Update.printError(Serial); 502 | } 503 | Serial.setDebugOutput(false); 504 | } else { 505 | Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status); 506 | } 507 | } 508 | 509 | void testWriteFile(fs::FS &fs, const char *path, uint8_t *buf, int len) { 510 | unsigned long start_time = millis(); 511 | Serial.printf("Test write %s\n", path); 512 | 513 | File file = fs.open(path, FILE_WRITE); 514 | if (!file) { 515 | Serial.println("Failed to open file for writing"); 516 | return; 517 | } 518 | int loop = 65536 / len; 519 | while (loop--) 520 | { 521 | if (!file.write(buf, len)) { 522 | Serial.println("Write failed"); 523 | return; 524 | } 525 | } 526 | file.flush(); 527 | file.close(); 528 | unsigned long time_used = millis() - start_time; 529 | Serial.printf("Write file used: %d ms, %f KB/s\n", time_used, (float)65536 / time_used); 530 | } 531 | 532 | void testReadFile(fs::FS &fs, const char *path, uint8_t *buf, int len) { 533 | unsigned long start_time = millis(); 534 | Serial.printf("Test read %s\n", path); 535 | 536 | File file = fs.open(path); 537 | if (!file) { 538 | Serial.println("Failed to open file for reading"); 539 | return; 540 | } 541 | int loop = 65536 / len; 542 | while (loop--) 543 | { 544 | if (!file.read(buf, len)) { 545 | Serial.println("Read failed"); 546 | return; 547 | } 548 | } 549 | file.close(); 550 | unsigned long time_used = millis() - start_time; 551 | Serial.printf("Read file used: %d ms, %f KB/s\n", time_used, (float)65536 / time_used); 552 | } 553 | 554 | void testIO(fs::FS &fs) { 555 | /* malloc will not reset all bytes to zero, so it is a random data */ 556 | uint8_t *buf = (uint8_t*)malloc(64 * 1024); 557 | testWriteFile(fs, "/test_1k.bin", buf, 1024); 558 | testReadFile(fs, "/test_1k.bin", buf, 1024); 559 | free(buf); 560 | } 561 | 562 | void printErrorMessage(uint8_t e, bool eol = true) { 563 | switch (e) { 564 | case IniFile::errorNoError: 565 | Serial.print("no error"); 566 | break; 567 | case IniFile::errorFileNotFound: 568 | Serial.print("file not found"); 569 | break; 570 | case IniFile::errorFileNotOpen: 571 | Serial.print("file not open"); 572 | break; 573 | case IniFile::errorBufferTooSmall: 574 | Serial.print("buffer too small"); 575 | break; 576 | case IniFile::errorSeekError: 577 | Serial.print("seek error"); 578 | break; 579 | case IniFile::errorSectionNotFound: 580 | Serial.print("section not found"); 581 | break; 582 | case IniFile::errorKeyNotFound: 583 | Serial.print("key not found"); 584 | break; 585 | case IniFile::errorEndOfFile: 586 | Serial.print("end of file"); 587 | break; 588 | case IniFile::errorUnknownError: 589 | Serial.print("unknown error"); 590 | break; 591 | default: 592 | Serial.print("unknown error value"); 593 | break; 594 | } 595 | if (eol) 596 | Serial.println(); 597 | } 598 | 599 | uint8_t get_lcd_brightness_by_adc (int adc_pin) { 600 | return uint8_t(( pow((analogRead(adc_pin)/4096.0), 2.0) * float(255-LCD_BACKLIGHT_MIN_8B) ) + LCD_BACKLIGHT_MIN_8B); 601 | } 602 | 603 | uint8_t lcd_pwm_fir_filter (uint8_t pwm_val) { 604 | uint32_t lcd_pwm_filter_sum = 0; 605 | 606 | lcd_pwm_filter[p_lcd_pwm_filter] = pwm_val; 607 | p_lcd_pwm_filter++; 608 | if (p_lcd_pwm_filter == sizeof(lcd_pwm_filter)) { 609 | p_lcd_pwm_filter = 0; 610 | } 611 | for (int i=0; ibegin(); 709 | if (battery_voltage_adc < BAT_ADC_THRESH_WARN) { 710 | gfx->fillScreen(RED); 711 | delay(200); 712 | } else { 713 | gfx->fillScreen(BLACK); 714 | } 715 | delay(200); 716 | 717 | 718 | ledcAttachPin(PIN_TFT_BL, 1); 719 | ledcSetup(1, 12000, 8); 720 | lcd_brightness = get_lcd_brightness_by_adc(PIN_SENSOR_ADC); 721 | for (i=0; ifillScreen(BLACK); 729 | digitalWrite(PIN_TFT_CS, HIGH); 730 | 731 | SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SD_CS); 732 | //SPIClass spi = SPIClass(HSPI); 733 | //spi.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SD_CS); 734 | //pinMode(PIN_MOSI, INPUT_PULLUP); 735 | //pinMode(PIN_SCK, INPUT_PULLUP); 736 | //pinMode(PIN_MISO, INPUT_PULLUP); 737 | //pinMode(PIN_SD_CS, INPUT_PULLUP); 738 | //if (!SD_MMC.begin("/sdcard", true)) { 739 | //if (!SD.begin(PIN_SD_CS, spi, 40000000)) { 740 | if (!SD.begin(PIN_SD_CS) && !SD.begin(PIN_SD_CS)) { 741 | Serial.println(F("ERROR: SD card mount failed!")); 742 | } else { 743 | hasSD = true; 744 | Serial.println(F("SD card mounted")); 745 | uint8_t cardType = FILE_SYSTEM.cardType(); 746 | if (cardType == CARD_NONE) { 747 | Serial.println("No SD_MMC card attached"); 748 | } 749 | Serial.print("SD_MMC Card Type: "); 750 | if (cardType == CARD_MMC) { 751 | Serial.println("MMC"); 752 | } else if (cardType == CARD_SD) { 753 | Serial.println("SDSC"); 754 | } else if (cardType == CARD_SDHC) { 755 | Serial.println("SDHC"); 756 | } else { 757 | Serial.println("UNKNOWN"); 758 | } 759 | Serial.printf("Total space: %lluMB\n", FILE_SYSTEM.totalBytes() / (1024 * 1024)); 760 | Serial.printf("Used space: %lluMB\n", FILE_SYSTEM.usedBytes() / (1024 * 1024)); 761 | //testIO(FILE_SYSTEM); 762 | } 763 | 764 | 765 | // check config file & generate default if not exist 766 | const size_t buf_len = 128; 767 | char conf_buf[buf_len]; 768 | if (!FILE_SYSTEM.exists(CONFIG_FILENAME)) { 769 | Serial.println("Config file not found, use default"); 770 | File conf_file = FILE_SYSTEM.open(CONFIG_FILENAME, FILE_WRITE); 771 | if (conf_file) { 772 | conf_file.println("[vision]"); 773 | conf_file.print("video="); conf_file.println(CONF_GIFNAME_DEFAULT); 774 | // conf_file.print("gravity="); conf_file.println(CONF_GRAVITY_DEFAULT? "true": "false"); 775 | // conf_file.print("target_fps="); conf_file.println(CONF_TARGET_FPS_DEFAULT); 776 | conf_file.print("lcd_rotation="); conf_file.println(CONF_LCD_ROTATION_DEFAULT); 777 | conf_file.print("loop_mode="); conf_file.println(CONF_LOOP_MODE_DEFAULT? "true": "false"); 778 | conf_file.close(); 779 | } 780 | } 781 | // open ini 782 | 783 | IniFile ini(CONFIG_FILENAME); 784 | if (!ini.open()) { 785 | Serial.println("config file open error"); 786 | } 787 | if (!ini.validate(conf_buf, buf_len)) { 788 | Serial.println("config file not valid"); 789 | printErrorMessage(ini.getError()); 790 | } 791 | Serial.println("Reading config file: "); 792 | if (ini.getValue("vision", "video", conf_buf, buf_len)) { 793 | conf_gifname = String(conf_buf); 794 | Serial.print("vision.video="); 795 | Serial.println(conf_gifname); 796 | } else { 797 | printErrorMessage(ini.getError()); 798 | Serial.print(" not found; use vision.video.default="); 799 | Serial.println(conf_gifname); 800 | } 801 | /* 802 | if (ini.getValue("vision", "gravity", conf_buf, buf_len, conf_gravity)) { 803 | Serial.print("vision.gravity="); 804 | Serial.println(conf_gravity ? "true" : "false"); 805 | } else { 806 | printErrorMessage(ini.getError()); 807 | Serial.print(" not found; use vision.gravity.default="); 808 | Serial.println(conf_gravity ? "true" : "false"); 809 | }*/ 810 | if (ini.getValue("vision", "loop_mode", conf_buf, buf_len, conf_loop_mode)) { 811 | Serial.print("vision.loop_mode="); 812 | Serial.println(conf_loop_mode ? "true" : "false"); 813 | } else if (loop_mode_coldboot != COLDBOOT_Y_NOT) { 814 | Serial.print("vision.loop_mode set by cold boot: "); 815 | conf_loop_mode = (loop_mode_coldboot == COLDBOOT_Y_UP)? true: false; 816 | Serial.println(conf_loop_mode ? "true" : "false"); 817 | } else { 818 | printErrorMessage(ini.getError()); 819 | Serial.print(" not found; use vision.loop_mode.default="); 820 | Serial.println(conf_loop_mode ? "true" : "false"); 821 | } 822 | if (ini.getValue("vision", "lcd_rotation", conf_buf, buf_len)) { 823 | conf_lcd_rotation = String(conf_buf).toInt(); 824 | Serial.print("vision.lcd_rotation="); 825 | Serial.println(conf_lcd_rotation); 826 | } else { 827 | printErrorMessage(ini.getError()); 828 | Serial.print(" not found; use vision.target_fps.default="); 829 | Serial.println(conf_lcd_rotation); 830 | } 831 | 832 | digitalWrite(PIN_LED, HIGH); // startup blink 833 | 834 | if (rst_reason == DEEPSLEEP_RESET) { 835 | 836 | gfx->setRotation(conf_lcd_rotation); 837 | 838 | // wakeup by accel. or timer 839 | // play GIF 840 | ledcWrite(1, lcd_pwm_fir_filter(get_lcd_brightness_by_adc(PIN_SENSOR_ADC))); 841 | 842 | if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) { 843 | Serial.println("wakeup by ext0"); 844 | lastTappedCount = bootCount; 845 | } else if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) { 846 | Serial.println("wakeup by timer"); 847 | } else { 848 | Serial.println("wakeup by yjsnpi"); 849 | } 850 | mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE); 851 | if (!mjpeg_buf) { 852 | Serial.println(F("mjpeg_buf malloc failed!")); 853 | } 854 | vFile = FILE_SYSTEM.open(conf_gifname); 855 | if (!vFile || vFile.isDirectory()) { 856 | Serial.println(F("ERROR: Failed to open GIF file for reading")); 857 | } else { 858 | do { 859 | if (!vFile.seek(0)) { 860 | Serial.println(F("seek failed")); 861 | delay(500); 862 | continue; 863 | } 864 | Serial.println(F("MJPEG video start")); 865 | mjpeg.setup(&vFile, mjpeg_buf, drawMCU, false, true); 866 | while (vFile.available()) { 867 | mjpeg.readMjpegBuf(); 868 | mjpeg.drawJpg(); 869 | // check key 870 | //digitalWrite(PIN_LED, digitalRead(PIN_KEY)); // sync LED to key 871 | last_key_state = key_state; 872 | key_state = digitalRead(PIN_KEY); 873 | if (last_key_state == LOW && key_state == LOW) { 874 | lastTappedCount = bootCount; 875 | if (conf_loop_mode) { 876 | while(digitalRead(PIN_KEY) == LOW); 877 | Serial.println("Light sleep start"); 878 | ledcWrite(1, 0); 879 | digitalWrite(PIN_LED, LOW); delay(50); 880 | digitalWrite(PIN_LED, HIGH); 881 | gfx->displayOff(); 882 | lightsleep_ms = millis(); 883 | gpio_wakeup_enable((gpio_num_t)PIN_KEY, GPIO_INTR_LOW_LEVEL); 884 | esp_sleep_enable_gpio_wakeup(); 885 | esp_light_sleep_start(); 886 | lightsleep_ms = millis() - lightsleep_ms; 887 | Serial.print("Wakeup from light sleep, "); 888 | Serial.print(lightsleep_ms); Serial.println("ms"); 889 | gfx->begin(); 890 | while(digitalRead(PIN_KEY) == LOW); // wait for key release 891 | } 892 | } 893 | SD.begin(PIN_SD_CS); // FUCK! THAT! MAGIC!!!就这一行调了一晚上 894 | // sync lcd brightness 895 | ledcWrite(1, lcd_pwm_fir_filter(get_lcd_brightness_by_adc(PIN_SENSOR_ADC))); 896 | } 897 | Serial.println(F("Rewind")); 898 | //delay(500); 899 | } while (conf_loop_mode); 900 | Serial.println(F("MJPEG video end")); 901 | vFile.close(); 902 | } 903 | 904 | digitalWrite(PIN_LED, HIGH); 905 | Serial.println(F("deep sleep")); 906 | ledcWrite(1, 0); 907 | ledcDetachPin(PIN_TFT_BL); 908 | delay(20); 909 | gfx->displayOff(); 910 | if (bootCount - lastTappedCount < DEEP_SLEEP_SHORT_CNT) { 911 | esp_sleep_enable_timer_wakeup(DEEP_SLEEP_SHORT_S * uS_TO_S_FACTOR); 912 | } else { 913 | esp_sleep_enable_timer_wakeup(DEEP_SLEEP_LONG_S * uS_TO_S_FACTOR); 914 | } 915 | 916 | set_io_before_deep_sleep(); 917 | esp_deep_sleep_start(); 918 | 919 | } else { 920 | gfx->begin(); 921 | gfx->fillScreen(LIGHTGREY);SD.begin(PIN_SD_CS); 922 | // poweron reset, start wi-fi ap 923 | Serial.println("Power on reset (maybe); "); 924 | Serial.print("Start "); 925 | Serial.println(wifi_ssid); 926 | WiFi.mode(WIFI_STA); 927 | WiFi.setTxPower(WIFI_POWER_MINUS_1dBm); 928 | WiFi.begin(wifi_ssid, wifi_pwd); 929 | ledcWrite(1, LCD_BACKLIGHT_MIN_8B); // brightness 930 | 931 | int wifi_cnt = 0; 932 | while (WiFi.status() != WL_CONNECTED ) { 933 | digitalWrite(PIN_LED, led_status); 934 | led_status = (led_status == HIGH)? LOW: HIGH; 935 | delay(250); 936 | Serial.print("."); 937 | wifi_cnt++; 938 | if (wifi_cnt > 4 * WIFI_CONNECT_TIMEOUT_S) { 939 | Serial.println("WiFi timeout"); 940 | break; 941 | } 942 | if (digitalRead(PIN_KEY) == LOW) { 943 | delay(20); 944 | if (digitalRead(PIN_KEY) == LOW) { 945 | Serial.println("Wi-Fi skipped by key"); 946 | break; 947 | } 948 | } 949 | } 950 | digitalWrite(PIN_LED, HIGH); 951 | server.on("/status", HTTP_GET, getStatus); 952 | server.on("/list", HTTP_GET, printDirectory); 953 | server.on("/edit", HTTP_DELETE, handleDelete); 954 | server.on("/edit", HTTP_PUT, handleCreate); 955 | server.on("/edit", HTTP_POST, []() { 956 | returnOK(); 957 | }, handleFileUpload); 958 | server.onNotFound(handleNotFound); 959 | server.on("/update", HTTP_POST, handleOTAresult, handleOTA); 960 | 961 | if (WiFi.status() == WL_CONNECTED) { 962 | Serial.println("WiFi connected"); 963 | Serial.println("IP address: "); 964 | Serial.println(WiFi.localIP()); 965 | server.begin(); 966 | if (!MDNS.begin(wifi_host)) { 967 | Serial.println("Error setting up MDNS responder!"); 968 | } 969 | MDNS.addService("http", "tcp", 80); 970 | 971 | gfx->fillScreen(BLUE); SD.begin(PIN_SD_CS); 972 | } else { 973 | // wifi not connected 974 | } 975 | } 976 | } 977 | 978 | // 这个setup好tm长啊 979 | 980 | void loop() 981 | { 982 | bool exit_loop = false; 983 | 984 | if (WiFi.status() == WL_CONNECTED) { 985 | server.handleClient(); 986 | delay(2);//allow the cpu to switch to other tasks 987 | 988 | ledcWrite(1, lcd_pwm_fir_filter(get_lcd_brightness_by_adc(PIN_SENSOR_ADC))); 989 | 990 | // 退出webserver进入深睡条件:按下按键 991 | if (digitalRead(PIN_KEY) == LOW) { 992 | delay(20); 993 | if (digitalRead(PIN_KEY) == LOW) { 994 | exit_loop = true; 995 | } 996 | } 997 | } else { 998 | // 如果没有连接就进到loop,说明 a.前面尝试连接wifi炒屎了 或 b.wifi断开了 999 | // 就直接睡眠了准备正常工作 1000 | exit_loop = true; 1001 | } 1002 | 1003 | if (exit_loop) { 1004 | Serial.println(F("init done, deep sleep now")); 1005 | gfx->begin(); 1006 | ledcWrite(1, LCD_BACKLIGHT_MIN_8B); 1007 | DFRobot_LIS2DW12::eOrient_t orientation = acce.getOrientation(); 1008 | if (orientation == DFRobot_LIS2DW12::eYDown){ 1009 | Serial.println("Y down"); 1010 | loop_mode_coldboot = COLDBOOT_Y_DOWN; 1011 | gfx->fillScreen(PURPLE); 1012 | } else if (orientation == DFRobot_LIS2DW12::eYUp){ 1013 | Serial.println("Y up"); 1014 | loop_mode_coldboot = COLDBOOT_Y_UP; 1015 | gfx->fillScreen(PINK); 1016 | } else { 1017 | Serial.println("not Y"); 1018 | loop_mode_coldboot = COLDBOOT_Y_NOT; 1019 | gfx->fillScreen(GREEN); 1020 | } 1021 | 1022 | delay(500); 1023 | gfx->fillScreen(BLACK); 1024 | ledcWrite(1, 0); 1025 | ledcDetachPin(PIN_TFT_BL); 1026 | delay(5); 1027 | gfx->displayOff(); 1028 | esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR); 1029 | set_io_before_deep_sleep(); 1030 | esp_deep_sleep_start(); 1031 | } 1032 | 1033 | } 1034 | -------------------------------------------------------------------------------- /stl/inazuma/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Vision 稻妻版外壳组装指引 2 | 当前外壳版本 V1.1,适配圆版 PCB V3.3 3 | 4 | 5 | 由于都是圆的和蒙德版基本一致,这里就只写不同的部分 6 | 7 | ## 材料清单 8 | 这里仅列出与蒙德版的不同 9 | | 名称 | 数量 | 备注 | 10 | | :--- | :--- | :--- | 11 | | M1.2\*4 螺丝 | 4 | | 12 | | R40350 锂电池 | 1 | | 13 | 14 | ## 安装 15 | 过程基本与蒙德版一致,这里就随便放几个图 16 | 17 | ![image](https://user-images.githubusercontent.com/8705034/163842193-81053324-46e0-4afc-af74-356deac1c882.png) 18 | 19 | ![image](https://user-images.githubusercontent.com/8705034/163842230-3ae6e63e-4a93-4142-90ae-276fed94f30a.png) 20 | 21 | ![image](https://user-images.githubusercontent.com/8705034/163842265-5878ed77-1373-4c01-b6e2-1498b31d56da.png) 22 | 23 | ![image](https://user-images.githubusercontent.com/8705034/163842293-fc504e68-b4ae-4927-9638-03ae84569b42.png) 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /stl/inazuma/inazuma-bigcap-v1.1.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/inazuma/inazuma-bigcap-v1.1.STL -------------------------------------------------------------------------------- /stl/inazuma/inazuma-main-v1.1.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/inazuma/inazuma-main-v1.1.STL -------------------------------------------------------------------------------- /stl/inazuma/inazuma-v1.0-bare-220401.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/inazuma/inazuma-v1.0-bare-220401.STL -------------------------------------------------------------------------------- /stl/liyue/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Vision 璃月版外壳组装指引 2 | 3 | 当前外壳版本 V2.2,适配 PCB 版本 V3.3 ST7789 4 | 由于外壳更新可能会出现教程图片前后不兼容的情况,这里保留了最初版的教程没有改动,版本更新带来的变动会单独列在下面;但具体可能还是要自己发挥一下 5 | 6 | 本安装指引的内容仅作为参考,不作为标准流程,可能会随时间推移而失效。请一定清醒一点带着脑子,搞明白自己做的是什么该怎么做,造成损失自负。 7 | 作为一个 DIY 项目,它当然充满瑕疵,过程中还可能会出现各种问题,还请自行发挥。 8 | 作者保留最终解释权。 9 | 10 | ## 材料清单 11 | 注:这里的淘宝链接没有任何利益关系,只是为了方便说明。你可以随便买。 12 | | 名称 | 数量 | 备注 | 13 | | :--- | :--- | :--- | 14 | | liyue-main*.STL | 1 | 本体 | 15 | | liyue-cap*.STL | 1 | 盖子 | 16 | | 焊接调试好的电路板及屏幕 | 1 | | 17 | | 403035(~450mAh) 或稍小尺寸的锂电池 | 1 | 403035是最大的,其余能不能塞得进去自己判断 | 18 | | NTC 热敏电阻 10k B3380 | 1 | 焊线的那种,粘在电池旁边测温度的 | 19 | | M1.2\*3 圆头螺丝 国标 | 6 | 可以多买一点备用,头部什么形状其实……没所谓? | 20 | | M1.2 螺母 | 6 | 可以多买一点备用,要确定它和上面的能拧到一起就行 | 21 | | M1.5\*4 平头铜铆钉 | 1 | 用作按键。例如 [这个](https://detail.tmall.com/item.htm?id=15757285605) | 22 | | O型密封圈,外径3mm内径1.4mm线径0.8mm | 可选 | 例如 [这个](https://item.taobao.com/item.htm?id=614352089868)。套在上面的铆钉上。它仅起到改善手感的功能,可以不用,反正也不可能防水的,我写是因为买都买了。。| 23 | | FPC 排线 4P 1.5mm间距 3cm长 | 1 | 不是那种常见白色的,一定要耐高温(茶色),比如 [这个](https://item.taobao.com/item.htm?id=639292801110) | 24 | | TEMT6000X01 环境光传感器 | 1 | 焊到上面的排线上 | 25 | | 无线充电用的隔磁片 | 若干 | 虽然下面模块会送,但需要两层| 26 | | Qi 协议的无线充电接收模块 | 1 | 比如 [这个](https://item.taobao.com/item.htm?id=626465407103) ;买线圈大一点的版本 | 27 | | 线圈 | 可选 | 接收模块送的线圈比较小,如果你使用下面提到的充电座则可以换一个稍大的线圈来提高效率;对于这些常见接收模块,选择电感量 10uH ~ 12uH,直径不超过 34mm 即可,比如芯科泰的 L93 和 L104 都实测可用 | 28 | | Qi 协议的无线充电发射模块 | 可选 | 外壳是按照 华为 GT2 Pro 的充电座的外形画的,那个充电器淘宝大概不到30,买来直接吸上就能用;也可以在接收模块同一家买| 29 | | 30mm 边长正方形的低弧的玻璃 | 1 | 比如 [这个](https://item.taobao.com/item.htm?id=591580399917) ;建议多买一点,运输过程中可能撞出裂痕,需要挑选| 30 | | 透明的胶水 | 若干 | 需要能仅依靠外壳那一圈的面积粘得住那一大块玻璃,我用的是 维修佬 E8000 | 31 | | 8mm 直径, 1mm 厚的强磁铁 | 1 | 和 华为 GT2 Pro 充电器 一起使用时,用来吸附定位 | 32 | | 自喷漆,金色(香槟金) | 若干 | 外壳上色 | 33 | | 自喷漆,光油 | 可选 | 喷了有点脏,不喷金色容易掉,自己选 | 34 | | 马克笔,金色 | 可选 | 用于补瑕疵;手残建议买,颜色需要和上面自喷漆一致 | 35 | | 美纹纸胶带 | 若干 | 喷漆时用 | 36 | | 砂纸 | 可选 | 用来打磨 3D 打印件上由于支撑带来的瑕疵,需要细一点的,我用的 2000 感觉粗了,建议 2000 5000 10000 各买一点 | 37 | | 补土 | 可选 | 如果 3D 打印件上有坑就自己补一下,我买的田宫那个感觉还行 | 38 | | 漆包线| 可选 | 那种耳机线材。比如我买的 [这个](https://item.taobao.com/item.htm?id=650753503748) | 39 | | 青稞纸 | 可选 | 如果是大电池,贴在电池上防止被电路板上的元件划伤| 40 | 41 | 42 | ### 外壳喷漆 43 | 拿出 3D 打印的外壳。检查外观,如果有坑就用补土填一下(下面的图都没有使用,按实际情况来); 44 | 使用粗砂纸和细砂纸打磨表面至没有明显的支撑凸起; 45 | 使用美纹纸胶带将不想被喷漆的位置覆盖好,然后使用自喷漆在表面薄薄地均匀地喷涂数次。 46 | 47 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_painting.jpg) 48 | 49 | ### 粘贴玻璃 50 | 挑选一块没有裂痕没有瑕疵的 30 mm 边长低弧方形形玻璃。 51 | 由于成本原因,它在运输过程中很容易出现刮花或者裂痕,所以建议多买一些到手后再挑;并且方的还有个更坑的问题,它四个角的圆角半径经常就tm不一样。。。还得挑 52 | 同样地,装配过程中也要小心碰撞。方形玻璃建议按照实际用量的 4~5 倍购买,不然可能不太好挑。 53 | 54 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_glass_install.jpg) 55 | 56 | 在外壳上均匀地挤一圈胶水,不要太多但是要能填满空隙,然后将玻璃放上去。注意要从多个侧面角度确认玻璃是否水平。 57 | 由于 3D 打印精度和各地气温不同等原因,你可能需要在涂胶水前试一下玻璃能不能塞得进去。 58 | 放上玻璃后胶水会溢出一些,我是将浸湿酒精的无尘布裹在镊子头部擦掉它们的,你也可以自由发挥。 59 | 清理好溢出的胶水后,再次确认玻璃水平,使用美纹纸胶带将玻璃与外壳固定。我选择的这个胶水的固化大概需要一至两天,固化后暂时还没遇到过强度问题。 60 | 61 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_glass_install_taped.jpg) 62 | 63 | 64 | ### 安装环境光传感器 65 | 准备一个 TEMT6000X01 环境光传感器,一条上表中提到的排线和胶水。 66 | 将传感器如下图所示焊接在排线的引脚上。你可能需要像图上那样记录下引脚关系。 67 | 68 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/fpc_cable_with_temt6000_welded_02.jpg) 69 | 70 | 然后小心地将传感器所在位置折弯 90° 如下: 71 | 72 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_fpc_90degree.jpg) 73 | 74 | 在外壳的对应孔位挤一点胶水,并将传感器粘到孔中。 75 | 使用美纹胶固定好玻璃和传感器,等待胶水固化,大约需要两天。 76 | 77 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_sensor_taped.jpg) 78 | 79 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_sensor_taped_Aside.jpg) 80 | 81 | ### 安装固定电路板用的螺母 82 | 在屏幕内侧有两个螺母位置,用来固定电路板。你需要把 2 颗 M1.2 螺母塞进去。内部情况如下图,平边与屏幕边缘平行。 83 | 84 | ![image](https://user-images.githubusercontent.com/8705034/156000332-b3ade494-03d7-4a66-a809-acf51c8dd5dd.png) 85 | 86 | ![image](https://user-images.githubusercontent.com/8705034/156000411-0fd1b761-19fe-4533-9da7-ffc01669f7f6.png) 87 | 88 | 这里没有实物图,又是忘了拍了 89 | 90 | ### 组装后壳 91 | 92 | 后壳上需要嵌入剩下的 8 颗 M1.2 螺母。见下图,一共就八个坑 93 | 将无线充电用的定位磁铁通过胶水粘贴在最大的坑里,注意和充电座放在一起试一下,别贴反了。。。 94 | 确定极性后,用美纹胶带辅助定位,需要两天固化。 95 | 96 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_charger_install.jpg) 97 | 98 | 将无线充电的线圈粘贴上。 99 | 100 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_v2.1_Qi_coil.jpg) 101 | 102 | 按照后盖尺寸裁剪两片铁氧体隔磁片: 103 | 104 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_charger_ferrite_size.jpg) 105 | 106 | 将第一层隔磁片粘贴在线圈上,将无线充电模块粘贴在第一层隔磁片上; 107 | 108 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_v2.1_Qi_coil_with_ferrite_sheet.jpg) 109 | 110 | 最后将第二层隔磁片粘贴在无线充电模块上,确保 PCB 上空有一层完整的隔磁片(下面几张图中并未安装,但实际需要)。 111 | 112 | 113 | 114 | ### 安装电路 115 | 116 | 你可以选择在安装电路板前先刷写固件。刷固件指引见 [/src](https://github.com/libc0607/esp32-vision/tree/main/src) 目录下,根据你的需要选择固件。 117 | 由于这个 1.54 寸液晶屏背面不是金属,所以背面可以不贴绝缘胶带。 118 | 119 | 按照引脚定义焊接好:环境光传感器的 2 条线;无线充电模块 2 条线;电池和 NTC 电阻共 3 条线(共用 GND)。 120 | 如果发现原配的导线较硬,导致难以装配,可以将你不喜欢的线换成材料清单中提到的耳机线,将外皮剥掉使用线芯。 121 | 完整的电路连接如下图。 122 | 123 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_full_circuit.jpg) 124 | 125 | 将这些电路塞进去: 126 | 127 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_asm.jpg) 128 | 129 | 使用 2 颗 M1.2 螺丝将 PCB 固定在外壳上。盖上盖子,拧好周围的 8 颗螺丝,装配完成。 130 | 131 | --- 132 | 133 | ### PCB V3.3 (STL V2.2) 改动 134 | 135 | #### 按键 136 | PCB V3.3 (STL V2.2) 版本在侧面增加了一个按键,需要将按键塞进按键孔后再放入屏幕和电路板,像这样: 137 | 138 | ![image](https://user-images.githubusercontent.com/8705034/161296508-7e029cf6-a358-4934-b6de-04c473078351.png) 139 | 140 | ![image](https://user-images.githubusercontent.com/8705034/161296531-63b61340-e421-4d5f-a445-a533746b3dfa.png) 141 | 142 | 尝试按动,能够按下能够回弹即可。在铆钉上套一个 O 型圈可以让它的手感更好一点,不过没有的话影响也不大。 143 | 144 | #### 电池安装 145 | 由于换用了403035 电池,安装结构需要做出调整:将无线充电模块贴在屏幕背面,然后将电池整个盖上去。 146 | 你可能需要在电池上贴一层青稞纸来保护电池。 147 | 148 | #### 光传感器 149 | PCB V3.3 (STL V2.2) 版本为了正面美观,将光传感器移动到了侧面。 150 | 按照同样的方式粘贴即可。 151 | 152 | -------------------------------------------------------------------------------- /stl/liyue/liyue-cap-v2.1.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/liyue/liyue-cap-v2.1.STL -------------------------------------------------------------------------------- /stl/liyue/liyue-main-v2.0.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/liyue/liyue-main-v2.0.STL -------------------------------------------------------------------------------- /stl/liyue/liyue-v2.2-cap.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/liyue/liyue-v2.2-cap.STL -------------------------------------------------------------------------------- /stl/liyue/liyue-v2.2-main.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/liyue/liyue-v2.2-main.STL -------------------------------------------------------------------------------- /stl/mondstadt/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Vision 蒙德版外壳组装指引 2 | 3 | 当前外壳版本 V2.3,适配圆版 PCB V3.3 4 | 由于外壳更新可能会出现前后不兼容的情况,还是自己发挥一下 5 | 6 | 本安装指引的内容仅作为参考,不作为标准流程,可能会随时间推移而失效。请一定清醒一点带着脑子,搞明白自己做的是什么该怎么做,造成损失自负。 7 | 作为一个 DIY 项目,它当然充满瑕疵,过程中还可能会出现各种问题,还请自行发挥。 8 | 作者保留最终解释权。 9 | (其实很多细节都和游戏中的模型有出入,暂时还没改的原因是因为 SolidWorks 水平太烂,SLDPRT 里变成屎山了,想改都没法改(() 10 | 11 | ## 材料清单 12 | 注:这里的淘宝链接没有任何利益关系,只是为了方便说明。你可以随便买。 13 | | 名称 | 数量 | 备注 | 14 | | :--- | :--- | :--- | 15 | | mondstadt-main*.STL | 1 | 本体 | 16 | | mondstadt-cap*.STL | 1 | 盖子 | 17 | | 焊接调试好的电路板及屏幕 | 1 | | 18 | | 401120 或相近尺寸的锂电池 | 1 | 能不能塞得进去自己判断 | 19 | | NTC 热敏电阻 10k B3380 | 1 | 焊线的那种,粘在电池旁边测温度的 | 20 | | M1.2\*4 圆头螺丝 | 2/4 | 可以多买一点备用;大电池版仅用到 2 个 | 21 | | M1.2\*6 圆头螺丝 | 2 | 仅大电池版用到,可以多买一点备用 | 22 | | M1.2 螺母 | 4 | 能和上面的螺丝拧到一起就行 | 23 | | M1.5\*3 平头铜铆钉 | 1 | 用作按键。例如 [这个](https://detail.tmall.com/item.htm?id=15757285605) | 24 | | O型密封圈,外径3mm内径1.4mm线径0.8mm| 可选 | 例如 [这个](https://item.taobao.com/item.htm?id=614352089868)。套在上面的铆钉上。它仅起到改善手感的功能,可以不用,反正也不可能防水的,我写是因为买都买了。。 | 25 | | FPC 排线 4P 1.5mm间距 3cm长 | 1 | 不是那种常见白色的,一定要耐高温(茶色),比如 [这个](https://item.taobao.com/item.htm?id=639292801110) | 26 | | TEMT6000X01 环境光传感器 | 1 | 焊到上面的排线上 | 27 | | 无线充电用的隔磁片 | 若干 | 虽然下面模块会送,但看情况可能需要两层| 28 | | Qi 协议的无线充电接收模块 | 1 | 比如 [这个](https://item.taobao.com/item.htm?id=626465407103) ;买线圈大一点的版本 | 29 | | 线圈 | 可选 | 接收模块送的线圈比较小,如果你使用下面提到的充电座则可以换一个稍大的线圈来提高效率;对于这些常见接收模块,选择电感量 10uH ~ 12uH,直径不超过 34mm 即可,比如芯科泰的 L93 和 L104 都实测可用 | 30 | | Qi 协议的无线充电发射模块 | 可选 | 外壳是按照 华为 GT2 Pro 的充电座的外形画的,那个充电器淘宝大概不到30,买来直接吸上就能用;也可以在接收模块同一家买| 31 | | 35mm直径圆形的低弧的玻璃 | 1 | 比如 [这个](https://item.taobao.com/item.htm?id=591580399917) ;建议多买一点,运输过程中可能撞出裂痕,需要挑选| 32 | | 透明的胶水 | 若干 | 需要能仅依靠外壳那一圈的面积粘得住那一大块玻璃,我用的是 维修佬 E8000 | 33 | | 8mm 直径, 1mm 厚的强磁铁 | 1 | 和 华为 GT2 Pro 充电器 一起使用时,用来吸附定位 | 34 | | 自喷漆,金色(香槟金) | 若干 | 外壳上色 | 35 | | 自喷漆,光油 | 可选 | 喷了有点脏,不喷金色容易掉,自己选 | 36 | | 马克笔,金色 | 可选 | 用于补瑕疵;手残建议买,颜色需要和上面自喷漆一致 | 37 | | 马克笔,黑色 | 1 | 用于给外壳上黑色的那几个位置涂色 | 38 | | 美纹纸胶带 | 若干 | 喷漆时用 | 39 | | 砂纸 | 可选 | 用来打磨 3D 打印件上由于支撑带来的瑕疵,需要细一点的,我用的 2000 感觉粗了,建议 2000 5000 10000 各买一点 | 40 | | 补土 | 可选 | 如果 3D 打印件上有坑就自己补一下,我买的田宫那个感觉还行 | 41 | | 漆包线| 可选 | 那种耳机线材。比如我买的 [这个](https://item.taobao.com/item.htm?id=650753503748) | 42 | 43 | 44 | ### 外壳喷漆 45 | 拿出 3D 打印的外壳。检查外观,如果有坑就用补土填一下(下面的图都没有使用,按实际情况来); 46 | 使用粗砂纸和细砂纸打磨表面至没有明显的支撑凸起; 47 | 然后使用美纹纸胶带将不想被喷漆的位置覆盖好,然后使用自喷漆在表面薄薄地均匀地喷涂数次。 48 | 49 | 如果上一步打磨过程中出现了奇怪的坑或者划痕,可以适当多喷一点点,但最忌心急喷一大堆。 50 | 51 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_main_painting.jpg) 52 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/liyue_painting.jpg) 53 | 54 | 喷漆过程中请注意防护。等干透了再摸它。。 55 | 56 | 喷光油的步骤可选,喷了的话外表显得有些脏,可能是我水平太烂(?);不喷的话金色漆面可能容易掉。 57 | 58 | 完成喷漆后,使用黑色马克笔把该涂成黑色的位置涂黑: 59 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_bare_with_glass.jpg) 60 | 61 | ### 粘贴玻璃 62 | 挑选一块没有裂痕没有瑕疵的 35 mm 直径低弧圆形玻璃。 63 | 由于成本原因,它在运输过程中很容易出现刮花或者裂痕,所以建议多买一些到手后再挑;同样地,装配过程中也要小心碰撞。我买过两批,总体良率大概在一半左右。 64 | 65 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_bare_with_glass_and_glue.jpg) 66 | 67 | 在外壳上均匀地挤一圈胶水,不要太多但是要能填满空隙,然后将玻璃放上去。注意要从多个侧面角度确认玻璃是否水平。 68 | 由于 3D 打印精度和各地气温不同等原因,你可能需要在涂胶水前试一下玻璃能不能塞得进去。 69 | 放上玻璃后胶水会溢出一些,我是将浸湿酒精的无尘布裹在镊子头部擦掉它们的,你也可以自由发挥。 70 | 71 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_glass_glue.jpg) 72 | 73 | 清理好溢出的胶水后,再次确认玻璃水平,使用美纹纸胶带将玻璃与外壳固定。我选择的这个胶水的固化大概需要一至两天,固化后暂时还没遇到过强度问题。 74 | 75 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_glass_glued_taped.jpg) 76 | 77 | ### 安装环境光传感器 78 | 准备一个 TEMT6000X01 环境光传感器,一条上表中提到的排线和胶水。 79 | 将传感器如下图所示焊接在排线的引脚上。你可能需要像图上那样记录下引脚关系。 80 | 81 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/fpc_cable_with_temt6000_welded_02.jpg) 82 | 83 | 然后小心地将传感器所在位置折弯 90° 如下: 84 | 85 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_fpc_90degree.jpg) 86 | 87 | 在外壳的对应孔位挤一点胶水,并将传感器粘到孔中。 88 | 89 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_sensor_position.jpg) 90 | 91 | 使用美纹胶固定好传感器,等待胶水固化。 92 | 93 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_sensor_position_taped.jpg) 94 | 95 | 这时从外壳的正面应该可以正好露出传感器接收光线的部分。再在黑色部分均匀地覆盖一层胶水,同时作为固定和保护: 96 | 97 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_sensor_Aside_glue_filled_focus.jpg) 98 | 99 | 至此你已完成了外壳主体部分的组装。在等待两天胶水固化后即可拆除所有胶带。 100 | 101 | ### 安装电路 102 | 这一节的部分图片基于早期版本的电路和外壳,然后因为又懒又鸽导致还没更新,所以可能和实际的最新版本看上去不太一样。请动动脑 103 | 104 | 你可以选择在安装电路板前先刷写固件。刷固件指引见 [/src](https://github.com/libc0607/esp32-vision/tree/main/src) 目录下,根据你的需要选择固件。 105 | 106 | 将焊接好的电路板背面贴一层随便什么绝缘且最好导热好一点的东西,防止屏幕背板接触到电路板背面的触电造成短路: 107 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_pcb_taped.jpg) 108 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_pcb_with_screen.jpg) 109 | 110 | 按照引脚定义焊接好:环境光传感器的 2 条线;无线充电模块 2 条线;电池和 NTC 电阻共 3 条线(共用 GND)。 111 | 如果发现原配的导线较硬,导致难以装配,可以将你不喜欢的线换成材料清单中提到的耳机线,将外皮剥掉使用线芯。 112 | 113 | ![](https://github.com/libc0607/esp32-vision/raw/main/img/mondstadt_pcb_welding_01.jpg) 114 | 115 | 无线充电的线圈通过随便什么耐热的胶粘在后盖上,并在上面贴一层铁氧体隔磁片。钱多的同学也可以使用纳米晶隔磁片。 116 | 然后在电路板的元件那面上再贴一小片隔磁片,将无线充电模块隔着这层隔磁片粘在电路板的上方附近。这一步没图,忘了拍了 117 | 118 | V3.3 版本在侧面增加了一个按键,需要将按键塞进按键孔后再放入屏幕和电路板,像这样: 119 | 120 | ![image](https://user-images.githubusercontent.com/8705034/158618050-33e1ea7a-3312-4b3d-a32a-61e8f06e79d5.png) 121 | 122 | 尝试按动,能够按下能够回弹即可。在铆钉上套一个 O 型圈可以让它的手感更好一点,不过没有的话影响也不大。 123 | 124 | 最后就是像往肚子里塞肠子那样把它们都塞进去,只要能盖得上后盖,拧得上螺丝,就成功了(( 125 | 126 | ![image](https://user-images.githubusercontent.com/8705034/158618763-27db3f55-eda1-4754-ac19-02bad9ffdd55.png) 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /stl/mondstadt/mondstadt-cap-bigbattery-v2.3.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/mondstadt/mondstadt-cap-bigbattery-v2.3.STL -------------------------------------------------------------------------------- /stl/mondstadt/mondstadt-cap-v2.1.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/mondstadt/mondstadt-cap-v2.1.STL -------------------------------------------------------------------------------- /stl/mondstadt/mondstadt-cap-v2.3.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/mondstadt/mondstadt-cap-v2.3.STL -------------------------------------------------------------------------------- /stl/mondstadt/mondstadt-main-v2.0.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/mondstadt/mondstadt-main-v2.0.STL -------------------------------------------------------------------------------- /stl/mondstadt/mondstadt-main-v2.3.1.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/mondstadt/mondstadt-main-v2.3.1.STL -------------------------------------------------------------------------------- /stl/sumeru/sumeru-v1.0.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libc0607/esp32-vision/ee724e2a3df17d10f9baa3a1431ed18bc9e7534a/stl/sumeru/sumeru-v1.0.STL --------------------------------------------------------------------------------