├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── asm_thumb
├── README.md
├── __init__.py
├── fractal.py
└── monobitmap.py
├── fractal.py
├── main.py
├── monobitmap.py
├── photos
└── m4-epaper27-julia.jpg
├── third_party
├── __init__.py
└── waveshare
│ ├── LICENSE
│ ├── __init__.py
│ ├── color_epd1in54.py
│ ├── color_epd2in13.py
│ ├── epd2in13.py
│ ├── epd2in7.py
│ ├── epd2in9.py
│ └── epdif.py
└── update-device.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # e-Paper Display driver code for CircuitPython with a fractal demo
2 |
3 | An e-Paper display library for
4 | [CircuitPython](https://github.com/adafruit/circuitpython),
5 | a [MicroPython](https://micropython.org/) variant,
6 | with a demo that draws a Julia or Mandlebrot fractal on the display.
7 |
8 | 
9 | [as tweeted](https://twitter.com/gpshead/status/1005603935413915648)
10 |
11 | # Hardware and Software Requirements
12 |
13 | This code was written and tested on an Adafruit Metro M4 running CircuitPython
14 | 3.0. The e-Paper display code should work on anything capable of running
15 | CircuitPython. The fractal demo requires floating point; it'll be extremely
16 | slow if you get it running on an M0.
17 |
18 | The e-Paper displays are the monochrome SPI varities from Waveshare.
19 | Six digital I/O pins are required.
20 |
21 | ## Supported e-Paper Displays
22 |
23 | * The monochrome 2.7" Waveshare e-Paper display module
24 | (no hardware partial update support, ~6 seconds to update).
25 | * The monochrome 2.13" and 2.9" Waveshare e-Paper display modules (these have
26 | partial update support and take ~1-2 seconds to update).
27 | * The duochrome 2.13" color Waveshare e-Paper display modules (these take ~20
28 | seconds to update; sorting microscopic pigmented balls is not a fast).
29 |
30 | Based on my reading I *suspect* the interfaces for the above cover all of the
31 | SPI connected Waveshare display types but others have not yet been tested.
32 |
33 | ## Connections
34 |
35 | This library assumes the jumper on the displays remains in 8-bit mode with a
36 | dedicated data/command (DC) pin which is how the ones I purchased were shipped.
37 |
38 | 1. Digital input for Busy.
39 | 1. Digital output for Reset.
40 | 1. Digital output for Chip Select.
41 | 1. Digital output for SPI MOSI.
42 | 1. Digital output for SPI CLK
43 | 1. Digital output for Data / Command.
44 |
45 | By default the `epdif` code uses the hardware SPI bus for SPI MOSI and SPI CLK.
46 |
47 | # Licenses
48 |
49 | Apache 2.0 for top level code. The `third_party/` tree contains code from
50 | other sources which carries its own licenses. In particular the Waveshare code
51 | has a MIT style license on it so the ported code retains that license.
52 |
53 | # Disclaimer
54 |
55 | This is not an official Google product.
56 |
--------------------------------------------------------------------------------
/asm_thumb/README.md:
--------------------------------------------------------------------------------
1 | # ARM Assembly CircuitPython Fractal Optimizations
2 |
3 | This package is where the fun continues... when originally writing
4 | the code, I was happy in the "Yay, it works!" sense, but wasn't
5 | satisfied with the speed _and_ wanted to get my hands dirty with
6 | a fancy MicroPython feature to learn some ARM assembly.
7 |
8 | A microcontroller running at 25x the clock speed of my old PCjr
9 | should have no trouble spitting out fractals in the blink of an eye,
10 | made even easier as the M4 has floating point hardware!
11 | Challenge accepted, lets give it a whirl.
12 |
13 | ## Prerequisite: Build a custom CircuitPython (!)
14 |
15 | To use any code in this subdirectory you will need to make a custom
16 | build of [CircuitPython](https://github.com/adafruit/circuitpython) 3
17 | or later and flash it to your device. Edit the
18 | `ports/atmel-samd/mpconfigport.h` file and ensure the following
19 | lines are present. By default, if they exist, they are probably `(0)`
20 | to disable the feature to save space instead of our required `(1)`.
21 |
22 | ```c
23 | #define MICROPY_EMIT_INLINE_THUMB (1)
24 | #define MICROPY_EMIT_INLINE_THUMB_FLOAT (1)
25 | ```
26 |
27 | With that change saved, build your atmel-samd interpreter following the normal
28 | CircuitPython build instructions and it will be `@micropython.asm_thumb`
29 | enabled.
30 |
31 | # Performance Results
32 |
33 | Using the `third_party.waveshare.epd2in7` display size (176x264) on my Adafruit
34 | Metro M4 Express (120Mhz SAMD51J19) under CircuitPython 3.0 I observed this
35 | Julia fractal computation performance.
36 |
37 | ## Original optimized pure CircuitPython 3.0 code.
38 |
39 | 23.9 seconds
40 |
41 | ## + `_fractal_iterate()` implemented using asm.
42 |
43 | 12.4 seconds
44 |
45 | ## + `MonoBitmap.set_pixel` implemented using asm.
46 |
47 | 7.74 seconds
48 |
49 | ### + Removing the `use_julia` conditional.
50 |
51 | 7.5 seconds
52 |
53 | ## Reimplement the entire `for x in range(width)` loop using asm.
54 |
55 | 0.211 seconds
56 |
57 | ## + Fix the bug in the above code.
58 |
59 | 0.199 seconds - now that we are computing the correct thing.
60 |
61 | ## + Optimize the x-loop assembly code.
62 |
63 | **0.156 seconds**!
64 |
65 | Instructions and memory loads were moved outside of loops and the 14 cycle
66 | `vsqrt` instruction was removed by rethinking our math needs.
67 |
68 | ## + Stop printing a `*` to the serial console every row
69 |
70 | **0.140 seconds**
71 |
72 | A full *170x faster* than the original optimized Python code.
73 |
74 | ## Couldn't we optimize more?
75 |
76 | Yes. The next obvious step would be to move the `for y in range(height)`
77 | loop into assembly. If we weren't already an order of magnitude faster
78 | than the slow e-Paper displays this code was originally a demo for, that
79 | might make sense. For that purpose, we've already outdone ourselves. :)
80 |
81 | ## What about CircuitPython 4.1?
82 |
83 | It is noticably faster due to 4.1 enabling some overlooked micropython compile
84 | time optimizations. The pure CircuitPython 4.1 Julia fractal computation drops
85 | from our previous 23.9 down to 16.3 seconds. The asm version is 0.137 seconds.
86 | We're only *119x faster* now. :P
87 |
88 | # You Made This Look Too Easy a.k.a. How Hard Was This?
89 |
90 | It wasn't easy! I spent numerous multi-hour stretches on random evenings
91 | or weekends in early 2018 weeks hammering this out. I wasn't using git
92 | to track my work at the time, though I saved various snapshots of my progress
93 | which I used to recreate parts of my journey in this repository.
94 |
95 | ## Challenge: Debugging `@micropython.asm_thumb` Code
96 |
97 | It is painful. This code is ultimately native asm, you cannot simply do
98 | the equivalent of `print()` debugging or even try things out in a REPL
99 | as you can with normal Python code. Despite its appearance to fit within
100 | Python syntax, this really is just a weird looking subset of ARM Thumb
101 | assembly.
102 |
103 | In the earlier `_fractal_iterate` version of the code you may have noticed that
104 | I made the `fast_in` array extra large and left some debugging code commented
105 | out. That was from a debugging effort where I had it export some internal
106 | state so I could attempt to reason about what was or wasn't going right
107 | internally.
108 |
109 | ## An Example bug fixed along the way
110 |
111 | Fixed now, but I made commit 0d3efe94c84b84237c1e3193fe5746fa5c2eec7c with a
112 | known issue in my x-loop assembly code. Can you spot it? If so, you must have
113 | the ARM Thumb architecture reference manual memorized. :P It took me a very
114 | long time to debug that one. I only noticed it when I started playing with a
115 | newly acquired epd2in9 display which made me actually look closely at what was
116 | on the screen... One row of pixels at the edge was simply wrong. Look at
117 | the diff to see why. I spent many hours over many days puzzling over that.
118 |
119 | # Future Work
120 |
121 | MicroPython is _supposed_ to support non-assembly optimization features via
122 | `@micropython.native` (similar to Cython) and a Numba like `@micropython.viper`
123 | dynamic compiler.
124 |
125 | I'd love to test out their performance, but I have been unable to get them to
126 | build properly in my circuitpython tree. Viper might do well for the
127 | `MonoBitmap.set_pixel` function, but without support for floats it would not be
128 | of much help for the inner loop complex math iteration.
129 |
130 | It is also possible to write fractal algorithms using only integers. Great for
131 | running on previous generation smaller MCUs without floating point. The
132 | [famous fractint](https://fractint.org) program has been a repository of that
133 | art for decades. If you are really looking to get fractals on a smaller MCU
134 | without floating point, you may find inspiration there. There are also a
135 | people who have made real time [FPGA implementations of
136 | Mandlebrot](https://www.google.com/search?q=fpga+mandlebrot). Who needs a
137 | microcontroller at all?
138 |
139 | # Caveats
140 |
141 | Writing native assembly is instructive (pun intended!) but extremely difficult
142 | to maintain. Do it to learn, but it should always be a last resort.
143 |
--------------------------------------------------------------------------------
/asm_thumb/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gpshead/epaper-circuitpython/29555dccd8dde5c592d6c9974423158358b53e34/asm_thumb/__init__.py
--------------------------------------------------------------------------------
/asm_thumb/fractal.py:
--------------------------------------------------------------------------------
1 | # python3: CircuitPython 3.0
2 |
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2018 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | """Compute a Mandlebrot or Julia fractal into a bitmap."""
20 |
21 | import array
22 | import struct
23 | import time
24 |
25 | from . import monobitmap
26 |
27 |
28 | MAX_ITERATIONS = 40
29 |
30 |
31 | # Data must be passed in via an array as micropython.asm_thumb doesn't
32 | # support float arguments or more than three integer register inputs.
33 | #
34 | # Inputs:
35 | # Constant inputs:
36 | # 0 [0x00]. &MonoBitmap.bit_buf (addr/int)
37 | # 1 [0x04]. width (int)
38 | # 2 [0x08]. julia or mandlebrot iteration? (bool true=julia)
39 | # 3 [0x0c]. scale (float)
40 | # 4 [0x10]. center_x (float)
41 | # 5 [0x14]. center_y (float)
42 | # 6 [0x18]. julia_c real (float)
43 | # 7 [0x1c]. julia_c imag (float)
44 | # 8 [0x20]. max_iterations (int)
45 | # Variants:
46 | # - y
47 | #
48 | # r0: &buffer containing the above 32-bit values.
49 | # r1: y
50 | @micropython.asm_thumb
51 | def _xloop_iterate_and_set_pixels(r0, r1):
52 | # scaled_y_j: float = (y*scale - center_y)*1j
53 | push({r8,r9,r10,r11,r12})
54 | mov(r8, r1) # REG: r8 <- y, free REG r1
55 | vmov(s11, r8)
56 | vcvt_f32_s32(s11, s11) # REG: s11 <- y (float)
57 | ldr(r1, [r0, 0x00]) # address of bit_buf
58 | ldr(r7, [r0, 0x04]) # width
59 | mov(r9, r1) # REG: r9 <- address of bit_buf
60 | ldr(r1, [r0, 0x20]) # MAX_ITERATIONS
61 | mov(r10, r7) # REG: r10 <- width
62 | mov(r11, r1) # REG: r11 <- MAX_ITERATIONS
63 | ldr(r7, [r0, 0x08]) # use_julia
64 | mov(r12, r7) # REG: r12 <- use_julia
65 | vldr(s10, [r0, 0x0c]) # REG: s10 <- scale
66 | vldr(s15, [r0, 0x10]) # REG: s15 <- center_x
67 | vldr(s12, [r0, 0x14]) # REG: s12 <- center_y
68 | vmul(s13, s11, s10)
69 | vsub(s12, s13, s12) # REG: s12 <- scaled_y_j (c_imag)
70 | vldr(s0, [r0, 0x18]) # REG: s0 <- julia_c real
71 | vldr(s1, [r0, 0x1c]) # REG: s1 <- julia_c imag
72 |
73 | # for x in range(width):
74 | mov(r1, 0) # REG: r1 <- x
75 | label(X_RANGE_START)
76 | # c: complex = x*scale - center_x + scaled_y_j
77 | vmov(s13, r1)
78 | vcvt_f32_s32(s13, s13) # REG: s13 <- x
79 | vmul(s14, s13, s10)
80 | vsub(s14, s14, s15) # REG: s14 <- (c_real)
81 |
82 | # if use_julia:
83 | mov(r7, r12) # REG: r7 <- use_julia
84 | cmp(r7, 0)
85 | beq(MANDLEBROT)
86 | label(JULIA)
87 | # v: int = iterate(julia_c, c) # Julia
88 | # iterate c= parameter.
89 | # (done before the loop - s0 and s1 are not clobbered by our code)
90 | #vldr(s0, [r0, 0x18]) # REG: s0 <- julia_c real
91 | #vldr(s1, [r0, 0x1c]) # REG: s1 <- julia_c imag
92 | # iterate z= parameter.
93 | ##vmov(s5, s14)
94 | vmov(r7, s14)
95 | vmov(s5, r7) # REG: s5 <- c_real
96 | ##vmov(s3, s12)
97 | vmov(r7, s12)
98 | vmov(s3, r7) # REG: s3 <- c_imag
99 | b(CALL_ITERATE)
100 |
101 | # else:
102 | label(MANDLEBROT)
103 | # v: int = iterate(c, 0) # Mandlebrot
104 | # iterate c= parameter.
105 | ##vmov(s0, s14)
106 | vmov(r7, s14)
107 | vmov(s0, r7) # REG: s0 <- c_real
108 | ##vmov(s1, s12)
109 | vmov(r7, s12)
110 | vmov(s1, r7) # REG: s1 <- c_imag
111 | # iterate z= parameter.
112 | mov(r7, 0)
113 | vmov(s5, r7) # REG: s5 <- 0
114 | vmov(s3, r7) # REG: s3 <- 0
115 |
116 | label(CALL_ITERATE)
117 | # Input: s0, s1, s5, s3
118 | # Output: r0
119 | #push({r0})
120 | bl(FRACTAL_ITERATE)
121 | # set_pixel(x, y, v)
122 | # Inputs: r9 (&bit_buf), r10 (width), r1 (x), r2 (value), r8 (y)
123 | # set_pixel v= parameter is the result of iterate from r0
124 | mov(r2, r0)
125 | #pop({r0})
126 | # set_pixel x= parameters is already in r1 due to the loop.
127 | bl(SET_PIXEL)
128 |
129 | mov(r7, r10) # REG: r7 <- width
130 | add(r1, 1) # x += 1
131 | cmp(r1, r7) # if x < width, repeat the loop
132 | blt(X_RANGE_START)
133 | b(RETURN)
134 |
135 | ####
136 | label(FRACTAL_ITERATE)
137 | mov(r6, r11) # r6 <- MAX_ITERATIONS
138 | # @asm_thumb doesn't do arm float immediates on vmov,
139 | # otherwise we could just write vmov(r4, 4.0).
140 | movt(r4, 0x4080) # r4 <- 32bit floating point 4.0 (2.0^2) 0x40800000
141 | ##mov(r4, 4) # r4 <- 2^2
142 | vmov(s4, r4) # s4 <- 2.0^2
143 | ##vcvt_f32_s32(s4, s4) # s4 = 2.0^2;
144 | # for n in range(MAX_ITERATIONS+1):
145 | mov(r0, 0) # r0 = n; current iteration number
146 | label(FI_LOOP)
147 | vmov(r2, s5) # start with zr real in s2. The loop
148 | vmov(s2, r2) # always begins/ends/cycles with it in s5.
149 | ##vmov(s2, s5) not supported by @arm_thumb
150 | # complex z = z * z + c
151 | vmul(s5, s3, s3) # zj^2
152 | vmul(s6, s2, s2) # zr^2
153 | vsub(s5, s6, s5) # z = z*z real part (zr^2 - zj^2) [into a NEW reg]
154 | ##vmls(s5, s6, s5) not supported by @arm_thumb
155 | vmul(s6, s2, s3)
156 | vadd(s3, s6, s6) # z = z*z imaginary part (zr*zj + zr*zj)
157 | # REG: s5 is the new zr real
158 | # REG: s3 is (still) zj imaginary
159 | vadd(s5, s5, s0) # zr += cr real part
160 | vadd(s3, s3, s1) # zj += cj imaginary part
161 | # complex abs(z)
162 | vmul(s6, s5, s5) # zr^2
163 | vmul(s7, s3, s3) # zj^2
164 | vadd(s6, s6, s7) # s6 <- zr^2 + zj^2
165 | # sqrt(A) < B is the same as A < B^2 for non-negative A and B.
166 | # This avoids a 14-cycle vsqrt instruction in our innermost loop! :)
167 | ##vsqrt(s6, s6) # sqrt(zr^2 + zj^2)
168 | # if abs(z) > 2
169 | vcmp(s6, s4)
170 | vmrs(APSR_nzcv, FPSCR)
171 | bgt(FI_END) # return n
172 | add(r0, 1)
173 | cmp(r0, r6)
174 | ble(FI_LOOP)
175 | mov(r0, 1) # return -1
176 | neg(r0, r0)
177 | label(FI_END) # return value is already in r0
178 | bx(lr)
179 | #### end FRACTAL_ITERATE()
180 |
181 | ####
182 | label(SET_PIXEL)
183 | mov(r3, r9) # r3 <- address of bit_buf
184 | mov(r5, r10) # r5 <- width
185 | mov(r6, r8) # r6 <- y
186 | mul(r5, r6) # r5 <- width * y
187 | add(r4, r1, r5) # r4 <- binary_idx (width * y + x)
188 | #mla(r4, r5, r6, r1) @asm_thumb doesn't support mla?
189 | mov(r5, r4) # r5 <- binary_idx
190 | mov(r7, 3)
191 | lsr(r4, r7) # r4 <- byte_idx
192 | mov(r7, 7)
193 | and_(r5, r7)
194 | sub(r5, r7, r5) # r5 <- bit_no
195 | mov(r6, 1)
196 | lsl(r6, r5) # r6 <- (1 << bit_no)
197 | add(r3, r3, r4) # r3 <- &bit_buf[byte_idx]
198 | ldrb(r4, [r3, 0]) # r4 <- bit_buf[byte_idx]
199 | mov(r7, 1)
200 | and_(r2, r7) # r2 <- value &= 1 (sets branch flags)
201 | beq(SPF_CLEAR_BIT)
202 | orr(r4, r6) # r4 |= (1 << bit_no)
203 | b(SPF_STORE)
204 | label(SPF_CLEAR_BIT)
205 | bic(r4, r6) # r4 &= ~(1 << bit_no) # Nice instruction!
206 | label(SPF_STORE)
207 | strb(r4, [r3, 0])
208 | bx(lr)
209 | #### end SET_PIXEL
210 |
211 | label(RETURN)
212 | pop({r8,r9,r10,r11,r12})
213 |
214 |
215 | # Created based on looking up how others have written Mandlebrot and Julia
216 | # fractal computations in Python. Well known algorithms. Python's
217 | # built-in complex number support makes them easy to express in code.
218 | # https://en.wikipedia.org/wiki/Mandelbrot_set#Computer_drawings
219 | # https://en.wikipedia.org/wiki/Julia_set#Pseudocode
220 | # https://github.com/ActiveState/code/blob/master/recipes/Python/579143_Mandelbrot_Set_made_simple/recipe-579143.py
221 | # https://github.com/ActiveState/code/blob/master/recipes/Python/577120_Julia_fractals/recipe-577120.py
222 | # http://0pointer.de/blog/projects/mandelbrot.html
223 | def get_fractal(width: int, height: int, use_julia: bool = True,
224 | max_iterations: int = MAX_ITERATIONS) -> monobitmap.MonoBitmap:
225 | scale = 1/(width/1.5)
226 | if use_julia:
227 | center_x, center_y = 1.15, 1.6 # Julia
228 | else:
229 | center_x, center_y = 2.2, 1.5 # Mandlebrot
230 |
231 | fractal = monobitmap.MonoBitmap(width, height)
232 | julia_c = 0.3+0.6j # Only load the complex constant once.
233 |
234 | xloop_params = array.array('i', (0,)*9)
235 | monobitmap.store_addr(xloop_params, fractal.bit_buf)
236 | xloop_params[1] = width
237 | xloop_params[2] = use_julia # True: Julia, False: Mandlebrot
238 | struct.pack_into('fffff', xloop_params, 0x0c,
239 | scale, center_x, center_y,
240 | julia_c.real, julia_c.imag)
241 | xloop_params[8] = max_iterations
242 |
243 | # Make these local
244 | compute_row_and_set_pixels = _xloop_iterate_and_set_pixels
245 | monotonic = time.monotonic
246 |
247 | start_time = monotonic()
248 | for y in range(height):
249 | compute_row_and_set_pixels(xloop_params, y)
250 | end_time = monotonic()
251 | print('Computation took', end_time - start_time, 'seconds.')
252 | return fractal
253 |
254 |
--------------------------------------------------------------------------------
/asm_thumb/monobitmap.py:
--------------------------------------------------------------------------------
1 | # python3: CircuitPython 3.0
2 |
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2018 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | """A monochrome bitmap to represent display frame buffers."""
20 |
21 | import array
22 |
23 |
24 | class MonoBitmap:
25 | """A monochrome bitmap stored compactly.
26 |
27 | Attributes:
28 | bit_buf: The raw bitmap buffer bytearray. Do not resize!
29 | fast_in: Input array to pass to set_pixel_fast. Do not modify.
30 | width: The width. Do not modify.
31 | height: The height. Do not modify.
32 |
33 | Usage:
34 | hidden_cat = MonoBitmap(204, 112)
35 | hidden_cat.set_pixel(42, 23, 0)
36 | epd.display_frame_buf(hidden_cat.bit_buf)
37 | """
38 | def __init__(self, width, height):
39 | self.width = width
40 | self.height = height
41 | self._bit_buf = bytearray(width * height // 8)
42 |
43 | # (&bit_buf, width, y)
44 | self.fast_in = array.array('i', [-1, width, 0])
45 | store_addr(self.fast_in, self._bit_buf)
46 |
47 | @property
48 | def bit_buf(self):
49 | return self._bit_buf
50 |
51 | def set_pixel(self, x: int, y: int, value: bool) -> None:
52 | #assert self.width > x >= 0
53 | #assert self.height > y >= 0
54 | binary_idx = self.width * y + x
55 | #byte_idx, bit_no = divmod(binary_idx, 8)
56 | #bit_no = 7 - bit_no # The EPD's bit order.
57 | byte_idx = binary_idx >> 3
58 | bit_no = 7 - (binary_idx & 7)
59 | if value:
60 | self.bit_buf[byte_idx] |= (1 << bit_no)
61 | else:
62 | self.bit_buf[byte_idx] &= ~(1 << bit_no)
63 |
64 | # r0 must be the self.fast_in array (&bit_buf, width, y)
65 | # r1 is the x coordinate
66 | # r2 is the value to set for the pixel
67 | # No bounds checking is performed!
68 | @staticmethod
69 | @micropython.asm_thumb
70 | def set_pixel_fast(r0, r1, r2):
71 | # r0 <- inputs array
72 | # r1 <- x
73 | # r2 <- value
74 | ldr(r3, [r0, 0]) # r3 <- address of bit_buf
75 | ldr(r5, [r0, 4]) # r5 <- width
76 | ldr(r6, [r0, 8]) # r6 <- y
77 | mul(r5, r6) # r5 <- width * y
78 | add(r4, r1, r5) # r4 <- binary_idx (width * y + x)
79 | mov(r5, r4) # r5 <- binary_idx
80 | mov(r7, 3)
81 | lsr(r4, r7) # r4 <- byte_idx
82 | mov(r7, 7)
83 | and_(r5, r7)
84 | sub(r5, r7, r5) # r5 <- bit_no
85 | mov(r6, 1)
86 | lsl(r6, r5) # r6 <- (1 << bit_no)
87 | add(r3, r3, r4) # r1 <- &bit_buf[byte_idx]
88 | ldrb(r4, [r3, 0]) # r4 <- bit_buf[byte_idx]
89 | mov(r7, 1)
90 | and_(r2, r7) # r2 <- value &= 1 (sets the eq zero flag)
91 | beq(CLEAR_BIT)
92 | orr(r4, r6) # r4 |= (1 << bit_no)
93 | b(STORE)
94 | label(CLEAR_BIT)
95 | bic(r4, r6) # r4 &= ~(1 << bit_no) # Nice instruction!
96 | label(STORE)
97 | strb(r4, [r3, 0])
98 |
99 |
100 | # This utility function really belongs somewhere else.
101 | @micropython.asm_thumb
102 | def store_addr(r0, r1):
103 | """Store r1 into r0[0]. Good for getting &thing into a buffer."""
104 | str(r1, [r0, 0])
105 |
--------------------------------------------------------------------------------
/fractal.py:
--------------------------------------------------------------------------------
1 | # python3: CircuitPython 3.0
2 |
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2018 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | """Compute a Mandlebrot or Julia fractal into a bitmap."""
20 |
21 | import time
22 |
23 | import monobitmap
24 |
25 |
26 | MAX_ITERATIONS = 40
27 |
28 |
29 | # Avoiding the builtin abs lookup in the loop is ~10% faster.
30 | # Avoiding the builtin range lookup gains another ~3%.
31 | def _fractal_iterate(c, z=0, max_iter1=MAX_ITERATIONS+1,
32 | _abs=abs, _range=range) -> int:
33 | for n in _range(max_iter1):
34 | z = z * z + c
35 | if _abs(z) > 2:
36 | return n
37 | return -1
38 |
39 |
40 | # Created based on looking up how others have written Mandlebrot and Julia
41 | # fractal computations in Python. Well known algorithms. Python's
42 | # built-in complex number support makes them easy to express in code.
43 | # https://en.wikipedia.org/wiki/Mandelbrot_set#Computer_drawings
44 | # https://en.wikipedia.org/wiki/Julia_set#Pseudocode
45 | # https://github.com/ActiveState/code/blob/master/recipes/Python/579143_Mandelbrot_Set_made_simple/recipe-579143.py
46 | # https://github.com/ActiveState/code/blob/master/recipes/Python/577120_Julia_fractals/recipe-577120.py
47 | # http://0pointer.de/blog/projects/mandelbrot.html
48 | def get_fractal(width, height, use_julia=True, max_iterations=MAX_ITERATIONS):
49 | scale = 1/(width/1.5)
50 | if use_julia:
51 | center_x, center_y = 1.15, 1.6 # Julia
52 | else:
53 | center_x, center_y = 2.2, 1.5 # Mandlebrot
54 |
55 | fractal = monobitmap.MonoBitmap(width, height)
56 | set_pixel = fractal.set_pixel # faster name lookup
57 | iterate = _fractal_iterate # faster name lookup
58 | julia_c = 0.3+0.6j # Only load the complex constant once.
59 | max_iterations += 1
60 |
61 | start_time = time.monotonic()
62 | for y in range(height):
63 | scaled_y_j_m_cx = (y*scale - center_y)*1j - center_x
64 | for x in range(width):
65 | c = x*scale + scaled_y_j_m_cx
66 | if use_julia:
67 | n = iterate(julia_c, c, max_iter1=max_iterations) # Julia
68 | else:
69 | n = iterate(c, max_iter1=max_iterations) # Mandlebrot
70 |
71 | set_pixel(x, y, n & 1)
72 |
73 | print('*', end='')
74 | print()
75 | print('Computation took', time.monotonic() - start_time, 'seconds.')
76 | return fractal
77 |
78 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # python3: CircuitPython 3.0
2 |
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2018 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | import board
20 | import digitalio
21 | import random
22 | import time
23 |
24 | #from third_party.waveshare import color_epd2in13 as connected_epd
25 | from third_party.waveshare import epd2in7 as connected_epd
26 | #from third_party.waveshare import epd2in9 as connected_epd
27 | #from third_party.waveshare import epd2in13 as connected_epd
28 |
29 | try:
30 | # Optimized version - requires a custom CircuitPython build.
31 | from asm_thumb import fractal
32 | from asm_thumb import monobitmap
33 | HAVE_ASM = True
34 | except (ImportError, SyntaxError):
35 | import fractal
36 | import monobitmap
37 | HAVE_ASM = False
38 |
39 |
40 | def sample_keys():
41 | """Yields four bools representing the button press state on the 2.7" EPD.
42 |
43 | The 2.7 inch EPD rpi hat has four push buttons connected to RPI hat pins.
44 |
45 | Follow the traces to see which pins those are and wire them up to digital
46 | inputs D2 through D5 on your CircuitPython device. A button press grounds
47 | the pin.
48 |
49 | This function samples them all and yields four bools for each button.
50 | """
51 | DigitalInOut = digitalio.DigitalInOut
52 | Pull = digitalio.Pull
53 | with DigitalInOut(board.D2) as key1, DigitalInOut(board.D3) as key2, \
54 | DigitalInOut(board.D4) as key3, DigitalInOut(board.D5) as key4:
55 | key1.switch_to_input(Pull.UP)
56 | key2.switch_to_input(Pull.UP)
57 | key3.switch_to_input(Pull.UP)
58 | key4.switch_to_input(Pull.UP)
59 | for k in (key1, key2, key3, key4):
60 | yield not k.value # False is pressed
61 |
62 |
63 | class StatusLED:
64 | """A simple interface to the onboard pixel."""
65 |
66 | def __init__(self):
67 | if hasattr(board, 'NEOPIXEL'):
68 | import neopixel
69 | self._led = neopixel.NeoPixel(board.NEOPIXEL, 1)
70 | self._led.brightness = 1/16
71 | elif hasattr(board, 'APA102_MOSI'):
72 | import adafruit_dotstar
73 | self._led = adafruit_dotstar.DotStar(
74 | board.APA102_SCK, board.APA102_MOSI, 1)
75 | self._led.brightness = 0.7
76 | else:
77 | self._led = [None]
78 |
79 | def off(self):
80 | self._led[0] = b'\0\0\0'
81 |
82 | def busy(self):
83 | self._led[0] = b'\x50\x10\0'
84 |
85 | def ready(self):
86 | self._led[0] = b'\x10\x50\0' if HAVE_ASM else b'\x10\0\x70'
87 |
88 |
89 | def main():
90 | led = StatusLED()
91 | led.busy()
92 |
93 | epd = connected_epd.EPD()
94 | print("Initializing display...")
95 | epd.init()
96 | if HAVE_ASM:
97 | print("@micropython.asm_thumb implementation loaded.")
98 | else:
99 | print("Pure Python implementation loaded.")
100 |
101 | keys = [1,] # Print message on start.
102 | while True:
103 | if any(keys):
104 | led.ready()
105 | print("Awaiting key1-key4 button press.")
106 | keys = list(sample_keys())
107 | if any(keys):
108 | led.busy()
109 |
110 | if keys[0]:
111 | print("Computing Mandlebrot fractal.")
112 | fractal_image = fractal.get_fractal(epd.width, epd.height,
113 | use_julia=False)
114 | print("Displaying.")
115 | if getattr(epd, 'colors', 2) > 2:
116 | epd.display_frames(None, fractal_image.bit_buf)
117 | else:
118 | epd.display_bitmap(fractal_image, fast_ghosting=True)
119 | del fractal_image
120 | elif keys[1]:
121 | print("Setting display to white.")
122 | epd.clear_frame_memory(0xff)
123 | epd.display_frame()
124 | elif keys[2]:
125 | raw_framebuf = bytearray(epd.fb_bytes)
126 | for pos in range(len(raw_framebuf)):
127 | raw_framebuf[pos] = random.randint(0, 255)
128 | print("Displaying random framebuf.")
129 | if getattr(epd, 'colors', 2) <= 2:
130 | epd.display_frame_buf(raw_framebuf, fast_ghosting=True)
131 | else:
132 | raw_framebuf2 = bytearray(epd.fb_bytes)
133 | for pos in range(len(raw_framebuf2)):
134 | raw_framebuf2[pos] = random.randint(0, 255)
135 | epd.display_frames(raw_framebuf, raw_framebuf2)
136 | del raw_framebuf2
137 | del raw_framebuf
138 | elif keys[3]:
139 | print("Computing Julia fractal.")
140 | fractal_image = fractal.get_fractal(epd.width, epd.height,
141 | use_julia=True)
142 | print("Displaying.")
143 | if getattr(epd, 'colors', 2) > 2:
144 | epd.display_frames(fractal_image.bit_buf, None)
145 | else:
146 | epd.display_bitmap(fractal_image, fast_ghosting=True)
147 | del fractal_image
148 | else:
149 | # This effectively debounces the keys, we merely sample them
150 | # rather than treat a state change as an interrupt. If a key
151 | # is not pressed for this duration it may not be noticed.
152 | time.sleep(0.05) # short time between polling.
153 |
154 | print("Done.")
155 |
156 |
157 | if __name__ == '__main__':
158 | main()
159 |
--------------------------------------------------------------------------------
/monobitmap.py:
--------------------------------------------------------------------------------
1 | # python3: CircuitPython 3.0
2 |
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2018 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | """A monochrome bitmap to represent display frame buffers."""
20 |
21 |
22 | class MonoBitmap:
23 | """A monochrome bitmap stored compactly.
24 |
25 | Attributes:
26 | bit_buf: The raw bitmap buffer bytearray. Do not resize!
27 | width: The width. Do not modify.
28 | height: The height. Do not modify.
29 |
30 | Usage:
31 | hidden_cat = MonoBitmap(204, 112)
32 | hidden_cat.set_pixel(42, 23, 0)
33 | epd.display_frame_buf(hidden_cat.bit_buf)
34 | """
35 | def __init__(self, width, height):
36 | self.width = width
37 | self.height = height
38 | self.bit_buf = bytearray(width * height // 8)
39 |
40 | def set_pixel(self, x: int, y: int, value: bool) -> None:
41 | #assert self.width > x >= 0
42 | #assert self.height > y >= 0
43 | binary_idx = self.width * y + x
44 | #byte_idx, bit_no = divmod(binary_idx, 8)
45 | #bit_no = 7 - bit_no # The EPD's bit order.
46 | byte_idx = binary_idx >> 3
47 | bit_no = 7 - (binary_idx & 7)
48 | if value:
49 | self.bit_buf[byte_idx] |= (1 << bit_no)
50 | else:
51 | self.bit_buf[byte_idx] &= ~(1 << bit_no)
52 |
--------------------------------------------------------------------------------
/photos/m4-epaper27-julia.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gpshead/epaper-circuitpython/29555dccd8dde5c592d6c9974423158358b53e34/photos/m4-epaper27-julia.jpg
--------------------------------------------------------------------------------
/third_party/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gpshead/epaper-circuitpython/29555dccd8dde5c592d6c9974423158358b53e34/third_party/__init__.py
--------------------------------------------------------------------------------
/third_party/waveshare/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) Waveshare July 4 2017
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documnetation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
21 | ******************************************************************************
22 | *
23 | * Copyright (c) 2017 Waveshare
24 | * All rights reserved.
25 | *
26 | * THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 | * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29 | * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
30 | * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
31 | * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
34 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
37 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 | *
39 | ******************************************************************************
40 |
--------------------------------------------------------------------------------
/third_party/waveshare/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gpshead/epaper-circuitpython/29555dccd8dde5c592d6c9974423158358b53e34/third_party/waveshare/__init__.py
--------------------------------------------------------------------------------
/third_party/waveshare/color_epd1in54.py:
--------------------------------------------------------------------------------
1 | # python3: CircuitPython
2 |
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2019 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
19 | from third_party.waveshare import color_epd2in13
20 |
21 | class EPD(color_epd2in13.EPD):
22 | width = 152
23 | height = 252
24 |
--------------------------------------------------------------------------------
/third_party/waveshare/color_epd2in13.py:
--------------------------------------------------------------------------------
1 | # Ported to CircuitPython by Gregory P. Smith
2 |
3 | ##
4 | # @filename : epd2in13b.py
5 | # @brief : Implements for Dual-color e-paper library
6 | # @author : Yehui from Waveshare
7 | #
8 | # Copyright (C) Waveshare August 15 2017
9 | #
10 | # Permission is hereby granted, free of charge, to any person obtaining a copy
11 | # of this software and associated documnetation files (the "Software"), to deal
12 | # in the Software without restriction, including without limitation the rights
13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | # copies of the Software, and to permit persons to whom the Software is
15 | # furished to do so, subject to the following conditions:
16 | #
17 | # The above copyright notice and this permission notice shall be included in
18 | # all copies or substantial portions of the Software.
19 | #
20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 | # THE SOFTWARE.
27 | #
28 |
29 | import time
30 |
31 | from . import epdif
32 |
33 | # EPD2IN13B commands
34 | PANEL_SETTING = 0x00
35 | POWER_SETTING = 0x01
36 | POWER_OFF = 0x02
37 | POWER_OFF_SEQUENCE_SETTING = 0x03
38 | POWER_ON = 0x04
39 | POWER_ON_MEASURE = 0x05
40 | BOOSTER_SOFT_START = 0x06
41 | DEEP_SLEEP = 0x07
42 | DATA_START_TRANSMISSION_1 = 0x10 # B&W data
43 | DATA_STOP = 0x11
44 | DISPLAY_REFRESH = 0x12
45 | DATA_START_TRANSMISSION_2 = 0x13 # tinted data
46 | VCOM_LUT = 0x20
47 | W2W_LUT = 0x21
48 | B2W_LUT = 0x22
49 | W2B_LUT = 0x23
50 | B2B_LUT = 0x24
51 | PLL_CONTROL = 0x30
52 | TEMPERATURE_SENSOR_CALIBRATION = 0x40
53 | TEMPERATURE_SENSOR_SELECTION = 0x41
54 | TEMPERATURE_SENSOR_WRITE = 0x42
55 | TEMPERATURE_SENSOR_READ = 0x43
56 | VCOM_AND_DATA_INTERVAL_SETTING = 0x50
57 | LOW_POWER_DETECTION = 0x51
58 | TCON_SETTING = 0x60
59 | RESOLUTION_SETTING = 0x61
60 | GET_STATUS = 0x71
61 | AUTO_MEASURE_VCOM = 0x80
62 | VCOM_VALUE = 0x81
63 | VCM_DC_SETTING = 0x82
64 | PARTIAL_WINDOW = 0x90
65 | PARTIAL_IN = 0x91
66 | PARTIAL_OUT = 0x92
67 | PROGRAM_MODE = 0xA0
68 | ACTIVE_PROGRAM = 0xA1
69 | READ_OTP_DATA = 0xA2
70 | POWER_SAVING = 0xE3
71 |
72 | class EPD:
73 | width = 104
74 | height = 212
75 | colors = 3
76 |
77 | def __init__(self):
78 | self.reset_pin = epdif.RST_PIN
79 | self.dc_pin = epdif.DC_PIN
80 | self.busy_pin = epdif.BUSY_PIN
81 |
82 | def _delay_ms(self, ms):
83 | time.sleep(ms / 1000.)
84 |
85 | def _send_command(self, command):
86 | self.dc_pin.value = 0
87 | epdif.spi_transfer(command.to_bytes(1, 'big'))
88 |
89 | def _send_data(self, data):
90 | self.dc_pin.value = 1
91 | if isinstance(data, int):
92 | epdif.spi_transfer(data.to_bytes(1, 'big'))
93 | else:
94 | # The EPD needs CS to cycle hi between every byte so we loop doing
95 | # one byte transfers to cause that. Not efficient, but it makes
96 | # it work. Data sheets say needs to be at least a 60ns CS pulse.
97 | for i in range(len(data)):
98 | epdif.spi_transfer(data[i:i+1])
99 |
100 | @property
101 | def fb_bytes(self):
102 | return self.width * self.height // 8
103 |
104 | def init(self):
105 | try:
106 | epdif.epd_io_bus_init()
107 | except RuntimeError:
108 | pass # It avoids global io bus reinitialization. Good.
109 | self.reset_pin = epdif.RST_PIN
110 | self.dc_pin = epdif.DC_PIN
111 | self.busy_pin = epdif.BUSY_PIN
112 | self.reset()
113 | self._send_command(POWER_SETTING) # Adafruit EPD
114 | self._send_data(b'\x03\x00\x2b\x2b\x09') # Adafruit EPD
115 | self._send_command(BOOSTER_SOFT_START)
116 | self._send_data(b'\x17\x17\x17')
117 | self._send_command(POWER_ON)
118 | self.wait_until_idle()
119 | # Q: what does this setting do?
120 | self._send_command(PANEL_SETTING)
121 | self._send_data(0xCF) # Adafruit EPD
122 | #self._send_data(0x8F)
123 | self._send_command(VCOM_AND_DATA_INTERVAL_SETTING)
124 | self._send_data(0x37)
125 | self._send_command(PLL_CONTROL) # Adafruit EPD
126 | self._send_data(0x29) # Adafruit EPD
127 | self._send_command(RESOLUTION_SETTING)
128 | self._send_data(self.height.to_bytes(2, 'little'))
129 | self._send_data(self.width.to_bytes(2, 'little'))
130 | self._send_command(VCM_DC_SETTING) # Adafruit EPD
131 | self._send_data(0x0A) # Adafruit EPD
132 |
133 | def wait_until_idle(self):
134 | while self.busy_pin.value == 1: # 0: idle, 1: busy
135 | self._delay_ms(10)
136 |
137 | def reset(self):
138 | self.reset_pin.value = 0 # module reset
139 | self._delay_ms(200)
140 | self.reset_pin.value = 1
141 | self._delay_ms(200)
142 |
143 | def clear_frame_memory(self, pattern: int, tint_pattern: int = -1):
144 | self._send_command(DATA_START_TRANSMISSION_1)
145 | self._delay_ms(2)
146 | row = pattern.to_bytes(1, 'big') * ((self.width + 7) // 8)
147 | for j in range(self.height):
148 | self._send_data(row)
149 | if tint_pattern < 0:
150 | tint_pattern = pattern
151 | self._delay_ms(2)
152 | self._send_command(DATA_START_TRANSMISSION_2)
153 | self._delay_ms(2)
154 | row = tint_pattern.to_bytes(1, 'big') * ((self.width + 7) // 8)
155 | for j in range(self.height):
156 | self._send_data(row)
157 | self._delay_ms(2)
158 |
159 | # TODO An API that takes a two bits per pixel image input would be good.
160 |
161 | def display_frame(self):
162 | self.display_frames(None, None)
163 |
164 | def display_frames(self, frame_buffer_black, frame_buffer_red):
165 | # TODO: assert buffer lengths and _send_data all of it w/o loop?
166 | if frame_buffer_black:
167 | self._send_command(DATA_START_TRANSMISSION_1)
168 | self._delay_ms(2)
169 | for i in range(0, self.width * self.height / 8):
170 | self._send_data(frame_buffer_black[i])
171 | self._delay_ms(2)
172 | if frame_buffer_red:
173 | self._send_command(DATA_START_TRANSMISSION_2)
174 | self._delay_ms(2)
175 | for i in range(0, self.width * self.height / 8):
176 | self._send_data(frame_buffer_red[i])
177 | self._delay_ms(2)
178 |
179 | # Observation: On some EPDs a display refresh won't do anything
180 | # unless two buffers have been written.
181 | self._send_command(DISPLAY_REFRESH)
182 | self.wait_until_idle()
183 |
184 | # after this, call epd.init() to awaken the module
185 | def sleep(self):
186 | self._send_command(VCOM_AND_DATA_INTERVAL_SETTING)
187 | self._send_data(0x37)
188 | self._send_command(VCM_DC_SETTING_REGISTER) #to solve Vcom drop
189 | self._send_data(0x00)
190 | self._send_command(POWER_SETTING) #power setting
191 | self._send_data(0x02) #gate switch to external
192 | self._send_data(b'\x00\x00\x00')
193 | self._wait_until_idle()
194 | self._send_command(POWER_OFF) #power off
195 |
196 | # TODO: pixel and drawing functions should be put in a shared class.
197 | # such as MonoBitmap... the only actual relevance to us is set_absolute_pixel
198 | # which describes how the bits are set per byte and that a color pixel is
199 | # a 0 bit (1 transparent) and a black pixel is a 1 bit (0 white) in their
200 | # respective frame buffers. the color buffer is added after the b&w is drawn.
201 |
202 | def _set_absolute_pixel(self, frame_buffer, x: int, y: int, tinted: bool):
203 | """Set a pixel black or tinted in a bytearray frame buffer."""
204 | if (x < 0 or x >= self.width or y < 0 or y >= self.height):
205 | raise ValueError('x %d, y %d out of range' % (x, y))
206 | if tinted:
207 | frame_buffer[(x + y * self.width) / 8] &= ~(0x80 >> (x % 8))
208 | else:
209 | frame_buffer[(x + y * self.width) / 8] |= 0x80 >> (x % 8)
210 |
211 | set_pixel = _set_absolute_pixel # I got rid of the rotation code.
212 |
213 | # TODO(gps): left off porting here.
214 |
215 | def draw_line(self, frame_buffer, x0, y0, x1, y1, tinted):
216 | # Bresenham algorithm
217 | dx = abs(x1 - x0)
218 | sx = 1 if x0 < x1 else -1
219 | dy = -abs(y1 - y0)
220 | sy = 1 if y0 < y1 else -1
221 | err = dx + dy
222 | while((x0 != x1) and (y0 != y1)):
223 | self.set_pixel(frame_buffer, x0, y0 , tinted)
224 | if (2 * err >= dy):
225 | err += dy
226 | x0 += sx
227 | if (2 * err <= dx):
228 | err += dx
229 | y0 += sy
230 |
231 | def draw_horizontal_line(self, frame_buffer, x, y, width, tinted):
232 | for i in range(x, x + width):
233 | self.set_pixel(frame_buffer, i, y, tinted)
234 |
235 | def draw_vertical_line(self, frame_buffer, x, y, height, tinted):
236 | for i in range(y, y + height):
237 | self.set_pixel(frame_buffer, x, i, tinted)
238 |
239 | def draw_rectangle(self, frame_buffer, x0, y0, x1, y1, tinted):
240 | min_x = x0 if x1 > x0 else x1
241 | max_x = x1 if x1 > x0 else x0
242 | min_y = y0 if y1 > y0 else y1
243 | max_y = y1 if y1 > y0 else y0
244 | self.draw_horizontal_line(frame_buffer, min_x, min_y, max_x - min_x + 1, tinted)
245 | self.draw_horizontal_line(frame_buffer, min_x, max_y, max_x - min_x + 1, tinted)
246 | self.draw_vertical_line(frame_buffer, min_x, min_y, max_y - min_y + 1, tinted)
247 | self.draw_vertical_line(frame_buffer, max_x, min_y, max_y - min_y + 1, tinted)
248 |
249 | def draw_filled_rectangle(self, frame_buffer, x0, y0, x1, y1, tinted):
250 | min_x = x0 if x1 > x0 else x1
251 | max_x = x1 if x1 > x0 else x0
252 | min_y = y0 if y1 > y0 else y1
253 | max_y = y1 if y1 > y0 else y0
254 | for i in range(min_x, max_x + 1):
255 | self.draw_vertical_line(frame_buffer, i, min_y, max_y - min_y + 1, tinted)
256 |
257 | def draw_circle(self, frame_buffer, x, y, radius, tinted):
258 | # Bresenham algorithm
259 | x_pos = -radius
260 | y_pos = 0
261 | err = 2 - 2 * radius
262 | if (x >= self.width or y >= self.height):
263 | return
264 | while True:
265 | self.set_pixel(frame_buffer, x - x_pos, y + y_pos, tinted)
266 | self.set_pixel(frame_buffer, x + x_pos, y + y_pos, tinted)
267 | self.set_pixel(frame_buffer, x + x_pos, y - y_pos, tinted)
268 | self.set_pixel(frame_buffer, x - x_pos, y - y_pos, tinted)
269 | e2 = err
270 | if (e2 <= y_pos):
271 | y_pos += 1
272 | err += y_pos * 2 + 1
273 | if(-x_pos == y_pos and e2 <= x_pos):
274 | e2 = 0
275 | if (e2 > x_pos):
276 | x_pos += 1
277 | err += x_pos * 2 + 1
278 | if x_pos > 0:
279 | break
280 |
281 | def draw_filled_circle(self, frame_buffer, x, y, radius, tinted):
282 | # Bresenham algorithm
283 | x_pos = -radius
284 | y_pos = 0
285 | err = 2 - 2 * radius
286 | if (x >= self.width or y >= self.height):
287 | return
288 | while True:
289 | self.set_pixel(frame_buffer, x - x_pos, y + y_pos, tinted)
290 | self.set_pixel(frame_buffer, x + x_pos, y + y_pos, tinted)
291 | self.set_pixel(frame_buffer, x + x_pos, y - y_pos, tinted)
292 | self.set_pixel(frame_buffer, x - x_pos, y - y_pos, tinted)
293 | self.draw_horizontal_line(frame_buffer, x + x_pos, y + y_pos, 2 * (-x_pos) + 1, tinted)
294 | self.draw_horizontal_line(frame_buffer, x + x_pos, y - y_pos, 2 * (-x_pos) + 1, tinted)
295 | e2 = err
296 | if (e2 <= y_pos):
297 | y_pos += 1
298 | err += y_pos * 2 + 1
299 | if(-x_pos == y_pos and e2 <= x_pos):
300 | e2 = 0
301 | if (e2 > x_pos):
302 | x_pos += 1
303 | err += x_pos * 2 + 1
304 | if x_pos > 0:
305 | break
306 |
--------------------------------------------------------------------------------
/third_party/waveshare/epd2in13.py:
--------------------------------------------------------------------------------
1 | # Ported to CircuitPython by Gregory P. Smith
2 | # The Waveshare EPD 2.13" display uses a protocol compatible with the 2.9".
3 | # So this file now just wraps that.
4 |
5 | ##
6 | # @filename : epd2in13.py
7 | # @brief : Implements for e-paper library
8 | # @author : Yehui from Waveshare
9 | #
10 | # Copyright (C) Waveshare September 9 2017
11 | #
12 | # Permission is hereby granted, free of charge, to any person obtaining a copy
13 | # of this software and associated documnetation files (the "Software"), to deal
14 | # in the Software without restriction, including without limitation the rights
15 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 | # copies of the Software, and to permit persons to whom the Software is
17 | # furished to do so, subject to the following conditions:
18 | #
19 | # The above copyright notice and this permission notice shall be included in
20 | # all copies or substantial portions of the Software.
21 | #
22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28 | # THE SOFTWARE.
29 | #
30 | from . import epd2in9
31 |
32 | class EPD(epd2in9.EPD):
33 | width = 128 # physically 122, logically 128
34 | height = 250
35 |
36 | # TODO convert to raw bytes literals to save space / mem / import time
37 | lut_full_update = bytes((
38 | 0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
40 | 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
41 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
42 | ))
43 |
44 | lut_partial_update = bytes((
45 | 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
47 | 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
49 | ))
50 |
--------------------------------------------------------------------------------
/third_party/waveshare/epd2in7.py:
--------------------------------------------------------------------------------
1 | # ported to CircuitPython 3.0.0-beta0 by Gregory P. Smith
2 |
3 | ##
4 | # @filename : epd2in7.py
5 | # @brief : Implements for e-paper library
6 | # @author : Yehui from Waveshare
7 | #
8 | # Copyright (C) Waveshare July 31 2017
9 | #
10 | # Permission is hereby granted, free of charge, to any person obtaining a copy
11 | # of this software and associated documnetation files (the "Software"), to deal
12 | # in the Software without restriction, including without limitation the rights
13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | # copies of the Software, and to permit persons to whom the Software is
15 | # furished to do so, subject to the following conditions:
16 | #
17 | # The above copyright notice and this permission notice shall be included in
18 | # all copies or substantial portions of the Software.
19 | #
20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 | # THE SOFTWARE.
27 | #
28 |
29 | import time
30 |
31 | from . import epdif
32 |
33 | # EPD2IN7 commands
34 | PANEL_SETTING = 0x00
35 | POWER_SETTING = 0x01
36 | POWER_OFF = 0x02
37 | POWER_OFF_SEQUENCE_SETTING = 0x03
38 | POWER_ON = 0x04
39 | POWER_ON_MEASURE = 0x05
40 | BOOSTER_SOFT_START = 0x06
41 | DEEP_SLEEP = 0x07
42 | DATA_START_TRANSMISSION_1 = 0x10 # NOTE: B&W data according to datasheet
43 | DATA_STOP = 0x11
44 | DISPLAY_REFRESH = 0x12
45 | DATA_START_TRANSMISSION_2 = 0x13 # NOTE: Red Data according to datasheet
46 | PARTIAL_DATA_START_TRANSMISSION_1 = 0x14
47 | PARTIAL_DATA_START_TRANSMISSION_2 = 0x15
48 | PARTIAL_DISPLAY_REFRESH = 0x16
49 | LUT_FOR_VCOM = 0x20
50 | LUT_WHITE_TO_WHITE = 0x21
51 | LUT_BLACK_TO_WHITE = 0x22
52 | LUT_WHITE_TO_BLACK = 0x23
53 | LUT_BLACK_TO_BLACK = 0x24
54 | PLL_CONTROL = 0x30
55 | TEMPERATURE_SENSOR_COMMAND = 0x40
56 | TEMPERATURE_SENSOR_CALIBRATION = 0x41
57 | TEMPERATURE_SENSOR_WRITE = 0x42
58 | TEMPERATURE_SENSOR_READ = 0x43
59 | VCOM_AND_DATA_INTERVAL_SETTING = 0x50
60 | LOW_POWER_DETECTION = 0x51
61 | TCON_SETTING = 0x60
62 | TCON_RESOLUTION = 0x61
63 | SOURCE_AND_GATE_START_SETTING = 0x62
64 | GET_STATUS = 0x71
65 | AUTO_MEASURE_VCOM = 0x80
66 | VCOM_VALUE = 0x81
67 | VCM_DC_SETTING_REGISTER = 0x82
68 | PROGRAM_MODE = 0xA0
69 | ACTIVE_PROGRAM = 0xA1
70 | READ_OTP_DATA = 0xA2
71 |
72 | class EPD:
73 | width = 176
74 | height = 264
75 |
76 | def __init__(self):
77 | self.reset_pin = None
78 | self.dc_pin = None
79 | self.busy_pin = None
80 |
81 | # TODO convert to raw bytes literals to save space / mem / import time
82 | lut_vcom_dc = bytes((
83 | 0x00, 0x00,
84 | 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x05,
85 | 0x00, 0x32, 0x32, 0x00, 0x00, 0x02,
86 | 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x05,
87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91 | ))
92 |
93 | # R21H
94 | lut_ww = bytes((
95 | 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05,
96 | 0x60, 0x32, 0x32, 0x00, 0x00, 0x02,
97 | 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05,
98 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
102 | ))
103 |
104 | # R22H r
105 | lut_bw = bytes((
106 | 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05,
107 | 0x60, 0x32, 0x32, 0x00, 0x00, 0x02,
108 | 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05,
109 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
111 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
113 | ))
114 |
115 | # R24H b
116 | lut_bb = bytes((
117 | 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05,
118 | 0x60, 0x32, 0x32, 0x00, 0x00, 0x02,
119 | 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05,
120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
124 | ))
125 |
126 | # R23H w
127 | lut_wb = bytes((
128 | 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05,
129 | 0x60, 0x32, 0x32, 0x00, 0x00, 0x02,
130 | 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05,
131 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
132 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
133 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
134 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
135 | ))
136 |
137 |
138 | def delay_ms(self, ms):
139 | time.sleep(ms / 1000.)
140 |
141 | def send_command(self, command):
142 | self.dc_pin.value = 0
143 | epdif.spi_transfer(command.to_bytes(1, 'big'))
144 |
145 | def send_data(self, data):
146 | self.dc_pin.value = 1
147 | if isinstance(data, int):
148 | epdif.spi_transfer(data.to_bytes(1, 'big'))
149 | else:
150 | # The EPD appears to want CS to cycle between every byte
151 | # so we loop doing one byte transfers. Not efficient, but
152 | # it makes it work.
153 | for i in range(len(data)):
154 | epdif.spi_transfer(data[i:i+1])
155 |
156 | @property
157 | def fb_bytes(self):
158 | return self.width * self.height // 8
159 |
160 | def init(self):
161 | try:
162 | epdif.epd_io_bus_init()
163 | except RuntimeError:
164 | pass # it avoids global io bus reinitialization. good.
165 | self.reset_pin = epdif.RST_PIN
166 | self.dc_pin = epdif.DC_PIN
167 | self.busy_pin = epdif.BUSY_PIN
168 | # EPD hardware init start
169 | self.reset()
170 | self.send_command(POWER_SETTING)
171 | self.send_data(0x03) # VDS_EN, VDG_EN
172 | self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0]
173 | self.send_data(0x2b) # VDH
174 | self.send_data(0x2b) # VDL
175 | self.send_data(0x09) # VDHR
176 | self.send_command(BOOSTER_SOFT_START)
177 | self.send_data(0x07)
178 | self.send_data(0x07)
179 | self.send_data(0x17)
180 | # Power optimization
181 | self.send_command(0xF8)
182 | self.send_data(0x60)
183 | self.send_data(0xA5)
184 | # Power optimization
185 | self.send_command(0xF8)
186 | self.send_data(0x89)
187 | self.send_data(0xA5)
188 | # Power optimization
189 | self.send_command(0xF8)
190 | self.send_data(0x90)
191 | self.send_data(0x00)
192 | # Power optimization
193 | self.send_command(0xF8)
194 | self.send_data(0x93)
195 | self.send_data(0x2A)
196 | # Power optimization
197 | self.send_command(0xF8)
198 | self.send_data(0xA0)
199 | self.send_data(0xA5)
200 | # Power optimization
201 | self.send_command(0xF8)
202 | self.send_data(0xA1)
203 | self.send_data(0x00)
204 | # Power optimization
205 | self.send_command(0xF8)
206 | self.send_data(0x73)
207 | self.send_data(0x41)
208 | self.send_command(PARTIAL_DISPLAY_REFRESH)
209 | self.send_data(0x00)
210 | self.send_command(POWER_ON)
211 | self.wait_until_idle()
212 |
213 | self.send_command(PANEL_SETTING)
214 | self.send_data(0xAF) # KW-BF KWR-AF BWROTP 0f
215 | self.send_command(PLL_CONTROL)
216 | self.send_data(0x3A) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ
217 | self.send_command(VCM_DC_SETTING_REGISTER)
218 | self.send_data(0x12)
219 | self.delay_ms(2)
220 | self.set_lut()
221 | # EPD hardware init end
222 |
223 | def wait_until_idle(self):
224 | while self.busy_pin.value == 0: # 0: busy, 1: idle
225 | self.delay_ms(10)
226 |
227 | def reset(self):
228 | self.reset_pin.value = 0 # module reset
229 | self.delay_ms(200)
230 | self.reset_pin.value = 1
231 | self.delay_ms(200)
232 |
233 | def set_lut(self):
234 | assert len(self.lut_vcom_dc) == 44
235 | self.send_command(LUT_FOR_VCOM) # vcom
236 | self.send_data(self.lut_vcom_dc)
237 | assert len(self.lut_ww) == 42
238 | self.send_command(LUT_WHITE_TO_WHITE) # ww --
239 | self.send_data(self.lut_ww)
240 | assert len(self.lut_bw) == 42
241 | self.send_command(LUT_BLACK_TO_WHITE) # bw r
242 | self.send_data(self.lut_bw)
243 | assert len(self.lut_bb) == 42
244 | self.send_command(LUT_WHITE_TO_BLACK) # wb w
245 | self.send_data(self.lut_bb)
246 | assert len(self.lut_wb) == 42
247 | self.send_command(LUT_BLACK_TO_BLACK) # bb b
248 | self.send_data(self.lut_wb)
249 |
250 | def clear_frame_memory(self, pattern=0xff):
251 | """Fill the frame memory with a pattern byte. Does not call update."""
252 | self.send_command(DATA_START_TRANSMISSION_2)
253 | self.delay_ms(2)
254 | row = pattern.to_bytes(1, 'big') * ((self.width + 7) // 8)
255 | for j in range(self.height):
256 | self.send_data(row)
257 |
258 | def display_frame(self):
259 | # TODO Determine if the 2.7" display can do double buffering.
260 | self.send_command(DISPLAY_REFRESH)
261 | self.wait_until_idle()
262 |
263 | def display_frame_buf(self, frame_buffer, fast_ghosting=None):
264 | """fast_ghosting ignored on the 2.7" display; it is always slow."""
265 | fb_bytes = self.width * self.height // 8
266 | assert len(frame_buffer) == fb_bytes
267 |
268 | # This seems to do nothing on my EPD.
269 | #self.send_command(DATA_START_TRANSMISSION_1)
270 | #self.delay_ms(2)
271 | #for i in range(fb_bytes):
272 | # self.send_data(b'\xff')
273 | #self.delay_ms(2)
274 |
275 | # This is all that is needed on my display.
276 | # DATA_START_TRANSMISSION_1 seems to have no effect
277 | # despite a data sheet documenting it and mentioning 2
278 | # as being for the "Red" channel (bad datasheet...).
279 | self.send_command(DATA_START_TRANSMISSION_2)
280 | self.delay_ms(2)
281 | self.send_data(frame_buffer)
282 | self.delay_ms(2)
283 |
284 | self.display_frame()
285 |
286 | def display_bitmap(self, bitmap, fast_ghosting=False):
287 | """Render a MonoBitmap onto the display.
288 |
289 | fast_ghosting ignored on the 2.7" display; it is always slow.
290 | """
291 | self.display_frame_buf(bitmap.bit_buf, fast_ghosting=fast_ghosting)
292 |
293 | # After this command is transmitted, the chip would enter the deep-sleep
294 | # mode to save power. The deep sleep mode would return to standby by
295 | # hardware reset. The only one parameter is a check code, the command would
296 | # be executed if check code = 0xA5.
297 | # Use self.reset() to awaken and use self.init() to initialize.
298 | def sleep(self):
299 | self.send_command(DEEP_SLEEP)
300 | self.delay_ms(2)
301 | self.send_data(0xa5)
302 |
303 | ### END OF FILE ###
304 |
--------------------------------------------------------------------------------
/third_party/waveshare/epd2in9.py:
--------------------------------------------------------------------------------
1 | # Ported to CircuitPython 3.0 by Gregory P. Smith
2 |
3 | ##
4 | # @filename : epd2in9.py
5 | # @brief : Implements for e-paper library
6 | # @author : Yehui from Waveshare
7 | #
8 | # Copyright (C) Waveshare September 9 2017
9 | #
10 | # Permission is hereby granted, free of charge, to any person obtaining a copy
11 | # of this software and associated documnetation files (the "Software"), to deal
12 | # in the Software without restriction, including without limitation the rights
13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | # copies of the Software, and to permit persons to whom the Software is
15 | # furished to do so, subject to the following conditions:
16 | #
17 | # The above copyright notice and this permission notice shall be included in
18 | # all copies or substantial portions of the Software.
19 | #
20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 | # THE SOFTWARE.
27 | #
28 |
29 | import time
30 |
31 | from . import epdif
32 |
33 | # EPD2IN9 commands
34 | DRIVER_OUTPUT_CONTROL = 0x01
35 | BOOSTER_SOFT_START_CONTROL = 0x0C
36 | GATE_SCAN_START_POSITION = 0x0F
37 | DEEP_SLEEP_MODE = 0x10
38 | DATA_ENTRY_MODE_SETTING = 0x11
39 | SW_RESET = 0x12
40 | TEMPERATURE_SENSOR_CONTROL = 0x1A
41 | MASTER_ACTIVATION = 0x20
42 | DISPLAY_UPDATE_CONTROL_1 = 0x21
43 | DISPLAY_UPDATE_CONTROL_2 = 0x22
44 | WRITE_RAM = 0x24
45 | WRITE_VCOM_REGISTER = 0x2C
46 | WRITE_LUT_REGISTER = 0x32
47 | SET_DUMMY_LINE_PERIOD = 0x3A
48 | SET_GATE_TIME = 0x3B
49 | BORDER_WAVEFORM_CONTROL = 0x3C
50 | SET_RAM_X_ADDRESS_START_END_POSITION = 0x44
51 | SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45
52 | SET_RAM_X_ADDRESS_COUNTER = 0x4E
53 | SET_RAM_Y_ADDRESS_COUNTER = 0x4F
54 | TERMINATE_FRAME_READ_WRITE = 0xFF
55 |
56 |
57 | class EPD:
58 | width = 128
59 | height = 296
60 |
61 | def __init__(self):
62 | assert not (self.width & 3), "width must be a multiple of 8"
63 | self.reset_pin = None
64 | self.dc_pin = None
65 | self.busy_pin = None
66 | self.lut = self.lut_full_update
67 |
68 | # TODO convert to raw bytes literals to save space / mem / import time
69 | lut_full_update = bytes((
70 | 0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22,
71 | 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88,
72 | 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51,
73 | 0x35, 0x51, 0x51, 0x19, 0x01, 0x00
74 | ))
75 |
76 | lut_partial_update = bytes((
77 | 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00,
78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
79 | 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12,
80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
81 | ))
82 |
83 | def _delay_ms(self, ms):
84 | time.sleep(ms / 1000.)
85 |
86 | def _send_command(self, command):
87 | self.dc_pin.value = 0
88 | epdif.spi_transfer(command.to_bytes(1, 'big'))
89 |
90 | def _send_data(self, data):
91 | self.dc_pin.value = 1
92 | if isinstance(data, int):
93 | epdif.spi_transfer(data.to_bytes(1, 'big'))
94 | else:
95 | # The EPD needs CS to cycle hi between every byte so we loop doing
96 | # one byte transfers to cause that. Not efficient, but it makes
97 | # it work. Data sheets say needs to be at least a 60ns CS pulse.
98 | for i in range(len(data)):
99 | epdif.spi_transfer(data[i:i+1])
100 |
101 | @property
102 | def fb_bytes(self):
103 | return self.width * self.height // 8
104 |
105 | def init(self, lut=None):
106 | try:
107 | epdif.epd_io_bus_init()
108 | except RuntimeError:
109 | pass # It avoids global io bus reinitialization. Good.
110 | self.reset_pin = epdif.RST_PIN
111 | self.dc_pin = epdif.DC_PIN
112 | self.busy_pin = epdif.BUSY_PIN
113 | # EPD hardware init start
114 | self.lut = lut or self.lut_full_update
115 | self.reset()
116 | self._send_command(DRIVER_OUTPUT_CONTROL)
117 | self._send_data((self.height - 1) & 0xFF)
118 | self._send_data(((self.height - 1) >> 8) & 0xFF)
119 | self._send_data(0x00) # GD = 0 SM = 0 TB = 0
120 | self._send_command(BOOSTER_SOFT_START_CONTROL)
121 | self._send_data(0xD7)
122 | self._send_data(0xD6)
123 | self._send_data(0x9D)
124 | self._send_command(WRITE_VCOM_REGISTER)
125 | self._send_data(0xA8) # VCOM 7C
126 | self._send_command(SET_DUMMY_LINE_PERIOD)
127 | self._send_data(0x1A) # 4 dummy lines per gate
128 | self._send_command(SET_GATE_TIME)
129 | self._send_data(0x08) # 2us per line
130 | self._send_command(DATA_ENTRY_MODE_SETTING)
131 | self._send_data(0x03) # X increment Y increment
132 | self.set_lut(self.lut)
133 | # EPD hardware init end
134 |
135 | def wait_until_idle(self):
136 | while self.busy_pin.value == 1: # 0: idle, 1: busy
137 | self._delay_ms(10)
138 |
139 | ##
140 | # @brief: module reset.
141 | # often used to awaken the module in deep sleep,
142 | ##
143 | def reset(self):
144 | self.reset_pin.value = 0 # module reset
145 | self._delay_ms(200)
146 | self.reset_pin.value = 1
147 | self._delay_ms(200)
148 |
149 | ##
150 | # @brief: set the look-up table register
151 | ##
152 | def set_lut(self, lut=None):
153 | self.lut = lut or self.lut_full_update
154 | assert len(self.lut) == 30 # the length of look-up table is 30 bytes
155 | self._send_command(WRITE_LUT_REGISTER)
156 | self._send_data(self.lut)
157 |
158 | ##
159 | # @brief: put an image to the frame memory.
160 | # this won't update the display.
161 | ##
162 | def set_frame_memory(self, bitmap, x, y):
163 | """Place bitmap at x (multiple of 8), y in the EPD frame buffer.
164 |
165 | bitmap: A MonoBitmap instance; must be a multiple of 8 wide.
166 | """
167 | if x & 0x7 or bitmap.width & 0x7 or x < 0 or y < 0:
168 | raise ValueError('bad x, y, or width: %d, %d, %d'
169 | % (x, y, bitmap.width))
170 | image_width = bitmap.width
171 | image_height = bitmap.height
172 | if (x + image_width >= self.width):
173 | x_end = self.width - 1
174 | else:
175 | x_end = x + image_width - 1
176 | if (y + image_height >= self.height):
177 | y_end = self.height - 1
178 | else:
179 | y_end = y + image_height - 1
180 | self._set_memory_area(x, y, x_end, y_end)
181 | for j in range(y, y_end + 1):
182 | # The 2.13" display only likes receiving one row of data per WRITE_RAM.
183 | # At a guess: Internally it may be bit based and does this to avoid
184 | # implementing skipping partial end of row bytes given the non
185 | # multiple of 8 width resolution?
186 | self._set_memory_pointer(x, j)
187 | offset = j * self.width // 8
188 | self._send_command(WRITE_RAM)
189 | self._send_data(bitmap.bit_buf[offset+x:offset+(x_end//8)+1])
190 |
191 | def clear_frame_memory(self, pattern=0xff):
192 | """Fill the frame memory with a pattern byte. Does not call update."""
193 | self._set_memory_area(0, 0, self.width - 1, self.height - 1)
194 | self._set_memory_pointer(0, 0)
195 | row = pattern.to_bytes(1, 'big') * ((self.width + 7) // 8)
196 | for j in range(self.height):
197 | # Some displays only accept one row of data per WRITE_RAM.
198 | self._set_memory_pointer(0, j)
199 | self._send_command(WRITE_RAM)
200 | self._send_data(row)
201 |
202 | ##
203 | # @brief: update the display
204 | # there are 2 memory areas embedded in the e-paper display
205 | # but once this function is called,
206 | # the the next action of SetFrameMemory or ClearFrame will
207 | # set the other memory area.
208 | ##
209 | def display_frame(self):
210 | """Calling this will swap the display for the other buffer."""
211 | self._send_command(DISPLAY_UPDATE_CONTROL_2)
212 | self._send_data(0xC4)
213 | self._send_command(MASTER_ACTIVATION)
214 | self._send_command(TERMINATE_FRAME_READ_WRITE)
215 | self.wait_until_idle()
216 |
217 | def display_frame_buf(self, frame_buffer, fast_ghosting=False):
218 | assert len(frame_buffer) == self.fb_bytes
219 | for _ in range(2):
220 | self._set_memory_area(0, 0, self.width-1, self.height-1)
221 | for j in range(0, self.height):
222 | # Some displays only accept one row of data per WRITE_RAM.
223 | self._set_memory_pointer(0, j)
224 | offset = j * self.width // 8
225 | self._send_command(WRITE_RAM)
226 | self._send_data(frame_buffer[offset:offset + (self.width//8) + 1])
227 | self.display_frame()
228 | if fast_ghosting:
229 | break
230 |
231 | def display_bitmap(self, bitmap, fast_ghosting=False):
232 | """Render a MonoBitmap onto the display.
233 |
234 | Args:
235 | bitmap: A MonoBitmap instance
236 | fast_ghosting: If true the display update is twice as fast by only
237 | refreshing once; this can leave a ghost of the previous contents.
238 | """
239 | # TODO: add partial update support.
240 | # if bitmap size is full frame size and x/y offsets are 0:
241 | # epd.init(epd.lut_full_update)
242 | # else:
243 | # epd.init(epd.lut_partial_update)
244 |
245 | self.set_frame_memory(bitmap, 0, 0)
246 | self.display_frame()
247 | if not fast_ghosting:
248 | self.set_frame_memory(bitmap, 0, 0)
249 | self.display_frame()
250 |
251 | ##
252 | # @brief: specify the memory area for data R/W
253 | ##
254 | def _set_memory_area(self, x_start, y_start, x_end, y_end):
255 | if x_start & 0x7:
256 | raise ValueError('x must be a multiple of 8 (%d)' % (x_start,))
257 | self._send_command(SET_RAM_X_ADDRESS_START_END_POSITION)
258 | self._send_data((x_start >> 3) & 0xFF)
259 | self._send_data((x_end >> 3) & 0xFF)
260 | self._send_command(SET_RAM_Y_ADDRESS_START_END_POSITION)
261 | self._send_data(y_start & 0xFF)
262 | self._send_data((y_start >> 8) & 0xFF)
263 | self._send_data(y_end & 0xFF)
264 | self._send_data((y_end >> 8) & 0xFF)
265 |
266 | ##
267 | # @brief: specify the start point for data R/W
268 | ##
269 | def _set_memory_pointer(self, x, y):
270 | if x & 0x7:
271 | raise ValueError('x must be a multiple of 8')
272 | self._send_command(SET_RAM_X_ADDRESS_COUNTER)
273 | self._send_data((x >> 3) & 0xFF)
274 | self._send_command(SET_RAM_Y_ADDRESS_COUNTER)
275 | self._send_data(y & 0xFF)
276 | self._send_data((y >> 8) & 0xFF)
277 | self.wait_until_idle()
278 |
279 | ##
280 | # @brief: After this command is transmitted, the chip would enter the
281 | # deep-sleep mode to save power.
282 | # The deep sleep mode would return to standby by hardware reset.
283 | # You can use reset() to awaken or init() to initialize
284 | ##
285 | def sleep(self):
286 | self._send_command(DEEP_SLEEP_MODE)
287 | self.wait_until_idle()
288 |
289 |
--------------------------------------------------------------------------------
/third_party/waveshare/epdif.py:
--------------------------------------------------------------------------------
1 | # ported to CircuitPython 3.0.0-beta0 by Gregory P. Smith
2 |
3 | ##
4 | # @filename : epdif.py
5 | # @brief : EPD hardware interface implements (GPIO, SPI)
6 | # @author : Yehui from Waveshare
7 | #
8 | # Copyright (C) Waveshare July 4 2017
9 | #
10 | # Permission is hereby granted, free of charge, to any person obtaining a copy
11 | # of this software and associated documnetation files (the "Software"), to deal
12 | # in the Software without restriction, including without limitation the rights
13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | # copies of the Software, and to permit persons to whom the Software is
15 | # furished to do so, subject to the following conditions:
16 | #
17 | # The above copyright notice and this permission notice shall be included in
18 | # all copies or substantial portions of the Software.
19 | #
20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | # FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | # LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 | # THE SOFTWARE.
27 | #
28 |
29 | import adafruit_bus_device.spi_device
30 | import board
31 | import digitalio
32 |
33 | # Pin definition as hooked up on my Metro M4 (reassigned to instances in init)
34 | # These pins are also present on the ItsyBitsy M4
35 | RST_PIN = board.D11
36 | DC_PIN = board.D9
37 | CS_PIN = board.D10
38 | BUSY_PIN = board.D7
39 |
40 | #_SPI_MOSI = board.MOSI
41 | #_SPI_CLK = board.SCK
42 | _SPI_MOSI = board.A2
43 | _SPI_CLK = board.A3
44 | _SPI_BUS = None
45 | _init = False
46 |
47 |
48 | def spi_transfer(data):
49 | with _SPI_BUS as device:
50 | device.write(data)
51 |
52 | def epd_io_bus_init():
53 | global _init
54 | if _init:
55 | raise RuntimeError("epd_io_bus_init() called twice")
56 | _init = True
57 | global RST_PIN, DC_PIN, CS_PIN, BUSY_PIN
58 | DInOut = digitalio.DigitalInOut
59 | OUTPUT = digitalio.Direction.OUTPUT
60 | INPUT = digitalio.Direction.INPUT
61 | RST_PIN = DInOut(RST_PIN)
62 | RST_PIN.direction = OUTPUT
63 | DC_PIN = DInOut(DC_PIN)
64 | DC_PIN.direction = OUTPUT
65 | CS_PIN = DInOut(CS_PIN)
66 | CS_PIN.direction = OUTPUT
67 | BUSY_PIN = DInOut(BUSY_PIN)
68 | BUSY_PIN.direction = INPUT
69 | global _SPI_BUS
70 | # bus vs bitbang isn't really important for slow displays, detecting
71 | # when to use one vs the other is overkill...
72 | if (_SPI_CLK == getattr(board, 'SCK', None) and
73 | _SPI_MOSI == getattr(board, 'MOSI', None)):
74 | import busio as io_module
75 | else:
76 | import bitbangio as io_module
77 | _SPI_BUS = adafruit_bus_device.spi_device.SPIDevice(
78 | io_module.SPI(_SPI_CLK, _SPI_MOSI), CS_PIN,
79 | baudrate=2000000)
80 |
81 | ### END OF FILE ###
82 |
--------------------------------------------------------------------------------
/update-device.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #-------------------------------------------------------------------------
3 | # Author: Gregory P. Smith (@gpshead)
4 | #
5 | # Copyright 2018 Google LLC
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #-------------------------------------------------------------------------
19 |
20 | # A convenient way to push code changes to a Circuitpython device.
21 | # You may not need the depending on your Linux distro setup. I'm
22 | # using a minimal Debian install on a compute stick rather than a
23 | # desktop distro configured to auto-mount USB devices.
24 | #
25 | # This assumes you have something like this in your /etc/fstab:
26 | # /dev/sda1 /mnt vfat user,noauto 0 0
27 | #
28 | mount /dev/sda1
29 | rsync --checksum --inplace --exclude '*README*' --exclude '*LICENSE' \
30 | -r *.py asm_thumb third_party /mnt
31 | sync
32 | umount /mnt
33 |
--------------------------------------------------------------------------------