├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .readthedocs.yml ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── doc ├── Doxyfile ├── Makefile ├── html │ ├── bc_s.png │ ├── bdwn.png │ ├── closed.png │ ├── doc.png │ ├── doxygen.css │ ├── doxygen.png │ ├── dynsections.js │ ├── folderclosed.png │ ├── folderopen.png │ ├── index.html │ ├── jquery.js │ ├── menu.js │ ├── menudata.js │ ├── nav_f.png │ ├── nav_g.png │ ├── nav_h.png │ ├── open.png │ ├── search │ │ ├── close.png │ │ ├── mag_sel.png │ │ ├── nomatches.html │ │ ├── search.css │ │ ├── search.js │ │ ├── search_l.png │ │ ├── search_m.png │ │ ├── search_r.png │ │ └── searchdata.js │ ├── splitbar.png │ ├── sync_off.png │ ├── sync_on.png │ ├── tab_a.png │ ├── tab_b.png │ ├── tab_h.png │ ├── tab_s.png │ └── tabs.css ├── latex │ ├── Makefile │ ├── doxygen.sty │ ├── longtable_doxygen.sty │ ├── refman.tex │ └── tabu_doxygen.sty ├── make.bat ├── requirements.txt └── source │ ├── about.rst │ ├── api.rst │ ├── conf.py │ ├── filegen.rst │ ├── getting_started.rst │ ├── img │ ├── board_p1.jpg │ ├── board_p2.jpg │ ├── demo.jpg │ ├── ed060sc4.jpg │ ├── ed097oc4.jpg │ ├── ed097tc2.jpg │ ├── hardware_page.png │ ├── v6.jpg │ ├── vcom.jpg │ ├── vcom_opamp.jpg │ └── vcom_tp.jpg │ ├── index.rst │ └── tips.rst ├── examples ├── calibration_helper │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ └── sdkconfig.defaults.esp32s3 ├── demo │ ├── CMakeLists.txt │ ├── Makefile │ ├── files │ │ ├── beach.jpg │ │ ├── board.jpg │ │ ├── giraffe.jpg │ │ └── zebra.jpg │ ├── main │ │ ├── CMakeLists.txt │ │ ├── firasans_12.h │ │ ├── firasans_20.h │ │ ├── img_beach.h │ │ ├── img_board.h │ │ ├── img_zebra.h │ │ ├── main.c │ │ └── main.ino │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ └── sdkconfig.defaults.esp32s3 ├── dragon │ ├── CMakeLists.txt │ ├── README.md │ ├── cc_dragon.jpg │ ├── main │ │ ├── CMakeLists.txt │ │ ├── dragon.h │ │ ├── main.c │ │ └── main.ino │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ └── sdkconfig.defaults.esp32s3 ├── fb_mode_test │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ └── sdkconfig.defaults.esp32s3 ├── grayscale_test │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ └── sdkconfig.defaults.esp32s3 ├── http-server │ ├── CMakeLists.txt │ ├── README.md │ ├── component.mk │ ├── main │ │ ├── CMakeLists.txt │ │ ├── epd.c │ │ ├── epd.h │ │ ├── main.c │ │ ├── server.c │ │ ├── server.h │ │ └── settings.h │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ ├── sdkconfig.defaults.esp32s3 │ └── send_image.py ├── lilygo-t5-47-epd-platformio │ ├── .gitignore │ ├── README.md │ ├── lib │ │ ├── Firasans │ │ │ └── Firasans.h │ │ └── README │ ├── platformio.ini │ └── src │ │ └── main.cpp ├── screen_diag │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── commands.c │ │ ├── commands.h │ │ ├── commands │ │ │ ├── graphics.c │ │ │ ├── graphics.h │ │ │ ├── screen.c │ │ │ ├── screen.h │ │ │ ├── system.c │ │ │ ├── system.h │ │ │ ├── tests.c │ │ │ └── tests.h │ │ ├── epd.c │ │ ├── epd.h │ │ ├── res │ │ │ └── fonts │ │ │ │ ├── alexandria.h │ │ │ │ ├── amiri.h │ │ │ │ └── fonts.h │ │ └── screen_diag.c │ ├── partitions_example.csv │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ └── sdkconfig.defaults.esp32s3 ├── test │ ├── CMakeLists.txt │ └── main │ │ ├── CMakeLists.txt │ │ └── main.c ├── vcom-kickback │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── dragon.h │ │ ├── main.c │ │ └── main.ino │ ├── sdkconfig.defaults │ └── sdkconfig.defaults.esp32s3 ├── weather │ ├── CMakeLists.txt │ ├── Licence.txt │ ├── README.md │ ├── main │ │ ├── ArduinoJson.h │ │ ├── CMakeLists.txt │ │ ├── forecast_record.h │ │ ├── lang.h │ │ ├── main.ino │ │ ├── opensans10.h │ │ ├── opensans12.h │ │ ├── opensans12b.h │ │ ├── opensans16.h │ │ ├── opensans16b.h │ │ ├── opensans24.h │ │ ├── opensans24b.h │ │ ├── opensans6.h │ │ ├── opensans8.h │ │ ├── opensans8b.h │ │ ├── owm_credentials.h │ │ └── weather.cpp │ ├── partitions.csv │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ ├── sdkconfig.defaults.esp32s3 │ └── weather.jpg └── www-image │ ├── CMakeLists.txt │ ├── README.md │ ├── component.mk │ ├── main │ ├── CMakeLists.txt │ ├── idf_component.yml │ ├── jpg-render.c │ ├── jpgdec-render.cpp │ └── settings.h │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ ├── sdkconfig.defaults.esp32s3 │ └── ssl_cert │ └── server_cert.pem ├── idf_component.yml ├── library.json ├── library.properties ├── scripts ├── LICENSE ├── README.md ├── epdiy_waveform_gen.py ├── fontconvert.py ├── imgconvert.py ├── modenames.py └── waveform_hdrgen.py ├── src ├── Makefile ├── board │ ├── epd_board.c │ ├── epd_board_common.c │ ├── epd_board_common.h │ ├── epd_board_lilygo_t5_47.c │ ├── epd_board_v2_v3.c │ ├── epd_board_v4.c │ ├── epd_board_v5.c │ ├── epd_board_v6.c │ ├── epd_board_v7.c │ ├── lilygo_board_s3.c │ ├── pca9555.c │ ├── pca9555.h │ ├── tps65185.c │ └── tps65185.h ├── board_specific.c ├── builtin_waveforms.c ├── diff.S ├── displays.c ├── epd_board.h ├── epd_board_specific.h ├── epd_display.h ├── epd_highlevel.h ├── epd_internals.h ├── epdiy.c ├── epdiy.h ├── font.c ├── hacks.cmake ├── highlevel.c ├── output_common │ ├── line_queue.c │ ├── line_queue.h │ ├── lut.S │ ├── lut.c │ ├── lut.h │ ├── render_context.c │ ├── render_context.h │ ├── render_method.c │ └── render_method.h ├── output_i2s │ ├── i2s_data_bus.c │ ├── i2s_data_bus.h │ ├── render_i2s.c │ ├── render_i2s.h │ ├── rmt_pulse.c │ └── rmt_pulse.h ├── output_lcd │ ├── idf-4-backports.h │ ├── lcd_driver.c │ ├── lcd_driver.h │ ├── render_lcd.c │ └── render_lcd.h ├── render.c ├── render.h └── waveforms │ ├── epdiy_ED047TC1.h │ ├── epdiy_ED047TC2.h │ ├── epdiy_ED060SC4.h │ ├── epdiy_ED060SCT.h │ ├── epdiy_ED060XC3.h │ ├── epdiy_ED097OC4.h │ ├── epdiy_ED097TC2.h │ ├── epdiy_ED133UT2.h │ └── epdiy_NULL.h └── test ├── CMakeLists.txt ├── test_diff.c ├── test_initialization.c ├── test_line_mask.c └── test_lut.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: chromium 2 | IndentWidth: 4 3 | ColumnLimit: 100 4 | AlignAfterOpenBracket: BlockIndent 5 | IncludeBlocks: Preserve 6 | BreakBeforeBinaryOperators: All 7 | Cpp11BracedListStyle: false 8 | AllowAllParametersOfDeclarationOnNextLine: true 9 | BinPackArguments: false 10 | BinPackParameters: false 11 | SortIncludes: false 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: ESP-IDF 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | format-check: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: "espressif/idf:v5.4" 10 | steps: 11 | - uses: actions/checkout@v4 12 | - run: | 13 | . $IDF_PATH/export.sh 14 | idf_tools.py install esp-clang 15 | . $IDF_PATH/export.sh 16 | which clang-format 17 | make format-check 18 | 19 | build: 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | version: 25 | - v5.2 26 | - v5.3 27 | - v5.4 28 | target: 29 | - esp32 30 | - esp32s3 31 | example: 32 | - demo 33 | include: 34 | - version: v5.4 35 | example: screen_diag 36 | - version: v5.4 37 | example: dragon 38 | - version: v5.4 39 | example: grayscale_test 40 | - version: v5.4 41 | example: www-image 42 | - version: v5.4 43 | example: calibration_helper 44 | 45 | continue-on-error: ${{ matrix.version == 'latest' }} 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | with: 50 | submodules: 'recursive' 51 | - uses: 'espressif/esp-idf-ci-action@main' 52 | with: 53 | esp_idf_version: ${{ matrix.version }} 54 | target: ${{ matrix.target }} 55 | path: 'examples/${{ matrix.example }}' 56 | 57 | build-arduino: 58 | runs-on: ubuntu-latest 59 | container: 60 | image: "espressif/idf:${{ matrix.version }}" 61 | strategy: 62 | fail-fast: false 63 | matrix: 64 | version: 65 | - v5.3.2 66 | example: 67 | - weather 68 | arduino-esp32: 69 | - 3.1.3 70 | 71 | steps: 72 | - name: Install latest git 73 | run: | 74 | apt update -qq && apt install -y -qq git 75 | - name: Checkout repo 76 | uses: actions/checkout@v4 77 | - name: Install Arduino ESP 78 | run: | 79 | cd examples/${{ matrix.example }} 80 | mkdir components && cd components 81 | git clone --depth 1 --recursive --branch ${{ matrix.arduino-esp32 }} https://github.com/espressif/arduino-esp32.git arduino 82 | - name: esp-idf build 83 | run: | 84 | . $IDF_PATH/export.sh 85 | cd examples/${{ matrix.example }} 86 | idf.py set-target esp32s3 87 | idf.py build 88 | 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | build/ 4 | build.clang 5 | sdkconfig.old 6 | sdkconfig 7 | **/build/ 8 | .ccls-cache 9 | doc/source/xml/ 10 | compile_commands.json 11 | examples/mpd_status/main/wifi_config.h 12 | /.idea/ 13 | *.kicad_pcb-bak 14 | *.sch-bak 15 | eink_*.h 16 | eink_*.json 17 | fp-info-cache 18 | .cache 19 | __pycache__ 20 | examples/weather/components 21 | sdkconfig 22 | managed_components/ 23 | epaper-breakout-backups/ 24 | dependencies.lock 25 | ED*.h 26 | ES*.h 27 | examples/private_*/ 28 | *.code-workspace 29 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: doc/source/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF and ePub 13 | formats: all 14 | 15 | build: 16 | os: ubuntu-22.04 17 | tools: 18 | python: "3.11" 19 | 20 | # Optionally set the version of Python and requirements required to build your docs 21 | python: 22 | install: 23 | - requirements: doc/requirements.txt 24 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(app_sources "src/epdiy.c" 3 | "src/render.c" 4 | "src/output_lcd/render_lcd.c" 5 | "src/output_lcd/lcd_driver.c" 6 | "src/output_i2s/render_i2s.c" 7 | "src/output_i2s/rmt_pulse.c" 8 | "src/output_i2s/i2s_data_bus.c" 9 | "src/output_common/lut.c" 10 | "src/output_common/lut.S" 11 | "src/output_common/line_queue.c" 12 | "src/output_common/render_context.c" 13 | "src/output_common/render_method.c" 14 | "src/font.c" 15 | "src/displays.c" 16 | "src/diff.S" 17 | "src/board_specific.c" 18 | "src/builtin_waveforms.c" 19 | "src/highlevel.c" 20 | "src/board/tps65185.c" 21 | "src/board/pca9555.c" 22 | "src/board/epd_board.c" 23 | "src/board/epd_board_common.c" 24 | "src/board/epd_board_lilygo_t5_47.c" 25 | "src/board/lilygo_board_s3.c" 26 | "src/board/epd_board_v2_v3.c" 27 | "src/board/epd_board_v4.c" 28 | "src/board/epd_board_v5.c" 29 | "src/board/epd_board_v6.c" 30 | "src/board/epd_board_v7.c" 31 | ) 32 | 33 | 34 | # Can also use IDF_VER for the full esp-idf version string but that is harder to parse. i.e. v4.1.1, v5.0-beta1, etc 35 | if (${IDF_VERSION_MAJOR} GREATER 4) 36 | idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "src/" REQUIRES driver esp_timer esp_adc esp_lcd) 37 | else() 38 | idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "src/" REQUIRES esp_adc_cal esp_timer esp_lcd) 39 | endif() 40 | 41 | # formatting specifiers maybe incompatible between idf versions because of different int definitions 42 | component_compile_options(-Wno-error=format= -Wno-format) 43 | set_source_files_properties("src/output_common/lut.c" PROPERTIES COMPILE_OPTIONS -mno-fix-esp32-psram-cache-issue) 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Rules for generating waveform and font headers from raw data. 2 | 3 | SUPPORTRED_DISPLAYS := ED060SC4 ED097OC4 ED097TC2 ED047TC1 ED133UT2 ED060XC3 ED060SCT 4 | 5 | # Generate 16 grascale update waveforms + epdiy special waveforms 6 | EXPORTED_MODES ?= 1,2,5,16,17 7 | 8 | # Generate waveforms in room temperature range 9 | EXPORT_TEMPERATURE_RANGE ?= 15,35 10 | 11 | FORMATTED_FILES := $(shell find ./ -regex '.*\.\(c\|cpp\|h\|ino\)$$' \ 12 | -not -regex '.*/\(.ccls-cache\|.cache\|waveforms\|\components\|build\)/.*' \ 13 | -not -regex '.*/img_.*.h' \ 14 | -not -regex '.*/build.*' \ 15 | -not -regex '.*/\(firasans_.*.h\|opensans.*.h\|amiri.h\|alexandria.h\|dragon.h\)' \ 16 | -not -regex '.*E[DS][0-9]*[A-Za-z]*[0-9].h') 17 | 18 | # the default headers that should come with the distribution 19 | default: \ 20 | $(patsubst %,src/waveforms/epdiy_%.h,$(SUPPORTRED_DISPLAYS)) 21 | 22 | clean: 23 | rm src/waveforms/epdiy_*.h 24 | rm src/waveforms/eink_*.h 25 | 26 | format: 27 | clang-format --style=file -i $(FORMATTED_FILES) 28 | 29 | format-check: 30 | clang-format --style=file --dry-run -Werror $(FORMATTED_FILES) 31 | 32 | 33 | src/waveforms/epdiy_%.h: src/waveforms/epdiy_%.json 34 | python3 scripts/waveform_hdrgen.py \ 35 | --export-modes $(EXPORTED_MODES) \ 36 | --temperature-range $(EXPORT_TEMPERATURE_RANGE) \ 37 | epdiy_$* < $< > $@ 38 | 39 | src/waveforms/eink_%.h: src/waveforms/eink_%.json 40 | python3 scripts/waveform_hdrgen.py \ 41 | --export-modes $(EXPORTED_MODES) \ 42 | --temperature-range $(EXPORT_TEMPERATURE_RANGE) \ 43 | eink_$* < $< > $@ 44 | 45 | src/waveforms/epdiy_%.json: 46 | python3 scripts/epdiy_waveform_gen.py $* > $@ 47 | 48 | .PHONY: default format 49 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | doxygen: 16 | doxygen 17 | 18 | .PHONY: help Makefile doxygen 19 | 20 | # Catch-all target: route all unknown targets to Sphinx using the new 21 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 22 | %: Makefile doxygen 23 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 24 | -------------------------------------------------------------------------------- /doc/html/bc_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/bc_s.png -------------------------------------------------------------------------------- /doc/html/bdwn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/bdwn.png -------------------------------------------------------------------------------- /doc/html/closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/closed.png -------------------------------------------------------------------------------- /doc/html/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/doc.png -------------------------------------------------------------------------------- /doc/html/doxygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/doxygen.png -------------------------------------------------------------------------------- /doc/html/dynsections.js: -------------------------------------------------------------------------------- 1 | /* 2 | @licstart The following is the entire license notice for the 3 | JavaScript code in this file. 4 | 5 | Copyright (C) 1997-2017 by Dimitri van Heesch 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | @licend The above is the entire license notice 22 | for the JavaScript code in this file 23 | */ 24 | function toggleVisibility(linkObj) 25 | { 26 | var base = $(linkObj).attr('id'); 27 | var summary = $('#'+base+'-summary'); 28 | var content = $('#'+base+'-content'); 29 | var trigger = $('#'+base+'-trigger'); 30 | var src=$(trigger).attr('src'); 31 | if (content.is(':visible')===true) { 32 | content.hide(); 33 | summary.show(); 34 | $(linkObj).addClass('closed').removeClass('opened'); 35 | $(trigger).attr('src',src.substring(0,src.length-8)+'closed.png'); 36 | } else { 37 | content.show(); 38 | summary.hide(); 39 | $(linkObj).removeClass('closed').addClass('opened'); 40 | $(trigger).attr('src',src.substring(0,src.length-10)+'open.png'); 41 | } 42 | return false; 43 | } 44 | 45 | function updateStripes() 46 | { 47 | $('table.directory tr'). 48 | removeClass('even').filter(':visible:even').addClass('even'); 49 | } 50 | 51 | function toggleLevel(level) 52 | { 53 | $('table.directory tr').each(function() { 54 | var l = this.id.split('_').length-1; 55 | var i = $('#img'+this.id.substring(3)); 56 | var a = $('#arr'+this.id.substring(3)); 57 | if (l 2 | 3 | 4 | 5 | 6 | 7 | 8 | My Project: Main Page 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 27 | 28 | 29 |
24 |
My Project 25 |
26 |
30 |
31 | 32 | 33 | 38 | 39 | 40 | 47 | 48 |
49 | 50 |
54 |
55 | 56 | 57 |
58 | 61 |
62 | 63 |
64 |
65 |
My Project Documentation
66 |
67 |
68 |
69 | 70 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /doc/html/menu.js: -------------------------------------------------------------------------------- 1 | /* 2 | @licstart The following is the entire license notice for the 3 | JavaScript code in this file. 4 | 5 | Copyright (C) 1997-2017 by Dimitri van Heesch 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | @licend The above is the entire license notice 22 | for the JavaScript code in this file 23 | */ 24 | function initMenu(relPath,searchEnabled,serverSide,searchPage,search) { 25 | function makeTree(data,relPath) { 26 | var result=''; 27 | if ('children' in data) { 28 | result+=''; 35 | } 36 | return result; 37 | } 38 | 39 | $('#main-nav').append(makeTree(menudata,relPath)); 40 | $('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu'); 41 | if (searchEnabled) { 42 | if (serverSide) { 43 | $('#main-menu').append('
  • '); 44 | } else { 45 | $('#main-menu').append('
  • '); 46 | } 47 | } 48 | $('#main-menu').smartmenus(); 49 | } 50 | /* @license-end */ 51 | -------------------------------------------------------------------------------- /doc/html/menudata.js: -------------------------------------------------------------------------------- 1 | /* 2 | @licstart The following is the entire license notice for the 3 | JavaScript code in this file. 4 | 5 | Copyright (C) 1997-2019 by Dimitri van Heesch 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of version 2 of the GNU General Public License as published by 9 | the Free Software Foundation 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License along 17 | with this program; if not, write to the Free Software Foundation, Inc., 18 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | @licend The above is the entire license notice 21 | for the JavaScript code in this file 22 | */ 23 | var menudata={children:[ 24 | {text:"Main Page",url:"index.html"}]} 25 | -------------------------------------------------------------------------------- /doc/html/nav_f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/nav_f.png -------------------------------------------------------------------------------- /doc/html/nav_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/nav_g.png -------------------------------------------------------------------------------- /doc/html/nav_h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/nav_h.png -------------------------------------------------------------------------------- /doc/html/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/open.png -------------------------------------------------------------------------------- /doc/html/search/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/search/close.png -------------------------------------------------------------------------------- /doc/html/search/mag_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/search/mag_sel.png -------------------------------------------------------------------------------- /doc/html/search/nomatches.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
    9 |
    No Matches
    10 |
    11 | 12 | 13 | -------------------------------------------------------------------------------- /doc/html/search/search_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/search/search_l.png -------------------------------------------------------------------------------- /doc/html/search/search_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/search/search_m.png -------------------------------------------------------------------------------- /doc/html/search/search_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/search/search_r.png -------------------------------------------------------------------------------- /doc/html/search/searchdata.js: -------------------------------------------------------------------------------- 1 | var indexSectionsWithContent = 2 | { 3 | }; 4 | 5 | var indexSectionNames = 6 | { 7 | }; 8 | 9 | var indexSectionLabels = 10 | { 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /doc/html/splitbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/splitbar.png -------------------------------------------------------------------------------- /doc/html/sync_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/sync_off.png -------------------------------------------------------------------------------- /doc/html/sync_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/sync_on.png -------------------------------------------------------------------------------- /doc/html/tab_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/tab_a.png -------------------------------------------------------------------------------- /doc/html/tab_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/tab_b.png -------------------------------------------------------------------------------- /doc/html/tab_h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/tab_h.png -------------------------------------------------------------------------------- /doc/html/tab_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/html/tab_s.png -------------------------------------------------------------------------------- /doc/latex/Makefile: -------------------------------------------------------------------------------- 1 | LATEX_CMD=pdflatex 2 | 3 | all: refman.pdf 4 | 5 | pdf: refman.pdf 6 | 7 | refman.pdf: clean refman.tex 8 | $(LATEX_CMD) refman 9 | makeindex refman.idx 10 | $(LATEX_CMD) refman 11 | latex_count=8 ; \ 12 | while egrep -s 'Rerun (LaTeX|to get cross-references right)' refman.log && [ $$latex_count -gt 0 ] ;\ 13 | do \ 14 | echo "Rerunning latex...." ;\ 15 | $(LATEX_CMD) refman ;\ 16 | latex_count=`expr $$latex_count - 1` ;\ 17 | done 18 | makeindex refman.idx 19 | $(LATEX_CMD) refman 20 | 21 | 22 | clean: 23 | rm -f *.ps *.dvi *.aux *.toc *.idx *.ind *.ilg *.log *.out *.brf *.blg *.bbl refman.pdf 24 | -------------------------------------------------------------------------------- /doc/latex/refman.tex: -------------------------------------------------------------------------------- 1 | \let\mypdfximage\pdfximage\def\pdfximage{\immediate\mypdfximage}\documentclass[twoside]{book} 2 | 3 | %% moved from doxygen.sty due to workaround for LaTex 2019 version and unmaintained tabu package 4 | \usepackage{ifthen} 5 | \ifx\requestedLaTeXdate\undefined 6 | \usepackage{array} 7 | \else 8 | \usepackage{array}[=2016-10-06] 9 | \fi 10 | %% 11 | % Packages required by doxygen 12 | \usepackage{fixltx2e} 13 | \usepackage{calc} 14 | \usepackage{doxygen} 15 | \usepackage{graphicx} 16 | \usepackage[utf8]{inputenc} 17 | \usepackage{makeidx} 18 | \usepackage{multicol} 19 | \usepackage{multirow} 20 | \PassOptionsToPackage{warn}{textcomp} 21 | \usepackage{textcomp} 22 | \usepackage[nointegrals]{wasysym} 23 | \usepackage[table]{xcolor} 24 | \usepackage{ifpdf,ifxetex} 25 | 26 | % Font selection 27 | \usepackage[T1]{fontenc} 28 | \usepackage[scaled=.90]{helvet} 29 | \usepackage{courier} 30 | \usepackage{amssymb} 31 | \usepackage{sectsty} 32 | \renewcommand{\familydefault}{\sfdefault} 33 | \allsectionsfont{% 34 | \fontseries{bc}\selectfont% 35 | \color{darkgray}% 36 | } 37 | \renewcommand{\DoxyLabelFont}{% 38 | \fontseries{bc}\selectfont% 39 | \color{darkgray}% 40 | } 41 | \newcommand{\+}{\discretionary{\mbox{\scriptsize$\hookleftarrow$}}{}{}} 42 | 43 | % Arguments of doxygenemoji: 44 | % 1) '::' form of the emoji, already "LaTeX"-escaped 45 | % 2) file with the name of the emoji without the .png extension 46 | % in case image exist use this otherwise use the '::' form 47 | \newcommand{\doxygenemoji}[2]{% 48 | \IfFileExists{./#2.png}{\raisebox{-0.1em}{\includegraphics[height=0.9em]{./#2.png}}}{#1}% 49 | } 50 | % Page & text layout 51 | \usepackage{geometry} 52 | \geometry{% 53 | a4paper,% 54 | top=2.5cm,% 55 | bottom=2.5cm,% 56 | left=2.5cm,% 57 | right=2.5cm% 58 | } 59 | \tolerance=750 60 | \hfuzz=15pt 61 | \hbadness=750 62 | \setlength{\emergencystretch}{15pt} 63 | \setlength{\parindent}{0cm} 64 | \newcommand{\doxynormalparskip}{\setlength{\parskip}{3ex plus 2ex minus 2ex}} 65 | \newcommand{\doxytocparskip}{\setlength{\parskip}{1ex plus 0ex minus 0ex}} 66 | \doxynormalparskip 67 | \makeatletter 68 | \renewcommand{\paragraph}{% 69 | \@startsection{paragraph}{4}{0ex}{-1.0ex}{1.0ex}{% 70 | \normalfont\normalsize\bfseries\SS@parafont% 71 | }% 72 | } 73 | \renewcommand{\subparagraph}{% 74 | \@startsection{subparagraph}{5}{0ex}{-1.0ex}{1.0ex}{% 75 | \normalfont\normalsize\bfseries\SS@subparafont% 76 | }% 77 | } 78 | \makeatother 79 | 80 | \makeatletter 81 | \newcommand\hrulefilll{\leavevmode\leaders\hrule\hskip 0pt plus 1filll\kern\z@} 82 | \makeatother 83 | 84 | % Headers & footers 85 | \usepackage{fancyhdr} 86 | \pagestyle{fancyplain} 87 | \fancyhead[LE]{\fancyplain{}{\bfseries\thepage}} 88 | \fancyhead[CE]{\fancyplain{}{}} 89 | \fancyhead[RE]{\fancyplain{}{\bfseries\leftmark}} 90 | \fancyhead[LO]{\fancyplain{}{\bfseries\rightmark}} 91 | \fancyhead[CO]{\fancyplain{}{}} 92 | \fancyhead[RO]{\fancyplain{}{\bfseries\thepage}} 93 | \fancyfoot[LE]{\fancyplain{}{}} 94 | \fancyfoot[CE]{\fancyplain{}{}} 95 | \fancyfoot[RE]{\fancyplain{}{\bfseries\scriptsize Generated by Doxygen }} 96 | \fancyfoot[LO]{\fancyplain{}{\bfseries\scriptsize Generated by Doxygen }} 97 | \fancyfoot[CO]{\fancyplain{}{}} 98 | \fancyfoot[RO]{\fancyplain{}{}} 99 | \renewcommand{\footrulewidth}{0.4pt} 100 | \renewcommand{\chaptermark}[1]{% 101 | \markboth{#1}{}% 102 | } 103 | \renewcommand{\sectionmark}[1]{% 104 | \markright{\thesection\ #1}% 105 | } 106 | 107 | % Indices & bibliography 108 | \usepackage{natbib} 109 | \usepackage[titles]{tocloft} 110 | \setcounter{tocdepth}{3} 111 | \setcounter{secnumdepth}{5} 112 | \makeindex 113 | 114 | \usepackage{newunicodechar} 115 | \newunicodechar{⁻}{${}^{-}$}% Superscript minus 116 | \newunicodechar{²}{${}^{2}$}% Superscript two 117 | \newunicodechar{³}{${}^{3}$}% Superscript three 118 | 119 | % Hyperlinks (required, but should be loaded last) 120 | \ifpdf 121 | \usepackage[pdftex,pagebackref=true]{hyperref} 122 | \else 123 | \ifxetex 124 | \usepackage[pagebackref=true]{hyperref} 125 | \else 126 | \usepackage[ps2pdf,pagebackref=true]{hyperref} 127 | \fi 128 | \fi 129 | 130 | \hypersetup{% 131 | colorlinks=true,% 132 | linkcolor=blue,% 133 | citecolor=blue,% 134 | unicode% 135 | } 136 | 137 | % Custom commands 138 | \newcommand{\clearemptydoublepage}{% 139 | \newpage{\pagestyle{empty}\cleardoublepage}% 140 | } 141 | 142 | \usepackage{caption} 143 | \captionsetup{labelsep=space,justification=centering,font={bf},singlelinecheck=off,skip=4pt,position=top} 144 | 145 | \usepackage{etoc} 146 | \etocsettocstyle{\doxytocparskip}{\doxynormalparskip} 147 | \renewcommand{\numberline}[1]{#1~} 148 | %===== C O N T E N T S ===== 149 | 150 | \begin{document} 151 | 152 | % Titlepage & ToC 153 | \hypersetup{pageanchor=false, 154 | bookmarksnumbered=true, 155 | pdfencoding=unicode 156 | } 157 | \pagenumbering{alph} 158 | \begin{titlepage} 159 | \vspace*{7cm} 160 | \begin{center}% 161 | {\Large My Project }\\ 162 | \vspace*{1cm} 163 | {\large Generated by Doxygen 1.8.17}\\ 164 | \end{center} 165 | \end{titlepage} 166 | \clearemptydoublepage 167 | \pagenumbering{roman} 168 | \tableofcontents 169 | \clearemptydoublepage 170 | \pagenumbering{arabic} 171 | \hypersetup{pageanchor=true} 172 | 173 | %--- Begin generated contents --- 174 | %--- End generated contents --- 175 | 176 | % Index 177 | \backmatter 178 | \newpage 179 | \phantomsection 180 | \clearemptydoublepage 181 | \addcontentsline{toc}{chapter}{\indexname} 182 | \printindex 183 | 184 | \end{document} 185 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | doxygen.exe 29 | 30 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 31 | goto end 32 | 33 | :help 34 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 35 | 36 | :end 37 | popd 38 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | breathe>=4 2 | Sphinx>=3 3 | sphinx-rtd-theme>=0.5.2 -------------------------------------------------------------------------------- /doc/source/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | EPDiy is a driver board for e-Paper (or E-ink) displays. 5 | 6 | .. _display_types: 7 | 8 | Display Types 9 | ------------- 10 | 11 | The EPDiy driver board targets multiple E-Paper displays. 12 | As the driving method for all matrix-based E-ink displays seems to be more or less the same, only the right connector and timings are needed. 13 | A table of supported displays is mainained in the :code:`README.md` file. 14 | 15 | Some of the supported displays are showcased below. 16 | 17 | ED097OC4 18 | ~~~~~~~~ 19 | 20 | The ED097OC4 was the original target of this project. It is an 9.7 inch screen, with a resolution of 1200 * 825 pixels (150dpi). 21 | It is fairly available on Ebay and AliExpress, for around 30$ to 35$. 22 | There is also a lower contrast version (ED097OC1) which also works. 23 | 24 | .. image:: img/ed097oc4.jpg 25 | 26 | ED060SC4 27 | ~~~~~~~~ 28 | 29 | This is a 6 inch display, with a 800 * 600 resolution. With 150dpi as well, it has about half the total display area of the ED097OC4. 30 | To connect this display, the 39-pin connector on the back has to be populated. 31 | It is also the display a lot of experimentation was done with (see Thanks To), so there are alternative controllers available. 32 | Besides the obvious difference in size, this display is cheaper (~20$) and also refreshes slightly faster than the ED097OC4. 33 | 34 | .. image:: img/ed060sc4.jpg 35 | 36 | ED097TC2 37 | ~~~~~~~~ 38 | 39 | Information on this display should be taken with a grain of salt. One of the displays I ordered as ED097OC4 came as ED097TC2, 40 | and upon testing it also exhibited noticably better contrast and a more responsive electro-phoretic medium. 41 | The ribbon connector looked like a ED097TC2 as well, or like the `9.7 inch screens offered by Waveshare ` (which is sold for a lot more). 42 | If you are on the lookout for such a display keep in mind the authenticity of my sample is disputable and resolution and connector type should be double-checked. 43 | 44 | .. image:: img/ed097tc2.jpg 45 | -------------------------------------------------------------------------------- /doc/source/api.rst: -------------------------------------------------------------------------------- 1 | .. _pub_api: 2 | 3 | Library API 4 | =========== 5 | 6 | Highlevel API 7 | ------------- 8 | .. doxygenfile:: epd_highlevel.h 9 | 10 | Complete API 11 | ------------ 12 | .. doxygenfile:: epdiy.h 13 | 14 | Internals 15 | ---------- 16 | .. doxygenfile:: epd_internals.h 17 | 18 | Board-Specific Extensions 19 | ------------------------- 20 | .. doxygenfile:: epd_board_specific.h 21 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | import subprocess, os 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'EPDiy' 21 | copyright = '2023, Valentin Roland' 22 | author = 'Valentin Roland' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '2.0.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'breathe' 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = [] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | breathe_projects = { "epdiy": "./xml/" } 59 | 60 | breathe_default_project = "epdiy" 61 | 62 | breathe_domain_by_extension = { 63 | "h" : "c", 64 | "c" : "c", 65 | } 66 | 67 | read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' 68 | 69 | if read_the_docs_build: 70 | 71 | subprocess.call('cd ../; doxygen', shell=True) 72 | -------------------------------------------------------------------------------- /doc/source/filegen.rst: -------------------------------------------------------------------------------- 1 | Fonts, Images, Waveforms 2 | ======================== 3 | 4 | The ESP32 is, although fairly capable, still a microcontroller. 5 | Thus, with memory and computational resources limited, it is useful to do as much of the processing 6 | for displaying fonts and images on a computer. 7 | 8 | Epdiy comes with scripts that convert fonts, images and waveforms to C headers, 9 | that you can then simply `#include` in your project. 10 | 11 | Generating Font Files 12 | --------------------- 13 | 14 | Fonts can only be used by the driver in a special header format 15 | (inspired by the Adafruit GFX library), which need to be generated from TTF fonts. 16 | For this purpose, the :code:`scripts/fontconvert.py` utility is provided. 17 | .. code-block:: 18 | 19 | fontconvert.py [-h] [--compress] [--additional-intervals ADDITIONAL_INTERVALS] name size fontstack [fontstack ...] 20 | 21 | The following example generates a header file for Fira Code at size 10, where glyphs that are not found in Fira Code will be taken from Symbola: 22 | .. code-block:: 23 | 24 | ./fontconvert.py FiraCode 10 /usr/share/fonts/TTF/FiraCode-Regular.ttf /usr/share/fonts/TTF/Symbola.ttf > ../examples/terminal/main/firacode.h 25 | 26 | You can change which unicode character codes are to be exported by specifying additional 27 | ranges of unicode code points with :code:`--additional-intervals`. 28 | Intervals are written as :code:`min,max`. 29 | To add multiple intervals, you can specify the :code:`--additional-intervals` option multiple times. 30 | .. code-block:: 31 | 32 | ./fontconvert.py ... --additional-intervals 0xE0A0,0xE0A2 --additional-intervals 0xE0B0,0xE0B3 ... 33 | 34 | The above command would add two addtitional ranges. 35 | 36 | You can enable compression with :code:`--compress`, which reduces the size of the generated font but comes at a performance cost. 37 | 38 | If the generated font files with the default characters are too large for your application, 39 | you can modify :code:`intervals` in :code:`fontconvert.py`. 40 | 41 | Generating Images 42 | ----------------- 43 | 44 | The process for converting images is very similar to converting fonts. 45 | Run the :code:`scripts/imgconvert.py` script with an input image, an image name and an output image. 46 | .. code-block:: 47 | 48 | imgconvert.py [-h] -i INPUTFILE -n NAME -o OUTPUTFILE [-maxw MAX_WIDTH] [-maxh MAX_HEIGHT] 49 | 50 | The image is converted to grayscale scaled down to match fit into :code:`MAX_WIDTH` and :code:`MAX_HEIGHT` (1200x825 by default). 51 | For accurate grayscale it is advisable to color-grade and scale the image with a dedicated tool before converting it. 52 | 53 | :code:`OUTPUTFILE` will be a C header with the following constants defined: 54 | 55 | - :code:`{NAME}_width` is the width of the image 56 | - :code:`{NAME}_height` is the height of the image 57 | - :code:`{NAME}_data` is the image data in 4 bit-per-pixel grayscale format. 58 | 59 | Converting Waveforms 60 | -------------------- 61 | 62 | 63 | .. note:: Waveform Timings and V7 64 | 65 | Epdiy builtin waveforms currently use variable frame timings to reduce the number 66 | of update cycles required. This is currently not implemented in V7. Hence, for best results it is recommended to use Vendor waveforms where available, which use constant frame timings. 67 | 68 | In comercial applications, displays are driven with information in so-called `Waveform Files`. 69 | These specify how which pulses to apply to the pixel to transition from one gray tone to another. 70 | Unfortunately, they are display-specific and proprietary. 71 | However, while they are not freely available, they can be obtained through a number of ways: 72 | 73 | - Being a large customer of E-Ink. Unfortunately not doable for mere mortals. 74 | - Finding them scattered around the internet. Examples include the `MobileRead forums `_ or the `NXP Support forum `_. 75 | - Extracting from e-Reader firmware. 76 | - Extracting from a flash chip that comes with some displays. More on this can be found `here `_. 77 | 78 | Waveforms usually come with a :code:`*.wbf` file extension. 79 | 80 | 81 | If you have a matching waveform file for your display, it can be converted to a waveform header that's usable by epdiy. 82 | The advantage of using vendor waveforms include the availability of all implemented modes in the waveform file, support of a wide range of temperatures and more accurate grayscale-to-grayscale transitions. 83 | 84 | As a first step, the waveform data is extracted from the original waveform file and stored in JSON format. 85 | This can be done using a `modified version `_ of `inkwave `_ by Marc Juul. 86 | 87 | Once a matching JSON file is obtained, the :code:`scripts/waveform_hdrgen.py` utility can be used to generate a waveform header, which can be included in your project. 88 | :: 89 | 90 | waveform_hdrgen.py [-h] [--list-modes] [--temperature-range TEMPERATURE_RANGE] [--export-modes EXPORT_MODES] name 91 | 92 | With the :code:`--list-modes` option, a list of all included modes is printed. 93 | :code:`name` specifies a name for the generated :code:`EpdWaveform` object. 94 | Additionally, the temperature range and modes to export can be limited in order to reduce file size. 95 | An example for the usage of this script can be found in the top-level :code:`Makefile` of the epdiy repository. 96 | -------------------------------------------------------------------------------- /doc/source/img/board_p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/board_p1.jpg -------------------------------------------------------------------------------- /doc/source/img/board_p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/board_p2.jpg -------------------------------------------------------------------------------- /doc/source/img/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/demo.jpg -------------------------------------------------------------------------------- /doc/source/img/ed060sc4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/ed060sc4.jpg -------------------------------------------------------------------------------- /doc/source/img/ed097oc4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/ed097oc4.jpg -------------------------------------------------------------------------------- /doc/source/img/ed097tc2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/ed097tc2.jpg -------------------------------------------------------------------------------- /doc/source/img/hardware_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/hardware_page.png -------------------------------------------------------------------------------- /doc/source/img/v6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/v6.jpg -------------------------------------------------------------------------------- /doc/source/img/vcom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/vcom.jpg -------------------------------------------------------------------------------- /doc/source/img/vcom_opamp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/vcom_opamp.jpg -------------------------------------------------------------------------------- /doc/source/img/vcom_tp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/doc/source/img/vcom_tp.jpg -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. EPDiy documentation master file, created by 2 | sphinx-quickstart on Sat Apr 18 23:41:08 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | The EPDiy Driver Board 7 | ====================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Quickstart: 12 | 13 | about.rst 14 | getting_started.rst 15 | filegen.rst 16 | tips.rst 17 | api.rst 18 | 19 | EPDiy is a driver board which talks to affordable E-Paper (or E-Ink) screens, which are usually sold as replacement screens for E-Book readers. Why are they interesting? 20 | 21 | * Easy on the eyes and paper-like aesthetics 22 | * No power consumption when not updating 23 | * Sunlight-readable 24 | 25 | Ready-made DIY modules for this size and with 4bpp (16 Grayscale) color support are currently quite expensive. This project uses Kindle replacement screens, which are available for 20$ (small) / 30$ (large) on ebay! 26 | 27 | The EPDiy driver board targets multiple E-Paper displays. As the driving method for all matrix-based E-ink displays seems to be more or less the same, only the right connector and timings are needed. The EPDiy PCB features a 33pin and a 39pin connector, which allow to drive the following display types: ED097OC4, ED060SC4, ED097TC2 28 | 29 | :ref:`getting_started` 30 | 31 | .. image:: img/demo.jpg 32 | 33 | .. image:: img/board_p1.jpg 34 | 35 | .. image:: img/board_p2.jpg 36 | -------------------------------------------------------------------------------- /doc/source/tips.rst: -------------------------------------------------------------------------------- 1 | 2 | Tips & Tricks 3 | ============= 4 | 5 | 6 | Temperature Dependence 7 | ---------------------- 8 | 9 | The display refresh speed depends on the environmental temperature. 10 | Thus, if your room temperature is significantly different from ~22°C, grayscale 11 | accuracy might be affected when using the builtin waveform. 12 | This can be mitigated by using a different timing curve, but this would require calibrating the display timings at that temperature. 13 | If you did this for some temperature other than room temperature, please submit a pull request! 14 | 15 | Deep Sleep Current 16 | ------------------ 17 | 18 | Board Revision V5 is optimized for running from a battery thanks to its low deep sleep current consumption. 19 | In order to achieve the lowest possible deep sleep current, call 20 | :: 21 | 22 | epd_deinit() 23 | 24 | before going to deep sleep. This will de-initialize the I2S peripheral used to drive the diplay and bring the pins used by epdiy to a low-power state. 25 | You should be able to achieve a deep-sleep current of less than 13µA. 26 | If your deep-sleep current is much higher, please check your attached peripherals. 27 | With some modules, you have to isolate GPIO 12 before going to deep sleep: 28 | :: 29 | 30 | rtc_gpio_isolate(GPIO_NUM_12) 31 | 32 | Adding a New Display 33 | -------------------- 34 | 35 | This section is work-in-progress. 36 | 37 | - Add display definitions in :code:`displays.c` and :code:`epd_display.h`. 38 | - Include waveform in :code:`bulitin_waveforms.c` 39 | - Calibrate timing curve in :code:`scripts/generate_epdiy_waveforms.py`. 40 | - Add to the list of displays to build waveforms for in :code:`Makefile` 41 | - Document 42 | -------------------------------------------------------------------------------- /examples/calibration_helper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(vcom_calibration_helper) 5 | -------------------------------------------------------------------------------- /examples/calibration_helper/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "main.c") 2 | 3 | idf_component_register(SRCS ${app_sources} REQUIRES epdiy) 4 | -------------------------------------------------------------------------------- /examples/calibration_helper/main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple helper program enabling EPD power to allow for easier VCOM calibration. 3 | * 4 | * This is only needed for boards V5 or lower! 5 | **/ 6 | 7 | #include 8 | #include 9 | #include "esp_heap_caps.h" 10 | #include "esp_log.h" 11 | #include "freertos/FreeRTOS.h" 12 | #include "freertos/task.h" 13 | #include "sdkconfig.h" 14 | 15 | #include "epdiy.h" 16 | 17 | // choose the default demo board depending on the architecture 18 | #ifdef CONFIG_IDF_TARGET_ESP32 19 | #define DEMO_BOARD epd_board_v6 20 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 21 | #define DEMO_BOARD epd_board_v7 22 | #endif 23 | 24 | void enable_vcom() { 25 | epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K); 26 | ESP_LOGI("main", "waiting for one second before poweron..."); 27 | vTaskDelay(1000); 28 | ESP_LOGI("main", "enabling VCOMM..."); 29 | epd_poweron(); 30 | ESP_LOGI( 31 | "main", 32 | "VCOMM enabled. You can now adjust the on-board trimmer to the VCOM value specified on the " 33 | "display." 34 | ); 35 | while (1) { 36 | vTaskDelay(1000); 37 | }; 38 | } 39 | 40 | void app_main() { 41 | enable_vcom(); 42 | } 43 | -------------------------------------------------------------------------------- /examples/calibration_helper/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/calibration_helper/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(firmware) 5 | -------------------------------------------------------------------------------- /examples/demo/Makefile: -------------------------------------------------------------------------------- 1 | FONT_DIR ?= /usr/share/fonts/TTF 2 | FONTCONVERT ?= ../../scripts/fontconvert.py 3 | IMGCONVERT ?= ../../scripts/imgconvert.py 4 | 5 | .PHONY: fonts 6 | fonts: \ 7 | main/firasans_20.h \ 8 | main/firasans_12.h \ 9 | main/img_zebra.h \ 10 | main/img_board.h \ 11 | main/img_giraffe.h \ 12 | main/img_beach.h 13 | 14 | main/firasans_%.h: $(FONTCONVERT) 15 | python3 $(FONTCONVERT) --compress FiraSans_$* $* $(FONT_DIR)/FiraSans-Regular.ttf $(FONT_DIR)/Symbola.ttf > $@ 16 | 17 | main/img_%.h: files/%.jpg 18 | python3 $(IMGCONVERT) -i $< -o $@ -n img_$* 19 | 20 | clean: 21 | rm main/*.h 22 | -------------------------------------------------------------------------------- /examples/demo/files/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/demo/files/beach.jpg -------------------------------------------------------------------------------- /examples/demo/files/board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/demo/files/board.jpg -------------------------------------------------------------------------------- /examples/demo/files/giraffe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/demo/files/giraffe.jpg -------------------------------------------------------------------------------- /examples/demo/files/zebra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/demo/files/zebra.jpg -------------------------------------------------------------------------------- /examples/demo/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "main.c") 2 | 3 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 4 | idf_component_register(SRCS ${app_sources} REQUIRES epdiy) 5 | -------------------------------------------------------------------------------- /examples/demo/main/main.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the Arduino wrapper for the "Demo" example. 3 | * Please go to the main.c for the main example file. 4 | * 5 | * This example was developed for the ESP IoT Development Framework (IDF). 6 | * You can still use this code in the Arduino IDE, but it may not look 7 | * and feel like a classic Arduino sketch. 8 | * If you are looking for an example with Arduino look-and-feel, 9 | * please check the other examples. 10 | */ 11 | 12 | // Important: These are C functions, so they must be declared with C linkage! 13 | extern "C" { 14 | void idf_setup(); 15 | void idf_loop(); 16 | } 17 | 18 | void setup() { 19 | if (psramInit()) { 20 | Serial.println("\nThe PSRAM is correctly initialized"); 21 | } else { 22 | Serial.println("\nPSRAM does not work"); 23 | } 24 | 25 | idf_setup(); 26 | } 27 | 28 | void loop() { 29 | idf_loop(); 30 | } 31 | -------------------------------------------------------------------------------- /examples/demo/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/demo/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/dragon/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(dragon_example) 5 | -------------------------------------------------------------------------------- /examples/dragon/README.md: -------------------------------------------------------------------------------- 1 | A demo showing a Full-Screen Image 2 | ================================== 3 | 4 | *The image size is chosen to fit a 1200 * 825 display!* 5 | 6 | Image by David REVOY / CC BY (https://creativecommons.org/licenses/by/3.0) 7 | https://commons.wikimedia.org/wiki/File:Durian_-_Sintel-wallpaper-dragon.jpg 8 | -------------------------------------------------------------------------------- /examples/dragon/cc_dragon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/dragon/cc_dragon.jpg -------------------------------------------------------------------------------- /examples/dragon/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "main.c") 2 | 3 | idf_component_register(SRCS ${app_sources} REQUIRES epdiy) 4 | -------------------------------------------------------------------------------- /examples/dragon/main/main.c: -------------------------------------------------------------------------------- 1 | /* Simple firmware for a ESP32 displaying a static image on an EPaper Screen */ 2 | 3 | #include "esp_heap_caps.h" 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | 7 | #include "dragon.h" 8 | #include "epd_highlevel.h" 9 | #include "epdiy.h" 10 | 11 | EpdiyHighlevelState hl; 12 | 13 | // choose the default demo board depending on the architecture 14 | #ifdef CONFIG_IDF_TARGET_ESP32 15 | #define DEMO_BOARD epd_board_v6 16 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 17 | #define DEMO_BOARD epd_board_v7 18 | #endif 19 | 20 | void idf_loop() { 21 | EpdRect dragon_area = { .x = 0, .y = 0, .width = dragon_width, .height = dragon_height }; 22 | 23 | int temperature = 25; 24 | 25 | epd_poweron(); 26 | epd_fullclear(&hl, temperature); 27 | 28 | epd_copy_to_framebuffer(dragon_area, dragon_data, epd_hl_get_framebuffer(&hl)); 29 | 30 | enum EpdDrawError _err = epd_hl_update_screen(&hl, MODE_GC16, temperature); 31 | epd_poweroff(); 32 | 33 | vTaskDelay(1000); 34 | } 35 | 36 | void idf_setup() { 37 | epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K); 38 | epd_set_vcom(1560); 39 | hl = epd_hl_init(EPD_BUILTIN_WAVEFORM); 40 | } 41 | 42 | #ifndef ARDUINO_ARCH_ESP32 43 | void app_main() { 44 | idf_setup(); 45 | 46 | while (1) { 47 | idf_loop(); 48 | }; 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /examples/dragon/main/main.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the Arduino wrapper for the "Demo" example. 3 | * Please go to the main.c for the main example file. 4 | * 5 | * This example was developed for the ESP IoT Development Framework (IDF). 6 | * You can still use this code in the Arduino IDE, but it may not look 7 | * and feel like a classic Arduino sketch. 8 | * If you are looking for an example with Arduino look-and-feel, 9 | * please check the other examples. 10 | */ 11 | 12 | // Important: These are C functions, so they must be declared with C linkage! 13 | extern "C" { 14 | void idf_setup(); 15 | void idf_loop(); 16 | } 17 | 18 | void setup() { 19 | if (psramInit()) { 20 | Serial.println("\nThe PSRAM is correctly initialized"); 21 | } else { 22 | Serial.println("\nPSRAM does not work"); 23 | } 24 | 25 | idf_setup(); 26 | } 27 | 28 | void loop() { 29 | idf_loop(); 30 | } 31 | -------------------------------------------------------------------------------- /examples/dragon/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/dragon/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/fb_mode_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(firmware) 5 | -------------------------------------------------------------------------------- /examples/fb_mode_test/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "main.c") 2 | 3 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 4 | idf_component_register(SRCS ${app_sources} REQUIRES epdiy) 5 | -------------------------------------------------------------------------------- /examples/fb_mode_test/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/fb_mode_test/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/grayscale_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(grayscale_example) 5 | -------------------------------------------------------------------------------- /examples/grayscale_test/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "main.c") 2 | 3 | idf_component_register(SRCS ${app_sources} REQUIRES epdiy) 4 | -------------------------------------------------------------------------------- /examples/grayscale_test/main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Test program for displaying a grayscale pattern. 3 | * 4 | * Use this to calibrate grayscale timings for new displays or test alternative waveforms. 5 | */ 6 | 7 | #include 8 | #include 9 | #include "esp_heap_caps.h" 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "sdkconfig.h" 13 | 14 | #include "epd_highlevel.h" 15 | #include "epdiy.h" 16 | 17 | // choose the default demo board depending on the architecture 18 | #ifdef CONFIG_IDF_TARGET_ESP32 19 | #define DEMO_BOARD epd_board_v6 20 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 21 | #define DEMO_BOARD epd_board_v7 22 | #endif 23 | 24 | #define WAVEFORM EPD_BUILTIN_WAVEFORM 25 | 26 | EpdiyHighlevelState hl; 27 | 28 | void write_grayscale_pattern(bool direction, uint8_t* fb) { 29 | int ep_width = epd_width(); 30 | uint8_t grayscale_line[ep_width / 2]; 31 | if (direction) { 32 | for (uint32_t i = 0; i < ep_width / 2; i++) { 33 | uint8_t segment = i / (ep_width / 16 / 2); 34 | grayscale_line[i] = (segment << 4) | segment; 35 | } 36 | } else { 37 | for (uint32_t i = 0; i < ep_width / 2; i++) { 38 | uint8_t segment = (ep_width / 2 - i - 1) / (ep_width / 16 / 2); 39 | grayscale_line[i] = (segment << 4) | segment; 40 | } 41 | } 42 | for (uint32_t y = 0; y < epd_height(); y++) { 43 | memcpy(fb + ep_width / 2 * y, grayscale_line, ep_width / 2); 44 | } 45 | } 46 | 47 | void loop() { 48 | uint8_t* fb = epd_hl_get_framebuffer(&hl); 49 | 50 | write_grayscale_pattern(false, fb); 51 | 52 | int temperature = 25; // epd_ambient_temperature(); 53 | 54 | epd_poweron(); 55 | epd_clear(); 56 | enum EpdDrawError err = epd_hl_update_screen(&hl, MODE_GC16, temperature); 57 | epd_poweroff(); 58 | 59 | vTaskDelay(5000); 60 | 61 | write_grayscale_pattern(true, fb); 62 | 63 | epd_poweron(); 64 | err = epd_hl_update_screen(&hl, MODE_GC16, temperature); 65 | if (err != EPD_DRAW_SUCCESS) { 66 | printf("Error in epd_hl_update_screen:%d\n", err); 67 | } 68 | epd_poweroff(); 69 | 70 | vTaskDelay(100000); 71 | } 72 | 73 | void IRAM_ATTR app_main() { 74 | epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K); 75 | epd_set_vcom(1560); 76 | hl = epd_hl_init(WAVEFORM); 77 | 78 | while (1) { 79 | loop(); 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /examples/grayscale_test/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/grayscale_test/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/http-server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | set(EXTRA_COMPONENT_DIRS "../../") 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(http-server) 7 | -------------------------------------------------------------------------------- /examples/http-server/README.md: -------------------------------------------------------------------------------- 1 | # HTTP server 2 | 3 | Runs an HTTP server that will draw images on the screen. 4 | Useful for setting up a small digital frame that can be remotely controlled. 5 | 6 | ## Config 7 | 1. In `main/server.h`, edit `WIFI_SSID` and `WIFI_PASSWORD` to match your wifi config 8 | 2. In `main/main.c`, edit `n_epd_setup` to refer to the right EPD screen 9 | 10 | ## Running 11 | flash (`idf.py flash`), then connect the EPDiy to a power source (computer is fine). 12 | The endpoints are: 13 | 1. `GET /`, prints the screen temp / height / width as headers 14 | 2. `POST /clear`, clears the screen 15 | 3. `POST /draw`, expects: 16 | 1. a body that is a binary stream already encoded to EPDiy's standards (like the one in `dragon.h`). 17 | 2. Headers `width`, `height` 18 | 3. Optional headers `x`,`y` (default to 0) 19 | 4. Optional header `clear`, if set to nonzero integer will force-clear the screen before drawing 20 | 21 | ## Helper script 22 | `send_image.py` is a friendlier client. 23 | ```bash 24 | $ ./send_image.py ESP_IP info 25 | EpdInfo(width=1024, height=768, temperature=20) 26 | 27 | $ ./send_image.py ESP_IP clear 28 | # Clears the screen 29 | 30 | $ ./send_image.y ESP_IP draw /tmp/spooder-man.png 31 | # Draws on screen 32 | ``` 33 | Thanks to argparse, all arguments are visible with `--help`. 34 | Requires `requests` and `PIL` (or Pillow) 35 | -------------------------------------------------------------------------------- /examples/http-server/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | -------------------------------------------------------------------------------- /examples/http-server/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set( 2 | app_sources "epd.c" "server.c" "main.c" 3 | ) 4 | 5 | idf_component_register( 6 | SRCS ${app_sources} 7 | REQUIRES epdiy esp_wifi nvs_flash esp_http_server esp_netif 8 | ) 9 | -------------------------------------------------------------------------------- /examples/http-server/main/epd.c: -------------------------------------------------------------------------------- 1 | #include "epd.h" 2 | 3 | static EpdiyHighlevelState hl; 4 | static EpdData data; 5 | 6 | static inline void checkError(enum EpdDrawError err) { 7 | if (err != EPD_DRAW_SUCCESS) { 8 | ESP_LOGE("demo", "draw error: %X", err); 9 | } 10 | } 11 | 12 | EpdData n_epd_data() { 13 | return data; 14 | } 15 | 16 | void n_epd_setup(const EpdDisplay_t* display) { 17 | epd_init(&epd_board_v7, display, EPD_LUT_64K); 18 | epd_set_vcom(1560); 19 | hl = epd_hl_init(EPD_BUILTIN_WAVEFORM); 20 | epd_set_rotation(EPD_ROT_LANDSCAPE); 21 | data.width = epd_rotated_display_width(); 22 | data.height = epd_rotated_display_height(); 23 | data.temperature = epd_ambient_temperature(); 24 | } 25 | 26 | void n_epd_clear() { 27 | epd_poweron(); 28 | epd_fullclear(&hl, data.temperature); 29 | epd_poweroff(); 30 | } 31 | 32 | void n_epd_draw(uint8_t* content, int x, int y, int width, int height) { 33 | uint8_t* fb = epd_hl_get_framebuffer(&hl); 34 | EpdRect area = { 35 | .x = x, 36 | .y = y, 37 | .width = width, 38 | .height = height, 39 | }; 40 | epd_draw_rotated_image(area, content, fb); 41 | epd_poweron(); 42 | checkError(epd_hl_update_screen(&hl, MODE_GC16, data.temperature)); 43 | epd_poweroff(); 44 | } 45 | -------------------------------------------------------------------------------- /examples/http-server/main/epd.h: -------------------------------------------------------------------------------- 1 | #ifndef EPD_H 2 | #define EPD_H 3 | 4 | #include "epdiy.h" 5 | #include 6 | 7 | typedef struct { 8 | int width; 9 | int height; 10 | int temperature; 11 | } EpdData; 12 | 13 | EpdData n_epd_data(); 14 | void n_epd_setup(); 15 | void n_epd_clear(); 16 | void n_epd_draw(uint8_t* content, int x, int y, int width, int height); 17 | #endif /* EPD_H */ 18 | -------------------------------------------------------------------------------- /examples/http-server/main/main.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | #include "esp_http_server.h" 3 | #include "epd.h" 4 | #include "esp_heap_caps.h" 5 | #include "settings.h" 6 | 7 | #define WRITE_HEADER(req, buffer, name, format, src) \ 8 | sprintf(buffer, format, src); \ 9 | ESP_ERROR_CHECK(httpd_resp_set_hdr(req, name, buffer)); 10 | static esp_err_t http_index(httpd_req_t* req) { 11 | EpdData data = n_epd_data(); 12 | char width[20], height[20], temperature[20]; 13 | WRITE_HEADER(req, width, "width", "%d", data.width); 14 | WRITE_HEADER(req, height, "height", "%d", data.height); 15 | WRITE_HEADER(req, temperature, "temperature", "%d", data.temperature); 16 | const char* response = "Hello! Check headers\n"; 17 | httpd_resp_set_type(req, "text/plain"); 18 | httpd_resp_set_status(req, "200"); 19 | httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN); 20 | return ESP_OK; 21 | } 22 | 23 | esp_err_t http_clear(httpd_req_t* req) { 24 | ESP_LOGI(__FUNCTION__, "Clear\n"); 25 | n_epd_clear(); 26 | const char* response = "Cleared\n"; 27 | httpd_resp_set_type(req, "text/plain"); 28 | httpd_resp_set_status(req, "200"); 29 | httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN); 30 | return ESP_OK; 31 | } 32 | 33 | esp_err_t http_draw(httpd_req_t* req) { 34 | // optional headers: x,y. Default to 0 35 | // required headers: height, width 36 | // Content should be a stream of special bytes - we're reading 4 bits at a time. 37 | int x, y, width, height, clear; 38 | char header[20]; 39 | memset(header, 0, 20); 40 | if (httpd_req_get_hdr_value_str(req, "clear", header, 20) == ESP_OK) { 41 | sscanf(header, "%d", &clear); 42 | } else { 43 | clear = 0; 44 | } 45 | if (httpd_req_get_hdr_value_str(req, "x", header, 20) == ESP_OK) { 46 | sscanf(header, "%d", &x); 47 | } else { 48 | x = 0; 49 | } 50 | if (httpd_req_get_hdr_value_str(req, "y", header, 20) == ESP_OK) { 51 | sscanf(header, "%d", &y); 52 | } else { 53 | y = 0; 54 | } 55 | if (httpd_req_get_hdr_value_str(req, "width", header, 20) == ESP_OK) { 56 | sscanf(header, "%d", &width); 57 | } else { 58 | char response[60]; 59 | sprintf(response, "Missing header width"); 60 | httpd_resp_set_type(req, "text/plain"); 61 | httpd_resp_set_status(req, "400"); 62 | httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN); 63 | return ESP_OK; 64 | } 65 | if (httpd_req_get_hdr_value_str(req, "height", header, 20) == ESP_OK) { 66 | sscanf(header, "%d", &height); 67 | } else { 68 | char response[60]; 69 | sprintf(response, "Missing header height"); 70 | httpd_resp_set_type(req, "text/plain"); 71 | httpd_resp_set_status(req, "400"); 72 | httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN); 73 | return ESP_OK; 74 | } 75 | 76 | // READING STREAM 77 | int req_size = req->content_len; 78 | char* content = (char*)heap_caps_malloc(req_size, MALLOC_CAP_SPIRAM); 79 | if (content == NULL) { 80 | char msg[50]; 81 | sprintf(msg, "Failed to allocate %d chars\n", req_size); 82 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg); 83 | return ESP_ERR_INVALID_ARG; 84 | } 85 | int current_pos = 0; 86 | int amount_recieved; 87 | while ((amount_recieved = httpd_req_recv(req, (content + current_pos), req_size)) > 0) { 88 | ESP_LOGI(__FUNCTION__, "Read %d bytes\n", amount_recieved); 89 | current_pos += amount_recieved; 90 | } 91 | if (amount_recieved < 0) { 92 | char msg[50]; 93 | heap_caps_free(content); 94 | ESP_LOGE(msg, "Failed to read byets. Error code %d\n", amount_recieved); 95 | httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg); 96 | return ESP_ERR_INVALID_ARG; 97 | } 98 | ESP_LOGI(__FUNCTION__, "Done reading %d bytes out of %d\n", current_pos, req_size); 99 | 100 | if (clear) { 101 | n_epd_clear(); 102 | } 103 | n_epd_draw(((uint8_t*)content), x, y, width, height); 104 | heap_caps_free(content); 105 | 106 | // Done reading 107 | char response[100]; 108 | sprintf( 109 | response, "x %d, y %d, width %d, height %d, byte count %d\n", x, y, width, height, req_size 110 | ); 111 | httpd_resp_set_type(req, "text/plain"); 112 | httpd_resp_set_status(req, "200"); 113 | httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN); 114 | return ESP_OK; 115 | } 116 | 117 | void register_paths(httpd_handle_t server) { 118 | { 119 | httpd_uri_t uri 120 | = { .uri = "/", .method = HTTP_GET, .handler = http_index, .user_ctx = NULL }; 121 | httpd_register_uri_handler(server, &uri); 122 | } 123 | { 124 | httpd_uri_t uri 125 | = { .uri = "/clear", .method = HTTP_POST, .handler = http_clear, .user_ctx = NULL }; 126 | httpd_register_uri_handler(server, &uri); 127 | } 128 | { 129 | httpd_uri_t uri 130 | = { .uri = "/draw", .method = HTTP_POST, .handler = http_draw, .user_ctx = NULL }; 131 | httpd_register_uri_handler(server, &uri); 132 | } 133 | } 134 | 135 | void app_main(void) { 136 | // Initialize NVS, needed for wifi 137 | esp_err_t ret = nvs_flash_init(); 138 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 139 | ESP_ERROR_CHECK(nvs_flash_erase()); 140 | ret = nvs_flash_init(); 141 | } 142 | ESP_ERROR_CHECK(ret); 143 | 144 | httpd_handle_t server = get_server(); 145 | if (server != NULL) { 146 | register_paths(server); 147 | } 148 | n_epd_setup(&SCREEN_MODEL); 149 | } 150 | -------------------------------------------------------------------------------- /examples/http-server/main/server.c: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | httpd_handle_t get_server(void); 4 | 5 | static void wifi_init_sta(void) { 6 | // Initialize the ESP-NETIF 7 | esp_netif_init(); 8 | esp_event_loop_create_default(); 9 | 10 | // Create default event loop 11 | esp_netif_create_default_wifi_sta(); 12 | 13 | // Initialize the Wi-Fi driver 14 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 15 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 16 | 17 | // Set Wi-Fi mode to station 18 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 19 | 20 | // Configure Wi-Fi connection 21 | wifi_config_t wifi_config = { 22 | .sta = { 23 | .ssid = WIFI_SSID, 24 | .password = WIFI_PASSWORD, 25 | }, 26 | }; 27 | 28 | // Set the Wi-Fi configuration 29 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); 30 | ESP_ERROR_CHECK(esp_wifi_start()); 31 | ESP_ERROR_CHECK(esp_wifi_connect()); 32 | } 33 | 34 | httpd_handle_t start_webserver(void) { 35 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 36 | config.lru_purge_enable = true; 37 | 38 | httpd_handle_t server = NULL; 39 | ESP_ERROR_CHECK(httpd_start(&server, &config)); 40 | return server; 41 | } 42 | 43 | httpd_handle_t get_server(void) { 44 | wifi_init_sta(); 45 | return start_webserver(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/http-server/main/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | 4 | #include 5 | #include 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/task.h" 8 | #include "esp_event.h" 9 | #include "esp_log.h" 10 | #include "nvs_flash.h" 11 | #include "esp_wifi.h" 12 | #include "esp_netif.h" 13 | #include "esp_http_server.h" 14 | 15 | #include "settings.h" 16 | 17 | httpd_handle_t get_server(void); 18 | 19 | #endif /* SERVER_H */ 20 | -------------------------------------------------------------------------------- /examples/http-server/main/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #define WIFI_SSID "best ssid" // Replace with your Wi-Fi SSID 5 | #define WIFI_PASSWORD "great password much wow" // Replace with your Wi-Fi password 6 | 7 | #define SCREEN_MODEL ED060XC3 8 | 9 | #endif /* SETTINGS_H */ 10 | -------------------------------------------------------------------------------- /examples/http-server/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/http-server/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/http-server/send_image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import requests 4 | from typing import NamedTuple 5 | import PIL 6 | import PIL.Image 7 | 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("hostname") 12 | subparsers = parser.add_subparsers(dest="command") 13 | clear_parser = subparsers.add_parser("clear") 14 | draw_parser = subparsers.add_parser("draw") 15 | draw_parser.add_argument("-c", "--clear", action="store_true") 16 | draw_parser.add_argument("file") 17 | info_praser = subparsers.add_parser("info") 18 | 19 | return parser.parse_args() 20 | 21 | 22 | def clear(hostname): 23 | requests.post(f"http://{hostname}/clear").raise_for_status() 24 | 25 | 26 | class EpdInfo(NamedTuple): 27 | width: int 28 | height: int 29 | temperature: int 30 | 31 | @classmethod 32 | def from_response(cls, resp): 33 | return cls( 34 | width=int(resp.headers["width"]), 35 | height=int(resp.headers["height"]), 36 | temperature=int(resp.headers["temperature"]), 37 | ) 38 | 39 | 40 | class Dimensions(NamedTuple): 41 | width: int 42 | height: int 43 | 44 | 45 | def info(hostname): 46 | resp = requests.get(f"http://{hostname}") 47 | resp.raise_for_status() 48 | return EpdInfo.from_response(resp) 49 | 50 | 51 | def image_refit(image: PIL.Image, bounder: Dimensions) -> PIL.Image: 52 | bounder_ratio = bounder.width / bounder.height 53 | image_width, image_height = image.size 54 | 55 | image_width_by_height = int(image_height * bounder_ratio) 56 | image_height_by_width = int(image_width / bounder_ratio) 57 | if image_width > image_width_by_height: 58 | new_dimensions = Dimensions(image_width_by_height, image_height) 59 | else: 60 | new_dimensions = Dimensions(image_width, image_height_by_width) 61 | return PIL.ImageOps.fit(image, new_dimensions) 62 | 63 | 64 | def convert_8bit_to_4bit(bytestring): 65 | fourbit = [] 66 | for i in range(0, len(bytestring), 2): 67 | first_nibble = int(bytestring[i] / 17) 68 | second_nibble = int(bytestring[i + 1] / 17) 69 | fourbit += [first_nibble << 4 | second_nibble] 70 | fourbit = bytes(fourbit) 71 | return fourbit 72 | 73 | 74 | def draw(hostname, filename, clear): 75 | inf = info(hostname) 76 | img = PIL.Image.open(filename) 77 | img = image_refit(img, Dimensions(width=inf.width, height=inf.height)) 78 | img = img.resize((inf.width, inf.height)) 79 | img = img.convert("L") 80 | img_bytes = convert_8bit_to_4bit(img.tobytes()) 81 | requests.post( 82 | f"http://{hostname}/draw", 83 | headers={ 84 | "width": str(inf.width), 85 | "height": str(inf.height), 86 | "x": "0", 87 | "y": "0", 88 | "clear": "1" if clear else "0", 89 | }, 90 | data=img_bytes, 91 | ) 92 | 93 | 94 | def main(): 95 | args = parse_args() 96 | if args.command == "clear": 97 | clear(args.hostname) 98 | elif args.command == "info": 99 | print(info(args.hostname)) 100 | elif args.command == "draw": 101 | draw(args.hostname, args.file, args.clear) 102 | else: 103 | raise Exception(f"Unknown command {args.command}") 104 | 105 | 106 | if __name__ == "__main__": 107 | main() 108 | -------------------------------------------------------------------------------- /examples/lilygo-t5-47-epd-platformio/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch -------------------------------------------------------------------------------- /examples/lilygo-t5-47-epd-platformio/README.md: -------------------------------------------------------------------------------- 1 | # LilyGo T5 4.7 Inch Example 2 | 3 | This is an example project to kick-start you using the LilyGo T5 4.7 inch EPaper display. 4 | 5 | It also incorporates a few deepsleep methods to show how to use deepsleep to effectively save battery. 6 | Additionally, it inherits and modifies the battery calculation code provided in examples by the manufacturer. 7 | -------------------------------------------------------------------------------- /examples/lilygo-t5-47-epd-platformio/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /examples/lilygo-t5-47-epd-platformio/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [common_env_data] 12 | framework = arduino 13 | board_build.f_cpu = 240000000L 14 | upload_speed = 921600 15 | monitor_speed = 115200 16 | lib_deps = 17 | Wire 18 | https://github.com/vroland/epdiy.git 19 | 20 | build_flags = 21 | ; device has PRSRAM 22 | ; and should be used for ram intensive display work 23 | -DBOARD_HAS_PSRAM 24 | ; Setup display format and model via build flags 25 | -DCONFIG_EPD_DISPLAY_TYPE_ED047TC1 26 | -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47 27 | 28 | [env:esp32dev] 29 | platform = espressif32 30 | board = esp32dev 31 | framework = ${common_env_data.framework} 32 | upload_speed = ${common_env_data.upload_speed} 33 | monitor_speed = ${common_env_data.monitor_speed} 34 | lib_deps = ${common_env_data.lib_deps} 35 | build_flags = ${common_env_data.build_flags} 36 | 37 | ; uncomment if you want serial debugging with exception decoding 38 | ; build_type = debug 39 | ; monitor_filters = esp32_exception_decoder 40 | -------------------------------------------------------------------------------- /examples/screen_diag/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about build system see 2 | # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html 3 | # The following five lines of boilerplate have to be in your project's 4 | # CMakeLists in this exact order for cmake to work correctly 5 | cmake_minimum_required(VERSION 3.18) 6 | 7 | # dependencies 8 | set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../../") 9 | 10 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 11 | project(screen_diag) 12 | 13 | set(IDF_VER_SANE "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}") 14 | if (IDF_VER_SANE VERSION_LESS "5.0.0") 15 | message(FATAL_ERROR "screen_diag requires at least ESP-IDF v5.0.0") 16 | endif() 17 | -------------------------------------------------------------------------------- /examples/screen_diag/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "screen_diag.c" "epd.c" "commands.c" "commands/system.c" 2 | "commands/screen.c" "commands/graphics.c" "commands/tests.c" 3 | INCLUDE_DIRS "." "res/fonts" 4 | REQUIRES epdiy console nvs_flash fatfs esp_app_format) 5 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands.c: -------------------------------------------------------------------------------- 1 | #include "commands.h" 2 | 3 | bool validate_color(uint8_t* inout_color, struct arg_int* arg) { 4 | int user_color = arg->count != 0 ? arg->ival[0] : *inout_color; 5 | if (user_color < 0 || user_color > 0xFF) { 6 | printf( 7 | "Invalid color %d (0x%02x): Must be in range 0x00 to 0xFF.\r\n", 8 | user_color, 9 | (uint8_t)user_color 10 | ); 11 | return false; 12 | } 13 | 14 | *inout_color = (uint8_t)user_color; 15 | 16 | return true; 17 | } 18 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* Helper functions and macros for common use cases, 3 | * when registering or implementing commands. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #ifndef ARRAY_SIZE 13 | /** 14 | * Returns size of a (static) C array. 15 | */ 16 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) 17 | #endif 18 | 19 | /** 20 | * Checks whether the given color argument is a valid color. 21 | * I.e. when it's color value is possible to represent by an uint8_t (0x00 - 0xFF). 22 | * If no color argument was provided by the user, returns the default color of 0x00. 23 | * 24 | * @param inout_color initial value will be used as default color, 25 | * when the user did not specify a color. 26 | * when successful, will be set to the given color. 27 | * @param arg user-specified color argument 28 | * @return whether the given color is valid 29 | */ 30 | bool validate_color(uint8_t* inout_color, struct arg_int* arg); 31 | 32 | /** 33 | * Determines the number of arguments stored in a struct (container). 34 | * That is usually the number of arguments for a given command and 35 | * can be used when initializing the arg_end parameter. 36 | * 37 | * This macro assumes, that 38 | * 1. each argument inside the struct is referenced by a pointer, 39 | * 2. each struct ends with an arg_end*, 40 | * 3. there are no other members in the struct, besides different argument types. 41 | */ 42 | #define NARGS(container) ((sizeof(container) / sizeof(struct arg_end*)) - 1) 43 | 44 | /** 45 | * Handles argument validation for the command. 46 | * Assumes that `argc` and `argv` variables are visible, thus should 47 | * only be used inside the command implementation function. 48 | * 49 | * @param args_struct name of the (static) argument struct. 50 | */ 51 | #define HANDLE_ARGUMENTS(args_struct) \ 52 | { \ 53 | int nerrors = arg_parse(argc, argv, (void**)&args_struct); \ 54 | if (nerrors > 0) { \ 55 | arg_print_errors(stdout, args_struct.end, argv[0]); \ 56 | return 1; \ 57 | } \ 58 | } 59 | 60 | /** 61 | * Get optional argument value if provided by the user. Otherwise use the default value. 62 | * 63 | * @param arg pointer to argument struct (e.g. struct arg_int*) 64 | * @param accessor accessor used to retrieve the first value (e.g. ival for struct arg_int) 65 | * @param default_value 66 | */ 67 | #define GET_ARG(arg, accessor, default_value) \ 68 | (arg)->count == 1 ? (arg)->accessor[0] : (default_value) 69 | 70 | /** 71 | * Alias for GET_ARG, specialized for int arguments. 72 | * 73 | * @param arg pointer to an struct arg_int 74 | * @param default_value 75 | */ 76 | #define GET_INT_ARG(arg, default_value) GET_ARG(arg, ival, default_value) 77 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands/graphics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void register_graphics_commands(void); 4 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands/screen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void register_screen_commands(void); 4 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands/system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void register_system_commands(void); 4 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands/tests.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "../commands.h" 9 | #include "../epd.h" 10 | #include "fonts.h" 11 | 12 | static struct { 13 | struct arg_int* slope; 14 | struct arg_int* width; 15 | struct arg_int* color; 16 | struct arg_end* end; 17 | } render_stairs_args; 18 | 19 | static struct { 20 | struct arg_int* gutter; 21 | struct arg_int* color; 22 | struct arg_end* end; 23 | } render_grid_args; 24 | 25 | static int render_stairs_cmd(int argc, char* argv[]); 26 | static int render_grid_cmd(int argc, char* argv[]); 27 | 28 | void register_tests_commands(void) { 29 | // setup args 30 | render_stairs_args.slope = arg_intn( 31 | NULL, NULL, "", 0, 1, "angle by which each diagonal line is drawn. default value: 3" 32 | ); 33 | render_stairs_args.width = arg_intn( 34 | NULL, NULL, "", 0, 1, "thickness of each diagonal line. default value: 100" 35 | ); 36 | render_stairs_args.color = arg_intn(NULL, NULL, "", 0, 1, "default value: 0x00"); 37 | render_stairs_args.end = arg_end(NARGS(render_stairs_args)); 38 | 39 | render_grid_args.gutter 40 | = arg_intn(NULL, NULL, "", 0, 1, "default value: 75"); // gcd(1200, 825) = 75 41 | render_grid_args.color = arg_intn(NULL, NULL, "", 0, 1, "default value: 0x00"); 42 | render_grid_args.end = arg_end(NARGS(render_grid_args)); 43 | 44 | // register commands 45 | const esp_console_cmd_t commands[] = { 46 | { .command = "render_stairs", 47 | .help = "Render multiple diagonal lines across the screen.", 48 | .hint = NULL, 49 | .func = &render_stairs_cmd, 50 | .argtable = &render_stairs_args }, 51 | { .command = "render_grid", 52 | .help 53 | = "Renders a grid across the whole screen. At a certain gutter size, cell info will " 54 | "be printed as well.", 55 | .hint = NULL, 56 | .func = &render_grid_cmd, 57 | .argtable = &render_grid_args }, 58 | }; 59 | 60 | for (size_t i = 0; i < ARRAY_SIZE(commands); ++i) 61 | ESP_ERROR_CHECK(esp_console_cmd_register(&commands[i])); 62 | } 63 | 64 | static void render_stairs(int slope, int width, uint8_t color) { 65 | for (int y = 0, x = 0; y < epd_rotated_display_height(); y++) { 66 | epd_draw_hline(x, y, width, color, g_framebuffer); 67 | x += slope; 68 | if (x + width > epd_rotated_display_width()) 69 | x = 0; 70 | } 71 | } 72 | 73 | static int render_stairs_cmd(int argc, char* argv[]) { 74 | HANDLE_ARGUMENTS(render_stairs_args) 75 | 76 | uint8_t color = 0x00; 77 | if (!validate_color(&color, render_stairs_args.color)) 78 | return 1; 79 | 80 | const int slope = GET_INT_ARG(render_stairs_args.slope, 3); 81 | const int width = GET_INT_ARG(render_stairs_args.width, 100); 82 | 83 | if (slope < 1 || slope > width) { 84 | printf("Slope %d is too steep: Must be between 1 and width (%d)\r\n", slope, width); 85 | return 1; 86 | } 87 | 88 | render_stairs(slope, width, color); 89 | update_screen(); 90 | 91 | return 0; 92 | } 93 | 94 | void render_grid(int gutter, uint8_t color) { 95 | const int width = epd_rotated_display_width(); 96 | const int height = epd_rotated_display_height(); 97 | 98 | // draw lines 99 | for (int row = gutter; row < height; row += gutter) 100 | epd_draw_hline(0, row, width, color, g_framebuffer); 101 | 102 | for (int col = gutter; col < width; col += gutter) 103 | epd_draw_vline(col, 0, height, color, g_framebuffer); 104 | 105 | // skip printing info if it wouldn't fit 106 | if (gutter < Alexandria.advance_y * 2) 107 | return; 108 | 109 | // prepare info 110 | static char label[32]; 111 | int col = 0, row; 112 | 113 | for (int y = 0; y < height; y += gutter, ++col) { 114 | row = 0; 115 | for (int x = 0; x < width; x += gutter, ++row) { 116 | // print info 117 | snprintf(label, sizeof(label), "(%d,%d)", row, col); 118 | int rx = y + Alexandria.advance_y; 119 | int cx = x + 4; // margin 120 | epd_write_default(&Alexandria, label, &cx, &rx, g_framebuffer); 121 | } 122 | } 123 | } 124 | 125 | static int render_grid_cmd(int argc, char* argv[]) { 126 | HANDLE_ARGUMENTS(render_grid_args) 127 | 128 | uint8_t color = 0x00; 129 | if (!validate_color(&color, render_grid_args.color)) 130 | return 1; 131 | 132 | const int gutter = GET_INT_ARG(render_grid_args.gutter, 75); 133 | 134 | render_grid(gutter, color); 135 | update_screen(); 136 | 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /examples/screen_diag/main/commands/tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void register_tests_commands(void); 4 | -------------------------------------------------------------------------------- /examples/screen_diag/main/epd.c: -------------------------------------------------------------------------------- 1 | #include "epd.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static EpdiyHighlevelState s_state; 8 | uint8_t* g_framebuffer; 9 | static int s_temperature; 10 | 11 | // choose the default demo board depending on the architecture 12 | #ifdef CONFIG_IDF_TARGET_ESP32 13 | #define DEMO_BOARD epd_board_v6 14 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 15 | #define DEMO_BOARD epd_board_v7 16 | #endif 17 | 18 | void initialize_screen(void) { 19 | epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K); 20 | // Set VCOM for boards that allow to set this in software (in mV). 21 | // This will print an error if unsupported. In this case, 22 | // set VCOM using the hardware potentiometer and delete this line. 23 | epd_set_vcom(1560); 24 | 25 | s_state = epd_hl_init(EPD_BUILTIN_WAVEFORM); 26 | g_framebuffer = epd_hl_get_framebuffer(&s_state); 27 | 28 | epd_set_rotation(EPD_ROT_PORTRAIT); 29 | 30 | epd_poweron(); 31 | s_temperature = (int)epd_ambient_temperature(); 32 | epd_poweroff(); 33 | } 34 | 35 | void update_screen(void) { 36 | enum EpdDrawError err; 37 | 38 | epd_poweron(); 39 | err = epd_hl_update_screen(&s_state, EPD_MODE_DEFAULT, s_temperature); 40 | taskYIELD(); 41 | epd_poweroff(); 42 | 43 | if (err != EPD_DRAW_SUCCESS) { 44 | ESP_LOGW("screen_diag", "Could not update screen. Reason: %d", err); 45 | } 46 | } 47 | 48 | void clear_screen(void) { 49 | epd_hl_set_all_white(&s_state); 50 | update_screen(); 51 | } 52 | 53 | void full_clear_screen(void) { 54 | epd_poweron(); 55 | epd_fullclear(&s_state, s_temperature); 56 | epd_poweroff(); 57 | } 58 | -------------------------------------------------------------------------------- /examples/screen_diag/main/epd.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern uint8_t* g_framebuffer; 4 | 5 | void initialize_screen(void); 6 | void update_screen(void); 7 | void clear_screen(void); 8 | void full_clear_screen(void); 9 | -------------------------------------------------------------------------------- /examples/screen_diag/main/res/fonts/fonts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef DEFINE_FONTS 6 | #include "alexandria.h" 7 | #include "amiri.h" 8 | #else 9 | extern const EpdFont Alexandria; 10 | extern const EpdFont Amiri; 11 | #endif 12 | -------------------------------------------------------------------------------- /examples/screen_diag/main/screen_diag.c: -------------------------------------------------------------------------------- 1 | /* Based on the console example app, which is licensed under CC0. 2 | * @link 3 | * https://github.com/espressif/esp-idf/blob/v4.4.3/examples/system/console/basic/main/console_example_main.c 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "commands/graphics.h" 14 | #include "commands/screen.h" 15 | #include "commands/system.h" 16 | #include "commands/tests.h" 17 | #include "epd.h" 18 | 19 | #define DEFINE_FONTS 20 | #include "fonts.h" 21 | 22 | static const char* TAG = "screen_diag"; 23 | 24 | static void initialize_filesystem(void) { 25 | static wl_handle_t wl_handle; 26 | const esp_vfs_fat_mount_config_t mount_config 27 | = { .max_files = 4, .format_if_mount_failed = true }; 28 | 29 | esp_err_t err 30 | = esp_vfs_fat_spiflash_mount("/screen_diag", "storage", &mount_config, &wl_handle); 31 | 32 | if (err != ESP_OK) { 33 | ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); 34 | return; 35 | } 36 | } 37 | 38 | static void initialize_nvs(void) { 39 | esp_err_t err = nvs_flash_init(); 40 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { 41 | ESP_ERROR_CHECK(nvs_flash_erase()); 42 | err = nvs_flash_init(); 43 | } 44 | ESP_ERROR_CHECK(err); 45 | } 46 | 47 | void app_main(void) { 48 | initialize_nvs(); 49 | initialize_filesystem(); 50 | initialize_screen(); 51 | 52 | esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); 53 | repl_config.prompt = "diag>"; 54 | repl_config.history_save_path = "/screen_diag/history.txt"; 55 | 56 | /* Register commands */ 57 | esp_console_register_help_command(); 58 | register_system_commands(); 59 | register_screen_commands(); 60 | register_graphics_commands(); 61 | register_tests_commands(); 62 | 63 | esp_console_repl_t* repl; 64 | esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); 65 | ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl)); 66 | 67 | ESP_ERROR_CHECK(esp_console_start_repl(repl)); 68 | } 69 | -------------------------------------------------------------------------------- /examples/screen_diag/partitions_example.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 1M, 6 | storage, data, fat, , 1M, 7 | -------------------------------------------------------------------------------- /examples/screen_diag/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/screen_diag/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is the project CMakeLists.txt file for the test subproject 2 | cmake_minimum_required(VERSION 3.16) 3 | 4 | # Add newly added components to one of these lines: 5 | # 1. Add here if the component is compatible with IDF >= v4.3 6 | set(EXTRA_COMPONENT_DIRS "../../") 7 | 8 | set(TEST_COMPONENTS "epdiy") 9 | 10 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 11 | project(epdiy_testrunner) 12 | -------------------------------------------------------------------------------- /examples/test/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 2 | 3 | idf_component_register(SRCS "main.c" INCLUDE_DIRS ".") 4 | -------------------------------------------------------------------------------- /examples/test/main/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "unity_test_runner.h" 4 | 5 | static void print_banner(const char* text) { 6 | printf("\n#### %s #####\n\n", text); 7 | } 8 | 9 | void app_main(void) { 10 | print_banner("Running all the registered tests"); 11 | UNITY_BEGIN(); 12 | // unity_run_tests_by_tag("lut", false); 13 | unity_run_all_tests(); 14 | UNITY_END(); 15 | } 16 | -------------------------------------------------------------------------------- /examples/vcom-kickback/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(dragon_example) 5 | -------------------------------------------------------------------------------- /examples/vcom-kickback/README.md: -------------------------------------------------------------------------------- 1 | A demo showing a Full-Screen Image 2 | ================================== 3 | 4 | *The image size is chosen to fit a 1200 * 825 display!* 5 | 6 | Image by David REVOY / CC BY (https://creativecommons.org/licenses/by/3.0) 7 | https://commons.wikimedia.org/wiki/File:Durian_-_Sintel-wallpaper-dragon.jpg 8 | -------------------------------------------------------------------------------- /examples/vcom-kickback/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "main.c") 2 | 3 | idf_component_register(SRCS ${app_sources} REQUIRES epdiy) 4 | -------------------------------------------------------------------------------- /examples/vcom-kickback/main/main.c: -------------------------------------------------------------------------------- 1 | /* Simple firmware for a ESP32 displaying a static image on an EPaper Screen */ 2 | 3 | #include "esp_heap_caps.h" 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "esp_log.h" 7 | #include "dragon.h" 8 | #include "epd_highlevel.h" 9 | #include "epdiy.h" 10 | #include "board/tps65185.h" 11 | 12 | EpdiyHighlevelState hl; 13 | 14 | // choose the default demo board depending on the architecture 15 | #ifdef CONFIG_IDF_TARGET_ESP32 16 | #define DEMO_BOARD epd_board_v6 17 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 18 | #define DEMO_BOARD epd_board_v7 19 | #endif 20 | int temperature = 25; 21 | 22 | void draw_dragon() { 23 | EpdRect dragon_area = { .x = 0, .y = 0, .width = dragon_width, .height = dragon_height }; 24 | epd_poweron(); 25 | epd_fullclear(&hl, temperature); 26 | epd_copy_to_framebuffer(dragon_area, dragon_data, epd_hl_get_framebuffer(&hl)); 27 | enum EpdDrawError _err = epd_hl_update_screen(&hl, MODE_GC16, temperature); 28 | epd_poweroff(); 29 | } 30 | 31 | void idf_loop() { 32 | // make a full black | white print to force epdiy to send the update 33 | epd_fill_rect(epd_full_screen(), 0, epd_hl_get_framebuffer(&hl)); 34 | epd_hl_update_screen(&hl, MODE_DU, temperature); 35 | vTaskDelay(pdMS_TO_TICKS(1)); 36 | epd_fill_rect(epd_full_screen(), 255, epd_hl_get_framebuffer(&hl)); 37 | epd_hl_update_screen(&hl, MODE_DU, temperature); 38 | } 39 | 40 | void idf_setup() { 41 | epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K); 42 | hl = epd_hl_init(&epdiy_NULL); 43 | // starts the board in kickback more 44 | tps_vcom_kickback(); 45 | 46 | // display starts to pass BLACK to WHITE but doing nothing+ 47 | // dince the NULL waveform is full of 0 "Do nothing for each pixel" 48 | idf_loop(); 49 | // start measure and set ACQ bit: 50 | tps_vcom_kickback_start(); 51 | int isrdy = 1; 52 | int kickback_volt = 0; 53 | while (kickback_volt == 0) { 54 | idf_loop(); 55 | isrdy++; 56 | kickback_volt = tps_vcom_kickback_rdy(); 57 | } 58 | ESP_LOGI("vcom", "readings are of %d mV. It was ready in %d refreshes", kickback_volt, isrdy); 59 | 60 | vTaskDelay(pdMS_TO_TICKS(2000)); 61 | esp_restart(); 62 | } 63 | 64 | #ifndef ARDUINO_ARCH_ESP32 65 | void app_main() { 66 | idf_setup(); 67 | } 68 | #endif 69 | -------------------------------------------------------------------------------- /examples/vcom-kickback/main/main.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the Arduino wrapper for the "Demo" example. 3 | * Please go to the main.c for the main example file. 4 | * 5 | * This example was developed for the ESP IoT Development Framework (IDF). 6 | * You can still use this code in the Arduino IDE, but it may not look 7 | * and feel like a classic Arduino sketch. 8 | * If you are looking for an example with Arduino look-and-feel, 9 | * please check the other examples. 10 | */ 11 | 12 | // Important: These are C functions, so they must be declared with C linkage! 13 | extern "C" { 14 | void idf_setup(); 15 | void idf_loop(); 16 | } 17 | 18 | void setup() { 19 | if (psramInit()) { 20 | Serial.println("\nThe PSRAM is correctly initialized"); 21 | } else { 22 | Serial.println("\nPSRAM does not work"); 23 | } 24 | 25 | idf_setup(); 26 | } 27 | 28 | void loop() { 29 | idf_loop(); 30 | } 31 | -------------------------------------------------------------------------------- /examples/vcom-kickback/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/vcom-kickback/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/weather/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.0) 2 | set(EXTRA_COMPONENT_DIRS "../../") 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | project(weather) 5 | -------------------------------------------------------------------------------- /examples/weather/Licence.txt: -------------------------------------------------------------------------------- 1 | This software, the ideas and concepts is Copyright (c) David Bird 2014 and beyond. 2 | 3 | All rights to this software are reserved. 4 | 5 | It is prohibited to redistribute or reproduce of any part or all of the software contents in any form other than the following: 6 | 7 | 1. You may print or download to a local hard disk extracts for your personal and non-commercial use only. 8 | 9 | 2. You may copy the content to individual third parties for their personal use, but only if you acknowledge the author David Bird as the source of the material. 10 | 11 | 3. You may not, except with my express written permission, distribute or commercially exploit the content. 12 | 13 | 4. You may not transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes. 14 | 15 | 5. You MUST include all of this copyright and permission notice ('as annotated') and this shall be included in all copies or substantial portions of the software and where the software use is visible to an end-user. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT. 18 | 19 | FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | 21 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/weather/README.md: -------------------------------------------------------------------------------- 1 | Weather display example 2 | ======================================= 3 | 4 | This is **port of project https://github.com/G6EJD/ESP32-e-Paper-Weather-Display by David Bird 2018** to work with ED097OC4 Kindle display and this driver. 5 | **Please note that David Bird (https://github.com/G6EJD) owns the copyright to this software!** Only minor modifications where made for it to run with the epdiy driver. 6 | THe license details are outlined in **License.txt**. 7 | 8 | Building It 9 | ----------- 10 | 11 | - Girst you need to install Arduino esp-idf as a component https://github.com/espressif/arduino-esp32/blob/master/docs/esp-idf_component.md (the easiest way is to put it into components folder of ESP-IDF) 12 | - Put Arduino JSON https://github.com/bblanchon/ArduinoJson into components/arduino/ 13 | - Dont forget to insert your Wi-Fi settings and openweathermap API key into owm_credentials.h 14 | - If you want to add more fonts, firmware may not fit into 1M and easiest way to fix it is to edit components/partition_table/partitions_singleapp.csv and change 1M to 2M 15 | 16 | ![weather image](weather.jpg) 17 | 18 | License 19 | ------- 20 | 21 | This software, the ideas and concepts is Copyright (c) David Bird 2014 and beyond. 22 | 23 | All rights to this software are reserved. 24 | 25 | It is prohibited to redistribute or reproduce of any part or all of the software contents in any form other than the following: 26 | 27 | 1. You may print or download to a local hard disk extracts for your personal and non-commercial use only. 28 | 29 | 2. You may copy the content to individual third parties for their personal use, but only if you acknowledge the author David Bird as the source of the material. 30 | 31 | 3. You may not, except with my express written permission, distribute or commercially exploit the content. 32 | 33 | 4. You may not transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes. 34 | 35 | 5. You MUST include all of this copyright and permission notice ('as annotated') and this shall be included in all copies or substantial portions of the software and where the software use is visible to an end-user. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT. 38 | 39 | FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 40 | 41 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 42 | -------------------------------------------------------------------------------- /examples/weather/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app_sources "weather.cpp") 2 | 3 | idf_component_register(SRCS ${app_sources} 4 | REQUIRES epdiy arduino) 5 | -------------------------------------------------------------------------------- /examples/weather/main/forecast_record.h: -------------------------------------------------------------------------------- 1 | #ifndef FORECAST_RECORD_H_ 2 | #define FORECAST_RECORD_H_ 3 | 4 | #include 5 | 6 | typedef struct { // For current Day and Day 1, 2, 3, etc 7 | String Dt; 8 | String Period; 9 | String Icon; 10 | String Trend; 11 | String Main0; 12 | String Forecast0; 13 | String Forecast1; 14 | String Forecast2; 15 | String Description; 16 | String Time; 17 | String Country; 18 | float lat; 19 | float lon; 20 | float Temperature; 21 | float Humidity; 22 | float High; 23 | float Low; 24 | float Winddir; 25 | float Windspeed; 26 | float Rainfall; 27 | float Snowfall; 28 | float Pressure; 29 | int Cloudcover; 30 | int Visibility; 31 | int Sunrise; 32 | int Sunset; 33 | int Timezone; 34 | } Forecast_record_type; 35 | 36 | #endif /* ifndef FORECAST_RECORD_H_ */ 37 | -------------------------------------------------------------------------------- /examples/weather/main/lang.h: -------------------------------------------------------------------------------- 1 | #define FONT(x) x##_tf 2 | 3 | // Temperature - Humidity - Forecast 4 | const String TXT_FORECAST_VALUES = "3-Day Forecast Values"; 5 | const String TXT_CONDITIONS = "Conditions"; 6 | const String TXT_DAYS = "(Days)"; 7 | const String TXT_TEMPERATURES = "Temperature"; 8 | const String TXT_TEMPERATURE_C = "Temperature (*C)"; 9 | const String TXT_TEMPERATURE_F = "Temperature (*F)"; 10 | const String TXT_HUMIDITY_PERCENT = "Humidity (%)"; 11 | 12 | // Pressure 13 | const String TXT_PRESSURE = "Pressure"; 14 | const String TXT_PRESSURE_HPA = "Pressure (hpa)"; 15 | const String TXT_PRESSURE_IN = "Pressure (in)"; 16 | const String TXT_PRESSURE_STEADY = "Steady"; 17 | const String TXT_PRESSURE_RISING = "Rising"; 18 | const String TXT_PRESSURE_FALLING = "Falling"; 19 | 20 | // RainFall / SnowFall 21 | const String TXT_RAINFALL_MM = "Rainfall (mm)"; 22 | const String TXT_RAINFALL_IN = "Rainfall (in)"; 23 | const String TXT_SNOWFALL_MM = "Snowfall (mm)"; 24 | const String TXT_SNOWFALL_IN = "Snowfall (in)"; 25 | const String TXT_PRECIPITATION_SOON = "Prec."; 26 | 27 | // Sun 28 | const String TXT_SUNRISE = "Sunrise"; 29 | const String TXT_SUNSET = "Sunset"; 30 | 31 | // Moon 32 | const String TXT_MOON_NEW = "New"; 33 | const String TXT_MOON_WAXING_CRESCENT = "Waxing Crescent"; 34 | const String TXT_MOON_FIRST_QUARTER = "First Quarter"; 35 | const String TXT_MOON_WAXING_GIBBOUS = "Waxing Gibbous"; 36 | const String TXT_MOON_FULL = "Full"; 37 | const String TXT_MOON_WANING_GIBBOUS = "Waning Gibbous"; 38 | const String TXT_MOON_THIRD_QUARTER = "Third Quarter"; 39 | const String TXT_MOON_WANING_CRESCENT = "Waning Crescent"; 40 | 41 | // Power / WiFi 42 | const String TXT_POWER = "Power"; 43 | const String TXT_WIFI = "WiFi"; 44 | const char* TXT_UPDATED = "Updated:"; 45 | 46 | // Wind 47 | const String TXT_WIND_SPEED_DIRECTION = "Wind Speed/Direction"; 48 | const String TXT_N = "N"; 49 | const String TXT_NNE = "NNE"; 50 | const String TXT_NE = "NE"; 51 | const String TXT_ENE = "ENE"; 52 | const String TXT_E = "E"; 53 | const String TXT_ESE = "ESE"; 54 | const String TXT_SE = "SE"; 55 | const String TXT_SSE = "SSE"; 56 | const String TXT_S = "S"; 57 | const String TXT_SSW = "SSW"; 58 | const String TXT_SW = "SW"; 59 | const String TXT_WSW = "WSW"; 60 | const String TXT_W = "W"; 61 | const String TXT_WNW = "WNW"; 62 | const String TXT_NW = "NW"; 63 | const String TXT_NNW = "NNW"; 64 | 65 | // Day of the week 66 | const char* weekday_D[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 67 | 68 | // Month 69 | const char* month_M[] 70 | = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 71 | -------------------------------------------------------------------------------- /examples/weather/main/main.ino: -------------------------------------------------------------------------------- 1 | // weather.cpp is the actual main file, 2 | // this is just for Arduino to recognize this as a project. 3 | -------------------------------------------------------------------------------- /examples/weather/main/opensans10.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/weather/main/opensans10.h -------------------------------------------------------------------------------- /examples/weather/main/owm_credentials.h: -------------------------------------------------------------------------------- 1 | const bool DebugDisplayUpdate = false; 2 | 3 | // Change to your WiFi credentials 4 | const char* ssid = ""; 5 | const char* password = ""; 6 | 7 | // Use your own API key by signing up for a free developer account at https://openweathermap.org/ 8 | String apikey = ""; // See: https://openweathermap.org/ 9 | const char server[] = "api.openweathermap.org"; 10 | // http://api.openweathermap.org/data/2.5/forecast?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=40 11 | // http://api.openweathermap.org/data/2.5/weather?q=Melksham,UK&APPID=your_OWM_API_key&mode=json&units=metric&cnt=1 12 | // Set your location according to OWM locations 13 | String City = "KIEV"; // Your home city See: http://bulk.openweathermap.org/sample/ 14 | String Country = "UA"; // Your _ISO-3166-1_two-letter_country_code country code, on OWM find your 15 | // nearest city and the country code is displayed 16 | // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes 17 | String Language 18 | = "EN"; // NOTE: Only the weather description is translated by OWM 19 | // Examples: Arabic (AR) Czech (CZ) English (EN) Greek (EL) Persian(Farsi) (FA) 20 | // Galician (GL) Hungarian (HU) Japanese (JA) Korean (KR) Latvian (LA) Lithuanian (LT) 21 | // Macedonian (MK) Slovak (SK) Slovenian (SL) Vietnamese (VI) 22 | String Hemisphere = "north"; // or "south" 23 | String Units = "M"; // Use 'M' for Metric or I for Imperial 24 | const char* Timezone 25 | = "EET-2EEST,M3.5.0/3,M10.5.0/4"; // Choose your time zone from: 26 | // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv 27 | // See below for examples 28 | const char* ntpServer 29 | = "pool.ntp.org"; // Or, choose a time server close to you, but in most cases it's best to use 30 | // pool.ntp.org to find an NTP server then the NTP system decides e.g. 31 | // 0.pool.ntp.org, 1.pool.ntp.org as the NTP syem tries to find the closest 32 | // available servers EU "0.europe.pool.ntp.org" US 33 | // "0.north-america.pool.ntp.org" See: https://www.ntppool.org/en/ 34 | int gmtOffset_sec = 0; // UK normal time is GMT, so GMT Offset is 0, for US (-5Hrs) is typically 35 | // -18000, AU is typically (+8hrs) 28800 36 | int daylightOffset_sec 37 | = 3600; // In the UK DST is +1hr or 3600-secs, other countries may use 2hrs 7200 or 30-mins 38 | // 1800 or 5.5hrs 19800 Ahead of GMT use + offset behind - offset 39 | 40 | // Example time zones 41 | // const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe 42 | // const char* Timezone = "CET-1CEST,M3.5.0,M10.5.0/3"; // Central Europe 43 | // const char* Timezone = "EST-2METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe 44 | // const char* Timezone = "EST5EDT,M3.2.0,M11.1.0"; // EST USA 45 | // const char* Timezone = "CST6CDT,M3.2.0,M11.1.0"; // CST USA 46 | // const char* Timezone = "MST7MDT,M4.1.0,M10.5.0"; // MST USA 47 | // const char* Timezone = "NZST-12NZDT,M9.5.0,M4.1.0/3"; // Auckland 48 | // const char* Timezone = "EET-2EEST,M3.5.5/0,M10.5.5/0"; // Asia 49 | // const char* Timezone = "ACST-9:30ACDT,M10.1.0,M4.1.0/3": // Australia -------------------------------------------------------------------------------- /examples/weather/partitions.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 3M, 6 | -------------------------------------------------------------------------------- /examples/weather/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_FREERTOS_HZ=1000 2 | -------------------------------------------------------------------------------- /examples/weather/weather.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/weather/weather.jpg -------------------------------------------------------------------------------- /examples/www-image/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | set(EXTRA_COMPONENT_DIRS "../../") 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(www-image_example) 7 | 8 | # This is just to avoid that components/jpegdec includes Arduino.h 9 | idf_build_set_property(COMPILE_OPTIONS "-D __LINUX__" APPEND) -------------------------------------------------------------------------------- /examples/www-image/README.md: -------------------------------------------------------------------------------- 1 | Download and render image example 2 | ================================= 3 | 4 | After discussing the idea of collaborating adding an WiFi download and render example in the epdiy.slack.com 5 | we decided to also add a JPG decoding example suggested by @vroland. 6 | 7 | **jpg-render.c** 8 | Takes aprox. 1.5 to 2 seconds to download a 200Kb jpeg. Additionally another second to decompress and render the image using EPDiy epd_draw_pixel() 9 | 10 | **jpgdec-render.cpp** 11 | This version uses [Bitbank2 jpeg decoder](https://github.com/bitbank2/JPEGDEC) as an external component, please run: **git submodule update --init --recursive** 12 | in order to download it and it will be placed in components/jpegdec. This is a second C++ example that uses JPEGDEC a decoder than is faster and in this particular example we are also adding the possibility of copying each entire row of the JPG to the epaper framebuffer (Using **JPEG_CPY_FRAMEBUFFER** set to true) 13 | 14 | Copying the entire rows to the framebuffer reduces almost completely the rendering time at the cost of loosing software rotation and gamma correction, but that might be not needed if you want to render an image as fast as possible. 15 | 16 | Detailed statistics using jpg-render.c: 17 | 18 | ``` 19 | 48772 bytes read from https://loremflickr.com/960/540 20 | 21 | decode: 757 ms . image decompression 22 | render: 297 ms - jpeg draw 23 | www-dw: 1728 ms - download 24 | total: 2782 ms - total time spent 25 | ``` 26 | 27 | **Note:** Statistics where taken with the 4.7" Lilygo display 960x540 pixels and may be significantly higher using bigger displays. 28 | 29 | JPEGDEC is useful to make a difference when decoding larger images. Here an example decoding a 250 Kb image: 30 | 31 | ``` 32 | 250Kb bytes read from http://img.cale.es/jpg/fasani/603fcbb59bff8 33 | decode: 593 ms - 1024x768 image decode MCUs:48 34 | render: 1 ms - copying pix (memcpy rows) 35 | www-dw: 1134 ms - download 36 | total: 1726 ms 37 | ``` 38 | 39 | Building it 40 | =========== 41 | 42 | Do not forget to update your WiFi credentials and point it to a proper URL that contains the image with the right format: 43 | 44 | ```c 45 | // WiFi configuration 46 | #define ESP_WIFI_SSID "WIFI NAME" 47 | #define ESP_WIFI_PASSWORD "" 48 | // www URL of the JPG image. As default: 49 | #define IMG_URL ("https://loremflickr.com/800/600") 50 | ``` 51 | 52 | Progressive JPG images are not supported. 53 | 54 | Note that as default an random image taken from loremflickr.com is used. You can use any URL that points to a valid Image, take care to use the right JPG format, or you can also use the image-service [cale.es](https://cale.es) to create your own gallery. 55 | 56 | If your epaper resolution is not listed, just send me an email, you can find it on my [profile page on Github](https://github.com/martinberlin). 57 | 58 | Using HTTPS 59 | =========== 60 | 61 | Using SSL requires a bit more effort if you need to verify the certificate. For example, getting the SSL cert from loremflickr.com needs to be extracted using this command: 62 | 63 | openssl s_client -showcerts -connect www.loremflickr.com:443 = 4.3 it needs to have VALIDATE_SSL_CERTIFICATE set to true. 70 | In case you want to allow insecure requests please follow this: 71 | 72 | In menu choose Component config->ESP LTS-> (enable these options) "Allow potentially insecure options" and then "Skip server verification by default" 73 | 74 | Also needs the main Stack to be bigger otherwise the embedTLS validation fails: 75 | Just 1Kb makes it work: 76 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=4584 77 | 78 | You can set this in **idf.py menuconfig** 79 | 80 | -> Component config -> ESP32-specific -> Main task stack size 81 | 82 | Please be aware that in order to validate SSL certificates the ESP32 needs to be aware of the time. Setting the define VALIDATE_SSL_CERTIFICATE to true will make an additional SNTP server ping to do that. That takes between 1 or 2 seconds more. 83 | 84 | Setting VALIDATE_SSL_CERTIFICATE to false also works skipping the .cert_pem in the esp_http_client_config_t struct. 85 | 86 | 87 | Happy to collaborate once again with this amazing project, 88 | 89 | Martin Fasani, Berlin 20 Aug. 2021 90 | -------------------------------------------------------------------------------- /examples/www-image/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | COMPONENT_EMBED_TXTFILES := ${project_dir}/ssl_cert/server_cert.pem -------------------------------------------------------------------------------- /examples/www-image/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set( 2 | app_sources "jpg-render.c" 3 | #app_sources "jpgdec-render.cpp" 4 | ) 5 | 6 | idf_component_register(SRCS ${app_sources} 7 | REQUIRES epdiy esp_wifi 8 | # Add only if you use jpgdec-render.cpp example 9 | jpegdec 10 | nvs_flash esp-tls esp_http_client 11 | # Embed the server root certificate into the final binary 12 | EMBED_TXTFILES ${project_dir}/ssl_cert/server_cert.pem 13 | ) 14 | -------------------------------------------------------------------------------- /examples/www-image/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | ## IDF Component Manager Manifest File 2 | dependencies: 3 | bitbank2/jpegdec: "^1.6.2" 4 | ## Required IDF version 5 | idf: 6 | version: ">=4.1.0" 7 | # # Put list of dependencies here 8 | # # For components maintained by Espressif: 9 | # component: "~1.0.0" 10 | # # For 3rd party components: 11 | # username/component: ">=1.0.0,<2.0.0" 12 | # username2/component2: 13 | # version: "~1.0.0" 14 | # # For transient dependencies `public` flag can be set. 15 | # # `public` flag doesn't have an effect dependencies of the `main` component. 16 | # # All dependencies of `main` are public by default. 17 | # public: true 18 | -------------------------------------------------------------------------------- /examples/www-image/main/settings.h: -------------------------------------------------------------------------------- 1 | // WiFi configuration: 2 | #define ESP_WIFI_SSID "" 3 | #define ESP_WIFI_PASSWORD "" 4 | 5 | // Affects the gamma to calculate gray (lower is darker/higher contrast) 6 | // Nice test values: 0.9 1.2 1.4 higher and is too bright 7 | double gamma_value = 0.7; 8 | 9 | // - - - - Display configuration - - - - - - - - - 10 | // EPD Waveform should match your EPD for good grayscales 11 | #define WAVEFORM EPD_BUILTIN_WAVEFORM 12 | #define DISPLAY_ROTATION EPD_ROT_LANDSCAPE 13 | // - - - - end of Display configuration - - - - - 14 | 15 | // Deepsleep configuration 16 | #define MILLIS_DELAY_BEFORE_SLEEP 2000 17 | #define DEEPSLEEP_MINUTES_AFTER_RENDER 6 18 | 19 | // Image URL and jpg settings. Make sure to update WIDTH/HEIGHT if using loremflickr 20 | // Note: Only HTTP protocol supported (Check README to use SSL secure URLs) loremflickr 21 | #define IMG_URL ("https://loremflickr.com/1024/768") 22 | 23 | // idf >= 4.3 needs VALIDATE_SSL_CERTIFICATE set to true for https URLs 24 | // Please check the README to understand how to use an SSL Certificate 25 | // Note: This makes a sntp time sync query for cert validation (It's slower) 26 | // IMPORTANT: idf updated and now when you use Internet requests you need to server cert 27 | // verification 28 | // heading ESP-TLS in 29 | // https://newreleases.io/project/github/espressif/esp-idf/release/v4.3-beta1 30 | #define VALIDATE_SSL_CERTIFICATE true 31 | // To make an insecure request please check Readme 32 | 33 | // Alternative non-https URL: 34 | // #define IMG_URL "http://img.cale.es/jpg/fasani/5e636b0f39aac" 35 | 36 | // Jpeg: Adds dithering to image rendering (Makes grayscale smoother on transitions) 37 | #define JPG_DITHERING true 38 | 39 | // As default is 512 without setting buffer_size property in esp_http_client_config_t 40 | #define HTTP_RECEIVE_BUFFER_SIZE 1986 41 | 42 | #define DEBUG_VERBOSE true 43 | -------------------------------------------------------------------------------- /examples/www-image/sdkconfig.defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vroland/epdiy/fe3113a02fd62622ef0d31706510bcebf2ac7634/examples/www-image/sdkconfig.defaults -------------------------------------------------------------------------------- /examples/www-image/ssl_cert/server_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFBjCCAu6gAwIBAgIRAIp9PhPWLzDvI4a9KQdrNPgwDQYJKoZIhvcNAQELBQAw 3 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 4 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw 5 | WhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg 6 | RW5jcnlwdDEMMAoGA1UEAxMDUjExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEAuoe8XBsAOcvKCs3UZxD5ATylTqVhyybKUvsVAbe5KPUoHu0nsyQYOWcJ 8 | DAjs4DqwO3cOvfPlOVRBDE6uQdaZdN5R2+97/1i9qLcT9t4x1fJyyXJqC4N0lZxG 9 | AGQUmfOx2SLZzaiSqhwmej/+71gFewiVgdtxD4774zEJuwm+UE1fj5F2PVqdnoPy 10 | 6cRms+EGZkNIGIBloDcYmpuEMpexsr3E+BUAnSeI++JjF5ZsmydnS8TbKF5pwnnw 11 | SVzgJFDhxLyhBax7QG0AtMJBP6dYuC/FXJuluwme8f7rsIU5/agK70XEeOtlKsLP 12 | Xzze41xNG/cLJyuqC0J3U095ah2H2QIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB 13 | hjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB 14 | /wIBADAdBgNVHQ4EFgQUxc9GpOr0w8B6bJXELbBeki8m47kwHwYDVR0jBBgwFoAU 15 | ebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC 16 | hhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG 17 | A1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN 18 | AQELBQADggIBAE7iiV0KAxyQOND1H/lxXPjDj7I3iHpvsCUf7b632IYGjukJhM1y 19 | v4Hz/MrPU0jtvfZpQtSlET41yBOykh0FX+ou1Nj4ScOt9ZmWnO8m2OG0JAtIIE38 20 | 01S0qcYhyOE2G/93ZCkXufBL713qzXnQv5C/viOykNpKqUgxdKlEC+Hi9i2DcaR1 21 | e9KUwQUZRhy5j/PEdEglKg3l9dtD4tuTm7kZtB8v32oOjzHTYw+7KdzdZiw/sBtn 22 | UfhBPORNuay4pJxmY/WrhSMdzFO2q3Gu3MUBcdo27goYKjL9CTF8j/Zz55yctUoV 23 | aneCWs/ajUX+HypkBTA+c8LGDLnWO2NKq0YD/pnARkAnYGPfUDoHR9gVSp/qRx+Z 24 | WghiDLZsMwhN1zjtSC0uBWiugF3vTNzYIEFfaPG7Ws3jDrAMMYebQ95JQ+HIBD/R 25 | PBuHRTBpqKlyDnkSHDHYPiNX3adPoPAcgdF3H2/W0rmoswMWgTlLn1Wu0mrks7/q 26 | pdWfS6PJ1jty80r2VKsM/Dj3YIDfbjXKdaFU5C+8bhfJGqU3taKauuz0wHVGT3eo 27 | 6FlWkWYtbt4pgdamlwVeZEW+LM7qZEJEsMNPrfC03APKmZsJgpWCDWOKZvkZcvjV 28 | uYkQ4omYCTX5ohy+knMjdOmdH9c7SpqEWBDC86fiNex+O0XOMEZSa8DA 29 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | version: "2.0.0" 2 | description: "Drive parallel e-Paper displays with epdiy-based boards." 3 | url: "https://github.com/vroland/epdiy" 4 | license: LGPL-3.0-or-later 5 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "epdiy", 3 | "version": "2.0.0", 4 | "description": "Drive parallel e-Paper displays with epdiy-based boards.", 5 | "keywords": "epd, driver, e-ink", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/vroland/epdiy.git" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Valentin Roland", 13 | "email": "github@vroland.de", 14 | "maintainer": true 15 | } 16 | ], 17 | "license": "LGPL-3.0", 18 | "frameworks": [ 19 | "arduino", 20 | "espidf" 21 | ], 22 | "platforms": "espressif32", 23 | "export": { 24 | "exclude": [ 25 | "hardware" 26 | ] 27 | }, 28 | "build": { 29 | "includeDir": "include", 30 | "srcDir": "src/" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=epdiy 2 | version=2.0.0 3 | author=Valentin Roland 4 | maintainer=Valentin Roland 5 | sentence=Drive parallel e-Paper displays with epdiy-based boards. 6 | paragraph=See https://github.com/vroland/epdiy for details. 7 | architectures=esp32,esp32s3 8 | url=https://github.com/vroland/epdiy 9 | category=Display 10 | -------------------------------------------------------------------------------- /scripts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mr. PK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | ## Scripts in this folder are for adding addtional capabilities to epdiy. 2 | 3 | 4 | 5 | ## imgconvert.py 6 | 7 | #### usage: 8 | 9 | python3 imgconvert.py [-h] -i INPUTFILE -n NAME -o OUTPUTFILE [-maxw MAX_WIDTH] 10 | [-maxh MAX_HEIGHT] 11 | 12 | **optional arguments:** 13 | 14 | * **-h, --help** show this help message and exit 15 | 16 | * **-i INPUTFILE** 17 | 18 | * **-n NAME** 19 | 20 | * **-o OUTPUTFILE** 21 | 22 | * **-maxw MAX_WIDTH** 23 | 24 | * **-maxh MAX_HEIGHT** 25 | 26 | 27 | ========================================================== 28 | 29 | ## fontconvert.py 30 | 31 | #### usage: 32 | 33 | python3 fontconvert.py [-h] [--compress] [--additional-intervals ADDITIONAL_INTERVALS] 34 | [--string STRING] 35 | name size fontstack [fontstack ...] 36 | 37 | Generate a header file from a font to be used with epdiy. 38 | 39 | **positional arguments:** 40 | 41 | * **name** name of the font to be used in epdiy. 42 | * **size** font size to use. 43 | * **fontstack** list of font files, ordered by descending priority. This is not actually implemented as yet. Please just use one file for now. 44 | 45 | **optional arguments:** 46 | 47 | * **-h**, --help show this help message and exit 48 | 49 | * **--compress** compress glyph bitmaps. 50 | 51 | * **--additional-intervals** ADDITIONAL_INTERVALS 52 | 53 | Additional code point intervals to export as min,max. This argument 54 | can be repeated. 55 | 56 | * **--string STRING** A quoted string of all required characters. The intervals are will be made from these characters if they exist in the ttf file. Missing characters will warn about their abscence. 57 | 58 | 59 | 60 | ####example: 61 | 1. Download a ttf from where you like to a directory. As in: "~/Downloads/any_old_ttf.ttf" 62 | in the download directory 63 | 64 | 2. Run 65 | 66 | `python3 fontconvert.py my_font 30 ~/Downloads/any_old_ttf.ttf --string '/0123456789:;@ABCDEFGH[\]^_`abcdefgh\{|}~¡¢£¤¥¦§¨©ª' > fonts.h` 67 | 68 | * you will need to use special escapes for characters like ' or " This is system dependant though. 69 | 70 | 3. copy fonts.h into your app folder or where ever your app can find it. 71 | 4. include it into your project with 72 | `#include fonts.h` 73 | Then use it just like any other font file in epdiy. 74 | 75 | **To run this script the freetype module needs to be installed. This can be done with `pip install freetype-py` You will be warned if it is not accessible by the script.** 76 | 77 | ========================================================== 78 | 79 | ##waveform_hdrgen.py 80 | 81 | ####usage: 82 | 83 | waveform_hdrgen.py [-h] [--list-modes] [--temperature-range TEMPERATURE_RANGE] 84 | [--export-modes EXPORT_MODES] 85 | name 86 | 87 | **positional arguments:** 88 | name name of the waveform object. 89 | 90 | **optional arguments:** 91 | 92 | * **-h, --help** show this help message and exit 93 | 94 | * **--list-modes** list the available modes for tis file. 95 | 96 | * **--temperature-range TEMPERATURE_RANGE** 97 | only export waveforms in the temperature range of min,max °C. 98 | 99 | * **--export-modes EXPORT_MODES** 100 | comma-separated list of waveform mode IDs to export. 101 | 102 | -------------------------------------------------------------------------------- /scripts/imgconvert.py: -------------------------------------------------------------------------------- 1 | #!python3 2 | 3 | from PIL import Image, ImageOps 4 | from argparse import ArgumentParser 5 | import sys 6 | import math 7 | 8 | parser = ArgumentParser() 9 | parser.add_argument('-i', action="store", dest="inputfile", required=True) 10 | parser.add_argument('-n', action="store", dest="name", required=True) 11 | parser.add_argument('-o', action="store", dest="outputfile", required=True) 12 | parser.add_argument('-maxw', action="store", dest="max_width", default=1200, type=int) 13 | parser.add_argument('-maxh', action="store", dest="max_height", default=825, type=int) 14 | 15 | args = parser.parse_args() 16 | 17 | im = Image.open(args.inputfile) 18 | # convert to grayscale 19 | im = im.convert(mode='L') 20 | im.thumbnail((args.max_width, args.max_height), Image.LANCZOS) 21 | 22 | # Write out the output file. 23 | with open(args.outputfile, 'w') as f: 24 | f.write("const uint32_t {}_width = {};\n".format(args.name, im.size[0])) 25 | f.write("const uint32_t {}_height = {};\n".format(args.name, im.size[1])) 26 | f.write( 27 | "const uint8_t {}_data[({}*{})/2] = {{\n".format(args.name, math.ceil(im.size[0] / 2) * 2, im.size[1]) 28 | ) 29 | for y in range(0, im.size[1]): 30 | byte = 0 31 | done = True 32 | for x in range(0, im.size[0]): 33 | l = im.getpixel((x, y)) 34 | if x % 2 == 0: 35 | byte = l >> 4 36 | done = False; 37 | else: 38 | byte |= l & 0xF0 39 | f.write("0x{:02X}, ".format(byte)) 40 | done = True 41 | if not done: 42 | f.write("0x{:02X}, ".format(byte)) 43 | f.write("\n\t"); 44 | f.write("};\n") 45 | -------------------------------------------------------------------------------- /scripts/modenames.py: -------------------------------------------------------------------------------- 1 | mode_names = { 2 | 0: "MODE_INIT", 3 | 1: "MODE_DU", 4 | 2: "MODE_GC16", 5 | 3: "MODE_GC16_FAST", 6 | 4: "MODE_A2", 7 | 5: "MODE_GL16", 8 | 6: "MODE_GL16_FAST", 9 | 7: "MODE_DU4", 10 | 10: "MODE_GL4", 11 | 11: "MODE_GL16_INV", 12 | 0x10: "MODE_EPDIY_WHITE_TO_GL16", 13 | 0x11: "MODE_EPDIY_BLACK_TO_GL16", 14 | 0x3F: "MODE_UNKNOWN_WAVEFORM", 15 | } 16 | 17 | def mode_id(name): 18 | for i, n in mode_names.items(): 19 | if name == n: 20 | return i 21 | return 0x3F 22 | -------------------------------------------------------------------------------- /scripts/waveform_hdrgen.py: -------------------------------------------------------------------------------- 1 | #!env python3 2 | 3 | import json 4 | import sys 5 | import argparse 6 | from modenames import mode_names 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("--list-modes", help="list the available modes for tis file.", action = "store_true"); 10 | parser.add_argument("--temperature-range", help="only export waveforms in the temperature range of min,max °C."); 11 | parser.add_argument("--export-modes", help="comma-separated list of waveform mode IDs to export."); 12 | parser.add_argument("name", help="name of the waveform object."); 13 | 14 | args = parser.parse_args() 15 | 16 | waveforms = json.load(sys.stdin); 17 | 18 | 19 | total_size = 0 20 | 21 | def phase_to_c(phase,bits_per_pixel_c=4): 22 | 23 | N1 = len(phase) 24 | N2 = len(phase[0]) 25 | N = 2**bits_per_pixel_c 26 | 27 | if N1%N != 0: 28 | raise ValueError(f"first dimension of phases is {N1}. Allowed are multiples of {N}") 29 | 30 | step1 = int(N1/N) 31 | 32 | if N2%N != 0: 33 | raise ValueError(f"second dimension of phases is {N2}. Allowed are multiples of {N}") 34 | 35 | step2 = int(N2/N) 36 | 37 | targets = [] 38 | for t in range(0, N1, step1): 39 | chunk = 0 40 | line = [] 41 | i = 0 42 | for f in range(0, N2, step2): 43 | fr = phase[t][f] 44 | chunk = (chunk << 2) | fr 45 | i += 1 46 | if i == 4: 47 | i = 0 48 | line.append(chunk) 49 | chunk = 0 50 | targets.append(line) 51 | 52 | return targets 53 | 54 | def list_to_c(l): 55 | if isinstance(l, list): 56 | children = [list_to_c(c) for c in l] 57 | return "{" + ",".join(children) + "}" 58 | elif isinstance(l, int): 59 | return f"0x{l:02x}" 60 | else: 61 | assert(false) 62 | 63 | if args.list_modes: 64 | for mode in waveforms["modes"]: 65 | print(f"""{mode["mode"]}: {mode_names[mode["mode"]]}""" ) 66 | sys.exit(0) 67 | 68 | tmin = -100 69 | tmax = 1000 70 | 71 | if args.temperature_range: 72 | tmin, tmax = map(int, args.temperature_range.split(",")) 73 | 74 | modes = [] 75 | 76 | mode_filter = [wm["mode"] for wm in waveforms["modes"]] 77 | 78 | if args.export_modes: 79 | mode_filter = list(map(int, args.export_modes.split(","))) 80 | 81 | mode_filter = [m for m in mode_filter if any([wm["mode"] == m for wm in waveforms["modes"]])] 82 | 83 | num_modes = len(mode_filter) 84 | 85 | temp_intervals = [] 86 | for bounds in waveforms["temperature_ranges"]["range_bounds"]: 87 | if bounds["to"] < tmin or bounds["from"] > tmax: 88 | continue 89 | temp_intervals.append(f"{{ .min = {bounds['from']}, .max = {bounds['to']} }}") 90 | 91 | modes = [] 92 | num_ranges = -1 93 | for m_index, mode in enumerate(waveforms["modes"]): 94 | 95 | if not mode["mode"] in mode_filter: 96 | continue 97 | 98 | ranges = [] 99 | for i, r in enumerate(mode["ranges"]): 100 | bounds = waveforms["temperature_ranges"]["range_bounds"][i] 101 | if bounds["to"] < tmin or bounds["from"] > tmax: 102 | continue 103 | 104 | phases = [] 105 | phase_count = len(r["phases"]) 106 | prev_phase = None 107 | for phase in r["phases"]: 108 | phases.append(phase_to_c(phase)) 109 | 110 | name = f"epd_wp_{args.name}_{mode['mode']}_{r['index']}" 111 | 112 | phase_times= None 113 | if r.get("phase_times"): 114 | phase_times = [str(int(t * 10)) for t in r["phase_times"]] 115 | print(f"const int {name}_times[{len(phase_times)}] = {{ {','.join(phase_times) } }};") 116 | 117 | 118 | phase_times_str = f"&{name}_times[0]" if phase_times else "NULL" 119 | print(f"const uint8_t {name}_data[{phase_count}][16][4] = {list_to_c(phases)};") 120 | print(f"const EpdWaveformPhases {name} = {{ .phases = {phase_count}, .phase_times = {phase_times_str}, .luts = (const uint8_t*)&{name}_data[0] }};") 121 | ranges.append(name) 122 | 123 | assert(num_ranges < 0 or num_ranges == len(ranges)) 124 | 125 | num_ranges = len(ranges) 126 | name = f"epd_wm_{args.name}_{mode['mode']}" 127 | range_pointers = ','.join(['&' + n for n in ranges]) 128 | print(f"const EpdWaveformPhases* {name}_ranges[{len(ranges)}] = {{ {range_pointers} }};") 129 | print(f"const EpdWaveformMode {name} = {{ .type = {mode['mode']}, .temp_ranges = {len(ranges)}, .range_data = &{name}_ranges[0] }};"); 130 | modes.append(name) 131 | 132 | mode_pointers = ','.join(['&' + n for n in modes]) 133 | range_data = ",".join(temp_intervals) 134 | 135 | print(f"const EpdWaveformTempInterval {args.name}_intervals[{len(temp_intervals)}] = {{ {range_data} }};"); 136 | print(f"const EpdWaveformMode* {args.name}_modes[{num_modes}] = {{ {mode_pointers} }};"); 137 | print(f"const EpdWaveform {args.name} = {{ .num_modes = {num_modes}, .num_temp_ranges = {num_ranges}, .mode_data = &{args.name}_modes[0], .temp_intervals = &{args.name}_intervals[0] }};"); 138 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for generating waveform header data 2 | 3 | #### WAVEFORM HEADERS TO GENERATE: ######## 4 | all: include/epdiy_ED097TC2.h include/eink_ED097TC2.h include/eink_ED047TC2.h 5 | 6 | # Generate 16 grascale update waveforms 7 | EXPORT_EINK_MODES ?= 1,2,5,16,17 8 | # Generate waveforms in room temperature range 9 | EXPORT_TEMPERATURE_RANGE ?= 15,35 10 | 11 | HRDGEN ?= ../../scripts/waveform_hdrgen.py 12 | WAVEFORM_GEN ?= ../../scripts/epdiy_waveform_gen.py 13 | 14 | include/eink_%.h: waveforms/eink_%.json 15 | python3 $(HRDGEN) --export-modes $(EXPORT_EINK_MODES) --temperature-range $(EXPORT_TEMPERATURE_RANGE) $* < $< > $@ 16 | 17 | include/epdiy_%.h: waveforms/epdiy_%.json 18 | python3 $(HRDGEN) --export-modes $(EXPORT_EINK_MODES) --temperature-range $(EXPORT_TEMPERATURE_RANGE) $* < $< > $@ 19 | 20 | waveforms/epdiy_%.json: 21 | python3 $(WAVEFORM_GEN) $* > $@ 22 | 23 | .PHONY: all 24 | -------------------------------------------------------------------------------- /src/board/epd_board.c: -------------------------------------------------------------------------------- 1 | #include "epd_board.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "epdiy.h" 7 | 8 | /** 9 | * The board's display control pin state. 10 | */ 11 | static epd_ctrl_state_t ctrl_state; 12 | 13 | /** 14 | * The EPDIY board in use. 15 | */ 16 | const EpdBoardDefinition* epd_board = NULL; 17 | 18 | void IRAM_ATTR epd_busy_delay(uint32_t cycles) { 19 | volatile unsigned long counts = XTHAL_GET_CCOUNT() + cycles; 20 | while (XTHAL_GET_CCOUNT() < counts) { 21 | }; 22 | } 23 | 24 | void epd_set_board(const EpdBoardDefinition* board_definition) { 25 | if (epd_board == NULL) { 26 | epd_board = board_definition; 27 | } else { 28 | ESP_LOGW("epdiy", "EPD board can only be set once!"); 29 | } 30 | } 31 | 32 | const EpdBoardDefinition* epd_current_board() { 33 | return epd_board; 34 | } 35 | 36 | void epd_set_mode(bool state) { 37 | ctrl_state.ep_output_enable = state; 38 | ctrl_state.ep_mode = state; 39 | epd_ctrl_state_t mask = { 40 | .ep_output_enable = true, 41 | .ep_mode = true, 42 | }; 43 | epd_board->set_ctrl(&ctrl_state, &mask); 44 | } 45 | 46 | epd_ctrl_state_t* epd_ctrl_state() { 47 | return &ctrl_state; 48 | } 49 | 50 | void epd_control_reg_init() { 51 | ctrl_state.ep_latch_enable = false; 52 | ctrl_state.ep_output_enable = false; 53 | ctrl_state.ep_sth = true; 54 | ctrl_state.ep_mode = false; 55 | ctrl_state.ep_stv = true; 56 | epd_ctrl_state_t mask = { 57 | .ep_latch_enable = true, 58 | .ep_output_enable = true, 59 | .ep_sth = true, 60 | .ep_mode = true, 61 | .ep_stv = true, 62 | }; 63 | 64 | epd_board->set_ctrl(&ctrl_state, &mask); 65 | } 66 | 67 | void epd_control_reg_deinit() { 68 | ctrl_state.ep_output_enable = false; 69 | ctrl_state.ep_mode = false; 70 | ctrl_state.ep_stv = false; 71 | epd_ctrl_state_t mask = { 72 | .ep_output_enable = true, 73 | .ep_mode = true, 74 | .ep_stv = true, 75 | }; 76 | epd_board->set_ctrl(&ctrl_state, &mask); 77 | } 78 | -------------------------------------------------------------------------------- /src/board/epd_board_common.c: -------------------------------------------------------------------------------- 1 | #include "driver/adc.h" 2 | #include "epd_board.h" 3 | #include "esp_adc_cal.h" 4 | #include "esp_log.h" 5 | 6 | static const adc1_channel_t channel = ADC1_CHANNEL_7; 7 | static esp_adc_cal_characteristics_t adc_chars; 8 | 9 | #define NUMBER_OF_SAMPLES 100 10 | 11 | void epd_board_temperature_init_v2() { 12 | esp_adc_cal_value_t val_type 13 | = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, &adc_chars); 14 | if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { 15 | ESP_LOGI("epd_temperature", "Characterized using Two Point Value\n"); 16 | } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { 17 | ESP_LOGI("esp_temperature", "Characterized using eFuse Vref\n"); 18 | } else { 19 | ESP_LOGI("esp_temperature", "Characterized using Default Vref\n"); 20 | } 21 | adc1_config_width(ADC_WIDTH_BIT_12); 22 | adc1_config_channel_atten(channel, ADC_ATTEN_DB_6); 23 | } 24 | 25 | float epd_board_ambient_temperature_v2() { 26 | uint32_t value = 0; 27 | for (int i = 0; i < NUMBER_OF_SAMPLES; i++) { 28 | value += adc1_get_raw(channel); 29 | } 30 | value /= NUMBER_OF_SAMPLES; 31 | // voltage in mV 32 | float voltage = esp_adc_cal_raw_to_voltage(value, &adc_chars); 33 | return (voltage - 500.0) / 10.0; 34 | } 35 | -------------------------------------------------------------------------------- /src/board/epd_board_common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file "epd_board_common.h" 3 | * @brief Common board functions shared between boards. 4 | */ 5 | #pragma once 6 | 7 | void epd_board_temperature_init_v2(); 8 | float epd_board_ambient_temperature_v2(); 9 | -------------------------------------------------------------------------------- /src/board/epd_board_v2_v3.c: -------------------------------------------------------------------------------- 1 | #include "epd_board.h" 2 | 3 | #include 4 | #include "../output_i2s/i2s_data_bus.h" 5 | #include "../output_i2s/render_i2s.h" 6 | #include "../output_i2s/rmt_pulse.h" 7 | #include "epd_board_common.h" 8 | 9 | // Make this compile on the S3 to avoid long ifdefs 10 | #ifndef CONFIG_IDF_TARGET_ESP32 11 | #define GPIO_NUM_22 0 12 | #define GPIO_NUM_23 0 13 | #define GPIO_NUM_24 0 14 | #define GPIO_NUM_25 0 15 | #endif 16 | 17 | #define CFG_DATA GPIO_NUM_23 18 | #define CFG_CLK GPIO_NUM_18 19 | #define CFG_STR GPIO_NUM_19 20 | #define D7 GPIO_NUM_22 21 | #define D6 GPIO_NUM_21 22 | #define D5 GPIO_NUM_27 23 | #define D4 GPIO_NUM_2 24 | #define D3 GPIO_NUM_0 25 | #define D2 GPIO_NUM_4 26 | #define D1 GPIO_NUM_32 27 | #define D0 GPIO_NUM_33 28 | 29 | /* Control Lines */ 30 | #define CKV GPIO_NUM_25 31 | #define STH GPIO_NUM_26 32 | 33 | /* Edges */ 34 | #define CKH GPIO_NUM_5 35 | 36 | typedef struct { 37 | bool power_disable : 1; 38 | bool pos_power_enable : 1; 39 | bool neg_power_enable : 1; 40 | bool ep_scan_direction : 1; 41 | } epd_config_register_t; 42 | 43 | static i2s_bus_config i2s_config = { 44 | .clock = CKH, 45 | .start_pulse = STH, 46 | .data_0 = D0, 47 | .data_1 = D1, 48 | .data_2 = D2, 49 | .data_3 = D3, 50 | .data_4 = D4, 51 | .data_5 = D5, 52 | .data_6 = D6, 53 | .data_7 = D7, 54 | }; 55 | 56 | static void IRAM_ATTR push_cfg_bit(bool bit) { 57 | gpio_set_level(CFG_CLK, 0); 58 | gpio_set_level(CFG_DATA, bit); 59 | gpio_set_level(CFG_CLK, 1); 60 | } 61 | 62 | static epd_config_register_t config_reg; 63 | 64 | static void epd_board_init(uint32_t epd_row_width) { 65 | /* Power Control Output/Off */ 66 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_DATA], PIN_FUNC_GPIO); 67 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_CLK], PIN_FUNC_GPIO); 68 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_STR], PIN_FUNC_GPIO); 69 | gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT); 70 | gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT); 71 | gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT); 72 | fast_gpio_set_lo(CFG_STR); 73 | 74 | config_reg.power_disable = true; 75 | config_reg.pos_power_enable = false; 76 | config_reg.neg_power_enable = false; 77 | config_reg.ep_scan_direction = true; 78 | 79 | // Setup I2S 80 | // add an offset off dummy bytes to allow for enough timing headroom 81 | i2s_bus_init(&i2s_config, epd_row_width + 32); 82 | 83 | rmt_pulse_init(CKV); 84 | 85 | epd_board_temperature_init_v2(); 86 | } 87 | 88 | static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) { 89 | if (state->ep_sth) { 90 | fast_gpio_set_hi(STH); 91 | } else { 92 | fast_gpio_set_lo(STH); 93 | } 94 | 95 | if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv || mask->ep_latch_enable) { 96 | fast_gpio_set_lo(CFG_STR); 97 | 98 | // push config bits in reverse order 99 | push_cfg_bit(state->ep_output_enable); 100 | push_cfg_bit(state->ep_mode); 101 | push_cfg_bit(config_reg.ep_scan_direction); 102 | push_cfg_bit(state->ep_stv); 103 | 104 | push_cfg_bit(config_reg.neg_power_enable); 105 | push_cfg_bit(config_reg.pos_power_enable); 106 | push_cfg_bit(config_reg.power_disable); 107 | push_cfg_bit(state->ep_latch_enable); 108 | 109 | fast_gpio_set_hi(CFG_STR); 110 | } 111 | } 112 | 113 | static void epd_board_poweron(epd_ctrl_state_t* state) { 114 | // POWERON 115 | i2s_gpio_attach(&i2s_config); 116 | 117 | epd_ctrl_state_t mask = { 118 | // Trigger output to shift register 119 | .ep_stv = true, 120 | }; 121 | config_reg.power_disable = false; 122 | epd_board_set_ctrl(state, &mask); 123 | epd_busy_delay(100 * 240); 124 | config_reg.neg_power_enable = true; 125 | epd_board_set_ctrl(state, &mask); 126 | epd_busy_delay(500 * 240); 127 | config_reg.pos_power_enable = true; 128 | epd_board_set_ctrl(state, &mask); 129 | epd_busy_delay(100 * 240); 130 | state->ep_stv = true; 131 | state->ep_sth = true; 132 | mask.ep_sth = true; 133 | epd_board_set_ctrl(state, &mask); 134 | // END POWERON 135 | } 136 | 137 | static void epd_board_poweroff(epd_ctrl_state_t* state) { 138 | // POWEROFF 139 | epd_ctrl_state_t mask = { 140 | // Trigger output to shift register 141 | .ep_stv = true, 142 | }; 143 | config_reg.pos_power_enable = false; 144 | epd_board_set_ctrl(state, &mask); 145 | epd_busy_delay(10 * 240); 146 | 147 | config_reg.neg_power_enable = false; 148 | config_reg.pos_power_enable = false; 149 | epd_board_set_ctrl(state, &mask); 150 | epd_busy_delay(100 * 240); 151 | 152 | state->ep_stv = false; 153 | state->ep_output_enable = false; 154 | mask.ep_output_enable = true; 155 | state->ep_mode = false; 156 | mask.ep_mode = true; 157 | config_reg.power_disable = true; 158 | epd_board_set_ctrl(state, &mask); 159 | 160 | i2s_gpio_detach(&i2s_config); 161 | // END POWEROFF 162 | } 163 | 164 | const EpdBoardDefinition epd_board_v2_v3 = { 165 | .init = epd_board_init, 166 | .deinit = NULL, 167 | .set_ctrl = epd_board_set_ctrl, 168 | .poweron = epd_board_poweron, 169 | .poweroff = epd_board_poweroff, 170 | .get_temperature = epd_board_ambient_temperature_v2, 171 | .set_vcom = NULL, 172 | }; 173 | -------------------------------------------------------------------------------- /src/board/pca9555.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "pca9555.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define REG_INPUT_PORT0 0 10 | #define REG_INPUT_PORT1 1 11 | 12 | #define REG_OUTPUT_PORT0 2 13 | #define REG_OUTPUT_PORT1 3 14 | 15 | #define REG_INVERT_PORT0 4 16 | #define REG_INVERT_PORT1 5 17 | 18 | #define REG_CONFIG_PORT0 6 19 | #define REG_CONFIG_PORT1 7 20 | 21 | static esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t* data_rd, size_t size, int reg) { 22 | if (size == 0) { 23 | return ESP_OK; 24 | } 25 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 26 | if (cmd == NULL) { 27 | ESP_LOGE("epdiy", "insufficient memory for I2C transaction"); 28 | } 29 | i2c_master_start(cmd); 30 | i2c_master_write_byte(cmd, (EPDIY_PCA9555_ADDR << 1) | I2C_MASTER_WRITE, true); 31 | i2c_master_write_byte(cmd, reg, true); 32 | i2c_master_stop(cmd); 33 | 34 | esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); 35 | if (ret != ESP_OK) { 36 | return ret; 37 | } 38 | i2c_cmd_link_delete(cmd); 39 | 40 | cmd = i2c_cmd_link_create(); 41 | if (cmd == NULL) { 42 | ESP_LOGE("epdiy", "insufficient memory for I2C transaction"); 43 | } 44 | i2c_master_start(cmd); 45 | i2c_master_write_byte(cmd, (EPDIY_PCA9555_ADDR << 1) | I2C_MASTER_READ, true); 46 | if (size > 1) { 47 | i2c_master_read(cmd, data_rd, size - 1, I2C_MASTER_ACK); 48 | } 49 | i2c_master_read_byte(cmd, data_rd + size - 1, I2C_MASTER_NACK); 50 | i2c_master_stop(cmd); 51 | 52 | ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); 53 | if (ret != ESP_OK) { 54 | return ret; 55 | } 56 | i2c_cmd_link_delete(cmd); 57 | 58 | return ESP_OK; 59 | } 60 | 61 | static esp_err_t i2c_master_write_slave( 62 | i2c_port_t i2c_num, uint8_t ctrl, uint8_t* data_wr, size_t size 63 | ) { 64 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 65 | if (cmd == NULL) { 66 | ESP_LOGE("epdiy", "insufficient memory for I2C transaction"); 67 | } 68 | i2c_master_start(cmd); 69 | i2c_master_write_byte(cmd, (EPDIY_PCA9555_ADDR << 1) | I2C_MASTER_WRITE, true); 70 | i2c_master_write_byte(cmd, ctrl, true); 71 | 72 | i2c_master_write(cmd, data_wr, size, true); 73 | i2c_master_stop(cmd); 74 | esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); 75 | i2c_cmd_link_delete(cmd); 76 | return ret; 77 | } 78 | 79 | static esp_err_t pca9555_write_single(i2c_port_t port, int reg, uint8_t value) { 80 | uint8_t w_data[1] = { value }; 81 | return i2c_master_write_slave(port, reg, w_data, sizeof(w_data)); 82 | } 83 | 84 | esp_err_t pca9555_set_config(i2c_port_t port, uint8_t config_value, int high_port) { 85 | return pca9555_write_single(port, REG_CONFIG_PORT0 + high_port, config_value); 86 | } 87 | 88 | esp_err_t pca9555_set_inversion(i2c_port_t port, uint8_t config_value, int high_port) { 89 | return pca9555_write_single(port, REG_INVERT_PORT0 + high_port, config_value); 90 | } 91 | 92 | esp_err_t pca9555_set_value(i2c_port_t port, uint8_t config_value, int high_port) { 93 | return pca9555_write_single(port, REG_OUTPUT_PORT0 + high_port, config_value); 94 | } 95 | 96 | uint8_t pca9555_read_input(i2c_port_t i2c_port, int high_port) { 97 | esp_err_t err; 98 | uint8_t r_data[1]; 99 | 100 | err = i2c_master_read_slave(i2c_port, r_data, 1, REG_INPUT_PORT0 + high_port); 101 | if (err != ESP_OK) { 102 | ESP_LOGE("PCA9555", "%s failed", __func__); 103 | return 0; 104 | } 105 | 106 | return r_data[0]; 107 | } 108 | -------------------------------------------------------------------------------- /src/board/pca9555.h: -------------------------------------------------------------------------------- 1 | #ifndef PCA9555_H 2 | #define PCA9555_H 3 | 4 | #include 5 | #include 6 | 7 | #define PCA_PIN_P00 0x0001 8 | #define PCA_PIN_P01 0x0002 9 | #define PCA_PIN_P02 0x0004 10 | #define PCA_PIN_P03 0x0008 11 | #define PCA_PIN_P04 0x0010 12 | #define PCA_PIN_P05 0x0020 13 | #define PCA_PIN_P06 0x0040 14 | #define PCA_PIN_P07 0x0080 15 | #define PCA_PIN_PC10 0x0100 16 | #define PCA_PIN_PC11 0x0200 17 | #define PCA_PIN_PC12 0x0400 18 | #define PCA_PIN_PC13 0x0800 19 | #define PCA_PIN_PC14 0x1000 20 | #define PCA_PIN_PC15 0x2000 21 | #define PCA_PIN_PC16 0x4000 22 | #define PCA_PIN_PC17 0x8000 23 | #define PCA_PIN_P_ALL 0x00FF 24 | #define PCA_PIN_PC_ALL 0xFF00 25 | #define PCA_PIN_ALL 0xFFFF 26 | #define PCA_PIN_NULL 0x0000 27 | 28 | static const int EPDIY_PCA9555_ADDR = 0x20; 29 | 30 | uint8_t pca9555_read_input(i2c_port_t port, int high_port); 31 | esp_err_t pca9555_set_value(i2c_port_t port, uint8_t config_value, int high_port); 32 | esp_err_t pca9555_set_inversion(i2c_port_t port, uint8_t config_value, int high_port); 33 | esp_err_t pca9555_set_config(i2c_port_t port, uint8_t config_value, int high_port); 34 | 35 | #endif // PCA9555_H 36 | -------------------------------------------------------------------------------- /src/board/tps65185.c: -------------------------------------------------------------------------------- 1 | 2 | #include "tps65185.h" 3 | #include "pca9555.h" 4 | #include "epd_board.h" 5 | #include "esp_err.h" 6 | #include "esp_log.h" 7 | 8 | #include 9 | #include 10 | 11 | static const int EPDIY_TPS_ADDR = 0x68; 12 | 13 | static uint8_t i2c_master_read_slave(i2c_port_t i2c_num, int reg) { 14 | uint8_t r_data[1]; 15 | 16 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 17 | i2c_master_start(cmd); 18 | i2c_master_write_byte(cmd, (EPDIY_TPS_ADDR << 1) | I2C_MASTER_WRITE, true); 19 | i2c_master_write_byte(cmd, reg, true); 20 | i2c_master_stop(cmd); 21 | 22 | ESP_ERROR_CHECK(i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS)); 23 | i2c_cmd_link_delete(cmd); 24 | 25 | cmd = i2c_cmd_link_create(); 26 | i2c_master_start(cmd); 27 | i2c_master_write_byte(cmd, (EPDIY_TPS_ADDR << 1) | I2C_MASTER_READ, true); 28 | /* 29 | if (size > 1) { 30 | i2c_master_read(cmd, data_rd, size - 1, I2C_MASTER_ACK); 31 | } 32 | */ 33 | i2c_master_read_byte(cmd, r_data, I2C_MASTER_NACK); 34 | i2c_master_stop(cmd); 35 | 36 | ESP_ERROR_CHECK(i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS)); 37 | i2c_cmd_link_delete(cmd); 38 | 39 | return r_data[0]; 40 | } 41 | 42 | static esp_err_t i2c_master_write_slave( 43 | i2c_port_t i2c_num, uint8_t ctrl, uint8_t* data_wr, size_t size 44 | ) { 45 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 46 | i2c_master_start(cmd); 47 | i2c_master_write_byte(cmd, (EPDIY_TPS_ADDR << 1) | I2C_MASTER_WRITE, true); 48 | i2c_master_write_byte(cmd, ctrl, true); 49 | 50 | i2c_master_write(cmd, data_wr, size, true); 51 | i2c_master_stop(cmd); 52 | esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS); 53 | i2c_cmd_link_delete(cmd); 54 | return ret; 55 | } 56 | 57 | esp_err_t tps_write_register(i2c_port_t port, int reg, uint8_t value) { 58 | uint8_t w_data[1]; 59 | esp_err_t err; 60 | 61 | w_data[0] = value; 62 | 63 | err = i2c_master_write_slave(port, reg, w_data, 1); 64 | return err; 65 | } 66 | 67 | uint8_t tps_read_register(i2c_port_t i2c_num, int reg) { 68 | return i2c_master_read_slave(i2c_num, reg); 69 | } 70 | 71 | void tps_set_vcom(i2c_port_t i2c_num, unsigned vcom_mV) { 72 | unsigned val = vcom_mV / 10; 73 | ESP_ERROR_CHECK(tps_write_register(i2c_num, 4, (val & 0x100) >> 8)); 74 | ESP_ERROR_CHECK(tps_write_register(i2c_num, 3, val & 0xFF)); 75 | } 76 | 77 | int8_t tps_read_thermistor(i2c_port_t i2c_num) { 78 | tps_write_register(i2c_num, TPS_REG_TMST1, 0x80); 79 | int tries = 0; 80 | while (true) { 81 | uint8_t val = tps_read_register(i2c_num, TPS_REG_TMST1); 82 | // temperature conversion done 83 | if (val & 0x20) { 84 | break; 85 | } 86 | tries++; 87 | 88 | if (tries >= 100) { 89 | ESP_LOGE("epdiy", "thermistor read timeout!"); 90 | break; 91 | } 92 | } 93 | return (int8_t)tps_read_register(i2c_num, TPS_REG_TMST_VALUE); 94 | } 95 | 96 | void tps_vcom_kickback() { 97 | printf("VCOM Kickback test\n"); 98 | // pull the WAKEUP pin and the PWRUP pin high to enable all output rails. 99 | epd_current_board()->measure_vcom(epd_ctrl_state()); 100 | // set the HiZ bit in the VCOM2 register (BIT 5) 0x20 101 | // this puts the VCOM pin in a high-impedance state. 102 | // bit 3 & 4 Number of acquisitions that is averaged to a single kick-back V. measurement 103 | tps_write_register(I2C_NUM_0, 4, 0x38); 104 | vTaskDelay(1); 105 | 106 | uint8_t int1reg = tps_read_register(I2C_NUM_0, TPS_REG_INT1); 107 | uint8_t vcomreg = tps_read_register(I2C_NUM_0, TPS_REG_VCOM2); 108 | } 109 | 110 | void tps_vcom_kickback_start() { 111 | uint8_t int1reg = tps_read_register(I2C_NUM_0, TPS_REG_INT1); 112 | // set the ACQ bit in the VCOM2 register to 1 (BIT 7) 113 | tps_write_register(I2C_NUM_0, TPS_REG_VCOM2, 0xA0); 114 | } 115 | 116 | unsigned tps_vcom_kickback_rdy() { 117 | uint8_t int1reg = tps_read_register(I2C_NUM_0, TPS_REG_INT1); 118 | 119 | if (int1reg == 0x02) { 120 | uint8_t lsb = tps_read_register(I2C_NUM_0, 3); 121 | uint8_t msb = tps_read_register(I2C_NUM_0, 4); 122 | int u16Value = (lsb | (msb << 8)) & 0x1ff; 123 | ESP_LOGI("vcom", "raw value:%d temperature:%d C", u16Value, tps_read_thermistor(I2C_NUM_0)); 124 | return u16Value * 10; 125 | } else { 126 | return 0; 127 | } 128 | } -------------------------------------------------------------------------------- /src/board/tps65185.h: -------------------------------------------------------------------------------- 1 | #ifndef TPS65185_H 2 | #define TPS65185_H 3 | 4 | #include 5 | 6 | #define TPS_REG_TMST_VALUE 0x00 7 | #define TPS_REG_ENABLE 0x01 8 | #define TPS_REG_VADJ 0x02 9 | #define TPS_REG_VCOM1 0x03 10 | #define TPS_REG_VCOM2 0x04 11 | #define TPS_REG_INT_EN1 0x05 12 | #define TPS_REG_INT_EN2 0x06 13 | #define TPS_REG_INT1 0x07 14 | #define TPS_REG_INT2 0x08 15 | #define TPS_REG_UPSEQ0 0x09 16 | #define TPS_REG_UPSEQ1 0x0A 17 | #define TPS_REG_DWNSEQ0 0x0B 18 | #define TPS_REG_DWNSEQ1 0x0C 19 | #define TPS_REG_TMST1 0x0D 20 | #define TPS_REG_TMST2 0x0E 21 | #define TPS_REG_PG 0x0F 22 | #define TPS_REG_REVID 0x10 23 | 24 | esp_err_t tps_write_register(i2c_port_t port, int reg, uint8_t value); 25 | uint8_t tps_read_register(i2c_port_t i2c_num, int reg); 26 | 27 | /** 28 | * Sets the VCOM voltage in positive milivolts: 1600 -> -1.6V 29 | */ 30 | void tps_set_vcom(i2c_port_t i2c_num, unsigned vcom_mV); 31 | 32 | /** 33 | * @brief Please read datasheet section 8.3.7.1 Kick-Back Voltage Measurement 34 | * 1 Device enters ACTIVE mode 35 | * 2 All power rails are up except VCOM 36 | * VCOM pin is in HiZ state 37 | */ 38 | void tps_vcom_kickback(); 39 | 40 | /** 41 | * @brief start VCOM kick-back voltage measurements 42 | */ 43 | void tps_vcom_kickback_start(); 44 | 45 | /** 46 | * VCOM kick-back ACQC (Acquisition Complete) bit in the INT1 register is set 47 | * @return unsigned: 0 is not read 48 | */ 49 | unsigned tps_vcom_kickback_rdy(); 50 | 51 | /** 52 | * Read the temperature via the on-board thermistor. 53 | */ 54 | int8_t tps_read_thermistor(i2c_port_t i2c_num); 55 | 56 | #endif // TPS65185_H 57 | -------------------------------------------------------------------------------- /src/board_specific.c: -------------------------------------------------------------------------------- 1 | #include "epd_board_specific.h" 2 | 3 | void epd_powerdown() { 4 | epd_powerdown_lilygo_t5_47(); 5 | } 6 | -------------------------------------------------------------------------------- /src/builtin_waveforms.c: -------------------------------------------------------------------------------- 1 | #include "epdiy.h" 2 | 3 | #include "waveforms/epdiy_ED047TC1.h" 4 | 5 | // Note: Alternative Waveform added by Lilygo on Oct 2021, size: 266 Kb (ED047TC1 is 37 Kb, 7 times 6 | // smaller) 7 | #include "waveforms/epdiy_ED047TC2.h" 8 | 9 | #include "waveforms/epdiy_ED060SC4.h" 10 | #include "waveforms/epdiy_ED060SCT.h" 11 | #include "waveforms/epdiy_ED060XC3.h" 12 | #include "waveforms/epdiy_ED097OC4.h" 13 | #include "waveforms/epdiy_ED097TC2.h" 14 | #include "waveforms/epdiy_ED133UT2.h" 15 | #include "waveforms/epdiy_NULL.h" -------------------------------------------------------------------------------- /src/diff.S: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sdkconfig.h" 4 | 5 | #ifdef CONFIG_IDF_TARGET_ESP32S3 6 | 7 | .text 8 | .align 4 9 | .global epd_interlace_4bpp_line_VE 10 | .type epd_interlace_4bpp_line_VE,@function 11 | 12 | // // CRASH AND BURN for debugging 13 | // EE.MOVI.32.A q3, a2, 0 14 | // EE.MOVI.32.A q3, a3, 1 15 | // EE.MOVI.32.A q3, a4, 2 16 | // EE.MOVI.32.A q3, a5, 3 17 | // l8ui a10, a10, 0 18 | 19 | // bool interlace_line( 20 | // const uint8_t *to, 21 | // const uint8_t *from, 22 | // uint8_t *col_dirtyness; 23 | // uint8_t *interlaced, 24 | // int fb_width 25 | // ) 26 | epd_interlace_4bpp_line_VE: 27 | // to - a2 28 | // from - a3 29 | // interlaced - a4 30 | // col_dirtyness - a5 31 | // fb_width - a6 32 | 33 | entry a1, 32 34 | 35 | // divide by 32 for loop count 36 | srli a11, a6, 5 37 | 38 | movi.n a10, 0xF0F0F0F0; 39 | EE.MOVI.32.Q q6,a10,0 40 | EE.MOVI.32.Q q6,a10,1 41 | EE.MOVI.32.Q q6,a10,2 42 | EE.MOVI.32.Q q6,a10,3 43 | 44 | movi.n a10, 0x0F0F0F0F 45 | EE.MOVI.32.Q q7,a10,0 46 | EE.MOVI.32.Q q7,a10,1 47 | EE.MOVI.32.Q q7,a10,2 48 | EE.MOVI.32.Q q7,a10,3 49 | 50 | // put 4 into shift amount 51 | movi.n a10, 4 52 | WSR.SAR a10 53 | 54 | // "dirtyness" register 55 | EE.ZERO.Q q5 56 | 57 | // Instructions sometimes are in an unexpected order 58 | // for best pipeline utilization 59 | loopnez a11, .loop_end_difference 60 | 61 | EE.VLD.128.IP q0, a2, 16 62 | EE.VLD.128.IP q1, a3, 16 63 | 64 | // load column dirtyness 65 | EE.VLD.128.IP q3, a5, 0 66 | 67 | // update dirtyness 68 | EE.XORQ q4, q1, q0 69 | 70 | // line dirtyness accumulator 71 | EE.ORQ q5, q5, q4 72 | // column dirtyness 73 | EE.ORQ q3, q3, q4 74 | 75 | // store column dirtyness 76 | EE.VST.128.IP q3, a5, 16 77 | 78 | // mask out every second value 79 | EE.ANDQ q2, q0, q7 80 | EE.ANDQ q0, q0, q6 81 | EE.ANDQ q3, q1, q7 82 | EE.ANDQ q1, q1, q6 83 | 84 | // shift vectors to align 85 | EE.VSL.32 q2, q2 86 | EE.VSR.32 q1, q1 87 | 88 | // the right shift sign-extends, 89 | // so we make sure the resulting shift is logical by masking again 90 | EE.ANDQ q1, q1, q7 91 | 92 | // Combine "from" and "to" nibble 93 | EE.ORQ q2, q2, q3 94 | EE.ORQ q0, q0, q1 95 | 96 | // Zip masked out values together 97 | EE.VZIP.8 q2, q0 98 | 99 | // store interlaced buffer data 100 | EE.VST.128.IP q2, a4, 16 101 | EE.VST.128.IP q0, a4, 16 102 | 103 | .loop_end_difference: 104 | 105 | EE.MOVI.32.A q5, a2, 0 106 | EE.MOVI.32.A q5, a3, 1 107 | EE.MOVI.32.A q5, a4, 2 108 | EE.MOVI.32.A q5, a5, 3 109 | or a2, a2, a3 110 | or a2, a2, a4 111 | or a2, a2, a5 112 | 113 | //movi.n a2, 1 // return "true" 114 | 115 | // CRASH AND BURN for debugging 116 | //EE.MOVI.32.A q5, a2, 0 117 | //EE.MOVI.32.A q5, a3, 1 118 | //EE.MOVI.32.A q5, a4, 2 119 | //EE.MOVI.32.A q5, a5, 3 120 | //movi.n a10, 0 121 | //l8ui a10, a10, 0 122 | 123 | retw.n 124 | 125 | 126 | .global epd_apply_line_mask_VE 127 | .type epd_apply_line_mask_VE,@function 128 | 129 | // void epd_apply_line_mask_VE( 130 | // uint8_t *line, 131 | // const uint8_t *mask, 132 | // int mask_len 133 | // ) 134 | epd_apply_line_mask_VE: 135 | // line - a2 136 | // mask - a3 137 | // mask_len - a4 138 | 139 | entry a1, 32 140 | 141 | // divide by 16 for loop count 142 | srli a4, a4, 4 143 | 144 | // Instructions sometimes are in an unexpected order 145 | // for best pipeline utilization 146 | loopnez a4, .loop_end_mask 147 | 148 | EE.VLD.128.IP q0, a2, 0 149 | EE.VLD.128.IP q1, a3, 16 150 | 151 | EE.ANDQ q0, q0, q1 152 | 153 | EE.VST.128.IP q0, a2, 16 154 | 155 | .loop_end_mask: 156 | 157 | retw.n 158 | 159 | #endif -------------------------------------------------------------------------------- /src/displays.c: -------------------------------------------------------------------------------- 1 | #include "epd_display.h" 2 | 3 | const EpdDisplay_t ED060SCT = { 4 | .width = 800, 5 | .height = 600, 6 | .bus_width = 8, 7 | .bus_speed = 20, 8 | .default_waveform = &epdiy_ED060SCT, 9 | .display_type = DISPLAY_TYPE_GENERIC, 10 | }; 11 | 12 | const EpdDisplay_t ED060XC3 = { 13 | .width = 1024, 14 | .height = 768, 15 | .bus_width = 8, 16 | .bus_speed = 20, 17 | .default_waveform = &epdiy_ED060XC3, 18 | .display_type = DISPLAY_TYPE_GENERIC, 19 | }; 20 | 21 | const EpdDisplay_t ED097OC4 = { 22 | .width = 1200, 23 | .height = 825, 24 | .bus_width = 8, 25 | .bus_speed = 15, 26 | .default_waveform = &epdiy_ED097OC4, 27 | .display_type = DISPLAY_TYPE_GENERIC, 28 | }; 29 | 30 | const EpdDisplay_t ED097TC2 = { 31 | .width = 1200, 32 | .height = 825, 33 | .bus_width = 8, 34 | .bus_speed = 22, 35 | .default_waveform = &epdiy_ED097TC2, 36 | .display_type = DISPLAY_TYPE_ED097TC2, 37 | }; 38 | 39 | const EpdDisplay_t ED133UT2 = { 40 | .width = 1600, 41 | .height = 1200, 42 | .bus_width = 8, 43 | .bus_speed = 20, 44 | .default_waveform = &epdiy_ED097TC2, 45 | .display_type = DISPLAY_TYPE_ED097TC2, 46 | }; 47 | 48 | const EpdDisplay_t ED047TC1 = { 49 | .width = 960, 50 | .height = 540, 51 | .bus_width = 8, 52 | .bus_speed = 20, 53 | .default_waveform = &epdiy_ED047TC1, 54 | .display_type = DISPLAY_TYPE_GENERIC, 55 | }; 56 | 57 | const EpdDisplay_t ED047TC2 = { 58 | .width = 960, 59 | .height = 540, 60 | .bus_width = 8, 61 | .bus_speed = 20, 62 | .default_waveform = &epdiy_ED047TC2, 63 | .display_type = DISPLAY_TYPE_GENERIC, 64 | }; 65 | 66 | const EpdDisplay_t ED078KC1 = { 67 | .width = 1872, 68 | .height = 1404, 69 | .bus_width = 16, 70 | .bus_speed = 11, 71 | .default_waveform = &epdiy_ED047TC2, 72 | .display_type = DISPLAY_TYPE_GENERIC, 73 | }; 74 | 75 | // Attention is by default horizontal rows mirrored 76 | const EpdDisplay_t ED052TC4 = { 77 | .width = 1280, 78 | .height = 720, 79 | .bus_width = 8, 80 | .bus_speed = 22, 81 | .default_waveform = &epdiy_ED097TC2, 82 | .display_type = DISPLAY_TYPE_ED097TC2, 83 | }; -------------------------------------------------------------------------------- /src/epd_board.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file "epd_board.h" 3 | * @brief Board-definitions provided by epdiy. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | /** 15 | * State of display control pins. 16 | */ 17 | typedef struct { 18 | bool ep_latch_enable : 1; 19 | bool ep_output_enable : 1; 20 | bool ep_sth : 1; 21 | bool ep_mode : 1; 22 | bool ep_stv : 1; 23 | } epd_ctrl_state_t; 24 | 25 | /** 26 | * Operations available on an epdiy board. 27 | */ 28 | typedef struct { 29 | /** 30 | * Initialize the board. 31 | */ 32 | void (*init)(uint32_t epd_row_width); 33 | /** 34 | * Clean up resources and peripherals used by the board. 35 | */ 36 | void (*deinit)(void); 37 | /** 38 | * Set display line state 39 | */ 40 | void (*set_ctrl)(epd_ctrl_state_t*, const epd_ctrl_state_t* const); 41 | /** 42 | * Enable power to the display. 43 | */ 44 | void (*poweron)(epd_ctrl_state_t*); 45 | 46 | /** 47 | * Measure VCOM kick-back. Only in v6 & v7 boards! 48 | */ 49 | void (*measure_vcom)(epd_ctrl_state_t* state); 50 | 51 | /** 52 | * Disable power to the display. 53 | */ 54 | void (*poweroff)(epd_ctrl_state_t*); 55 | 56 | /** 57 | * Set the display common voltage if supported. 58 | * 59 | * Voltage is set as absolute value in millivolts. 60 | * Although VCOM is negative, this function takes a positive (absolute) value. 61 | */ 62 | void (*set_vcom)(int); 63 | 64 | /** 65 | * Get the current temperature if supported by the board. 66 | */ 67 | float (*get_temperature)(void); 68 | 69 | /** 70 | * Set GPIO direction of the broken-out GPIO extender port, 71 | * if available. 72 | * Setting `make_input` to `1` corresponds to input, `0` corresponds to output. 73 | */ 74 | esp_err_t (*gpio_set_direction)(int pin, bool make_input); 75 | 76 | /** 77 | * Get the input level of a GPIO extender pin, if available. 78 | */ 79 | bool (*gpio_read)(int pin); 80 | 81 | /** 82 | * Set the output level of a GPIO extender, if available. 83 | */ 84 | esp_err_t (*gpio_write)(int pin, bool value); 85 | } EpdBoardDefinition; 86 | 87 | /** 88 | * Get the current board. 89 | */ 90 | const EpdBoardDefinition* epd_current_board(); 91 | 92 | /** 93 | * Set the board hardware definition. This must be called before epd_init() 94 | * 95 | * The implementation of this method is in board/epd_board.c. 96 | **/ 97 | void epd_set_board(const EpdBoardDefinition* board); 98 | 99 | /** 100 | * Get the board's current control register state. 101 | */ 102 | epd_ctrl_state_t* epd_ctrl_state(); 103 | 104 | /** 105 | * Set the display mode pin. 106 | */ 107 | void epd_set_mode(bool state); 108 | 109 | /** 110 | * Initialize the control register 111 | */ 112 | void epd_control_reg_init(); 113 | 114 | /** 115 | * Put the control register into the state of lowest power consumption. 116 | */ 117 | void epd_control_reg_deinit(); 118 | 119 | // Built in board definitions 120 | extern const EpdBoardDefinition epd_board_lilygo_t5_47; 121 | extern const EpdBoardDefinition epd_board_lilygo_t5_47_touch; 122 | extern const EpdBoardDefinition lilygo_board_s3; 123 | extern const EpdBoardDefinition epd_board_v2_v3; 124 | extern const EpdBoardDefinition epd_board_v4; 125 | extern const EpdBoardDefinition epd_board_v5; 126 | extern const EpdBoardDefinition epd_board_v6; 127 | extern const EpdBoardDefinition epd_board_v7; 128 | 129 | /** 130 | * Helper for short, precise delays. 131 | */ 132 | void epd_busy_delay(uint32_t cycles); 133 | -------------------------------------------------------------------------------- /src/epd_board_specific.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file "epd_board_specific.h" 3 | * @brief Board-specific functions that are only conditionally defined. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | /** This is a Lilygo47 specific function 11 | 12 | This is a work around a hardware issue with the Lilygo47 epd_poweroff() turns off the epaper 13 | completely however the hardware of the Lilygo47 is different than the official boards. Which means 14 | that on the Lilygo47 this disables power to the touchscreen. 15 | 16 | This is a workaround to allow to disable display power but not the touch screen. 17 | On the Lilygo the epd power flag was re-purposed as power enable 18 | for everything. This is a hardware thing. 19 | \warning This workaround may still leave power on to epd and as such may cause other problems such 20 | as grey screen. 21 | 22 | Please use epd_poweroff() and epd_deinit() whenever you sleep the system. 23 | The following code can be used to sleep the lilygo and power down the peripherals and wake the 24 | unit on touch. However is should be noted that the touch controller is not powered and as such the 25 | touch coordinates will not be captured. Arduino specific code: \code{.c} epd_poweroff(); 26 | epd_deinit(); 27 | esp_sleep_enable_ext1_wakeup(GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH); 28 | esp_deep_sleep_start(); 29 | \endcode 30 | */ 31 | void epd_powerdown_lilygo_t5_47(); 32 | void epd_powerdown() __attribute__((deprecated)); 33 | -------------------------------------------------------------------------------- /src/epd_display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "epd_internals.h" 5 | 6 | /** 7 | * Display type as "compatibility classes", 8 | * Grouping displays by workarounds needed. 9 | */ 10 | enum EpdDisplayType { 11 | /// A generic EPD, assume default config. 12 | DISPLAY_TYPE_GENERIC, 13 | /// Fast display where we can get away with low hold times. 14 | DISPLAY_TYPE_ED097TC2, 15 | }; 16 | 17 | typedef struct { 18 | /// Width of the display in pixels. 19 | int width; 20 | /// Height of the display in pixels. 21 | int height; 22 | 23 | /// Width of the data bus in bits. 24 | uint8_t bus_width; 25 | /// Speed of the data bus in MHz, if configurable. 26 | /// (Only used by the LCD based renderer in V7+) 27 | int bus_speed; 28 | 29 | /// Default waveform to use. 30 | const EpdWaveform* default_waveform; 31 | /// Display type 32 | enum EpdDisplayType display_type; 33 | } EpdDisplay_t; 34 | 35 | extern const EpdDisplay_t ED060SCT; 36 | extern const EpdDisplay_t ED060XC3; 37 | extern const EpdDisplay_t ED097OC4; 38 | extern const EpdDisplay_t ED097TC2; 39 | extern const EpdDisplay_t ED133UT2; 40 | extern const EpdDisplay_t ED047TC1; 41 | extern const EpdDisplay_t ED047TC2; 42 | extern const EpdDisplay_t ED078KC1; 43 | extern const EpdDisplay_t ED052TC4; -------------------------------------------------------------------------------- /src/epd_internals.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file epd_internals.h 3 | * @brief Internal definitions and auxiliary data types. 4 | * 5 | * Unless you want to extend the library itself (Which you are very welcome to do), 6 | * you will most likely not need to know about this file. 7 | */ 8 | 9 | #ifndef EPD_INTERNALS_H 10 | #define EPD_INTERNALS_H 11 | 12 | #include 13 | #include 14 | 15 | /// minimal draw time in ms for a frame layer, 16 | /// which will allow all particles to set properly. 17 | #define MINIMUM_FRAME_TIME 12 18 | 19 | /// Frame draw time for monochrome mode in 1/10 us. 20 | #define MONOCHROME_FRAME_TIME 120 21 | 22 | typedef struct { 23 | int phases; 24 | const uint8_t* luts; 25 | /// If we have timing information for the individual 26 | /// phases, this is an array of the on-times for each phase. 27 | /// Otherwise, this is NULL. 28 | const int* phase_times; 29 | } EpdWaveformPhases; 30 | 31 | typedef struct { 32 | uint8_t type; 33 | uint8_t temp_ranges; 34 | EpdWaveformPhases const** range_data; 35 | } EpdWaveformMode; 36 | 37 | typedef struct { 38 | int min; 39 | int max; 40 | } EpdWaveformTempInterval; 41 | 42 | typedef struct { 43 | uint8_t num_modes; 44 | uint8_t num_temp_ranges; 45 | EpdWaveformMode const** mode_data; 46 | EpdWaveformTempInterval const* temp_intervals; 47 | } EpdWaveform; 48 | 49 | extern const EpdWaveform epdiy_ED060SC4; 50 | extern const EpdWaveform epdiy_ED097OC4; 51 | extern const EpdWaveform epdiy_ED047TC1; 52 | extern const EpdWaveform epdiy_ED047TC2; 53 | extern const EpdWaveform epdiy_ED097TC2; 54 | extern const EpdWaveform epdiy_ED060XC3; 55 | extern const EpdWaveform epdiy_ED060SCT; 56 | extern const EpdWaveform epdiy_ED133UT2; 57 | extern const EpdWaveform epdiy_NULL; 58 | 59 | /// Font data stored PER GLYPH 60 | typedef struct { 61 | uint16_t width; ///< Bitmap dimensions in pixels 62 | uint16_t height; ///< Bitmap dimensions in pixels 63 | uint16_t advance_x; ///< Distance to advance cursor (x axis) 64 | int16_t left; ///< X dist from cursor pos to UL corner 65 | int16_t top; ///< Y dist from cursor pos to UL corner 66 | uint32_t compressed_size; ///< Size of the zlib-compressed font data. 67 | uint32_t data_offset; ///< Pointer into EpdFont->bitmap 68 | } EpdGlyph; 69 | 70 | /// Glyph interval structure 71 | typedef struct { 72 | uint32_t first; ///< The first unicode code point of the interval 73 | uint32_t last; ///< The last unicode code point of the interval 74 | uint32_t offset; ///< Index of the first code point into the glyph array 75 | } EpdUnicodeInterval; 76 | 77 | /// Data stored for FONT AS A WHOLE 78 | typedef struct { 79 | const uint8_t* bitmap; ///< Glyph bitmaps, concatenated 80 | const EpdGlyph* glyph; ///< Glyph array 81 | const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font 82 | uint32_t interval_count; ///< Number of unicode intervals. 83 | bool compressed; ///< Does this font use compressed glyph bitmaps? 84 | uint16_t advance_y; ///< Newline distance (y axis) 85 | int ascender; ///< Maximal height of a glyph above the base line 86 | int descender; ///< Maximal height of a glyph below the base line 87 | } EpdFont; 88 | 89 | #endif // EPD_INTERNALS_H 90 | -------------------------------------------------------------------------------- /src/hacks.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Applies CMAKE_CXX_FLAGS to all targets in the current CMake directory. 3 | # After this operation, CMAKE_CXX_FLAGS is cleared. 4 | # 5 | macro(apply_global_cxx_flags_to_all_targets) 6 | separate_arguments(_global_cxx_flags_list UNIX_COMMAND ${CMAKE_CXX_FLAGS}) 7 | get_property(_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) 8 | foreach(_target ${_targets}) 9 | target_compile_options(${_target} PUBLIC ${_global_cxx_flags_list}) 10 | endforeach() 11 | unset(CMAKE_CXX_FLAGS) 12 | set(_flag_sync_required TRUE) 13 | endmacro() 14 | 15 | # 16 | # Removes the specified compiler flag from the specified file. 17 | # _target - The target that _file belongs to 18 | # _file - The file to remove the compiler flag from 19 | # _flag - The compiler flag to remove. 20 | # 21 | # Pre: apply_global_cxx_flags_to_all_targets() must be invoked. 22 | # 23 | macro(remove_flag_from_file _target _file _flag) 24 | get_target_property(_target_sources ${_target} SOURCES) 25 | # Check if a sync is required, in which case we'll force a rewrite of the cache variables. 26 | if(_flag_sync_required) 27 | unset(_cached_${_target}_cxx_flags CACHE) 28 | unset(_cached_${_target}_${_file}_cxx_flags CACHE) 29 | endif() 30 | get_target_property(_${_target}_cxx_flags ${_target} COMPILE_OPTIONS) 31 | # On first entry, cache the target compile flags and apply them to each source file 32 | # in the target. 33 | if(NOT _cached_${_target}_cxx_flags) 34 | # Obtain and cache the target compiler options, then clear them. 35 | get_target_property(_target_cxx_flags ${_target} COMPILE_OPTIONS) 36 | set(_cached_${_target}_cxx_flags "${_target_cxx_flags}" CACHE INTERNAL "") 37 | set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "") 38 | # Apply the target compile flags to each source file. 39 | foreach(_source_file ${_target_sources}) 40 | # Check for pre-existing flags set by set_source_files_properties(). 41 | get_source_file_property(_source_file_cxx_flags ${_source_file} COMPILE_FLAGS) 42 | if(_source_file_cxx_flags) 43 | separate_arguments(_source_file_cxx_flags UNIX_COMMAND ${_source_file_cxx_flags}) 44 | list(APPEND _source_file_cxx_flags "${_target_cxx_flags}") 45 | else() 46 | set(_source_file_cxx_flags "${_target_cxx_flags}") 47 | endif() 48 | # Apply the compile flags to the current source file. 49 | string(REPLACE ";" " " _source_file_cxx_flags_string "${_source_file_cxx_flags}") 50 | set_source_files_properties(${_source_file} PROPERTIES COMPILE_FLAGS "${_source_file_cxx_flags_string}") 51 | endforeach() 52 | endif() 53 | list(FIND _target_sources ${_file} _file_found_at) 54 | if(_file_found_at GREATER -1) 55 | if(NOT _cached_${_target}_${_file}_cxx_flags) 56 | # Cache the compile flags for the specified file. 57 | # This is the list that we'll be removing flags from. 58 | get_source_file_property(_source_file_cxx_flags ${_file} COMPILE_FLAGS) 59 | separate_arguments(_source_file_cxx_flags UNIX_COMMAND ${_source_file_cxx_flags}) 60 | set(_cached_${_target}_${_file}_cxx_flags ${_source_file_cxx_flags} CACHE INTERNAL "") 61 | endif() 62 | # Remove the specified flag, then re-apply the rest. 63 | list(REMOVE_ITEM _cached_${_target}_${_file}_cxx_flags ${_flag}) 64 | string(REPLACE ";" " " _cached_${_target}_${_file}_cxx_flags_string "${_cached_${_target}_${_file}_cxx_flags}") 65 | set_source_files_properties(${_file} PROPERTIES COMPILE_FLAGS "${_cached_${_target}_${_file}_cxx_flags_string}") 66 | endif() 67 | endmacro() 68 | -------------------------------------------------------------------------------- /src/output_common/line_queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "line_queue.h" 9 | #include "render_method.h" 10 | 11 | static inline int ceil_div(int x, int y) { 12 | return x / y + (x % y != 0); 13 | } 14 | 15 | /// Initialize the line queue and allocate memory. 16 | LineQueue_t lq_init(int queue_len, int element_size) { 17 | LineQueue_t queue; 18 | queue.element_size = element_size; 19 | queue.size = queue_len; 20 | queue.current = 0; 21 | queue.last = 0; 22 | 23 | int elem_buf_size = ceil_div(element_size, 16) * 16; 24 | 25 | queue.bufs = calloc(queue.size, elem_buf_size); 26 | assert(queue.bufs != NULL); 27 | 28 | for (int i = 0; i < queue.size; i++) { 29 | queue.bufs[i] = heap_caps_aligned_alloc(16, elem_buf_size, MALLOC_CAP_INTERNAL); 30 | assert(queue.bufs[i] != NULL); 31 | } 32 | 33 | return queue; 34 | } 35 | 36 | /// Deinitialize the line queue and free memory. 37 | void lq_free(LineQueue_t* queue) { 38 | for (int i = 0; i < queue->size; i++) { 39 | heap_caps_free(queue->bufs[i]); 40 | } 41 | 42 | free(queue->bufs); 43 | } 44 | 45 | uint8_t* IRAM_ATTR lq_current(LineQueue_t* queue) { 46 | int current = atomic_load_explicit(&queue->current, memory_order_acquire); 47 | int last = atomic_load_explicit(&queue->last, memory_order_acquire); 48 | 49 | if ((current + 1) % queue->size == last) { 50 | return NULL; 51 | } 52 | return queue->bufs[current]; 53 | } 54 | 55 | void IRAM_ATTR lq_commit(LineQueue_t* queue) { 56 | int current = atomic_load_explicit(&queue->current, memory_order_acquire); 57 | 58 | if (current == queue->size - 1) { 59 | queue->current = 0; 60 | } else { 61 | atomic_fetch_add(&queue->current, 1); 62 | } 63 | } 64 | 65 | int IRAM_ATTR lq_read(LineQueue_t* queue, uint8_t* dst) { 66 | int current = atomic_load_explicit(&queue->current, memory_order_acquire); 67 | int last = atomic_load_explicit(&queue->last, memory_order_acquire); 68 | 69 | if (current == last) { 70 | return -1; 71 | } 72 | 73 | memcpy(dst, queue->bufs[last], queue->element_size); 74 | 75 | if (last == queue->size - 1) { 76 | queue->last = 0; 77 | } else { 78 | atomic_fetch_add(&queue->last, 1); 79 | } 80 | return 0; 81 | } 82 | 83 | void IRAM_ATTR lq_reset(LineQueue_t* queue) { 84 | queue->current = 0; 85 | queue->last = 0; 86 | } 87 | -------------------------------------------------------------------------------- /src/output_common/line_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /// Circular line queue with atomic read / write operations 9 | /// and accelerated masking on the output buffer. 10 | typedef struct { 11 | int size; 12 | atomic_int current; 13 | atomic_int last; 14 | uint8_t** bufs; 15 | // size of an element 16 | size_t element_size; 17 | } LineQueue_t; 18 | 19 | /// Initialize the line queue and allocate memory. 20 | LineQueue_t lq_init(int queue_len, int element_size); 21 | 22 | /// Deinitialize the line queue and free memory. 23 | void lq_free(LineQueue_t* queue); 24 | 25 | /// Pointer to the next empty element in the line queue. 26 | /// 27 | /// NULL if the queue is currently full. 28 | uint8_t* lq_current(LineQueue_t* queue); 29 | 30 | /// Advance the line queue. 31 | void lq_commit(LineQueue_t* queue); 32 | 33 | /// Read from the line queue. 34 | /// 35 | /// Returns 0 for a successful read to `dst`, -1 for a failed read (empty queue). 36 | int lq_read(LineQueue_t* queue, uint8_t* dst); 37 | 38 | /// Reset the queue into an empty state. 39 | /// This operation is *not* atomic! 40 | void lq_reset(LineQueue_t* queue); 41 | -------------------------------------------------------------------------------- /src/output_common/lut.S: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sdkconfig.h" 4 | 5 | #ifdef CONFIG_IDF_TARGET_ESP32S3 6 | 7 | .text 8 | .align 4 9 | .global calc_epd_input_1ppB_1k_S3_VE_aligned 10 | .type calc_epd_input_1ppB_1k_S3_VE_aligned,@function 11 | 12 | // // CRASH AND BURN for debugging 13 | // EE.MOVI.32.A q3, a2, 0 14 | // EE.MOVI.32.A q3, a3, 1 15 | // EE.MOVI.32.A q3, a4, 2 16 | // EE.MOVI.32.A q3, a5, 3 17 | // l8ui a10, a10, 0 18 | 19 | // void calc_epd_input_1ppB_1k_S3_VE_aligned( 20 | // const uint32_t *ld, 21 | // uint8_t *epd_input, 22 | // const uint8_t *conversion_lut, 23 | // uint32_t epd_width 24 | //); 25 | calc_epd_input_1ppB_1k_S3_VE_aligned: 26 | // input - a2 27 | // output - a3 28 | // lut - a4 29 | // len - a5 30 | 31 | entry a1, 32 32 | 33 | // divide by 16 and do one loop lesss, 34 | // because the last loop is special 35 | srli a5, a5, 4 36 | addi.n a5, a5, -1 37 | 38 | 39 | // bitmasks for bit shift by multiplication 40 | movi.n a10, 0x40001000 41 | EE.MOVI.32.Q q4,a10,0 42 | movi.n a10, 0x04000100 43 | EE.MOVI.32.Q q4,a10,1 44 | movi.n a10, 0x00400010 45 | EE.MOVI.32.Q q4,a10,2 46 | movi a10, 0x00040001 47 | EE.MOVI.32.Q q4,a10,3 48 | 49 | EE.ZERO.Q q0 50 | 51 | EE.VLD.128.IP q1, a2, 16 52 | 53 | // Instructions sometimes are in an unexpected order 54 | // for best pipeline utilization 55 | loopnez a5, .loop_end_lut_lookup 56 | 57 | // q1, q0 contain the input bytes, zero-extended to bits bytes 58 | EE.VZIP.8 q1, q0 59 | 60 | // load 32-bit LUT results 61 | EE.LDXQ.32 q2, q0, a4, 0, 6 62 | EE.LDXQ.32 q2, q0, a4, 1, 7 63 | EE.LDXQ.32 q2, q0, a4, 2, 4 64 | EE.LDXQ.32 q2, q0, a4, 3, 5 65 | EE.LDXQ.32 q3, q0, a4, 0, 2 66 | EE.LDXQ.32 q3, q0, a4, 1, 3 67 | EE.LDXQ.32 q3, q0, a4, 2, 0 68 | EE.LDXQ.32 q3, q0, a4, 3, 1 69 | 70 | EE.ZERO.ACCX 71 | 72 | // zip to have 16bit LUT results in q2, q3 zeroes 73 | EE.VUNZIP.16 q2, q3 74 | 75 | // combine results with using multiply-add as shift-or 76 | EE.VMULAS.U16.ACCX q2,q4 77 | 78 | // load 32-bit LUT results 79 | EE.LDXQ.32 q2, q1, a4, 0, 6 80 | EE.LDXQ.32 q2, q1, a4, 1, 7 81 | EE.LDXQ.32 q2, q1, a4, 2, 4 82 | EE.LDXQ.32 q2, q1, a4, 3, 5 83 | EE.LDXQ.32 q0, q1, a4, 0, 2 84 | EE.LDXQ.32 q0, q1, a4, 1, 3 85 | EE.LDXQ.32 q0, q1, a4, 2, 0 86 | EE.LDXQ.32 q0, q1, a4, 3, 1 87 | 88 | // store multiplication result in a6 89 | RUR.ACCX_0 a6 90 | s16i a6, a3, 2 91 | 92 | EE.ZERO.ACCX 93 | 94 | // zip to have 16bit LUT results in q2, q0 zeroes 95 | EE.VUNZIP.16 q2, q0 96 | 97 | // Combine second set of results and load the next data 98 | EE.VMULAS.U16.ACCX.LD.IP q1, a2, 16, q2, q4 99 | 100 | // store result in a6 101 | RUR.ACCX_0 a6 102 | s16i a6, a3, 0 103 | 104 | addi.n a3, a3, 4 105 | .loop_end_lut_lookup: 106 | 107 | // Same as above, but in the last iteration 108 | // we do not load to not access out of bounds. 109 | EE.VZIP.8 q1, q0 110 | 111 | EE.LDXQ.32 q2, q0, a4, 0, 6 112 | EE.LDXQ.32 q2, q0, a4, 1, 7 113 | EE.LDXQ.32 q2, q0, a4, 2, 4 114 | EE.LDXQ.32 q2, q0, a4, 3, 5 115 | EE.LDXQ.32 q3, q0, a4, 0, 2 116 | EE.LDXQ.32 q3, q0, a4, 1, 3 117 | EE.LDXQ.32 q3, q0, a4, 2, 0 118 | EE.LDXQ.32 q3, q0, a4, 3, 1 119 | 120 | EE.ZERO.ACCX 121 | EE.VUNZIP.16 q2, q3 122 | EE.VMULAS.U16.ACCX q2,q4 123 | 124 | EE.LDXQ.32 q2, q1, a4, 0, 6 125 | EE.LDXQ.32 q2, q1, a4, 1, 7 126 | EE.LDXQ.32 q2, q1, a4, 2, 4 127 | EE.LDXQ.32 q2, q1, a4, 3, 5 128 | EE.LDXQ.32 q0, q1, a4, 0, 2 129 | EE.LDXQ.32 q0, q1, a4, 1, 3 130 | EE.LDXQ.32 q0, q1, a4, 2, 0 131 | EE.LDXQ.32 q0, q1, a4, 3, 1 132 | 133 | RUR.ACCX_0 a6 134 | s16i a6, a3, 2 135 | EE.ZERO.ACCX 136 | 137 | EE.VUNZIP.16 q2, q0 138 | EE.VMULAS.U16.ACCX q2, q4 139 | RUR.ACCX_0 a6 140 | s16i a6, a3, 0 141 | 142 | movi.n a2, 0 // return status ESP_OK 143 | retw.n 144 | 145 | #endif -------------------------------------------------------------------------------- /src/output_common/lut.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "epdiy.h" 5 | 6 | // Make a block of 4 pixels lighter on the EPD. 7 | #define CLEAR_BYTE 0B10101010 8 | // Make a block of 4 pixels darker on the EPD. 9 | #define DARK_BYTE 0B01010101 10 | 11 | /** 12 | * Type signature of a framebuffer to display output lookup function. 13 | */ 14 | typedef void (*lut_func_t)( 15 | const uint32_t* line_buffer, uint8_t* epd_input, const uint8_t* lut, uint32_t epd_width 16 | ); 17 | 18 | /** 19 | * Type signature of a LUT preparation function. 20 | */ 21 | typedef void (*lut_build_func_t)(uint8_t* lut, const EpdWaveformPhases* phases, int frame); 22 | 23 | typedef struct { 24 | lut_build_func_t build_func; 25 | lut_func_t lookup_func; 26 | } LutFunctionPair; 27 | 28 | /** 29 | * Select the appropriate LUT building and lookup function 30 | * for the selected draw mode and allocated LUT size. 31 | */ 32 | LutFunctionPair find_lut_functions(enum EpdDrawMode mode, uint32_t lut_size); 33 | 34 | /* 35 | * Reorder the output buffer to account for I2S FIFO order. 36 | */ 37 | void reorder_line_buffer(uint32_t* line_data, int buf_len); 38 | 39 | /** 40 | * Apply a mask to a line buffer. 41 | * `len` must be divisible by 4. 42 | */ 43 | void epd_apply_line_mask(uint8_t* buf, const uint8_t* mask, int len); 44 | 45 | // legacy functions 46 | void bit_shift_buffer_right(uint8_t* buf, uint32_t len, int shift); 47 | void nibble_shift_buffer_right(uint8_t* buf, uint32_t len); 48 | -------------------------------------------------------------------------------- /src/output_common/render_context.c: -------------------------------------------------------------------------------- 1 | #include "render_context.h" 2 | 3 | #include 4 | #include "esp_log.h" 5 | 6 | #include "../epdiy.h" 7 | #include "lut.h" 8 | #include "render_method.h" 9 | 10 | /// For waveforms without timing and the I2S diving method, 11 | /// the default hold time for each line is 12us 12 | const static int DEFAULT_FRAME_TIME = 120; 13 | 14 | static inline int min(int x, int y) { 15 | return x < y ? x : y; 16 | } 17 | 18 | void get_buffer_params( 19 | RenderContext_t* ctx, 20 | int* bytes_per_line, 21 | const uint8_t** start_ptr, 22 | int* min_y, 23 | int* max_y, 24 | int* pixels_per_byte 25 | ) { 26 | EpdRect area = ctx->area; 27 | 28 | enum EpdDrawMode mode = ctx->mode; 29 | const EpdRect crop_to = ctx->crop_to; 30 | const bool horizontally_cropped = !(crop_to.x == 0 && crop_to.width == area.width); 31 | const bool vertically_cropped = !(crop_to.y == 0 && crop_to.height == area.height); 32 | 33 | // number of pixels per byte of input data 34 | int width_divider = 0; 35 | 36 | if (mode & MODE_PACKING_1PPB_DIFFERENCE) { 37 | *bytes_per_line = area.width; 38 | width_divider = 1; 39 | } else if (mode & MODE_PACKING_2PPB) { 40 | *bytes_per_line = area.width / 2 + area.width % 2; 41 | width_divider = 2; 42 | } else if (mode & MODE_PACKING_8PPB) { 43 | *bytes_per_line = (area.width / 8 + (area.width % 8 > 0)); 44 | width_divider = 8; 45 | } else { 46 | ctx->error |= EPD_DRAW_INVALID_PACKING_MODE; 47 | } 48 | 49 | int crop_x = (horizontally_cropped ? crop_to.x : 0); 50 | int crop_y = (vertically_cropped ? crop_to.y : 0); 51 | int crop_h = (vertically_cropped ? crop_to.height : 0); 52 | 53 | const uint8_t* ptr_start = ctx->data_ptr; 54 | 55 | // Adjust for negative starting coordinates with optional crop 56 | if (area.x - crop_x < 0) { 57 | ptr_start += -(area.x - crop_x) / width_divider; 58 | } 59 | 60 | if (area.y - crop_y < 0) { 61 | ptr_start += -(area.y - crop_y) * *bytes_per_line; 62 | } 63 | 64 | // calculate start and end row with crop 65 | *min_y = area.y + crop_y; 66 | *max_y = min(*min_y + (vertically_cropped ? crop_h : area.height), area.height); 67 | *start_ptr = ptr_start; 68 | *pixels_per_byte = width_divider; 69 | } 70 | 71 | void IRAM_ATTR prepare_context_for_next_frame(RenderContext_t* ctx) { 72 | int frame_time = DEFAULT_FRAME_TIME; 73 | if (ctx->phase_times != NULL) { 74 | frame_time = ctx->phase_times[ctx->current_frame]; 75 | } 76 | 77 | if (ctx->mode & MODE_EPDIY_MONOCHROME) { 78 | frame_time = MONOCHROME_FRAME_TIME; 79 | } 80 | ctx->frame_time = frame_time; 81 | 82 | const EpdWaveformPhases* phases 83 | = ctx->waveform->mode_data[ctx->waveform_index]->range_data[ctx->waveform_range]; 84 | 85 | assert(ctx->lut_build_func != NULL); 86 | ctx->lut_build_func(ctx->conversion_lut, phases, ctx->current_frame); 87 | 88 | ctx->lines_prepared = 0; 89 | ctx->lines_consumed = 0; 90 | } 91 | 92 | void epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len) { 93 | if (dirty_columns == NULL) { 94 | memset(line_mask, 0xFF, mask_len); 95 | } else { 96 | int pixels = mask_len * 4; 97 | for (int c = 0; c < pixels / 2; c += 2) { 98 | uint8_t mask = 0; 99 | mask |= (dirty_columns[c + 1] & 0xF0) != 0 ? 0xC0 : 0x00; 100 | mask |= (dirty_columns[c + 1] & 0x0F) != 0 ? 0x30 : 0x00; 101 | mask |= (dirty_columns[c] & 0xF0) != 0 ? 0x0C : 0x00; 102 | mask |= (dirty_columns[c] & 0x0F) != 0 ? 0x03 : 0x00; 103 | line_mask[c / 2] = mask; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /src/output_common/render_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../epdiy.h" 10 | #include "line_queue.h" 11 | #include "lut.h" 12 | 13 | #define NUM_RENDER_THREADS 2 14 | 15 | typedef struct { 16 | EpdRect area; 17 | EpdRect crop_to; 18 | const bool* drawn_lines; 19 | const uint8_t* data_ptr; 20 | 21 | /// The display width for quick access. 22 | int display_width; 23 | /// The display height for quick access. 24 | int display_height; 25 | 26 | /// index of the next line of data to process 27 | atomic_int lines_prepared; 28 | volatile int lines_consumed; 29 | int lines_total; 30 | 31 | /// frame currently in the current update cycle 32 | int current_frame; 33 | /// number of frames in the current update cycle 34 | int cycle_frames; 35 | 36 | TaskHandle_t feed_tasks[NUM_RENDER_THREADS]; 37 | SemaphoreHandle_t feed_done_smphr[NUM_RENDER_THREADS]; 38 | SemaphoreHandle_t frame_done; 39 | /// Line buffers for feed tasks 40 | uint8_t* feed_line_buffers[NUM_RENDER_THREADS]; 41 | 42 | /// index of the waveform mode when using vendor waveforms. 43 | /// This is not necessarily the mode number if the waveform header 44 | // only contains a selection of modes! 45 | int waveform_index; 46 | /// waveform range when using vendor waveforms 47 | int waveform_range; 48 | /// Draw time for the current frame in 1/10ths of us. 49 | int frame_time; 50 | 51 | const int* phase_times; 52 | 53 | const EpdWaveform* waveform; 54 | enum EpdDrawMode mode; 55 | enum EpdDrawError error; 56 | 57 | // Lookup table size. 58 | size_t conversion_lut_size; 59 | // Lookup table space. 60 | uint8_t* conversion_lut; 61 | 62 | /// LUT lookup function. Must not be NULL. 63 | lut_func_t lut_lookup_func; 64 | /// LUT building function. Must not be NULL 65 | lut_build_func_t lut_build_func; 66 | 67 | /// Queue of lines prepared for output to the display, 68 | /// one for each thread. 69 | LineQueue_t line_queues[NUM_RENDER_THREADS]; 70 | uint8_t* line_threads; 71 | 72 | // Output line mask 73 | uint8_t* line_mask; 74 | 75 | /// track line skipping when working in old i2s mode 76 | int skipping; 77 | 78 | /// line buffer when using epd_push_pixels 79 | uint8_t* static_line_buffer; 80 | } RenderContext_t; 81 | 82 | /** 83 | * Based on the render context, assign the bytes per line, 84 | * framebuffer start pointer, min and max vertical positions and the pixels per byte. 85 | */ 86 | void get_buffer_params( 87 | RenderContext_t* ctx, 88 | int* bytes_per_line, 89 | const uint8_t** start_ptr, 90 | int* min_y, 91 | int* max_y, 92 | int* pixels_per_byte 93 | ); 94 | 95 | /** 96 | * Prepare the render context for drawing the next frame. 97 | * 98 | * (Reset counters, etc) 99 | */ 100 | void prepare_context_for_next_frame(RenderContext_t* ctx); 101 | 102 | /** 103 | * Populate an output line mask from line dirtyness with two bits per pixel. 104 | * If the dirtyness data is NULL, set the mask to neutral. 105 | * 106 | * don't inline for to ensure availability in tests. 107 | */ 108 | void __attribute__((noinline)) epd_populate_line_mask( 109 | uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len 110 | ); -------------------------------------------------------------------------------- /src/output_common/render_method.c: -------------------------------------------------------------------------------- 1 | #include "render_method.h" 2 | #include "sdkconfig.h" 3 | 4 | #ifdef CONFIG_IDF_TARGET_ESP32 5 | const enum EpdRenderMethod EPD_CURRENT_RENDER_METHOD = RENDER_METHOD_I2S; 6 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 7 | const enum EpdRenderMethod EPD_CURRENT_RENDER_METHOD = RENDER_METHOD_LCD; 8 | #else 9 | #error "unknown chip, cannot choose render method!" 10 | #endif -------------------------------------------------------------------------------- /src/output_common/render_method.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sdkconfig.h" 4 | 5 | /** 6 | * Rendering Method / Hardware to use. 7 | */ 8 | enum EpdRenderMethod { 9 | /// Use the I2S peripheral on ESP32 chips. 10 | RENDER_METHOD_I2S = 1, 11 | /// Use the CAM/LCD peripheral in ESP32-S3 chips. 12 | RENDER_METHOD_LCD = 2, 13 | }; 14 | 15 | extern const enum EpdRenderMethod EPD_CURRENT_RENDER_METHOD; 16 | 17 | #ifdef CONFIG_IDF_TARGET_ESP32 18 | #define RENDER_METHOD_I2S 1 19 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 20 | #define RENDER_METHOD_LCD 1 21 | #else 22 | #error "unknown chip, cannot choose render method!" 23 | #endif 24 | 25 | #ifdef __clang__ 26 | #define IRAM_ATTR 27 | // define this if we're using clangd to make it accept the GCC builtin 28 | void __assert_func(const char* file, int line, const char* func, const char* failedexpr); 29 | #endif -------------------------------------------------------------------------------- /src/output_i2s/i2s_data_bus.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Implements a 8bit parallel interface to transmit pixel 3 | * data to the display, based on the I2S peripheral. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /** 16 | * I2S bus configuration parameters. 17 | */ 18 | typedef struct { 19 | // GPIO numbers of the parallel bus pins. 20 | gpio_num_t data_0; 21 | gpio_num_t data_1; 22 | gpio_num_t data_2; 23 | gpio_num_t data_3; 24 | gpio_num_t data_4; 25 | gpio_num_t data_5; 26 | gpio_num_t data_6; 27 | gpio_num_t data_7; 28 | 29 | // Data clock pin. 30 | gpio_num_t clock; 31 | 32 | // "Start Pulse", enabling data input on the slave device (active low) 33 | gpio_num_t start_pulse; 34 | } i2s_bus_config; 35 | 36 | /** 37 | * Initialize the I2S data bus for communication 38 | * with a 8bit parallel display interface. 39 | */ 40 | void i2s_bus_init(i2s_bus_config* cfg, uint32_t epd_row_width); 41 | 42 | /** 43 | * Attach I2S to gpio's 44 | */ 45 | void i2s_gpio_attach(i2s_bus_config* cfg); 46 | 47 | /** 48 | * Detach I2S from gpio's 49 | */ 50 | void i2s_gpio_detach(i2s_bus_config* cfg); 51 | 52 | /** 53 | * Get the currently writable line buffer. 54 | */ 55 | uint8_t* i2s_get_current_buffer(); 56 | 57 | /** 58 | * Switches front and back line buffer. 59 | * If the switched-to line buffer is currently in use, 60 | * this function blocks until transmission is done. 61 | */ 62 | void i2s_switch_buffer(); 63 | 64 | /** 65 | * Start transmission of the current back buffer. 66 | */ 67 | void i2s_start_line_output(); 68 | 69 | /** 70 | * Returns true if there is an ongoing transmission. 71 | */ 72 | bool i2s_is_busy(); 73 | 74 | /** 75 | * Give up allocated resources. 76 | */ 77 | void i2s_bus_deinit(); 78 | -------------------------------------------------------------------------------- /src/output_i2s/render_i2s.h: -------------------------------------------------------------------------------- 1 | #include "epdiy.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../output_common/render_context.h" 10 | #include "sdkconfig.h" 11 | 12 | /** 13 | * Lighten / darken picels using the I2S driving method. 14 | */ 15 | void epd_push_pixels_i2s(RenderContext_t* ctx, EpdRect area, short time, int color); 16 | 17 | /** 18 | * Do a full update cycle with a configured context. 19 | */ 20 | void i2s_do_update(RenderContext_t* ctx); 21 | 22 | /** 23 | * Worker to fetch framebuffer data and write into a queue for processing. 24 | */ 25 | void i2s_fetch_frame_data(RenderContext_t* ctx, int thread_id); 26 | 27 | /** 28 | * Worker to output frame data to the display. 29 | */ 30 | void i2s_output_frame(RenderContext_t* ctx, int thread_id); 31 | 32 | /** 33 | * Deinitialize the I2S peripheral for low power consumption. 34 | */ 35 | void i2s_deinit(); 36 | 37 | /* 38 | * Write bits directly using the registers in the ESP32. 39 | * Won't work for some pins (>= 32). 40 | */ 41 | inline void fast_gpio_set_hi(gpio_num_t gpio_num) { 42 | #ifdef CONFIG_IDF_TARGET_ESP32 43 | gpio_dev_t* device = GPIO_LL_GET_HW(GPIO_PORT_0); 44 | device->out_w1ts = (1 << gpio_num); 45 | #else 46 | // not supportd on non ESP32 chips 47 | assert(false); 48 | #endif 49 | } 50 | 51 | inline void fast_gpio_set_lo(gpio_num_t gpio_num) { 52 | #ifdef CONFIG_IDF_TARGET_ESP32 53 | gpio_dev_t* device = GPIO_LL_GET_HW(GPIO_PORT_0); 54 | device->out_w1tc = (1 << gpio_num); 55 | #else 56 | // not supportd on non ESP32 chips 57 | assert(false); 58 | #endif 59 | } 60 | -------------------------------------------------------------------------------- /src/output_i2s/rmt_pulse.c: -------------------------------------------------------------------------------- 1 | #include "../output_common/render_method.h" 2 | #include "esp_intr_alloc.h" 3 | 4 | #ifdef RENDER_METHOD_I2S 5 | 6 | #include "driver/rmt.h" 7 | #include "rmt_pulse.h" 8 | 9 | #include "soc/rmt_struct.h" 10 | 11 | static intr_handle_t gRMT_intr_handle = NULL; 12 | 13 | // the RMT channel configuration object 14 | static rmt_config_t row_rmt_config; 15 | 16 | // keep track of wether the current pulse is ongoing 17 | volatile bool rmt_tx_done = true; 18 | 19 | /** 20 | * Remote peripheral interrupt. Used to signal when transmission is done. 21 | */ 22 | static void IRAM_ATTR rmt_interrupt_handler(void* arg) { 23 | rmt_tx_done = true; 24 | RMT.int_clr.val = RMT.int_st.val; 25 | } 26 | 27 | // The extern line is declared in esp-idf/components/driver/deprecated/rmt_legacy.c. It has access 28 | // to RMTMEM through the rmt_private.h header which we can't access outside the sdk. Declare our own 29 | // extern here to properly use the RMTMEM smybol defined in 30 | // components/soc/[target]/ld/[target].peripherals.ld Also typedef the new rmt_mem_t struct to the 31 | // old rmt_block_mem_t struct. Same data fields, different names 32 | typedef rmt_mem_t rmt_block_mem_t; 33 | extern rmt_block_mem_t RMTMEM; 34 | 35 | void rmt_pulse_init(gpio_num_t pin) { 36 | row_rmt_config.rmt_mode = RMT_MODE_TX; 37 | // currently hardcoded: use channel 0 38 | row_rmt_config.channel = RMT_CHANNEL_1; 39 | 40 | row_rmt_config.gpio_num = pin; 41 | row_rmt_config.mem_block_num = 2; 42 | 43 | // Divide 80MHz APB Clock by 8 -> .1us resolution delay 44 | row_rmt_config.clk_div = 8; 45 | 46 | row_rmt_config.tx_config.loop_en = false; 47 | row_rmt_config.tx_config.carrier_en = false; 48 | row_rmt_config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; 49 | row_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; 50 | row_rmt_config.tx_config.idle_output_en = true; 51 | 52 | esp_intr_alloc( 53 | ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_LEVEL3, rmt_interrupt_handler, 0, &gRMT_intr_handle 54 | ); 55 | 56 | rmt_config(&row_rmt_config); 57 | rmt_set_tx_intr_en(row_rmt_config.channel, true); 58 | } 59 | 60 | void rmt_pulse_deinit() { 61 | esp_intr_disable(gRMT_intr_handle); 62 | esp_intr_free(gRMT_intr_handle); 63 | } 64 | 65 | void IRAM_ATTR pulse_ckv_ticks(uint16_t high_time_ticks, uint16_t low_time_ticks, bool wait) { 66 | while (!rmt_tx_done) { 67 | }; 68 | volatile rmt_item32_t* rmt_mem_ptr = &(RMTMEM.chan[row_rmt_config.channel].data32[0]); 69 | if (high_time_ticks > 0) { 70 | rmt_mem_ptr->level0 = 1; 71 | rmt_mem_ptr->duration0 = high_time_ticks; 72 | rmt_mem_ptr->level1 = 0; 73 | rmt_mem_ptr->duration1 = low_time_ticks; 74 | } else { 75 | rmt_mem_ptr->level0 = 1; 76 | rmt_mem_ptr->duration0 = low_time_ticks; 77 | rmt_mem_ptr->level1 = 0; 78 | rmt_mem_ptr->duration1 = 0; 79 | } 80 | RMTMEM.chan[row_rmt_config.channel].data32[1].val = 0; 81 | rmt_tx_done = false; 82 | RMT.conf_ch[row_rmt_config.channel].conf1.mem_rd_rst = 1; 83 | RMT.conf_ch[row_rmt_config.channel].conf1.mem_owner = RMT_MEM_OWNER_TX; 84 | RMT.conf_ch[row_rmt_config.channel].conf1.tx_start = 1; 85 | while (wait && !rmt_tx_done) { 86 | }; 87 | } 88 | 89 | void IRAM_ATTR pulse_ckv_us(uint16_t high_time_us, uint16_t low_time_us, bool wait) { 90 | pulse_ckv_ticks(10 * high_time_us, 10 * low_time_us, wait); 91 | } 92 | 93 | bool IRAM_ATTR rmt_busy() { 94 | return !rmt_tx_done; 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/output_i2s/rmt_pulse.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Emit a pulse of precise length on a pin, using the RMT peripheral. 3 | */ 4 | 5 | #pragma once 6 | #include 7 | #include "driver/gpio.h" 8 | #include "esp_attr.h" 9 | 10 | /** 11 | * Initializes RMT Channel 0 with a pin for RMT pulsing. 12 | * The pin will have to be re-initialized if subsequently used as GPIO. 13 | */ 14 | void rmt_pulse_init(gpio_num_t pin); 15 | 16 | /** 17 | * Resets the pin and RMT peripheral, frees associated resources. 18 | */ 19 | void rmt_pulse_deinit(); 20 | 21 | /** 22 | * Outputs a single pulse (high -> low) on the configured pin. 23 | * This function will always wait for a previous call to finish. 24 | * 25 | * @param: high_time_us Pulse high time in us. 26 | * @param: low_time_us Pulse low time in us. 27 | * @param: wait Block until the pulse is finished. 28 | */ 29 | void pulse_ckv_us(uint16_t high_time_us, uint16_t low_time_us, bool wait); 30 | /** 31 | * Indicates if the rmt is currently sending a pulse. 32 | */ 33 | bool rmt_busy(); 34 | 35 | /** 36 | * Outputs a single pulse (high -> low) on the configured pin. 37 | * This function will always wait for a previous call to finish. 38 | * 39 | * @param: high_time_us Pulse high time clock ticks. 40 | * @param: low_time_us Pulse low time in clock ticks. 41 | * @param: wait Block until the pulse is finished. 42 | */ 43 | void pulse_ckv_ticks(uint16_t high_time_us, uint16_t low_time_us, bool wait); 44 | -------------------------------------------------------------------------------- /src/output_lcd/idf-4-backports.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Backported functions to make the LCD-based driver compile with IDF < 5.0 3 | */ 4 | 5 | #define RMT_BASECLK_DEFAULT RMT_BASECLK_APB 6 | typedef int rmt_clock_source_t; 7 | 8 | static inline void rmt_ll_enable_periph_clock(rmt_dev_t* dev, bool enable) { 9 | dev->sys_conf.clk_en = enable; // register clock gating 10 | dev->sys_conf.mem_clk_force_on = enable; // memory clock gating 11 | } 12 | 13 | static inline void rmt_ll_enable_mem_access_nonfifo(rmt_dev_t* dev, bool enable) { 14 | dev->sys_conf.apb_fifo_mask = enable; 15 | } 16 | 17 | __attribute__((always_inline)) static inline void rmt_ll_tx_fix_idle_level( 18 | rmt_dev_t* dev, uint32_t channel, uint8_t level, bool enable 19 | ) { 20 | dev->chnconf0[channel].idle_out_en_n = enable; 21 | dev->chnconf0[channel].idle_out_lv_n = level; 22 | } 23 | -------------------------------------------------------------------------------- /src/output_lcd/lcd_driver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * LCD bus configuration parameters. 10 | */ 11 | typedef struct { 12 | // GPIO numbers of the parallel bus pins. 13 | gpio_num_t data[16]; 14 | 15 | // horizontal clock pin. 16 | gpio_num_t clock; 17 | // vertical clock pin 18 | gpio_num_t ckv; 19 | 20 | // horizontal "Start Pulse", enabling data input on the line shift register 21 | gpio_num_t start_pulse; 22 | // latch enable 23 | gpio_num_t leh; 24 | // vertical start pulse, resetting the vertical line shift register. 25 | gpio_num_t stv; 26 | } lcd_bus_config_t; 27 | 28 | /// Configuration structure for the LCD-based Epd driver. 29 | typedef struct { 30 | // high time for CKV in 1/10us. 31 | size_t pixel_clock; // = 12000000 32 | int ckv_high_time; // = 70 33 | int line_front_porch; // = 4 34 | int le_high_time; // = 4 35 | int bus_width; // = 16 36 | lcd_bus_config_t bus; 37 | } LcdEpdConfig_t; 38 | 39 | typedef bool (*line_cb_func_t)(void*, uint8_t*); 40 | typedef void (*frame_done_func_t)(void*); 41 | 42 | void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height); 43 | void epd_lcd_deinit(); 44 | void epd_lcd_frame_done_cb(frame_done_func_t, void* payload); 45 | void epd_lcd_line_source_cb(line_cb_func_t, void* payload); 46 | void epd_lcd_start_frame(); 47 | /** 48 | * Set the LCD pixel clock frequency in MHz. 49 | */ 50 | void epd_lcd_set_pixel_clock_MHz(int frequency); 51 | -------------------------------------------------------------------------------- /src/output_lcd/render_lcd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../output_common/render_context.h" 4 | 5 | /** 6 | * Lighten / darken picels using the LCD driving method. 7 | */ 8 | void epd_push_pixels_lcd(RenderContext_t* ctx, short time, int color); 9 | 10 | /** 11 | * Do a full update cycle with a configured context. 12 | */ 13 | void lcd_do_update(RenderContext_t* ctx); 14 | 15 | /** 16 | * Worker thread for output calculation. 17 | * In LCD mode, both threads do the same thing. 18 | */ 19 | void lcd_calculate_frame(RenderContext_t* ctx, int thread_id); 20 | -------------------------------------------------------------------------------- /src/render.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "epdiy.h" 4 | /** 5 | * Initialize the EPD renderer and its render context. 6 | */ 7 | void epd_renderer_init(enum EpdInitOptions options); 8 | 9 | /** 10 | * Deinitialize the EPD renderer and free up its resources. 11 | */ 12 | void epd_renderer_deinit(); 13 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRC_DIRS "." 2 | INCLUDE_DIRS "." 3 | REQUIRES unity epdiy) -------------------------------------------------------------------------------- /test/test_initialization.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/task.h" 5 | 6 | #include "epd_board.h" 7 | #include "epd_display.h" 8 | #include "epdiy.h" 9 | 10 | // choose the default demo board depending on the architecture 11 | #ifdef CONFIG_IDF_TARGET_ESP32 12 | #define TEST_BOARD epd_board_v6 13 | #elif defined(CONFIG_IDF_TARGET_ESP32S3) 14 | #define TEST_BOARD epd_board_v7 15 | #endif 16 | 17 | TEST_CASE("initialization and deinitialization works", "[epdiy,e2e]") { 18 | epd_init(&TEST_BOARD, &ED097TC2, EPD_OPTIONS_DEFAULT); 19 | 20 | epd_poweron(); 21 | vTaskDelay(2); 22 | epd_poweroff(); 23 | 24 | epd_deinit(); 25 | } 26 | 27 | TEST_CASE("re-initialization works", "[epdiy,e2e]") { 28 | epd_init(&TEST_BOARD, &ED097TC2, EPD_OPTIONS_DEFAULT); 29 | 30 | epd_poweron(); 31 | vTaskDelay(2); 32 | epd_poweroff(); 33 | 34 | epd_deinit(); 35 | 36 | int before_init = esp_get_free_internal_heap_size(); 37 | epd_init(&TEST_BOARD, &ED097TC2, EPD_OPTIONS_DEFAULT); 38 | 39 | epd_poweron(); 40 | vTaskDelay(2); 41 | epd_poweroff(); 42 | 43 | epd_deinit(); 44 | int after_init = esp_get_free_internal_heap_size(); 45 | TEST_ASSERT_EQUAL(after_init, before_init); 46 | } -------------------------------------------------------------------------------- /test/test_line_mask.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len); 7 | 8 | const uint8_t col_dirtyness_example[8] = { 0x00, 0x0F, 0x00, 0x11, 0xFF, 0xFF, 0x00, 0x80 }; 9 | 10 | TEST_CASE("mask populated correctly", "[epdiy,unit]") { 11 | const uint8_t expected_mask[8] = { 0x30, 0xF0, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00 }; 12 | uint8_t mask[8] = { 0 }; 13 | epd_populate_line_mask(mask, col_dirtyness_example, 4); 14 | 15 | TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_mask, mask, 8); 16 | } 17 | 18 | TEST_CASE("neutral mask with null dirtyness", "[epdiy,unit]") { 19 | const uint8_t expected_mask[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }; 20 | uint8_t mask[8] = { 0 }; 21 | 22 | epd_populate_line_mask(mask, NULL, 4); 23 | 24 | TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_mask, mask, 8); 25 | } 26 | --------------------------------------------------------------------------------