├── GHOSTING.md ├── IMG_2340.JPG ├── LICENSE ├── LiberationSerif-Regular44 ├── LiberationSerif-Regular44.py ├── README.md ├── SIL Open Font License.txt ├── UML_DIAGRAMS.md ├── aphrodite_2_7.xbm ├── cat_2_7.xbm ├── classes_epaper.png ├── clock.py ├── courier25 ├── courier25.py ├── ea_2_7.xbm ├── epaper.py ├── epd.py ├── epd_clock ├── IMG_3139.JPG ├── IMG_3140.JPG ├── README.md ├── arial12.py ├── arial15.py ├── newclock.py └── watch.mp4 ├── epdpart.py ├── flash.py ├── ghosting.jpg ├── micropower.py ├── packages_epaper.png ├── panel.py ├── pyboard_barometer.JPG ├── raspberry_pi_clock.py ├── saturn_2_7.xbm ├── text_image_2_7.xbm └── venus_2_7.xbm /GHOSTING.md: -------------------------------------------------------------------------------- 1 | # Ghosting 2 | 3 | This only appears in FAST mode, after calling ``refresh()``. The following is an example of a 4 | display from the Pyboard driver in normal mode: no ghosting visible. 5 | 6 | ![Normal display](pyboard_barometer.JPG) 7 | 8 | This shows the outcome of clock.py on the Pyboard, and raspberry_pi_clock.py on the Pi. In both 9 | cases partial updates are used and ghosting is apparent. It demonstrates that the Pyboard driver 10 | is no worse in this repect than the RePaper reference driver. The two images are from a single 11 | photographic exposure of two displays mounted side by side, so the improved contrast of the Pyboard 12 | image was genuine. However it may simply be a consequence of manufacturing tolerances. 13 | 14 | ![Ghosting](ghosting.jpg) 15 | 16 | Ghosting can be always be cleared by issuing show() or exchange(). 17 | 18 | To some degree ghosting can be reduced by design: unlike the official Pi demos, my clock demos aim 19 | to show it at its worst. A prettier clock would result if the second hand and digits were 20 | eliminated and exchange() issued every few minutes. 21 | -------------------------------------------------------------------------------- /IMG_2340.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/IMG_2340.JPG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LiberationSerif-Regular44: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/LiberationSerif-Regular44 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-epaper 2 | 3 | ## 17th Jan 2021 Warning 4 | 5 | Both the Embedded Artists and the Adafruit modules now appear to be obsolete. 6 | New users are advised to go to 7 | [micropython-nano-gui](https://github.com/peterhinch/micropython-nano-gui) 8 | which now supports two ePaper displays. Drivers for Waveshare displays may also 9 | be found [here](https://github.com/mcauser/micropython-waveshare-epaper). 10 | 11 | # The driver 12 | 13 | The driver enables the Pyboard to access a 2.7 inch e-paper display from 14 | [Embedded Artists](http://www.embeddedartists.com/products/displays/lcd_27_epaper.php). 15 | The onboard Flash memory is optionally supported. 16 | 17 | The display can also be bought from Mouser Electronics (a company with worldwide depots) e.g. 18 | [MouserUK](http://www.mouser.co.uk/ProductDetail/Embedded-Artists/EA-LCD-009/?qs=sGAEpiMZZMt7dcPGmvnkBrNVf0ehHpp1LPMnQSPTe1M%3d). 19 | Also available in Europe from [Cool Components](http://www.coolcomponents.co.uk). 20 | 21 | Support for the Adafruit [module](https://learn.adafruit.com/repaper-eink-development-board) 22 | is provided but limited to the 2.7 inch display module; its onboard flash memory is not currently 23 | supported. 24 | 25 | The driver works on the Pyboard Lite but its limited RAM means that the board's use is probably 26 | best confined to simple applications. The driver is fairly demanding on RAM especially in FAST mode 27 | or modes using the Embedded Artists (EA) flash memory. A test of the FAST mode clock demo indicated 28 | that about 50% of the Lite's memory was used. 29 | 30 | ![Sample](IMG_2340.JPG) 31 | 32 | An open source Python utility is used to create binary and python fonts from 33 | `ttf` or `otf` sources. Python fonts may be frozen as bytecode to conserve RAM, 34 | binary font files are accessed from a mounted drive. 35 | 36 | # Introduction 37 | 38 | E-paper displays have high contrast and the ability to retain an image with the power 39 | disconnected. They also have very low power consumption when in use. These displays offer 40 | monochrome only, with no grey scale: pixels are either on or off. Further the display refresh takes 41 | time. In normal mode the minimum update time defined by explicit delays is 1.6 seconds. With the 42 | current driver it takes 3.5s. This after some efforts at optimisation. A time closer to 1.6s might 43 | be achieved by writing key methods in Assembler but I have no plans to do this. It is considerably 44 | faster than the Arduino code and as fast (in this mode) as the best alternative board I have seen. 45 | 46 | The Embedded Artists (EA) rev D display includes an LM75 temperature sensor and a 4MB flash memory 47 | chip. The driver provides access to the current temperature. The display driver does not use the 48 | flash chip: the current image is buffered in RAM. An option is provided to mount the flash device 49 | in the Pyboard's filesystem enabling it to be used to store data such as images and fonts. This is 50 | the `use_flash` Display constructor argument. Setting this `False` will save over 8K of RAM. The 51 | primary application for the onboard flash is in ultra low power applications where the consumption 52 | of an SD card would be excessive. 53 | 54 | An issue with the EA module is that the Flash memory and the display module use the SPI bus in 55 | different, incompatible ways. The driver provides a means of arbitration between these devices 56 | discussed below. This is transparent to the user of the Display class. A consequence of this is 57 | that the SPI bus used by the display should not be shared with other devices. 58 | 59 | One application area for e-paper displays is in ultra low power applications. The Pyboard 1.1 in 60 | standby mode consumes about 7μA. To avoid adding to this an external circuit is required to turn 61 | off the power to the display and any other peripherals before entering standby. A way of achieving 62 | this is presented [here](https://github.com/peterhinch/micropython-micropower.git). 63 | 64 | This driver was ported from the RePaper reference designs [here](https://github.com/repaper/gratis.git). 65 | There are two reference drivers, one for resource constrained platforms (Arduino library) and 66 | another for systems with an OS. The latter attempts faster screen writes by employing a double 67 | buffered algorithm. This MicroPython driver supports both modes. By default it uses the resource 68 | constrained "NORMAL" single buffered mode: the downside is slower updates with repeated black-white 69 | screen transitions. 70 | 71 | "FAST" mode is intended for display of (fairly) realtime data. It uses more RAM and also precludes 72 | the concurrent use of the display and its onboard Flash memory. The following instructions largely 73 | refer to NORMAL mode. FAST mode is a superset with features covered in a separate section below. In 74 | this mode the fastest update method causes the display to exhibit some "ghosting" where a trace of 75 | the prior image remains. After much effort to reduce this I'm doubtful whether further progress can 76 | be made without departing from the RePaper reference algorithms. 77 | 78 | # The driver 79 | 80 | This enables the display of simple graphics and/or text in any font. Font files 81 | are created using the `font_to_py.py` [utility](https://github.com/peterhinch/micropython-font-to-py.git). 82 | This PC utility converts industry standard `ttf` or `otf` files to formats 83 | compatible with the driver. The graphics capability may readily be extended by 84 | the user. 85 | 86 | The driver also supports the display of XBM format graphics files, including 87 | the full screen sample images from Embedded Artists. 88 | 89 | # Connecting the display 90 | 91 | ### Embedded Artists hardware 92 | 93 | The display is supplied with a 14 way ribbon cable. The easiest way to connect it to the Pyboard is 94 | to cut this cable in half and wire one half of it (each half is identical) as follows. I fitted the 95 | Pyboard with socket headers and wired the display cable to a 14 way pin header, enabling it to be 96 | plugged in to either side of the Pyboard (the two sides are symmetrical). I have labelled them L 97 | and R indicating the left and right sides of the board as seen from the component side with the USB 98 | connector at the top. 99 | 100 | | display | signal | L | R | Python name | 101 | |:-------:|:----------:|:---:|:---:|:-------------:| 102 | | 1 | GND | GND | GND | | 103 | | 2 | 3V3 | 3V3 | 3V3 | | 104 | | 3 | SCK | Y6 | X6 | (SPI bus) | 105 | | 4 | MOSI | Y8 | X8 | | 106 | | 5 | MISO | Y7 | X7 | | 107 | | 6 | SSEL | Y5 | X5 | Pin_EPD_CS | 108 | | 7 | Busy | X11 | Y11 | Pin_BUSY | 109 | | 8 | Border Ctl | X12 | Y12 | Pin_BORDER | 110 | | 9 | SCL | X9 | Y9 | (I2C bus) | 111 | | 10 | SDA | X10 | Y10 | | 112 | | 11 | CS Flash | Y1 | X1 | Pin_FLASH_CS | 113 | | 12 | Reset | Y2 | X2 | Pin_RESET | 114 | | 13 | Pwr | Y3 | X3 | Pin_PANEL_ON | 115 | | 14 | Discharge | Y4 | X4 | Pin_DISCHARGE | 116 | 117 | The SPI bus is not designed for driving long wires. This driver uses it at upto 10.5MHz so keep 118 | them short! 119 | 120 | Red stripe on cable is pin 1. 121 | 122 | For information this shows the E-paper 14 way 0.1inch pitch connector as viewed looking down on the 123 | pins with the keying cutout to the left: 124 | 125 | | L | R | 126 | |:--:|:--:| 127 | | 1 | 2 | 128 | | 3 | 4 | 129 | | 5 | 6 | 130 | | 7 | 8 | 131 | | 9 | 10 | 132 | | 11 | 12 | 133 | | 13 | 14 | 134 | 135 | ### Adafruit hardware 136 | 137 | The Adafruit module is supplied with a cable: colours below refer to this. 138 | 139 | | Display | signal | L | R | Python name | 140 | |:---------:|:----------:|:---:|:---:|:-------------:| 141 | | 20 black | GND | GND | GND | | 142 | | 1 red | 3V3 | 3V3 | 3V3 | | 143 | | 7 yellow | SCK | Y6 | X6 | (SPI bus) | 144 | | 15 blue | MOSI | Y8 | X8 | | 145 | | 14 purple | MISO | Y7 | X7 | | 146 | | 19 brown | SSEL | Y5 | X5 | Pin_EPD_CS | 147 | | 6 green | Temp | X11 | Y11 | Temperature | 148 | | 13 grey | Border Ctl | X12 | Y12 | Pin_BORDER | 149 | | | (n/c) | X9 | Y9 | | 150 | | 8 orange | Busy | X10 | Y10 | Pin_BUSY | 151 | | 18 orange | CS Flash | Y1 | X1 | Pin_FLASH_CS | 152 | | 10 black | Reset | Y2 | X2 | Pin_RESET | 153 | | 11 red | Pwr | Y3 | X3 | Pin_PANEL_ON | 154 | | 12 white | Discharge | Y4 | X4 | Pin_DISCHARGE | 155 | 156 | Looking at the board oriented display side up and connector on the left, Pin 1 is the top left pin 157 | (the left column are all odd numbered pins) and pin 2 is immediately to its right (right hand 158 | column is all the even pins). There is also small 1 and 2 on the PCB silk screen above the socket 159 | and a 19 and 20 below the socket. 160 | 161 | # Getting started 162 | 163 | Copy the mandatory modules listed below to the Pyboard. Assuming the device is 164 | connected on the 'L' side simply cut and paste this at the REPL. Note that with 165 | all code samples it's best to issue `D` before pasting to reset the 166 | Pyboard: this is because the driver needs to instantiate large buffers. Memory 167 | allocation errors are likely unless RAM is first cleared by a soft reset. Note 168 | that all the following code samples assume EA hardware: if using Adafruit add 169 | `mode=epaper.ADAFRUIT` as a constructor argument. 170 | 171 | ```python 172 | import epaper 173 | a = epaper.Display('L') 174 | a.rect(20, 20, 150, 150, 3) 175 | a.show() 176 | ``` 177 | 178 | To clear the screen and print a message (assuming we are using an SD card): 179 | 180 | ```python 181 | a.clear_screen() 182 | with a.font('/sd/courier25'): 183 | a.puts("Large font\ntext here") 184 | a.show() 185 | ``` 186 | 187 | The above example assumes a binary font file on the SD card. If a Python font module (frozen 188 | or otherwise) were used suitable code would be as follows: 189 | 190 | ```python 191 | import pyb, epaper, courier25 192 | a = epaper.Display(side = 'L') 193 | with a.font(courier25): 194 | a.puts('Text here') 195 | a.show() 196 | ``` 197 | 198 | # Modules 199 | 200 | To employ the driver it is only necessary to import the epaper module and to 201 | instantiate the `Display` class. The driver comprises the following modules. 202 | 203 | Mandatory modules: 204 | * `epaper.py` The user interface to the display and flash memory. 205 | * `epd.py` Low level NORMAL mode driver for the EPD (electrophoretic display). 206 | * `panel.py` Pin definitions for the display. 207 | 208 | Optional modules: 209 | * `epdpart.py` Low level FAST mode driver for the EPD. 210 | * `flash.py` Low level driver for the flash memory. 211 | 212 | Note that the flash drive will need to be formatted before first use: see the 213 | `flash.py` doc below. 214 | 215 | # Files and Utilities 216 | 217 | Sample binary font files: 218 | * `LiberationSerif-Regular44` (Times Roman lookalike). 219 | * `courier25` 25 pixel high terminal font. 220 | 221 | Same fonts in Python source format: 222 | * `LiberationSerif-Regular44.py` 223 | * `courier25.py` 224 | 225 | Sample full screen image files from Embedded Artists. 226 | * `aphrodite_2_7.xbm` 227 | * `cat_2_7.xbm` 228 | * `ea_2_7.xbm` 229 | * `saturn_2_7.xbm` 230 | * `text_image_2_7.xbm` 231 | * `venus_2_7.xbm` 232 | 233 | License for fonts. 234 | * `SIL Open Font License.txt` 235 | 236 | # Module epaper.py 237 | 238 | This is the user interface to the display, the flash memory and the temperature sensor. Display 239 | data is buffered. The procedure for displaying text or graphics is to use the various methods 240 | described below to write text or graphics to the buffer and then to call `show()` to 241 | display the result. The `show()` method is the only one to access the EPD module (although 242 | `clear_screen()` calls `show()`). Others affect the buffer alone. 243 | 244 | The coordinate system has its origin at the top left corner of the display, with integer 245 | X values from 0 to 263 and Y from 0 to 175 (inclusive). 246 | 247 | In general the graphics code prioritises simplicity over efficiency: e-paper displays are far 248 | from fast. But I might get round to improving the speed of font rendering which is particularly 249 | slow when you write a string using a large font (frozen fonts are faster). In the meantime be 250 | patient. Or offer a patch :) 251 | 252 | ## Display class 253 | 254 | ### Constructor 255 | 256 | This has one positional argument: 257 | 1. `side` This must be 'L' or 'R' depending on the side of the Pyboard in use. Default 'L'. This 258 | is based on the wiring notes above. 259 | It has the following keyword only arguments: 260 | 2. `mode` `epaper.NORMAL` or `epaper.FAST` - default normal mode. 261 | 3. `model` `epaper.EMBEDDED_ARTISTS` or `epaper.ADAFRUIT`. Default EA. 262 | 4. `use_flash` Mounts the flash drive as /fc for general use. Default False. N/A in FAST mode. 263 | 5. `up_time` Applies to FAST mode only. See below. 264 | 265 | ### Methods 266 | 267 | `clear_screen(show=True, both=False)` Clears the screen. Argument `show` blanks 268 | the screen buffer and resets the text cursor. If `show` is set it also displays 269 | the result by calling the `show()` method. The `both` arg is ignored in 270 | normal mode. See FAST mode below for its usage. 271 | 272 | `show()` Displays the contents of the screen buffer. 273 | 274 | `line()` Draw a line. Arguments `X0, Y0, X1, Y1, Width, Black`. Defaults: width = 1 pixel, 275 | Black = True. 276 | 277 | `rect()` Draw a rectangle. Arguments `X0, Y0, X1, Y1, Width, Black`. Defaults: width = 1 pixel, 278 | Black = True. 279 | 280 | `fillrect()` Draw a filled rectangle. Arguments `X0, Y0, X1, Y1, Black`. Defaults: Black = True. 281 | 282 | `circle()` Draw a circle. Arguments `x0, y0, r, width, black`. Defaults: width = 1 pixel, 283 | Black = True. x0, y0 are the coordinates of the centre, r is the radius. 284 | 285 | `fillcircle()` Draw a filled circle. Arguments `x0, y0, r, black`. Defaults: Black = True. 286 | 287 | `load_xbm()` Load an image formatted as an XBM file. Arguments `sourcefile, x0, y0`: Path 288 | to the XBM file followed by coordinates defaulting to 0, 0. 289 | 290 | `loadgfx()` Fill a rectangular area with a bitmap. Arguments: `gen, width, height, x0, y0` where 291 | gen is a generator supplying bytes for each line in turn. These are displayed left to right, LSB of 292 | the 1st byte being at the top LH corner. Unused bits at the end of the line are ignored with a new 293 | line starting on the next byte. 294 | 295 | `locate()` This sets the pixel location of the text cursor. Arguments `x, y`. 296 | 297 | `puts()` Write a text string to the buffer. Argument `s`, the string to display. This must 298 | be called from a `with` block that defines the font; text will be rendered to the pixel location 299 | of the text cursor. Newline characters and line wrapping are supported. Example usage: 300 | 301 | ```python 302 | with a.font('/sd/LiberationSerif-Regular45x44'): 303 | a.puts("Large font\ntext here") 304 | a.show() 305 | ``` 306 | 307 | `setpixel()` Set or clear a pixel. Arguments `x, y, black`. Checks for and ignores pixels not 308 | within the display boundary. 309 | `setpixelfast()` Set or clear a pixel. Arguments `x, y, black`. Caller must check bounds. Uses 310 | the Viper emitter for maximum speed. 311 | 312 | The following methods are primarily for internal use and should not be used in normal operation as 313 | in this case the flash device is mounted automatically. 314 | 315 | `mountflash()` Mount the flash device. 316 | `umountflash()` Unmount the flash memory. 317 | 318 | ### Properties 319 | 320 | `temperature` Returns the current temperature in degrees Celsius. 321 | `location` Returns the x, y coordinates of the text cursor. 322 | 323 | ## Font class 324 | 325 | This is a Python context manager whose purpose is to define a context for the display's `puts()` 326 | method described above. It ensures that any font file is closed after use. It has no user 327 | accessible properties or methods. A font is instantiated for the duration of outputting one or more 328 | strings. It must be provided with the path to a valid binary font file or the name of a frozen 329 | font. See the code sample above. 330 | 331 | By default fonts are proportional. Where true monospaced output is required, for best results a 332 | non-proportional font should be used. It can then be employed as follows (`a` is a `Display` 333 | instance): 334 | 335 | ```python 336 | a.clear_screen() 337 | with a.font('/sd/courier25', monospaced = True): 338 | a.puts("Large font\ntext here") 339 | a.show() 340 | ``` 341 | 342 | The `monospaced` argument, which can be used with font files or Python font modules, will force 343 | any font to be rendered with fixed spacing. Results are unlikely to be visually pleasing unless the 344 | font is designed for such rendering. 345 | 346 | In the interests of conserving scarce RAM, font files use a binary format. Individual 347 | characters are buffered in RAM as required. This contrasts with the conventional approach of 348 | buffering the entire font in RAM, which is faster. The EPD is not a fast device and RAM is 349 | in short supply, hence the design decision. This is transparent to the user. 350 | 351 | With frozen fonts the fonts are stored in Flash as part of the device firmware. This enables them 352 | to be accessed as a `bytes` instance with faster operation. It uses even less RAM than file 353 | access at the cost of having to build the firmware from source. 354 | 355 | # Module epd.py 356 | 357 | This provides the low level interface to the EPD display module in NORMAL mode. It provides two 358 | methods and one property accessed by the `epaper` module: 359 | 360 | ### Methods 361 | 362 | `showdata()` Displays the current text buffer 363 | `clear_data()` Clears the buffer without displaying it. 364 | 365 | # Module epdpart.py 366 | 367 | This provides the low level interface to the EPD display module in FAST. Its interface is a 368 | superset of that of `epd.py` providing two additional methods: 369 | 370 | `refresh()` Fast update using current data buffer. 371 | `exchange()` A faster alternative to `showdata`. 372 | 373 | # Module flash.py 374 | 375 | This provides an interface to the 4MB flash memory on the Embedded Artists 376 | display; its use is entirely optional. Its most likely use is in ultra low 377 | power applications where the Pyboard runs without an SD card. 378 | 379 | It supports the ioctl protocol enabling the flash device to be mounted on the 380 | Pyboard filesystem and used for any purpose. There is a compromise in the 381 | design of this class between RAM usage and flash device wear. The compromise 382 | chosen is to buffer the two most recently written sectors: this uses 8K of RAM 383 | and substantially reduces the number of erase/write cycles, especially for low 384 | numbered sectors, compared to a naive unbuffered approach. The anticipated use 385 | for the flash is for storing rarely changing images and fonts so I think the 386 | compromise is reasonable. 387 | 388 | Buffering also improves perceived performance by reducing the number of 389 | erase/write cycles. 390 | 391 | ## Getting Started 392 | 393 | The flash drive must be formatted before first use. The code below will do this, and demonstrates 394 | copying a file to the drive (assuming you have first put the file on the SD card - modify this for 395 | any available file). 396 | 397 | ```python 398 | import pyb, flash, uos 399 | f = flash.FlashClass(0) # If on right hand side pass 1 400 | vfs = uos.VfsFat(f) 401 | vfs.mkfs(f) # To create a new filesystem (will erase any existing contents) 402 | uos.mount(vfs, f.mountpoint) 403 | flash.cp('/sd/LiberationSerif-Regular44','/fc/') 404 | uos.listdir('/fc') 405 | uos.umount('/fc') 406 | ``` 407 | 408 | ## File copy 409 | 410 | A rudimentary `cp(source, dest)` function is provided as a generic file copy routine. The first 411 | argument is the full pathname to the source file. The second may be a full path to the destination 412 | file or a directory specifier which must have a trailing '/'. If an OSError is thrown (e.g. a 413 | non-existent source file) it is up to the caller to handle it. 414 | 415 | ## FlashClass 416 | 417 | ### Constructor 418 | 419 | `FlashClass()` This takes one argument `intside` Indicates whether the device is mounted on the 420 | left (0) or right hand (1) side of the Pyboard (as defined above). 421 | 422 | ### Methods providing the ioctl protocol 423 | 424 | For the protocol definition see 425 | [the pyb documentation](http://docs.micropython.org/en/latest/library/pyb.html) 426 | 427 | `readblocks()` 428 | `writeblocks()` 429 | `ioctl()` 430 | 431 | ### Other methods 432 | 433 | The following methods are available for general use. 434 | `available()` Returns True if the device is detected and is supported. 435 | `info()` Returns manufacturer and device ID as integers. 436 | `begin()` Set up the bus and device. Throws a FlashException if device cannot be validated. 437 | `end()` Sync the device then shut down the bus. 438 | 439 | Other than for debugging there is no need to call `available()`: the constructor will throw 440 | a `FlashException` if it fails to communicate with and correctly identify the chip. 441 | 442 | ### SPI bus arbitration 443 | 444 | This information is provided for those wishing to modify the code. The Embedded Artists device 445 | has a flash memory chip (Winbond W25Q32 32Mbit chip) which shares the SPI bus with the display 446 | device (Pervasive Displays EM027BS013). These use the bus in different incompatible ways, 447 | consequently to use the display with the flash device mounted requires arbitration. This is done in 448 | the Display class `show()` method. Firstly it disables the Flash memory's use of the bus with 449 | `self.flash.end()`. The EPD class is a Python context manager and its appearance in a `with` 450 | statement performs hardware initialisation including setting up the bus. 451 | 452 | On completion of the `with` block the display hardware is shut down in an orderly fashion and 453 | the bus de-initialised. The flash device is then re-initialised and re-mounted. This works because of 454 | the buffered nature of the display driver: the flash chip is used for operations which modify the 455 | buffer but is not required for the display of the buffer contents. 456 | 457 | # Module panel.py 458 | 459 | This simply provides a dictionary of pin definitions enabling the panel to be installed on either 460 | side of the Pyboard. 461 | 462 | # Fonts 463 | 464 | Fonts can be handled in two ways. The first employs binary font files located on any accessible 465 | drive including the flash device on the display. The second involves creating a Python script which 466 | includes the font data and implementing this as persistent bytecode. The font then resides on the 467 | Pyboard in flash memory as part of the firmware. These approaches are described below. 468 | 469 | Font files are created using the `font_to_py.py` [utility](https://github.com/peterhinch/micropython-font-to-py.git). 470 | This runs on a PC and converts industry standard `ttf` or `otf` files to Python source or to 471 | binary format. The following examples assume you want to produce a 44 pixel high font, in the 472 | first case a binary file (`-b` argument) and in the second a Python source file. 473 | 474 | ``` 475 | ./font_to_py.py LiberationSerif-Regular.ttf 44 -xrb lib-serif44 476 | ./font_to_py.py LiberationSerif-Regular.ttf 44 -xr lib-serif44.py 477 | ``` 478 | 479 | The -xr options must be specified. There is no need to specify -f (fixed spacing). The best 480 | approach to fixed width fonts is to use one such as Courier designed as such. Any font may be 481 | rendered as fixed pitch by specifying the `monospaced` option as described above. 482 | 483 | Only Python source can be frozen as bytecode. Instructions as to how to do this are available on 484 | the MicroPython site, but in essence it involves putting the files to be frozen in a common 485 | directory and compiling the source with an option specifying the directory. The build is then 486 | flashed in the normal way. To test success (assuming a font called `myfont`) ensure that 487 | `myfont` does not exist on the device's filesystem and issue 488 | 489 | ``` 490 | import myfont 491 | ``` 492 | 493 | If no `ImportError` is thrown, `fonts.py` is frozen in the firmware. 494 | 495 | # Micropower Support 496 | 497 | A major application for e-paper displays is in devices intended to run for long periods from 498 | battery power. To achieve this, external hardware can be used to ensure power to the display and 499 | other peripherals is removed when the Pyboard is in standby. On waking the program turns on power 500 | to the peripherals, turning it off before going back into standby. For the lowest possible 501 | consumption an SD card should not be installed on the Pyboard as this consumes power at all times. 502 | Fonts and images should be stored in the Pyboard flash memory or on an external device whose power 503 | is switched (such as the flash memory on a power-switched display). 504 | 505 | Full details of how to achieve this are provided 506 | [here](https://github.com/peterhinch/micropython-micropower.git). 507 | 508 | # Frozen bytecode 509 | 510 | RAM and space on the Pyboard flash drive may be conserved by freezing bytecode. 511 | Note that the optional `epdpart.py` file cannot currently be frozen because it 512 | includes Arm Thumb assembler code. 513 | 514 | # FAST mode 515 | 516 | Fast mode offers a way to update the display in a way which avoids the flashing 517 | black and white of a normal update. It has a drawback known as "ghosting" 518 | whereby black pixels remain faintly visible if they have subsequently been 519 | turned white. In some cases careful interface design can overcome this 520 | limitation: see the [epd clock](epd_clock/README.md) demo with a novel analog 521 | clock display. 522 | 523 | A typical fast mode application might have this general form, where on occasion 524 | a full (slow) screen update is performed, with intermediate fast updates (with 525 | potential ghosting). 526 | 527 | ```python 528 | epd = epaper.Display(side = 'L', mode = epaper.FAST) 529 | with epd: 530 | while True: 531 | # Check for need to update 532 | if an_update_is_needed: 533 | if a_major_update: # Slow display of blank screen with flashing: 534 | # clears any ghosting. Reset buffers to initial state. 535 | epd.clear_screen(True, True) 536 | else: # minor update 537 | epd.clear_screen(False) # Clear current buffer, don't display 538 | populate(epd) # Perform graphics and/or text display of entire screen 539 | epd.refresh() # Physically update display. Minor updates may have ghosting. 540 | time.sleep(20) 541 | ``` 542 | 543 | The graphics and text primitives operate identically in both modes: a buffer is updated without 544 | affecting the display. Then a `Display` method is called to update the display hardware; FAST 545 | mode offers two additional update methods. 546 | 547 | The mode is invoked by instantiating the Display object with `mode=epaper.FAST`. In this mode the 548 | `Display` instance must be used within a context manager: this turns on the display electronics and 549 | ensures they are properly shut down. A consequence of this mode of operation is that the onboard 550 | Flash cannot be used while this context is active, so fonts must be stored elsewhere. The following 551 | additional `Display` methods are supported: 552 | 553 | `refresh()` Quickly updates the display additively: existing content will be retained but new 554 | content will be included (overwriting the old where they overlap). Currently this is imperfect with 555 | some ghosting evident. 556 | 557 | `exchange()` This takes a single mandatory boolean argument `clear_data`. Display data is 558 | double buffered. Calling `exchange` causes the current data to be displayed and the buffers to be 559 | swapped. If `clear_data` is `True` the new current buffer is cleared: this enables it to act as 560 | a faster version of `show()`. If `clear_data` is `False` it provides a means of switching 561 | between two images. Ghosting should not be visible with this method. 562 | 563 | The `Display` constructor has an additional kwonly argument `up_time` applicable to 564 | FAST mode. If set it overrides the default temperature related value allowing the user to speed 565 | redrawing at the likely expense of more ghosting. Its value is in ms: if not overridden its value 566 | ranges between 630-1260ms at typical room temperatures. 567 | 568 | The `clear_screen` method has an additional `both` arg. If this is set, the two 569 | buffers are cleared and the pointers reset: essentially the driver is restored 570 | to its power-on state. The `show` arg operates as above: if `True` the screen 571 | will be cleared. Such a reset is recommended prior to a complete redraw of the 572 | screen: after writing text and graphics `refresh` will update the entire 573 | display. 574 | 575 | The following example illustrates FAST mode by means of a simple digital clock display - some 576 | ghosting is evident. Note the additional two spaces at the end of the text: if refreshing 577 | proportional fonts the physical length of the text string varies. This can lead to the rightmost 578 | pixels of the previous string failing to be overwritten unless monospaced fonts are used with the 579 | `monospaced` context manager argument described above. 580 | 581 | ```python 582 | import epaper, time 583 | a = epaper.Display('L', mode = epaper.FAST) 584 | with a: 585 | a.clear_screen() 586 | while True: 587 | t =time.localtime() 588 | with a.font('/sd/LiberationSerif-Regular45x44'): 589 | a.locate(0,0) 590 | a.puts('{:02d}.{:02d}.{:02d} '.format(t[3],t[4],t[5])) 591 | a.refresh() 592 | time.sleep(2) 593 | ``` 594 | 595 | This example (`clock.py`) has digital and analog displays and better illustrates the issue of 596 | ghosting. A similar demo run on the Raspberry Pi with the reference driver resulted in an identical 597 | amount of ghosting. [Reference](./GHOSTING.md) 598 | 599 | ```python 600 | import epaper, time, math, pyb, gc 601 | a = epaper.Display('L', mode = epaper.FAST) 602 | 603 | def polar_line(origin, length, width): 604 | def draw(radians): 605 | x_end = origin[0] + length * math.sin(radians) 606 | y_end = origin[1] - length * math.cos(radians) 607 | a.line(origin[0], origin[1], x_end, y_end, width, True) 608 | return draw 609 | 610 | origin = 100, 100 611 | secs = polar_line(origin, 50, 1) 612 | mins = polar_line(origin, 50, 2) 613 | hours = polar_line(origin, 30, 4) 614 | 615 | with a: 616 | a.clear_screen() 617 | while True: 618 | a.clear_screen(False) # Clear data 619 | a.circle(origin[0], origin[1], 55, 1) 620 | t = time.localtime() 621 | h, m, s = t[3:6] 622 | hh = h + m /60 623 | with a.font('/sd/LiberationSerif-Regular45x44'): 624 | a.locate(0,0) 625 | a.puts('{:02d}.{:02d}.{:02d}'.format(h, m, s)) 626 | secs(2 * math.pi * s/60) 627 | mins(2 * math.pi * m/60) 628 | hours(2 * math.pi * (h + m/60)/12) 629 | a.refresh() 630 | gc.collect() # especially on PB Lite 631 | ``` 632 | 633 | ### For experimenters 634 | 635 | The `refresh` method has a boolean argument `fast`, defaulting `True`. Setting this `False` 636 | invokes a slower method claimed by some developers to reduce ghosting. Under investigation; I'm 637 | underwhelmed so far. The code (epdpart.py) has web references in the comments. 638 | 639 | # RAM usage 640 | 641 | As mentioned in "Getting started" the driver uses a significant amount of RAM, especially in FAST 642 | mode. In development, issue `D` before importing and instantiating the Display. In code which 643 | is to run unattended, instantiate the Display early to ensure it can obtain contiguous RAM blocks 644 | for its buffers. With code such as this which employs a lot of RAM, heap fragmentation can become 645 | more of an issue than usual. To avoid allocation failures issue gc.collect() periodically, notably 646 | after any large object goes out of scope. 647 | 648 | # Legalities 649 | 650 | The EPD and Flash driver code is based on C code released under the Apache licence. Accordingly I 651 | have released this code under the same licence and included the original copyright headers in the 652 | source. If the copyright owner has any issues with this I will be happy to accommodate any requests 653 | for changes. 654 | 655 | # References 656 | 657 | This code is derived from that at [Embedded Artists](https://github.com/embeddedartists/gratis). 658 | Graphics code derived from [ARM mbed](https://developer.mbed.org/users/dreschpe/code/EaEpaper/). 659 | [RePaper reference designs](https://github.com/repaper/gratis.git). 660 | [Ideas on ultra low power Pyboard systems](https://github.com/peterhinch/micropython-micropower.git). 661 | 662 | Further sources of information: 663 | [device datasheet](https://www.embeddedartists.com/wp-content/uploads/2018/06/1P053-00_04_EM027BS013_20140819.pdf). 664 | [COG interface timing](https://www.embeddedartists.com/wp-content/uploads/2018/06/4P015-00_01_G2_V230_COG_Driver_Interface_Timing_for_smallsize_20140128.pdf). 665 | [Flash device data](http://www.elinux.org/images/f/f5/Winbond-w25q32.pdf) (EA device only). 666 | [RePaper](http://repaper.org/doc/cog_driving.html). This link is broken: 667 | unfortunately the RePaper site only has broken links so it's not obvious how to 668 | fix this. 669 | 670 | Fonts acquired from [fontsquirrel](https://www.fontsquirrel.com/). 671 | 672 | Notes on the font file layout are available [here](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/DRIVERS.md#python-font-files). 673 | -------------------------------------------------------------------------------- /SIL Open Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Quote-Unquote Apps (http://quoteunquoteapps.com), 2 | with Reserved Font Name Courier Prime. 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 13 | 14 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 18 | 19 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 20 | 21 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 22 | 23 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 24 | 25 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 26 | 27 | PERMISSION & CONDITIONS 28 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 29 | 30 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 31 | 32 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 33 | 34 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 35 | 36 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 37 | 38 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 39 | 40 | TERMINATION 41 | This license becomes null and void if any of the above conditions are not met. 42 | 43 | DISCLAIMER 44 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /UML_DIAGRAMS.md: -------------------------------------------------------------------------------- 1 | # UML Diagrams 2 | 3 | ## Packages 4 | 5 | ![Packages](packages_epaper.png) 6 | 7 | ## Classes 8 | 9 | ![Classes](classes_epaper.png) 10 | -------------------------------------------------------------------------------- /classes_epaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/classes_epaper.png -------------------------------------------------------------------------------- /clock.py: -------------------------------------------------------------------------------- 1 | # clock.py demo for e-paper fast mode 2 | # Simpler approach: clear data each pass. 3 | 4 | import epaper, time, math, pyb 5 | a = epaper.Display('L', mode = epaper.FAST) 6 | 7 | def polar_line(origin, length, width): 8 | def draw(radians): 9 | x_end = origin[0] + length * math.sin(radians) 10 | y_end = origin[1] - length * math.cos(radians) 11 | a.line(origin[0], origin[1], x_end, y_end, width, True) 12 | return draw 13 | 14 | origin = 100, 100 15 | secs = polar_line(origin, 50, 1) 16 | mins = polar_line(origin, 50, 2) 17 | hours = polar_line(origin, 30, 4) 18 | 19 | with a: 20 | a.clear_screen() 21 | while True: 22 | a.clear_screen(False) 23 | a.circle(origin[0], origin[1], 55, 1) 24 | t = time.localtime() 25 | h, m, s = t[3:6] 26 | hh = h + m /60 27 | with a.font('/sd/LiberationSerif-Regular45x44'): 28 | a.locate(0,0) 29 | a.puts('{:02d}.{:02d}.{:02d}'.format(h, m, s)) 30 | secs(2 * math.pi * s/60) 31 | mins(2 * math.pi * m/60) 32 | hours(2 * math.pi * (h + m/60)/12) 33 | a.refresh() 34 | -------------------------------------------------------------------------------- /courier25: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/courier25 -------------------------------------------------------------------------------- /courier25.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: Courier Prime.ttf 3 | version = '0.1' 4 | 5 | def height(): 6 | return 25 7 | 8 | def max_width(): 9 | return 17 10 | 11 | def hmap(): 12 | return True 13 | 14 | def reverse(): 15 | return True 16 | 17 | def monospaced(): 18 | return False 19 | 20 | _font =\ 21 | b'\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 22 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 23 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 24 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 25 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00'\ 26 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00'\ 27 | b'\x00\x0e\x00\x00\x0e\x00\x00\x06\x00\x00\x06\x00\x00\x06\x00\x00'\ 28 | b'\x04\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x0f'\ 29 | b'\x00\x00\x0f\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 30 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00'\ 31 | b'\x00\x00\x00\x00\x00\xe7\x01\x00\xe7\x01\x00\xc7\x00\x00\xc7\x00'\ 32 | b'\x00\xc6\x00\x00\xc6\x00\x00\xc6\x00\x00\x00\x00\x00\x00\x00\x00'\ 33 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 35 | b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x80'\ 36 | b'\x10\x00\xc0\x18\x00\xc0\x18\x00\xc0\x08\x00\x60\x0c\x00\xfe\x7f'\ 37 | b'\x00\xfe\x7f\x00\x30\x06\x00\x30\x06\x00\x30\x02\x00\xff\x3f\x00'\ 38 | b'\xff\x3f\x00\x18\x01\x00\x8c\x01\x00\x8c\x01\x00\x8c\x01\x00\x84'\ 39 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 40 | b'\x00\x00\x00\x00\x11\x00\x00\x00\x00\x30\x00\x00\x30\x00\x00\x30'\ 41 | b'\x00\x00\x7c\x06\x00\xfe\x07\x00\x33\x07\x00\x33\x06\x00\x33\x00'\ 42 | b'\x00\x3f\x00\x00\xfe\x01\x00\xfc\x03\x00\xb0\x07\x00\x30\x06\x00'\ 43 | b'\x33\x06\x00\x33\x07\x00\xff\x03\x00\xfb\x00\x00\x30\x00\x00\x30'\ 44 | b'\x00\x00\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 45 | b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x3e'\ 46 | b'\x20\x00\x63\x30\x00\x63\x18\x00\x63\x0c\x00\x3e\x06\x00\x9c\x03'\ 47 | b'\x00\xc0\x01\x00\xe0\x00\x00\x70\x1c\x00\x38\x3e\x00\x1c\x63\x00'\ 48 | b'\x0e\x63\x00\x07\x63\x00\x02\x3e\x00\x00\x1c\x00\x00\x00\x00\x00'\ 49 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00'\ 50 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\x00\xfc\x03\x00\x0e'\ 51 | b'\x03\x00\x06\x03\x00\x06\x00\x00\x06\x00\x00\x0c\x00\x00\x1c\x00'\ 52 | b'\x00\x3e\x1e\x00\x67\x1e\x00\xe3\x07\x00\xc3\x03\x00\x83\x03\x00'\ 53 | b'\xc7\x07\x00\xfe\x1e\x00\x7c\x1c\x00\x00\x00\x00\x00\x00\x00\x00'\ 54 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00'\ 55 | b'\x00\x00\x00\x00\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07'\ 56 | b'\x00\x00\x07\x00\x00\x03\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'\ 57 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 58 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 59 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\xc0\x00\x00'\ 60 | b'\xf0\x00\x00\x38\x00\x00\x18\x00\x00\x0c\x00\x00\x0e\x00\x00\x06'\ 61 | b'\x00\x00\x06\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00'\ 62 | b'\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x06\x00\x00\x06\x00\x00'\ 63 | b'\x06\x00\x00\x0c\x00\x00\x1c\x00\x00\x38\x00\x00\x70\x00\x00\xe0'\ 64 | b'\x00\x00\x40\x00\x00\x11\x00\x00\x00\x00\x03\x00\x00\x07\x00\x00'\ 65 | b'\x0c\x00\x00\x18\x00\x00\x30\x00\x00\x70\x00\x00\x60\x00\x00\x60'\ 66 | b'\x00\x00\xc0\x00\x00\xc0\x00\x00\xc0\x00\x00\xc0\x00\x00\xc0\x00'\ 67 | b'\x00\xc0\x00\x00\xc0\x00\x00\xe0\x00\x00\x60\x00\x00\x60\x00\x00'\ 68 | b'\x30\x00\x00\x38\x00\x00\x1c\x00\x00\x0e\x00\x00\x07\x00\x00\x01'\ 69 | b'\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00'\ 70 | b'\x70\x00\x00\x30\x00\x00\x21\x06\x00\xaf\x07\x00\xff\x07\x00\x70'\ 71 | b'\x00\x00\xd8\x00\x00\xdc\x00\x00\x8e\x01\x00\x8e\x01\x00\x00\x00'\ 72 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 73 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11'\ 74 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 75 | b'\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\xff'\ 76 | b'\x0f\x00\xff\x0f\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00'\ 77 | b'\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 78 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\ 79 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 80 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 81 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x1e\x00'\ 82 | b'\x00\x0e\x00\x00\x0e\x00\x00\x06\x00\x00\x07\x00\x00\x03\x00\x00'\ 83 | b'\x03\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00'\ 84 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 85 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\x00\xff\x0f\x00\x00'\ 86 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 87 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 88 | b'\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 89 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 90 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 91 | b'\x00\x00\x00\x00\x00\x06\x00\x00\x0f\x00\x00\x0f\x00\x00\x06\x00'\ 92 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 93 | b'\x00\x00\x00\x11\x00\x00\x00\x00\x00\x06\x00\x00\x06\x00\x00\x03'\ 94 | b'\x00\x00\x03\x00\x80\x01\x00\x80\x01\x00\xc0\x01\x00\xc0\x00\x00'\ 95 | b'\xe0\x00\x00\x60\x00\x00\x60\x00\x00\x30\x00\x00\x30\x00\x00\x18'\ 96 | b'\x00\x00\x18\x00\x00\x0c\x00\x00\x0c\x00\x00\x0e\x00\x00\x06\x00'\ 97 | b'\x00\x07\x00\x00\x03\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00'\ 98 | b'\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\x00\xfc\x01'\ 99 | b'\x00\x8e\x03\x00\x06\x03\x00\x07\x07\x00\x03\x06\x00\x03\x06\x00'\ 100 | b'\x03\x06\x00\x03\x06\x00\x03\x06\x00\x03\x06\x00\x07\x07\x00\x06'\ 101 | b'\x03\x00\x8e\x03\x00\xfc\x01\x00\xf8\x00\x00\x00\x00\x00\x00\x00'\ 102 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00'\ 103 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x70\x00\x00\x7e\x00\x00\x6f\x00'\ 104 | b'\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00'\ 105 | b'\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60'\ 106 | b'\x00\x00\xfe\x07\x00\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 107 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00'\ 108 | b'\x00\x00\x00\x00\x00\x7c\x00\x00\xff\x01\x00\x87\x03\x00\x03\x03'\ 109 | b'\x00\x03\x03\x00\x00\x03\x00\x80\x03\x00\x80\x01\x00\xc0\x01\x00'\ 110 | b'\xe0\x00\x00\x70\x00\x00\x38\x00\x00\x1c\x06\x00\x0e\x06\x00\xff'\ 111 | b'\x07\x00\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 112 | b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00'\ 113 | b'\x00\x00\x7c\x00\x00\xff\x01\x00\x83\x03\x00\x03\x03\x00\x00\x03'\ 114 | b'\x00\x80\x03\x00\xf8\x01\x00\xf8\x01\x00\x80\x03\x00\x00\x06\x00'\ 115 | b'\x00\x06\x00\x00\x06\x00\x01\x06\x00\x07\x03\x00\xfe\x03\x00\xf8'\ 116 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 117 | b'\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80'\ 118 | b'\x01\x00\xc0\x01\x00\xe0\x01\x00\xb0\x01\x00\xb0\x01\x00\x98\x01'\ 119 | b'\x00\x8c\x01\x00\x86\x01\x00\x87\x01\x00\xff\x0f\x00\xff\x0f\x00'\ 120 | b'\x80\x01\x00\x80\x01\x00\x80\x01\x00\xf0\x0f\x00\xf0\x0f\x00\x00'\ 121 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 122 | b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x03\x00\xfe'\ 123 | b'\x03\x00\x06\x00\x00\x06\x00\x00\x06\x00\x00\xfe\x00\x00\xfe\x03'\ 124 | b'\x00\x06\x03\x00\x00\x07\x00\x00\x06\x00\x00\x06\x00\x00\x06\x00'\ 125 | b'\x01\x07\x00\x87\x03\x00\xfe\x01\x00\xf8\x00\x00\x00\x00\x00\x00'\ 126 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00'\ 127 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x03\x00\xf0\x03\x00\x78'\ 128 | b'\x00\x00\x1c\x00\x00\x0e\x00\x00\x06\x00\x00\xf3\x00\x00\xff\x03'\ 129 | b'\x00\x07\x03\x00\x03\x06\x00\x03\x06\x00\x03\x06\x00\x02\x06\x00'\ 130 | b'\x06\x03\x00\xfc\x03\x00\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 131 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00'\ 132 | b'\x00\x00\x00\x00\x00\x00\xff\x07\x00\xff\x07\x00\x03\x03\x00\x03'\ 133 | b'\x03\x00\x80\x03\x00\x80\x01\x00\x80\x01\x00\xc0\x00\x00\xc0\x00'\ 134 | b'\x00\x60\x00\x00\x60\x00\x00\x70\x00\x00\x30\x00\x00\x30\x00\x00'\ 135 | b'\x18\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 136 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00'\ 137 | b'\x00\x00\x00\x78\x00\x00\xfe\x01\x00\x87\x03\x00\x03\x03\x00\x03'\ 138 | b'\x03\x00\x86\x03\x00\xfc\x01\x00\xfc\x01\x00\x06\x03\x00\x03\x06'\ 139 | b'\x00\x03\x06\x00\x03\x06\x00\x03\x06\x00\x06\x03\x00\xfe\x03\x00'\ 140 | b'\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 141 | b'\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 142 | b'\xf8\x00\x00\xfe\x01\x00\x86\x03\x00\x03\x02\x00\x03\x06\x00\x03'\ 143 | b'\x06\x00\x03\x06\x00\x06\x07\x00\xfe\x07\x00\x78\x06\x00\x00\x03'\ 144 | b'\x00\x80\x03\x00\xc0\x01\x00\xf0\x00\x00\x7e\x00\x00\x1e\x00\x00'\ 145 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 146 | b'\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 147 | b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x0f\x00\x00\x0f\x00\x00\x06'\ 148 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 149 | b'\x00\x06\x00\x00\x0f\x00\x00\x0f\x00\x00\x06\x00\x00\x00\x00\x00'\ 150 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11'\ 151 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 152 | b'\x00\x00\x00\x0c\x00\x00\x1e\x00\x00\x1e\x00\x00\x0c\x00\x00\x00'\ 153 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00'\ 154 | b'\x00\x1e\x00\x00\x0e\x00\x00\x0e\x00\x00\x06\x00\x00\x07\x00\x00'\ 155 | b'\x03\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\ 156 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00'\ 157 | b'\x00\x0f\x00\xc0\x03\x00\xf0\x00\x00\x3c\x00\x00\x0f\x00\x00\x0f'\ 158 | b'\x00\x00\x3e\x00\x00\xf8\x00\x00\xc0\x03\x00\x00\x0f\x00\x00\x0c'\ 159 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 160 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00'\ 161 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 162 | b'\xff\x0f\x00\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 163 | b'\x00\x00\xff\x0f\x00\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 164 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 165 | b'\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 166 | b'\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x1e\x00\x00\x78\x00\x00'\ 167 | b'\xe0\x01\x00\x80\x07\x00\x00\x1e\x00\x00\x1e\x00\x80\x07\x00\xe0'\ 168 | b'\x01\x00\x78\x00\x00\x1e\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00'\ 169 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 170 | b'\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x00'\ 171 | b'\x00\xff\x01\x00\x83\x03\x00\x03\x03\x00\x00\x03\x00\x00\x03\x00'\ 172 | b'\x80\x01\x00\xf0\x01\x00\x70\x00\x00\x30\x00\x00\x30\x00\x00\x10'\ 173 | b'\x00\x00\x30\x00\x00\x78\x00\x00\x78\x00\x00\x30\x00\x00\x00\x00'\ 174 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 175 | b'\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x07'\ 176 | b'\x00\xf0\x0f\x00\x38\x1c\x00\x0c\x30\x00\xc6\x75\x00\xe6\x67\x00'\ 177 | b'\x73\x66\x00\x3b\x62\x00\x1b\x62\x00\x1b\x63\x00\x1b\x33\x00\xfb'\ 178 | b'\x3e\x00\x76\x1e\x00\x06\x00\x00\x1c\x04\x00\xf8\x0f\x00\xf0\x03'\ 179 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00'\ 180 | b'\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x03\x00\xf8\x03\x00\xc0\x06'\ 181 | b'\x00\xc0\x06\x00\x40\x0e\x00\x60\x0c\x00\x60\x0c\x00\x30\x1c\x00'\ 182 | b'\x30\x18\x00\xf0\x1f\x00\xf8\x3f\x00\x18\x30\x00\x18\x30\x00\x0c'\ 183 | b'\x60\x00\x3f\xfc\x01\x7f\xfc\x01\x00\x00\x00\x00\x00\x00\x00\x00'\ 184 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00'\ 185 | b'\x00\x00\x00\x00\x00\xff\x03\x00\xff\x0f\x00\x0c\x1c\x00\x0c\x18'\ 186 | b'\x00\x0c\x18\x00\x0c\x18\x00\x0c\x0c\x00\xfc\x0f\x00\xfc\x1f\x00'\ 187 | b'\x0c\x38\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x0c\x38\x00\xff'\ 188 | b'\x1f\x00\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 189 | b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00'\ 190 | b'\x00\x00\xf0\x19\x00\xf8\x1f\x00\x1c\x1e\x00\x0e\x1c\x00\x06\x18'\ 191 | b'\x00\x03\x18\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00'\ 192 | b'\x03\x00\x00\x06\x00\x00\x06\x10\x00\x1c\x1c\x00\xf8\x0f\x00\xf0'\ 193 | b'\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 194 | b'\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff'\ 195 | b'\x03\x00\xff\x07\x00\x0c\x0e\x00\x0c\x1c\x00\x0c\x18\x00\x0c\x30'\ 196 | b'\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00'\ 197 | b'\x0c\x18\x00\x0c\x1c\x00\x0c\x0e\x00\xff\x07\x00\xff\x03\x00\x00'\ 198 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 199 | b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x3f\x00\xff'\ 200 | b'\x3f\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x0c\x03\x00\x0c\x03'\ 201 | b'\x00\xfc\x03\x00\xfc\x03\x00\x0c\x03\x00\x0c\x33\x00\x0c\x30\x00'\ 202 | b'\x0c\x30\x00\x0c\x30\x00\xff\x3f\x00\xff\x3f\x00\x00\x00\x00\x00'\ 203 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00'\ 204 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x3f\x00\xff\x3f\x00\x18'\ 205 | b'\x30\x00\x18\x30\x00\x18\x30\x00\x18\x03\x00\x18\x03\x00\xf8\x03'\ 206 | b'\x00\xf8\x03\x00\x18\x03\x00\x18\x03\x00\x18\x00\x00\x18\x00\x00'\ 207 | b'\x18\x00\x00\xff\x01\x00\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00'\ 208 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00'\ 209 | b'\x00\x00\x00\x00\x00\x00\xf0\x1b\x00\xf8\x1f\x00\x1c\x1c\x00\x0e'\ 210 | b'\x18\x00\x06\x18\x00\x03\x18\x00\x03\x00\x00\x03\x00\x00\x03\x00'\ 211 | b'\x00\x83\x7f\x00\x83\x7f\x00\x06\x18\x00\x06\x18\x00\x1c\x18\x00'\ 212 | b'\xf8\x1f\x00\xf0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 213 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00'\ 214 | b'\x00\x00\x00\x3f\x7e\x00\x3f\x7e\x00\x0c\x18\x00\x0c\x18\x00\x0c'\ 215 | b'\x18\x00\x0c\x18\x00\x0c\x18\x00\xfc\x1f\x00\xfc\x1f\x00\x0c\x18'\ 216 | b'\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x3f\x7e\x00'\ 217 | b'\x3f\x7e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 218 | b'\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 219 | b'\xfe\x07\x00\xfe\x07\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60'\ 220 | b'\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00'\ 221 | b'\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\xff\x1f\x00\xff\x1f\x00'\ 222 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 223 | b'\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x3f\x00'\ 224 | b'\xfc\x3f\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00'\ 225 | b'\x03\x00\x00\x03\x00\x01\x03\x00\x03\x03\x00\x03\x03\x00\x03\x03'\ 226 | b'\x00\x03\x03\x00\x87\x03\x00\xfe\x01\x00\xfc\x00\x00\x00\x00\x00'\ 227 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11'\ 228 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x7e\x00\x3f\x7e\x00'\ 229 | b'\x0c\x1c\x00\x0c\x06\x00\x0c\x03\x00\xcc\x01\x00\xec\x00\x00\xfc'\ 230 | b'\x00\x00\xfc\x03\x00\x0c\x07\x00\x0c\x06\x00\x0c\x06\x00\x0c\x0c'\ 231 | b'\x00\x0c\x0c\x00\x7f\x78\x00\x7f\x78\x00\x00\x00\x00\x00\x00\x00'\ 232 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\ 233 | b'\x00\x00\x00\x00\x00\x00\x00\xff\x01\x00\xff\x01\x00\x18\x00\x00'\ 234 | b'\x18\x00\x00\x18\x00\x00\x18\x00\x00\x18\x00\x00\x18\x00\x00\x18'\ 235 | b'\x00\x00\x18\x30\x00\x18\x30\x00\x18\x30\x00\x18\x30\x00\x18\x30'\ 236 | b'\x00\xff\x3f\x00\xff\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 237 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00'\ 238 | b'\x00\x00\x00\x00\x1e\xf0\x00\x3e\xf8\x00\x3c\x78\x00\x7c\x7c\x00'\ 239 | b'\x6c\x6c\x00\x6c\x6c\x00\xcc\x66\x00\xcc\x66\x00\xcc\x66\x00\x8c'\ 240 | b'\x63\x00\x8c\x63\x00\x0c\x61\x00\x0c\x60\x00\x0c\x60\x00\x3f\xf8'\ 241 | b'\x01\x3f\xf8\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 242 | b'\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 243 | b'\x00\x1f\x7e\x00\x1f\x7e\x00\x3c\x18\x00\x3c\x18\x00\x6c\x18\x00'\ 244 | b'\x4c\x18\x00\xcc\x18\x00\x8c\x19\x00\x8c\x19\x00\x0c\x1b\x00\x0c'\ 245 | b'\x1b\x00\x0c\x1e\x00\x0c\x1e\x00\x0c\x1c\x00\x7f\x1c\x00\x7f\x18'\ 246 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 247 | b'\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x03'\ 248 | b'\x00\xf8\x0f\x00\x1c\x1c\x00\x0e\x38\x00\x06\x30\x00\x03\x60\x00'\ 249 | b'\x03\x60\x00\x03\x60\x00\x03\x60\x00\x03\x60\x00\x03\x60\x00\x06'\ 250 | b'\x30\x00\x0e\x38\x00\x1c\x1c\x00\xf8\x0f\x00\xe0\x03\x00\x00\x00'\ 251 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 252 | b'\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x07\x00\xff\x1f'\ 253 | b'\x00\x18\x1c\x00\x18\x30\x00\x18\x30\x00\x18\x30\x00\x18\x30\x00'\ 254 | b'\x18\x18\x00\xf8\x1f\x00\xf8\x07\x00\x18\x00\x00\x18\x00\x00\x18'\ 255 | b'\x00\x00\x18\x00\x00\xff\x01\x00\xff\x01\x00\x00\x00\x00\x00\x00'\ 256 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00'\ 257 | b'\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x03\x00\xf8\x0f\x00\x1c\x1c'\ 258 | b'\x00\x0e\x38\x00\x06\x30\x00\x03\x60\x00\x03\x60\x00\x03\x60\x00'\ 259 | b'\x03\x60\x00\x03\x60\x00\x03\x60\x00\x06\x30\x00\x0e\x38\x00\x1c'\ 260 | b'\x1c\x00\xf8\x0f\x00\xe0\x03\x00\x30\x10\x00\xf8\x3f\x00\xfc\x1f'\ 261 | b'\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00'\ 262 | b'\x00\x00\x00\x00\x00\xff\x03\x00\xff\x0f\x00\x0c\x1c\x00\x0c\x18'\ 263 | b'\x00\x0c\x18\x00\x0c\x18\x00\x0c\x0c\x00\xfc\x0f\x00\xfc\x01\x00'\ 264 | b'\x8c\x07\x00\x0c\x06\x00\x0c\x0c\x00\x0c\x1c\x00\x0c\x18\x00\x3f'\ 265 | b'\x78\x00\x3f\x70\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 266 | b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00'\ 267 | b'\x00\x00\xf8\x0c\x00\xfe\x0f\x00\x07\x0f\x00\x03\x0c\x00\x03\x00'\ 268 | b'\x00\x03\x00\x00\x1e\x00\x00\xfc\x03\x00\xc0\x0f\x00\x00\x1c\x00'\ 269 | b'\x01\x18\x00\x03\x18\x00\x07\x18\x00\x0f\x1c\x00\xff\x0f\x00\xf3'\ 270 | b'\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 271 | b'\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff'\ 272 | b'\x1f\x00\xff\x1f\x00\x63\x18\x00\x63\x18\x00\x63\x18\x00\x63\x18'\ 273 | b'\x00\x63\x18\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00'\ 274 | b'\x60\x00\x00\x60\x00\x00\x60\x00\x00\xfe\x07\x00\xfe\x07\x00\x00'\ 275 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 276 | b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x7e\x00\x3f'\ 277 | b'\x7e\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18'\ 278 | b'\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00'\ 279 | b'\x0c\x18\x00\x18\x0c\x00\xf8\x0f\x00\xe0\x03\x00\x00\x00\x00\x00'\ 280 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00'\ 281 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xf8\x01\x7f\xf8\x01\x0c'\ 282 | b'\x60\x00\x18\x30\x00\x18\x30\x00\x18\x38\x00\x30\x18\x00\x30\x18'\ 283 | b'\x00\x70\x0c\x00\x60\x0c\x00\x60\x0c\x00\xc0\x06\x00\xc0\x06\x00'\ 284 | b'\xc0\x07\x00\x80\x03\x00\x80\x03\x00\x00\x00\x00\x00\x00\x00\x00'\ 285 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00'\ 286 | b'\x00\x00\x00\x00\x00\x00\x3f\xf8\x01\x3f\xf8\x01\x0c\x60\x00\x0c'\ 287 | b'\x60\x00\x8c\x63\x00\x8c\x63\x00\x8c\x63\x00\x8c\x67\x00\xcc\x66'\ 288 | b'\x00\xd8\x26\x00\xd8\x3c\x00\x78\x3c\x00\x78\x3c\x00\x78\x38\x00'\ 289 | b'\x38\x38\x00\x38\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 290 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00'\ 291 | b'\x00\x00\x00\x7e\x7c\x00\x7e\x7c\x00\x18\x18\x00\x30\x0c\x00\x70'\ 292 | b'\x0e\x00\x60\x07\x00\xc0\x03\x00\x80\x01\x00\xc0\x03\x00\xe0\x07'\ 293 | b'\x00\x60\x06\x00\x30\x0c\x00\x18\x18\x00\x0c\x30\x00\x3f\xfc\x00'\ 294 | b'\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 295 | b'\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 296 | b'\x3f\x7e\x00\x3f\x7e\x00\x0c\x1c\x00\x18\x0c\x00\x18\x06\x00\x30'\ 297 | b'\x07\x00\x70\x03\x00\xe0\x01\x00\xc0\x01\x00\xc0\x00\x00\xc0\x00'\ 298 | b'\x00\xc0\x00\x00\xc0\x00\x00\xc0\x00\x00\xfc\x0f\x00\xfc\x0f\x00'\ 299 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 300 | b'\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x0f\x00'\ 301 | b'\xff\x0f\x00\x03\x07\x00\x03\x03\x00\x83\x03\x00\xc3\x01\x00\xc0'\ 302 | b'\x00\x00\x60\x00\x00\x70\x00\x00\x30\x00\x00\x18\x0c\x00\x1c\x0c'\ 303 | b'\x00\x0e\x0c\x00\x06\x0c\x00\xff\x0f\x00\xff\x0f\x00\x00\x00\x00'\ 304 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11'\ 305 | b'\x00\x00\x00\x00\x7f\x00\x00\x7f\x00\x00\x03\x00\x00\x03\x00\x00'\ 306 | b'\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03'\ 307 | b'\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00'\ 308 | b'\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00'\ 309 | b'\x03\x00\x00\x7f\x00\x00\x7f\x00\x00\x00\x00\x00\x11\x00\x00\x00'\ 310 | b'\x00\x03\x00\x00\x03\x00\x00\x07\x00\x00\x06\x00\x00\x0e\x00\x00'\ 311 | b'\x0c\x00\x00\x0c\x00\x00\x18\x00\x00\x18\x00\x00\x30\x00\x00\x30'\ 312 | b'\x00\x00\x60\x00\x00\x60\x00\x00\xe0\x00\x00\xc0\x00\x00\xc0\x01'\ 313 | b'\x00\x80\x01\x00\x80\x01\x00\x00\x03\x00\x00\x03\x00\x00\x06\x00'\ 314 | b'\x00\x06\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x7f\x00'\ 315 | b'\x00\x7f\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00'\ 316 | b'\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60'\ 317 | b'\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00'\ 318 | b'\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x7f\x00\x00'\ 319 | b'\x7f\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 320 | b'\x00\x30\x00\x00\x70\x00\x00\x78\x00\x00\xd8\x00\x00\xdc\x00\x00'\ 321 | b'\x8c\x01\x00\x8e\x01\x00\x06\x03\x00\x07\x03\x00\x02\x06\x00\x00'\ 322 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 323 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 324 | b'\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 325 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 326 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 327 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff'\ 328 | b'\x01\xff\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 329 | b'\x11\x00\x03\x00\x00\x07\x00\x00\x1f\x00\x00\x78\x00\x00\x40\x00'\ 330 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 331 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 332 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 333 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00'\ 334 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 335 | b'\x00\xf0\x03\x00\xfc\x07\x00\x0e\x0e\x00\x00\x0c\x00\x00\x0c\x00'\ 336 | b'\xf8\x0d\x00\xfe\x0f\x00\x07\x0c\x00\x03\x0c\x00\x03\x0e\x00\x87'\ 337 | b'\x0f\x00\xfe\x3f\x00\xfc\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 338 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x0f'\ 339 | b'\x00\x00\x0f\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\xcc\x07'\ 340 | b'\x00\xec\x0f\x00\x3c\x1c\x00\x1c\x18\x00\x0c\x30\x00\x0c\x30\x00'\ 341 | b'\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x1c\x18\x00\x3c\x1c\x00\xef'\ 342 | b'\x0f\x00\xcf\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 343 | b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00'\ 344 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x19\x00\xfc\x1f'\ 345 | b'\x00\x1e\x1e\x00\x06\x18\x00\x03\x18\x00\x03\x18\x00\x03\x00\x00'\ 346 | b'\x03\x00\x00\x03\x00\x00\x06\x10\x00\x0e\x1c\x00\xfc\x0f\x00\xf0'\ 347 | b'\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 348 | b'\x00\x00\x00\x00\x11\x00\x00\x00\x00\xc0\x0f\x00\xc0\x0f\x00\x00'\ 349 | b'\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\xf8\x0c\x00\xfc\x0d'\ 350 | b'\x00\x0e\x0f\x00\x07\x0e\x00\x03\x0c\x00\x03\x0c\x00\x03\x0c\x00'\ 351 | b'\x03\x0c\x00\x07\x0e\x00\x0e\x0f\x00\xfc\x3d\x00\xf8\x3c\x00\x00'\ 352 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 353 | b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 354 | b'\x00\x00\x00\x00\x00\xf0\x01\x00\xfc\x07\x00\x0e\x0e\x00\x06\x0c'\ 355 | b'\x00\x03\x18\x00\xff\x1f\x00\xff\x1f\x00\x03\x00\x00\x03\x00\x00'\ 356 | b'\x07\x00\x00\x0e\x0e\x00\xfc\x0f\x00\xf8\x03\x00\x00\x00\x00\x00'\ 357 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00'\ 358 | b'\x00\x00\x00\xc0\x0f\x00\xe0\x1f\x00\x70\x18\x00\x30\x00\x00\x30'\ 359 | b'\x00\x00\x30\x00\x00\xff\x0f\x00\xff\x0f\x00\x30\x00\x00\x30\x00'\ 360 | b'\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00'\ 361 | b'\x30\x00\x00\xff\x0f\x00\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00'\ 362 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00'\ 363 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8'\ 364 | b'\x3c\x00\xfc\x3d\x00\x0e\x0f\x00\x06\x0e\x00\x03\x0c\x00\x03\x0c'\ 365 | b'\x00\x03\x0c\x00\x03\x0c\x00\x03\x0c\x00\x06\x0e\x00\x0e\x0f\x00'\ 366 | b'\xfc\x0d\x00\xf8\x0c\x00\x00\x0c\x00\x00\x0c\x00\x06\x06\x00\xfe'\ 367 | b'\x07\x00\xf8\x01\x00\x00\x00\x00\x11\x00\x00\x00\x00\x0f\x00\x00'\ 368 | b'\x0f\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x8c\x07\x00\xec'\ 369 | b'\x0f\x00\x7c\x1c\x00\x1c\x18\x00\x1c\x18\x00\x0c\x18\x00\x0c\x18'\ 370 | b'\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x3f\x7e\x00'\ 371 | b'\x3f\x7e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 372 | b'\x00\x00\x00\x00\x00\x11\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00'\ 373 | b'\x60\x00\x00\x00\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x60'\ 374 | b'\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00'\ 375 | b'\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\xff\x0f\x00\xff\x0f\x00'\ 376 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 377 | b'\x00\x00\x11\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00'\ 378 | b'\x00\x00\x00\x00\x00\x00\xfe\x03\x00\xfe\x03\x00\x00\x03\x00\x00'\ 379 | b'\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03'\ 380 | b'\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00'\ 381 | b'\x00\x03\x00\x87\x03\x00\xfe\x01\x00\xf8\x00\x00\x00\x00\x00\x11'\ 382 | b'\x00\x00\x00\x00\x0f\x00\x00\x0f\x00\x00\x0c\x00\x00\x0c\x00\x00'\ 383 | b'\x0c\x00\x00\x0c\x3f\x00\x0c\x3f\x00\x0c\x0e\x00\x0c\x07\x00\x8c'\ 384 | b'\x03\x00\xec\x00\x00\xfc\x01\x00\xbc\x03\x00\x1c\x07\x00\x0c\x0e'\ 385 | b'\x00\x0c\x0c\x00\x3f\x7e\x00\x3f\x7e\x00\x00\x00\x00\x00\x00\x00'\ 386 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\ 387 | b'\x00\x7f\x00\x00\x7f\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00'\ 388 | b'\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60'\ 389 | b'\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00'\ 390 | b'\x00\xff\x0f\x00\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 391 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00'\ 392 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xef\x39\x00'\ 393 | b'\xff\x7d\x00\x1c\x67\x00\x1c\x67\x00\x0c\x63\x00\x0c\x63\x00\x0c'\ 394 | b'\x63\x00\x0c\x63\x00\x0c\x63\x00\x0c\x63\x00\x0c\x63\x00\x1f\xef'\ 395 | b'\x01\x3f\xef\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 396 | b'\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 397 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\x07\x00\xef\x0f\x00'\ 398 | b'\x3c\x1c\x00\x1c\x18\x00\x1c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c'\ 399 | b'\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x3f\x7e\x00\x3f\x7e'\ 400 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 401 | b'\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 402 | b'\x00\x00\x00\x00\x00\x00\x00\xf0\x01\x00\xfc\x07\x00\x0e\x0e\x00'\ 403 | b'\x06\x0c\x00\x03\x18\x00\x03\x18\x00\x03\x18\x00\x03\x18\x00\x03'\ 404 | b'\x18\x00\x06\x0c\x00\x0e\x0e\x00\xfc\x07\x00\xf0\x01\x00\x00\x00'\ 405 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 406 | b'\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 407 | b'\x00\x00\x00\x00\xcf\x07\x00\xef\x0f\x00\x3c\x1c\x00\x1c\x18\x00'\ 408 | b'\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x0c\x30\x00\x1c'\ 409 | b'\x18\x00\x3c\x1c\x00\xec\x0f\x00\xcc\x07\x00\x0c\x00\x00\x0c\x00'\ 410 | b'\x00\x0c\x00\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x11\x00\x00'\ 411 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 412 | b'\x00\xf8\x3c\x00\xfc\x3d\x00\x0e\x0f\x00\x06\x0e\x00\x03\x0c\x00'\ 413 | b'\x03\x0c\x00\x03\x0c\x00\x03\x0c\x00\x03\x0c\x00\x06\x0e\x00\x0e'\ 414 | b'\x0f\x00\xfc\x0d\x00\xf8\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c'\ 415 | b'\x00\xc0\x3f\x00\xc0\x3f\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00'\ 416 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\x07'\ 417 | b'\x00\xcf\x0f\x00\xec\x04\x00\x3c\x00\x00\x1c\x00\x00\x1c\x00\x00'\ 418 | b'\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\xff'\ 419 | b'\x01\x00\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 420 | b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00'\ 421 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7c\x06\x00\xfe\x07'\ 422 | b'\x00\x07\x07\x00\x03\x06\x00\x03\x00\x00\x7e\x00\x00\xfc\x03\x00'\ 423 | b'\x80\x07\x00\x00\x06\x00\x03\x06\x00\x07\x07\x00\xff\x03\x00\xfb'\ 424 | b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 425 | b'\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00\x30'\ 426 | b'\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\xff\x0f\x00\xff\x0f'\ 427 | b'\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00'\ 428 | b'\x30\x00\x00\x30\x00\x00\x70\x38\x00\xe0\x1f\x00\xc0\x07\x00\x00'\ 429 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 430 | b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 431 | b'\x00\x00\x00\x00\x00\x0f\x1f\x00\x0f\x1f\x00\x0c\x18\x00\x0c\x18'\ 432 | b'\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x18\x00\x0c\x1c\x00'\ 433 | b'\x0c\x1c\x00\x1c\x1e\x00\xf8\x7b\x00\xf0\x78\x00\x00\x00\x00\x00'\ 434 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00'\ 435 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 436 | b'\x00\x00\x3f\x7e\x00\x7f\x7e\x00\x0c\x18\x00\x1c\x18\x00\x18\x1c'\ 437 | b'\x00\x18\x0c\x00\x30\x0e\x00\x30\x06\x00\x70\x06\x00\x60\x03\x00'\ 438 | b'\x60\x03\x00\xc0\x03\x00\xc0\x01\x00\x00\x00\x00\x00\x00\x00\x00'\ 439 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00'\ 440 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f'\ 441 | b'\xf0\x01\x3f\xf0\x01\x06\xc0\x00\x8c\xc3\x00\x8c\x63\x00\x8c\x63'\ 442 | b'\x00\xcc\x66\x00\xd8\x36\x00\xd8\x36\x00\x78\x3c\x00\x78\x3c\x00'\ 443 | b'\x70\x1c\x00\x30\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 444 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00'\ 445 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3e\x3e\x00\x3e'\ 446 | b'\x3e\x00\x18\x0c\x00\x30\x06\x00\x60\x03\x00\xc0\x01\x00\xc0\x01'\ 447 | b'\x00\x60\x03\x00\x30\x06\x00\x38\x0c\x00\x1c\x1c\x00\x3e\x3e\x00'\ 448 | b'\x3f\x7e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 449 | b'\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 450 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\x7e\x00\x7f\x7e\x00\x0c'\ 451 | b'\x18\x00\x1c\x18\x00\x18\x0c\x00\x30\x0c\x00\x30\x06\x00\x60\x06'\ 452 | b'\x00\x60\x03\x00\xc0\x03\x00\xc0\x01\x00\x80\x01\x00\xc0\x00\x00'\ 453 | b'\xc0\x00\x00\x60\x00\x00\x70\x00\x00\x3e\x00\x00\x0e\x00\x00\x00'\ 454 | b'\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 455 | b'\x00\x00\x00\x00\x00\x00\xff\x0f\x00\xff\x0f\x00\x03\x07\x00\x83'\ 456 | b'\x03\x00\xc0\x01\x00\xe0\x00\x00\x70\x00\x00\x78\x00\x00\x38\x0c'\ 457 | b'\x00\x1c\x0c\x00\x0e\x0c\x00\xff\x0f\x00\xff\x0f\x00\x00\x00\x00'\ 458 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11'\ 459 | b'\x00\x00\x00\x00\xc0\x03\x00\xe0\x03\x00\x70\x00\x00\x30\x00\x00'\ 460 | b'\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x38'\ 461 | b'\x00\x00\x1f\x00\x00\x1f\x00\x00\x18\x00\x00\x30\x00\x00\x30\x00'\ 462 | b'\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00'\ 463 | b'\x70\x00\x00\xe0\x03\x00\xc0\x03\x00\x00\x00\x00\x11\x00\x00\x00'\ 464 | b'\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00'\ 465 | b'\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03'\ 466 | b'\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00'\ 467 | b'\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00'\ 468 | b'\x03\x00\x00\x03\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x0f\x00'\ 469 | b'\x00\x1f\x00\x00\x38\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00'\ 470 | b'\x30\x00\x00\x30\x00\x00\x30\x00\x00\x70\x00\x00\xe0\x03\x00\xe0'\ 471 | b'\x03\x00\x70\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x30\x00'\ 472 | b'\x00\x30\x00\x00\x30\x00\x00\x30\x00\x00\x38\x00\x00\x1f\x00\x00'\ 473 | b'\x0f\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 474 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 475 | b'\x00\x00\x00\x00\x00\x00\x3c\x08\x00\xff\x1f\x00\xc3\x07\x00\x00'\ 476 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 477 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 478 | b'\x00\x00\x00' 479 | 480 | _index =\ 481 | b'\x00\x00\x4d\x00\x9a\x00\xe7\x00\x34\x01\x81\x01\xce\x01\x1b\x02'\ 482 | b'\x68\x02\xb5\x02\x02\x03\x4f\x03\x9c\x03\xe9\x03\x36\x04\x83\x04'\ 483 | b'\xd0\x04\x1d\x05\x6a\x05\xb7\x05\x04\x06\x51\x06\x9e\x06\xeb\x06'\ 484 | b'\x38\x07\x85\x07\xd2\x07\x1f\x08\x6c\x08\xb9\x08\x06\x09\x53\x09'\ 485 | b'\xa0\x09\xed\x09\x3a\x0a\x87\x0a\xd4\x0a\x21\x0b\x6e\x0b\xbb\x0b'\ 486 | b'\x08\x0c\x55\x0c\xa2\x0c\xef\x0c\x3c\x0d\x89\x0d\xd6\x0d\x23\x0e'\ 487 | b'\x70\x0e\xbd\x0e\x0a\x0f\x57\x0f\xa4\x0f\xf1\x0f\x3e\x10\x8b\x10'\ 488 | b'\xd8\x10\x25\x11\x72\x11\xbf\x11\x0c\x12\x59\x12\xa6\x12\xf3\x12'\ 489 | b'\x40\x13\x8d\x13\xda\x13\x27\x14\x74\x14\xc1\x14\x0e\x15\x5b\x15'\ 490 | b'\xa8\x15\xf5\x15\x42\x16\x8f\x16\xdc\x16\x29\x17\x76\x17\xc3\x17'\ 491 | b'\x10\x18\x5d\x18\xaa\x18\xf7\x18\x44\x19\x91\x19\xde\x19\x2b\x1a'\ 492 | b'\x78\x1a\xc5\x1a\x12\x1b\x5f\x1b\xac\x1b\xf9\x1b\x46\x1c\x93\x1c'\ 493 | 494 | 495 | 496 | def _chr_addr(ordch): 497 | offset = 2 * (ordch - 32) 498 | return int.from_bytes(_index[offset:offset + 2], 'little') 499 | 500 | def get_ch(ch): 501 | ordch = ord(ch) 502 | ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?') 503 | offset = _chr_addr(ordch) 504 | width = int.from_bytes(_font[offset:offset + 2], 'little') 505 | next_offs = _chr_addr(ordch +1) 506 | return memoryview(_font[offset + 2:next_offs]), 25, width 507 | 508 | -------------------------------------------------------------------------------- /epaper.py: -------------------------------------------------------------------------------- 1 | # epaper.py main module for Embedded Artists' 2.7 inch E-paper Display. 2 | # Peter Hinch 3 | # version 0.9 4 | # 17 Jun 2018 Adapted for VFS mount/unmount. 5 | # 18 Mar 2016 Adafruit module and fast (partial) updates. 6 | # 2 Mar 2016 Power control support removed. Support for fonts as persistent byte code 7 | # 29th Jan 2016 Monospaced fonts supported. 8 | 9 | # Copyright 2015 Peter Hinch 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License") 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at: 14 | # 15 | # http:#www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, 18 | # software distributed under the License is distributed on an 19 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 20 | # express or implied. See the License for the specific language 21 | # governing permissions and limitations under the License. 22 | 23 | # Code translated and developed from https://developer.mbed.org/users/dreschpe/code/EaEpaper/ 24 | 25 | import pyb, gc, uos 26 | from panel import NORMAL, FAST, EMBEDDED_ARTISTS, ADAFRUIT 27 | LINES_PER_DISPLAY = const(176) # 2.7 inch panel only! 28 | BYTES_PER_LINE = const(33) 29 | BITS_PER_LINE = const(264) 30 | 31 | gc.collect() 32 | 33 | NEWLINE = const(10) # ord('\n') 34 | 35 | class EPDError(OSError): 36 | pass 37 | 38 | def checkstate(state, msg): 39 | if not state: 40 | raise EPDError(msg) 41 | 42 | # Generator parses an XBM file returning width, height, followed by data bytes 43 | def get_xbm_data(sourcefile): 44 | errmsg = ''.join(("File: '", sourcefile, "' is not a valid XBM file")) 45 | try: 46 | with open(sourcefile, 'r') as f: 47 | phase = 0 48 | for line in f: 49 | if phase < 2: 50 | if line.startswith('#define'): 51 | yield int(line.split(' ')[-1]) 52 | phase += 1 53 | if phase == 2: 54 | start = line.find('{') 55 | if start >= 0: 56 | line = line[start +1:] 57 | phase += 1 58 | if phase == 3: 59 | if not line.isspace(): 60 | phase += 1 61 | if phase == 4: 62 | end = line.find('}') 63 | if end >=0 : 64 | line = line[:end] 65 | phase += 1 66 | hexnums = line.split(',') 67 | if hexnums[0] != '': 68 | for hexnum in [q for q in hexnums if not q.isspace()]: 69 | yield int(hexnum, 16) 70 | if phase != 5 : 71 | print(errmsg) 72 | except OSError: 73 | print("Can't open " + sourcefile + " for reading") 74 | 75 | 76 | class FontFileError(Exception): 77 | pass 78 | 79 | class Font(object): 80 | def __init__(self): 81 | self.bytes_per_ch = 0 # Number of bytes to define a character 82 | self.bytes_horiz = 0 # No. of bytes per character row 83 | self.bits_horiz = 0 # Horzontal bits in character matrix 84 | self.bits_vert = 0 # Vertical bits in character matrix 85 | self.monospaced = False # Default is variable width 86 | self.exists = False 87 | self.modfont = None 88 | self.fontfilename = None 89 | self.fontfile = None 90 | 91 | # monospaced only applies to binary files. Since these lack an index FIXME 92 | # characters are saved in fixed pitch with width data, hence can be 93 | # rendered as fixed or variable pitch. 94 | # Python fonts are saved as variable or fixed pitch depending on the -f arg. 95 | # The monospaced flag saved with the file enables the renderer to 96 | # determine the correct x advance. 97 | def __call__(self, fontfilename, monospaced = False): 98 | self.fontfilename = fontfilename 99 | self.monospaced = monospaced 100 | return self 101 | 102 | def __enter__(self): #fopen(self, fontfile): 103 | if isinstance(self.fontfilename, type(uos)): # Using a Python font 104 | self.fontfile = None 105 | f = self.fontfilename 106 | ok = False 107 | try: 108 | ok = f.hmap() and f.reverse() 109 | except AttributeError: 110 | pass 111 | if not ok: 112 | raise FontFileError('Font module {} is invalid'.format(f.__name__)) 113 | self.monospaced = f.monospaced() 114 | self.modfont = f 115 | self.bits_horiz = f.max_width() 116 | self.bits_vert = f.height() 117 | else: 118 | self.modfont = None 119 | try: 120 | f = open(self.fontfilename, 'rb') 121 | except OSError as err: 122 | raise FontFileError(err) 123 | self.fontfile = f 124 | header = f.read(4) 125 | if header[0] == 0x42 and header[1] == 0xe7: 126 | self.bits_horiz = header[2] # font[1] 127 | self.bits_vert = header[3] # font[2] 128 | else: 129 | raise FontFileError('Font file {} is invalid'.format(self.fontfilename)) 130 | self.bytes_horiz = (self.bits_horiz + 7) // 8 131 | self.bytes_per_ch = self.bytes_horiz * self.bits_vert 132 | self.exists = True 133 | return self 134 | 135 | def __exit__(self, *_): 136 | self.exists = False 137 | if self.fontfile is not None: 138 | self.fontfile.close() 139 | 140 | class Display(object): 141 | FONT_HEADER_LENGTH = 4 142 | def __init__(self, side='L',*, mode=NORMAL, model=EMBEDDED_ARTISTS, use_flash=False, up_time=None): 143 | self.flash = None # Assume flash is unused 144 | self.in_context = False 145 | try: 146 | intside = {'l':0, 'r':1}[side.lower()] 147 | except (KeyError, AttributeError): 148 | raise ValueError("Side must be 'L' or 'R'") 149 | if model not in (EMBEDDED_ARTISTS, ADAFRUIT): 150 | raise ValueError('Unsupported model') 151 | if mode == FAST and use_flash: 152 | raise ValueError('Flash memory unavailable in fast mode') 153 | if mode == NORMAL and up_time is not None: 154 | raise ValueError('Cannot set up_time in normal mode') 155 | if mode == NORMAL: 156 | from epd import EPD 157 | self.epd = EPD(intside, model) 158 | elif mode == FAST: 159 | from epdpart import EPD 160 | self.epd = EPD(intside, model, up_time) 161 | else: 162 | raise ValueError('Unsupported mode {}'.format(mode)) 163 | self.mode = mode 164 | self.font = Font() 165 | gc.collect() 166 | self.locate(0, 0) # Text cursor: default top left 167 | 168 | self.mounted = False # umountflash() not to sync 169 | if use_flash: 170 | from flash import FlashClass 171 | gc.collect() 172 | self.flash = FlashClass(intside) 173 | self.umountflash() # In case mounted by prior tests. 174 | self.mountflash() 175 | gc.collect() 176 | 177 | def checkcm(self): 178 | if not (self.mode == NORMAL or self.in_context): 179 | raise EPDError('Fast mode must be run using a context manager') 180 | 181 | def __enter__(self): # Power up 182 | checkstate(self.mode == FAST, "In normal mode, can't use context manager") 183 | self.in_context = True 184 | self.epd.enter() 185 | return self 186 | 187 | def __exit__(self, *_): # shut down 188 | self.in_context = False 189 | self.epd.exit() 190 | pass 191 | 192 | def mountflash(self): 193 | if self.flash is None: # Not being used 194 | return 195 | self.flash.begin() # Initialise. 196 | vfs = uos.VfsFat(self.flash) # Instantiate FAT filesystem 197 | uos.mount(vfs, self.flash.mountpoint) 198 | self.mounted = True 199 | 200 | def umountflash(self): # Unmount flash 201 | if self.flash is None: 202 | return 203 | if self.mounted: 204 | self.flash.synchronise() 205 | try: 206 | uos.umount(self.flash.mountpoint) 207 | except OSError: 208 | pass # Don't care if it wasn't mounted 209 | self.flash.end() # Shut down 210 | self.mounted = False # flag unmounted to prevent spurious syncs 211 | 212 | def show(self): 213 | self.checkcm() 214 | self.umountflash() # sync, umount flash, shut it down and disable SPI 215 | if self.mode == NORMAL: # EPD functions which access the display electronics must be 216 | with self.epd as epd: # called from a with block to ensure proper startup & shutdown 217 | epd.showdata() 218 | else: # Fast mode: already in context manager 219 | self.epd.showdata() 220 | self.mountflash() 221 | 222 | def clear_screen(self, show=True, both=False): 223 | self.checkcm() 224 | self.locate(0, 0) # Reset text cursor 225 | self.epd.clear_data(both) 226 | if show: 227 | if self.mode == NORMAL: 228 | self.show() 229 | else: 230 | self.epd.EPD_clear() 231 | 232 | def refresh(self, fast =True): # Fast mode only functions 233 | checkstate(self.mode == FAST, 'refresh() invalid in normal mode') 234 | self.checkcm() 235 | self.epd.refresh(fast) 236 | 237 | def exchange(self, clear_data): 238 | checkstate(self.mode == FAST, 'exchange() invalid in normal mode') 239 | self.checkcm() 240 | self.epd.exchange(clear_data) 241 | 242 | @property 243 | def temperature(self): # return temperature as integer in Celsius 244 | return self.epd.temperature 245 | 246 | @property 247 | def location(self): 248 | return self.char_x, self.char_y 249 | 250 | @micropython.native 251 | def setpixel(self, x, y, black): # 41uS. Clips to borders. x, y must be integer 252 | if y < 0 or y >= LINES_PER_DISPLAY or x < 0 or x >= BITS_PER_LINE : 253 | return 254 | image = self.epd.image 255 | omask = 1 << (x & 0x07) 256 | index = (x >> 3) + y *BYTES_PER_LINE 257 | if black: 258 | image[index] |= omask 259 | else: 260 | image[index] &= (omask ^ 0xff) 261 | 262 | @micropython.viper 263 | def setpixelfast(self, x: int, y: int, black: int): # 27uS. Caller checks bounds 264 | image = ptr8(self.epd.image) 265 | omask = 1 << (x & 0x07) 266 | index = (x >> 3) + y * 33 #BYTES_PER_LINE 267 | if black: 268 | image[index] |= omask 269 | else: 270 | image[index] &= (omask ^ 0xff) 271 | 272 | # ****** Simple graphics support ****** 273 | 274 | def _line(self, x0, y0, x1, y1, black = True): # Sinle pixel line 275 | dx = x1 -x0 276 | dy = y1 -y0 277 | dx_sym = 1 if dx > 0 else -1 278 | dy_sym = 1 if dy > 0 else -1 279 | 280 | dx = dx_sym*dx 281 | dy = dy_sym*dy 282 | dx_x2 = dx*2 283 | dy_x2 = dy*2 284 | if (dx >= dy): 285 | di = dy_x2 - dx 286 | while (x0 != x1): 287 | self.setpixel(x0, y0, black) 288 | x0 += dx_sym 289 | if (di<0): 290 | di += dy_x2 291 | else : 292 | di += dy_x2 - dx_x2 293 | y0 += dy_sym 294 | self.setpixel(x0, y0, black) 295 | else: 296 | di = dx_x2 - dy 297 | while (y0 != y1): 298 | self.setpixel(x0, y0, black) 299 | y0 += dy_sym 300 | if (di < 0): 301 | di += dx_x2 302 | else: 303 | di += dx_x2 - dy_x2 304 | x0 += dx_sym 305 | self.setpixel(x0, y0, black) 306 | 307 | def line(self, x0, y0, x1, y1, width =1, black = True): # Draw line 308 | x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1) 309 | if abs(x1 - x0) > abs(y1 - y0): # < 45 degrees 310 | for w in range(-width//2 +1, width//2 +1): 311 | self._line(x0, y0 +w, x1, y1 +w, black) 312 | else: 313 | for w in range(-width//2 +1, width//2 +1): 314 | self._line(x0 +w, y0, x1 +w, y1, black) 315 | 316 | def _rect(self, x0, y0, x1, y1, black): # Draw rectangle 317 | self.line(x0, y0, x1, y0, 1, black) 318 | self.line(x0, y0, x0, y1, 1, black) 319 | self.line(x0, y1, x1, y1, 1, black) 320 | self.line(x1, y0, x1, y1, 1, black) 321 | 322 | def rect(self, x0, y0, x1, y1, width =1, black = True): # Draw rectangle 323 | x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1) 324 | x0, x1 = (x0, x1) if x1 > x0 else (x1, x0) # x0, y0 is top left, x1, y1 is bottom right 325 | y0, y1 = (y0, y1) if y1 > y0 else (y1, y0) 326 | for w in range(width): 327 | self._rect(x0 +w, y0 +w, x1 -w, y1 -w, black) 328 | 329 | def fillrect(self, x0, y0, x1, y1, black = True): # Draw filled rectangle 330 | x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1) 331 | x0, x1 = (x0, x1) if x1 > x0 else (x1, x0) 332 | y0, y1 = (y0, y1) if y1 > y0 else (y1, y0) 333 | for x in range(x0, x1): 334 | for y in range(y0, y1): 335 | self.setpixel(x, y, black) 336 | 337 | def _circle(self, x0, y0, r, black = True): # Single pixel circle 338 | x = -r 339 | y = 0 340 | err = 2 -2*r 341 | while x <= 0: 342 | self.setpixel(x0 -x, y0 +y, black) 343 | self.setpixel(x0 +x, y0 +y, black) 344 | self.setpixel(x0 +x, y0 -y, black) 345 | self.setpixel(x0 -x, y0 -y, black) 346 | e2 = err 347 | if (e2 <= y): 348 | y += 1 349 | err += y*2 +1 350 | if (-x == y and e2 <= x): 351 | e2 = 0 352 | if (e2 > x): 353 | x += 1 354 | err += x*2 +1 355 | 356 | def circle(self, x0, y0, r, width =1, black = True): # Draw circle 357 | x0, y0, r = int(x0), int(y0), int(r) 358 | for r in range(r, r -width, -1): 359 | self._circle(x0, y0, r, black) 360 | 361 | def fillcircle(self, x0, y0, r, black = True): # Draw filled circle 362 | x0, y0, r = int(x0), int(y0), int(r) 363 | x = -r 364 | y = 0 365 | err = 2 -2*r 366 | while x <= 0: 367 | self._line(x0 -x, y0 -y, x0 -x, y0 +y, black) 368 | self._line(x0 +x, y0 -y, x0 +x, y0 +y, black) 369 | e2 = err 370 | if (e2 <= y): 371 | y +=1 372 | err += y*2 +1 373 | if (-x == y and e2 <= x): 374 | e2 = 0 375 | if (e2 > x): 376 | x += 1 377 | err += x*2 +1 378 | 379 | # ****** Image display ****** 380 | 381 | def load_xbm(self, sourcefile, x = 0, y = 0): 382 | g = get_xbm_data(sourcefile) 383 | width = next(g) 384 | height = next(g) 385 | self.loadgfx(g, width, height, x, y) 386 | 387 | # Load a rectangular region with a bitmap supplied by a generator. 388 | 389 | def loadgfx(self, gen, width, height, x0, y0): 390 | byteoffset = x0 >> 3 391 | bitshift = x0 & 7 # Offset of image relative to byte boundary 392 | bytes_per_line = width >> 3 393 | if width & 7 > 0: 394 | bytes_per_line += 1 395 | for line in range(height): 396 | y = y0 + line 397 | if y >= LINES_PER_DISPLAY: 398 | break 399 | index = y * BYTES_PER_LINE + byteoffset 400 | bitsleft = width 401 | x = x0 402 | for byte in range(bytes_per_line): 403 | val = next(gen) 404 | bits_to_write = min(bitsleft, 8) 405 | x += bits_to_write 406 | if x <= BITS_PER_LINE: 407 | if bitshift == 0 and bits_to_write == 8: 408 | self.epd.image[index] = val 409 | index += 1 410 | else: 411 | mask = ((1 << bitshift) -1) # Bits in current byte to preserve 412 | bitsused = bitshift + bits_to_write 413 | overflow = max(0, bitsused -8) 414 | underflow = max(0, 8 -bitsused) 415 | if underflow: # Underflow in current byte 416 | mask = (mask | ~((1 << bitsused) -1)) & 0xff 417 | nmask = ~mask & 0xff # Bits to overwrite 418 | self.epd.image[index] = (self.epd.image[index] & mask) | ((val << bitshift) & nmask) 419 | index += 1 420 | if overflow : # Bits to write to next byte 421 | mask = ~((1 << overflow) -1) & 0xff # Preserve 422 | self.epd.image[index] = (self.epd.image[index] & mask) | (val >> (8 - bitshift)) 423 | bitsleft -= bits_to_write 424 | 425 | # ****** Text support ****** 426 | 427 | def locate(self, x, y): # set cursor position 428 | self.char_x = x # Text input cursor to (x, y) 429 | self.char_y = y 430 | 431 | # font.bytes_horiz 432 | # In cse of font file it's the pysical width of every character as stored in file 433 | # In case of Python font it's the value of max_width converted to bytes 434 | def _character(self, c, usefile): 435 | font = self.font # Cache for speed 436 | bits_vert = font.bits_vert 437 | if usefile: 438 | ff = font.fontfile 439 | ff.seek(self.FONT_HEADER_LENGTH + (c -32) * (font.bytes_per_ch + 1)) 440 | buf = ff.read(font.bytes_per_ch + 1) 441 | # Characters are stored as constant width. 442 | bytes_horiz = font.bytes_horiz # No. of bytes before next row 443 | # Advance = bits_horiz if variable pitch else font.bits_horiz 444 | bits_horiz = buf[0] 445 | offset = 1 446 | else: 447 | modfont = font.modfont 448 | buf, height, bits_horiz = modfont.get_ch(chr(c)) 449 | # Width varies between characters 450 | bytes_horiz = (bits_horiz + 7) // 8 451 | offset = 0 452 | # Sanity checks: prevent index errors. Wrapping should be done at string/word level. 453 | if (self.char_x + bytes_horiz * 8) > BITS_PER_LINE : 454 | self.char_x = 0 455 | self.char_y += bits_vert 456 | if self.char_y >= (LINES_PER_DISPLAY - bits_vert): 457 | self.char_y = 0 458 | 459 | image = self.epd.image 460 | y = self.char_y # x, y are pixel coordinates 461 | for bit_vert in range(bits_vert): # for each vertical line 462 | x = self.char_x 463 | for byte_horiz in range(bytes_horiz): 464 | fontbyte = buf[bit_vert * bytes_horiz + byte_horiz + offset] 465 | index = (x >> 3) + y * BYTES_PER_LINE 466 | nbits = x & 0x07 467 | if nbits == 0: 468 | image[index] = fontbyte 469 | else: 470 | image[index] &= (0xff >> (8 - nbits)) 471 | image[index] |= (fontbyte << nbits) 472 | image[index + 1] &= (0xff << nbits) 473 | image[index + 1] |= (fontbyte >> (8 - nbits)) 474 | x += 8 475 | y += 1 476 | self.char_x += font.bits_horiz if font.monospaced else bits_horiz 477 | 478 | def _putc(self, value, usefile): # print char 479 | if (value == NEWLINE): 480 | self.char_x = 0 481 | self.char_y += self.font.bits_vert 482 | if (self.char_y >= LINES_PER_DISPLAY - self.font.bits_vert): 483 | self.char_y = 0 484 | else: 485 | self._character(value, usefile) 486 | return value 487 | 488 | def puts(self, s): # Output a string at cursor 489 | if self.font.exists: 490 | if self.font.modfont is None: # No font module: using binary file 491 | for char in s: 492 | c = ord(char) 493 | if (c > 31 and c < 127) or c == NEWLINE: 494 | self._putc(c, True) 495 | else: # Python font file is self-checking 496 | for char in s: 497 | self._putc(ord(char), False) 498 | else: 499 | raise FontFileError("There is no current font") 500 | -------------------------------------------------------------------------------- /epd.py: -------------------------------------------------------------------------------- 1 | # epd.py module for Embedded Artists' 2.7 inch E-paper Display. Imported by epaper.py 2 | # Ported from Arduino library (see README for web ref). 3 | # Peter Hinch 4 | # version 0.85 5 | # 18th Mar 2016 Support for Adafruit module. This file supports normal mode. 6 | # 17th Aug 2015 __exit__() sequence adjusted to conform with datasheet rather than Arduino code 7 | 8 | # Copyright 2013 Pervasive Displays, Inc, 2015 Peter Hinch 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License") 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at: 13 | # 14 | # http:#www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, 17 | # software distributed under the License is distributed on an 18 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 19 | # express or implied. See the License for the specific language 20 | # governing permissions and limitations under the License. 21 | 22 | import pyb, gc 23 | from panel import EMBEDDED_ARTISTS, getpins 24 | 25 | EPD_OK = const(0) # error codes 26 | EPD_UNSUPPORTED_COG = const(1) 27 | EPD_PANEL_BROKEN = const(2) 28 | EPD_DC_FAILED = const(3) 29 | 30 | EPD_normal = const(0) # Stage 31 | EPD_inverse = const(1) 32 | 33 | LINES_PER_DISPLAY = const(176) 34 | BYTES_PER_LINE = const(33) 35 | BYTES_PER_SCAN = const(44) 36 | BITS_PER_LINE = const(264) 37 | 38 | # LM75 Temperature sensor 39 | 40 | LM75_ADDR = const(0x49) # LM75 I2C address 41 | LM75_TEMP_REGISTER = const(0) # LM75 registers 42 | LM75_CONF_REGISTER = const(1) 43 | 44 | class LM75(): 45 | def __init__(self, bus): # Check existence and wake it 46 | self._i2c = pyb.I2C(bus, pyb.I2C.MASTER) 47 | devices = self._i2c.scan() 48 | if not LM75_ADDR in devices: 49 | raise OSError("No LM75 device detected") 50 | self.wake() 51 | 52 | def wake(self): 53 | self._i2c.mem_write(0, LM75_ADDR, LM75_CONF_REGISTER) 54 | 55 | def sleep(self): 56 | self._i2c.mem_write(1, LM75_ADDR, LM75_CONF_REGISTER) # put sensor in shutdown mode 57 | 58 | @property 59 | def temperature(self): # return temperature as integer in Celsius 60 | temp = bytearray(2) 61 | self._i2c.mem_read(temp, LM75_ADDR, LM75_TEMP_REGISTER) 62 | temperature = int(temp[0]) 63 | return temperature if temperature < 128 else temperature -256 # sign bit: subtract once to clear, 2nd time to add its value 64 | 65 | class EPDException(Exception): 66 | pass 67 | 68 | class EPD(object): 69 | def __init__(self, intside, model): 70 | self.model = model 71 | gc.collect() 72 | self.image = bytearray(BYTES_PER_LINE * LINES_PER_DISPLAY) 73 | self.linebuf = bytearray(BYTES_PER_LINE * 2 + BYTES_PER_SCAN) 74 | pins = getpins(intside, model) 75 | self.Pin_PANEL_ON = pyb.Pin(pins['PANEL_ON'], mode = pyb.Pin.OUT_PP) 76 | self.Pin_BORDER = pyb.Pin(pins['BORDER'], mode = pyb.Pin.OUT_PP) 77 | self.Pin_DISCHARGE = pyb.Pin(pins['DISCHARGE'], mode = pyb.Pin.OUT_PP) 78 | self.Pin_RESET = pyb.Pin(pins['RESET'], mode = pyb.Pin.OUT_PP) 79 | self.Pin_BUSY = pyb.Pin(pins['BUSY'], mode = pyb.Pin.IN) 80 | self.Pin_EPD_CS = pyb.Pin(pins['EPD_CS'], mode = pyb.Pin.OUT_PP) # cs for e-paper display 81 | self.Pin_FLASH_CS = pyb.Pin(pins['FLASH_CS'], mode = pyb.Pin.OUT_PP) # Instantiate flash CS and set high 82 | self.Pin_MOSI = pyb.Pin(pins['MOSI'], mode = pyb.Pin.OUT_PP) 83 | self.Pin_SCK = pyb.Pin(pins['SCK'], mode = pyb.Pin.OUT_PP) 84 | self.Pin_RESET.low() 85 | self.Pin_PANEL_ON.low() 86 | self.Pin_DISCHARGE.low() 87 | self.Pin_BORDER.low() 88 | self.Pin_EPD_CS.low() 89 | self.Pin_FLASH_CS.high() 90 | self.spi_no = pins['SPI_BUS'] 91 | if model == EMBEDDED_ARTISTS: 92 | self.lm75 = LM75(pins['I2C_BUS']) # early error if not working 93 | else: 94 | self.adc = pyb.ADC(pins['TEMPERATURE']) 95 | 96 | # USER INTERFACE 97 | 98 | def showdata(self): # Call from a with block 99 | self._frame_data_13(EPD_inverse) 100 | self._frame_stage2() # 1.6S 101 | self._frame_data_13(EPD_normal) 102 | 103 | def clear_data(self, arg=None): 104 | for x in range(len(self.image)): 105 | self.image[x] = 0 106 | # self.image[:] = bytes((0 for x in range(len(self.image)))) needless RAM allocation 107 | 108 | @property 109 | def temperature(self): # return temperature as integer in Celsius 110 | if self.model == EMBEDDED_ARTISTS: 111 | return self.lm75.temperature 112 | else: 113 | return 202.5 - 0.1824 * self.adc.read() 114 | 115 | # END OF USER INTERFACE 116 | 117 | def __enter__(self): # power up sequence 118 | self.status = EPD_OK 119 | self.Pin_RESET.low() 120 | self.Pin_PANEL_ON.low() 121 | self.Pin_DISCHARGE.low() 122 | self.Pin_BORDER.low() 123 | self.Pin_EPD_CS.low() 124 | # Baud rate: data sheet says 20MHz max. Pyboard's closest (21MHz) was unreliable 125 | self.spi = pyb.SPI(self.spi_no, pyb.SPI.MASTER, baudrate=10500000, polarity=1, phase=1, bits=8) # 5250000 10500000 supported by Pyboard 126 | self._SPI_send(b'\x00\x00') 127 | pyb.delay(5) 128 | self.Pin_PANEL_ON.high() 129 | pyb.delay(10) 130 | 131 | self.Pin_RESET.high() 132 | self.Pin_BORDER.high() 133 | self.Pin_EPD_CS.high() 134 | pyb.delay(5) 135 | 136 | self.Pin_RESET.low() 137 | pyb.delay(5) 138 | 139 | self.Pin_RESET.high() 140 | pyb.delay(5) 141 | 142 | while self.Pin_BUSY.value() == 1: # wait for COG to become ready 143 | pyb.delay(1) 144 | 145 | # read the COG ID 146 | cog_id = self._SPI_read(b'\x71\x00') & 0x0f 147 | 148 | if cog_id != 2: 149 | self.status = EPD_UNSUPPORTED_COG 150 | self._power_off() 151 | raise EPDException("Unsupported EPD COG device: " +str(cog_id)) 152 | # Disable OE 153 | self._SPI_send(b'\x70\x02') 154 | self._SPI_send(b'\x72\x40') 155 | 156 | # check breakage 157 | self._SPI_send(b'\x70\x0f') 158 | broken_panel = self._SPI_read(b'\x73\x00') & 0x80 159 | if broken_panel == 0: 160 | self.status = EPD_PANEL_BROKEN 161 | self._power_off() 162 | raise EPDException("EPD COG device reports broken status") 163 | # power saving mode 164 | self._SPI_send(b'\x70\x0b') 165 | self._SPI_send(b'\x72\x02') 166 | # channel select 167 | self._SPI_send(b'\x70\x01') 168 | self._SPI_send(b'\x72\x00\x00\x00\x7f\xff\xfe\x00\x00') # Channel select 169 | # high power mode osc 170 | self._SPI_send(b'\x70\x07') 171 | self._SPI_send(b'\x72\xd1') 172 | # power setting 173 | self._SPI_send(b'\x70\x08') 174 | self._SPI_send(b'\x72\x02') 175 | # Vcom level 176 | self._SPI_send(b'\x70\x09') 177 | self._SPI_send(b'\x72\xc2') 178 | # power setting 179 | self._SPI_send(b'\x70\x04') 180 | self._SPI_send(b'\x72\x03') 181 | # driver latch on 182 | self._SPI_send(b'\x70\x03') 183 | self._SPI_send(b'\x72\x01') 184 | # driver latch off 185 | self._SPI_send(b'\x70\x03') 186 | self._SPI_send(b'\x72\x00') 187 | 188 | pyb.delay(5) 189 | dc_ok = False 190 | for i in range(4): 191 | # charge pump positive voltage on - VGH/VDL on 192 | self._SPI_send(b'\x70\x05') 193 | self._SPI_send(b'\x72\x01') 194 | pyb.delay(240) 195 | # charge pump negative voltage on - VGL/VDL on 196 | self._SPI_send(b'\x70\x05') 197 | self._SPI_send(b'\x72\x03') 198 | pyb.delay(40) 199 | # charge pump Vcom on - Vcom driver on 200 | self._SPI_send(b'\x70\x05') 201 | self._SPI_send(b'\x72\x0f') 202 | pyb.delay(40) 203 | # check DC/DC 204 | self._SPI_send(b'\x70\x0f') 205 | dc_state = self._SPI_read(b'\x73\x00') & 0x40 206 | if dc_state == 0x40: 207 | dc_ok = True 208 | # output enable to disable 209 | self._SPI_send(b'\x70\x02') 210 | self._SPI_send(b'\x72\x04') 211 | break 212 | if not dc_ok: 213 | # output enable to disable 214 | self._SPI_send(b'\x70\x02') 215 | self._SPI_send(b'\x72\x04') 216 | 217 | self.status = EPD_DC_FAILED 218 | self._power_off() 219 | raise EPDException("EPD DC power failure") 220 | # Set temperature factor 221 | # stage1: repeat, step, block 222 | # stage2: repeat, t1, t2 223 | # stage3: repeat, step, block 224 | temperature = self.temperature 225 | if temperature < 10 : 226 | self.compensation = {'stage1_repeat':2, 'stage1_step':8, 'stage1_block':64, 227 | 'stage2_repeat':4, 'stage2_t1':392, 'stage2_t2':392, 228 | 'stage3_repeat':2, 'stage3_step':8, 'stage3_block':64}# 0 ... 10 Celcius 229 | elif temperature < 40: 230 | self.compensation = {'stage1_repeat':2, 'stage1_step':4, 'stage1_block':32, 231 | 'stage2_repeat':4, 'stage2_t1':196, 'stage2_t2':196, 232 | 'stage3_repeat':2, 'stage3_step':4, 'stage3_block':32} # 10 ... 40 Celcius 233 | else: 234 | self.compensation = {'stage1_repeat':4, 'stage1_step':8, 'stage1_block':64, 235 | 'stage2_repeat':4, 'stage2_t1':196, 'stage2_t2':196, 236 | 'stage3_repeat':4, 'stage3_step':8, 'stage3_block':64}# 40 ... 50 Celcius 237 | return self 238 | 239 | def __exit__(self, *_): 240 | self._nothing_frame() 241 | self._dummy_line() 242 | 243 | self.Pin_BORDER.low() 244 | pyb.delay(200) 245 | self.Pin_BORDER.high() 246 | 247 | # check DC/DC 248 | self._SPI_send(b'\x70\x0f') 249 | dc_state = self._SPI_read(b'\x73\x00') & 0x40 250 | if dc_state != 0x40: 251 | self.status = EPD_DC_FAILED 252 | self._power_off() 253 | raise EPDException("EPD DC power failure") 254 | self._SPI_send(b'\x70\x0B') # Conform with datasheet 255 | self._SPI_send(b'\x72\x00') 256 | # latch reset turn on 257 | self._SPI_send(b'\x70\x03') 258 | self._SPI_send(b'\x72\x01') 259 | # output enable off 260 | # self._SPI_send(b'\x70\x02') Conform with datasheet 261 | # self._SPI_send(b'\x72\x05') 262 | # power off charge pump Vcom 263 | self._SPI_send(b'\x70\x05') 264 | self._SPI_send(b'\x72\x03') 265 | # power off charge pump neg voltage 266 | self._SPI_send(b'\x70\x05') 267 | self._SPI_send(b'\x72\x01') 268 | pyb.delay(120) 269 | # discharge internal on 270 | self._SPI_send(b'\x70\x04') 271 | self._SPI_send(b'\x72\x80') 272 | # power off all charge pumps 273 | self._SPI_send(b'\x70\x05') 274 | self._SPI_send(b'\x72\x00') 275 | # turn of osc 276 | self._SPI_send(b'\x70\x07') 277 | self._SPI_send(b'\x72\x01') 278 | pyb.delay(50) 279 | self._power_off() 280 | 281 | def _power_off(self): # turn of power and all signals 282 | self.Pin_PANEL_ON.low() 283 | # self._SPI_send(b'\x00\x00') 284 | self.spi.deinit() 285 | self.Pin_SCK.init(mode = pyb.Pin.OUT_PP) 286 | self.Pin_SCK.low() 287 | self.Pin_MOSI.init(mode = pyb.Pin.OUT_PP) 288 | self.Pin_MOSI.low() 289 | self.Pin_BORDER.low() 290 | # ensure SPI MOSI and CLOCK are Low before CS Low 291 | self.Pin_RESET.low() 292 | self.Pin_EPD_CS.low() 293 | # pulse discharge pin 294 | self.Pin_DISCHARGE.high() 295 | pyb.delay(150) 296 | self.Pin_DISCHARGE.low() 297 | 298 | # One frame of data is the number of lines * rows. For example: 299 | # The 2.7” frame of data is 176 lines * 264 dots. 300 | 301 | def _frame_fixed_timed(self, fixed_value, stage_time): 302 | t_start = pyb.millis() 303 | t_elapsed = -1 304 | while t_elapsed < stage_time: 305 | for line in range(LINES_PER_DISPLAY -1, -1, -1): 306 | self._line_fixed(line, fixed_value, set_voltage_limit = False) 307 | t_elapsed = pyb.elapsed_millis(t_start) 308 | 309 | def _nothing_frame(self): 310 | for line in range(LINES_PER_DISPLAY) : 311 | self._line_fixed(line, 0, set_voltage_limit = True) 312 | 313 | def _dummy_line(self): 314 | line = 0x7fff 315 | self._line_fixed(line, 0, set_voltage_limit = True) 316 | 317 | def _frame_stage2(self): 318 | for i in range(self.compensation['stage2_repeat']): # 4 319 | self._frame_fixed_timed(0xff, self.compensation['stage2_t1']) # 196mS 320 | self._frame_fixed_timed(0xaa, self.compensation['stage2_t2']) # 196mS 321 | 322 | def _frame_data_13(self, stage): 323 | if stage == EPD_inverse : # stage 1 324 | self.pixelmask = 0xff 325 | repeat = self.compensation['stage1_repeat'] 326 | step = self.compensation['stage1_step'] 327 | block = self.compensation['stage1_block'] 328 | else: # stage 3 329 | self.pixelmask = 0 330 | repeat = self.compensation['stage3_repeat'] 331 | step = self.compensation['stage3_step'] 332 | block = self.compensation['stage3_block'] 333 | 334 | for n in range(repeat): 335 | block_begin = 0 336 | block_end = 0 337 | while block_begin < LINES_PER_DISPLAY: 338 | block_end += step 339 | block_begin = max(block_end - block, 0) 340 | if block_begin >= LINES_PER_DISPLAY: 341 | break 342 | 343 | full_block = (block_end - block_begin == block) 344 | for line in range(block_begin, block_end): 345 | if (line >= LINES_PER_DISPLAY): 346 | break 347 | if (full_block and (line < (block_begin + step))): 348 | self._line_fixed(line, 0, set_voltage_limit = False) 349 | else: 350 | self._line(line, line * BYTES_PER_LINE) 351 | 352 | # Optimisation: display refresh code spends 98.5% of its time running _line() [97.8% after optimisation] 353 | @micropython.native 354 | def _line_fixed(self, line, fixed_value, set_voltage_limit): 355 | spi_send_byte = self.spi.send # Optimisation: save function in local namespace 356 | if set_voltage_limit: # charge pump voltage level reduce voltage shift 357 | self._SPI_send(b'\x70\x04\x72\x00') # voltage level 0 for 2.7 inch panel 358 | self._SPI_send(b'\x70\x0a') 359 | self.Pin_EPD_CS.low() # CS low: stays low until end of line 360 | spi_send_byte(b'\x72\x00') 361 | self._setbuf_fixed(line, fixed_value) 362 | spi_send_byte(self.linebuf) 363 | self.Pin_EPD_CS.high() 364 | # output data to panel 365 | self._SPI_send(b'\x70\x02\x72\x07') 366 | 367 | @micropython.native 368 | def _line(self, line, offset): 369 | spi_send_byte = self.spi.send # Optimisation: save function in local namespace 370 | self._SPI_send(b'\x70\x0a') 371 | self.Pin_EPD_CS.low() # CS low: stays low until end of line 372 | spi_send_byte(b'\x72\x00') 373 | self._setbuf_data(line, offset) 374 | spi_send_byte(self.linebuf) 375 | self.Pin_EPD_CS.high() 376 | # output data to panel 377 | self._SPI_send(b'\x70\x02\x72\x07') 378 | 379 | @micropython.viper 380 | def _setbuf_fixed(self, line: int, fixed_value: int): 381 | index = 0 # odd pixels 382 | buf = ptr8(self.linebuf) 383 | for b in range(BYTES_PER_LINE, 0, -1): # Optimisation: replacing for .. in range made trivial gains. 384 | buf[index] = fixed_value # Optimisation: buffer SPI data 385 | index += 1 386 | # scan line 387 | scan_pos = (LINES_PER_DISPLAY - line - 1) >> 2 388 | scan_shift = (line & 3) << 1 389 | for b in range(BYTES_PER_SCAN): 390 | if scan_pos == b: 391 | buf[index] = 3 << scan_shift 392 | else: 393 | buf[index] = 0 394 | index += 1 395 | for b in range(BYTES_PER_LINE): # Even pixels 396 | buf[index] = fixed_value 397 | index += 1 398 | 399 | @micropython.viper 400 | def _setbuf_data(self, line: int, offset: int): # 5.85S 401 | pixelmask = int(self.pixelmask) 402 | buf = ptr8(self.linebuf) # Optimisation: use local namespace 403 | image = ptr8(self.image) 404 | index = 0 # odd pixels 405 | for b in range(BYTES_PER_LINE, 0, -1): 406 | buf[index] = int(image[offset + b - 1]) ^ pixelmask | 0xaa 407 | index += 1 408 | # scan line 409 | scan_pos = (LINES_PER_DISPLAY - line -1) >> 2 410 | scan_shift = (line & 3) << 1 411 | for b in range(BYTES_PER_SCAN): 412 | buf[index] = 3 << scan_shift if scan_pos == b else 0 413 | index += 1 414 | for b in range(BYTES_PER_LINE): # Even pixels 415 | pixels = ((int(image[offset + b]) ^ pixelmask) >> 1) | 0xaa 416 | pixels = (((pixels & 0xc0) >> 6) 417 | | ((pixels & 0x30) >> 2) 418 | | ((pixels & 0x0c) << 2) 419 | | ((pixels & 0x03) << 6)) 420 | buf[index] = pixels 421 | index += 1 422 | @micropython.native 423 | def _SPI_send(self, buf): 424 | self.Pin_EPD_CS.low() 425 | self.spi.send(buf) 426 | self.Pin_EPD_CS.high() 427 | 428 | @micropython.native 429 | def _SPI_read(self, buf): 430 | self.Pin_EPD_CS.low() 431 | for x in range(len(buf)): 432 | result = self.spi.send_recv(buf[x])[0] 433 | self.Pin_EPD_CS.high() 434 | return result 435 | 436 | -------------------------------------------------------------------------------- /epd_clock/IMG_3139.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/epd_clock/IMG_3139.JPG -------------------------------------------------------------------------------- /epd_clock/IMG_3140.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/epd_clock/IMG_3140.JPG -------------------------------------------------------------------------------- /epd_clock/README.md: -------------------------------------------------------------------------------- 1 | I'm rarely impressed by alternative clock displays firstly because the 2 | traditional analog design is so damn good. You can tell the time at a glance 3 | just from the angle between the two hands: how can you improve on that? But 4 | then I found 5 | [this bizarre watch display](watch.mp4) on the internet (press download to view 6 | the video). 7 | 8 | Hard to realise on a mechanical watch ;) but interesting. And it got me 9 | thinking. This repo has analog and digital clocks on the e-paper display. In 10 | theory e-paper should be ideal for clocks with exceptionally low power 11 | consumption: zero if you switch them off! Results were poor as there are two 12 | choices. Either you refresh the display normally, in which case every (say) 1 13 | minute the screen goes through a five second rigmarole to update. Or you use 14 | fast updates, in which case you get ghosting. 15 | 16 | E-paper displays in fast mode are additive. Ghosting only occurs when you turn 17 | a black pixel white. I therefore devised this adaptation of the video which 18 | only turns pixels white with a full refresh once per hour. 19 | 20 | ![Image](IMG_3139.JPG) 21 | 22 | As in the video the display inside the circle is a window onto a dial too large 23 | to fit the screen. The dial is the minutes display, with a range of +-30 24 | minutes. The hour hand (chevron) moves to the next hour when the minutes pass 25 | 30, with the minutes before or after the hour shown by the black band on the 26 | scale. 27 | 28 | The above image shows 20 to 7 PM, that below shows nearly quarter past 2 PM. 29 | Like a conventional analog clock it encourages reading the display in the way 30 | in which we normally describe time. 31 | 32 | ![Image](IMG_3140.JPG) 33 | 34 | Implemented on a Pyboard 1.x with Pervasive Displays EPD. 35 | 36 | This demo will need minor adaptation to run. The `micropower` import is only 37 | required for my hardware, to turn on power to the display. 38 | 39 | ###### [Main README](../README.md) 40 | -------------------------------------------------------------------------------- /epd_clock/arial12.py: -------------------------------------------------------------------------------- 1 | # Code generated by font_to_py.py. 2 | # Font: Arial.ttf 3 | # Cmd: /mnt/qnap2/data/Projects/MicroPython/micropython-font-to-py/font_to_py.py Arial.ttf 12 -xr arial12.py 4 | version = '0.33' 5 | 6 | def height(): 7 | return 12 8 | 9 | def baseline(): 10 | return 9 11 | 12 | def max_width(): 13 | return 12 14 | 15 | def hmap(): 16 | return True 17 | 18 | def reverse(): 19 | return True 20 | 21 | def monospaced(): 22 | return False 23 | 24 | def min_ch(): 25 | return 32 26 | 27 | def max_ch(): 28 | return 126 29 | 30 | _font =\ 31 | b'\x07\x00\x1c\x22\x22\x20\x10\x08\x08\x00\x08\x00\x00\x00\x03\x00'\ 32 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x02\x02'\ 33 | b'\x02\x02\x02\x02\x02\x00\x02\x00\x00\x00\x04\x00\x05\x05\x05\x00'\ 34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x28\x28\x7f\x14\x14\x7f'\ 35 | b'\x14\x0a\x0a\x00\x00\x00\x07\x00\x1c\x2a\x0a\x0a\x1c\x28\x2a\x2a'\ 36 | b'\x1c\x08\x00\x00\x0b\x00\x8c\x00\x52\x00\x52\x00\x32\x00\xac\x01'\ 37 | b'\x60\x02\x50\x02\x50\x02\x88\x01\x00\x00\x00\x00\x00\x00\x08\x00'\ 38 | b'\x18\x24\x24\x14\x0c\x52\x22\x62\x9c\x00\x00\x00\x02\x00\x02\x02'\ 39 | b'\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x08\x04\x04\x02'\ 40 | b'\x02\x02\x02\x02\x04\x04\x08\x00\x04\x00\x01\x02\x02\x04\x04\x04'\ 41 | b'\x04\x04\x02\x02\x01\x00\x05\x00\x04\x1f\x04\x0a\x00\x00\x00\x00'\ 42 | b'\x00\x00\x00\x00\x07\x00\x00\x00\x08\x08\x3e\x08\x08\x00\x00\x00'\ 43 | b'\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x02\x00'\ 44 | b'\x04\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x03\x00'\ 45 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x04\x04'\ 46 | b'\x02\x02\x02\x02\x02\x01\x01\x00\x00\x00\x07\x00\x1c\x22\x22\x22'\ 47 | b'\x22\x22\x22\x22\x1c\x00\x00\x00\x07\x00\x08\x0c\x0a\x08\x08\x08'\ 48 | b'\x08\x08\x08\x00\x00\x00\x07\x00\x1c\x22\x20\x20\x10\x10\x08\x04'\ 49 | b'\x3e\x00\x00\x00\x07\x00\x1c\x22\x20\x20\x18\x20\x20\x22\x1c\x00'\ 50 | b'\x00\x00\x07\x00\x10\x18\x18\x14\x14\x12\x3e\x10\x10\x00\x00\x00'\ 51 | b'\x07\x00\x3c\x04\x02\x1e\x22\x20\x20\x22\x1c\x00\x00\x00\x07\x00'\ 52 | b'\x1c\x22\x02\x1a\x26\x22\x22\x22\x1c\x00\x00\x00\x07\x00\x3e\x10'\ 53 | b'\x10\x08\x08\x08\x04\x04\x04\x00\x00\x00\x07\x00\x1c\x22\x22\x22'\ 54 | b'\x1c\x22\x22\x22\x1c\x00\x00\x00\x07\x00\x1c\x22\x22\x22\x32\x2c'\ 55 | b'\x20\x22\x1c\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x00\x00'\ 56 | b'\x02\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x00\x00\x02\x02'\ 57 | b'\x02\x00\x07\x00\x00\x00\x20\x1c\x02\x1c\x20\x00\x00\x00\x00\x00'\ 58 | b'\x07\x00\x00\x00\x00\x3f\x00\x00\x3f\x00\x00\x00\x00\x00\x07\x00'\ 59 | b'\x00\x00\x02\x1c\x20\x1c\x02\x00\x00\x00\x00\x00\x07\x00\x1c\x22'\ 60 | b'\x22\x20\x10\x08\x08\x00\x08\x00\x00\x00\x0c\x00\xe0\x01\x18\x06'\ 61 | b'\x04\x04\x64\x09\x92\x09\x8a\x08\x8a\x08\x8a\x04\xf2\x03\x04\x08'\ 62 | b'\x08\x06\xf0\x01\x07\x00\x08\x14\x14\x14\x22\x3e\x22\x41\x41\x00'\ 63 | b'\x00\x00\x08\x00\x3e\x42\x42\x42\x7e\x42\x42\x42\x3e\x00\x00\x00'\ 64 | b'\x09\x00\x38\x00\x44\x00\x82\x00\x02\x00\x02\x00\x02\x00\x82\x00'\ 65 | b'\x44\x00\x38\x00\x00\x00\x00\x00\x00\x00\x09\x00\x3e\x00\x42\x00'\ 66 | b'\x82\x00\x82\x00\x82\x00\x82\x00\x82\x00\x42\x00\x3e\x00\x00\x00'\ 67 | b'\x00\x00\x00\x00\x08\x00\x7e\x02\x02\x02\x7e\x02\x02\x02\x7e\x00'\ 68 | b'\x00\x00\x07\x00\x3e\x02\x02\x02\x1e\x02\x02\x02\x02\x00\x00\x00'\ 69 | b'\x09\x00\x38\x00\x44\x00\x82\x00\x02\x00\xe2\x00\x82\x00\x82\x00'\ 70 | b'\x44\x00\x38\x00\x00\x00\x00\x00\x00\x00\x09\x00\x82\x00\x82\x00'\ 71 | b'\x82\x00\x82\x00\xfe\x00\x82\x00\x82\x00\x82\x00\x82\x00\x00\x00'\ 72 | b'\x00\x00\x00\x00\x03\x00\x02\x02\x02\x02\x02\x02\x02\x02\x02\x00'\ 73 | b'\x00\x00\x06\x00\x10\x10\x10\x10\x10\x10\x11\x11\x0e\x00\x00\x00'\ 74 | b'\x08\x00\x82\x42\x22\x12\x0a\x16\x22\x42\x82\x00\x00\x00\x07\x00'\ 75 | b'\x02\x02\x02\x02\x02\x02\x02\x02\x7e\x00\x00\x00\x09\x00\x82\x00'\ 76 | b'\xc6\x00\xc6\x00\xaa\x00\xaa\x00\xaa\x00\xaa\x00\x92\x00\x92\x00'\ 77 | b'\x00\x00\x00\x00\x00\x00\x09\x00\x82\x00\x86\x00\x8a\x00\x8a\x00'\ 78 | b'\x92\x00\xa2\x00\xa2\x00\xc2\x00\x82\x00\x00\x00\x00\x00\x00\x00'\ 79 | b'\x09\x00\x38\x00\x44\x00\x82\x00\x82\x00\x82\x00\x82\x00\x82\x00'\ 80 | b'\x44\x00\x38\x00\x00\x00\x00\x00\x00\x00\x08\x00\x3e\x42\x42\x42'\ 81 | b'\x3e\x02\x02\x02\x02\x00\x00\x00\x09\x00\x38\x00\x44\x00\x82\x00'\ 82 | b'\x82\x00\x82\x00\x82\x00\xb2\x00\x44\x00\xb8\x00\x00\x00\x00\x00'\ 83 | b'\x00\x00\x09\x00\x7e\x00\x82\x00\x82\x00\x82\x00\x7e\x00\x22\x00'\ 84 | b'\x42\x00\x42\x00\x82\x00\x00\x00\x00\x00\x00\x00\x08\x00\x3c\x42'\ 85 | b'\x42\x02\x3c\x40\x42\x42\x3c\x00\x00\x00\x07\x00\x7f\x08\x08\x08'\ 86 | b'\x08\x08\x08\x08\x08\x00\x00\x00\x09\x00\x82\x00\x82\x00\x82\x00'\ 87 | b'\x82\x00\x82\x00\x82\x00\x82\x00\x44\x00\x38\x00\x00\x00\x00\x00'\ 88 | b'\x00\x00\x07\x00\x41\x41\x22\x22\x22\x14\x14\x08\x08\x00\x00\x00'\ 89 | b'\x0b\x00\x21\x04\x51\x04\x51\x02\x52\x02\x8a\x02\x8a\x02\x8a\x02'\ 90 | b'\x04\x01\x04\x01\x00\x00\x00\x00\x00\x00\x07\x00\x41\x22\x22\x14'\ 91 | b'\x08\x14\x22\x22\x41\x00\x00\x00\x07\x00\x41\x22\x22\x14\x08\x08'\ 92 | b'\x08\x08\x08\x00\x00\x00\x07\x00\x7e\x20\x10\x10\x08\x04\x04\x02'\ 93 | b'\x7f\x00\x00\x00\x03\x00\x06\x02\x02\x02\x02\x02\x02\x02\x02\x02'\ 94 | b'\x06\x00\x03\x00\x01\x01\x02\x02\x02\x02\x02\x04\x04\x00\x00\x00'\ 95 | b'\x03\x00\x03\x02\x02\x02\x02\x02\x02\x02\x02\x02\x03\x00\x05\x00'\ 96 | b'\x04\x0a\x0a\x0a\x11\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00'\ 97 | b'\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x04\x00\x02\x04\x00\x00'\ 98 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x1c\x22\x20\x3c'\ 99 | b'\x22\x32\x2c\x00\x00\x00\x07\x00\x02\x02\x1a\x26\x22\x22\x22\x26'\ 100 | b'\x1a\x00\x00\x00\x06\x00\x00\x00\x0c\x12\x02\x02\x02\x12\x0c\x00'\ 101 | b'\x00\x00\x07\x00\x20\x20\x2c\x32\x22\x22\x22\x32\x2c\x00\x00\x00'\ 102 | b'\x07\x00\x00\x00\x1c\x22\x22\x3e\x02\x22\x1c\x00\x00\x00\x04\x00'\ 103 | b'\x0c\x02\x07\x02\x02\x02\x02\x02\x02\x00\x00\x00\x07\x00\x00\x00'\ 104 | b'\x2c\x32\x22\x22\x22\x32\x2c\x20\x1e\x00\x07\x00\x02\x02\x1a\x26'\ 105 | b'\x22\x22\x22\x22\x22\x00\x00\x00\x03\x00\x02\x00\x02\x02\x02\x02'\ 106 | b'\x02\x02\x02\x00\x00\x00\x04\x00\x04\x00\x04\x04\x04\x04\x04\x04'\ 107 | b'\x04\x04\x02\x00\x06\x00\x02\x02\x22\x12\x0a\x0e\x12\x12\x22\x00'\ 108 | b'\x00\x00\x03\x00\x02\x02\x02\x02\x02\x02\x02\x02\x02\x00\x00\x00'\ 109 | b'\x0b\x00\x00\x00\x00\x00\x9a\x01\x66\x02\x22\x02\x22\x02\x22\x02'\ 110 | b'\x22\x02\x22\x02\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x1a\x26'\ 111 | b'\x22\x22\x22\x22\x22\x00\x00\x00\x07\x00\x00\x00\x1c\x22\x22\x22'\ 112 | b'\x22\x22\x1c\x00\x00\x00\x07\x00\x00\x00\x1a\x26\x22\x22\x22\x26'\ 113 | b'\x1a\x02\x02\x00\x07\x00\x00\x00\x2c\x32\x22\x22\x22\x32\x2c\x20'\ 114 | b'\x20\x00\x04\x00\x00\x00\x0a\x06\x02\x02\x02\x02\x02\x00\x00\x00'\ 115 | b'\x07\x00\x00\x00\x1c\x22\x02\x1c\x20\x22\x1c\x00\x00\x00\x03\x00'\ 116 | b'\x02\x02\x07\x02\x02\x02\x02\x02\x06\x00\x00\x00\x07\x00\x00\x00'\ 117 | b'\x22\x22\x22\x22\x22\x22\x3c\x00\x00\x00\x05\x00\x00\x00\x11\x11'\ 118 | b'\x0a\x0a\x0a\x04\x04\x00\x00\x00\x09\x00\x00\x00\x00\x00\x11\x01'\ 119 | b'\x11\x01\xaa\x00\xaa\x00\xaa\x00\x44\x00\x44\x00\x00\x00\x00\x00'\ 120 | b'\x00\x00\x05\x00\x00\x00\x11\x0a\x0a\x04\x0a\x0a\x11\x00\x00\x00'\ 121 | b'\x05\x00\x00\x00\x11\x11\x0a\x0a\x0a\x04\x04\x04\x02\x00\x05\x00'\ 122 | b'\x00\x00\x1f\x08\x08\x04\x02\x02\x1f\x00\x00\x00\x04\x00\x04\x02'\ 123 | b'\x02\x02\x02\x01\x02\x02\x02\x02\x04\x00\x03\x00\x02\x02\x02\x02'\ 124 | b'\x02\x02\x02\x02\x02\x02\x02\x00\x04\x00\x02\x04\x04\x04\x04\x08'\ 125 | b'\x04\x04\x04\x04\x02\x00\x07\x00\x00\x00\x00\x00\x4c\x32\x00\x00'\ 126 | b'\x00\x00\x00\x00' 127 | 128 | _index =\ 129 | b'\x00\x00\x0e\x00\x1c\x00\x2a\x00\x38\x00\x46\x00\x54\x00\x6e\x00'\ 130 | b'\x7c\x00\x8a\x00\x98\x00\xa6\x00\xb4\x00\xc2\x00\xd0\x00\xde\x00'\ 131 | b'\xec\x00\xfa\x00\x08\x01\x16\x01\x24\x01\x32\x01\x40\x01\x4e\x01'\ 132 | b'\x5c\x01\x6a\x01\x78\x01\x86\x01\x94\x01\xa2\x01\xb0\x01\xbe\x01'\ 133 | b'\xcc\x01\xda\x01\xf4\x01\x02\x02\x10\x02\x2a\x02\x44\x02\x52\x02'\ 134 | b'\x60\x02\x7a\x02\x94\x02\xa2\x02\xb0\x02\xbe\x02\xcc\x02\xe6\x02'\ 135 | b'\x00\x03\x1a\x03\x28\x03\x42\x03\x5c\x03\x6a\x03\x78\x03\x92\x03'\ 136 | b'\xa0\x03\xba\x03\xc8\x03\xd6\x03\xe4\x03\xf2\x03\x00\x04\x0e\x04'\ 137 | b'\x1c\x04\x2a\x04\x38\x04\x46\x04\x54\x04\x62\x04\x70\x04\x7e\x04'\ 138 | b'\x8c\x04\x9a\x04\xa8\x04\xb6\x04\xc4\x04\xd2\x04\xe0\x04\xfa\x04'\ 139 | b'\x08\x05\x16\x05\x24\x05\x32\x05\x40\x05\x4e\x05\x5c\x05\x6a\x05'\ 140 | b'\x78\x05\x92\x05\xa0\x05\xae\x05\xbc\x05\xca\x05\xd8\x05\xe6\x05'\ 141 | b'\xf4\x05' 142 | 143 | _mvfont = memoryview(_font) 144 | _mvi = memoryview(_index) 145 | ifb = lambda l : l[0] | (l[1] << 8) 146 | 147 | def get_ch(ch): 148 | oc = ord(ch) 149 | ioff = 2 * (oc - 32 + 1) if oc >= 32 and oc <= 126 else 0 150 | doff = ifb(_mvi[ioff : ]) 151 | width = ifb(_mvfont[doff : ]) 152 | 153 | next_offs = doff + 2 + ((width - 1)//8 + 1) * 12 154 | return _mvfont[doff + 2:next_offs], 12, width 155 | 156 | -------------------------------------------------------------------------------- /epd_clock/arial15.py: -------------------------------------------------------------------------------- 1 | # Code generated by font_to_py.py. 2 | # Font: Arial.ttf 3 | # Cmd: /mnt/qnap2/data/Projects/MicroPython/micropython-font-to-py/font_to_py.py Arial.ttf 15 -xr arial15.py 4 | version = '0.33' 5 | 6 | def height(): 7 | return 15 8 | 9 | def baseline(): 10 | return 12 11 | 12 | def max_width(): 13 | return 15 14 | 15 | def hmap(): 16 | return True 17 | 18 | def reverse(): 19 | return True 20 | 21 | def monospaced(): 22 | return False 23 | 24 | def min_ch(): 25 | return 32 26 | 27 | def max_ch(): 28 | return 126 29 | 30 | _font =\ 31 | b'\x08\x00\x00\x3c\x42\x42\x40\x20\x10\x08\x08\x08\x00\x08\x00\x00'\ 32 | b'\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 33 | b'\x00\x00\x05\x00\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x00\x04'\ 34 | b'\x00\x00\x00\x05\x00\x00\x0a\x0a\x0a\x0a\x00\x00\x00\x00\x00\x00'\ 35 | b'\x00\x00\x00\x00\x08\x00\x00\x48\x48\x48\xff\x24\x24\x24\xff\x12'\ 36 | b'\x12\x12\x00\x00\x00\x08\x00\x08\x3c\x6a\x49\x09\x0a\x1c\x28\x48'\ 37 | b'\x49\x2a\x1c\x08\x00\x00\x0d\x00\x00\x00\x0c\x02\x12\x01\x12\x01'\ 38 | b'\x92\x00\x92\x00\x4c\x06\x20\x09\x20\x09\x10\x09\x10\x09\x08\x06'\ 39 | b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x38\x00\x44\x00\x44\x00'\ 40 | b'\x44\x00\x28\x00\x1c\x00\x22\x01\x22\x01\xc2\x00\x42\x01\x3c\x03'\ 41 | b'\x00\x00\x00\x00\x00\x00\x03\x00\x00\x02\x02\x02\x02\x00\x00\x00'\ 42 | b'\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x08\x04\x04\x02\x02\x02'\ 43 | b'\x02\x02\x02\x02\x06\x04\x04\x08\x05\x00\x00\x02\x04\x04\x08\x08'\ 44 | b'\x08\x08\x08\x08\x08\x0c\x04\x04\x02\x06\x00\x00\x04\x1f\x04\x0a'\ 45 | b'\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 46 | b'\x00\x00\x10\x00\x10\x00\x10\x00\xfe\x00\x10\x00\x10\x00\x10\x00'\ 47 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00'\ 48 | b'\x00\x00\x00\x00\x00\x00\x00\x04\x04\x04\x00\x05\x00\x00\x00\x00'\ 49 | b'\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'\ 50 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00'\ 51 | b'\x08\x08\x04\x04\x04\x02\x02\x02\x02\x01\x01\x00\x00\x00\x08\x00'\ 52 | b'\x00\x3c\x42\x42\x42\x42\x42\x42\x42\x42\x42\x3c\x00\x00\x00\x08'\ 53 | b'\x00\x00\x10\x18\x14\x10\x10\x10\x10\x10\x10\x10\x10\x00\x00\x00'\ 54 | b'\x08\x00\x00\x3c\x42\x42\x40\x40\x20\x20\x10\x08\x04\x7e\x00\x00'\ 55 | b'\x00\x08\x00\x00\x3c\x42\x42\x40\x40\x38\x40\x40\x42\x42\x3c\x00'\ 56 | b'\x00\x00\x08\x00\x00\x20\x30\x28\x24\x24\x22\x21\x7f\x20\x20\x20'\ 57 | b'\x00\x00\x00\x08\x00\x00\x7c\x04\x04\x02\x3e\x42\x40\x40\x42\x22'\ 58 | b'\x3c\x00\x00\x00\x08\x00\x00\x3c\x42\x42\x02\x3a\x46\x42\x42\x42'\ 59 | b'\x42\x3c\x00\x00\x00\x08\x00\x00\x7e\x40\x20\x20\x10\x10\x10\x08'\ 60 | b'\x08\x08\x08\x00\x00\x00\x08\x00\x00\x3c\x42\x42\x42\x42\x3c\x42'\ 61 | b'\x42\x42\x42\x3c\x00\x00\x00\x08\x00\x00\x3c\x42\x42\x42\x42\x62'\ 62 | b'\x5c\x40\x42\x42\x3c\x00\x00\x00\x04\x00\x00\x00\x00\x00\x02\x00'\ 63 | b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x00\x00\x02'\ 64 | b'\x00\x00\x00\x00\x00\x00\x02\x02\x02\x00\x09\x00\x00\x00\x00\x00'\ 65 | b'\x00\x00\x80\x00\x70\x00\x0c\x00\x02\x00\x0c\x00\x70\x00\x80\x00'\ 66 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 67 | b'\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00'\ 68 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 69 | b'\x00\x00\x02\x00\x1c\x00\x60\x00\x80\x00\x60\x00\x1c\x00\x02\x00'\ 70 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x3c\x42\x42'\ 71 | b'\x40\x20\x10\x08\x08\x08\x00\x08\x00\x00\x00\x0f\x00\x00\x00\xe0'\ 72 | b'\x03\x18\x0c\x04\x10\xc2\x35\x32\x26\x11\x24\x09\x22\x09\x22\x09'\ 73 | b'\x12\x11\x13\xf2\x0e\x02\x20\x0c\x18\xf0\x07\x09\x00\x00\x00\x10'\ 74 | b'\x00\x28\x00\x28\x00\x28\x00\x44\x00\x44\x00\x82\x00\xfe\x00\x82'\ 75 | b'\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\xfe'\ 76 | b'\x00\x02\x01\x02\x01\x02\x01\x82\x00\xfe\x00\x02\x01\x02\x01\x02'\ 77 | b'\x01\x02\x01\xfe\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\xf0'\ 78 | b'\x00\x0c\x01\x04\x02\x02\x00\x02\x00\x02\x00\x02\x00\x02\x00\x04'\ 79 | b'\x02\x0c\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\xfe'\ 80 | b'\x00\x02\x01\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'\ 81 | b'\x02\x02\x01\xfe\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\xfe'\ 82 | b'\x01\x02\x00\x02\x00\x02\x00\x02\x00\xfe\x01\x02\x00\x02\x00\x02'\ 83 | b'\x00\x02\x00\xfe\x01\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\xfe'\ 84 | b'\x00\x02\x00\x02\x00\x02\x00\x02\x00\x7e\x00\x02\x00\x02\x00\x02'\ 85 | b'\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\xf8'\ 86 | b'\x00\x04\x01\x02\x02\x02\x00\x02\x00\xc2\x03\x02\x02\x02\x02\x02'\ 87 | b'\x02\x04\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x02'\ 88 | b'\x01\x02\x01\x02\x01\x02\x01\x02\x01\xfe\x01\x02\x01\x02\x01\x02'\ 89 | b'\x01\x02\x01\x02\x01\x00\x00\x00\x00\x00\x00\x03\x00\x00\x02\x02'\ 90 | b'\x02\x02\x02\x02\x02\x02\x02\x02\x02\x00\x00\x00\x07\x00\x00\x20'\ 91 | b'\x20\x20\x20\x20\x20\x20\x20\x21\x21\x1e\x00\x00\x00\x0a\x00\x00'\ 92 | b'\x00\x02\x01\x82\x00\x42\x00\x22\x00\x12\x00\x2a\x00\x46\x00\x42'\ 93 | b'\x00\x82\x00\x02\x01\x02\x02\x00\x00\x00\x00\x00\x00\x08\x00\x00'\ 94 | b'\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\xfe\x00\x00\x00\x0b\x00'\ 95 | b'\x00\x00\x02\x02\x06\x03\x06\x03\x8a\x02\x8a\x02\x8a\x02\x52\x02'\ 96 | b'\x52\x02\x52\x02\x22\x02\x22\x02\x00\x00\x00\x00\x00\x00\x0a\x00'\ 97 | b'\x00\x00\x02\x01\x06\x01\x0a\x01\x0a\x01\x12\x01\x12\x01\x22\x01'\ 98 | b'\x42\x01\x42\x01\x82\x01\x02\x01\x00\x00\x00\x00\x00\x00\x0c\x00'\ 99 | b'\x00\x00\xf0\x00\x0c\x03\x04\x02\x02\x04\x02\x04\x02\x04\x02\x04'\ 100 | b'\x02\x04\x04\x02\x0c\x03\xf0\x00\x00\x00\x00\x00\x00\x00\x0a\x00'\ 101 | b'\x00\x00\xfe\x00\x02\x01\x02\x01\x02\x01\x02\x01\xfe\x00\x02\x00'\ 102 | b'\x02\x00\x02\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x0c\x00'\ 103 | b'\x00\x00\xf0\x00\x0c\x03\x04\x02\x02\x04\x02\x04\x02\x04\x02\x04'\ 104 | b'\x02\x04\xc4\x02\x0c\x03\xf0\x04\x00\x00\x00\x00\x00\x00\x0b\x00'\ 105 | b'\x00\x00\xfe\x01\x02\x02\x02\x02\x02\x02\x02\x02\xfe\x01\x42\x00'\ 106 | b'\x82\x00\x82\x00\x02\x01\x02\x02\x00\x00\x00\x00\x00\x00\x0a\x00'\ 107 | b'\x00\x00\x78\x00\x84\x00\x02\x01\x02\x00\x1e\x00\xe0\x00\x00\x01'\ 108 | b'\x00\x01\x02\x01\x86\x00\x78\x00\x00\x00\x00\x00\x00\x00\x09\x00'\ 109 | b'\x00\x00\xff\x01\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\ 110 | b'\x10\x00\x10\x00\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x0a\x00'\ 111 | b'\x00\x00\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01'\ 112 | b'\x02\x01\x02\x01\x84\x00\x78\x00\x00\x00\x00\x00\x00\x00\x09\x00'\ 113 | b'\x00\x00\x01\x01\x01\x01\x82\x00\x82\x00\x44\x00\x44\x00\x44\x00'\ 114 | b'\x28\x00\x28\x00\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x0f\x00'\ 115 | b'\x00\x00\x81\x40\x41\x41\x42\x21\x42\x21\x22\x22\x22\x22\x24\x12'\ 116 | b'\x14\x14\x14\x14\x08\x08\x08\x08\x00\x00\x00\x00\x00\x00\x09\x00'\ 117 | b'\x00\x00\x01\x01\x82\x00\x44\x00\x44\x00\x28\x00\x30\x00\x28\x00'\ 118 | b'\x44\x00\x44\x00\x82\x00\x01\x01\x00\x00\x00\x00\x00\x00\x09\x00'\ 119 | b'\x00\x00\x01\x01\x82\x00\x44\x00\x44\x00\x28\x00\x10\x00\x10\x00'\ 120 | b'\x10\x00\x10\x00\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x08\x00'\ 121 | b'\x00\xfe\x40\x20\x20\x10\x08\x08\x04\x04\x02\xff\x00\x00\x00\x04'\ 122 | b'\x00\x00\x0e\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x0e'\ 123 | b'\x04\x00\x00\x01\x01\x02\x02\x02\x04\x04\x04\x04\x08\x08\x00\x00'\ 124 | b'\x00\x04\x00\x00\x07\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04'\ 125 | b'\x04\x07\x05\x00\x00\x04\x0a\x0a\x0a\x11\x11\x00\x00\x00\x00\x00'\ 126 | b'\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 127 | b'\x00\xff\x00\x00\x05\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00'\ 128 | b'\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x3c\x42\x40\x78\x44'\ 129 | b'\x42\x62\x5c\x00\x00\x00\x08\x00\x00\x02\x02\x02\x3a\x46\x42\x42'\ 130 | b'\x42\x42\x46\x3a\x00\x00\x00\x08\x00\x00\x00\x00\x00\x3c\x42\x02'\ 131 | b'\x02\x02\x02\x42\x3c\x00\x00\x00\x08\x00\x00\x40\x40\x40\x5c\x62'\ 132 | b'\x42\x42\x42\x42\x62\x5c\x00\x00\x00\x08\x00\x00\x00\x00\x00\x3c'\ 133 | b'\x42\x42\x7e\x02\x02\x42\x3c\x00\x00\x00\x04\x00\x00\x0c\x02\x02'\ 134 | b'\x0f\x02\x02\x02\x02\x02\x02\x02\x00\x00\x00\x08\x00\x00\x00\x00'\ 135 | b'\x00\x5c\x62\x42\x42\x42\x42\x62\x5c\x40\x42\x3c\x08\x00\x00\x02'\ 136 | b'\x02\x02\x3a\x46\x42\x42\x42\x42\x42\x42\x00\x00\x00\x03\x00\x00'\ 137 | b'\x02\x00\x00\x02\x02\x02\x02\x02\x02\x02\x02\x00\x00\x00\x04\x00'\ 138 | b'\x00\x04\x00\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x03\x07'\ 139 | b'\x00\x00\x02\x02\x02\x42\x22\x12\x0a\x16\x22\x22\x42\x00\x00\x00'\ 140 | b'\x03\x00\x00\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x00\x00'\ 141 | b'\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3a\x07\xc6\x08\x42'\ 142 | b'\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x00\x00\x00\x00\x00'\ 143 | b'\x00\x08\x00\x00\x00\x00\x00\x3a\x46\x42\x42\x42\x42\x42\x42\x00'\ 144 | b'\x00\x00\x08\x00\x00\x00\x00\x00\x3c\x42\x42\x42\x42\x42\x42\x3c'\ 145 | b'\x00\x00\x00\x08\x00\x00\x00\x00\x00\x3a\x46\x42\x42\x42\x42\x46'\ 146 | b'\x3a\x02\x02\x02\x08\x00\x00\x00\x00\x00\x5c\x62\x42\x42\x42\x42'\ 147 | b'\x62\x5c\x40\x40\x40\x05\x00\x00\x00\x00\x00\x1a\x06\x02\x02\x02'\ 148 | b'\x02\x02\x02\x00\x00\x00\x08\x00\x00\x00\x00\x00\x3c\x42\x02\x0c'\ 149 | b'\x30\x40\x42\x3c\x00\x00\x00\x04\x00\x00\x00\x02\x02\x0f\x02\x02'\ 150 | b'\x02\x02\x02\x02\x0e\x00\x00\x00\x08\x00\x00\x00\x00\x00\x42\x42'\ 151 | b'\x42\x42\x42\x42\x62\x5c\x00\x00\x00\x07\x00\x00\x00\x00\x00\x41'\ 152 | b'\x41\x22\x22\x14\x14\x08\x08\x00\x00\x00\x0b\x00\x00\x00\x00\x00'\ 153 | b'\x00\x00\x00\x00\x21\x04\x51\x04\x52\x02\x52\x02\x8a\x02\x8a\x02'\ 154 | b'\x84\x01\x04\x01\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00'\ 155 | b'\x41\x22\x14\x08\x08\x14\x22\x41\x00\x00\x00\x07\x00\x00\x00\x00'\ 156 | b'\x00\x41\x41\x22\x22\x14\x14\x14\x08\x08\x08\x06\x08\x00\x00\x00'\ 157 | b'\x00\x00\x7e\x20\x10\x10\x08\x08\x04\x7e\x00\x00\x00\x05\x00\x00'\ 158 | b'\x18\x04\x04\x04\x04\x04\x04\x03\x04\x04\x04\x04\x04\x18\x03\x00'\ 159 | b'\x00\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x05'\ 160 | b'\x00\x00\x03\x04\x04\x04\x04\x04\x04\x18\x04\x04\x04\x04\x04\x03'\ 161 | b'\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8e\x00'\ 162 | b'\x71\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 163 | 164 | _index =\ 165 | b'\x00\x00\x11\x00\x22\x00\x33\x00\x44\x00\x55\x00\x66\x00\x86\x00'\ 166 | b'\xa6\x00\xb7\x00\xc8\x00\xd9\x00\xea\x00\x0a\x01\x1b\x01\x2c\x01'\ 167 | b'\x3d\x01\x4e\x01\x5f\x01\x70\x01\x81\x01\x92\x01\xa3\x01\xb4\x01'\ 168 | b'\xc5\x01\xd6\x01\xe7\x01\xf8\x01\x09\x02\x1a\x02\x3a\x02\x5a\x02'\ 169 | b'\x7a\x02\x8b\x02\xab\x02\xcb\x02\xeb\x02\x0b\x03\x2b\x03\x4b\x03'\ 170 | b'\x6b\x03\x8b\x03\xab\x03\xbc\x03\xcd\x03\xed\x03\xfe\x03\x1e\x04'\ 171 | b'\x3e\x04\x5e\x04\x7e\x04\x9e\x04\xbe\x04\xde\x04\xfe\x04\x1e\x05'\ 172 | b'\x3e\x05\x5e\x05\x7e\x05\x9e\x05\xaf\x05\xc0\x05\xd1\x05\xe2\x05'\ 173 | b'\xf3\x05\x04\x06\x15\x06\x26\x06\x37\x06\x48\x06\x59\x06\x6a\x06'\ 174 | b'\x7b\x06\x8c\x06\x9d\x06\xae\x06\xbf\x06\xd0\x06\xe1\x06\x01\x07'\ 175 | b'\x12\x07\x23\x07\x34\x07\x45\x07\x56\x07\x67\x07\x78\x07\x89\x07'\ 176 | b'\x9a\x07\xba\x07\xcb\x07\xdc\x07\xed\x07\xfe\x07\x0f\x08\x20\x08'\ 177 | b'\x40\x08' 178 | 179 | _mvfont = memoryview(_font) 180 | _mvi = memoryview(_index) 181 | ifb = lambda l : l[0] | (l[1] << 8) 182 | 183 | def get_ch(ch): 184 | oc = ord(ch) 185 | ioff = 2 * (oc - 32 + 1) if oc >= 32 and oc <= 126 else 0 186 | doff = ifb(_mvi[ioff : ]) 187 | width = ifb(_mvfont[doff : ]) 188 | 189 | next_offs = doff + 2 + ((width - 1)//8 + 1) * 15 190 | return _mvfont[doff + 2:next_offs], 15, width 191 | 192 | -------------------------------------------------------------------------------- /epd_clock/newclock.py: -------------------------------------------------------------------------------- 1 | # newclock.py Unusual clock display for Embedded Artists' 2.7 inch E-paper Display. 2 | 3 | # Released under the Apache license: see LICENSE 4 | # Copyright (c) Peter Hinch 2020 5 | 6 | # Tested on a Pyboard 1.0 7 | from cmath import rect, phase 8 | from math import sin, cos, pi 9 | from micropython import const 10 | import gc 11 | import epaper 12 | from micropower import PowerController # Only required for my hardware 13 | import time 14 | import arial15 as font1 15 | import arial12 as font2 16 | 17 | # **** BEGIN DISPLAY CONSTANTS **** 18 | THETA = pi/3 # Intersection of arc with unit circle 19 | PHI = pi/12 # Arc is +-30 minute segment 20 | CRADIUS = const(70) # Circle radius in pixels 21 | CXOFFSET = const(17) # Circle offset in pixels 22 | CYOFFSET = const(17) 23 | ST = CRADIUS + CXOFFSET 24 | # Locations for external hours strings. Hand optimised. 25 | TXT = ((ST + 38, CYOFFSET - 4), 26 | (ST + 64, CYOFFSET + 22), 27 | (ST + 75, CYOFFSET + 63), 28 | (ST + 64, CYOFFSET + 101), 29 | (ST + 39, CYOFFSET + 131), 30 | (ST - 3, CYOFFSET + 143), 31 | (ST - 47, CYOFFSET + 131), 32 | (ST - 73, CYOFFSET + 101), 33 | (ST - 83, CYOFFSET + 63), 34 | (ST - 79, CYOFFSET + 22), 35 | (ST - 50, CYOFFSET - 5), 36 | (ST - 7, CYOFFSET - 17)) 37 | # Locations for internal text 38 | TXTI = ((ST -48, CYOFFSET + 26, ST + 50, CYOFFSET + 75), 39 | (ST - 22, CYOFFSET + 13, ST + 35, CYOFFSET + 103), 40 | (ST + 2, CYOFFSET + 10, ST + 8, CYOFFSET + 123), 41 | (ST + 25, CYOFFSET + 28, ST - 25, CYOFFSET + 118), 42 | (ST + 38, CYOFFSET + 56, ST - 45, CYOFFSET + 105), 43 | (ST + 43, CYOFFSET + 78, ST - 60, CYOFFSET + 78), 44 | (ST + 32, CYOFFSET + 105, ST - 65, CYOFFSET + 53), 45 | (ST + 2, CYOFFSET + 118, ST - 48, CYOFFSET + 27), 46 | (ST - 20, CYOFFSET + 123, ST - 20, CYOFFSET + 8), 47 | (ST - 40, CYOFFSET + 106, ST + 10, CYOFFSET + 8), 48 | (ST - 55, CYOFFSET + 76, ST + 33, CYOFFSET + 27), 49 | (ST - 60, CYOFFSET + 52, ST + 50, CYOFFSET + 52)) 50 | 51 | # **** BEGIN DERIVED AND OTHER CONSTANTS **** 52 | 53 | days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 54 | 'Sunday') 55 | months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 56 | 'Aug', 'Sept', 'Oct', 'Nov', 'Dec') 57 | 58 | # Translation vector from unit circle to display coordinates 59 | RADIUS = sin(THETA) / sin(PHI) 60 | XLT = cos(THETA) - RADIUS * cos(PHI) # Convert arc relative to [0,0] relative 61 | RV = pi / 360 # Interpolate arc to 1 minute 62 | TV = RV / 5 # Small increment << I minute 63 | OR = cos(THETA) - RADIUS * cos(PHI) + 0j # Origin of arc 64 | 65 | # **** BEGIN DAYLIGHT SAVING **** 66 | # This code returns the UK Time including daylight saving 67 | # Adapted from https://forum.micropython.org/viewtopic.php?f=2&t=4034 68 | # Winter UTC Summer (BST) is UTC+1H 69 | # Changes happen last Sundays of March (BST) and October (UTC) at 01:00 UTC 70 | # Ref. formulas : http://www.webexhibits.org/daylightsaving/i.html 71 | # Since 1996, valid through 2099 72 | def gbtime(): 73 | year = time.localtime()[0] #get current year 74 | HHMarch = time.mktime((year,3 ,(31-(int(5*year/4+4))%7),1,0,0,0,0,0)) #Time of March change to BST 75 | HHOctober = time.mktime((year,10,(31-(int(5*year/4+1))%7),1,0,0,0,0,0)) #Time of October change to UTC 76 | now=time.time() 77 | if now < HHMarch : # we are before last sunday of march 78 | cet=time.localtime(now) # UTC 79 | elif now < HHOctober : # we are before last sunday of october 80 | cet=time.localtime(now+3600) # BST: UTC+1H 81 | else: # we are after last sunday of october 82 | cet=time.localtime(now) # UTC 83 | return(cet) 84 | 85 | # **** BEGIN VECTOR CODE **** 86 | # A vector is a line on the complex plane defined by a tuple of two complex 87 | # numbers. Vectors presented for display lie in the unit circle. 88 | 89 | # Generate vectors comprising sectors of an arc. hrs defines location of arc, 90 | # angle its length. 91 | # 1 <= hrs <= 12 0 <= angle < 60 in normal use 92 | # To print full arc angle == 60 93 | def arc(hrs, angle=60, mul=1.0): 94 | vs = rect(RADIUS * mul, PHI) # Coords relative to arc origin 95 | ve = rect(RADIUS * mul, PHI) 96 | pe = PHI - angle * RV + TV 97 | rv = rect(1, -RV) # Rotation vector for 1 minute (about OR) 98 | rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0]) 99 | while phase(vs) > pe: 100 | ve *= rv 101 | # Translate to 0, 0 102 | yield ((vs + XLT) * rot, (ve + XLT) * rot) 103 | vs *= rv 104 | 105 | # Currently unused. Draw the unit circle in a way which may readily be 106 | # ported to other displays. 107 | def circle(): 108 | segs = 60 109 | phi = 2 * pi / segs 110 | rv = rect(1, phi) 111 | vs = 1 + 0j 112 | ve = vs 113 | for _ in range(segs): 114 | ve *= rv 115 | yield vs, ve 116 | vs *= rv 117 | 118 | # Generate vectors for the minutes ticks 119 | def ticks(hrs, length): 120 | vs = rect(RADIUS, PHI) # Coords relative to arc origin 121 | ve = rect(RADIUS - length, PHI) # Short tick 122 | ve1 = rect(RADIUS - 1.5 * length, PHI) # Long tick 123 | ve2 = rect(RADIUS - 2.0 * length, PHI) # Extra long tick 124 | rv = rect(1, -5 * RV) # Rotation vector for 5 minutes (about OR) 125 | rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0]) 126 | for n in range(13): 127 | # Translate to 0, 0 128 | if n == 6: # Overdrawn by hour pointer: visually cleaner if we skip 129 | yield 130 | elif n % 3 == 0: 131 | yield ((vs + XLT) * rot, (ve2 + XLT) * rot) # Extra Long 132 | elif n % 2 == 0: 133 | yield ((vs + XLT) * rot, (ve1 + XLT) * rot) # Long 134 | else: 135 | yield ((vs + XLT) * rot, (ve + XLT) * rot) # Short 136 | vs *= rv 137 | ve *= rv 138 | ve1 *= rv 139 | ve2 *= rv 140 | 141 | # Generate vectors for the hour chevron 142 | def hour(hrs): 143 | vs = -1 + 0j 144 | ve = 0.96 + 0j 145 | rot = rect(1, (3 - hrs) * pi / 6) # hrs rotation (about [0,0]) 146 | yield (vs * rot, ve * rot) 147 | vs = 0.85 + 0.1j 148 | yield (vs * rot, ve * rot) 149 | vs = 0.85 - 0.1j 150 | yield (vs * rot, ve * rot) 151 | 152 | # Draw a vector scaling it for display and converting to integer x, y 153 | # BEWARE unconventional Y coordinate on Pervasive Displays EPD. 154 | def draw_vec(vec, width=1): 155 | vs, ve = vec 156 | vs = vs.real - 1j * vs.imag # Invert for weird coordinate system 157 | ve = ve.real - 1j * ve.imag 158 | vs += 1 + 1j # Real and imag now positive 159 | ve += 1 + 1j 160 | xlat = CXOFFSET + 1j * CYOFFSET # Translation vector 161 | vs = vs * CRADIUS + xlat # Scale and shift to graphics coords 162 | ve = ve * CRADIUS + xlat 163 | e.line(round(vs.real), round(vs.imag), round(ve.real), round(ve.imag), width) 164 | 165 | # **** BEGIN POPULATE DISPLAY **** 166 | 167 | def populate(e, angle, hrs): 168 | # Draw graphics. Built-in circle method provides a slightly better visual. 169 | e.circle(CXOFFSET + CRADIUS, CYOFFSET + CRADIUS, CRADIUS, 1) 170 | # Easily portable alternative: 171 | #for vec in circle(): 172 | #draw_vec(vec) 173 | for vec in arc(hrs): # -30 to +30 arc 174 | draw_vec(vec) # Arc 175 | for vec in ticks(hrs, 0.1): # Ticks 176 | if vec is not None: 177 | draw_vec(vec) 178 | for vec in arc(hrs, angle, 0.99): # Elapsed minutes arc 179 | draw_vec(vec, 3) # Elapsed minutes 180 | for vec in hour(hrs): # Chevron 181 | draw_vec(vec) 182 | # Draw text outside circle 183 | with e.font(font1): 184 | for n, pos in enumerate(TXT): 185 | e.locate(*pos) 186 | e.puts(str(n +1)) 187 | e.locate(0, 0) 188 | e.puts('AM' if t[3] < 11 else 'PM') 189 | # Draw internal minutes numbers 190 | with e.font(font2): 191 | pos = TXTI[hrs - 1] # Get locations 192 | e.locate(*pos[0:2]) 193 | e.puts('-30') 194 | e.locate(*pos[2:4]) 195 | e.puts('30') 196 | # Day/date text on RHS 197 | with e.font(font1): 198 | x = 175 199 | y = 0 200 | e.locate(x, y) 201 | e.puts(days[t[6]]) 202 | y += font1.height() + 2 203 | e.locate(x, y) 204 | e.puts('{} {} {}'.format(t[2], months[t[1] - 1], t[0])) 205 | 206 | # **** BEGIN REQUIRED FOR MY HARDWARE **** 207 | p = PowerController(pin_active_high = 'Y12', pin_active_low = 'Y11') 208 | p.power_up() 209 | # **** END **** 210 | 211 | 212 | e = epaper.Display(side = 'L', mode = epaper.FAST) #, up_time=200) 213 | with e: 214 | old_mins = -1 # Invalidate 215 | while True: 216 | t = gbtime() # Handle DST. t is tuple as per time.localtime() 217 | mins = t[4] 218 | if mins != old_mins: # Minute has changed 219 | # Calculate angle: minutes before half hour are displayed to the 220 | # right of centre, those after are to the left. 221 | angle = mins + 30 if mins < 30 else mins - 30 222 | if old_mins == -1 or angle == 0: 223 | # Reset buffers to initial state and display blank screen 224 | e.clear_screen(True, True) 225 | else: 226 | e.clear_screen(False) # Clear current buffer, don't display 227 | # Calculate hours for display. 228 | if old_mins == -1: # Just started 229 | if mins < 30: 230 | hrs = (t[3] % 12) 231 | if hrs == 0: 232 | hrs = 12 233 | else: 234 | hrs = (t[3] % 12) + 1 235 | elif angle == 0: # Time is at half past hour: hour shown is +1 236 | hrs = (t[3] % 12) + 1 237 | old_mins = mins 238 | populate(e, angle, hrs) 239 | e.refresh() 240 | gc.collect() 241 | time.sleep(20) 242 | -------------------------------------------------------------------------------- /epd_clock/watch.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/epd_clock/watch.mp4 -------------------------------------------------------------------------------- /epdpart.py: -------------------------------------------------------------------------------- 1 | # epd.py module for Embedded Artists' 2.7 inch E-paper Display. Imported by epaper.py 2 | # Peter Hinch 3 | # version 0.85 4 | # 18 Mar 2016 Assembler to increase iteration count and speed: reduce ghosting 5 | # 8th Mar 2016 Support for Adafruit module. This file implements fast mode. 6 | # 17th Aug 2015 __exit__() sequence adjusted to conform with datasheet rather than Arduino code 7 | 8 | # Copyright 2013 Pervasive Displays, Inc, 2015 Peter Hinch 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License") 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at: 13 | # 14 | # http:#www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, 17 | # software distributed under the License is distributed on an 18 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 19 | # express or implied. See the License for the specific language 20 | # governing permissions and limitations under the License. 21 | 22 | import pyb, gc 23 | from array import array 24 | from uctypes import addressof 25 | from panel import EMBEDDED_ARTISTS, getpins 26 | 27 | EPD_OK = const(0) # error codes 28 | EPD_UNSUPPORTED_COG = const(1) 29 | EPD_PANEL_BROKEN = const(2) 30 | EPD_DC_FAILED = const(3) 31 | 32 | LINES_PER_DISPLAY = const(176) 33 | BYTES_PER_LINE = const(33) 34 | BYTES_PER_SCAN = const(44) 35 | BITS_PER_LINE = const(264) 36 | BUFFER_SIZE = const(5808) # BYTES_PER_LINE * LINES_PER_DISPLAY 37 | 38 | BORDER_BYTE_BLACK = const(0xff) 39 | BORDER_BYTE_WHITE = const(0xaa) 40 | BORDER_BYTE_NULL = const(0x00) 41 | 42 | EPD_COMPENSATE = const(0) 43 | EPD_WHITE = const(1) 44 | EPD_INVERSE = const(2) 45 | EPD_NORMAL = const(3) 46 | 47 | EPD_BORDER_BYTE_NONE = const(0) 48 | EPD_BORDER_BYTE_ZERO = const(1) 49 | EPD_BORDER_BYTE_SET = const(2) 50 | # LM75 Temperature sensor 51 | 52 | LM75_ADDR = const(0x49) # LM75 I2C address 53 | LM75_TEMP_REGISTER = const(0) # LM75 registers 54 | LM75_CONF_REGISTER = const(1) 55 | 56 | class LM75(): 57 | def __init__(self, bus): # Check existence and wake it 58 | self._i2c = pyb.I2C(bus, pyb.I2C.MASTER) 59 | devices = self._i2c.scan() 60 | if not LM75_ADDR in devices: 61 | raise OSError("No LM75 device detected") 62 | self.wake() 63 | 64 | def wake(self): 65 | self._i2c.mem_write(0, LM75_ADDR, LM75_CONF_REGISTER) 66 | 67 | def sleep(self): 68 | self._i2c.mem_write(1, LM75_ADDR, LM75_CONF_REGISTER) # put sensor in shutdown mode 69 | 70 | @property 71 | def temperature(self): # return temperature as integer in Celsius 72 | temp = bytearray(2) 73 | self._i2c.mem_read(temp, LM75_ADDR, LM75_TEMP_REGISTER) 74 | temperature = int(temp[0]) 75 | return temperature if temperature < 128 else temperature -256 # sign bit: subtract once to clear, 2nd time to add its value 76 | 77 | class EPDException(Exception): 78 | pass 79 | 80 | def temperature_to_factor_10x(temperature): 81 | if temperature <= -10: 82 | return 170 83 | elif temperature <= -5: 84 | return 120 85 | elif temperature <= 5: 86 | return 80 87 | elif temperature <= 10: 88 | return 40 89 | elif temperature <= 15: 90 | return 30 91 | elif temperature <= 20: 92 | return 20 93 | elif temperature <= 40: 94 | return 10 95 | return 7 96 | 97 | class EPD(object): 98 | def __init__(self, intside, model, up_time): 99 | self.model = model 100 | self.compensate_temp = True if up_time is None else False 101 | self.verbose = False 102 | gc.collect() 103 | self.image_0 = bytearray(BUFFER_SIZE) # 5808. Contents 0. 104 | self.image_1 = bytearray(BUFFER_SIZE) # 5808 105 | self.asm_data = array('i', [0, 0, 0, 0]) 106 | self.image = self.image_0 107 | self.image_old = self.image_1 108 | self.line_buffer = bytearray(111) # total 11727 bytes! 109 | pins = getpins(intside, model) 110 | self.Pin_PANEL_ON = pyb.Pin(pins['PANEL_ON'], mode = pyb.Pin.OUT_PP) 111 | self.Pin_BORDER = pyb.Pin(pins['BORDER'], mode = pyb.Pin.OUT_PP) 112 | self.Pin_DISCHARGE = pyb.Pin(pins['DISCHARGE'], mode = pyb.Pin.OUT_PP) 113 | self.Pin_RESET = pyb.Pin(pins['RESET'], mode = pyb.Pin.OUT_PP) 114 | self.Pin_BUSY = pyb.Pin(pins['BUSY'], mode = pyb.Pin.IN) 115 | self.Pin_EPD_CS = pyb.Pin(pins['EPD_CS'], mode = pyb.Pin.OUT_PP) # cs for e-paper display 116 | self.Pin_FLASH_CS = pyb.Pin(pins['FLASH_CS'], mode = pyb.Pin.OUT_PP) # Instantiate flash CS and set high 117 | self.Pin_MOSI = pyb.Pin(pins['MOSI'], mode = pyb.Pin.OUT_PP) 118 | self.Pin_SCK = pyb.Pin(pins['SCK'], mode = pyb.Pin.OUT_PP) 119 | self.base_stage_time = 630 if up_time is None else up_time # ms 120 | self.factored_stage_time = self.base_stage_time 121 | 122 | self.Pin_RESET.low() 123 | self.Pin_PANEL_ON.low() 124 | self.Pin_DISCHARGE.low() 125 | self.Pin_BORDER.low() 126 | self.Pin_EPD_CS.low() 127 | self.Pin_FLASH_CS.high() 128 | self.spi_no = pins['SPI_BUS'] 129 | if model == EMBEDDED_ARTISTS: 130 | self.lm75 = LM75(pins['I2C_BUS']) # early error if not working 131 | else: 132 | self.adc = pyb.ADC(pins['TEMPERATURE']) 133 | 134 | def set_temperature(self): 135 | self.factored_stage_time = self.base_stage_time * temperature_to_factor_10x(self.temperature) / 10 136 | 137 | def enter(self): # power up sequence 138 | if self.compensate_temp: 139 | self.set_temperature() 140 | if self.verbose: 141 | print(self.factored_stage_time, self.compensate_temp) 142 | self.status = EPD_OK 143 | self.Pin_RESET.low() 144 | self.Pin_PANEL_ON.low() 145 | self.Pin_DISCHARGE.low() 146 | self.Pin_BORDER.low() 147 | # Baud rate: data sheet says 20MHz max. Pyboard's closest (21MHz) was unreliable 148 | self.spi = pyb.SPI(self.spi_no, pyb.SPI.MASTER, baudrate=10500000, polarity=1, phase=1, bits=8) # 5250000 10500000 supported by Pyboard 149 | self._SPI_send(b'\x00\x00') 150 | pyb.delay(5) 151 | self.Pin_PANEL_ON.high() 152 | pyb.delay(10) 153 | 154 | self.Pin_RESET.high() 155 | self.Pin_BORDER.high() 156 | pyb.delay(5) 157 | 158 | self.Pin_RESET.low() 159 | pyb.delay(5) 160 | 161 | self.Pin_RESET.high() 162 | pyb.delay(5) 163 | 164 | while self.Pin_BUSY.value() == 1: # wait for COG to become ready 165 | pyb.delay(1) 166 | 167 | # read the COG ID 168 | cog_id = self._SPI_read(b'\x71\x00') & 0x0f 169 | 170 | if cog_id != 2: 171 | self.status = EPD_UNSUPPORTED_COG 172 | self._power_off() 173 | raise EPDException("Unsupported EPD COG device: " +str(cog_id)) 174 | # Disable OE 175 | self._SPI_send(b'\x70\x02') 176 | self._SPI_send(b'\x72\x40') 177 | 178 | # check breakage 179 | self._SPI_send(b'\x70\x0f') 180 | broken_panel = self._SPI_read(b'\x73\x00') & 0x80 181 | if broken_panel == 0: 182 | self.status = EPD_PANEL_BROKEN 183 | self._power_off() 184 | raise EPDException("EPD COG device reports broken status") 185 | # power saving mode 186 | self._SPI_send(b'\x70\x0b') 187 | self._SPI_send(b'\x72\x02') 188 | # channel select 189 | self._SPI_send(b'\x70\x01') 190 | self._SPI_send(b'\x72\x00\x00\x00\x7f\xff\xfe\x00\x00') # Channel select 191 | # high power mode osc 192 | self._SPI_send(b'\x70\x07') 193 | self._SPI_send(b'\x72\xd1') 194 | # power setting 195 | self._SPI_send(b'\x70\x08') 196 | self._SPI_send(b'\x72\x02') 197 | # Vcom level 198 | self._SPI_send(b'\x70\x09') 199 | self._SPI_send(b'\x72\xc2') 200 | # power setting 201 | self._SPI_send(b'\x70\x04') 202 | self._SPI_send(b'\x72\x03') 203 | # driver latch on 204 | self._SPI_send(b'\x70\x03') 205 | self._SPI_send(b'\x72\x01') 206 | # driver latch off 207 | self._SPI_send(b'\x70\x03') 208 | self._SPI_send(b'\x72\x00') 209 | 210 | pyb.delay(5) 211 | dc_ok = False 212 | for i in range(4): 213 | # charge pump positive voltage on - VGH/VDL on 214 | self._SPI_send(b'\x70\x05') 215 | self._SPI_send(b'\x72\x01') 216 | pyb.delay(240) 217 | # charge pump negative voltage on - VGL/VDL on 218 | self._SPI_send(b'\x70\x05') 219 | self._SPI_send(b'\x72\x03') 220 | pyb.delay(40) 221 | # charge pump Vcom on - Vcom driver on 222 | self._SPI_send(b'\x70\x05') 223 | self._SPI_send(b'\x72\x0f') 224 | pyb.delay(40) 225 | # check DC/DC 226 | self._SPI_send(b'\x70\x0f') 227 | dc_state = self._SPI_read(b'\x73\x00') & 0x40 228 | if dc_state == 0x40: 229 | dc_ok = True 230 | break 231 | if not dc_ok: 232 | self.status = EPD_DC_FAILED 233 | raise EPDException("EPD DC power failure") # __exit__() will power doen 234 | # output enable to disable 235 | self._SPI_send(b'\x70\x02') 236 | self._SPI_send(b'\x72\x04') 237 | return self 238 | 239 | def exit(self, *_): 240 | self._nothing_frame() 241 | self._dummy_line() 242 | pyb.delay(25) 243 | self.Pin_BORDER.low() 244 | pyb.delay(200) 245 | self.Pin_BORDER.high() 246 | 247 | self._SPI_send(b'\x70\x0B') # Conform with datasheet 248 | self._SPI_send(b'\x72\x00') 249 | # latch reset turn on 250 | self._SPI_send(b'\x70\x03') 251 | self._SPI_send(b'\x72\x01') 252 | # power off charge pump Vcom 253 | self._SPI_send(b'\x70\x05') 254 | self._SPI_send(b'\x72\x03') 255 | # power off charge pump neg voltage 256 | self._SPI_send(b'\x70\x05') 257 | self._SPI_send(b'\x72\x01') 258 | pyb.delay(120) 259 | # discharge internal on 260 | self._SPI_send(b'\x70\x04') 261 | self._SPI_send(b'\x72\x80') 262 | # power off all charge pumps 263 | self._SPI_send(b'\x70\x05') 264 | self._SPI_send(b'\x72\x00') 265 | # turn of osc 266 | self._SPI_send(b'\x70\x07') 267 | self._SPI_send(b'\x72\x01') 268 | pyb.delay(50) 269 | self._power_off() 270 | 271 | def _power_off(self): # turn of power and all signals 272 | self.Pin_RESET.low() 273 | self.Pin_PANEL_ON.low() 274 | self.Pin_BORDER.low() 275 | self.spi.deinit() 276 | self.Pin_SCK.init(mode = pyb.Pin.OUT_PP) 277 | self.Pin_SCK.low() 278 | self.Pin_MOSI.init(mode = pyb.Pin.OUT_PP) 279 | self.Pin_MOSI.low() 280 | # ensure SPI MOSI and CLOCK are Low before CS Low 281 | self.Pin_EPD_CS.low() 282 | # pulse discharge pin 283 | self.Pin_DISCHARGE.high() 284 | pyb.delay(150) 285 | self.Pin_DISCHARGE.low() 286 | 287 | # USER INTERFACE 288 | # clear_screen() calls clear_data() and, if show, EPD_clear() 289 | # showdata() called from show() 290 | def showdata(self): 291 | self.EPD_clear() 292 | self.EPD_image_0() 293 | 294 | def clear_data(self, both): 295 | if both: # Reset buffers to initial state 296 | zero(self.image_0, BUFFER_SIZE) 297 | zero(self.image_1, BUFFER_SIZE) 298 | self.image = self.image_0 299 | self.image_old = self.image_1 300 | else: 301 | zero(self.image, BUFFER_SIZE) 302 | 303 | # EPD_partial_image() - fast update of current image. There are two schools of thought on this 304 | # https://github.com/repaper/gratis/issues/19 305 | # modified code at https://github.com/tvoverbeek/gratis/blob/master/PlatformWithOS/driver-common/V231_G2/epd.c 306 | def refresh(self, fast): 307 | if not fast: 308 | self.swap() 309 | self.frame_data_repeat(EPD_COMPENSATE, use_old = True) 310 | self.frame_data_repeat(EPD_WHITE, use_old = True) 311 | self.swap() 312 | self.frame_data_repeat(EPD_INVERSE, use_old = True) 313 | self.frame_data_repeat(EPD_NORMAL, use_old = True) 314 | self.frame_data_repeat(EPD_NORMAL, use_old = True) 315 | mv = memoryview(self.image_old) 316 | mv[:] = self.image 317 | 318 | def exchange(self, clear_data): 319 | self.EPD_image() # Does not affect buffer currency 320 | self.swap() # Current data -> old 321 | if clear_data: # Option to clear new current buffer 322 | zero(self.image, BUFFER_SIZE) 323 | 324 | @property 325 | def temperature(self): # return temperature as integer in Celsius 326 | if self.model == EMBEDDED_ARTISTS: 327 | return self.lm75.temperature 328 | else: 329 | return 202.5 - 0.1824 * self.adc.read() 330 | 331 | # END OF USER INTERFACE 332 | 333 | # self.use_old False is equivalent to passing NULL in old image 334 | # swap() determines which buffer to use 335 | # clear display (anything -> white) called from clear_screen(), which handles clearing data 336 | def EPD_clear(self): 337 | self.frame_fixed_repeat(0xff, EPD_COMPENSATE) 338 | self.frame_fixed_repeat(0xff, EPD_WHITE) 339 | self.frame_fixed_repeat(0xaa, EPD_INVERSE) 340 | self.frame_fixed_repeat(0xaa, EPD_NORMAL) 341 | 342 | # assuming a clear (white) screen output an image called from show() 343 | def EPD_image_0(self): 344 | self.frame_fixed_repeat(0xaa, EPD_COMPENSATE) 345 | self.frame_fixed_repeat(0xaa, EPD_WHITE) 346 | self.frame_data_repeat(EPD_INVERSE, use_old = False) 347 | self.frame_data_repeat(EPD_NORMAL, use_old = False) 348 | self.swap() 349 | zero(self.image, BUFFER_SIZE) 350 | 351 | # change from old image to new image called from exchange() 352 | def EPD_image(self): 353 | self.swap() # Display/clear old data 354 | self.frame_data_repeat(EPD_COMPENSATE, use_old = False) 355 | self.frame_data_repeat(EPD_WHITE, use_old = False) 356 | self.swap() # Display new 357 | self.frame_data_repeat(EPD_INVERSE, use_old = False) 358 | self.frame_data_repeat(EPD_NORMAL, use_old = False) 359 | 360 | def swap(self): 361 | i = self.image_old 362 | self.image_old = self.image 363 | self.image = i 364 | 365 | def frame_data_repeat(self, stage, use_old): 366 | self.asm_data[0] = addressof(self.image) 367 | self.asm_data[1] = addressof(self.image_old) if use_old else 0 368 | start = pyb.millis() 369 | count = 0 370 | while True: 371 | self.frame_data(stage) 372 | count +=1 373 | if pyb.elapsed_millis(start) > self.factored_stage_time: 374 | break 375 | if self.verbose: 376 | print('frame_data_repeat count = {}'.format(count)) 377 | 378 | def frame_data(self, stage): 379 | for line in range(0, LINES_PER_DISPLAY): 380 | self.one_line_data(line, stage) 381 | 382 | def frame_fixed_repeat(self, fixed_value, stage): 383 | start = pyb.millis() 384 | count = 0 385 | while True: 386 | self.frame_fixed(fixed_value, stage) 387 | count +=1 388 | if pyb.elapsed_millis(start) > self.factored_stage_time: 389 | break 390 | if self.verbose: 391 | print('frame_fixed_repeat count = {}'.format(count)) 392 | 393 | def frame_fixed(self, fixed_value, stage): 394 | for line in range(0, LINES_PER_DISPLAY): 395 | self.one_line_fixed(line, fixed_value, stage) 396 | 397 | def _nothing_frame(self): 398 | for line in range(LINES_PER_DISPLAY) : 399 | self.one_line_fixed(0x7fff, 0, EPD_COMPENSATE) 400 | 401 | def _dummy_line(self): 402 | self.one_line_fixed(0x7fff, 0, EPD_NORMAL) 403 | 404 | @micropython.viper 405 | def even_pixels_fixed(self, offset: int, fixed_value: int) -> int: 406 | p = ptr8(self.line_buffer) 407 | for b in range(0, BYTES_PER_LINE): 408 | p[offset] = fixed_value 409 | offset +=1 410 | return offset 411 | 412 | @micropython.viper 413 | def odd_pixels_fixed(self, offset: int, fixed_value: int) -> int: 414 | p = ptr8(self.line_buffer) 415 | for b in range(BYTES_PER_LINE, 0, -1): 416 | p[offset] = fixed_value 417 | offset +=1 418 | return offset 419 | 420 | # output one line of scan and data bytes to the display 421 | @micropython.native 422 | def one_line_data(self, line, stage): 423 | mv_linebuf = memoryview(self.line_buffer) 424 | self.asm_data[2] = addressof(mv_linebuf) 425 | self.asm_data[3] = stage 426 | spi_send_byte = self.spi.send # send data 427 | self._SPI_send(b'\x70\x0a') 428 | self.Pin_EPD_CS.low() # CS low until end of line 429 | spi_send_byte(b'\x72\x00') # data bytes 430 | odd_pixels(self.asm_data, 0, line * BYTES_PER_LINE) 431 | offset = BYTES_PER_LINE 432 | offset = scan(self.line_buffer, line, offset) 433 | even_pixels(self.asm_data, offset, line * BYTES_PER_LINE) 434 | offset += BYTES_PER_LINE 435 | 436 | spi_send_byte(mv_linebuf[:offset]) # send the accumulated line buffer 437 | self.Pin_EPD_CS.high() 438 | self._SPI_send(b'\x70\x02\x72\x07') # output data to panel 439 | 440 | @micropython.native 441 | def one_line_fixed(self, line, fixed_value, stage): 442 | spi_send_byte = self.spi.send # send data 443 | self._SPI_send(b'\x70\x0a') 444 | self.Pin_EPD_CS.low() # CS low until end of line 445 | spi_send_byte(b'\x72\x00') # data bytes 446 | mv_linebuf = memoryview(self.line_buffer) 447 | offset = self.odd_pixels_fixed(0, fixed_value) 448 | for b in range(BYTES_PER_SCAN, 0, -1): # scan line 449 | if line // 4 == b - 1: 450 | mv_linebuf[offset] = 0x03 << (2 * (line & 0x03)) 451 | else: 452 | mv_linebuf[offset] = 0x00 453 | offset += 1 454 | offset = self.even_pixels_fixed(offset, fixed_value) 455 | spi_send_byte(mv_linebuf[:offset]) # send the accumulated line buffer 456 | self.Pin_EPD_CS.high() 457 | self._SPI_send(b'\x70\x02\x72\x07') # output data to panel 458 | 459 | @micropython.native 460 | def _SPI_send(self, buf): 461 | self.Pin_EPD_CS.low() 462 | self.spi.send(buf) 463 | self.Pin_EPD_CS.high() 464 | 465 | @micropython.native 466 | def _SPI_read(self, buf): 467 | self.Pin_EPD_CS.low() 468 | for x in range(len(buf)): 469 | result = self.spi.send_recv(buf[x])[0] 470 | self.Pin_EPD_CS.high() 471 | return result 472 | 473 | @micropython.asm_thumb 474 | def zero(r0, r1): 475 | mov(r2, 0) 476 | label(LOOP) 477 | strb(r2, [r0, 0]) 478 | add(r0, 1) 479 | sub(r1, 1) 480 | bne(LOOP) 481 | 482 | @micropython.asm_thumb 483 | def even_pixels(r0, r1, r2): # array, offset, data_offset 484 | ldr(r3, [r0, 12]) # stage 485 | ldr(r4, [r0, 0]) 486 | add(r4, r4, r2) # r4 = *data 487 | ldr(r5, [r0, 4]) 488 | cmp(r5, 0) 489 | it(ne) 490 | add(r5, r5, r2) # r5 = *mask 491 | ldr(r2, [r0, 8]) 492 | add(r2, r2, r1) # r2 = *p 493 | mov(r6, 0) # r6 = b 494 | label(LOOP) 495 | push({r6}) 496 | mov(r0, r4) 497 | add(r0, r0, r6) 498 | ldr(r1, [r0, 0]) # r1 = data[b + data_offset] 499 | mov(r0, 0xaa) 500 | and_(r1, r0) # r1 = pixels = data[b + data_offset] & 0xaa 501 | mov(r7, 0xff) # r7 = pixel_mask 502 | cmp(r5, 0) # Check mask 503 | beq(SKIP) 504 | mov(r0, r5) 505 | add(r0, r0, r6) 506 | ldr(r7, [r0, 0]) # r7 = mask[b + data_offset] 507 | eor(r7, r1) 508 | mov(r0, 0xaa) 509 | and_(r7, r0) # r7 = pixel_mask = (mask[b + data_offset] ^ pixels) & 0xaa 510 | mov(r0, r7) 511 | mov(r6, 1) 512 | lsr(r0, r6) 513 | orr(r7, r0) # r7 = pixel_mask |= pixel_mask >> 1 514 | label(SKIP) 515 | cmp(r3, EPD_COMPENSATE) # r3 = stage 516 | bne(LABEL1) 517 | mov(r0, 0xaa) 518 | eor(r0, r1) # r0 = pixels ^ 0xaa 519 | mov(r6, 1) 520 | lsr(r0, r6) # r0 = (pixels ^ 0xaa) >> 1 521 | mov(r6, 0xaa) 522 | orr(r0, r6) # r0 = pixels = 0xaa | ((pixels ^ 0xaa) >> 1) 523 | b(LABEL5) 524 | label(LABEL1) 525 | cmp(r3, EPD_WHITE) 526 | bne(LABEL2) 527 | mov(r0, 0xaa) 528 | eor(r0, r1) 529 | mov(r6, 1) 530 | lsr(r0, r6) 531 | mov(r6, 0x55) 532 | add(r0, r0, r6) # r0 = pixels = 0x55 + ((pixels ^ 0xaa) >> 1) 533 | b(LABEL5) 534 | label(LABEL2) 535 | cmp(r3, EPD_INVERSE) 536 | bne(LABEL3) 537 | mov(r0, 0xaa) 538 | eor(r0, r1) # r0 = pixels ^ 0xaa 539 | mov(r6, 0x55) 540 | orr(r0, r6) # r0 = pixels = 0x55 | (pixels ^ 0xaa) 541 | b(LABEL5) 542 | label(LABEL3) 543 | cmp(r3, EPD_NORMAL) 544 | bne(LABEL4) 545 | mov(r0, r1) 546 | mov(r6, 1) 547 | lsr(r0, r6) 548 | mov(r6, 0xaa) 549 | orr(r0, r6) # r0 = pixels = 0xaa | (pixels >> 1) 550 | b(LABEL5) 551 | label(LABEL4) 552 | mov(r0, r1) # r0 = pixels 553 | label(LABEL5) 554 | # r0 = pixels 555 | mov(r1, r7) # pixel_mask 556 | push({r2, r3}) 557 | mvn(r2, r1) 558 | mov(r3, 0x55) 559 | and_(r3, r2) # r3 = (mask ^0xff) & 0x55 560 | and_(r0, r1) 561 | orr(r0, r3) # r0 = (r0 & mask) | (mask ^0xff) & 0x55 562 | rbit(r0, r0) 563 | mov(r1, 23) 564 | mov(r2, r0) 565 | lsr(r2, r1) # r2 = pixels >> 23 566 | mov(r1, 25) 567 | lsr(r0, r1) # r0 = pixels >> 25 568 | mov(r1, 0xaa) 569 | and_(r2, r1) # r2 = (pixels >> 23) & 0xaa 570 | mov(r1, 0x55) 571 | and_(r0, r1) # r0 = (pixels >> 25) & 0x55 572 | orr(r0, r2) 573 | pop({r2, r3}) 574 | # r0 = pixels 575 | strb(r0, [r2, 0]) # *p = pixels 576 | add(r2, 1) # p++ 577 | pop({r6}) # b, loop counter 578 | add(r6, 1) 579 | cmp(r6, BYTES_PER_LINE) 580 | blt(LOOP) 581 | mov(r0, r6) 582 | 583 | @micropython.asm_thumb 584 | def odd_pixels(r0, r1, r2): # array, offset, data_offset, stage 585 | ldr(r3, [r0, 12]) 586 | ldr(r4, [r0, 0]) 587 | add(r4, r4, r2) # r4 = *data 588 | ldr(r5, [r0, 4]) 589 | cmp(r5, 0) 590 | it(ne) 591 | add(r5, r5, r2) # r5 = *mask 592 | ldr(r2, [r0, 8]) 593 | add(r2, r2, r1) # r2 = *p 594 | mov(r6, BYTES_PER_LINE) # r6 = b 595 | label(LOOP) 596 | push({r6}) 597 | mov(r0, r4) 598 | add(r0, r0, r6) 599 | sub(r0, 1) 600 | ldr(r1, [r0, 0]) # r1 = data[b -1 + data_offset] 601 | mov(r0, 0x55) 602 | and_(r1, r0) # r1 = pixels = data[b -1 + data_offset] & 0x55 603 | mov(r7, 0xff) # r7 = pixel_mask 604 | cmp(r5, 0) # Check mask 605 | beq(SKIP) 606 | mov(r0, r5) 607 | add(r0, r0, r6) 608 | sub(r0, 1) 609 | ldr(r7, [r0, 0]) # r7 = mask[b -1 + data_offset] 610 | eor(r7, r1) 611 | mov(r0, 0x55) 612 | and_(r7, r0) # r7 = pixel_mask = (mask[b -1 + data_offset] ^ pixels) & 0x55 613 | mov(r0, r7) 614 | mov(r6, 1) 615 | lsl(r0, r6) 616 | orr(r7, r0) # r7 = pixel_mask |= pixel_mask << 1 617 | label(SKIP) 618 | cmp(r3, EPD_COMPENSATE) # r3 = stage 619 | bne(LABEL1) 620 | mov(r0, 0x55) 621 | eor(r0, r1) # r0 = pixels ^ 0x55 622 | mov(r6, 0xaa) 623 | orr(r0, r6) # r0 = pixels = 0xaa | (pixels ^ 0x55) 624 | b(LABEL5) 625 | label(LABEL1) 626 | cmp(r3, EPD_WHITE) 627 | bne(LABEL2) 628 | mov(r0, 0x55) 629 | eor(r0, r1) 630 | mov(r6, 0x55) 631 | add(r0, r0, r6) # r0 = pixels = 0x55 + (pixels ^ 0x55) 632 | b(LABEL5) 633 | label(LABEL2) 634 | cmp(r3, EPD_INVERSE) 635 | bne(LABEL3) 636 | mov(r0, 0x55) 637 | eor(r0, r1) # r0 = pixels ^ 0x55 638 | mov(r6, 1) 639 | lsl(r0, r6) 640 | mov(r6, 0x55) 641 | orr(r0, r6) # r0 = pixels = 0x55 | ((pixels ^ 0x55) << 1) 642 | b(LABEL5) 643 | label(LABEL3) 644 | cmp(r3, EPD_NORMAL) 645 | bne(LABEL4) 646 | mov(r0, r1) 647 | mov(r6, 0xaa) 648 | orr(r0, r6) # r0 = pixels = 0xaa | pixels 649 | b(LABEL5) 650 | label(LABEL4) 651 | mov(r0, r1) # r0 = pixels 652 | label(LABEL5) 653 | # r0 = pixels 654 | mov(r1, r7) # pixel_mask 655 | push({r2, r3}) 656 | mvn(r2, r1) 657 | mov(r3, 0x55) 658 | and_(r3, r2) # r3 = (mask ^0xff) & 0x55 659 | and_(r0, r1) 660 | orr(r0, r3) # r0 = (r0 & mask) | (mask ^0xff) & 0x55 661 | pop({r2, r3}) 662 | # r0 = pixels 663 | strb(r0, [r2, 0]) # *p = pixels 664 | add(r2, 1) # p++ 665 | pop({r6}) # b, loop counter 666 | sub(r6, 1) 667 | bne(LOOP) 668 | 669 | @micropython.asm_thumb 670 | def scan(r0, r1, r2): 671 | mov(r4, BYTES_PER_SCAN) # b 672 | label(LOOP) # for b in range(BYTES_PER_SCAN, 0, -1): 673 | mov(r5, r1) # line 674 | mov(r6, 2) 675 | lsr(r5, r6) 676 | mov(r6, r4) # b 677 | sub(r6, 1) 678 | mov(r7, 0) # Assume not equal mv_linebuf[offset] = 0x00 679 | cmp(r6, r5) # if line >> 2 == b - 1: 680 | bne(NOTEQUAL) 681 | mov(r6, 3) 682 | and_(r6, r1) 683 | add(r6, r6, r6) 684 | mov(r7, 3) 685 | lsl(r7, r6) # mv_linebuf[offset] = 0x03 << (2 * (line & 0x03)) 686 | label(NOTEQUAL) 687 | add(r6, r0, r2) 688 | strb(r7, [r6, 0]) 689 | add(r2, 1) # offset += 1 690 | sub(r4, 1) 691 | bne(LOOP) 692 | mov(r0, r2) 693 | -------------------------------------------------------------------------------- /flash.py: -------------------------------------------------------------------------------- 1 | # flash.py module for Embedded Artists' 2.7 inch E-paper Display. Imported by epaper.py 2 | # Provides optional support for the flash memory chip 3 | # Peter Hinch 4 | # version 0.7 5 | # 17 June 2018 Block protocol replaced with IOCTL 6 | # 2 Mar 2016 Power control support removed 7 | 8 | # Copyright 2013 Pervasive Displays, Inc, 2015 Peter Hinch 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License") 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at: 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, 17 | # software distributed under the License is distributed on an 18 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 19 | # express or implied. See the License for the specific language 20 | # governing permissions and limitations under the License. 21 | 22 | # Rev D of the Pervasive Displays module uses a Winbond W25Q32 chip: an 8 MB Flash chip 23 | # Sectors are 4096 bytes. 1024*4096*8 = 32Mbit: there are 1024 sectors 24 | 25 | # Terminology: 26 | # a sector is 4096 bytes, the size of data unit on the flash device for erasure 27 | # a page is 256 bytes and is the minimum data unit which may be written to the device 28 | # a block is 512 bytes, this is defined in the block protocol for FAT 29 | 30 | import pyb 31 | 32 | # Winbond W25Q32 command set 33 | FLASH_WREN = const(0x06) 34 | FLASH_WRDI = const(0x04) 35 | FLASH_RDID = const(0x9f) 36 | FLASH_RDSR = const(0x05) 37 | FLASH_WRSR = const(0x01) 38 | FLASH_READ = const(0x03) # read at half frequency 39 | FLASH_FAST_READ = const(0x0b) # read at full frequency 40 | FLASH_SE = const(0x20) 41 | FLASH_BE = const(0x52) 42 | FLASH_CE = const(0x60) 43 | FLASH_PP = const(0x02) 44 | FLASH_DP = const(0xb9) 45 | FLASH_RDP = const(0xab) 46 | FLASH_REMS = const(0x90) 47 | FLASH_NOP = const(0xff) 48 | 49 | # status register bits 50 | FLASH_WIP = const(0x01) 51 | FLASH_WEL = const(0x02) 52 | FLASH_BP0 = const(0x04) 53 | FLASH_BP1 = const(0x08) 54 | FLASH_BP2 = const(0x10) 55 | 56 | FLASH_SECTOR_SIZE = const(4096) 57 | FLASH_SECTOR_MASK = const(0xfff) 58 | 59 | # currently supported chip 60 | FLASH_MFG = 0xef # Winbond 61 | FLASH_ID = 0x4016 #W25Q32 memory type and capacity 62 | 63 | # Dumb file copy utility to help with managing flash contents at the REPL. 64 | def cp(source, dest): 65 | if dest.endswith('/'): # minimal way to allow 66 | dest = ''.join((dest, source.split('/')[-1])) # cp /sd/file /fc/ 67 | with open(source, 'rb') as infile: # Caller should handle any OSError 68 | with open(dest,'wb') as outfile: # e.g file not found 69 | while True: 70 | buf = infile.read(100) 71 | outfile.write(buf) 72 | if len(buf) < 100: 73 | break 74 | 75 | # FlashClass 76 | # The flash and epaper deviices use different SPI modes. You must issue objFlash.begin() before attempting to access 77 | # the device, and objFlash.end() afterwards 78 | # All address method arguments are byte addresses. Hence to erase block 100, issue objFlash.sector_erase(100*4096) 79 | # Sectors must be erased (to 0xff) before they can be written. Writing is done in 256 byte pages. The _write() method 80 | # handles paging transparently but does assume target pages are erased. 81 | class FlashException(Exception): 82 | pass 83 | 84 | BUFFER = const(0) # Indices into sector descriptor 85 | DIRTY = const(1) 86 | 87 | class FlashClass(object): 88 | def __init__(self, intside): 89 | from panel import getpins 90 | pins = getpins(intside) 91 | self.spi_no = pins['SPI_BUS'] 92 | self.pinCS = pyb.Pin(pins['FLASH_CS'], mode = pyb.Pin.OUT_PP) 93 | self.buff0 = bytearray(FLASH_SECTOR_SIZE) 94 | self.buff1 = bytearray(FLASH_SECTOR_SIZE) 95 | self.current_sector = None # Current flash sector number for writing 96 | self.prev_sector = None 97 | self.buffered_sectors = dict() # sector : sector descriptor which is [buffer, dirty] 98 | self.mountpoint = '/fc' 99 | self.begin() 100 | 101 | def begin(self): # Baud rates of 50MHz supported by chip 102 | self.pinCS.high() 103 | self.spi = pyb.SPI(self.spi_no, pyb.SPI.MASTER, baudrate = 21000000, polarity = 1, phase = 1, bits = 8) 104 | if not self._available(): # Includes 1mS wakeup delay 105 | raise FlashException("Unsupported flash device") 106 | 107 | def end(self): # Shutdown before using EPD 108 | self.synchronise() 109 | self.pinCS.high() 110 | self.spi.deinit() 111 | 112 | def _available(self): # return True if the chip is supported 113 | self.info() # initial read to reset the chip 114 | manufacturer, device = self.info() 115 | return (FLASH_MFG == manufacturer) and (FLASH_ID == device) 116 | 117 | def info(self): 118 | self.pinCS.low() 119 | pyb.udelay(1000) # FLASH wake up delay 120 | self.spi.send(FLASH_RDID) 121 | manufacturer = self.spi.send_recv(FLASH_NOP)[0] 122 | id_high = self.spi.send_recv(FLASH_NOP)[0] 123 | id_low = self.spi.send_recv(FLASH_NOP)[0] 124 | self.pinCS.high() 125 | return manufacturer, id_high << 8 | id_low 126 | 127 | def _read(self, buf, address, count = None):# Read data into preallocated bytearray 128 | self._await() 129 | end = len(buf) if count is None else min(count, len(buf)) 130 | index = 0 131 | while index < end : 132 | self.pinCS.low() 133 | self.spi.send(FLASH_FAST_READ) 134 | self.spi.send((address >> 16) & 0xff) 135 | self.spi.send((address >> 8) & 0xff) 136 | self.spi.send(address & 0xff) 137 | self.spi.send(FLASH_NOP) # dummy byte for fast read (to 50MHz) 138 | while True : 139 | buf[index] = self.spi.send_recv(FLASH_NOP)[0] 140 | address += 1 141 | index += 1 142 | if index >= end or address & FLASH_SECTOR_MASK == 0 : 143 | break 144 | self.pinCS.high() 145 | self._await() 146 | 147 | def _await(self): # Wait for device not busy 148 | self.pinCS.low() 149 | self.spi.send(FLASH_RDSR) 150 | busy = True 151 | while busy: 152 | busy = (FLASH_WIP & self.spi.send_recv(FLASH_NOP)[0]) != 0 153 | self.pinCS.high() 154 | 155 | def _write_enable(self): 156 | self._await() 157 | self.pinCS.low() 158 | self.spi.send(FLASH_WREN) 159 | self.pinCS.high() 160 | 161 | # def _write_disable(self): # Unused function 162 | # self._await() 163 | # self.pinCS.low() 164 | # self.spi.send(FLASH_WRDI) 165 | # self.pinCS.high() 166 | 167 | def _write(self, buf, address): # Write data in 256 byte pages 168 | end = len(buf) 169 | index = 0 170 | while index < end : 171 | self._write_enable() # Wait for any previous write to complete (upto 3mS) and set enable bit 172 | self.pinCS.low() 173 | self.spi.send(FLASH_PP) # Page program 256 bytes max 174 | self.spi.send((address >> 16) & 0xff) 175 | self.spi.send((address >> 8) & 0xff) 176 | self.spi.send(address & 0xff) 177 | while True: 178 | self.spi.send(buf[index]) 179 | address += 1 180 | index += 1 181 | if index >= end or address & 0xff == 0 : 182 | break 183 | self.pinCS.high() # Kick off the write 184 | self._await() 185 | 186 | def _sector_erase(self, address): 187 | self._write_enable() 188 | self.pinCS.low() 189 | self.spi.send(FLASH_SE) 190 | self.spi.send((address >> 16) & 0xff) 191 | self.spi.send((address >> 8) & 0xff) 192 | self.spi.send(address & 0xff) 193 | self.pinCS.high() 194 | self._await() 195 | 196 | def _readblock(self, blocknum, buf): 197 | sector = blocknum // 8 # Flash sector: 8 blocks per sector 198 | if sector in self.buffered_sectors: # It is cached: read from cache 199 | cache = self.buffered_sectors[sector][BUFFER] 200 | index = (blocknum << 9) & FLASH_SECTOR_MASK # Byte index into current sector 201 | buf[:] = cache[index : index + 512] 202 | else: 203 | self._read(buf, blocknum << 9) 204 | 205 | def _writesector(self, sector): # Erase and write a cached sector if neccessary. 206 | if self.buffered_sectors[sector][DIRTY]:# Caller ensures it is actually cached 207 | self.buffered_sectors[sector][DIRTY] = False # cache will be clean 208 | address = sector * FLASH_SECTOR_SIZE 209 | self._sector_erase(address) 210 | cache = self.buffered_sectors[sector][BUFFER] 211 | self._write(cache, address) 212 | 213 | def _writeblock(self, blocknum, buf): # Write a single 512 byte block 214 | sector = blocknum // 8 # Flash sector: 8 blocks per sector 215 | index = (blocknum << 9) & FLASH_SECTOR_MASK # Byte index into current sector 216 | if sector in self.buffered_sectors: # It is cached: update cache and return 217 | cache = self.buffered_sectors[sector][BUFFER] 218 | self.buffered_sectors[sector][DIRTY] = True 219 | cache[index : index + 512] = buf 220 | return 221 | cache = None 222 | if self.current_sector is None : # Program start: No sector is cached 223 | self.current_sector = sector 224 | cache = self.buff0 # allocate buff0 225 | elif self.prev_sector is None: # one sector was cached 226 | self.prev_sector = self.current_sector 227 | self.current_sector = sector 228 | cache = self.buff1 # allocate buf1 229 | if cache is not None: # A new buffer was allocated 230 | self._read(cache, sector * FLASH_SECTOR_SIZE) # Read new sector 231 | self.buffered_sectors[sector] = [cache, True] # put in dict marked dirty 232 | cache[index : index + 512] = buf # apply mods 233 | return 234 | # Normal running: two sectors already cached. new sector 235 | self._writesector(self.prev_sector) # needs to be cached. Write out old sector and 236 | cache = self.buffered_sectors.pop(self.prev_sector)[BUFFER] # remove from dict retrieving its buffer 237 | self.buffered_sectors[sector] = [cache, True] # Assign its buffer to new sector, mark dirty 238 | self.prev_sector = self.current_sector 239 | self.current_sector = sector 240 | self._read(cache, sector * FLASH_SECTOR_SIZE) # Read new sector 241 | cache[index : index + 512] = buf # and update 242 | 243 | # ******* THE IOCTL PROTOCOL ******* 244 | # In practice MicroPython currently only reads and writes single blocks but the protocol calls 245 | # for multiple blocks so these functions aim to provide that capability 246 | def readblocks(self, blocknum, buf): 247 | buflen = len(buf) 248 | if buflen == 512: # skip creating the blockbuf 249 | return self._readblock(blocknum, buf) 250 | start = 0 251 | blockbuf = bytearray[512] 252 | while buflen - start > 0: 253 | self._readblock(blocknum, blockbuf) 254 | buf[start : start +512] = blockbuf 255 | start += 512 256 | blocknum += 1 257 | 258 | def writeblocks(self, blocknum, buf): 259 | buflen = len(buf) 260 | start = 0 261 | while buflen - start > 0 : 262 | self._writeblock(blocknum, buf[start : start + 512]) 263 | start += 512 264 | blocknum += 1 265 | 266 | def synchronise(self): # Renamed: ioctl protocol doesn't like count() so may not like sync() 267 | for sector in self.buffered_sectors: 268 | self._writesector(sector) # Only if dirty 269 | 270 | def ioctl(self, op, arg): 271 | if op == 3: 272 | self.synchronise() 273 | if op == 4: # get number of blocks 274 | return 8192 275 | if op == 5: # get block size 276 | return 512 277 | -------------------------------------------------------------------------------- /ghosting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/ghosting.jpg -------------------------------------------------------------------------------- /micropower.py: -------------------------------------------------------------------------------- 1 | # micropower.py Support for hardware capable of switching off the power for Pyboard peripherals 2 | # 28th Aug 2015 3 | # version 0.45 4 | 5 | # Copyright 2015 Peter Hinch 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 | # http:#www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 16 | # express or implied. See the License for the specific language 17 | # governing permissions and limitations under the License. 18 | import pyb 19 | 20 | class PowerController(object): 21 | def __init__(self, pin_active_high, pin_active_low): 22 | self.upcount = 0 23 | if pin_active_low is not None: # Start with power down 24 | self.al = pyb.Pin(pin_active_low, mode = pyb.Pin.OUT_PP) 25 | self.al.high() 26 | else: 27 | self.al = None 28 | if pin_active_high is not None: # and pullups disabled 29 | self.ah = pyb.Pin(pin_active_high, mode = pyb.Pin.OUT_PP) 30 | self.ah.low() 31 | else: 32 | self.ah = None 33 | 34 | def __enter__(self): # Optional use as context manager 35 | self.power_up() 36 | return self 37 | 38 | def __exit__(self, *_): 39 | self.power_down() 40 | 41 | def power_up(self): 42 | self.upcount += 1 # Cope with nested calls 43 | if self.upcount == 1: 44 | if self.ah is not None: 45 | self.ah.high() # Enable I2C pullups 46 | if self.al is not None: 47 | self.al.low() # Power up 48 | pyb.delay(10) # Nominal time for device to settle 49 | 50 | def power_down(self): 51 | if self.upcount > 1: 52 | self.upcount -= 1 53 | elif self.upcount == 1: 54 | self.upcount = 0 55 | if self.al is not None: 56 | self.al.high() # Power off 57 | pyb.delay(10) # Avoid glitches on switched 58 | if self.ah is not None: # I2C bus while power decays 59 | self.ah.low() # Disable I2C pullups 60 | for bus in (pyb.SPI(1), pyb.SPI(2), pyb.I2C(1), pyb.I2C(2)): 61 | bus.deinit() # I2C drivers seem to need this 62 | 63 | @property 64 | def single_ended(self): 65 | return (self.ah is not None) and (self.al is not None) 66 | 67 | -------------------------------------------------------------------------------- /packages_epaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/packages_epaper.png -------------------------------------------------------------------------------- /panel.py: -------------------------------------------------------------------------------- 1 | # panel.py pin definition for Embedded Artists' 2.7 inch E-paper Display. 2 | # 18 Mar 2016 3 | # version 0.85 4 | 5 | # Copyright 2015 Peter Hinch 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 | # http:#www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 16 | # express or implied. See the License for the specific language 17 | # governing permissions and limitations under the License. 18 | 19 | # Pin definitions. Looking at board as in http://micropython.org/resources/pybv10-pinout.jpg 20 | # side 0 is on left. 21 | NORMAL = 0 # mode arg 22 | FAST = 1 23 | 24 | EMBEDDED_ARTISTS = 0 # model 25 | ADAFRUIT = 1 26 | 27 | def getpins(intside, model=0): 28 | if intside == 0: 29 | result = {'PANEL_ON': 'Y3', 'BORDER': 'X12', 'DISCHARGE': 'Y4', 30 | 'RESET': 'Y2', 'BUSY': 'X11', 'TEMPERATURE': 'X11', 'EPD_CS': 'Y5', 31 | 'FLASH_CS': 'Y1', 'MOSI': 'Y8', 'MISO': 'Y7', 32 | 'SCK': 'Y6', 'SPI_BUS': 'Y', 'I2C_BUS': 'X' } 33 | if model == ADAFRUIT: 34 | result['BUSY'] = 'X10' 35 | else: 36 | result = {'PANEL_ON': 'X3', 'BORDER': 'Y12', 'DISCHARGE': 'X4', 37 | 'RESET': 'X2', 'BUSY': 'Y11', 'TEMPERATURE': 'Y11', 'EPD_CS': 'X5', 38 | 'FLASH_CS': 'X1', 'MOSI': 'X8', 'MISO': 'X7', 39 | 'SCK': 'X6', 'SPI_BUS': 'X', 'I2C_BUS': 'Y'} 40 | if model == ADAFRUIT: 41 | result['BUSY'] = 'Y10' 42 | return result 43 | -------------------------------------------------------------------------------- /pyboard_barometer.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-epaper/2c02c24ea82064d52cb599d36a018d0eb0cb9fdc/pyboard_barometer.JPG -------------------------------------------------------------------------------- /raspberry_pi_clock.py: -------------------------------------------------------------------------------- 1 | # Ghosting test: Version of e-paper clock running on Raspberry Pi 2 | import sys, time, math 3 | from PIL import Image 4 | from PIL import ImageDraw, ImageFont 5 | from EPD import EPD 6 | 7 | WHITE = 1 8 | BLACK = 0 9 | origin = 100, 100 # of clock face 10 | 11 | def polar_line(radians, length, width): 12 | x_end = origin[0] + length * math.sin(radians) 13 | y_end = origin[1] - length * math.cos(radians) 14 | draw.line([(origin[0], origin[1]), (x_end, y_end)], fill = BLACK, width = width) 15 | 16 | epd = EPD() 17 | 18 | print('panel = {p:s} {w:d} x {h:d} version={v:s} COG={g:d} FILM={f:d}'.format(p=epd.panel, w=epd.width, h=epd.height, v=epd.version, g=epd.cog, f=epd.film)) 19 | 20 | epd.clear() 21 | 22 | # initially set all white background 23 | image = Image.new('1', epd.size, WHITE) 24 | width, height = image.size 25 | # prepare for drawing 26 | draw = ImageDraw.Draw(image) 27 | font = ImageFont.truetype("LiberationSerif-Regular.ttf", 36) 28 | while True: 29 | draw.rectangle((0, 0, width, height), fill=WHITE, outline=WHITE) 30 | draw.ellipse((origin[0] -55, origin[1] -55, origin[0] +55, origin[1] +55), fill=WHITE, outline=BLACK) 31 | 32 | t = time.localtime() 33 | h, m, s = t[3:6] 34 | hh = h + m /60 35 | draw.text((0,0), '{:02d}.{:02d}.{:02d} '.format(h, m, s), font=font) 36 | polar_line(2 * math.pi * s/60, 50, 1) 37 | polar_line(2 * math.pi * m/60, 50, 2) 38 | polar_line(2 * math.pi * (h + m/60)/12, 30, 4) 39 | epd.display(image) 40 | epd.partial_update() 41 | 42 | --------------------------------------------------------------------------------