From 873585bd9467182e9f4fbc257235f536c1ab8f0e Mon Sep 17 00:00:00 2001 From: Laurence Bank Date: Thu, 12 Sep 2024 17:33:11 +0100 Subject: [PATCH] initial commit --- LICENSE | 204 +++ README.md | 81 + library.properties | 11 + src/bb_ei.inl | 4351 ++++++++++++++++++++++++++++++++++++++++++++ src/bb_ei_gfx.inl | 2773 ++++++++++++++++++++++++++++ src/bb_ei_io.inl | 66 + src/bb_eink.cpp | 596 ++++++ src/bb_eink.h | 627 +++++++ 8 files changed, 8709 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 library.properties create mode 100644 src/bb_ei.inl create mode 100644 src/bb_ei_gfx.inl create mode 100644 src/bb_ei_io.inl create mode 100644 src/bb_eink.cpp create mode 100644 src/bb_eink.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..716f427 --- /dev/null +++ b/LICENSE @@ -0,0 +1,204 @@ +Copyright 2020 BitBank Software, Inc. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b56646 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +OneBitDisplay (1-bpp OLED/LCD library)
+----------------------------------- +Project started 3/23/2020
+Copyright (c) 2020 BitBank Software, Inc.
+Written by Larry Bank
+bitbank@pobox.com
+
+![OneBitDisplay](/demo.jpg?raw=true "OneBitDisplay") +
+The purpose of this code is to easily control monochrome (1-bit per pixel) OLED and LCD displays. The displays can be connected to the traditional I2C or SPI bus, or you can use GPIO pins to bit bang the signals.
+
+On AVR microcontrollers, there is an optimized option to speed up access to the GPIO pins to allow speeds which match or exceed normal I2C speeds. The pins are numbered with the Port letter as the first digit followed by the bit number. For example, To use bit 0 of Port B, you would reference pin number 0xb0.
+
+Includes the unique feature that the I2C init function can optionally detect the display address (0x3C or 0x3D) and the controller type (SSD1306, SH1106 or SH1107).
+
+I try to support as many OLEDs as I can. I was able to justify buying a bunch +of different sized SSD1306 displays because they're around $2 each. A generous patron +donated money so that I could purchase Pimoroni's 128x128 OLED and add support for it. +It uses the SH1107 controller and behaves very similarly to the SH1106. +
+ +Features:
+---------
+- C API and C++ wrapper class which mimics the Adafruit_GFX API +- Supports any number of simultaneous displays of any type (mix and match)
+- Optionally detect the display address and type (I2C only)
+- Supports 72x40, 96x16, 64x32, 128x32, 128x64, 64x128 (SH1107), 128x128 (SH1107) and 132x64 (SH1106) OLED display sizes
+- Supports 96x68 HX1230, 84x48 Nokia 5110 and 128x64 ST7567/UC1701 mono LCDs
+- Supports 144x168 and 400x240 Sharp Memory LCDs
+- Supports the ST7302 low power LCD (250x122, 2.13") +- *NEW* supports a few popular epd (e-ink) panels +- Virtual displays of any size which can be drawn across multiple physical displays +- Flexible copy function can convert the internal pixel format to any output format and orientation +- Drive displays from I2C, SPI or any GPIO pins (virtual I2C/SPI)
+- Includes 5 sizes of fixed fonts (6x8, 8x8, 12x16, 16x16, 16x32)
+- Text drawing at any fractional scale (e.g. 1.25x), and any of 4 directions/rotations
+- Can use Adafruit_GFX format bitmap fonts (proportional and fixed)
+- Deferred rendering allows preparing a back buffer, then displaying it (usually faster)
+- Text scrolling features (vertical and horizontal)
+- Text cursor position with optional line wrap
+- A function to load a Windows BMP file
+- Pixel drawing on SH1106/7 without needing backing RAM
+- Optimized Bresenham line drawing
+- Optimized Bresenham outline and filled ellipse drawing
+- Optimized outline and filled rectangle drawing
+- Optional backing RAM (needed for some text and drawing functions)
+- 16x16 Tile/Sprite drawing at any angle.
+- Run full frame animations at high frame rates with a simple API
+
+This code depends on the BitBang_I2C library. You can download it here:
+https://github.com/bitbank2/BitBang_I2C +
+See the Wiki for help getting started
+https://github.com/bitbank2/OneBitDisplay/wiki
+
+ +![Fonts](/fonts_opt.jpg?raw=true "fonts") +A few words about fonts
+-----------------------
+ +The library includes 3 fixed fonts (6x8, 8x8 and 16x32). The 16x32 font is disabled when compiling for AVR targets (e.g. Arduino Uno) to save FLASH program space. The other 2 fonts offer 2x stretched versions (12x16 from 6x8 and 16x16 from 8x8). A simple smoothing algorithm is applied to the stretched 6x8 font to make it look better. In the photo above are the first 4 font sizes shown on a 128x64 yellow OLED display. Only 96 ASCII characters are defined per font to save space. To use more elaborate fonts with more extensive character support, use Adafruit_GFX format bitmap fonts with the `obdWriteStringCustom()` function.
+ +![Sharp Memory LCD](/sharp_lcd.jpg?raw=true "Sharp_LCD") +Sharp Memory LCD Support
+------------------------
+New - support for the Sharp 144x168 and 400x240 memory LCDs. These are a different type of LCD that have a high refresh rate and low power usage. They cost quite a bit more than normal LCDs. They require a memory back buffer to use the drawing functions due to the way data is written to them one line at a time. I've also added a specific function if you would like to skip the back buffer - `obdWriteLCDLine()`. It allows you to write a single line of pixels without needing any additional memory. Please see the Wiki for more details.
+ +Instructions for use:
+---------------------
+Start by initializing the library. Either using hardware I2C, bit-banged I2C or SPI to talk to the display. For I2C, the +address of the display will be detected automatically (either 0x3c or 0x3d) or you can specify it. The typical MCU only allows setting the I2C speed up to 400Khz, but the SSD1306 displays can handle a much faster signal. With the bit-bang code, you can usually specify a stable 800Khz clock and with Cortex-M0 targets, the hardware I2C can be told to be almost any speed, but the displays I've tested tend to stop working beyond 1.6Mhz.
+
+After initializing the display you can begin drawing text or graphics on it. The final parameter of all of the drawing functions is a render flag. When true, the graphics will be sent to the internal backing buffer (when available) and sent to the display. You optionally pass the library a backing buffer (if your MCU has enough RAM) with the obdSetBackBuffer() function. When the render flag is false, the graphics will only be drawn into the internal buffer. Once you're ready to send the pixels to the display, call obdDumpBuffer(NULL) and it will copy the internal buffer in its entirety to the display.
+
+The text drawing function now has a scroll offset parameter. This tells it how many pixels of the text to skip before drawing the text at the given destination coordinates. For example, if you pass a value of 20 for the scroll offset and are using an 8-pixel wide font (FONT_8x8), the first two and a half characters will not be drawn; the second half of the third and subsequent characters will be drawn starting at the x/y you specified. This allows you to create a scrolling text effect by repeatedly calling the oledWriteString() function with progressively larger scroll offset values to make the text scroll from right to left.
+
+ +If you find this code useful, please consider sending a donation or becomming a Github sponsor. + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SR4F44J2UR8S4) + diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..ab13bd8 --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=OneBitDisplay +version=2.3.1 +author=Larry Bank +maintainer=Larry Bank +sentence=OLED, LCD and e-paper library for 1-bit per pixel displays. +paragraph=Supports most monochrome OLEDs, LCDs, Sharp Memory LCDs and many e-paper displays. Designed to provide a rich set of features with a simple API. Can automatically detect the display address (3C or 3D) and the controller type (SSD1306, SH1106 or SH1107). Includes 5 sizes of fixed fonts (6x8, 8x8, 12x16, 16x16, 16x32). Can use I2C, SPI or bit bang I2C+SPI on any GPIO pins with the use of my BitBang_I2C library. Supports display sizes of: 128x128, 128x64, 128x32, 64x32, 96x16, 72x40. Includes scrolling text/gfx, deferred rendering, lines, sprites, circles, rectangles and more. +category=Display +url=https://github.com/bitbank2/OneBitDisplay +architectures=* +includes=OneBitDisplay.h +depends=BitBang_I2C diff --git a/src/bb_ei.inl b/src/bb_ei.inl new file mode 100644 index 0000000..22bea1d --- /dev/null +++ b/src/bb_ei.inl @@ -0,0 +1,4351 @@ +// +// bb_eink +// Copyright (c) 2024 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 9/11/2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// bb_ei.inl +// display interfacing/control code for bb_eink library +// +#if defined(_LINUX_) || defined(ARDUINO_ARCH_MCS51) +#define memcpy_P memcpy +#endif +void initSPI(OBDISP *pBBEI, int iSpeed, int iMOSI, int iCLK, int iCS); +void _delay(int iDelay); +static void EPDWaitBusy(OBDISP *pBBEI, int bQuick); +static void EPDSleep(OBDISP *pBBEI, int bDeep); +#ifndef WIMPY_MCU +static void EPDWriteImage4bpp(OBDISP *pBBEI, uint8_t ucCMD, int x, int y, int w, int h); +static void EPDWriteImage2bpp(OBDISP *pBBEI, uint8_t ucCMD, int x, int y, int w, int h); +static void EPDWriteImage(OBDISP *pBBEI, uint8_t ucCMD, uint8_t *pBits, int x, int y, int w, int h, int bInvert); +#endif +void EPD213_Begin(OBDISP *pBBEI, int x, int y, int w, int h, int bPartial); +void EPD_CMD2(OBDISP *pBBEI, uint8_t cmd, uint8_t param); +void obdSetDCMode(OBDISP *pBBEI, int iMode); +void InvertBytes(uint8_t *pData, uint8_t bLen); +void SPI_BitBang(OBDISP *pBBEI, uint8_t *pData, int iLen, uint8_t iMOSIPin, uint8_t iSCKPin); +static void RawWrite(OBDISP *pBBEI, unsigned char *pData, int iLen); +void RawWriteData(OBDISP *pBBEI, unsigned char *pData, int iLen); +// EPD look up tables + +// 2.7" 176x264 LUTs +const uint8_t lut_fast_vcom_dc[] PROGMEM = { + 0x00, 0x00, + 0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, + 0x00, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x00, 0x0E, 0x01, 0x0E, 0x01, 0x01, + 0x00, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, + 0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, + 0x00, 0x23, 0x00, 0x00, 0x00, 0x01 +}; +const uint8_t lut_fast_ww[] PROGMEM = { + 0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, + 0x40, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x01, + 0x80, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, + 0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, + 0x00, 0x23, 0x00, 0x00, 0x00, 0x01 +}; +const uint8_t lut_fast_bw[] PROGMEM = { + 0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, + 0x00, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x01, + 0x90, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, + 0xB0, 0x03, 0x0E, 0x00, 0x00, 0x0A, + 0xC0, 0x23, 0x00, 0x00, 0x00, 0x01 +}; +const uint8_t lut_fast_bb[] PROGMEM = { + 0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, + 0x40, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x01, + 0x80, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, + 0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, + 0x00, 0x23, 0x00, 0x00, 0x00, 0x01 +}; +const uint8_t lut_fast_wb[] PROGMEM = { + 0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, + 0x20, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x01, + 0x10, 0x0A, 0x0A, 0x00, 0x00, 0x01, + 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, + 0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, + 0x00, 0x23, 0x00, 0x00, 0x00, 0x01 +}; + +const uint8_t lut_vcom_dc[] PROGMEM = +{ + 0x00, 0x00, + 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x00, 0x32, 0x32, 0x00, 0x00, 0x02, + 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +const uint8_t lut_ww[] PROGMEM = +{ + 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, + 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +const uint8_t lut_bw[] PROGMEM = +{ + 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, + 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + +}; +const uint8_t lut_bb[] PROGMEM = +{ + 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, + 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +const uint8_t lut_wb[] PROGMEM = +{ + 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, + 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char lut_vcom0_full[] PROGMEM = +{ + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char lut42_4gray_vcom[] PROGMEM = { +0x00 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, +0x60 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, +0x00 ,0x14 ,0x00 ,0x00 ,0x00 ,0x01, +0x00 ,0x13 ,0x0A ,0x01 ,0x00 ,0x01, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 +}; +const unsigned char lut42_4gray_ww[] PROGMEM = { +0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, +0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, +0x10 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, +0xA0 ,0x13 ,0x01 ,0x00 ,0x00 ,0x01, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 +}; +const unsigned char lut42_4gray_bw[] PROGMEM = { +0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, +0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, +0x00 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, +0x99 ,0x0C ,0x01 ,0x03 ,0x04 ,0x01, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 +}; +const unsigned char lut42_4gray_bb[] PROGMEM = { +0x80 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, +0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, +0x20 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, +0x50 ,0x13 ,0x01 ,0x00 ,0x00 ,0x01, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 +}; +const unsigned char lut42_4gray_wb[] PROGMEM = { +0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, +0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, +0x00 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, +0x99 ,0x0B ,0x04 ,0x04 ,0x01 ,0x01, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, +0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 +}; + +#ifndef WIMPY_MCU +const uint8_t epd29b_bwyr_init_sequence_full[] PROGMEM = { + 0x02, 0x4d, 0x78, + 0x03, 0x00, 0x0f, 0x29, // PSR + 0x03, 0x01, 0x07, 0x00, // PWRR + 0x04, 0x03, 0x10, 0x54, 0x44, // POFS + 0x08, 0x06, 0x05, 0x00, 0x3f, 0x0a, 0x25, 0x12, 0x1a, // BTST_P + 0x02, 0x50, 0x37, // CDI + 0x03, 0x60, 0x02, 0x02, // TCON + 0x05, 0x61, 0x00, 128, 0x01, 296-256, // TRES + 0x02, 0xe7, 0x1c, + 0x02, 0xe3, 0x22, + 0x04, 0xb4, 0xd0, 0xb5, 0x03, + 0x02, 0xe9, 0x01, + 0x02, 0x30, 0x08, + 0x01, 0x04, // PON + BUSY_WAIT, + 0x00 +}; +const uint8_t epd29_bwyr_init_sequence_full[] PROGMEM = { + 0x02, 0x4d, 0x78, + 0x03, 0x00, 0x0f, 0x29, // PSR + 0x03, 0x01, 0x07, 0x00, // PWRR + 0x04, 0x03, 0x10, 0x54, 0x44, // POFS + 0x08, 0x06, 0x05, 0x00, 0x3f, 0x0a, 0x25, 0x12, 0x1a, // BTST_P + 0x02, 0x50, 0x37, // CDI + 0x03, 0x60, 0x02, 0x02, // TCON + 0x05, 0x61, 0x00, 168, 0x01, 384-256, // TRES + 0x02, 0xe7, 0x1c, + 0x02, 0xe3, 0x22, + 0x04, 0xb4, 0xd0, 0xb5, 0x03, + 0x02, 0xe9, 0x01, + 0x02, 0x30, 0x08, + 0x01, 0x04, // PON + BUSY_WAIT, + 0x00 +}; +const uint8_t epd266_bwyr_init_sequence_full[] PROGMEM = { + 0x02, 0x4d, 0x78, + 0x03, 0x00, 0x0f, 0x29, // PSR + 0x03, 0x01, 0x07, 0x00, // PWRR + 0x04, 0x03, 0x10, 0x54, 0x44, // POFS + 0x08, 0x06, 0x05, 0x00, 0x3f, 0x0a, 0x25, 0x12, 0x1a, // BTST_P + 0x02, 0x50, 0x37, // CDI + 0x03, 0x60, 0x02, 0x02, // TCON + 0x05, 0x61, 0x00, 184, 0x01, 360-256, // TRES + 0x02, 0xe7, 0x1c, + 0x02, 0xe3, 0x22, + 0x04, 0xb4, 0xd0, 0xb5, 0x03, + 0x02, 0xe9, 0x01, + 0x02, 0x30, 0x08, + 0x01, 0x04, // PON + BUSY_WAIT, + 0x00 +}; +const uint8_t epd236_bwyr_init_sequence_full[] PROGMEM = { + 0x05, 0x66, 0x49, 0x55, 0x13, 0x5d, + 0x03, 0x66, 0x49, 0x55, + 0x02, 0xb0, 0x03, // boost + 0x03, 0x00, 0x4f, 0x6b, + 0x02, 0x03, 0x00, + 0x06, 0xf0, 0xf6, 0x0d, 0x00, 0x00, 0x00, + 0x04, 0x06, 0xcf, 0xdf, 0x0f, + 0x02, 0x41, 0x00, + 0x02, 0x50, 0x30, + 0x03, 0x60, 0x0c, 0x05, + 0x04, 0x61, 0xa8, 0x01, 0x28, // resolution + 0x02, 0x84, 0x01, + 0x00 +}; +const uint8_t epd164_bwyr_init_sequence_full[] PROGMEM = { + 0x05, 0x66, 0x49, 0x55, 0x13, 0x5d, + 0x03, 0x66, 0x49, 0x55, + 0x02, 0xb0, 0x03, // boost + 0x03, 0x00, 0x4f, 0x6b, + 0x02, 0x03, 0x00, + 0x06, 0xf0, 0xf6, 0x0d, 0x00, 0x00, 0x00, + 0x04, 0x06, 0xcf, 0xdf, 0x0f, + 0x02, 0x41, 0x00, + 0x02, 0x50, 0x30, + 0x03, 0x60, 0x0c, 0x05, + 0x04, 0x61, 0xa8, 0x00, 0xa8, // resolution + 0x02, 0x84, 0x01, + 0x00 +}; +const uint8_t epd30_bwyr_init_sequence_full[] PROGMEM = { + 0x07, 0x66, 0x49, 0x55, 0x13, 0x5d, 0x05, 0x10, + 0x02, 0xb0, 0x00, // boost + 0x03, 0x01, 0x0f, 0x00, + 0x03, 0x00, 0x4f, 0x6b, + 0x04, 0x06, 0xd7, 0xde, 0x12, + 0x05, 0x61, 0x00, 0xa8, 0x01, 0x90, // resolution + 0x02, 0x50, 0x37, + 0x03, 0x60, 0x0c, 0x05, + 0x02, 0xe3, 0xff, + 0x02, 0x84, 0x00, + 0x00 +}; + +const uint8_t epd583r_init_sequence_full[] PROGMEM = { + 0x03, 0x01, 0x37, 0x00, // power setting + 0x03, 0x00, 0xcf, 0x08, // panel setting + 0x04, 0x06, 0xc7, 0xcc, 0x28, // boost + 0x02, 0x30, 0x3a, // PLL 15s refresh + 0x02, 0x41, 0x00, // temperature + 0x02, 0x50, 0x77, // VCOM + 0x02, 0x60, 0x22, // TCON + 0x05, 0x61, 0x02, 0x58, 0x01, 0xc0, // RES + 0x02, 0x82, 0x28, // temp range + 0x02, 0xe5, 0x03, // flash mode + 0x01, 0x04, // power on + BUSY_WAIT, + 0x00 +}; +const uint8_t epd74r_init_sequence_full[] PROGMEM = { + 0x02, 0x65, 0x01, + 0x01, 0xab, + 0x02, 0x65, 0x00, + 0x01, 0x04, + BUSY_WAIT, + 0x03, 0x01, 0x37, 0x00, // 0x05, 0x05, + 0x03, 0x00, 0xcf, 0x08, +// 0x02, 0xe5, 0x03, +// 0x02, 0x03, 0x00, + 0x04, 0x06, 0xc7, 0xcc, 0x28, + 0x02, 0x30, 0x3c, + 0x02, 0x41, 0x00, + 0x02, 0x50, 0x77, + 0x02, 0x60, 0x22, + 0x05, 0x61, 0x02, 0x80, 0x01, 0x80, +// 0x02, 0x65, 0x01, +// 0x02, 0x65, 0x00, +// 0x01, 0x40, +// BUSY_WAIT, +// 0x02, 0x8d, 0x80, + 0x02, 0x82, 0x1e, + 0x02, 0xe5, 0x03, +// 0x01, 0x02, +// BUSY_WAIT, + 0x01, 0x04, + BUSY_WAIT, +// 0x02, 0x65, 0x01, +// 0x02, 0x65, 0x00, +// 0x01, 0x40, +// BUSY_WAIT, +// 0x02, 0x8d, 0xc0, +// 0x02, 0x30, 0x2a, + 0x00 +}; +#endif // !WIMPY_MCU + +const uint8_t epd35r_init_sequence_full[] PROGMEM = { + 0x01, 0x12, // SW RESET + BUSY_WAIT, + 0x02, 0x3c, 0x05, + 0x02, 0x18, 0x80, + 0x02, 0x22, 0xb1, + 0x01, 0x20, + BUSY_WAIT, + 0x03, 0x1b, 0x17, 0x80, // reading temp, delay 5ms + BUSY_WAIT, + 0x02, 0x4e, 0x00, // ram counter start + 0x03, 0x4f, 0x7f, 0x01, + 0x00 +}; + +// init sequence for GDEW042Z15 +const uint8_t epd42r2_init_sequence_full[] PROGMEM = { + 0x03, UC8151_PSR, 0xcf, 0x8d, + 0x06, UC8151_PWR, 0x03, 0x10, 0x3f, 0x3f, 0x0d, + 0x01, UC8151_PON, // power on + BUSY_WAIT, + 0x05, UC8151_TRES, 0x01, 0x90, 1, 0x2c, // resolution + 0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US +// 0x02, 0x00, 0x0f, // LUT from OTP + 0x02, UC8151_CDI, 0x5c, // inverted, white border + 0x00 +}; + +// init sequence for 2.13" Random screen double connector +const uint8_t EPD213R_104x212_d_init_sequence_full[] PROGMEM = { + 0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US + 0x01, UC8151_PON, // power on + BUSY_WAIT, + 0x02, 0x00, 0x0f, // LUT from OTP + 0x02, UC8151_CDI, 0x5c, // inverted, white border + 0x00 +}; + +const uint8_t epd37_init_sequence_full[] PROGMEM = { + 0x01, UC8151_PON, // Power on + BUSY_WAIT, + 0x02, 0x00, 0x1f, // flip x + 0x02, 0x50, 0x97, // VCOM + 0x00 +}; + +const uint8_t epd37_init_sequence_part[] PROGMEM = { + 0x01, UC8151_PON, // Power on + BUSY_WAIT, + 0x02, 0x00, 0x1f, // flip x + 0x02, 0xe0, 0x02, + 0x02, 0xe5, 0x6e, + 0x02, 0x50, 0xd7, // VCOM + 0x00 +}; + +// initialization sequence for 3.7" 240x416 e-paper +const uint8_t epd37xx_init_sequence_full[] PROGMEM = { + 0x03, UC8151_PSR, 0xdf, 0x8d, + 0x06, UC8151_PWR, 0x03, 0x10, 0x3f, 0x3f, 0x0d, + 0x01, UC8151_PON, // power on + BUSY_WAIT, + 0x04, UC8151_TRES, 0xf0, 1, 0xa0, // resolution + + 0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US + 0x02, UC8151_PFS, 0x00, // FRAMES_1 + 0x02, UC8151_TSE, 0x00, // TEMP_INTERNAL | OFFSET_0 + 0x02, UC8151_TCON, 0x22, + 0x02, UC8151_CDI, 0xd7, // inverted, white border + 0x02, UC8151_PLL, 0x09, // HZ_50 + 0x00 // end of table +}; +// initialization sequence for 2.9" 296x128 e-paper +const uint8_t epd29_init_sequence_full[] PROGMEM = { + 0x02, UC8151_PSR, 0x80 | 0x00 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01, // RES_128x296, LUT_OTP, FORMAT_BW, SHIFT_LEFT, BOOSTER_ON, RESET_NONE + 0x06, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x2b, + 0x01, UC8151_PON, // power on + BUSY_WAIT, + 0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US + 0x02, UC8151_PFS, 0x00, // FRAMES_1 + 0x04, UC8151_TRES, 0x80, 1, 0x28, // resolution + 0x02, UC8151_TSE, 0x00, // TEMP_INTERNAL | OFFSET_0 + 0x02, UC8151_TCON, 0x22, + 0x02, UC8151_CDI, 0x9c, // inverted, white border + 0x02, UC8151_PLL, 0x3a, // HZ_100 + 0x00 // end of table +}; + +const uint8_t epd42r_init_sequence[] PROGMEM = { + 0x01, 0x12, // soft reset + BUSY_WAIT, + 0x02, 0x74, 0x54, // set analog block control + 0x02, 0x7e, 0x3b, // set digital block control + 0x03, 0x2b, 0x04, 0x63, // ACVCOM + 0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart + 0x04, 0x01, 0x2b, 0x01, 0x00, // output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x31, // RAM X start/end + 0x05, 0x45, 0,0,0x2b, 0x01, // RAM Y start/end + 0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red) + 0x02, 0x18, 0x80, // temp sensor = internal + 0x02, 0x21, 0x00, // display update ctrl 1 + 0x02, 0x22, 0xb1, // display update ctrl 2 + 0x01, 0x20, // master activation + BUSY_WAIT, + 0x02, 0x4e, 0x00, // RAM X counter + 0x03, 0x4f, 0x2b, 0x01, // RAM Y counter + 0x00 +}; +const uint8_t epd29r_init_sequence[] PROGMEM = { + 0x01, 0x12, // soft reset + BUSY_WAIT, + 0x02, 0x74, 0x54, // set analog block control + 0x02, 0x7e, 0x3b, // set digital block control + 0x03, 0x2b, 0x04, 0x63, // ACVCOM + 0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart + 0x04, 0x01, 0x27, 0x01, 0x00, // output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x0f, // RAM X start/end + 0x05, 0x45, 0,0,0x27, 0x01, // RAM Y start/end + 0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red) + 0x02, 0x18, 0x80, // temp sensor = internal + 0x02, 0x21, 0x00, // display update ctrl 1 + 0x02, 0x22, 0xb1, // display update ctrl 2 + 0x01, 0x20, // master activation + BUSY_WAIT, + 0x02, 0x4e, 0x00, // RAM X counter + 0x03, 0x4f, 0x27, 0x01, // RAM Y counter + 0x00 +}; + +// for 152x152 BWR +const uint8_t epd29r_init_sequence_152[] PROGMEM = { + 0x01, 0x12, // soft reset + BUSY_WAIT, + 0x02, 0x74, 0x54, // set analog block control + 0x02, 0x7e, 0x3b, // set digital block control + 0x03, 0x2b, 0x04, 0x63, // ACVCOM + 0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart + 0x04, 0x01, 0x97, 0x00, 0x00, // output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x12, // RAM X start/end + 0x05, 0x45, 0,0,0x97, 0x00, // RAM Y start/end + 0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red) + 0x02, 0x18, 0x80, // temp sensor = internal + 0x02, 0x21, 0x00, // display update ctrl 1 + 0x02, 0x22, 0xb1, // display update ctrl 2 + 0x01, 0x20, // master activation + BUSY_WAIT, + 0x02, 0x4e, 0x00, // RAM X counter + 0x03, 0x4f, 0x97, 0x00, // RAM Y counter + 0x00 +}; +const uint8_t epd75_init_sequence_partial[] PROGMEM = { + 0x06, UC8151_PWR, 0x17, 0x17, 0x3f, 0x3f, 0x11, + 0x02, 0x82, 0x26, // VCOM DC + // 0x05, UC8151_BTST, 0x27,0x27,0x2f,0x17, + // 0x02, 0x30, 0x06, // oscillator + 0x01, UC8151_PON, // power on + BUSY_WAIT, + 0x02, UC8151_PSR, 0x3f, // from register + // 0x05, 0x61, 0x03, 0x20, 0x01, 0xe0, // resolution + // 0x02, 0x15, 0x00, // SPI mode + 0x03, 0x50, 0x39, 0x07, // VCOM Data interval + // 0x02, 0x60, 0x22, // TCON + // 0x05, 0x65, 0x00, 0x00, 0x00, 0x00, // resolution +//#ifdef FUTURE +// 0x02, UC8151_PLL, 0x3a, // HZ_100 +// 0x02, UC8151_VDCS, 0x12, // VCOM DC setting +// 0x02, UC8151_CDI, 0x97, + 0x2d, UC8151_LUT_VCOM, // VCOM LUT + 0x00, 30, 5, 30, 5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ,0x00, 0x00, + 0x2b, UC8151_LUT_WW, // WW LUT + 0x00, 30, 5, 30, 5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, UC8151_LUT_BW, + 0x5a, 30, 5, 30, 5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, UC8151_LUT_WB, + 0x84, 30, 5, 30, 5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, UC8151_LUT_BB, + 0x00, 30, 5, 30, 5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, UC8151_LUT_VCOM2, + 0x00, 30, 5, 30, 5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +//#endif // FUTURE + 0x00 // end of table +}; +// 1.54" 152x152 +const uint8_t epd154a_init_sequence_full[] PROGMEM = +{ + 1, UC8151_PON, + BUSY_WAIT, + 2, UC8151_PSR, 0xdf, // panel setting + 4, UC8151_TRES, 0x98, 0, 0x98, // resolution + 2, UC8151_CDI, 0x97, // VCOM + 0 +}; +const uint8_t epd154a_init_sequence_part[] PROGMEM = +{ + 1, UC8151_PON, + BUSY_WAIT, + 2, UC8151_PSR, 0x3f, // panel setting + 6, UC8151_PWR, 0x03, 0x00, 0x21, 0x21, 0x03, + 4, UC8151_TRES, 0x98, 0, 0x98, // resolution + 2, UC8151_VDCS, 0x12, + 2, UC8151_CDI, 0x17, // VCOM + 37, 0x20, 0x00, 10, 0, 60, 10, 1, // VCOM LUT + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 37, 0x21, 0x00, 10, 0, 60, 10, 1, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 37, 0x22, 0x5a, 10, 0, 60, 10, 1, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 37, 0x23, 0x84, 10, 0, 60, 10, 1, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 37, 0x24, 0x00, 10, 0, 60, 10, 1, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0 +}; + +// 1.54" 200x200 +const uint8_t epd154_init_sequence_full[] PROGMEM = +{ + 0x01, 0x12, // sw reset + BUSY_WAIT, + 0x04, 0x01, 199, 0x00, 0x00, // driver output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x18, + 0x05, 0x45, 0x00, 0x00, 0xc7, 0x00, + 0x02, 0x3c, 0x05, // border waveform + 0x02, 0x18, 0x80, // read temp sensor +// 0x03, 0x21, 0x00, 0x80, // display update control + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0x00 // end of table +}; + +const uint8_t epd154_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, + 0x03, 0x44, 0x00, 0x18, + 0x05, 0x45, 0x00, 0x00, 199, 0x00, + 0x02, 0x3c, 0x80, // border waveform + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + 0 +}; + +// partial (no flash) updates +const uint8_t epd154b_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, + 0x02, 0x3c, 0x80, // border waveform + 0x03, 0x44, 0x00, 0x18, + 0x05, 0x45, 0x00, 0x00, 199, 0x00, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + + 154, 0x32, // LUT + 0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0xF,0x0,0x0,0x0,0x0,0x0,0x0, + 0x1,0x1,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0, + + 2, 0x3f, 0x2, // ?? + 2, 0x03, 0x17, // gate voltage + 4, 0x04, 0x41, 0xb0, 0x32, // source voltage + 2, 0x2c, 0x28, // VCOM + 11, 0x37, 0,0,0,0,0,0x40,0,0,0,0, // ?? + 2, 0x3c, 0x80, // VCOM ?? + + 0x00 // end of table +}; + +const uint8_t epd213b_init_sequence_full[] PROGMEM = +{ + 0x01, 0x12, // sw reset + BUSY_WAIT, + 0x04, 0x01, 0xf9, 0x00, 0x00, + 0x02, 0x11, 0x03, + 0x03, 0x44, 0x00, 0x0f, + 0x05, 0x45, 0x00, 0x00, 0xf9, 0x00, + 0x02, 0x3c, 0x05, // border waveform + 0x03, 0x21, 0x00, 0x80, // display update control + 0x02, 0x18, 0x80, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0x00 // end of table +}; + +const uint8_t epd213b_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, // data direction + 0x03, 0x44, 0x00, 0x0f, // x start/end + 0x05, 0x45, 0x00, 0x00, 0xf9, 0x00, // y start/end + 0x02, 0x3c, 0x80, // border waveform + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + 0x00 // end of table +}; + +const uint8_t epd294_init_sequence_full[] PROGMEM = +{ +// 0x02, 0x74, 0x54, +// 0x02, 0x7e, 0x3b, +// 0x03, 0x2b, 0x04, 0x63, +// 0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, + 0x04, 0x01, 0x27, 0x01, 0x00, + 0x02, 0x11, 0x03, + 0x03, 0x44, 0x00, 0x0f, + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x03, 0x21, 0x00, 0x80, + 0x02, 0x3c, 0xc0, + 0x02, 0x18, 0x80, + 0x02, 0x22, 0xb1, + 0x01, 0x20, + BUSY_WAIT, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + 0x00 // end of table +}; /* epd294_init_sequence_full[] */ + +const uint8_t epd102_init_sequence_full[] PROGMEM = +{ + 2, 0x00, 0x5f, // panel setting + 3, 0x2a, 0x00, 0x00, // IC hidden instructions + 1, 0x04, // power on + BUSY_WAIT, + 2, 0x50, 0x97, // VCOM + 0 +}; +const uint8_t epd102_init_sequence_part[] PROGMEM = +{ + 0x2, 0xd2, 0x3f, + 0x2, 0x00, 0x6f, // panel setting + 0x5, 0x01, 0x03, 0x00, 0x26, 0x26, // power + 0x2, 0x06, 0x3f, + 0x3, 0x2a, 0x00, 0x00, + 0x2, 0x30, 0x13, // 50Hz + 0x2, 0x50, 0xf2, + 0x2, 0x60, 0x22, + 0x2, 0x82, 0x12, // -0.1v + 0x2, 0xe3, 0x33, +// send LUTs + 43, 0x23, // white + 0x60 ,0x01 ,0x01 ,0x00 ,0x00 ,0x01 , + 0x80 ,0x0f ,0x00 ,0x00 ,0x00 ,0x01 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 43, 0x24, // black + 0x90 ,0x01 ,0x01 ,0x00 ,0x00 ,0x01 , + 0x40 ,0x0f ,0x00 ,0x00 ,0x00 ,0x01 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , + 0x1, 0x4, // power on + BUSY_WAIT, + 0 +}; + +const uint8_t epd293_init_sequence_full[] PROGMEM = +{ + 0x01, SSD1608_SW_RESET, + BUSY_WAIT, + + 0x04, 0x01, 0x27, 0x01, 0x00, // driver output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x0f, // ram start/end + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x02, 0x3c, 0xc0, // border waveform + 0x03, 0x21, 0x00, 0x80, // display update control + 0x02, 0x18, 0x80, // read built-in temp sensor + + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0x00 // end of table +}; +// less flashing (fast) updates +const uint8_t epd293_init_sequence_fast[] PROGMEM = +{ + 0x01, SSD1608_SW_RESET, + BUSY_WAIT, + 0x02, 0x18, 0x80, // read built-in temp sensor + 0x02, 0x22, 0xb1, // load temp value + 0x01, 0x20, // execute + BUSY_WAIT, + 0x03, 0x1a, 0x64, 0x00, // write temp register + 0x02, 0x22, 0x91, // load temp value + 0x01, 0x20, // execute + BUSY_WAIT, + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x0f, // ram start/end + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0x00 // end of table +}; + +// partial (no flash) updates +const uint8_t epd293_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, + 0x02, 0x3c, 0x80, // border waveform + 0x03, 0x44, 0x00, 0x0f, + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + 0x00 // end of table +}; + +// partial (no flash) updates +const uint8_t epd295_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, + 0x02, 0x3c, 0x80, // border waveform + 0x03, 0x44, 0x00, 0x0f, + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + + 91, 0x32, // LUT + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00 // end of table +}; + +const uint8_t epd266_init_sequence_full[] PROGMEM = +{ + 0x01, SSD1608_SW_RESET, + BUSY_WAIT, + + 0x04, 0x01, 0x27, 0x01, 0x00, // driver output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x12, // ram start/end + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x02, 0x3c, 0x05, // border waveform + 0x03, 0x21, 0x00, 0x80, // display update control + 0x02, 0x18, 0x80, // read built-in temp sensor + + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0x00 // end of table +}; /* epd266_init_sequence_full[] */ + +const uint8_t epd266_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, + 0x02, 0x3c, 0x80, // border waveform + 0x03, 0x44, 0x00, 0x12, + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + + 0x00 // end of table +}; + +const uint8_t epd27_init_sequence_full[] PROGMEM = +{ + 0x04, 0x01, 0x07, 0x01, 0x00, // driver output control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x15, // ram address + 0x05, 0x45, 0x00, 0x00, 0x07, 0x01, + 0x02, 0x3c, 0x80, // border color + 0x02, 0x18, 0x80, // read built-in temp sensor + 0x02, 0x4e, 0x00, // ram counter x + 0x03, 0x4f, 0x00, 0x00, // ram counter y + 0x02, 0x22, 0xb1, + 0x01, 0x20, + BUSY_WAIT, + 0x00 // end of table +}; + +const uint8_t epd27_init_sequence_part[] PROGMEM = +{ + 0x02, 0x11, 0x03, // data entry mode + 0x02, 0x3c, 0x80, // border color + 0x03, 0x44, 0x00, 0x15, // ram address + 0x05, 0x45, 0x00, 0x00, 0x07, 0x01, + 0x02, 0x4e, 0x00, // ram counter x + 0x03, 0x4f, 0x00, 0x00, // ram counter y + + 0x00 // end of table +}; + +const uint8_t epd42b_init_sequence_full[] PROGMEM = +{ + 0x01, SSD1608_SW_RESET, + BUSY_WAIT, + + 0x04, 0x01, 0x2b, 0x01, 0x00, // driver output control + 0x03, 0x21, 0x40, 0x00, // display update control + 0x02, 0x11, 0x03, // data entry mode + 0x03, 0x44, 0x00, 0x31, // ram start/end + 0x05, 0x45, 0x00, 0x00, 0x2b, 0x01, + 0x02, 0x3c, 0x05, // border waveform + 0x02, 0x18, 0x80, // read built-in temp sensor + + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0x00 // end of table +}; + +const uint8_t epd42b_init_sequence_fast[] PROGMEM = +{ + 0x01, SSD1608_SW_RESET, + BUSY_WAIT, + 3, 0x21, 0x40, 0x00, + 2, 0x3c, 0x05, + 2, 0x1a, 0x6e, // temp register + 2, 0x22, 0x91, // load temp + 1, 0x20, + BUSY_WAIT, + 2, 0x11, 0x3, // data entry mode + 0x03, 0x44, 0x00, 0x31, // ram start/end + 0x05, 0x45, 0x00, 0x00, 0x2b, 0x01, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + BUSY_WAIT, + 0 +}; +const uint8_t epd42b_init_sequence_part[] PROGMEM = +{ + 0x03, 0x21, 0x00, 0x00, + 0x02, 0x11, 0x03, // data entry mode + 0x02, 0x3c, 0x80, // border color + 0x03, 0x44, 0x00, 0x31, // ram address + 0x05, 0x45, 0x00, 0x00, 0x2b, 0x01, + 0x02, 0x4e, 0x00, // ram counter x + 0x03, 0x4f, 0x00, 0x00, // ram counter y + + 0x00 // end of table +}; + +const uint8_t epd579_init_sequence_full[] PROGMEM = +{ + 2, 0x11, 0x03, // data format + 2, 0x3c, 0x80, // border color + 0x00 // end of table +}; /* epd579_init_sequence_full[] */ + +const uint8_t epd29b_init_sequence_full[] PROGMEM = { + 1, SSD1608_SW_RESET, + BUSY_WAIT, + 2, 0x74, 0x54, + 2, 0x7e, 0x3b, + 4, 0x01, 39, 1, 0, // gate setting (height -1) + 2, 0x11, 0x3, // data entry mode + 2, 0x3c, 0x03, // black border + 2, 0x2c, 0x55, // VCOM + 2, 0x03, 0x17, // gate drive voltage + 4, 0x04, 0x41, 0xac, 0x32, // source driving voltage + 2, 0x3a, 0x07, // dummy line period + 2, 0x3b, 0x04, // get line width + 2, 0x4e, 0x00, // ram x start + 3, 0x4f, 0x00, 0x00, // ram y start + BUSY_WAIT, + 0 +}; +const uint8_t epd213_104x212_init_sequence_full[] PROGMEM = { + 1, SSD1608_SW_RESET, + BUSY_WAIT, + 2, 0x74, 0x54, + 2, 0x7e, 0x3b, + 4, 0x01, 211, 0, 0, // gate setting (height -1) + 2, 0x11, 0x3, // data entry mode + 2, 0x3c, 0x03, // black border + 2, 0x2c, 0x55, // VCOM + 2, 0x03, 0x17, // gate drive voltage + 4, 0x04, 0x41, 0xac, 0x32, // source driving voltage + 2, 0x3a, 0x07, // dummy line period + 2, 0x3b, 0x04, // get line width + 2, 0x4e, 0x00, // ram x start + 3, 0x4f, 0x00, 0x00, // ram y start + BUSY_WAIT, + 0 +}; + +const uint8_t epd213_122x250_init_sequence_full[] PROGMEM = { + 1, SSD1608_SW_RESET, + BUSY_WAIT, + 2, 0x74, 0x54, + 2, 0x7e, 0x3b, + 4, 0x01, 249, 0, 0, // gate setting (height -1) + 2, 0x11, 0x3, // data entry mode + 2, 0x3c, 0x03, // black border + 2, 0x2c, 0x55, // VCOM + 2, 0x03, 0x15, // gate drive voltage + 4, 0x04, 0x41, 0xa8, 0x32, // source driving voltage + 2, 0x3a, 0x30, // dummy line period + 2, 0x3b, 0x0a, // get line width + 2, 0x4e, 0x00, // ram x start + 3, 0x4f, 0x00, 0x00, // ram y start + BUSY_WAIT, + 0 +}; + +const uint8_t epd213_122x250_init_sequence_part[] PROGMEM = { + 2, 0x3c, 0x80, // border + 2, 0x11, 0x3, // data entry mode + 3, 0x44, 0x00, 0xf, + 5, 0x45, 0x00, 0x00, 0xf9, 0x00, + 2, 0x4e, 0x00, // ram x start + 3, 0x4f, 0x00, 0x00, // ram y start + + 0 +}; +const uint8_t epd75_init_sequence_full[] PROGMEM = { + 6, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03, + 1, UC8151_PON, + BUSY_WAIT, + 2, UC8151_PSR, 0x1f, + 5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0, + 2, 0x15, 0x00, + 3, UC8151_CDI, 0x29, 0x07, + 2, UC8151_TCON, 0x22, +// 4, UC8151_BTST, 0x17, 0x17, 0x17, +// 2, UC8151_PFS, 0x00, +// 2, UC8151_TSE, 0x00, +// 2, UC8151_PLL, 0x3a, +// 2, UC8151_VDCS, 0x12, + 0 +}; + +const uint8_t epd75_init_sequence_fast[] PROGMEM = { + 2, 0x00, 0x1f, // panel setting + 3, 0x50, 0x10, 0x07, // VCOM + 1, 0x04, // Power On + BUSY_WAIT, + 5, 0x06, 0x27, 0x27, 0x18, 0x17, // booster soft start + 2, 0xe0, 0x02, + 2, 0xe5, 0x5a, + 0 +}; + +const uint8_t epd583_init_sequence_full[] PROGMEM = { + 2, UC8151_PSR, 0x9f, + 6, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x2b, + 1, UC8151_PON, + BUSY_WAIT, + 4, UC8151_BTST, 0x17, 0x17, 0x17, + 2, UC8151_PFS, 0x00, + 2, UC8151_TSE, 0x00, + 2, UC8151_TCON, 0x22, + 2, UC8151_CDI, 0xd7, + 2, UC8151_PLL, 0x3a, + 5, UC8151_TRES, 0x02, 0x88, 0x01, 0xe0, + 2, UC8151_VDCS, 0x12, + 0 +}; + +const uint8_t epd583_init_sequence_part[] PROGMEM = { + 1, UC8151_PON, + BUSY_WAIT, + 3, 0x00, 0x1f, 0x0b, // panel setting + 2, 0x50, 0x97, // VCOM + 5, 0x01, 0x03,0x00,0x2b,0x2b, // power setting + 4, 0x06, 0x17,0x17,0x17, // boost soft start + 1, 0x04, // power on + BUSY_WAIT, + 3, 0x00, 0xbf, 0x0b, // panel setting + 2, 0x30, 0x3c, // 3A 100HZ + 5, 0x61, 0x02, 0x88, 0x01, 0xe0, // resolution 648x480 + 2, 0x82, 0x12, // vcom_DC + 2, 0x50, 0x47, // mode + 45, 0x20, // VCOM LUT + 0x00, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x21, // WW LUT + 0x00, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x22, // BW LUT + 0x20, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x23, // WB LUT + 0x10, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x24, // BB LUT + 0x00, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00 // end of table +};const uint8_t epd42_4gray_init_sequence_full[] PROGMEM = { + 6, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x13, + 4, UC8151_BTST, 0x17, 0x17, 0x17, + 1, UC8151_PON, + BUSY_WAIT, + 2, UC8151_PSR, 0x3f, + 2, UC8151_PLL, 0x3c, + 5, UC8151_TRES, 0x01, 0x90, 0x01, 0x2c, + 2, UC8151_VDCS, 0x12, + 2, UC8151_CDI, 0x97, + 0 +}; + +const uint8_t epd42_init_sequence_full[] PROGMEM = { + 1, UC8151_PON, + BUSY_WAIT, + 3, 0x00, 0x1f, 0x0b, // panel setting + 5, UC8151_TRES, 0x01, 0x90, 0x01, 0x2c, + 2, 0x50, 0x97, // VCOM + 0 +}; + +const uint8_t epd42_init_sequence_part[] PROGMEM = { + 1, UC8151_PON, + BUSY_WAIT, + 3, 0x00, 0x1f, 0x0b, // panel setting + 2, 0x50, 0x97, // VCOM + 5, 0x01, 0x03,0x00,0x2b,0x2b, // power setting + 4, 0x06, 0x17,0x17,0x17, // boost soft start + 1, 0x04, // power on + BUSY_WAIT, + 3, 0x00, 0xbf, 0x0b, // panel setting + 2, 0x30, 0x3c, // 3A 100HZ + 5, 0x61, 0x01, 0x90, 0x01, 0x2c, // resolution 400x300 + 2, 0x82, 0x12, // vcom_DC + 2, 0x50, 0x47, // mode + 45, 0x20, // VCOM LUT + 0x00, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x21, // WW LUT + 0x00, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x22, // BW LUT + 0x20, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x23, // WB LUT + 0x10, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 43, 0x24, // BB LUT + 0x00, 0x01, 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00 // end of table +}; +#ifndef WIMPY_MCU +const uint8_t st7302_wenting[] PROGMEM PROGMEM = { + 0x02, 0xEB, 0x02, // Enable OTP + 0x02, 0xD7, 0x68, // OTP Load Control + 0x02, 0xD1, 0x01, // Auto Power Control + 0x02, 0xC0, 0x40, // Gate Voltage setting + 0x07, 0xC1, 0x22, 0x22, 0x22, 0x22, 0x14, 0x00, // VSH Setting + 0x05, 0xC2, 0x00, 0x00, 0x00, 0x00, // VSL Setting + 0x02, 0xCB, 0x0E, // VCOMH Setting + 0x0B, 0xB4, 0xE5, 0x66, 0x85, 0xFF, 0xFF, 0x52, 0x85, 0xFF, 0xFF, 0x52, // Gate EQ + 0x03, 0xC7, 0xA6, 0xE9, // OSC Setting + 0x02, 0xB0, 0x3F, // Duty Setting + 0x03, 0xB2, 0x00, 0x00, // Frame Rate Setting (lowest for both HPM/LPM) + 0x02, 0x36, 0x00, // Memory Access Mode + 0x02, 0x3A, 0x11, // Data Format + 0x02, 0xB9, 0x23, // Source Setting + 0x02, 0xB8, 0x09, // Source Setting + 0x01, 0x11, // Sleep out + 0x02, 0xff, 100, // delay 100 + 0x02, 0xD0, 0x1F, // enable auto power down + 0x01, 0x39, // Low power mode + 0x01, 0x29, // display on + 0x00 +}; + +const uint8_t st7302_hpm_init[] PROGMEM = { + 0x02, 0xEB, 0x02, // Enable OTP + 0x02, 0xD7, 0x68, // OTP Load Control + 0x02, 0xD1, 0x01, // Auto Power Control + 0x02, 0xC0, 0xba , // Gate Voltage setting + 0x02, 0xCB, 0x00, // VCOMH Setting + 0x02, 0xB3, 0x94, // VCOMEQ enable + 0x07, 0xC1, 0x28, 0x28, 0x28, 0x28, 0x14, 0x00, // source voltage VSH + 0x05, 0xC2, 0x00, 0x00, 0x00, 0x00, // VSL Setting + 0x03, 0xB2, 0x01, 0x05, // Frame Rate Setting (lowest for both HPM/LPM) + 0x0B, 0xB4, 0xA5, 0x66, 0xFD, 0xBF, 0x42, 0x55, 0x81, 0xBE, 0x42, 0x55, // Gate EQ + 0x02, 0xB7, 0x01, // Source eq enable + 0x01, 0x11, // Sleep out + 0x02, 0xff, 100, // delay 100 + 0x02, 0x72, 0x13, // gate setting + 0x02, 0xB0, 0x3F, // Duty Setting + 0x03, 0xC7, 0xA6, 0xE9, // OSC Setting + 0x02, 0xD6, 0x00, // VSHLSEL source voltage select + 0x02, 0x36, 0x00, // Memory Access Mode + 0x02, 0x3A, 0x11, // Data Format + 0x02, 0xB9, 0x23, // Source Setting + 0x02, 0xB8, 0x09, // Source Setting + 0x02, 0x35, 0x00, // tearing effect line on +// 0x02, 0xD0, 0x1F, // enable auto power down + 0x01, 0x38, // High power mode + 0x01, 0x29, // display on + 0x00 +}; +#endif // !WIMPY_MCU + +// Definitions for each supported panel +// The order tracks that of the enumerated panel types +// ** ONLY ADD NEW PANELS TO THE END OF THE LIST ** +// +const EPD_PANEL panelDefs[] = { + {400, 300, epd42_init_sequence_full, NULL, epd42_init_sequence_part, 0, BBEI_CHIP_UC8151}, // EPD42_400x300 + {400, 300, epd42b_init_sequence_full, epd42b_init_sequence_fast, epd42b_init_sequence_part, 0, BBEI_CHIP_SSD16xx}, // EPD42B_400x300 + {122, 250, epd213b_init_sequence_full, NULL, epd213b_init_sequence_part, 0, BBEI_CHIP_SSD16xx}, // EPD213B_122x250 + {128, 296, epd293_init_sequence_full, epd293_init_sequence_fast, epd293_init_sequence_part, 0, BBEI_CHIP_SSD16xx}, // EPD293_128x296 + {128, 296, epd294_init_sequence_full, NULL, NULL, 0, BBEI_CHIP_SSD16xx}, // EPD294_128x296 + {128, 296, epd293_init_sequence_full, epd293_init_sequence_fast, epd295_init_sequence_part, 0, BBEI_CHIP_SSD16xx}, // EPD295_128x296 + {152, 296, epd266_init_sequence_full, NULL, epd266_init_sequence_part, 0, BBEI_CHIP_SSD16xx}, // EPD266_152x296 + {80, 128, epd102_init_sequence_full, NULL, epd102_init_sequence_part, 0, BBEI_CHIP_UC8151}, // EPD102_80x128 + {176, 264, epd27_init_sequence_full, NULL, epd27_init_sequence_part, 0, BBEI_CHIP_SSD16xx}, // EPD27B_176x264 +}; +int bbeiSetPanelType(BBEIDISP *pBBEI, int iPanel) +{ + if (pBBEI == NULL || iPanel <= EPD_PANEL_UNDEFINED || iPanel >= EPD_PANEL_COUNT) + return BBEI_ERROR_BAD_PARAMETER; + + memset(&pBBEI, 0, sizeof(BBEIDISP)); + pBBEI->native_width = pBBEI->width = panelDefs[iPanel].width; + pBBEI->native_height = pBBEI->height = panelDefs[iPanel].height; + pBBEI->chip_type = panelDefs[iPanel].chip_type; + pBBEI->iFlags = panelDefs[iPanel].flags; + pBBEI->pInitFull = panelDefs[iPanel].pInitFull; + pBBEI->pInitFast = panelDefs[iPanel].pInitFast; + pBBEI->pInitPart = panelDefs[iPanel].pInitPart; + return BBEI_SUCCESS; +} /* bbeiSetPanelType() */ + +void bbeiSetPosition(OBDISP *pBBEI, int x, int y, int w, int h) +{ + uint8_t uc[12]; + int i, tx, ty; + + tx = x/8; // round down to next lower byte + ty = y; + cx = (cx + 7) & 0xfff8; // make width an even number of bytes + if (pBBEI->chip_type == EEBI_CHIP_UC8151) { + bbeiWriteCmd(pBBEI, UC8151_PTIN); // partial in + bbeiWriteCmd(pBBEI, UC8151_PTL); // partial window + i = 0; + tx *= 8; + if (iWidth >= 256) { // need 2 bytes per x + uc[i++] = (uint8_t)(tx>>8); // start x + uc[i++] = (uint8_t)tx; + uc[i++] = (uint8_t)((tx+cx-1)>>8); // end x + uc[i++] = (uint8_t)((tx+cx-1) | 7); + } else { + uc[i++] = tx; // start x + uc[i++] = (tx+cx-1) | 7; // end x + } + if (iHeight >= 256) { + uc[i++] = (uint8_t)(ty>>8); // start y + uc[i++] = (uint8_t)ty; + uc[i++] = (uint8_t)((ty+cy-1)>>8); // end y + uc[i++] = (uint8_t)(ty+cy-1); + } else { + uc[i++] = (uint8_t)ty; + uc[i++] = (uint8_t)(ty+cy-1); + } + uc[i++] = 1; // refresh whole screen (0=refresh partial window only) + bbeiWriteData(pBBEI, uc, i); + // EPDWriteCmd(UC8151_PTOU); // partial out + } else { // SSD16xx + bbeiWriteCmd(pBBEI, SSD1608_SET_RAMXPOS); + uc[0] = tx; // start x (byte boundary) + uc[1] = tx+((cx-1)>>3); // end x + bbeiWriteData(pBBEI, uc, 2); + bbeiWriteCmd(pBBEI, SSD1608_SET_RAMYPOS); + uc[0] = (uint8_t)ty; // start y + uc[1] = (uint8_t)(ty>>8); + uc[2] = (uint8_t)(ty+cy-1); // end y + uc[3] = (uint8_t)((ty+cy-1)>>8); + bbeiWriteData(pBBEI, uc, 4); + // set ram counter to start of this region + bbeiCMD2(pBBEI, SSD1608_SET_RAMXCOUNT, tx); + uc[0] = ty; + uc[1] = (ty>>8); + bbeiWriteCmd(pBBEI, SSD1608_SET_RAMYCOUNT); + bbeiWriteData(pBBEI, uc, 2); + // EPD_CMD2(SSD1608_DATA_MODE, 0x3); + } +} /* bbeiSetPosition() */ + +void obdWriteCommand(OBDISP *pBBEI, unsigned char c); +void obdWriteDataBlock(OBDISP *pBBEI, unsigned char *ucBuf, int iLen, int bRender); +const uint8_t ucMirror[256] PROGMEM = + {0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255}; + +// +// Draw the contents of a memory buffer onto a display +// The sub-window will be clipped if it specifies too large an area +// for the destination display. The source OBDISP structure must have +// a valid back buffer defined +// The top and bottom destination edges will be drawn on byte boundaries (8 rows) +// The source top/bot edges can be on pixel boundaries +// This can be used for partial screen updates +// +void obdDumpWindow(OBDISP *pBBEISrc, OBDISP *pBBEIDest, int srcx, int srcy, int destx, int desty, int width, int height) +{ +uint8_t *s,ucTemp[32]; // temp buffer to gather source pixels +int x, y, tx, i; +int iPitch; + + if (pBBEISrc == NULL || pBBEIDest == NULL || pBBEISrc->ucScreen == NULL) + return; // invalid pointers + if (width > pBBEIDest->width) + width = pBBEIDest->width; + if (height > pBBEIDest->height) + height = pBBEIDest->height; + iPitch = pBBEISrc->width; + for (y=0; yucScreen[((srcy+y)/8)*iPitch + srcx+x]; + if (srcy & 7) // need to shift the bits to get 8 rows of src data + { + uint8_t uc, ucShift = srcy & 7; + for (i=0; i> ucShift; + uc |= s[iPitch] << (7-ucShift); + ucTemp[i] = uc; + } + obdCachedWrite(pBBEIDest, ucTemp, tx, 1); + } + else + { // simpler case + obdCachedWrite(pBBEIDest, s, tx, 1); // just copy it + } + } // for x + } // for y + obdCachedFlush(pBBEIDest, 1); +} /* obdDumpWindow() */ + +// +// Write a subset of lines to a Sharp memory LCD +// +void obdWriteLCDLines(OBDISP *pBBEI, int iStart, int iCount) +{ + int x, y, j; + uint8_t uc, c, ucMask, *s, *d, ucStart; + uint8_t ucLineBuf[56]; // 400 pixels is max supported width = 50 bytes + 4 + int iPitch = pBBEI->width / 8; + static int iVCOM = 0; + + if (pBBEI == NULL || pBBEI->chip_type != BBEI_CHIP_SHARP || pBBEI->ucScreen == NULL || iStart < 0 || iStart > pBBEI->native_height-1 || iStart+iCount < 0 || iStart+iCount > pBBEI->native_height-1) { + return; // invalid request + } + + digitalWrite(pBBEI->iCSPin, HIGH); // active high + + ucStart = 0x80; // write command + iVCOM++; + if (iVCOM & 0x40) // flip it every 64 lines + ucStart |= 0x40; // VCOM bit + ucLineBuf[0] = ucStart; + RawWriteData(pBBEI, ucLineBuf, 1); // write command(01) + vcom(02) + + for (y=iStart; yucScreen[(y >> 3) * pBBEI->native_width]; + ucLineBuf[0] = pgm_read_byte(&ucMirror[y+1]); // current line number + for (x=0; x=0; j--) { + c = *s++; + if (c & ucMask) uc ^= (1 << j); + } + *d++ = uc; + } // for x + // write this line to the display + ucLineBuf[iPitch+1] = 0; // end of line + RawWriteData(pBBEI, ucLineBuf, iPitch+2); + } // for y + ucLineBuf[0] = 0; + RawWriteData(pBBEI, ucLineBuf, 1); // final transfer + digitalWrite(pBBEI->iCSPin, LOW); // de-activate +} /* obdWriteLCDLines() */ + +// +// Turn the display on or off +// +void obdPower(OBDISP *pBBEI, int bOn) +{ +uint8_t ucCMD; + + if (!pBBEI) return; + switch (pBBEI->type) { + case LCD_NOKIA5110: + ucCMD = (bOn) ? 0x20 : 0x24; + break; +#if !defined( WIMPY_MCU ) && !defined(__AVR__) + case LCD_ST7302: + ucCMD = (bOn) ? 0x29 : 0x28; + break; +#endif + default: // all other supported displays + ucCMD = (bOn) ? 0xaf : 0xae; + break; + // e-ink panels are put to sleep after each action + } + obdWriteCommand(pBBEI, ucCMD); +} /* obdPower() */ + +// Controls the LED backlight +void obdBacklight(OBDISP *pBBEI, int bOn) +{ + if (pBBEI->iLEDPin != 0xff) + { + digitalWrite(pBBEI->iLEDPin, (bOn) ? HIGH:LOW); + } +} /* obdBacklight() */ + +// +// Send the command sequence to power up the LCDs +static void LCDPowerUp(OBDISP *pBBEI) +{ +#ifndef MEMORY_ONLY + int iLen; + uint8_t *s; + u8Cache[0] = 0x00; // send everything in command mode + if (pBBEI->type == LCD_UC1701 || pBBEI->type == LCD_UC1609) + { + s = (uint8_t *)uc1701_initbuf; + iLen = sizeof(uc1701_initbuf); + } + else if (pBBEI->type == LCD_HX1230) + { + s = (uint8_t *)hx1230_initbuf; + iLen = sizeof(hx1230_initbuf); + } + else if (pBBEI->type == LCD_NOKIA5110) // Nokia 5110 + { + s = (uint8_t *)nokia5110_initbuf; + iLen = sizeof(nokia5110_initbuf); + } + else if (pBBEI->type == LCD_32x64) + { + s = (uint8_t *)st7567_initbuf; + iLen = sizeof(st7567_initbuf); + } + memcpy_P(&u8Cache[1], s, iLen); + RawWrite(pBBEI, u8Cache, iLen+1); + _delay(100); + obdWriteCommand(pBBEI, 0xa5); + _delay(100); + obdWriteCommand(pBBEI, 0xa4); + obdWriteCommand(pBBEI, 0xaf); +#endif // MEMORY_ONLY +} /* LCDPowerUp() */ +// +// More efficient means of sending commands, data and busy-pauses +// +void EPDSendCMDSequence(OBDISP *pBBEI, const uint8_t *pSeq) +{ +int iLen; +uint8_t *s; + + s = (uint8_t *)pSeq; + while (pgm_read_byte(s) != 0) { + iLen = pgm_read_byte(s++); + if (iLen == BUSY_WAIT) { + Serial.println("Calling busy_wait from cmd list"); + EPDWaitBusy(pBBEI, 1); + } else { + obdWriteCommand(pBBEI, pgm_read_byte(s)); + s++; + if (iLen > 1) { + memcpy_P(u8Cache, s, iLen-1); + s += (iLen-1); + RawWriteData(pBBEI, u8Cache, iLen-1); + } + } + } // while more commands to send +} /* EPDSendCMDSequence() */ + +void EPD29_Init(OBDISP *pBBEI) +{ +uint8_t ucTemp[4]; + + obdWriteCommand(pBBEI, UC8151_PON); // power on + EPDWaitBusy(pBBEI, 1); + EPD_CMD2(pBBEI, UC8151_PSR, 0x80 | 0x10 | 0x00 | 0x02 | 0x01); // panel setting + obdWriteCommand(pBBEI, UC8151_TRES); // resolution + ucTemp[0] = (uint8_t)pBBEI->native_width; + ucTemp[1] = (uint8_t)(pBBEI->native_height >> 8); + ucTemp[2] = (uint8_t)(pBBEI->native_height & 0xff); + RawWriteData(pBBEI, ucTemp, 3); +// obdWriteCommand(pBBEI, UC8151_CDI); + EPDSleep(pBBEI, 0); +} /* EPD29_Init() */ + +const unsigned char lut_w_full[] PROGMEM = +{ + 0x60, 0x5A, 0x5A, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char lut_b_full[] PROGMEM = +{ + 0x90, 0x5A, 0x5A, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#ifdef FUTURE +const uint8_t epd213_lut[] PROGMEM = +{ + // black + 0x48, 0x50, 0x10, 0x10, 0x13, 0x00, 0x00, + 0x48, 0x50, 0x80, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x55, 0x00, 0xbb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x10, 0x04, 0x04, 0x04, 0x04, + 0x10, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x08, 0x08, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; +#endif + +static const uint8_t epd294_lut_partial[] PROGMEM = { +0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0A,0x0,0x0,0x0,0x0,0x0,0x2, +0x1,0x0,0x0,0x0,0x0,0x0,0x0, +0x1,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x0,0x0,0x0,0x0, +0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0, +//0x22,0x17,0x41,0xB0,0x32,0x36, +}; + +const uint8_t epd213_lut2_partial[] PROGMEM = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7 + 0x80,0x00,0x00,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7 + 0x40,0x00,0x00,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7 + + 0x0A,0x00,0x00,0x00,0x00, // TP0 A~D RP0 + 0x00,0x00,0x00,0x00,0x00, // TP1 A~D RP1 + 0x00,0x00,0x00,0x00,0x00, // TP2 A~D RP2 + 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3 + 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4 + 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5 + 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6 + +}; + +const uint8_t epd213_lut2_full[] PROGMEM = +{ + // black + 0x80,0x60,0x40,0x00,0x00,0x00,0x00, + 0x10,0x60,0x20,0x00,0x00,0x00,0x00, + 0x80,0x60,0x40,0x00,0x00,0x00,0x00, + 0x10,0x60,0x20,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x03,0x03,0x00,0x00,0x02, + 0x09,0x09,0x00,0x00,0x02, + 0x03,0x03,0x00,0x00,0x02, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00 +}; + +const uint8_t lut154_full_update[] PROGMEM = + { + 0x80,0x48,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x40,0x48,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8, 0x1, 0x0, 0x8, 0x1, 0x0, 0x2, + 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0 + }; + +const uint8_t lut154_partial_update[] PROGMEM = +{ + 0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0xF,0x0,0x0,0x0,0x0,0x0,0x0, + 0x1,0x1,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x0, + 0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0 +}; + +// +// Initialize the display controller on an SPI bus +// +void obdSPIInit(OBDISP *pBBEI, int iType, int iDC, int iCS, int iReset, int iMOSI, int iCLK, int iLED, int bFlip, int bInvert, int bBitBang, int32_t iSpeed) +{ +#ifndef MEMORY_ONLY + uint8_t *s=NULL; + int iLen=0; + + if (pBBEI == NULL) + return; + pBBEI->iTimeout = 5000; // 5 seconds for most e-ink panels + pBBEI->can_flip = 1; // assume can flip 180 + pBBEI->ucScreen = NULL; // start with no backbuffer; user must provide one later + pBBEI->x_offset = 0; // e-paper memory column offset + pBBEI->iDCPin = iDC; + pBBEI->iCSPin = iCS; + pBBEI->iRSTPin = iReset; + pBBEI->iMOSIPin = iMOSI; + pBBEI->iCLKPin = iCLK; + pBBEI->iLEDPin = iLED; + pBBEI->type = iType; + pBBEI->chip_type = BBEI_CHIP_SSD13xx; // assume common OLEDs + pBBEI->flip = bFlip; + pBBEI->invert = bInvert; + pBBEI->bBitBang = bBitBang; + pBBEI->wrap = 0; // default - disable text wrap + pBBEI->com_mode = COM_SPI; // communication mode + if (pBBEI->iDCPin != 0xff) // Note - not needed on Sharp Memory LCDs + { + pinMode(pBBEI->iDCPin, OUTPUT); + digitalWrite(pBBEI->iDCPin, 0); // for some reason, command mode must be set or some OLEDs/LCDs won't initialize correctly even if set later + } + if (pBBEI->iCSPin != 0xff) + { + pinMode(pBBEI->iCSPin, OUTPUT); + if (iType == SHARP_400x240 || iType == SHARP_128x128 || iType == SHARP_160x68) { + digitalWrite(pBBEI->iCSPin, LOW); + } else { + digitalWrite(pBBEI->iCSPin, HIGH); // set to not-active + } + } + if (bBitBang) + { + pinMode(iMOSI, OUTPUT); + pinMode(iCLK, OUTPUT); + } + + // Reset it + if (pBBEI->iRSTPin != 0xff) + { + pinMode(pBBEI->iRSTPin, OUTPUT); + digitalWrite(pBBEI->iRSTPin, HIGH); + _delay(200); + digitalWrite(pBBEI->iRSTPin, LOW); + _delay(10); + digitalWrite(pBBEI->iRSTPin, HIGH); + _delay(200); + } + if (iLED != -1 && iLED != 0xff) + { + if (iType >= EPD42_400x300) // for EPD displays, LED = BUSY + pinMode(iLED, INPUT_PULLUP); + else + pinMode(iLED, OUTPUT); + } + initSPI(pBBEI, iSpeed, iMOSI, iCLK, iCS); + pBBEI->native_width = pBBEI->width = 128; // assume 128x64 + pBBEI->height = 64; +#if !defined( WIMPY_MCU ) && !defined(__AVR__) + if (iType == LCD_ST7302) + { + pBBEI->native_width = pBBEI->width = 250; + pBBEI->native_height = pBBEI->height = 122; + pBBEI->can_flip = 0; + } +#endif + if (iType == OLED_80x128) + { + pBBEI->native_width = pBBEI->width = 80; + pBBEI->native_height = pBBEI->height = 128; + } + else if (iType == SHARP_160x68) + { + pBBEI->native_width = pBBEI->width = 160; + pBBEI->native_height = pBBEI->height = 68; + pBBEI->chip_type = BBEI_CHIP_SHARP; + pBBEI->can_flip = 0; + pBBEI->iDCPin = 0xff; // no D/C wire on this display + } + else if (iType == SHARP_128x128) + { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 128; + pBBEI->chip_type = BBEI_CHIP_SHARP; + pBBEI->can_flip = 0; + pBBEI->iDCPin = 0xff; // no D/C wire on this display + } + else if (iType == SHARP_144x168) + { + pBBEI->native_width = pBBEI->width = 144; + pBBEI->native_height = pBBEI->height = 168; + pBBEI->chip_type = BBEI_CHIP_SHARP; + pBBEI->can_flip = 0; + pBBEI->iDCPin = 0xff; // no D/C wire on this display + } + else if (iType == SHARP_400x240) + { + pBBEI->native_width = pBBEI->width = 400; + pBBEI->native_height = pBBEI->height = 240; + pBBEI->chip_type = BBEI_CHIP_SHARP; + pBBEI->can_flip = 0; + pBBEI->iDCPin = 0xff; // no D/C wire on this display + pBBEI->iOrientation = 0; + return; + } + else if (iType == EPD583_648x480) { + pBBEI->native_width = pBBEI->width = 648; + pBBEI->native_height = pBBEI->height = 480; + pBBEI->busy_idle = HIGH; + pBBEI->can_flip = 0; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->type = EPD583_648x480; // same for the rest + pBBEI->pInitFull = epd583_init_sequence_full; + pBBEI->pInitPart = epd583_init_sequence_part; + return; // nothing else to do yet + } +#ifndef __AVR__ + else if (iType == EPD75_800x480) { + pBBEI->native_width = pBBEI->width = 800; + pBBEI->native_height = pBBEI->height = 480; + pBBEI->busy_idle = HIGH; + pBBEI->can_flip = 0; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->type = EPD75_800x480; // same for the rest + pBBEI->pInitFull = epd75_init_sequence_full; + pBBEI->pInitFast = epd75_init_sequence_fast; + pBBEI->pInitPart = epd75_init_sequence_partial; + return; // nothing else to do yet + } +#endif // __AVR__ + else if (iType == EPD294_128x296) + { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->busy_idle = LOW; + pBBEI->can_flip = 0; + //pBBEI->x_offset = 2; // column byte offset + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->pInitFull = epd294_init_sequence_full; + pBBEI->type = EPD293_128x296; // same for the rest + return; // nothing else to do yet + } + else if (iType == EPD102_80x128) + { + pBBEI->native_width = pBBEI->width = 80; + pBBEI->native_height = pBBEI->height = 130; + pBBEI->busy_idle = HIGH; + pBBEI->can_flip = 0; + pBBEI->type = iType; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->pInitFull = epd102_init_sequence_full; + pBBEI->pInitPart = epd102_init_sequence_part; + return; // nothing else to do yet + } + else if (iType == EPD42B_400x300) + { + pBBEI->native_width = pBBEI->width = 400; + pBBEI->native_height = pBBEI->height = 300; + pBBEI->busy_idle = LOW; + pBBEI->can_flip = 0; + pBBEI->type = iType; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->pInitFull = epd42b_init_sequence_full; + pBBEI->pInitFast = epd42b_init_sequence_fast; + pBBEI->pInitPart = epd42b_init_sequence_part; + return; // nothing else to do yet + } + else if (iType == EPD27B_176x264) + { + pBBEI->native_width = pBBEI->width = 176; + pBBEI->native_height = pBBEI->height = 264; + pBBEI->busy_idle = LOW; + pBBEI->can_flip = 0; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->pInitFull = epd27_init_sequence_full; + pBBEI->pInitPart = epd27_init_sequence_part; + return; // nothing else to do yet + } + else if (iType == EPD27_176x264) + { + pBBEI->native_width = pBBEI->width = 176; + pBBEI->native_height = pBBEI->height = 264; + pBBEI->busy_idle = HIGH; + pBBEI->can_flip = 0; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; // unfortunately needs this + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + return; // nothing else to do yet + } + else if (iType == EPD579_792x272) { + pBBEI->native_width = pBBEI->width = 792; + pBBEI->native_height = pBBEI->height = 272; + pBBEI->pInitFull = epd579_init_sequence_full; + pBBEI->busy_idle = LOW; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->can_flip = 0; + } + else if (iType == EPD266_152x296 || iType == EPD266B_152x296) { + pBBEI->native_width = pBBEI->width = 152; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd266_init_sequence_full; + pBBEI->pInitPart = epd266_init_sequence_part; + pBBEI->busy_idle = LOW; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->can_flip = 0; + pBBEI->type = iType; + // pBBEI->x_offset = (iType == EPD266B_152x296) ? 0:1; + return; // nothing else to do yet + } + else if (iType == EPD42_400x300 || iType == EPD42_4GRAY_400x300) + { // WFT0420CZ15 + pBBEI->native_width = pBBEI->width = 400; + pBBEI->native_height = pBBEI->height = 300; + pBBEI->busy_idle = HIGH; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->can_flip = 0; + if (iType == EPD42_400x300) { + pBBEI->pInitFull = epd42_init_sequence_full; + pBBEI->pInitPart = epd42_init_sequence_part; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->iFlags |= BBEI_FAST_INVERTED; + } else { + pBBEI->pInitFull = epd42_4gray_init_sequence_full; + pBBEI->iFlags |= BBEI_4COLOR | BBEI_4GRAY; + } + return; // nothing else to do yet + } + else if (iType == EPD293_128x296 || iType == EPD213B_122x250 || iType == EPD295_128x296) + { + if (iType == EPD213B_122x250) { + pBBEI->native_width = pBBEI->width = 122; + pBBEI->native_height = pBBEI->height = 250; + pBBEI->pInitFull = epd213b_init_sequence_full; + pBBEI->pInitPart = epd213b_init_sequence_part; + } else if (iType == EPD293_128x296) { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd293_init_sequence_full; + pBBEI->pInitFast = epd293_init_sequence_fast; + pBBEI->pInitPart = epd293_init_sequence_part; + } else { // EPD295_128x296 + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd293_init_sequence_full; + pBBEI->pInitFast = epd293_init_sequence_fast; + pBBEI->pInitPart = epd295_init_sequence_part; + } + pBBEI->busy_idle = LOW; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= (BBEI_HAS_PARTIAL_UPDATE | BBEI_HAS_FAST_UPDATE); + pBBEI->can_flip = 0; + return; // nothing else to do yet + } else if (iType == EPD29_BWYR_128x296) { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->iFlags |= BBEI_4COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iTimeout = 25000; // 4-color need a longer timeout (20 seconds) + pBBEI->pInitFull = epd29b_bwyr_init_sequence_full; + return; + } else if (iType == EPD29_BWYR_168x384) { + pBBEI->native_width = pBBEI->width = 168; + pBBEI->native_height = pBBEI->height = 384; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_4COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iTimeout = 25000; // 4-color need a longer timeout (20 seconds) + pBBEI->pInitFull = epd29_bwyr_init_sequence_full; + return; + } else if (iType == EPD266_BWYR_184x360) { + pBBEI->native_width = pBBEI->width = 184; + pBBEI->native_height = pBBEI->height = 360; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_4COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iTimeout = 20000; // 4-color need a longer timeout (20 seconds) + pBBEI->pInitFull = epd266_bwyr_init_sequence_full; + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + return; + } else if (iType == EPD236_BWYR_168x296) { + pBBEI->native_width = pBBEI->width = 168; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_4COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->iTimeout = 25000; // 4-color need a longer timeout (20 seconds) + pBBEI->pInitFull = epd236_bwyr_init_sequence_full; + return; + } else if (iType == EPD164_BWYR_168x168) { + pBBEI->native_width = pBBEI->width = 168; + pBBEI->native_height = pBBEI->height = 168; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_4COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->iTimeout = 25000; // 4-color need a longer timeout (20 seconds) + pBBEI->pInitFull = epd164_bwyr_init_sequence_full; + return; + } else if (iType == EPD30_BWYR_168x400) { + pBBEI->native_width = pBBEI->width = 168; + pBBEI->native_height = pBBEI->height = 400; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_4COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->iTimeout = 20000; // 4-color need a longer timeout (20 seconds) + pBBEI->pInitFull = epd30_bwyr_init_sequence_full; + return; + } else if (iType == EPD213_122x250 || iType == EPD213_104x212 || iType == EPD29B_128x296) + { + if (iType == EPD213_122x250) { + pBBEI->native_width = pBBEI->width = 122; + pBBEI->native_height = pBBEI->height = 250; + pBBEI->iFlags |= BBEI_HAS_FAST_UPDATE; + pBBEI->pInitFull = epd213_122x250_init_sequence_full; + pBBEI->pInitPart = epd213_122x250_init_sequence_part; + } else if (iType == EPD213_104x212) { + pBBEI->native_width = pBBEI->width = 104; + pBBEI->native_height = pBBEI->height = 212; + pBBEI->pInitFull = epd213_104x212_init_sequence_full; + } else { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd29b_init_sequence_full; + } + pBBEI->can_flip = 0; + pBBEI->busy_idle = LOW; + pBBEI->iFlags |= BBEI_HAS_FAST_UPDATE; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + return; +#if !defined( WIMPY_MCU ) && !defined(__AVR__) +} else if (iType == EPD74R_640x384 || iType == EPD583R_600x448) { + if (iType == EPD583R_600x448) { + pBBEI->native_width = pBBEI->width = 600; + pBBEI->native_height = pBBEI->height = 448; + pBBEI->type = EPD74R_640x384; + } else { + pBBEI->native_width = pBBEI->width = 640; + pBBEI->native_height = pBBEI->height = 384; + } + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_3COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; +// pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->iTimeout = 25000; // 3-color need a longer timeout (25 seconds) + return; +#endif // !WIMPY_MCU + } else if (iType == EPD42R2_400x300 || iType == EPD213R_104x212 || iType == EPD37Y_240x416) { + if (iType == EPD37Y_240x416) { + pBBEI->native_width = pBBEI->width = 240; + pBBEI->native_height = pBBEI->height = 416; + pBBEI->type = EPD42R2_400x300; // use the same commands + }else if (iType == EPD213R_104x212) { + pBBEI->native_width = pBBEI->width = 104; + pBBEI->native_height = pBBEI->height = 212; + pBBEI->type = EPD42R2_400x300; // use the same commands + } else { + pBBEI->native_width = pBBEI->width = 400; + pBBEI->native_height = pBBEI->height = 300; + } + pBBEI->pInitFull = epd42r2_init_sequence_full; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_3COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iTimeout = 25000; // 3-color need a longer timeout (25 seconds) + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + } else if (iType == EPD213R_104x212_d) { + pBBEI->native_width = pBBEI->width = 104; + pBBEI->native_height = pBBEI->height = 212; + pBBEI->type = EPD213R_104x212_d; + pBBEI->can_flip = 0; + pBBEI->busy_idle = HIGH; + pBBEI->iFlags |= BBEI_3COLOR; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iTimeout = 25000; // 3-color need a longer timeout (25 seconds) + } + else if (iType == EPD35R_184x384) { + pBBEI->native_width = pBBEI->width = 184; + pBBEI->native_height = pBBEI->height = 384; + pBBEI->iTimeout = 25000; + pBBEI->iFlags |= BBEI_3COLOR; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->can_flip = 0; + pBBEI->busy_idle = LOW; + pBBEI->pInitFull = epd35r_init_sequence_full; + return; + } + else if (iType == EPD29R_128x296 || iType == EPD154R_152x152 || iType == EPD42R_400x300 || iType == EPD31R_168x296 || iType == EPD154Y_152x152 || iType == EPD29Y_128x296 || iType == EPD213R2_122x250 || iType == EPD266Y_152x296 || iType == EPD42Y_400x300) + { // BLACK/WHITE/RED (or yellow) + if (iType == EPD266Y_152x296) { + pBBEI->native_width = pBBEI->width = 152; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd29r_init_sequence; + pBBEI->x_offset = 1; + } else if (iType == EPD31R_168x296) { + pBBEI->native_width = pBBEI->width = 168; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd29r_init_sequence; + } else if (iType == EPD29Y_128x296) { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->pInitFull = epd29r_init_sequence; + pBBEI->x_offset = 1; + } else if (iType == EPD29R_128x296) { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->iFlags |= BBEI_HAS_FAST_UPDATE; + pBBEI->pInitFull = epd29r_init_sequence; + } else if (iType == EPD154Y_152x152) { + pBBEI->native_width = pBBEI->width = 152; + pBBEI->native_height = pBBEI->height = 152; + pBBEI->pInitFull = epd29r_init_sequence_152; + pBBEI->x_offset = 1; + } else if (iType == EPD154R_152x152){ + pBBEI->native_width = pBBEI->width = 152; + pBBEI->native_height = pBBEI->height = 152; + pBBEI->pInitFull = epd29r_init_sequence_152; + } else if (iType == EPD213R2_122x250) { + pBBEI->native_width = pBBEI->width = 122; + pBBEI->native_height = pBBEI->height = 250; + pBBEI->pInitFull = epd29r_init_sequence; + pBBEI->x_offset = 1; + } else { // EPD42R_400x300 and EPD42Y_400x300 + pBBEI->native_width = pBBEI->width = 400; + pBBEI->native_height = pBBEI->height = 300; + pBBEI->pInitFull = epd42r_init_sequence; + } + pBBEI->type = EPD29R_128x296; // use the rest of the same code + pBBEI->iTimeout = 25000; // 3-color need a longer timeout (25 seconds) + pBBEI->iFlags |= BBEI_3COLOR; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->can_flip = 0; + pBBEI->busy_idle = LOW; +// EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + return; + } + else if (iType == EPD154_152x152) // GDEW0154M10 + { + pBBEI->native_width = pBBEI->width = 152; + pBBEI->native_height = pBBEI->height = 152; + pBBEI->can_flip = 0; // flip display commands don't exist + pBBEI->busy_idle = HIGH; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; +// pBBEI->iFlags |= BBEI_CS_EVERY_BYTE; + pBBEI->pInitFull = epd154a_init_sequence_full; + pBBEI->pInitPart = epd154a_init_sequence_part; + return; + } + else if (iType == EPD154_200x200 || iType == EPD154B_200x200) + { + pBBEI->native_width = pBBEI->width = 200; + pBBEI->native_height = pBBEI->height = 200; + pBBEI->can_flip = 0; // flip display commands don't exist + pBBEI->busy_idle = LOW; + pBBEI->chip_type = BBEI_CHIP_SSD16xx; + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->pInitFull = epd154_init_sequence_full; + pBBEI->pInitPart = (iType == EPD154B_200x200) ? epd154b_init_sequence_part : epd154_init_sequence_part; + return; + } + else if (iType == EPD29_128x296 || iType == EPD213B_104x212 || iType == EPD37_240x416) + { + pBBEI->iFlags |= BBEI_HAS_PARTIAL_UPDATE; + pBBEI->chip_type = BBEI_CHIP_UC8151; + pBBEI->can_flip = 0; // flip display commands don't exist + if (iType == EPD37_240x416) { + pBBEI->native_width = pBBEI->width = 240; + pBBEI->native_height = pBBEI->height = 416; + pBBEI->busy_idle = HIGH; + pBBEI->pInitFull = epd37_init_sequence_full; + pBBEI->pInitPart = epd37_init_sequence_part; +// EPDSendCMDSequence(pBBEI, epd37_init_sequence_full); + } else if (iType == EPD29_128x296) { + pBBEI->native_width = pBBEI->width = 128; + pBBEI->native_height = pBBEI->height = 296; + pBBEI->busy_idle = HIGH; + pBBEI->pInitFull = epd29_init_sequence_full; + // EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + } else { + pBBEI->native_width = pBBEI->width = 104; + pBBEI->native_height = pBBEI->height = 212; + pBBEI->busy_idle = LOW; + pBBEI->type = EPD29_128x296; // tell it to use this config +// EPD29_Init(pBBEI); + } + return; + } else if (iType == EPD47_540x960) { + pBBEI->native_width = pBBEI->width = 540; + pBBEI->native_height = pBBEI->height = 960; + pBBEI->can_flip = 0; // flip display commands don't exist + pBBEI->busy_idle = HIGH; + return; + } + else if (iType == LCD_32x64) + { + pBBEI->native_width = pBBEI->width = 32; + pBBEI->native_height = pBBEI->height = 64; + } + else if (iType == LCD_UC1609) + { + pBBEI->native_width = pBBEI->width = 192; + pBBEI->native_height = pBBEI->height = 64; + } + else if (iType == LCD_HX1230) + { + pBBEI->native_width = pBBEI->width = 96; + pBBEI->native_height = pBBEI->height = 68; + pBBEI->iDCPin = 0xff; // flag this as being 3-wire SPI + } + else if (iType == LCD_NOKIA5110) + { + pBBEI->native_width = pBBEI->width = 84; + pBBEI->native_height = pBBEI->height = 48; + } + else if (iType == OLED_96x16) + { + pBBEI->native_width = pBBEI->width = 96; + pBBEI->native_height = pBBEI->height = 16; + } + else if (iType == OLED_64x128) + { + // NOTE: 64x128 SH1107 doesn't seem to properly support + // segment remap, so the init commands are A0/C0 and + // it can't be flipped 180 + pBBEI->can_flip = 0; + pBBEI->native_width = pBBEI->width = 64; + pBBEI->native_height = pBBEI->height = 128; + } + else if (iType == OLED_128x32) + pBBEI->native_height = pBBEI->height = 32; + else if (iType == OLED_128x128) + pBBEI->native_height = pBBEI->height = 128; + else if (iType == OLED_64x48) + { + pBBEI->native_width = pBBEI->width = 64; + pBBEI->native_height = pBBEI->height = 48; + } + else if (iType == OLED_64x32) + { + pBBEI->native_width = pBBEI->width = 64; + pBBEI->native_height = pBBEI->height = 32; + } + else if (iType == OLED_72x40) + { + pBBEI->native_width = pBBEI->width = 72; + pBBEI->native_height = pBBEI->height = 40; + } + if (iType == OLED_80x128) + { + s = (uint8_t *)oled80_initbuf; + iLen = sizeof(oled80_initbuf); + } + else if (iType == OLED_128x32 || iType == OLED_96x16) + { + s = (uint8_t *)oled32_initbuf; + iLen = sizeof(oled32_initbuf); + } + else if (iType == OLED_64x128) + { + s = (uint8_t *)oled64x128_initbuf; + iLen = sizeof(oled64x128_initbuf); + } + else if (iType == OLED_128x128) + { + s = (uint8_t *)oled128_initbuf; + iLen = sizeof(oled128_initbuf); + } + else if (iType == OLED_132x64) { // SH1106 + s = (uint8_t *)oled132_initbuf; + iLen = sizeof(oled132_initbuf); + } + else if (iType < LCD_UC1701) // 128x64 and 64x32 oleds + { + s = (uint8_t *)oled64_initbuf; + iLen = sizeof(oled64_initbuf); + } + // OLED + if (iType < LCD_UC1701) + { + memcpy_P(u8Cache, s, iLen); // do it from RAM + RawWrite(pBBEI, u8Cache, iLen); + _delay(100); // on SPI display this delay is needed or the display + // never sees the "display on" command at the end of the sequence + obdWriteCommand(pBBEI, 0xaf); // turn on display + if (bInvert) + { + u8Cache[0] = 0; // command + u8Cache[1] = 0xa7; // invert command + RawWrite(pBBEI, u8Cache, 2); + } + if (bFlip) // rotate display 180 + { + u8Cache[0] = 0; // command + u8Cache[1] = 0xa0; + RawWrite(pBBEI, u8Cache, 2); + u8Cache[0] = 0; + u8Cache[1] = 0xc0; + RawWrite(pBBEI, u8Cache, 2); + } + } // OLED +#if !defined( WIMPY_MCU ) && !defined(__AVR__) + if (iType == LCD_ST7302) + { + // uint8_t *s = (uint8_t *)st7302_hpm_init; + const uint8_t *s = (uint8_t *)st7302_wenting; //st7302_lpm_init; + iLen = 1; + + while (iLen) { + iLen = pgm_read_byte(s++); // parameter byte count + if (iLen) { + if (pgm_read_byte(s) == 0xff) { // delay + _delay(pgm_read_byte(&s[1])); + s += 2; + } else { + obdWriteCommand(pBBEI, pgm_read_byte(s)); + if (iLen > 1) { + memcpy_P(u8Cache, s, iLen); + RawWrite(pBBEI, u8Cache, iLen); + } + s += iLen; + } + } + } // while commands to transmit + if (pBBEI->invert) + obdWriteCommand(pBBEI, 0x21); // inversion on + // Clear RAM +// obdWriteCommand(pBBEI, 0xb9); +// uc[0] = 0x40; +// uc[1] = 0xe3; +// RawWrite(pBBEI, uc, 2); +// _delay(1000); +// obdWriteCommand(pBBEI, 0xb9); +// uc[0] = 0x40; +// uc[1] = 0x23; +// RawWrite(pBBEI, uc, 2); + return; + } /* ST7302 */ +#endif // !WIMPY_MCU + if (iType == LCD_UC1701 || iType == LCD_HX1230 || iType == LCD_32x64) + { + uint8_t cCOM = 0xc0; + LCDPowerUp(pBBEI); + if (iType == LCD_HX1230) + { + obdSetContrast(pBBEI, 0); // contrast of 0 looks good + cCOM = 0xc8; + } + if (bFlip) // flip horizontal + vertical + { + obdWriteCommand(pBBEI, 0xa1); // set SEG direction (A1 to flip horizontal) + obdWriteCommand(pBBEI, cCOM); // set COM direction (C0 to flip vert) + } + if (bInvert) + { + obdWriteCommand(pBBEI, 0xa7); // set inverted pixel mode + } + } + if (iType == LCD_UC1609) + { + LCDPowerUp(pBBEI); + return; +// obdWriteCommand(pBBEI, 0xe2); // system reset +// _delay(100); +// obdWriteCommand(pBBEI, 0xa0); // set frame rate to 76fps +// obdWriteCommand(pBBEI, 0xeb); // set BR +// obdWriteCommand(pBBEI, 0x2f); // set Power Control +// obdWriteCommand(pBBEI, 0xc4); // set LCD mapping control +// obdWriteCommand(pBBEI, 0x81); // set PM +// obdWriteCommand(pBBEI, 0x90); // set contrast to 144 +// _delay(100); +// obdWriteCommand(pBBEI, 0xaf); // display enable +// if (bFlip) // flip horizontal + vertical +// { +// obdWriteCommand(pBBEI, 0xa1); // set SEG direction (A1 to flip horizontal) +// obdWriteCommand(pBBEI, 0xc2); // set COM direction (C0 to flip vert) +// } +// if (bInvert) +// { +// obdWriteCommand(pBBEI, 0xa7); // set inverted pixel mode +// } + } // UC1609 +#endif // MEMORY_ONLY +} /* obdSPIInit() */ +void obdSetRotation(OBDISP *pBBEI, int iRotation) +{ + pBBEI->iScreenOffset = 0; + + switch (iRotation) { + default: return; + case 0: + obdSetFlip(pBBEI, 0); + pBBEI->iOrientation = 0; + pBBEI->width = pBBEI->native_width; + pBBEI->height = pBBEI->native_height; + break; + case 1: + case 90: + obdSetFlip(pBBEI, 0); + pBBEI->iOrientation = 90; + pBBEI->width = pBBEI->native_height; + pBBEI->height = pBBEI->native_width; + break; + case 2: + case 180: + pBBEI->iOrientation = 180; + obdSetFlip(pBBEI, 1); + pBBEI->width = pBBEI->native_width; + pBBEI->height = pBBEI->native_height; + break; + case 3: + case 270: + pBBEI->iOrientation = 270; + obdSetFlip(pBBEI, 1); + pBBEI->width = pBBEI->native_height; + pBBEI->height = pBBEI->native_width; + break; + } +} /* obdSetRotation() */ +// +// Set the memory configuration to display the pixels at 0 or 180 degrees (flipped) +// Pass true (1) to flip 180, false (0) to set to 0 +// +void obdSetFlip(OBDISP *pBBEI, int iOnOff) +{ + if (pBBEI == NULL) return; + if (!pBBEI->can_flip) return; // don't try it on some displays + + pBBEI->flip = iOnOff; + if (pBBEI->type == LCD_UC1701 || pBBEI->type == LCD_UC1609 || pBBEI->type == LCD_32x64) + { + if (iOnOff) // rotate display 180 + { + obdWriteCommand(pBBEI, 0xa1); // SEG direction (A1 to flip horizontal) + obdWriteCommand(pBBEI, 0xc0); // COM direction (C0 to flip vert) + } else { // non-rotated + obdWriteCommand(pBBEI, 0xa0); + obdWriteCommand(pBBEI, 0xc8); + } + } else { // OLEDs + if (iOnOff) + { + obdWriteCommand(pBBEI, 0xa0); + obdWriteCommand(pBBEI, 0xc0); + } else { + obdWriteCommand(pBBEI, 0xa1); + obdWriteCommand(pBBEI, 0xc8); + } + } +} /* obdSetFlip() */ +// +// Initializes the OLED controller into "page mode" +// +int obdI2CInit(OBDISP *pBBEI, int iType, int iAddr, int bFlip, int bInvert, int bWire, int sda, int scl, int reset, int32_t iSpeed) +{ +int rc = OLED_NOT_FOUND; +#ifndef MEMORY_ONLY +unsigned char uc[32]; +uint8_t u8Len, *s; + + if (pBBEI == NULL) + return -1; + pBBEI->can_flip = 1; // assume can flip 180 + if (iType < 0 || iType >= LCD_COUNT) + return -1; // invalid display type + if (sda == scl) { // invalid situation, set to -1 + sda = scl = -1; + } + pBBEI->type = iType; + pBBEI->flip = bFlip; + pBBEI->invert = bInvert; + pBBEI->wrap = 0; // default - disable text wrap + pBBEI->bbi2c.iSDA = sda; + pBBEI->bbi2c.iSCL = scl; + pBBEI->bbi2c.bWire = bWire; + pBBEI->com_mode = COM_I2C; // communication mode +#ifdef _LINUX_ + pBBEI->bbi2c.iBus = sda; + pBBEI->ucScreen = NULL; +#endif + I2CInit(&pBBEI->bbi2c, iSpeed); // on Linux, SDA = bus number, SCL = device address +#ifdef _LINUX_ + pBBEI->oled_addr = (uint8_t)scl; +#else + // Reset it + if (reset != 0xff && reset != -1) + { + pinMode(reset, OUTPUT); + digitalWrite(reset, HIGH); + _delay(50); + digitalWrite(reset, LOW); + _delay(50); + digitalWrite(reset, HIGH); + _delay(10); + } + // find the device address if requested + if (iAddr == -1 || iAddr == 0 || iAddr == 0xff) // find it + { + I2CTest(&pBBEI->bbi2c, 0x3c); + if (I2CTest(&pBBEI->bbi2c, 0x3c)) + pBBEI->oled_addr = 0x3c; + else if (I2CTest(&pBBEI->bbi2c, 0x3d)) + pBBEI->oled_addr = 0x3d; + else + return rc; // no display found! + } + else + { + pBBEI->oled_addr = iAddr; + I2CTest(&pBBEI->bbi2c, iAddr); + if (!I2CTest(&pBBEI->bbi2c, iAddr)) + return rc; // no display found + } + +#endif + // Detect the display controller (SSD1306, SH1107 or SH1106) + uint8_t u = 0; + I2CReadRegister(&pBBEI->bbi2c, pBBEI->oled_addr, 0x00, &u, 1); // read the status register + u &= 0x0f; // mask off power on/off bit + if ((u == 0x7 || u == 0xf) && pBBEI->type == OLED_128x128) // SH1107 + { // A single SSD1306 display returned 7, so only act on it if the + // user specified that they're working with a 128x128 display + rc = OLED_SH1107_3C; + bFlip = !bFlip; // SH1107 seems to have this reversed from the usual direction + } + else if (u == 0x8) // SH1106 + { + rc = OLED_SH1106_3C; + pBBEI->type = OLED_132x64; // needs to be treated a little differently + } + else if (u == 3 || u == 6 || u == 7) // 7=128x64(rare),6=128x64 display, 3=smaller + { + rc = OLED_SSD1306_3C; + } + if (pBBEI->oled_addr == 0x3d) { + rc++; // return the '3D' version of the type + } + if (iType == OLED_80x128) + { + s = (uint8_t *)oled80_initbuf; + u8Len = sizeof(oled80_initbuf); + } + if (iType == OLED_128x32 || iType == OLED_96x16) + { + s = (uint8_t *)oled32_initbuf; + u8Len = sizeof(oled32_initbuf); + } + else if (iType == OLED_128x128) + { + s = (uint8_t *)oled128_initbuf; + u8Len = sizeof(oled128_initbuf); + } + else if (iType == OLED_72x40) + { + s = (uint8_t *)oled72_initbuf; + u8Len = sizeof(oled72_initbuf); + } + else if (iType == OLED_64x128) + { + s = (uint8_t *)oled64x128_initbuf; + u8Len = sizeof(oled64x128_initbuf); + pBBEI->can_flip = 0; // this display can't flip 180 (don't know why) + } + else if (iType == LCD_128x64_I2C) + { + obdWriteCommand(pBBEI, 0xe2); // Need to be reset after power-on + _delay(10); + s = (uint8_t *)lcd128x64_initbuf; + u8Len = sizeof(lcd128x64_initbuf); + rc = LCD_OK; + memcpy_P(uc, s, u8Len); + } + else // 132x64, 128x64, 64x48 and 64x32 + { + s = (uint8_t *)oled64_initbuf; + u8Len = sizeof(oled64_initbuf); + } + + memcpy_P(uc, s, u8Len); + RawWrite(pBBEI, uc, u8Len); + if (bInvert) + { + uc[0] = 0; // command + uc[1] = 0xa7; // invert command + RawWrite(pBBEI,uc, 2); + } + if (bFlip) // rotate display 180 + { + uc[0] = 0; // command + uc[1] = 0xa0; + RawWrite(pBBEI,uc, 2); + uc[1] = 0xc0; + RawWrite(pBBEI,uc, 2); + } + pBBEI->native_width = pBBEI->width = 128; // assume 128x64 + pBBEI->native_height = pBBEI->height = 64; + if (iType == OLED_96x16) + { + pBBEI->native_width = pBBEI->width = 96; + pBBEI->native_height = pBBEI->height = 16; + } + else if (iType == OLED_80x128) + { + pBBEI->native_width = pBBEI->width = 80; + pBBEI->native_height = pBBEI->height = 128; + } + else if (iType == OLED_64x128) + { + pBBEI->native_width = pBBEI->width = 64; + pBBEI->native_height = pBBEI->height = 128; + } + else if (iType == OLED_128x32) + pBBEI->native_height = pBBEI->height = 32; + else if (iType == OLED_128x128) + pBBEI->native_height = pBBEI->height = 128; + else if (iType == OLED_64x32) + { + pBBEI->native_width = pBBEI->width = 64; + pBBEI->native_height = pBBEI->height = 32; + } + else if (iType == OLED_64x48) + { + pBBEI->native_width = pBBEI->width = 64; + pBBEI->native_height = pBBEI->height = 48; + } + else if (iType == OLED_72x40) + { + pBBEI->native_width = pBBEI->width = 72; + pBBEI->native_height = pBBEI->height = 40; + } +#endif // MEMORY_ONLY + return rc; +} /* obdI2CInit() */ +// +// Sends a command to turn on or off the OLED display +// +void oledPower(OBDISP *pBBEI, uint8_t bOn) +{ + if (bOn) + obdWriteCommand(pBBEI, 0xaf); // turn on OLED + else + obdWriteCommand(pBBEI, 0xae); // turn off OLED +} /* oledPower() */ + +// Sets the D/C pin to data or command mode +void obdSetDCMode(OBDISP *pBBEI, int iMode) +{ + if (pBBEI->iDCPin == 0xff) // 9-bit SPI + pBBEI->mode = (uint8_t)iMode; + else // set the GPIO line + digitalWrite(pBBEI->iDCPin, (iMode == MODE_DATA)); +} /* obdSetDCMode() */ + +static void obdWriteCommand2(OBDISP *pBBEI, unsigned char c, unsigned char d) +{ +unsigned char buf[4]; + + if (pBBEI->com_mode == COM_I2C) {// I2C device + buf[0] = 0x00; + buf[1] = c; + buf[2] = d; + RawWrite(pBBEI, buf, 3); + } else { // must be SPI + obdWriteCommand(pBBEI, c); + obdWriteCommand(pBBEI, d); + } +} /* obdWriteCommand2() */ + +// +// Sets the brightness (0=off, 255=brightest) +// +void obdSetContrast(OBDISP *pBBEI, unsigned char ucContrast) +{ + if (pBBEI->type == LCD_HX1230) + { // valid values are 0-31, so scale it + ucContrast >>= 3; + obdWriteCommand(pBBEI, 0x80 + ucContrast); + } + else if (pBBEI->type == LCD_NOKIA5110) + { + // we allow values of 0xb0-0xbf, so shrink the range + ucContrast >>= 4; + obdWriteCommand(pBBEI, 0x21); // set advanced command mode + obdWriteCommand(pBBEI, 0xb0 | ucContrast); + obdWriteCommand(pBBEI, 0x20); // set simple command mode + } + else // OLEDs + UC1701 + obdWriteCommand2(pBBEI, 0x81, ucContrast); +} /* obdSetContrast() */ +static void EPDWaitBusy(OBDISP *pBBEI, int bQuick) +{ +#ifndef MEMORY_ONLY +int iTimeout = 0; +int iEndTime = (int)pBBEI->iTimeout; + long l = micros(); + + if (pBBEI->iLEDPin == 0xff || pBBEI->iLEDPin == -1) { + if (bQuick) + iEndTime /= 5; // 1/5th for short waits such as after Power-on + _delay(iEndTime); // no busy pin, wait fixed amount + return; + } + _delay(1); + while (1) { + if (digitalRead(pBBEI->iLEDPin) == pBBEI->busy_idle) break; + } +// while (1) { +// if (digitalRead(pBBEI->iLEDPin) == pBBEI->busy_idle) +// break; +// iTimeout += (iEndTime/4); +// if (iTimeout > iEndTime) +// break; // DEBUG - timeout +// _delay(iEndTime/4); +// } + l = micros() - l; + Serial.print((int)l, DEC); + Serial.println(" us spent in EPDWaitBusy"); + + if ((l/1000) > iEndTime) { +#if defined( ARDUINO ) && !defined(WIMPY_MCU) + Serial.println("EPDWaitBusy() timed out"); +#endif +#if defined(_LINUX_) + printf("EPDWaitBusy() timed out\n"); +#endif + } +#endif // MEMORY_ONLY +} /* EPDWaitBusy() */ + +void EPD27_SetLut(OBDISP *pBBEI, int bFast) +{ + if (bFast) { + obdWriteCommand(pBBEI, UC8151_LUT_VCOM); //vcom + RawWriteData(pBBEI, (uint8_t *)lut_fast_vcom_dc, 44); + + obdWriteCommand(pBBEI, UC8151_LUT_WW); + RawWriteData(pBBEI, (uint8_t *)lut_fast_ww, 42); + + obdWriteCommand(pBBEI, UC8151_LUT_BW); + RawWriteData(pBBEI, (uint8_t *)lut_fast_bw, 42); + + obdWriteCommand(pBBEI, UC8151_LUT_WB); + RawWriteData(pBBEI, (uint8_t *)lut_fast_bb, 42); + + obdWriteCommand(pBBEI, UC8151_LUT_BB); + RawWriteData(pBBEI, (uint8_t *)lut_fast_wb, 42); + } else { + obdWriteCommand(pBBEI, UC8151_LUT_VCOM); //vcom + RawWriteData(pBBEI, (uint8_t *)lut_vcom_dc, 44); + + obdWriteCommand(pBBEI, UC8151_LUT_WW); + RawWriteData(pBBEI, (uint8_t *)lut_ww, 42); + + obdWriteCommand(pBBEI, UC8151_LUT_BW); + RawWriteData(pBBEI, (uint8_t *)lut_bw, 42); + + obdWriteCommand(pBBEI, UC8151_LUT_WB); + RawWriteData(pBBEI, (uint8_t *)lut_bb, 42); + + obdWriteCommand(pBBEI, UC8151_LUT_BB); + RawWriteData(pBBEI, (uint8_t *)lut_wb, 42); + } +} + +// +// Wake up the EPD controller +// +static void EPDWakeUp(OBDISP *pBBEI, int bResetOnly) +{ + if (pBBEI->iRSTPin != 0xff) { + for (int i=0; i<1; i++) { + digitalWrite(pBBEI->iRSTPin, LOW); + _delay(10); + digitalWrite(pBBEI->iRSTPin, HIGH); + _delay(10); + } + } + if (bResetOnly) return; + + if (pBBEI->type == EPD164_BWYR_168x168 || pBBEI->type == EPD236_BWYR_168x296) return; // these don't like this cmd at this time + if (pBBEI->chip_type == BBEI_CHIP_UC8151) { + obdWriteCommand(pBBEI, UC8151_PON); + } else { + obdWriteCommand(pBBEI, SSD1608_SW_RESET); // soft reset + } + EPDWaitBusy(pBBEI, 1); +} /* EPDWakeUp() */ + +static void EPDSleep(OBDISP *pBBEI, int bDeep) +{ + + if (pBBEI->chip_type == BBEI_CHIP_SSD16xx) { + if (bDeep) + EPD_CMD2(pBBEI, 0x10, 0x02); // deep sleep mode 2 (loses RAM, needs reset) + else // no reset pin, put it into light sleep + EPD_CMD2(pBBEI, 0x10, 0x00); + return; + } else { // UC81xx + EPD_CMD2(pBBEI, UC8151_CDI, 0x17); // border floating + obdWriteCommand(pBBEI, UC8151_POFF); // power off + EPDWaitBusy(pBBEI, 1); + if (bDeep) { + EPD_CMD2(pBBEI, UC8151_DSLP, 0xa5); // deep sleep + } + } +} /* EPDSleep() */ +// +// Set the boundaries of the partial update area +// start/end x/y +// +void EPDSetPartialArea(OBDISP *pBBEI, int sx, int sy, int ex, int ey) +{ +uint8_t ucTemp[12]; + + ucTemp[0] = 0x40; // data operation + sx &= 0xFFF8; // byte boundary + ex = (ex - 1) | 0x7; // byte boundary - 1 + obdWriteCommand(pBBEI, 0x90); // partial window + ucTemp[1] = (uint8_t)(sx / 256); + ucTemp[2] = (uint8_t)(sx & 256); + ucTemp[3] = (uint8_t)(ex / 256); + ucTemp[4] = (uint8_t)(ex & 256); + ucTemp[5] = (uint8_t)(sy / 256); + ucTemp[6] = (uint8_t)(sy & 256); + ucTemp[7] = (uint8_t)(ey / 256); + ucTemp[8] = (uint8_t)(ey & 256); + ucTemp[9] = 0x01; // gates scan inside and outside the partial area (don't see any difference) + RawWrite(pBBEI, ucTemp, 10); + _delay(2); + //IO.writeDataTransaction(0x00); // don't see any difference + //return (7 + xe - x) / 8; // number of bytes to transfer +} /* EPDSetPartialArea() */ + +void EPD154_Finish(OBDISP *pBBEI, int bPartial) +{ + if (bPartial) { + EPD_CMD2(pBBEI, SSD1608_DISP_CTRL2, 0xcf); + } else { + EPD_CMD2(pBBEI, SSD1608_DISP_CTRL2, 0xc7); + } + obdWriteCommand(pBBEI, SSD1608_MASTER_ACTIVATE); + EPDWaitBusy(pBBEI, 0); +} /* EPD154_Finish() */ + +void EPD213_Begin(OBDISP *pBBEI, int x, int y, int w, int h, int bPartial) +{ +uint8_t ucLine[8]; + + // set luts + obdWriteCommand(pBBEI, SSD1608_WRITE_LUT); +// if (pBBEI->type == EPD213_104x212) { +// RawWriteData(pBBEI, (uint8_t *)epd213_lut, sizeof(epd213_lut)); +// } else { // 122x250 + if (bPartial) { +// obdWriteCommand(pBBEI, SSD1608_WRITE_LUT); + memcpy_P(u8Cache, epd213_lut2_partial, sizeof(epd213_lut2_partial)); + RawWriteData(pBBEI, u8Cache, sizeof(epd213_lut2_partial)); + } else { + memcpy_P(u8Cache, epd213_lut2_full, sizeof(epd213_lut2_full)); + RawWriteData(pBBEI, u8Cache, sizeof(epd213_lut2_full)); + } + + obdWriteCommand(pBBEI, 0x44); // set RAM X start/end + if (pBBEI->iOrientation == 0 || pBBEI->iOrientation == 180) { + ucLine[0] = x/8; + ucLine[1] = (x+w-1)/8; + } else { // 90/270 + ucLine[0] = y/8; + ucLine[1] = (y+h-1)/8; + } + RawWriteData(pBBEI, ucLine, 2); + obdWriteCommand(pBBEI, 0x45); // set RAM Y start/end + if (pBBEI->iOrientation == 0 || pBBEI->iOrientation == 180) { + ucLine[0] = (uint8_t)y; + ucLine[1] = (uint8_t)(y>>8); + ucLine[2] = (uint8_t)(y+h-1); + ucLine[3] = (uint8_t)((y+h-1)>>8); + } else { // 90/270 + ucLine[0] = (uint8_t)x; + ucLine[1] = (uint8_t)(x>>8); + ucLine[2] = (uint8_t)(x+w-1); + ucLine[3] = (uint8_t)((x+w-1)>>8); + } + RawWriteData(pBBEI, ucLine, 4); + EPD_CMD2(pBBEI, 0x4e, 0x00); // set RAM X pointer start + ucLine[0] = 0x00; + ucLine[1] = 0x00; + obdWriteCommand(pBBEI, 0x4f); // set RAM Y pointer start + RawWriteData(pBBEI, ucLine, 2); + if (bPartial) { + EPD_CMD2(pBBEI, SSD1608_WRITE_VCOM, 0x26); // VCOM + EPDWaitBusy(pBBEI, 1); + obdWriteCommand(pBBEI, 0x37); // ? + memset(ucLine, 0, 7); + ucLine[4] = 0x40; + RawWriteData(pBBEI, ucLine, 7); + + EPD_CMD2(pBBEI, 0x22, 0xc0); + obdWriteCommand(pBBEI, 0x20); + EPDWaitBusy(pBBEI, 1); + EPD_CMD2(pBBEI, 0x3c, 0x01); // border waveform + } else { + EPD_CMD2(pBBEI, SSD1608_WRITE_VCOM, 0x55); // VCOM + EPDWaitBusy(pBBEI, 1); + } +} /* EPD213_Begin() */ + +// +// Special case for some e-ink displays +// +static int EPDDumpPartial(OBDISP *pBBEI, uint8_t *pBuffer, int x, int y, int w, int h) +{ + long lTime; + + if (pBuffer == NULL && pBBEI->ucScreen == NULL) + return BBEI_ERROR_NO_MEMORY; + + EPDWakeUp(pBBEI, 1); // tickle the reset line + + if (pBBEI->pInitPart) { // command tables ready to go + lTime = millis(); + EPDSendCMDSequence(pBBEI, pBBEI->pInitPart); + if (pBBEI->chip_type == BBEI_CHIP_UC8151) { + int iSize = pBBEI->width * ((pBBEI->height+7)>>3); + EPDWriteImage(pBBEI, 0x10, &pBBEI->ucScreen[iSize], 0,0,pBBEI->width, pBBEI->height, pBBEI->iFlags & BBEI_FAST_INVERTED); // write old data inverted + EPDWriteImage(pBBEI, 0x13, NULL, 0, 0, pBBEI->width, pBBEI->height, pBBEI->iFlags & BBEI_FAST_INVERTED); + memcpy(&pBBEI->ucScreen[iSize], pBBEI->ucScreen, iSize); // new becomes old + lTime = millis() - lTime; + pBBEI->iDataTime = (int)lTime; + lTime = millis(); + obdWriteCommand(pBBEI, UC8151_DRF); + _delay(1); // wait before checking busy line (necessary!) + EPDWaitBusy(pBBEI, 1); + obdWriteCommand(pBBEI, UC8151_POFF); + lTime = millis() - lTime; + pBBEI->iOpTime = (int)lTime; + return 0; + } else { // SSD16xx + EPDWriteImage(pBBEI, 0x24, NULL, 0, 0, pBBEI->width, pBBEI->height, pBBEI->iFlags & BBEI_FAST_INVERTED); + EPD_CMD2(pBBEI, 0x22, 0xff); + lTime = millis() - lTime; + pBBEI->iDataTime = (int)lTime; + lTime = millis(); + obdWriteCommand(pBBEI, 0x20); // refresh + EPDWaitBusy(pBBEI, 0); + lTime = millis() - lTime; + pBBEI->iOpTime = (int)lTime; + return 0; + //EPD_CMD2(pBBEI, 0x10, 0x1); // deep sleep is needed (very strange behavior if it is not issued here) + } // SSD16xx + } // has a command list for fast updates + if (pBBEI->type == EPD29R_128x296 || pBBEI->type == EPD154R_152x152 || pBBEI->type == EPD42R_400x300) { + if (pBBEI->iFlags & BBEI_FULLUPDATE) { + // Kludge alert - fool it into going into B&W mode to allow partial updates. Need to combine the black+red pixels and send them to the ALTRAM first + uint8_t *s = pBBEI->ucScreen; + int iOffset = ((pBBEI->native_width+7)/8) * pBBEI->native_height; // full buffer size + for (int i=0; iwidth, pBBEI->height, false); +#ifndef WIMPY_MCU + EPDWriteImage(pBBEI, SSD1608_WRITE_RAM, NULL, 0, 0, pBBEI->width, pBBEI->height, 0); + EPDWriteImage(pBBEI, SSD1608_WRITE_ALTRAM, NULL, 0, 0, pBBEI->width, pBBEI->height, 0); // send image to current and previous buffers +#endif + return BBEI_SUCCESS; + } + EPD213_Begin(pBBEI, x, y, w, h, true); +#ifndef WIMPY_MCU + EPDWriteImage(pBBEI, SSD1608_WRITE_RAM, NULL, x, y, w, h, 0); +#endif + EPD154_Finish(pBBEI, true); + } + if (pBBEI->type == EPD213_104x212 || pBBEI->type == EPD213_122x250) { + EPD213_Begin(pBBEI, x, y, w, h, true); +#ifndef WIMPY_MCU + EPDWriteImage(pBBEI, SSD1608_WRITE_RAM, NULL, x, y, w, h, 0); +#endif + EPD154_Finish(pBBEI, true); + } else if (pBBEI->type == EPD154_152x152) { + EPDSendCMDSequence(pBBEI, pBBEI->pInitFast); +#ifndef WIMPY_MCU + if (pBBEI->ucScreen) { + EPDWriteImage(pBBEI, UC8151_DTM2, NULL, 0, 0, pBBEI->width, pBBEI->height, 1); + } +#endif + obdWriteCommand(pBBEI, UC8151_DRF); + EPDWaitBusy(pBBEI, 1); + } + EPDSleep(pBBEI, 0); + return BBEI_SUCCESS; +} /* EPDDumpFast() */ + +void EPD_CMD2(OBDISP *pBBEI, uint8_t cmd, uint8_t param) +{ + obdWriteCommand(pBBEI, cmd); + RawWriteData(pBBEI, ¶m, 1); +} /* EPD_CMD2() */ + +void EPD8951_REG(OBDISP *pBBEI, uint16_t addr, uint16_t data) +{ +uint8_t ucTemp[4]; + + EPDWaitBusy(pBBEI, 1); + ucTemp[0] = 0x60; // cmd intro 6000h + ucTemp[1] = 0x00; + ucTemp[2] = 0x00; // reg write 0011h + ucTemp[3] = 0x11; + RawWriteData(pBBEI, ucTemp, 4); + EPDWaitBusy(pBBEI, 1); + ucTemp[0] = ucTemp[1] = 0; // 0000 data intro + ucTemp[2] = (uint8_t)(addr >> 8); + ucTemp[3] = (uint8_t)(addr & 0xff); + RawWriteData(pBBEI, ucTemp, 4); + ucTemp[2] = (uint8_t)(data >> 8); + ucTemp[3] = (uint8_t)(data & 0xff); + RawWriteData(pBBEI, ucTemp, 4); + +} /* EPD8951_REG() */ + +void EPD8951_ARGS(OBDISP *pBBEI, uint16_t u16CMD, uint16_t *pArgs, int iCount) +{ + uint8_t ucTemp[4]; + EPDWaitBusy(pBBEI, 1); + ucTemp[0] = 0x60; + ucTemp[1] = 0x00; // 6000 cmd + ucTemp[2] = (uint8_t)(u16CMD >> 8); + ucTemp[3] = (uint8_t)(u16CMD & 0xff); + RawWriteData(pBBEI, ucTemp, 4); + + for (int i=0; i> 8); + ucTemp[3] = (uint8_t)(pArgs[i] & 0xff); + RawWriteData(pBBEI, ucTemp, 4); + } +} /* EPD8951_ARGS() */ + +void EPD8951_CMD(OBDISP *pBBEI, int iCMD) +{ +uint8_t ucTemp[4]; + + EPDWaitBusy(pBBEI, 1); + ucTemp[0] = 0x60; + ucTemp[1] = 0x00; // cmd prefix = 6000h + ucTemp[2] = (uint8_t)(iCMD >> 8); + ucTemp[3] = (uint8_t)(iCMD & 0xff); + RawWriteData(pBBEI, ucTemp, 4); +} /* EPD8951_CMD() */ + +void EPD8951_WORD(OBDISP *pBBEI, uint16_t u16Word) +{ +uint8_t ucTemp[4]; + + EPDWaitBusy(pBBEI, 1); + ucTemp[0] = 0x00; + ucTemp[1] = 0x00; // cmd prefix = 6000h + ucTemp[2] = (uint8_t)(u16Word >> 8); + ucTemp[3] = (uint8_t)(u16Word & 0xff); + RawWriteData(pBBEI, ucTemp, 4); +} /* EPD8951_WORD() */ + +void EPDFill(OBDISP *pBBEI, uint8_t ucCMD, uint8_t ucPattern) +{ +int iSize; + + if (ucCMD) { + obdWriteCommand(pBBEI, ucCMD); // start write + } + if (pBBEI->type == EPD579_792x272) { + iSize = 400*(272>>3); +#ifndef __AVR__ + } else if (pBBEI->type == EPD74R_640x384) { + iSize = (pBBEI->native_width * pBBEI->native_height)/2; // 4bpp +#endif // __AVR__ + } else { + iSize = ((pBBEI->native_width+7)>>3) * pBBEI->native_height; + } + for (int i=0; iucScreen; + iPitch = pBBEI->width; + } else { + // assume the passed buffer is only the size and width of the requested write + pBuffer = pBits; + iPitch = w; + } + if (bInvert) {// use second bit plane + //pBuffer += pBBEI->width * ((pBBEI->height+7)/8); + ucInvert = 0xff; // red logic is inverted + } + if (ucCMD) { + obdWriteCommand(pBBEI, ucCMD); // start write + } + // Convert the bit direction and write the data to the EPD + u8Cache[0] = u8Cache[1] = 0; // prefix for IT8951 + if (pBBEI->iOrientation == 180) { + for (ty=y+h-1; ty>=y; ty--) { + ucSrcMask = 1 << (ty & 7); + ucDstMask = 0x80; + uc = 0xff; + d = u8Cache+2; + s = &pBuffer[(ty>>3) * iPitch]; + for (tx=x+w-1; tx>=x; tx--) { + if (s[tx] & ucSrcMask) + uc &= ~ucDstMask; + ucDstMask >>= 1; + if (ucDstMask == 0) { + *d++ = (uc ^ ucInvert); + uc = 0xff; + ucDstMask = 0x80; + } + } // for tx + *d++ = uc; // store final partial byte + if (pBBEI->type == EPD47_540x960) + RawWriteData(pBBEI, u8Cache, 2 + ((w+7)/8)); + else + RawWriteData(pBBEI, u8Cache+2, (w+7)/8); + } // for ty + } else if (pBBEI->iOrientation == 0) { + for (ty=y; ty> 3) * iPitch]; + for (tx=x; tx>= 1; + if (ucDstMask == 0) { // completed byte + *d++ = (uc ^ ucInvert); + uc = 0xff; + ucDstMask = 0x80; + } + } // for tx + *d++ = (uc ^ ucInvert); // store final partial byte + if (pBBEI->type == EPD47_540x960) + RawWriteData(pBBEI, u8Cache, 2+((w+7)/8)); + else + RawWriteData(pBBEI, u8Cache+2, (w+7)/8); + } // for ty + } else if (pBBEI->iOrientation == 90) { + for (tx=x; tx=y; ty--) { + s = &pBuffer[tx + (ty>>3) * iPitch]; + if (s[0] & ucSrcMask) uc &= ~ucDstMask; + ucDstMask >>= 1; + ucSrcMask >>= 1; + if (ucDstMask == 0) { + *d++ = (uc ^ ucInvert); + ucDstMask = 0x80; + uc = 0xff; + } + if (ucSrcMask == 0) + ucSrcMask = 0x80; // bottom up + } // for ty + *d++ = (uc ^ ucInvert); // store final partial byte + if (pBBEI->type == EPD47_540x960) + RawWriteData(pBBEI, u8Cache, 2+((h+7)/8)); + else + RawWriteData(pBBEI, u8Cache+2, (h+7)/8); + } // for tx + } else if (pBBEI->iOrientation == 270) { + for (tx=x+w-1; tx>=x; tx--) { + d = u8Cache+2; + // reassemble every pixel + ucDstMask = 0x80; + uc = 0xff; + ucSrcMask = 1 << (y & 7); + for (ty=y; ty>3) * iPitch]; + if (s[0] & ucSrcMask) uc &= ~ucDstMask; + ucDstMask >>= 1; + ucSrcMask <<= 1; + if (ucDstMask == 0) { + *d++ = (uc ^ ucInvert); + ucDstMask = 0x80; + uc = 0xff; + } + if (ucSrcMask == 0) + ucSrcMask = 1; + } // for ty + *d++ = (uc ^ ucInvert); // store final partial byte + if (pBBEI->type == EPD47_540x960) + RawWriteData(pBBEI, u8Cache, 2+((h+7)/8)); + else + RawWriteData(pBBEI, u8Cache+2, (h+7)/8); + } // for x + } // 270 +} /* EPDWriteImage() */ +// +// Write EPD Image data to a 4-color (black/white/yellow/red) e-paper +// +static void EPDWriteImage2bpp(OBDISP *pBBEI, uint8_t ucCMD, int x, int y, int w, int h) +{ +uint8_t ucLine[324]; // send the data one line at a time +int tx, ty, iRedOff; +uint8_t *s, *d, ucSrcMask, uc; +uint8_t *pBuffer; + + pBuffer = pBBEI->ucScreen; + iRedOff = pBBEI->width * ((pBBEI->height+7)/8); + if (ucCMD) { + obdWriteCommand(pBBEI, ucCMD); // start write + } + // Convert the bit direction and write the data to the EPD + if (pBBEI->iOrientation == 180) { + for (ty=y+h-1; ty>=y; ty--) { + ucSrcMask = 1 << (ty & 7); + d = ucLine; + s = &pBuffer[(ty>>3) * pBBEI->width]; + for (tx=x+w-4; tx>=x; tx-=4) { + uc = 0; + for (int pix=3; pix>=0; pix--) { + uc <<= 2; // shift down 1 pixel + if (!(s[tx+pix] & ucSrcMask)) // inverted plane 0 + uc |= 0x01; + if (s[tx+pix+iRedOff] & ucSrcMask) // plane 1 + uc |= 0x02; + } + *d++ = uc; // store 4 pixels + } // for tx + RawWriteData(pBBEI, ucLine, w/4); + } // for ty + } else if (pBBEI->iOrientation == 0) { + for (ty=y; ty> 3) * pBBEI->width]; + for (tx=x; txiOrientation == 90) { + for (tx=x; tx=y; ty-=4) { + s = &pBuffer[tx + (ty>>3) * pBBEI->width]; + uc = 0; + for (int pix=3; pix>=0; pix--) { + uc <<= 2; // shift down 1 pixel + if (!(s[tx] & ucSrcMask)) // inverted plane 0 + uc |= 0x01; + if (s[tx+iRedOff] & ucSrcMask) // plane 1 + uc |= 0x02; + ucSrcMask >>= 1; + if (ucSrcMask == 0) ucSrcMask = 0x80; + } + *d++ = uc; // store 4 pixels + } // for ty + RawWriteData(pBBEI, ucLine, h/4); + } // for tx + } else if (pBBEI->iOrientation == 270) { + for (tx=x+w-1; tx>=x; tx--) { + d = ucLine; + ucSrcMask = 1 << (y & 7); + for (ty=y; ty>3) * pBBEI->width]; + uc = 0; + for (int pix=0; pix<4; pix++) { + uc <<= 2; + if (!(s[tx] & ucSrcMask)) // inverted plane 0 + uc |= 0x01; + if (s[tx+iRedOff] & ucSrcMask) // plane 1 + uc |= 0x02; + ucSrcMask <<= 1; + if (ucSrcMask == 0) ucSrcMask = 1; + } // for pix + *d++ = uc; // store 2 pixels + } // for ty + RawWriteData(pBBEI, ucLine, h/4); + } // for x + } // 270 +} /* EPDWriteImage2bpp() */ +// +// Write EPD Image data +// +static void EPDWriteImage4bpp(OBDISP *pBBEI, uint8_t ucCMD, int x, int y, int w, int h) +{ +uint8_t ucLine[324]; // send the data one line at a time +int tx, ty, iRedOff; +uint8_t *s, *d, ucSrcMask, uc; +uint8_t *pBuffer; + + pBuffer = pBBEI->ucScreen; + iRedOff = pBBEI->width * ((pBBEI->height+7)/8); + if (ucCMD) { + obdWriteCommand(pBBEI, ucCMD); // start write + } + // Convert the bit direction and write the data to the EPD + // This particular controller has 4 bits per pixel where 0=black, 3=white, 4=red + // this wastes 50% of the time transmitting bloated info (only need 2 bits) + if (pBBEI->iOrientation == 180) { + for (ty=y+h-1; ty>=y; ty--) { + ucSrcMask = 1 << (ty & 7); + d = ucLine; + s = &pBuffer[(ty>>3) * pBBEI->width]; + for (tx=x+w-1; tx>=x; tx-=2) { + uc = 0x33; // start with white + if (s[tx] & ucSrcMask) // black + uc = 0x03; + if (s[tx+iRedOff] & ucSrcMask) // red + uc = 0x43; + if (s[tx-1] & ucSrcMask) {// src pixel = black + uc &= 0xf0; + } + if (s[tx-1+iRedOff] & ucSrcMask) { // red + uc &= 0xf0; uc |= 0x4; + } + *d++ = uc; // store 2 pixels + } // for tx + RawWriteData(pBBEI, ucLine, w/2); + } // for ty + } else if (pBBEI->iOrientation == 0) { + for (ty=y; ty> 3) * pBBEI->width]; + for (tx=x; txiOrientation == 90) { + for (tx=x; tx=y; ty-=2) { + s = &pBuffer[tx + (ty>>3) * pBBEI->width]; + uc = 0x33; + if (s[0] & ucSrcMask) + uc = 0x03; // black + if (s[iRedOff] & ucSrcMask) + uc = 0x43; // red + ucSrcMask >>= 1; + if (s[0] & ucSrcMask) {// src pixel = black + uc &= 0xf0; + } + if (s[iRedOff] & ucSrcMask) { // red + uc &= 0xf0; uc |= 0x4; + } + ucSrcMask >>= 1; + *d++ = uc; // store 2 pixels + if (ucSrcMask == 0) + ucSrcMask = 0x80; // bottom up + } // for ty + RawWriteData(pBBEI, ucLine, h/2); + } // for tx + } else if (pBBEI->iOrientation == 270) { + for (tx=x+w-1; tx>=x; tx--) { + d = ucLine; + ucSrcMask = 1 << (y & 7); + for (ty=y; ty>3) * pBBEI->width]; + uc = 0x33; // start with white + if (s[0] & ucSrcMask) + uc = 0x03; // black + if (s[iRedOff] & ucSrcMask) + uc = 0x43; // red + ucSrcMask <<= 1; + if (s[0] & ucSrcMask) {// src pixel = black + uc &= 0xf0; + } + if (s[iRedOff] & ucSrcMask) { // red + uc &= 0xf0; uc |= 0x4; + } + ucSrcMask <<= 1; + *d++ = uc; // store 2 pixels + if (ucSrcMask == 0) + ucSrcMask = 1; + } // for ty + RawWriteData(pBBEI, ucLine, h/2); + } // for x + } // 270 +} /* EPDWriteImage4bpp() */ +#endif // !WIMPY_MCU +const uint8_t epd294_init_sequence_part[] = +{ + 0x0b, 0x37, 0,0,0,0,0,0x40,0,0,0,0, + 0x04, 0x01, 0x27, 0x01, 0x00, + 0x02, 0x11, 0x01, + 0x03, 0x44, 0x00, 0x0f, + 0x05, 0x45, 0x00, 0x00, 0x27, 0x01, + 0x03, 0x21, 0x00, 0x80, + 0x02, 0x3c, 0xc0, + 0x02, 0x22, 0xc0, + 0x01, 0x20, + BUSY_WAIT, + 0x02, 0x4e, 0x00, + 0x03, 0x4f, 0x00, 0x00, + 0x00 // end of sequence +}; /* epd294_init_sequence_part */ + +const uint8_t epd293_lut[] = +{ + 0x22, 0x11, 0x10, 0x00, 0x10, 0x00, 0x00, 0x11, 0x88, 0x80, 0x80, 0x80, 0x00, 0x00, 0x6A, 0x9B, + 0x9B, 0x9B, 0x9B, 0x00, 0x00, 0x6A, 0x9B, 0x9B, 0x9B, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x18, 0x04, 0x16, 0x01, 0x0A, 0x0A, 0x0A, 0x0A, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x3C, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +void EPD47_Begin(OBDISP *pBBEI, int x, int y, int w, int h, int bPartial) +{ + (void)x; + (void)y; + (void)w; + (void)h; + EPD8951_CMD(pBBEI, 0x0001); // IT8951_TCON_SYS_RUN + EPD8951_REG(pBBEI, 0x0004, 0x0001); // enable pack write + // set vcom to -2.3v + EPD8951_CMD(pBBEI, 0x0039); + EPD8951_WORD(pBBEI, 0x0001); + EPD8951_WORD(pBBEI, 2300); + if (bPartial) { + + } else { + uint16_t u16Args[5]; + // set target memory area + EPD8951_REG(pBBEI, 0x000a, 0x0012); // _dev_memaddr_h + EPD8951_REG(pBBEI, 0x0008, 0x36e0); // _dev_memaddr_l + // set image area + u16Args[0] = 0; // little endian, 2bpp, no rotation + u16Args[1] = 0; // start x + u16Args[2] = 0; // start y + u16Args[3] = pBBEI->native_width; + u16Args[4] = pBBEI->native_height; + EPD8951_ARGS(pBBEI, 0x0021, u16Args, 5); // IT8951_TCON_LD_IMG_AREA + EPD8951_CMD(pBBEI, 0x20); // IT8951_TCON_LD_IMG + EPD8951_WORD(pBBEI, 0); // default settings (endian, format, rotation) + } +} /* EPD47_Begin() */ +// +// Special case for e-ink displays +// +static int EPDDumpBuffer(OBDISP *pBBEI, int bRefresh, int bWait, int bFast) +{ + long lTime; + + if (!(pBBEI->iFlags & BBEI_HAS_FAST_UPDATE)) + bFast = 0; // no fast update available + + pBBEI->iFlags |= BBEI_FULLUPDATE; // mark it for later + // UC81xx need to be told the old and new images for fast updates + // Keep a copy of the last full update for this purpose + if (pBBEI->type >= EPD42_400x300 && pBBEI->chip_type == BBEI_CHIP_UC8151 && pBBEI->ucScreen && !(pBBEI->iFlags & BBEI_3COLOR)) { + int iSize = pBBEI->width * ((pBBEI->height+7)>>3); + memcpy(&pBBEI->ucScreen[iSize], pBBEI->ucScreen, iSize); + } + EPDWakeUp(pBBEI, 0); // tickle the reset line + if (pBBEI->pInitFull) { // if we have the command list, use common code + int iSize = pBBEI->width * ((pBBEI->height+7)>>3); + int bInvert = 0; + uint8_t *pBuf1 = NULL, *pBuf2 = NULL; + + lTime = millis(); // get data transmission time + if (bFast && pBBEI->pInitFast) { + Serial.println("Sending fast init"); + EPDSendCMDSequence(pBBEI, pBBEI->pInitFast); + } else { + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + } + if (pBBEI->chip_type == BBEI_CHIP_UC8151) { + if (pBBEI->ucScreen) { // we have a back buffer, send it + if (pBBEI->iFlags & BBEI_3COLOR) { // B/W/R + bInvert = 1; // invert the red plane + pBuf1 = pBBEI->ucScreen; // B/W plane + pBuf2 = &pBBEI->ucScreen[iSize]; // R plane + } + EPDWriteImage(pBBEI, UC8151_DTM1, pBuf1, 0, 0, pBBEI->width, pBBEI->height, 0); // B/W or 'old' plane + EPDWriteImage(pBBEI, UC8151_DTM2, pBuf2, 0, 0, pBBEI->width, pBBEI->height, bInvert); // R or 'new' plane + } + lTime = millis() - lTime; + pBBEI->iDataTime = (int)lTime; // record the time to xfer data + if (bRefresh) { + lTime = millis(); + obdWriteCommand(pBBEI, UC8151_DRF); + EPDWaitBusy(pBBEI, 0); + lTime = millis() - lTime; + pBBEI->iOpTime = (int)lTime; // record the update time + } + } else { // SSD16xx controllers + if (pBBEI->ucScreen) { // we have a back buffer, send it + uint8_t ucTemp[4]; + if (pBBEI->iFlags & BBEI_3COLOR) { // B/W/R + pBuf1 = pBBEI->ucScreen; // B/W plane + pBuf2 = &pBBEI->ucScreen[iSize]; // R plane + bInvert = 1; // invert the R plane + } + EPDWriteImage(pBBEI, SSD1608_WRITE_ALTRAM, pBuf2, 0, 0, pBBEI->width, pBBEI->height, bInvert); + // need to reset the write pointer between planes + EPD_CMD2(pBBEI, SSD1608_SET_RAMXCOUNT, 0x00); + ucTemp[0] = ucTemp[1] = 0; + obdWriteCommand(pBBEI, SSD1608_SET_RAMYCOUNT); + RawWriteData(pBBEI, ucTemp, 2); + EPDWriteImage(pBBEI, SSD1608_WRITE_RAM, pBuf1, 0, 0, pBBEI->width, pBBEI->height, 0); + } + lTime = millis() - lTime; // record data xfer time + pBBEI->iDataTime = (int)lTime; + if (bRefresh) { + lTime = millis(); + EPD_CMD2(pBBEI, SSD1608_DISP_CTRL2, (bFast || (pBBEI->iFlags & BBEI_3COLOR)) ? 0xc7 : 0xf7); + obdWriteCommand(pBBEI, SSD1608_MASTER_ACTIVATE); // refresh + EPDWaitBusy(pBBEI, 0); + lTime = millis() - lTime; // record the update time + pBBEI->iOpTime = (int)lTime; + } + } // UC8151 / SSD16xx + return 0; + } // has init sequence + // Special case EPDs below (4-gray, 4-color or 3-color/4-bpp) +#if !defined( WIMPY_MCU ) && !defined(__AVR__) + // not enough RAM on AVR to use this type of EPD + if (pBBEI->type == EPD42_4GRAY_400x300) { + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + if (pBBEI->ucScreen) { + EPDWriteImage(pBBEI, 0x10, NULL, 0, 0, pBBEI->width, pBBEI->height, 1); // MSB plane + EPDWriteImage(pBBEI, 0x13, NULL, 0, 0, pBBEI->width, pBBEI->height, 0); // LSB plane + } + obdWriteCommand(pBBEI, UC8151_LUT_VCOM); + RawWriteData(pBBEI, (uint8_t *)lut42_4gray_vcom, 42); + obdWriteCommand(pBBEI, UC8151_LUT_WW); + RawWriteData(pBBEI, (uint8_t *)lut42_4gray_ww, 42); + obdWriteCommand(pBBEI, UC8151_LUT_BW); + RawWriteData(pBBEI, (uint8_t *)lut42_4gray_bw, 42); + obdWriteCommand(pBBEI, UC8151_LUT_WB); + RawWriteData(pBBEI, (uint8_t *)lut42_4gray_wb, 42); + obdWriteCommand(pBBEI, UC8151_LUT_BB); + RawWriteData(pBBEI, (uint8_t *)lut42_4gray_bb, 42); + obdWriteCommand(pBBEI, UC8151_LUT_VCOM2); + RawWriteData(pBBEI, (uint8_t *)lut42_4gray_vcom, 42); + if (bRefresh) + obdWriteCommand(pBBEI, UC8151_DRF); // refresh display + else return 0; + } + if (pBBEI->type == EPD74R_640x384) { + if (pBBEI->native_width == 640) { + EPDSendCMDSequence(pBBEI, epd74r_init_sequence_full); + } else { + EPDSendCMDSequence(pBBEI, epd583r_init_sequence_full); + } + if (pBBEI->ucScreen) { + EPDWriteImage4bpp(pBBEI, 0x10, 0, 0, pBBEI->width, pBBEI->height); + } + if (bRefresh) { + obdWriteCommand(pBBEI, 0x12); // master activation + } + if (bWait) { + EPDWaitBusy(pBBEI, 0); + } + return BBEI_SUCCESS; + } +#endif // !WIMPY_MCU + if (pBBEI->type == EPD30_BWYR_168x400 || pBBEI->type == EPD164_BWYR_168x168 || pBBEI->type == EPD236_BWYR_168x296) { + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + if (pBBEI->ucScreen) { + if (pBBEI->type == EPD164_BWYR_168x168 || pBBEI->type == EPD236_BWYR_168x296) { + EPD_CMD2(pBBEI, 0x68, 0x01); + obdWriteCommand(pBBEI, 0x04); + EPDWaitBusy(pBBEI, 0); + } + EPDWriteImage2bpp(pBBEI, 0x10, 0, 0, pBBEI->width, pBBEI->height); + } + if (pBBEI->type == EPD164_BWYR_168x168 || pBBEI->type == EPD236_BWYR_168x296) { + EPD_CMD2(pBBEI, 0x68, 0x00); + } + EPD_CMD2(pBBEI, 0x12, 0x01); // display refresh + } + if (pBBEI->type == EPD266_BWYR_184x360 || pBBEI->type == EPD29_BWYR_168x384 || pBBEI->type == EPD29_BWYR_128x296) { + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + if (pBBEI->ucScreen) { + EPDWriteImage2bpp(pBBEI, 0x10, 0, 0, pBBEI->width, pBBEI->height); + } + EPD_CMD2(pBBEI, 0x12, 0x00); // display refresh + } + if (pBBEI->type == EPD42R2_400x300) { + EPDSendCMDSequence(pBBEI, pBBEI->pInitFull); + // EPD27_SetLut(pBBEI, false); +#ifndef WIMPY_MCU + if (pBBEI->ucScreen) { + EPDWriteImage(pBBEI, 0x10, NULL, 0, 0, pBBEI->width, pBBEI->height, 0); // black/white plane + EPDWriteImage(pBBEI, 0x13, NULL, 0, 0, pBBEI->width, pBBEI->height, 1); // red plane + } +#endif // !WIMPY_MCU + if (bRefresh) { + obdWriteCommand(pBBEI, 0x12); // master activation + } + } + if (pBBEI->type == EPD213R_104x212_d) { + EPDSendCMDSequence(pBBEI, EPD213R_104x212_d_init_sequence_full); + EPD27_SetLut(pBBEI, false); +#ifndef WIMPY_MCU + if (pBBEI->ucScreen) { + EPDWriteImage(pBBEI, 0x10, NULL, 0, 0, pBBEI->width, pBBEI->height, 0); // black/white plane + EPDWriteImage(pBBEI, 0x13, NULL, 0, 0, pBBEI->width, pBBEI->height, 1); // red plane + } +#endif // !WIMPY_MCU + if (bRefresh) { + obdWriteCommand(pBBEI, 0x12); // master activation + } + } + if (pBBEI->type == EPD579_792x272) { + EPD_CMD2(pBBEI, SSD1608_DISP_CTRL2, 0xf7); + obdWriteCommand(pBBEI, SSD1608_MASTER_ACTIVATE); + } + if (pBBEI->type == EPD47_540x960) { + uint16_t u16Args[7]; + EPD47_Begin(pBBEI, 0, 0, pBBEI->width, pBBEI->height, false); +#ifndef WIMPY_MCU + if (pBBEI->ucScreen) { + EPDWriteImage(pBBEI, 0, NULL, 0, 0, pBBEI->width, pBBEI->height, 0); + } +#endif + EPD8951_CMD(pBBEI, 0x0022); // LCD_IMG_END + u16Args[0] = 0; // x + u16Args[1] = 0; // y + u16Args[2] = pBBEI->native_width; + u16Args[3] = pBBEI->native_height; + u16Args[4] = 0; // full update mode (2 seconds) + u16Args[5] = 0x36e0; // _dev_memaddr_l + u16Args[6] = 0x0012; // _dev_memaddr_h + EPD8951_ARGS(pBBEI, 0x0037, u16Args, 7); // IT8951_I80_CMD_DPY_BUF_AREA + } + if (bWait) { + EPDWaitBusy(pBBEI, 0); + EPDSleep(pBBEI, 0); + } + return BBEI_SUCCESS; +} /* EPDDumpBuffer() */ +// +// Special case for ST7302 +// +#ifndef WIMPY_MCU +void obdST7302SetPos(OBDISP *pBBEI, int x, int y) +{ +uint8_t ucTemp[4]; + + obdWriteCommand(pBBEI, 0x2a); // Column set + ucTemp[0] = 0x19 + (y/12); // start x (we treat it as y) + ucTemp[1] = 0x27; // end x + RawWriteData(pBBEI, ucTemp, 2); + obdWriteCommand(pBBEI, 0x2b); // Row set + ucTemp[0] = (x/2); // start y (we treat it as x) + ucTemp[1] = 0x80; // end y + RawWriteData(pBBEI, ucTemp, 2); + obdWriteCommand(pBBEI, 0x2c); // memory write +} /* obdST7302SetPos() */ +// +// Special case for ST7302 +// +static int ST7302DumpBuffer(OBDISP *pBBEI, uint8_t *pBuffer) +{ +uint8_t ucPixels[40]; +int x, y, iPitch, count; +uint8_t ucMask, uc1, *s, *d; + + iPitch = pBBEI->width; +// Shift out the image in pairs of lines + ucPixels[0] = 0x40; + switch (pBBEI->iOrientation) { + case 0: + case 180: + for (x = 0; x < pBBEI->width; x += 2) { // a pair of columns at a time + d = ucPixels+1; + ucMask = 1; + uc1 = 0; + count = 0; + if (pBBEI->iOrientation == 180) { + ucMask = 1<<((pBBEI->height-1) & 7); + s = &pBuffer[pBBEI->width-2-x + ((pBBEI->height-1)>>3)*iPitch]; + for (y = pBBEI->height-1; y >= 0; y--) { + uc1 <<= 2; + if (s[0] & ucMask) uc1 |= 1; + if (s[1] & ucMask) uc1 |= 2; + count++; + if (count == 4) { // finish the byte + *d++ = uc1; + count = 0; + uc1 = 0; + } + ucMask >>= 1; + if (ucMask == 0) { + ucMask = 0x80; + s -= iPitch; + } // next line + } // for y + if (count) { + uc1 <<= (count*2); + *d++ = uc1; + } + } else { + s = &pBuffer[x]; + uc1 = 0; + for (y=0; y < pBBEI->height+3; y++) { + uc1 <<= 2; + if (s[0] & ucMask) uc1 |= 2; + if (s[1] & ucMask) uc1 |= 1; + count++; + if (count == 4) { // finish the byte + *d++ = uc1; + count = 0; + uc1 = 0; + } + ucMask <<= 1; + if (ucMask == 0) { + ucMask = 1; + s += iPitch; + } // next line + } // for y + } // flipped + obdSetPosition(pBBEI, (pBBEI->flip) ? 248-x : x, 0, 1); + RawWrite(pBBEI, ucPixels, 3 + (int)(d - ucPixels)); + } // for x + break; + case 90: + case 270: // x/y swapped + if (pBBEI->iOrientation == 270) { + for (y=pBBEI->height-2; y >= 0; y-=2) { + ucMask = 1<<(y & 7); + s = &pBuffer[(y>>3)*pBBEI->width]; + count = 0; + d = ucPixels+1; + uc1 = 0; + for (x=0; xwidth; x++) { + uc1 <<= 2; + if (s[x] & ucMask) uc1 |= 1; + if (s[x] & (ucMask << 1)) uc1 |= 2; + count++; + if (count == 4) { // finish the byte + *d++ = uc1; + count = 0; + } + } // for x + obdSetPosition(pBBEI, pBBEI->height - 2 - y, 0, 1); + RawWrite(pBBEI, ucPixels, 3 + (int)(d - ucPixels)); + } // for y + } else { // 90 + for (y=0; y < pBBEI->height; y+=2) { + ucMask = 1<<(y & 7); + s = &pBuffer[(y>>3)*pBBEI->width]; + count = 0; + d = ucPixels+1; + uc1 = 0; + for (x=pBBEI->width-1; x>=0; x--) { + uc1 <<= 2; + if (s[x] & ucMask) uc1 |= 2; + if (s[x] & (ucMask << 1)) uc1 |= 1; + count++; + if (count == 4) { // finish the byte + *d++ = uc1; + count = 0; + } + } // for x + obdSetPosition(pBBEI, y, 0, 1); + RawWrite(pBBEI, ucPixels, 3 + (int)(d - ucPixels)); + } // for y + } // flipped + break; + } // switch + return BBEI_SUCCESS; +} /* ST7302DumpBuffer() */ +#endif // !WIMPY_MCU +// +// Special case for Sharp Memory LCD +// +static int SharpDumpBuffer(OBDISP *pBBEI, uint8_t *pBuffer) +{ +int x, y; +uint8_t c, ucInvert, ucStart, ucMask; +uint8_t *s, *d; +uint8_t ucLineBuf[64]; +int iPitch = pBBEI->native_width / 8; +static int ucVCOM = 0; +int iBit; + + ucInvert = (pBBEI->invert) ? 0x00 : 0xff; + digitalWrite(pBBEI->iCSPin, HIGH); // active high + ucLineBuf[0] = 0; // Tell RawWrite that it's command (doesn't matter for Sharp LCDs) + ucStart = 0x80; // write command + if (ucVCOM) + ucStart |= 0x40; // VCOM bit + ucLineBuf[1] = ucStart; + // this code assumes I2C, so the first byte is ignored + RawWrite(pBBEI, ucLineBuf, 2); // write command(01) + vcom(02) + ucVCOM = !ucVCOM; // need to toggle this each transaction + + // We need to flip and invert the image in code because the Sharp memory LCD + // controller only has the simplest of commands for data writing + if (pBBEI->iOrientation == 180) + { + for (y=0; yheight; y++) // we have to write the memory in the wrong direction + { + ucMask = 0x80 >> (y & 7); + s = &pBuffer[pBBEI->width - 1 + (pBBEI->width * ((pBBEI->height - 1 - y) >> 3))]; // point to last line first + d = &ucLineBuf[2]; + ucLineBuf[1] = pgm_read_byte(&ucMirror[y+1]); // current line number + for (x=0; xwidth/8; x++) + { + c = ucInvert; // we need to brute-force invert it + for (iBit=7; iBit>=0; iBit--) + { + if (s[0] & ucMask) + c ^= (1 << iBit); + s--; + } + *d++ = c; + } // for y + // write this line to the display + ucLineBuf[iPitch+2] = 0; // end of line + RawWrite(pBBEI, ucLineBuf, iPitch+3); + } // for x + } + else if (pBBEI->iOrientation == 0) // normal orientation + { + for (y=0; yheight; y++) // we have to write the memory in the wrong direction + { + ucMask = 1 << (y & 7); + s = &pBuffer[pBBEI->width * (y >> 3)]; // point to last line first + d = &ucLineBuf[2]; + ucLineBuf[1] = pgm_read_byte(&ucMirror[y+1]); // current line number + for (x=0; xwidth/8; x++) + { + c = ucInvert; + for (iBit=7; iBit>=0; iBit--) + { + if (s[0] & ucMask) + c ^= (1 << iBit); + s++; + } + *d++ = c; + } // for x + // write this line to the display + *d++ = 0; // end of line + RawWrite(pBBEI, ucLineBuf, iPitch+3); + } // for y + } else if (pBBEI->iOrientation == 90) { + for (x=0; xwidth; x++) { + s = &pBuffer[x+((pBBEI->height-1)>>3)*pBBEI->width]; + d = &ucLineBuf[2]; + ucLineBuf[1] = pgm_read_byte(&ucMirror[x+1]); // line number + for (y=0; yheight; y+=8) { // we have to write the memory in the wrong direction + *d++ = s[0] ^ ucInvert; + s -= pBBEI->width; + } // for y + ucLineBuf[iPitch+2] = 0; // end of line + RawWrite(pBBEI, ucLineBuf, iPitch+3); + } // for x + } else { // must be 270 + for (x=0; xwidth; x++) { + d = &ucLineBuf[2]; + s = &pBuffer[(pBBEI->width-1-x)]; + ucLineBuf[1] = pgm_read_byte(&ucMirror[x+1]); // line number + for (y=0; yheight; y+=8) { + *d++ = pgm_read_byte(&ucMirror[s[0] ^ ucInvert]); + s += pBBEI->width; + } // for y + ucLineBuf[iPitch+2] = 0; // end of line + RawWrite(pBBEI, ucLineBuf, iPitch+3); + } // for x + } // 270 + ucLineBuf[1] = 0; + RawWrite(pBBEI, ucLineBuf, 2); // final transfer + digitalWrite(pBBEI->iCSPin, LOW); // de-activate + return BBEI_SUCCESS; +} /* SharpDumpBuffer() */ +// +// Dump a screen's worth of data directly to the display +// Try to speed it up by comparing the new bytes with the existing buffer +// +int obdDumpBuffer(OBDISP *pBBEI, uint8_t *pBuffer, int bRefresh, int bWait, int bFast) +{ +int x, y; +int iLines; + + if (pBBEI->type == LCD_VIRTUAL) { // wrong function for this type of display + return BBEI_ERROR_BAD_PARAMETER; + } + if (pBuffer == NULL) { // dump the internal buffer if none is given + pBuffer = pBBEI->ucScreen; + } +// if (pBuffer == NULL) { +// return; // no backbuffer and no provided buffer +// } + + if (pBBEI->type >= EPD42_400x300) // all e-ink panels + { + return EPDDumpBuffer(pBBEI, bRefresh, bWait, bFast); + } +#if !defined( WIMPY_MCU ) && !defined(__AVR__) + if (pBBEI->type == LCD_ST7302) // special case for ST7302 + { + return ST7302DumpBuffer(pBBEI, pBuffer); + } +#endif // !WIMPY_MCU + if (pBBEI->chip_type == BBEI_CHIP_SHARP) // special case for Sharp Memory LCDs + { + return SharpDumpBuffer(pBBEI, pBuffer); + } + iLines = (pBBEI->native_height+7) >> 3; + // 0/180 we can send the 8 lines of pixels straight through + if (pBBEI->iOrientation == 0 || pBBEI->iOrientation == 180) { + if (pBBEI->iOrientation == 0 || (pBBEI->iOrientation == 180 && pBBEI->can_flip)) { + for (y=0; ywidth); + pBuffer += pBBEI->width; + } + } else { // have to manually do 180 flipped + for (y=iLines-1; y>=0; y--) { + obdSetPosition(pBBEI, 0, y*8, 1); + for (int x=0; xwidth; x++) { + u8Cache[pBBEI->width-1-x] = ucMirror[pBuffer[x]]; + } + RawWriteData(pBBEI, u8Cache, pBBEI->width); + pBuffer += pBBEI->width; + } + } + return BBEI_SUCCESS; + } else { // must be 90/270 + // Capture the pixels 'sideways' and send a line at a time + if (pBBEI->iOrientation == 90 || (pBBEI->iOrientation == 270 && pBBEI->can_flip)) { + for (x=0; xwidth; x+=8) { + uint8_t *s, *d, uc, ucMask, ucTemp[132]; + int j; + d = ucTemp; + *d++ = 0x40; + s = &pBuffer[x + ((pBBEI->height-7)>>3)*pBBEI->width]; + ucMask = 0x80; + for (y=0; yheight; y++) { + uc = 0; + for (j=7; j>=0; j--) { + uc <<= 1; + if (s[j] & ucMask) uc |= 1; + } // for j + *d++ = uc; + ucMask >>= 1; + if (ucMask == 0) { + ucMask = 0x80; + s -= pBBEI->width; + } + } // for y + obdSetPosition(pBBEI, 0, x, 1); + RawWrite(pBBEI, ucTemp, pBBEI->height + 1); + } // for x + } else { // 270 degrees and can't flip 180 + for (x=pBBEI->width-8; x>=0; x-=8) { + uint8_t j, *s, *d, uc, ucMask, ucTemp[132]; + d = ucTemp; + *d++ = 0x40; + s = &pBuffer[x]; + ucMask = 1; + for (y=0; yheight; y++) { + uc = 0; + for (j=0; j<8; j++) { + uc <<= 1; + if (s[j] & ucMask) uc |= 1; + } // for j + *d++ = uc; + ucMask <<= 1; + if (ucMask == 0) { + ucMask = 1; + s += pBBEI->width; + } + } // for y + obdSetPosition(pBBEI, 0, pBBEI->width-8-x, 1); + RawWrite(pBBEI, ucTemp, pBBEI->height + 1); + } // for x + } + } // 90/270 degrees rotated + return BBEI_SUCCESS; +} /* obdDumpBuffer() */ + +// Send a single byte command to the OLED controller +void obdWriteCommand(OBDISP *pBBEI, unsigned char c) +{ +#ifndef MEMORY_ONLY +unsigned char buf[4]; + + buf[0] = 0x00; // command introducer + buf[1] = c; + RawWrite(pBBEI, buf, 2); +#endif // MEMORY_ONLY +} /* obdWriteCommand() */ +// +// Experimental bufferless E-ink drawing code +// +void EPDSetPosition(OBDISP *pBBEI, int x, int y, int cx, int cy) +{ +uint8_t uc[12]; +int i, tx, ty; + + // We need to rotate the drawing direction since native direction for EPDs + // is horizontal bytes across the X direction with the MSB on the left + if (pBBEI->iOrientation == 90 || pBBEI->iOrientation == 270) { + if (x + cx > pBBEI->native_height) + cx = pBBEI->native_height - x; + if (y + cy > pBBEI->native_width) + cy = pBBEI->native_width - y; + y &= 0xff8; // fix to byte boundary + if ((pBBEI->iFlags & BBEI_FLIPV) || (pBBEI->ucScreen == NULL/* && pBBEI->chip_type == BBEI_CHIP_SSD16xx*/)) // flipped Y when drawing with no back buffer + tx = (pBBEI->native_width /*+ 7*/ - y - cy)/8; + else + tx = y/8; + ty = x; + // swap width/height + i = cx; + cx = cy; + cy = i; + } else { + tx = (x+7)/8; + ty = y; + if (ty+cy > pBBEI->native_height) { + cy = pBBEI->native_height - ty; + } + if ((tx*8)+cx > pBBEI->native_width) { + cx = pBBEI->native_width - (tx*8); + } + } + switch (pBBEI->chip_type) { + case BBEI_CHIP_SSD16xx: + if (pBBEI->type == EPD579_792x272) { // special case + EPD_CMD2(pBBEI, SSD1608_DATA_MODE, 0x02); + if (cx > 400 && cy == 8) { // a single line of graphics + cx = 8; + cy = 271-ty; // different mem direction + } + if (cx > 400) cx = 400; // two sets of 400 pixels + obdWriteCommand(pBBEI, SSD1608_SET_RAMXPOS); + uc[1] = tx; // start x (byte boundary) + uc[0] = tx+(cx>>3); // end x + RawWriteData(pBBEI, uc, 2); + obdWriteCommand(pBBEI, SSD1608_SET_RAMYPOS); + uc[0] = (uint8_t)ty; // start y + uc[1] = (uint8_t)(ty>>8); + uc[2] = (uint8_t)(ty+cy-1); // end y + uc[3] = (uint8_t)((ty+cy-1)>>8); + RawWriteData(pBBEI, uc, 4); + EPD_CMD2(pBBEI, SSD1608_SET_RAMXCOUNT, 0xf); + obdWriteCommand(pBBEI, SSD1608_SET_RAMYCOUNT); + uc[0] = uc[1] = 0; + RawWriteData(pBBEI, uc, 2); + if (pBBEI->iFG == BBEI_RED) // write to red plane + obdWriteCommand(pBBEI, SSD1608_WRITE_ALTRAM); + else + obdWriteCommand(pBBEI, SSD1608_WRITE_RAM); // prepare to start writing pixels + } else { + obdWriteCommand(pBBEI, SSD1608_SET_RAMXPOS); + uc[0] = tx + pBBEI->x_offset; // start x (byte boundary) + // if (cx == pBBEI->native_width && (pBBEI->native_width & 7) != 0) + // cx += 7; // needs one more byte for the full display width + uc[1] = tx+((cx-1)>>3) + pBBEI->x_offset; // end x + RawWriteData(pBBEI, uc, 2); + obdWriteCommand(pBBEI, SSD1608_SET_RAMYPOS); + if (pBBEI->iFlags & BBEI_FLIPV) { + uc[0] = (uint8_t)(ty+cy-1); // start y + uc[1] = (uint8_t)((ty+cy-1)>>8); + uc[2] = (uint8_t)ty; // end y + uc[3] = (uint8_t)(ty>>8); + } else { + uc[0] = (uint8_t)ty; // start y + uc[1] = (uint8_t)(ty>>8); + uc[2] = (uint8_t)(ty+cy-1); // end y + uc[3] = (uint8_t)((ty+cy-1)>>8); + } + RawWriteData(pBBEI, uc, 4); + // set ram counter to start of this region + EPD_CMD2(pBBEI, SSD1608_SET_RAMXCOUNT, tx+pBBEI->x_offset); + uc[0] = ty; + uc[1] = (ty>>8); + obdWriteCommand(pBBEI, SSD1608_SET_RAMYCOUNT); + RawWriteData(pBBEI, uc, 2); + if (pBBEI->iFlags & BBEI_FLIPV) + EPD_CMD2(pBBEI, SSD1608_DATA_MODE, 0x1); + else + EPD_CMD2(pBBEI, SSD1608_DATA_MODE, 0x3); + if (pBBEI->iFG == BBEI_RED) // write to red plane + obdWriteCommand(pBBEI, SSD1608_WRITE_ALTRAM); + else + obdWriteCommand(pBBEI, SSD1608_WRITE_RAM); // prepare to start writing pixels + } + break; + case BBEI_CHIP_UC8151: + obdWriteCommand(pBBEI, UC8151_PTIN); // partial in + obdWriteCommand(pBBEI, UC8151_PTL); // partial window + i = 0; + tx *= 8; + if (pBBEI->native_width >= 256) { // need 2 bytes per x + uc[i++] = (uint8_t)(tx>>8); // start x + uc[i++] = (uint8_t)tx; + uc[i++] = (uint8_t)((tx+cx-1)>>8); // end x + uc[i++] = (uint8_t)((tx+cx-1) | 7); + } else { + uc[i++] = tx; // start x + uc[i++] = (tx+cx-1) | 7; // end x + } + if (pBBEI->iFlags & BBEI_FLIPV) { + if (pBBEI->native_height >= 256) { + uc[i++] = (uint8_t)((ty+cy-1)>>8); // start y + uc[i++] = (uint8_t)(ty+cy-1); + uc[i++] = (uint8_t)(ty>>8); // end y + uc[i++] = (uint8_t)ty; + } else { + uc[i++] = (uint8_t)(ty+cy-1); + uc[i++] = (uint8_t)ty; + } + } else { + if (pBBEI->native_height >= 256) { + uc[i++] = (uint8_t)(ty>>8); // start y + uc[i++] = (uint8_t)ty; + uc[i++] = (uint8_t)((ty+cy-1)>>8); // end y + uc[i++] = (uint8_t)(ty+cy-1); + } else { + uc[i++] = (uint8_t)ty; + uc[i++] = (uint8_t)(ty+cy-1); + } + } + uc[i++] = 0; // stay inside the partial window3 + RawWriteData(pBBEI, uc, i); + if (pBBEI->iFlags & BBEI_3COLOR) { + if (pBBEI->iFG == BBEI_RED) // write to red plane + obdWriteCommand(pBBEI, UC8151_DTM2); + else + obdWriteCommand(pBBEI, UC8151_DTM1); // prepare to start writing pixels + } else { + obdWriteCommand(pBBEI, UC8151_DTM2); + } + break; + default: // error + break; + } +} /* EPDSetPosition() */ +// +// Send commands to position the "cursor" (aka memory write address) +// to the given row and column +// +void obdSetPosition(OBDISP *pBBEI, int x, int y, int bRender) +{ +unsigned char buf[4]; +int iPitch = pBBEI->width; + + // e-paper direct access + if (pBBEI->type >= EPD42_400x300 && !pBBEI->ucScreen) { // immediate mode for e-paper displays + EPDSetPosition(pBBEI, x, y, 800, 8); // let the width get corrected in the command prep + return; + } +#if !defined( WIMPY_MCU ) && !defined(__AVR__) + if (pBBEI->type == LCD_ST7302) { // special case for ST7302 + obdST7302SetPos(pBBEI, x, y); + } +#endif // !WIMPY_MCU + y >>= 3; // DEBUG - since we address the display by lines of 8 pixels + pBBEI->iScreenOffset = (y*iPitch)+x; + + if (pBBEI->type == LCD_VIRTUAL || pBBEI->chip_type == BBEI_CHIP_SHARP) + return; // nothing to do + if (!bRender) + return; // don't send the commands to the OLED if we're not rendering the graphics now + obdCachedFlush(pBBEI, bRender); // flush any cached data first + if (pBBEI->type == LCD_NOKIA5110) + { + obdWriteCommand(pBBEI, 0x40 | y); + obdWriteCommand(pBBEI, 0x80 | x); + return; + } + if (pBBEI->type == OLED_80x128) // visible portion starts at column 24, row 0 + { + x += 24; + } + else if (pBBEI->type == LCD_32x64) + { + x += 50; + } + else if (pBBEI->type == OLED_64x32) // visible display starts at column 32, row 4 + { + x += 32; // display is centered in VRAM, so this is always true + if (pBBEI->flip == 0) // non-flipped display starts from line 4 + y += 4; + } + else if (pBBEI->type == OLED_64x48) { + x += 32; + if (!pBBEI->flip) + y += 2; // 128x64 internal RAM + } + else if (pBBEI->type == LCD_UC1701 && pBBEI->flip) + { + x += 4; + } + else if (pBBEI->type == OLED_132x64) // SH1106 has 128 pixels centered in 132 + { + x += 2; + } + else if (pBBEI->type == OLED_96x16) // visible display starts at line 2 + { // mapping is a bit strange on the 96x16 OLED + if (pBBEI->flip) + x += 32; + else + y += 2; + } + else if (pBBEI->type == OLED_72x40) // starts at x=28,y=3 + { + x += 28; + if (!pBBEI->flip) + { + y += 3; + } + } + if (pBBEI->com_mode == COM_I2C) { // I2C device + buf[0] = 0x00; // command introducer + buf[1] = 0xb0 | y; // set page to Y + buf[2] = x & 0xf; // lower column address + buf[3] = 0x10 | (x >> 4); // upper column addr + RawWrite(pBBEI, buf, 4); + } else { // SPI mode + obdWriteCommand(pBBEI, 0xb0 | y); // set Y + obdWriteCommand(pBBEI, 0x10 | (x >> 4)); // set X MSB + obdWriteCommand(pBBEI, 0x00 | (x & 0xf)); // set X LSB + } +} /* obdSetPosition() */ + +// +// Write a block of pixel data to the OLED +// Length can be anything from 1 to 1024 (whole display) +// +void obdWriteDataBlock(OBDISP *pBBEI, unsigned char *ucBuf, int iLen, int bRender) +{ +int iPitch, iBufferSize; +int iRedOffset = 0; + + iPitch = pBBEI->width; + iBufferSize = iPitch * ((pBBEI->height+7) / 8); + if (pBBEI->iFG >= BBEI_YELLOW) { + iRedOffset = pBBEI->width * ((pBBEI->height+7)/8); + } +// Keep a copy in local buffer +if (pBBEI->ucScreen && (iLen + pBBEI->iScreenOffset) <= iBufferSize) +{ + memcpy(&pBBEI->ucScreen[iRedOffset + pBBEI->iScreenOffset], ucBuf, iLen); + pBBEI->iScreenOffset += iLen; + // wrap around ? + if (pBBEI->iScreenOffset >= iBufferSize) + pBBEI->iScreenOffset -= iBufferSize; +} +if (pBBEI->type == LCD_VIRTUAL || pBBEI->chip_type == BBEI_CHIP_SHARP) + return; // nothing else to do +#ifndef MEMORY_ONLY + if (bRender) + { + RawWriteData(pBBEI, ucBuf, iLen); + } +#endif // MEMORY_ONLY +} /* obdWriteDataBlock() */ diff --git a/src/bb_ei_gfx.inl b/src/bb_ei_gfx.inl new file mode 100644 index 0000000..bbb8299 --- /dev/null +++ b/src/bb_ei_gfx.inl @@ -0,0 +1,2773 @@ +// +// OneBitDisplay (OLED/LCD/E-Paper library) +// Copyright (c) 2020 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 3/23/2020 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// obd_gfx.inl - graphics functions +// +// forward declarations +void InvertBytes(uint8_t *pData, uint8_t bLen); + +const uint8_t ucFont[]PROGMEM = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x5f,0x5f,0x06,0x00, + 0x00,0x07,0x07,0x00,0x07,0x07,0x00,0x14,0x7f,0x7f,0x14,0x7f,0x7f,0x14, + 0x24,0x2e,0x2a,0x6b,0x6b,0x3a,0x12,0x46,0x66,0x30,0x18,0x0c,0x66,0x62, + 0x30,0x7a,0x4f,0x5d,0x37,0x7a,0x48,0x00,0x04,0x07,0x03,0x00,0x00,0x00, + 0x00,0x1c,0x3e,0x63,0x41,0x00,0x00,0x00,0x41,0x63,0x3e,0x1c,0x00,0x00, + 0x08,0x2a,0x3e,0x1c,0x3e,0x2a,0x08,0x00,0x08,0x08,0x3e,0x3e,0x08,0x08, + 0x00,0x00,0x80,0xe0,0x60,0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08, + 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x30,0x18,0x0c,0x06,0x03,0x01, + 0x3e,0x7f,0x59,0x4d,0x47,0x7f,0x3e,0x40,0x42,0x7f,0x7f,0x40,0x40,0x00, + 0x62,0x73,0x59,0x49,0x6f,0x66,0x00,0x22,0x63,0x49,0x49,0x7f,0x36,0x00, + 0x18,0x1c,0x16,0x53,0x7f,0x7f,0x50,0x27,0x67,0x45,0x45,0x7d,0x39,0x00, + 0x3c,0x7e,0x4b,0x49,0x79,0x30,0x00,0x03,0x03,0x71,0x79,0x0f,0x07,0x00, + 0x36,0x7f,0x49,0x49,0x7f,0x36,0x00,0x06,0x4f,0x49,0x69,0x3f,0x1e,0x00, + 0x00,0x00,0x00,0x66,0x66,0x00,0x00,0x00,0x00,0x80,0xe6,0x66,0x00,0x00, + 0x08,0x1c,0x36,0x63,0x41,0x00,0x00,0x00,0x14,0x14,0x14,0x14,0x14,0x14, + 0x00,0x41,0x63,0x36,0x1c,0x08,0x00,0x00,0x02,0x03,0x59,0x5d,0x07,0x02, + 0x3e,0x7f,0x41,0x5d,0x5d,0x5f,0x0e,0x7c,0x7e,0x13,0x13,0x7e,0x7c,0x00, + 0x41,0x7f,0x7f,0x49,0x49,0x7f,0x36,0x1c,0x3e,0x63,0x41,0x41,0x63,0x22, + 0x41,0x7f,0x7f,0x41,0x63,0x3e,0x1c,0x41,0x7f,0x7f,0x49,0x5d,0x41,0x63, + 0x41,0x7f,0x7f,0x49,0x1d,0x01,0x03,0x1c,0x3e,0x63,0x41,0x51,0x33,0x72, + 0x7f,0x7f,0x08,0x08,0x7f,0x7f,0x00,0x00,0x41,0x7f,0x7f,0x41,0x00,0x00, + 0x30,0x70,0x40,0x41,0x7f,0x3f,0x01,0x41,0x7f,0x7f,0x08,0x1c,0x77,0x63, + 0x41,0x7f,0x7f,0x41,0x40,0x60,0x70,0x7f,0x7f,0x0e,0x1c,0x0e,0x7f,0x7f, + 0x7f,0x7f,0x06,0x0c,0x18,0x7f,0x7f,0x1c,0x3e,0x63,0x41,0x63,0x3e,0x1c, + 0x41,0x7f,0x7f,0x49,0x09,0x0f,0x06,0x1e,0x3f,0x21,0x31,0x61,0x7f,0x5e, + 0x41,0x7f,0x7f,0x09,0x19,0x7f,0x66,0x26,0x6f,0x4d,0x49,0x59,0x73,0x32, + 0x03,0x41,0x7f,0x7f,0x41,0x03,0x00,0x7f,0x7f,0x40,0x40,0x7f,0x7f,0x00, + 0x1f,0x3f,0x60,0x60,0x3f,0x1f,0x00,0x3f,0x7f,0x60,0x30,0x60,0x7f,0x3f, + 0x63,0x77,0x1c,0x08,0x1c,0x77,0x63,0x07,0x4f,0x78,0x78,0x4f,0x07,0x00, + 0x47,0x63,0x71,0x59,0x4d,0x67,0x73,0x00,0x7f,0x7f,0x41,0x41,0x00,0x00, + 0x01,0x03,0x06,0x0c,0x18,0x30,0x60,0x00,0x41,0x41,0x7f,0x7f,0x00,0x00, + 0x08,0x0c,0x06,0x03,0x06,0x0c,0x08,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x00,0x00,0x03,0x07,0x04,0x00,0x00,0x20,0x74,0x54,0x54,0x3c,0x78,0x40, + 0x41,0x7f,0x3f,0x48,0x48,0x78,0x30,0x38,0x7c,0x44,0x44,0x6c,0x28,0x00, + 0x30,0x78,0x48,0x49,0x3f,0x7f,0x40,0x38,0x7c,0x54,0x54,0x5c,0x18,0x00, + 0x48,0x7e,0x7f,0x49,0x03,0x06,0x00,0x98,0xbc,0xa4,0xa4,0xf8,0x7c,0x04, + 0x41,0x7f,0x7f,0x08,0x04,0x7c,0x78,0x00,0x44,0x7d,0x7d,0x40,0x00,0x00, + 0x60,0xe0,0x80,0x84,0xfd,0x7d,0x00,0x41,0x7f,0x7f,0x10,0x38,0x6c,0x44, + 0x00,0x41,0x7f,0x7f,0x40,0x00,0x00,0x7c,0x7c,0x18,0x78,0x1c,0x7c,0x78, + 0x7c,0x78,0x04,0x04,0x7c,0x78,0x00,0x38,0x7c,0x44,0x44,0x7c,0x38,0x00, + 0x84,0xfc,0xf8,0xa4,0x24,0x3c,0x18,0x18,0x3c,0x24,0xa4,0xf8,0xfc,0x84, + 0x44,0x7c,0x78,0x4c,0x04,0x0c,0x18,0x48,0x5c,0x54,0x74,0x64,0x24,0x00, + 0x04,0x04,0x3e,0x7f,0x44,0x24,0x00,0x3c,0x7c,0x40,0x40,0x3c,0x7c,0x40, + 0x1c,0x3c,0x60,0x60,0x3c,0x1c,0x00,0x3c,0x7c,0x60,0x30,0x60,0x7c,0x3c, + 0x44,0x6c,0x38,0x10,0x38,0x6c,0x44,0x9c,0xbc,0xa0,0xa0,0xfc,0x7c,0x00, + 0x4c,0x64,0x74,0x5c,0x4c,0x64,0x00,0x08,0x08,0x3e,0x77,0x41,0x41,0x00, + 0x00,0x00,0x00,0x77,0x77,0x00,0x00,0x41,0x41,0x77,0x3e,0x08,0x08,0x00, + 0x02,0x03,0x01,0x03,0x02,0x03,0x01,0x70,0x78,0x4c,0x46,0x4c,0x78,0x70}; +// AVR MCUs have very little memory; save 6K of FLASH by stretching the 'normal' +// font instead of using this large font +#ifndef WIMPY_MCU +const uint8_t ucBigFont[]PROGMEM = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x3f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x3f,0x3f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xfc,0xfc,0xfc,0xfc,0xc0,0xc0,0xfc,0xfc,0xfc,0xfc,0xc0,0xc0,0x00,0x00, + 0xc0,0xc0,0xff,0xff,0xff,0xff,0xc0,0xc0,0xff,0xff,0xff,0xff,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, + 0xf0,0xf0,0xc3,0xc3,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0x3f,0x3f,0x3f,0x3f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3c,0x3c,0xff,0xff,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x30,0x30,0x3f,0x3f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0xcc,0xcc,0xff,0xff,0x3f,0x3f,0x3f,0x3f,0xff,0xff,0xcc,0xcc,0x0c,0x0c, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x30,0x30,0x3f,0x3f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0xff,0xff,0xff,0xff,0x30,0x30,0x0f,0x0f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x30,0x30,0x3c,0x3c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00, + 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x0f,0x0f,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x3f,0x3f,0xf3,0xf3,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x3c,0x3c,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf3,0xf3,0x3f,0x3f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3c,0x3c,0x3f,0x3f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x3f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x3c,0x3c,0xf0,0xf0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0xc3,0xc3,0x0f,0x0f,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0xc3,0xc3,0x0f,0x0f,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x0c,0x0c,0x0c,0x0c,0xfc,0xfc,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00, + 0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x0f,0x0f,0x3f,0x3f,0xf0,0xf0,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xfc,0xfc,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0x0f,0x0f,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0xc0,0xc0,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0xff,0xff,0xff,0xff,0xc3,0xc3,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3c,0x3c,0xff,0xff,0xc3,0xc3,0x03,0x03,0x03,0x03,0x3f,0x3f,0x3c,0x3c,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x03,0x03,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3f,0x3f,0x0f,0x0f,0xff,0xff,0xff,0xff,0x0f,0x0f,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x3f,0x3f,0xff,0xff,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0xc0,0xc0,0xfc,0xfc,0xc0,0xc0,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3f,0x3f,0x0f,0x0f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x3f,0x3f,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0, + 0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xc3,0xc3,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0xc3,0xc3,0xcf,0xcf,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xcf,0xcf,0xcf,0xcf,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xcf,0xcf,0xcf,0xcf,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x3c,0x3c,0xff,0xff,0xc3,0xc3,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0xff,0xff,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x30,0x30,0xf0,0xf0,0xc3,0xc3,0x03,0x03,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xfc,0xfc,0xff,0xff,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0xf0,0xf0,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xcc,0xcc,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x03,0x03,0xc3,0xc3,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; +#endif // !WIMPY_MCU + + // 5x7 font (in 6x8 cell) +const uint8_t ucSmallFont[] PROGMEM = { +0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x5f,0x06,0x00, +0x07,0x03,0x00,0x07,0x03, +0x24,0x7e,0x24,0x7e,0x24, +0x24,0x2b,0x6a,0x12,0x00, +0x63,0x13,0x08,0x64,0x63, +0x36,0x49,0x56,0x20,0x50, +0x00,0x07,0x03,0x00,0x00, +0x00,0x3e,0x41,0x00,0x00, +0x00,0x41,0x3e,0x00,0x00, +0x08,0x3e,0x1c,0x3e,0x08, +0x08,0x08,0x3e,0x08,0x08, +0x00,0xe0,0x60,0x00,0x00, +0x08,0x08,0x08,0x08,0x08, +0x00,0x60,0x60,0x00,0x00, +0x20,0x10,0x08,0x04,0x02, +0x3e,0x51,0x49,0x45,0x3e, +0x00,0x42,0x7f,0x40,0x00, +0x62,0x51,0x49,0x49,0x46, +0x22,0x49,0x49,0x49,0x36, +0x18,0x14,0x12,0x7f,0x10, +0x2f,0x49,0x49,0x49,0x31, +0x3c,0x4a,0x49,0x49,0x30, +0x01,0x71,0x09,0x05,0x03, +0x36,0x49,0x49,0x49,0x36, +0x06,0x49,0x49,0x29,0x1e, +0x00,0x6c,0x6c,0x00,0x00, +0x00,0xec,0x6c,0x00,0x00, +0x08,0x14,0x22,0x41,0x00, +0x24,0x24,0x24,0x24,0x24, +0x00,0x41,0x22,0x14,0x08, +0x02,0x01,0x59,0x09,0x06, +0x3e,0x41,0x5d,0x55,0x1e, +0x7e,0x11,0x11,0x11,0x7e, +0x7f,0x49,0x49,0x49,0x36, +0x3e,0x41,0x41,0x41,0x22, +0x7f,0x41,0x41,0x41,0x3e, +0x7f,0x49,0x49,0x49,0x41, +0x7f,0x09,0x09,0x09,0x01, +0x3e,0x41,0x49,0x49,0x7a, +0x7f,0x08,0x08,0x08,0x7f, +0x00,0x41,0x7f,0x41,0x00, +0x30,0x40,0x40,0x40,0x3f, +0x7f,0x08,0x14,0x22,0x41, +0x7f,0x40,0x40,0x40,0x40, +0x7f,0x02,0x04,0x02,0x7f, +0x7f,0x02,0x04,0x08,0x7f, +0x3e,0x41,0x41,0x41,0x3e, +0x7f,0x09,0x09,0x09,0x06, +0x3e,0x41,0x51,0x21,0x5e, +0x7f,0x09,0x09,0x19,0x66, +0x26,0x49,0x49,0x49,0x32, +0x01,0x01,0x7f,0x01,0x01, +0x3f,0x40,0x40,0x40,0x3f, +0x1f,0x20,0x40,0x20,0x1f, +0x3f,0x40,0x3c,0x40,0x3f, +0x63,0x14,0x08,0x14,0x63, +0x07,0x08,0x70,0x08,0x07, +0x71,0x49,0x45,0x43,0x00, +0x00,0x7f,0x41,0x41,0x00, +0x02,0x04,0x08,0x10,0x20, +0x00,0x41,0x41,0x7f,0x00, +0x04,0x02,0x01,0x02,0x04, +0x80,0x80,0x80,0x80,0x80, +0x00,0x03,0x07,0x00,0x00, +0x20,0x54,0x54,0x54,0x78, +0x7f,0x44,0x44,0x44,0x38, +0x38,0x44,0x44,0x44,0x28, +0x38,0x44,0x44,0x44,0x7f, +0x38,0x54,0x54,0x54,0x08, +0x08,0x7e,0x09,0x09,0x00, +0x18,0xa4,0xa4,0xa4,0x7c, +0x7f,0x04,0x04,0x78,0x00, +0x00,0x00,0x7d,0x40,0x00, +0x40,0x80,0x84,0x7d,0x00, +0x7f,0x10,0x28,0x44,0x00, +0x00,0x00,0x7f,0x40,0x00, +0x7c,0x04,0x18,0x04,0x78, +0x7c,0x04,0x04,0x78,0x00, +0x38,0x44,0x44,0x44,0x38, +0xfc,0x44,0x44,0x44,0x38, +0x38,0x44,0x44,0x44,0xfc, +0x44,0x78,0x44,0x04,0x08, +0x08,0x54,0x54,0x54,0x20, +0x04,0x3e,0x44,0x24,0x00, +0x3c,0x40,0x20,0x7c,0x00, +0x1c,0x20,0x40,0x20,0x1c, +0x3c,0x60,0x30,0x60,0x3c, +0x6c,0x10,0x10,0x6c,0x00, +0x9c,0xa0,0x60,0x3c,0x00, +0x64,0x54,0x54,0x4c,0x00, +0x08,0x3e,0x41,0x41,0x00, +0x00,0x00,0x77,0x00,0x00, +0x00,0x41,0x41,0x3e,0x08, +0x02,0x01,0x02,0x01,0x00, +0x3c,0x26,0x23,0x26,0x3c}; + +static void obdCachedFlush(OBDISP *pOBD, int bRender) +{ + if (u8End > 0) { + obdWriteDataBlock(pOBD, u8Cache, u8End, bRender); + u8End = 0; + } +} /* obdCachedFlush() */ + +static void obdCachedWrite(OBDISP *pOBD, uint8_t *pData, uint8_t u8Len, int bRender) +{ + if (u8End + u8Len > MAX_CACHE) // need to flush it + { + obdCachedFlush(pOBD, bRender); // write the old data + } + memcpy(&u8Cache[u8End], pData, u8Len); + u8End = u8End + u8Len; + +} /* obdCachedWrite() */ +// +// Create a virtual display of any size +// The memory buffer must be provided at the time of creation +// +void obdCreateVirtualDisplay(OBDISP *pOBD, int width, int height, uint8_t *buffer) +{ + if (pOBD != NULL && buffer != NULL) + { + pOBD->width = width; + pOBD->height = height; + pOBD->type = LCD_VIRTUAL; + pOBD->ucScreen = buffer; + pOBD->iCursorX = pOBD->iCursorY = 0; + pOBD->iScreenOffset = 0; + } +} /* obdCreateVirtualDisplay() */ +// +// Scroll the internal buffer by 1 scanline (up/down) +// width is in pixels, lines is group of 8 rows +// +int obdScrollBuffer(OBDISP *pOBD, int iStartCol, int iEndCol, int iStartRow, int iEndRow, int bUp) +{ + uint8_t b, *s; + int col, row; + int iPitch; + + if (iStartCol < 0 || iStartCol >= pOBD->width || iEndCol < 0 || iEndCol > pOBD->width || iStartCol > iEndCol) // invalid + return OBD_ERROR_BAD_PARAMETER; + if (iStartRow < 0 || iStartRow >= (pOBD->height/8) || iEndRow < 0 || iEndRow >= (pOBD->height/8) || iStartRow > iEndRow) + return OBD_ERROR_BAD_PARAMETER; + iPitch = pOBD->width; + if (bUp) + { + for (row=iStartRow; row<=iEndRow; row++) + { + s = &pOBD->ucScreen[(row * iPitch) + iStartCol]; + for (col=iStartCol; col<=iEndCol; col++) + { + b = *s; + b >>= 1; // scroll pixels 'up' + if (row < iEndRow) + b |= (s[iPitch] << 7); // capture pixel of row below, except for last row + *s++ = b; + } // for col + } // for row + } // up + else // down + { + for (row=iEndRow; row>=iStartRow; row--) + { + s = &pOBD->ucScreen[(row * iPitch)+iStartCol]; + for (col=iStartCol; col<=iEndCol; col++) + { + b = *s; + b <<= 1; // scroll down + if (row > iStartRow) + b |= (s[-iPitch] >> 7); // capture pixel of row above + *s++ = b; + } // for col + } // for row + } + return OBD_SUCCESS; +} /* obdScrollBuffer() */ +// +// Return the number of bytes accumulated as commands +// +int obdGetCommandLen(OBDISP *pOBD) +{ + if (pOBD != NULL && pOBD->type == DISPLAY_COMMANDS) + return pOBD->iScreenOffset; + else + return 0; +} /* obdGetCommandLen() */ +// +// Write a single byte into the command queue +// +static void obdWriteCmdByte(OBDISP *pOBD, uint8_t u8) +{ +uint8_t *d = pOBD->ucScreen; +int i = pOBD->iScreenOffset; + + d[i++] = u8; + pOBD->iScreenOffset = i; + +} /* obdWriteCmdByte() */ +// +// Write an unsigned integer into the command queue +// use integer expansion to write small values as 1 byte +// and larger values as 2 bytes +// +static void obdWriteCmdInt(OBDISP *pOBD, int iValue) +{ +uint8_t *d = pOBD->ucScreen; +int i = pOBD->iScreenOffset; + + if (iValue <= 127) { + d[i++] = (uint8_t)iValue; + } else { + d[i++] = (uint8_t)(iValue | 0x80); // store lower 7 bits + d[i++] = (uint8_t)(iValue >> 7); // store next 8 bits + } + pOBD->iScreenOffset = i; + +} /* obdWriteCmdInt() */ +// +// Read an unsigned integer from the command queue +// use integer expansion to store small values as 1 byte +// and larger values as 2 bytes +// +static uint8_t * obdReadCmdInt(uint8_t *pData, int *iValue) +{ +int i; + + i = *pData++; + if (i & 0x80) { // two byte integer + i &= 0x7f; + i |= (pData[0] << 7); + pData++; + } + *iValue = i; + return pData; +} /* obdReadCmdInt() */ +// +// Set the current custom font pointers for playing back +// bytewise commands +// +void obdSetCustomFont(OBDISP *pOBD, GFXfont *pFont, uint8_t ucFont) +{ + if (pOBD != NULL && pFont != NULL && ucFont >= FONT_CUSTOM0 && ucFont <= FONT_CUSTOM2) { + pOBD->pFont[ucFont-FONT_CUSTOM0] = (void *)pFont; + } +} /* obdSetCustomFont() */ +// +// Execute a set of bytewise command bytes +// and execute the drawing instructions on the current display/buffer +// Optionally render on backbuffer or physical display +// +void obdExecCommands(uint8_t *pData, int iLen, OBDISP *pOBD, int bRender) +{ +uint8_t *s, *pEnd; +uint8_t uc, ucColor=1, ucFill, ucFont; +int x1, y1, x2, y2; +int iTextLen, iPitch; +uint8_t ucTemp[64]; + + if (pData == NULL || pOBD == NULL) return; + + s = pData; // source of the command data + pEnd = &s[iLen]; + while (s < pEnd-1) { + uc = *s++; + switch (uc & 0xf) { // lower 4 bits hold command + case OBD_FILL: + obdFill(pOBD, s[0], bRender); + s++; + break; + case OBD_DRAWTEXT: + iTextLen = *s++; + ucColor = (uc >> 7); // invert flag + ucFont = (uc >> 4) & 7; // font size + if (pEnd - s >= iTextLen+3) { + s = obdReadCmdInt(s, &x1); // col + s = obdReadCmdInt(s, &y1); // row + memcpy(ucTemp, s, iTextLen); + ucTemp[iTextLen] = 0; // terminate the string + s += iTextLen; + if (ucFont >= FONT_CUSTOM0) { // up to 3 custom fonts + if (pOBD->pFont[ucFont-FONT_CUSTOM0] != NULL) { + obdWriteStringCustom(pOBD, (GFXfont *)pOBD->pFont[ucFont-FONT_CUSTOM0], x1, y1, (char *)ucTemp, ucColor); + } + } else { + obdWriteString(pOBD, 0, x1, y1, (char *)ucTemp, ucFont, ucColor, bRender); + } + } else { + return; // something went wrong! + } + break; + case OBD_DRAWLINE: + if (pEnd - s >= 4) { + ucColor = (uc >> 4) & 1; + s = obdReadCmdInt(s, &x1); + s = obdReadCmdInt(s, &y1); + s = obdReadCmdInt(s, &x2); + s = obdReadCmdInt(s, &y2); + obdDrawLine(pOBD, x1, y1, x2, y2, ucColor, bRender); + } + break; + case OBD_DRAWRECT: + if (pEnd - s >= 4) { + ucColor = (uc >> 4) & 1; + ucFill = (uc >> 5) & 1; + s = obdReadCmdInt(s, &x1); + s = obdReadCmdInt(s, &y1); + s = obdReadCmdInt(s, &x2); + s = obdReadCmdInt(s, &y2); + obdRectangle(pOBD, x1, y1, x2, y2, ucColor, ucFill); + } + break; + case OBD_DRAWELLIPSE: + if (pEnd - s >= 4) { + ucColor = (uc >> 4) & 1; + ucFill = (uc >> 5) & 1; + s = obdReadCmdInt(s, &x1); + s = obdReadCmdInt(s, &y1); + s = obdReadCmdInt(s, &x2); + s = obdReadCmdInt(s, &y2); + obdEllipse(pOBD, x1, y1, x2, y2, ucColor, ucFill); + } + break; + case OBD_DRAWSPRITE: + if (pEnd - s >= 8) { + ucColor = (uc >> 4) & 1; + s = obdReadCmdInt(s, &x1); // width / height + s = obdReadCmdInt(s, &y1); + s = obdReadCmdInt(s, &x2); // destination x/y + s = obdReadCmdInt(s, &y2); + iPitch = (x1 + 7) >> 3; + if (pEnd - s >= (iPitch * y1)) { // enough to hold the data + obdDrawSprite(pOBD, s, x1, y1, iPitch, x2, y2, ucColor); + s += (iPitch * y1); + } else { + return; // error! + } + } + break; + default: + return; // invalid command! + } + } +} /* obdParseCommands() */ +// +// Draw a sprite of any size in any position +// If it goes beyond the left/right or top/bottom edges +// it's trimmed to show the valid parts +// This function requires a back buffer to be defined +// The priority color (0 or 1) determines which color is painted +// when a 1 is encountered in the source image. +// +void obdDrawSprite(OBDISP *pOBD, uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority) +{ + int tx, ty, dx, dy, iStartX; + uint8_t *s, *d, uc, pix, ucSrcMask, ucDstMask; + int iLocalPitch; + iLocalPitch = pOBD->width; + + if (pOBD == NULL) return; + if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence + int iLocalPitch = (cx+7)>>3; + obdWriteCmdByte(pOBD, OBD_DRAWSPRITE | ((iPriority & 1) << 4)); + obdWriteCmdInt(pOBD, cx); + obdWriteCmdInt(pOBD, cy); + obdWriteCmdInt(pOBD, x); + obdWriteCmdInt(pOBD, y); + d = pOBD->ucScreen; + tx = pOBD->iScreenOffset; + s = pSprite; + for (ty=0; tyiScreenOffset = tx; // store new length + return; // done + } + + if (x+cx < 0 || y+cy < 0 || x >= pOBD->width || y >= pOBD->height || pOBD->ucScreen == NULL) + return; // no backbuffer or out of bounds + dy = y; // destination y + if (y < 0) // skip the invisible parts + { + cy += y; + y = -y; + pSprite += (y * iPitch); + dy = 0; + } + if (y + cy > pOBD->height) + cy = pOBD->height - y; + iStartX = 0; + dx = x; + if (x < 0) + { + cx += x; + x = -x; + iStartX = x; + dx = 0; + } + if (x + cx > pOBD->width) + cx = pOBD->width - x; + for (ty=0; ty> 3)]; + d = &pOBD->ucScreen[(dy>>3) * iLocalPitch + dx]; + ucSrcMask = 0x80 >> (iStartX & 7); + pix = *s++; + ucDstMask = 1 << (dy & 7); + if (iPriority) // priority color is 1 + { + for (tx=0; tx>= 1; + if (ucSrcMask == 0) // read next byte + { + ucSrcMask = 0x80; + pix = *s++; + } + } // for tx + } // priorty color 1 + else + { + for (tx=0; tx>= 1; + if (ucSrcMask == 0) // read next byte + { + ucSrcMask = 0x80; + pix = *s++; + } + } // for tx + } // priority color 0 + dy++; + pSprite += iPitch; + } // for ty +} /* obdDrawSprite() */ +// +// Draw a 16x16 tile in any of 4 rotated positions +// Assumes input image is laid out like "normal" graphics with +// the MSB on the left and 2 bytes per line +// On AVR, the source image is assumed to be in FLASH memory +// The function can draw the tile on byte boundaries, so the x value +// can be from 0 to width-16 and y can be from 0 to (height/8)-2 +// +void obdDrawTile(OBDISP *pOBD, const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender) +{ + uint8_t ucTemp[32]; // prepare LCD data here + uint8_t i, j, k, iOffset, ucMask, uc, ucPixels; + uint8_t bFlipX=0, bFlipY=0; + + if (x < 0 || y < 0 || y > (pOBD->height/8)-2 || x > pOBD->width-16) + return; // out of bounds + if (pTile == NULL) return; // bad pointer; really? :( + if (iRotation == ANGLE_180 || iRotation == ANGLE_270 || iRotation == ANGLE_FLIPX) + bFlipX = 1; + if (iRotation == ANGLE_180 || iRotation == ANGLE_270 || iRotation == ANGLE_FLIPY) + bFlipY = 1; + + memset(ucTemp, 0, sizeof(ucTemp)); // we only set white pixels, so start from black + if (iRotation == ANGLE_0 || iRotation == ANGLE_180 || iRotation == ANGLE_FLIPX || iRotation == ANGLE_FLIPY) + { + for (j=0; j<16; j++) // y + { + for (i=0; i<16; i+=8) // x + { + ucPixels = pgm_read_byte((uint8_t*)pTile++); + ucMask = 0x80; // MSB is the first source pixel + for (k=0; k<8; k++) + { + if (ucPixels & ucMask) // translate the pixel + { + if (bFlipY) + uc = 0x80 >> (j & 7); + else + uc = 1 << (j & 7); + iOffset = i+k; + if (bFlipX) iOffset = 15-iOffset; + iOffset += (j & 8)<<1; // top/bottom half of output + if (bFlipY) + iOffset ^= 16; + ucTemp[iOffset] |= uc; + } + ucMask >>= 1; + } // for k + } // for i + } // for j + } + else // rotated 90/270 + { + for (j=0; j<16; j++) // y + { + for (i=0; i<16; i+=8) // x + { + ucPixels = pgm_read_byte((uint8_t*)pTile++); + ucMask = 0x80; // MSB is the first source pixel + for (k=0; k<8; k++) + { + if (ucPixels & ucMask) // translate the pixel + { + if (bFlipY) + uc = 0x80 >> k; + else + uc = 1 << k; + iOffset = 15-j; + if (bFlipX) iOffset = 15-iOffset; + iOffset += i<<1; // top/bottom half of output + if (bFlipY) + iOffset ^= 16; + ucTemp[iOffset] |= uc; + } + ucMask >>= 1; + } // for k + } // for i + } // for j + } + if (bInvert) InvertBytes(ucTemp, 32); + // Send the data to the display + obdSetPosition(pOBD, x, y, bRender); + obdWriteDataBlock(pOBD, ucTemp, 16, bRender); // top half + obdSetPosition(pOBD, x,y+1, bRender); + obdWriteDataBlock(pOBD, &ucTemp[16], 16, bRender); // bottom half +} /* obdDrawTile() */ + +// Set (or clear) an individual pixel +// The local copy of the frame buffer is used to avoid +// reading data from the display controller +int obdSetPixel(OBDISP *pOBD, int x, int y, unsigned char ucColor, int bRender) +{ +int i; +unsigned char uc, ucOld; +int iPitch, iSize; + + iPitch = pOBD->width; + iSize = iPitch * ((pOBD->height+7)/8); + + i = ((y >> 3) * iPitch) + x; + if (i < 0 || i > iSize-1) { // off the screen + return OBD_ERROR_BAD_PARAMETER; + } + // Special case for 4-color e-ink + if (pOBD->iFlags & OBD_4COLOR) { + uint8_t ucMask = (1 << (y & 7)); + ucColor ^= 1; // invert low bit + if (ucColor & 1) + pOBD->ucScreen[i] |= ucMask; + else + pOBD->ucScreen[i] &= ~ucMask; + if (ucColor & 2) + pOBD->ucScreen[iSize + i] |= ucMask; + else + pOBD->ucScreen[iSize + i] &= ~ucMask; + return OBD_SUCCESS; + } + // special case for 3-color e-ink + if (pOBD->iFlags & OBD_3COLOR) { + if (ucColor >= OBD_YELLOW) { // yellow/red has priority + pOBD->ucScreen[iSize + i] |= (1 << (y & 7)); + } else { + pOBD->ucScreen[iSize + i] &= ~(1 << (y & 7)); // clear red plane bit + if (ucColor == OBD_WHITE) { + pOBD->ucScreen[i] &= ~(1 << (y & 7)); + } else { // must be black + pOBD->ucScreen[i] |= (1 << (y & 7)); + } + } + return OBD_SUCCESS; + } + obdSetPosition(pOBD, x, y, bRender); + + if (pOBD->ucScreen) + uc = ucOld = pOBD->ucScreen[i]; +#ifndef MEMORY_ONLY + else if (pOBD->type == OLED_132x64 || pOBD->type == OLED_128x128) // SH1106/SH1107 can read data + { + uint8_t ucTemp[3]; + ucTemp[0] = 0x80; // one command + ucTemp[1] = 0xE0; // read_modify_write + ucTemp[2] = 0xC0; // one data + RawWrite(pOBD, ucTemp, 3); + + // read a dummy byte followed by the data byte we want + I2CRead(&pOBD->bbi2c, pOBD->oled_addr, ucTemp, 2); + uc = ucOld = ucTemp[1]; // first byte is garbage + } +#endif // MEMORY_ONLY + else + uc = ucOld = 0; + + uc &= ~(0x1 << (y & 7)); + if (ucColor) + { + uc |= (0x1 << (y & 7)); + } + if (uc != ucOld) // pixel changed + { +// obdSetPosition(x, y>>3); + if (pOBD->ucScreen) + { + obdWriteDataBlock(pOBD, &uc, 1, bRender); + pOBD->ucScreen[i] = uc; + } + else if (pOBD->type == OLED_132x64 || pOBD->type == OLED_128x128) // end the read_modify_write operation + { + uint8_t ucTemp[4]; + ucTemp[0] = 0xc0; // one data + ucTemp[1] = uc; // actual data + ucTemp[2] = 0x80; // one command + ucTemp[3] = 0xEE; // end read_modify_write operation + RawWrite(pOBD, ucTemp, 4); + } + } + return OBD_ERROR_BAD_PARAMETER; +} /* obdSetPixel() */ + +// +// Invert font data +// +void InvertBytes(uint8_t *pData, uint8_t bLen) +{ +uint8_t i; + for (i=0; iucScreen == NULL) { + // no BG specified or no back buffer, BG color is opposite of FG + if (iFG == -1) iFG = OBD_WHITE; + if (iBG == -1) iBG = OBD_BLACK; + } + // Don't use pgm_read_word because it can cause an unaligned + // access on RP2040 for odd addresses + i16 = pgm_read_byte(pBMP); + i16 += (pgm_read_byte(&pBMP[1]) << 8); + if (i16 != 0x4d42) // must start with 'BM' + return OBD_ERROR_BAD_DATA; // not a BMP file + cx = pgm_read_byte(pBMP + 18); + cx += (pgm_read_byte(pBMP+19)<<8); + if (cx + dx > pOBD->width) // must fit on the display + return OBD_ERROR_BAD_PARAMETER; + cy = pgm_read_byte(pBMP + 22); + cy += (pgm_read_byte(pBMP+23)<<8); + if (cy < 0) cy = -cy; + else bFlipped = 1; + if (cy + dy > pOBD->height) // must fit on the display + return OBD_ERROR_BAD_PARAMETER; + i16 = pgm_read_byte(pBMP + 28); + i16 += (pgm_read_byte(pBMP+29)<<8); + if (i16 != 1) // must be 1 bit per pixel + return OBD_ERROR_BAD_DATA; + iOffBits = pgm_read_byte(pBMP + 10); + iOffBits += (pgm_read_byte(pBMP+11)); + iPitch = (((cx+7)>>3) + 3) & 0xfffc; // must be DWORD aligned + if (bFlipped) + { + iOffBits += ((cy-1) * iPitch); // start from bottom + iPitch = -iPitch; + } + ucFill = (iBG == OBD_WHITE && pOBD->type < EPD42_400x300) ? iBG : 0xff; + if (!pOBD->ucScreen || iFG >= OBD_YELLOW) { // this will override the B/W plane, so invert things + ucFill = 0x00; + x = iFG; + iFG = iBG; + iBG = x; // swap colors + } + for (y=0; yucScreen) + { + dst_mask = (1 << (y & 7)); + d = u8Cache; + if ((y & 7) == 0) + memset(u8Cache, ucFill, sizeof(u8Cache)); + } + s = (uint8_t *)&pBMP[iOffBits + (y*iPitch)]; + src_mask = 0; + if (!pOBD->ucScreen) // direct to display + { + for (x=0; x>= 1; + } // for x + } else { // use the setPixel function for more features + for (x=0; x= 0) + obdSetPixel(pOBD, dx+x, dy+y, (uint8_t)iFG, 0); + } else { + if (iBG >= 0) + obdSetPixel(pOBD, dx+x, dy+y, (uint8_t)iBG, 0); + } + src_mask >>= 1; + } // for x + } + if (pOBD->ucScreen == NULL && ((y & 7) == 7 || y == cy-1)) // dump to LCD + { + obdSetPosition(pOBD, dx, y+dy, 1); + obdWriteDataBlock(pOBD, u8Cache, cx, 1); + } + } // for y + return OBD_SUCCESS; +} /* obdLoadBMP() */ +// +// Load a 4-bpp Windows bitmap for a 3-color bitmap +// Pass the pointer to the beginning of the BMP file +// First pass version assumes a full screen bitmap +// +int obdLoadBMP3(OBDISP *pOBD, const uint8_t *pBMP, int dx, int dy) +{ +int16_t i16, cx, cy, bpp; +int x, y, iOffBits; // offset to bitmap data +int iPitch, iDestPitch; +int iRedOff, iColors, iPalOff; +uint8_t uc, b, *s, *d; +uint8_t dst_mask; +uint8_t bFlipped = 0; +uint8_t ucColorMap[16]; + + if (!(pOBD->iFlags & OBD_3COLOR) || pOBD->ucScreen == 0) + return OBD_ERROR_NOT_SUPPORTED; // if not 3-color EPD or no back buffer, bye-byte + iDestPitch = pOBD->width; + iRedOff = ((pOBD->height+7)>>3) * iDestPitch; + // Need to avoid pgm_read_word because it can cause an + // unaligned address exception on the RP2040 for odd addresses + i16 = pgm_read_byte(pBMP); + i16 += (pgm_read_byte(pBMP+1)<<8); + if (i16 != 0x4d42) // must start with 'BM' + return OBD_ERROR_BAD_DATA; // not a BMP file + cx = pgm_read_byte(&pBMP[18]); + cx += (pgm_read_byte(&pBMP[19]) << 8); + if (cx + dx > pOBD->width) // must fit on the display + return OBD_ERROR_BAD_PARAMETER; + cy = pgm_read_byte(&pBMP[22]); + cy += (pgm_read_byte(&pBMP[23])<<8); + if (cy < 0) + cy = -cy; + else + bFlipped = 1; + if (cy + dy > pOBD->height) // must fit on the display + return OBD_ERROR_BAD_PARAMETER; + if (pgm_read_byte(&pBMP[30]) != 0) // compression must be NONE + return OBD_ERROR_BAD_DATA; + bpp = pgm_read_byte(&pBMP[28]); + if (bpp != 4) // must be 4 bits per pixel + return OBD_ERROR_BAD_DATA; + iOffBits = pgm_read_byte(&pBMP[10]); + iOffBits += (pgm_read_byte(&pBMP[11])<<8); + iColors = pgm_read_byte(&pBMP[46]); // colors used BMP field + if (iColors == 0 || iColors > (1<>1) + 3) & 0xfffc; // must be DWORD aligned + if (bFlipped) + { + iOffBits += ((cy-1) * iPitch); // start from bottom + iPitch = -iPitch; + } +// Map the colors to white/black/red with a simple quantization. Convert colors to G3R3B2 and find the closest value (red in the middle) + // white = 0xff, red = 0x1c, black = 0x00 + for (x=0; x> 6) | ((r >> 5) << 2) | ((g >> 5) << 5); + if (uc >= 0x1c) { // check for red/white + ucColorMap[x] = ((0xff - uc) < (uc - 0x1c)) ? OBD_WHITE : OBD_RED; + } else { + ucColorMap[x] = ((0x1c - uc) < uc) ? OBD_RED : OBD_BLACK; + } + } + for (y=0; yucScreen[(((y+dy)>>3)*iDestPitch)+dx]; + s = (uint8_t *)&pBMP[iOffBits+(y*iPitch)]; + for (x=0; x> 4]; // left pixel + if (uc == OBD_BLACK) + d[x] |= dst_mask; + else if (uc == OBD_RED) + d[x+iRedOff] |= dst_mask; // we made it white already + uc = ucColorMap[b & 0xf]; // right pixel + if (uc == OBD_BLACK) + d[x+1] |= dst_mask; + else if (uc == OBD_RED) + d[x+1+iRedOff] |= dst_mask; + } // for x + } // for y + return OBD_SUCCESS; +} /* obdLoadBMP3() */ +// +// Draw 1 or 2 planes of raw image into a specific spot +// in e-paper memory +// +int obdDrawEPDGFX(OBDISP *pOBD, int xdest, int ydest, int cx, int cy, uint8_t *pPlane0, uint8_t *pPlane1) +{ + int y, iPitch = ((cx + 7)/8); + uint8_t *s; + + if (xdest < 0 || xdest >= pOBD->native_width || xdest+cx > pOBD->native_width || ydest < 0 || ydest > pOBD->native_height || ydest+cy > pOBD->native_height || pPlane0 == NULL) + return OBD_ERROR_BAD_PARAMETER; // invalid + // Write plane 0 + pOBD->iFG = OBD_BLACK; // make sure we write to plane 0 + EPDSetPosition(pOBD, xdest, ydest, cx, cy); + s = pPlane0; + for (y=0; yiFG = OBD_RED; // write to plane 1 + EPDSetPosition(pOBD, xdest, ydest, cx, cy); + s = pPlane1; + for (y=0; yiCursorX = x; + pOBD->iCursorY = y; +} /* obdSetCursor() */ +// +// Advance to the next line +// +void obdNextLine(OBDISP *pOBD) +{ + pOBD->iCursorX = 0; + pOBD->iCursorY++; +} /* obdNextLine() */ +// +// Turn text wrap on or off for the oldWriteString() function +// +void obdSetTextWrap(OBDISP *pOBD, int bWrap) +{ + pOBD->wrap = bWrap; +} /* obdSetTextWrap() */ +// +// Draw a string with a fractional scale in both dimensions +// the scale is a 16-bit integer with and 8-bit fraction and 8-bit mantissa +// To draw at 1x scale, set the scale factor to 256. To draw at 2x, use 512 +// The output must be drawn into a memory buffer, not directly to the display +// +int obdScaledString(OBDISP *pOBD, int x, int y, char *szMsg, int iSize, int iColor, int iXScale, int iYScale, int iRotation) +{ +uint32_t row, col, dx, dy; +uint32_t sx, sy; +uint8_t c, uc, color; +const uint8_t *s; +uint8_t ucTemp[16]; +int tx, ty, bit, iFontOff; +int iFontWidth; + + if (iXScale == 0 || iYScale == 0 || szMsg == NULL || pOBD == NULL || pOBD->ucScreen == NULL || x < 0 || y < 0 || x >= pOBD->width-1 || y >= pOBD->height-1) + return OBD_ERROR_BAD_PARAMETER; // invalid display structure + if (iSize != FONT_8x8 && iSize != FONT_6x8) + return OBD_ERROR_BAD_PARAMETER; // only on the small fonts (for now) + iFontWidth = (iSize == FONT_6x8) ? 6:8; + s = (iSize == FONT_6x8) ? ucSmallFont : ucFont; + dx = (iFontWidth * iXScale) >> 8; // width of each character + dy = (8 * iYScale) >> 8; // height of each character + sx = 65536 / iXScale; // turn the scale into an accumulator value + sy = 65536 / iYScale; + while (*szMsg) { + c = *szMsg++; // debug - start with normal font + iFontOff = (int)(c-32) * (iFontWidth-1); + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + ucTemp[0] = 0; // first column is blank + memcpy_P(&ucTemp[1], &s[iFontOff], iFontWidth-1); +// if (iColor == OBD_WHITE) InvertBytes(ucTemp, iFontWidth); + col = 0; + for (tx=0; tx<(int)dx; tx++) { + row = 0; + uc = ucTemp[col >> 8]; + for (ty=0; ty<(int)dy; ty++) { + int nx=0, ny=0; + bit = row >> 8; + color = (uc & (1 << bit)); // set or clear the pixel + switch (iRotation) { + case ROT_0: + nx = x + tx; + ny = y + ty; + break; + case ROT_90: + nx = x - ty; + ny = y + tx; + break; + case ROT_180: + nx = x - tx; + ny = y - ty; + break; + case ROT_270: + nx = x + ty; + ny = y - tx; + break; + } // switch on rotation direction + // plot the pixel if it's within the image boundaries + if (color) + obdSetPixel(pOBD, nx, ny, pOBD->iFG, iColor); + row += sy; // add fractional increment to source row of character + } // for ty + col += sx; // add fractional increment to source column + } // for tx + // update the 'cursor' position + switch (iRotation) { + case ROT_0: + x += dx; + break; + case ROT_90: + y += dx; + break; + case ROT_180: + x -= dx; + break; + case ROT_270: + y -= dx; + break; + } // switch on rotation + } // while (*szMsg) + return OBD_SUCCESS; +} /* obdScaledString() */ + +// +// Draw a string of normal (8x8), small (6x8) or large (16x32) characters +// At the given col+row +// +int obdWriteString(OBDISP *pOBD, int iScroll, int x, int y, char *szMsg, int iSize, int iColor, int bRender) +{ +int i, iFontOff, iLen, iFontSkip; +unsigned char c, *s; +int iOldFG; // old fg color to make sure red works + + if (pOBD == NULL) return OBD_ERROR_BAD_PARAMETER; + if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence + uint8_t *d = pOBD->ucScreen; + iLen = (int)strlen(szMsg); + obdWriteCmdByte(pOBD, OBD_DRAWTEXT | ((iColor & 1) << 7) | ((iSize & 7) << 4)); + obdWriteCmdByte(pOBD, (uint8_t) iLen); + obdWriteCmdInt(pOBD, x); + obdWriteCmdInt(pOBD, y); + i = pOBD->iScreenOffset; + memcpy(&d[i], szMsg, iLen); + i += iLen; + pOBD->iScreenOffset = i; // store new length + return OBD_SUCCESS; // done + } + + // e-paper color is inverted compared to OLED/LCD. If we're in bufferless mode, we'll need to + // invert the requested color + if (pOBD->type >= EPD42_400x300 && !pOBD->ucScreen) { + iColor = 1 - iColor; // invert the color + } + if (x == -1 || y == -1) // use the cursor position + { + x = pOBD->iCursorX; y = pOBD->iCursorY; + } else { + pOBD->iCursorX = x; pOBD->iCursorY = y; + } + if (pOBD->iCursorX >= pOBD->width || pOBD->iCursorY >= pOBD->height) + return OBD_ERROR_BAD_PARAMETER; // can't draw off the display + + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + iOldFG = pOBD->iFG; // save old fg color + if (iColor >= OBD_YELLOW) { + pOBD->iFG = iColor; + } + if (iSize == FONT_8x8) // 8x8 font + { + i = 0; + iFontSkip = iScroll & 7; // number of columns to initially skip + while (pOBD->iCursorX < pOBD->width && szMsg[i] != 0 && pOBD->iCursorY < pOBD->height) + { + if (iScroll < 8) // only display visible characters + { + c = (unsigned char)szMsg[i]; + iFontOff = (int)(c-32) * 7; + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + u8Temp[0] = 0; // first column is blank + memcpy_P(&u8Temp[1], &ucFont[iFontOff], 7); + if (iColor == OBD_WHITE) InvertBytes(u8Temp, 8); + iLen = 8 - iFontSkip; + if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge + iLen = pOBD->width - pOBD->iCursorX; + obdCachedWrite(pOBD, &u8Temp[iFontSkip], iLen, bRender); +// obdWriteDataBlock(pOBD, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + pOBD->iCursorX += iLen; + if (pOBD->iCursorX >= pOBD->width-7 && pOBD->wrap) // word wrap enabled? + { + obdCachedFlush(pOBD, bRender); + pOBD->iCursorX = 0; // start at the beginning of the next line + pOBD->iCursorY+=8; + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + } + iFontSkip = 0; + } + iScroll -= 8; + i++; + } // while + obdCachedFlush(pOBD, bRender); // write any remaining data + pOBD->iFG = iOldFG; // restore color + return OBD_SUCCESS; + } // 8x8 +#ifndef WIMPY_MCU + else if (iSize == FONT_16x32) + { + i = 0; + iFontSkip = iScroll & 15; // number of columns to initially skip + while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) + { + if (iScroll < 16) // if characters are visible + { + s = (unsigned char *)&ucBigFont[(unsigned char)(szMsg[i]-32)*64]; + iLen = 16 - iFontSkip; + if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge + iLen = pOBD->width - pOBD->iCursorX; + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + memcpy_P(u8Cache, s, 16); + if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); + obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+8, bRender); + memcpy_P(u8Cache, s+16, 16); + if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); + obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+16, bRender); + memcpy_P(u8Cache, s+32, 16); + if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); + obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+24, bRender); + memcpy_P(u8Cache, s+48, 16); + if (iColor == OBD_WHITE) InvertBytes(u8Cache, 16); + obdWriteDataBlock(pOBD, &u8Cache[iFontSkip], iLen, bRender); // write character pattern + pOBD->iCursorX += iLen; + if (pOBD->iCursorX >= pOBD->width-15 && pOBD->wrap) // word wrap enabled? + { + pOBD->iCursorX = 0; // start at the beginning of the next line + pOBD->iCursorY+=32; + } + iFontSkip = 0; + } // if character visible from scrolling + iScroll -= 16; + i++; + } // while + pOBD->iFG = iOldFG; // restore color + return OBD_SUCCESS; + } // 16x32 +#endif // !WIMPY_MCU + else if (iSize == FONT_16x16) // 8x8 stretched to 16x16 + { + i = 0; + iFontSkip = iScroll & 15; // number of columns to initially skip + while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) + { +// stretch the 'normal' font instead of using the big font + if (iScroll < 16) // if characters are visible + { + int tx, ty; + c = szMsg[i] - 32; + unsigned char uc1, uc2, ucMask, *pDest; + s = (unsigned char *)&ucFont[(int)c*7]; + u8Temp[0] = 0; // first column is blank + memcpy_P(&u8Temp[1], s, 7); + if (iColor == OBD_WHITE) + InvertBytes(u8Temp, 8); + // Stretch the font to double width + double height + memset(&u8Temp[8], 0, 32); // write 32 new bytes + for (tx=0; tx<8; tx++) + { + ucMask = 3; + pDest = &u8Temp[8+tx*2]; + uc1 = uc2 = 0; + c = u8Temp[tx]; + for (ty=0; ty<4; ty++) + { + if (c & (1 << ty)) // a bit is set + uc1 |= ucMask; + if (c & (1 << (ty + 4))) + uc2 |= ucMask; + ucMask <<= 2; + } + pDest[0] = uc1; + pDest[1] = uc1; // double width + pDest[16] = uc2; + pDest[17] = uc2; + } + iLen = 16 - iFontSkip; + if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge + iLen = pOBD->width - pOBD->iCursorX; + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + obdWriteDataBlock(pOBD, &u8Temp[8+iFontSkip], iLen, bRender); + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+8, bRender); + obdWriteDataBlock(pOBD, &u8Temp[24+iFontSkip], iLen, bRender); + pOBD->iCursorX += iLen; + if (pOBD->iCursorX >= pOBD->width-15 && pOBD->wrap) // word wrap enabled? + { + pOBD->iCursorX = 0; // start at the beginning of the next line + pOBD->iCursorY += 16; + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + } + iFontSkip = 0; + } // if characters are visible + iScroll -= 16; + i++; + } // while + pOBD->iFG = iOldFG; // restore color + return OBD_SUCCESS; + } // 16x16 + else if (iSize == FONT_12x16) // 6x8 stretched to 12x16 + { + i = 0; + iFontSkip = iScroll % 12; // number of columns to initially skip + while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) + { +// stretch the 'normal' font instead of using the big font + if (iScroll < 12) // if characters are visible + { + int tx, ty; + c = szMsg[i] - 32; + unsigned char uc1, uc2, ucMask, *pDest; + s = (unsigned char *)&ucSmallFont[(int)c*5]; + u8Temp[0] = 0; // first column is blank + memcpy_P(&u8Temp[1], s, 6); + if (iColor == OBD_WHITE) + InvertBytes(u8Temp, 6); + // Stretch the font to double width + double height + memset(&u8Temp[6], 0, 24); // write 24 new bytes + for (tx=0; tx<6; tx++) + { + ucMask = 3; + pDest = &u8Temp[6+tx*2]; + uc1 = uc2 = 0; + c = u8Temp[tx]; + for (ty=0; ty<4; ty++) + { + if (c & (1 << ty)) // a bit is set + uc1 |= ucMask; + if (c & (1 << (ty + 4))) + uc2 |= ucMask; + ucMask <<= 2; + } + pDest[0] = uc1; + pDest[1] = uc1; // double width + pDest[12] = uc2; + pDest[13] = uc2; + } + // smooth the diagonal lines + for (tx=0; tx<5; tx++) + { + uint8_t c0, c1, ucMask2; + c0 = u8Temp[tx]; + c1 = u8Temp[tx+1]; + pDest = &u8Temp[6+tx*2]; + ucMask = 1; + ucMask2 = 2; + for (ty=0; ty<7; ty++) + { + if (((c0 & ucMask) && !(c1 & ucMask) && !(c0 & ucMask2) && (c1 & ucMask2)) || (!(c0 & ucMask) && (c1 & ucMask) && (c0 & ucMask2) && !(c1 & ucMask2))) + { + if (ty < 3) // top half + { + if (iColor == OBD_WHITE) { + pDest[1] &= ~(1 << ((ty * 2)+1)); + pDest[2] &= ~(1 << ((ty * 2)+1)); + pDest[1] &= ~(1 << ((ty+1) * 2)); + pDest[2] &= ~(1 << ((ty+1) * 2)); + } else { + pDest[1] |= (1 << ((ty * 2)+1)); + pDest[2] |= (1 << ((ty * 2)+1)); + pDest[1] |= (1 << ((ty+1) * 2)); + pDest[2] |= (1 << ((ty+1) * 2)); + } + } + else if (ty == 3) // on the border + { + if (iColor == OBD_WHITE) { + pDest[1] &= ~0x80; pDest[2] &= ~0x80; + pDest[13] &= ~1; pDest[14] &= ~1; + } else { + pDest[1] |= 0x80; pDest[2] |= 0x80; + pDest[13] |= 1; pDest[14] |= 1; + } + } + else // bottom half + { + if (iColor == OBD_WHITE) { + pDest[13] &= ~(1 << (2*(ty-4)+1)); + pDest[14] &= ~(1 << (2*(ty-4)+1)); + pDest[13] &= ~(1 << ((ty-3) * 2)); + pDest[14] &= ~(1 << ((ty-3) * 2)); + } else { + pDest[13] |= (1 << (2*(ty-4)+1)); + pDest[14] |= (1 << (2*(ty-4)+1)); + pDest[13] |= (1 << ((ty-3) * 2)); + pDest[14] |= (1 << ((ty-3) * 2)); + } + } + } + else if (!(c0 & ucMask) && (c1 & ucMask) && (c0 & ucMask2) && !(c1 & ucMask2)) + { + if (ty < 4) // top half + { + if (iColor == OBD_WHITE) { + pDest[1] &= ~(1 << ((ty * 2)+1)); + pDest[2] &= ~(1 << ((ty+1) * 2)); + } else { + pDest[1] |= (1 << ((ty * 2)+1)); + pDest[2] |= (1 << ((ty+1) * 2)); + } + } + else + { + if (iColor == OBD_WHITE) { + pDest[13] &= ~(1 << (2*(ty-4)+1)); + pDest[14] &= ~(1 << ((ty-3) * 2)); + } else { + pDest[13] |= (1 << (2*(ty-4)+1)); + pDest[14] |= (1 << ((ty-3) * 2)); + } + } + } + ucMask <<= 1; ucMask2 <<= 1; + } + } + iLen = 12 - iFontSkip; + if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge + iLen = pOBD->width - pOBD->iCursorX; + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + obdWriteDataBlock(pOBD, &u8Temp[6+iFontSkip], iLen, bRender); + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY+8, bRender); + obdWriteDataBlock(pOBD, &u8Temp[18+iFontSkip], iLen, bRender); + pOBD->iCursorX += iLen; + if (pOBD->iCursorX >= pOBD->width-11 && pOBD->wrap) // word wrap enabled? + { + pOBD->iCursorX = 0; // start at the beginning of the next line + pOBD->iCursorY += 16; + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + } + iFontSkip = 0; + } // if characters are visible + iScroll -= 12; + i++; + } // while + pOBD->iFG = iOldFG; // restore color + return OBD_SUCCESS; + } // 12x16 + else if (iSize == FONT_6x8) + { + i = 0; + iFontSkip = iScroll % 6; + while (pOBD->iCursorX < pOBD->width && pOBD->iCursorY < pOBD->height && szMsg[i] != 0) + { + if (iScroll < 6) // if characters are visible + { + c = szMsg[i] - 32; + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + u8Temp[0] = 0; // first column is blank + memcpy_P(&u8Temp[1], &ucSmallFont[(int)c*5], 5); + if (iColor == OBD_WHITE) InvertBytes(u8Temp, 6); + iLen = 6 - iFontSkip; + if (pOBD->iCursorX + iLen > pOBD->width) // clip right edge + iLen = pOBD->width - pOBD->iCursorX; + obdCachedWrite(pOBD, &u8Temp[iFontSkip], iLen, bRender); +// obdWriteDataBlock(pOBD, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + pOBD->iCursorX += iLen; + iFontSkip = 0; + if (pOBD->iCursorX >= pOBD->width-5 && pOBD->wrap) // word wrap enabled? + { + obdCachedFlush(pOBD, bRender); + pOBD->iCursorX = 0; // start at the beginning of the next line + pOBD->iCursorY +=8; + obdSetPosition(pOBD, pOBD->iCursorX, pOBD->iCursorY, bRender); + } + } // if characters are visible + iScroll -= 6; + i++; + } + obdCachedFlush(pOBD, bRender); // write any remaining data + pOBD->iFG = iOldFG; // restore color + return OBD_SUCCESS; + } // 6x8 + pOBD->iFG = iOldFG; // restore color + return OBD_ERROR_BAD_PARAMETER; // invalid size +} /* obdWriteString() */ +// +// Get the width of text in a custom font +// +void obdGetStringBox(GFXfont *pFont, char *szMsg, int *width, int *top, int *bottom) +{ +int cx = 0; +unsigned int c, i = 0; +GFXglyph *pGlyph; +int miny, maxy; + + if (width == NULL || top == NULL || bottom == NULL || pFont == NULL || szMsg == NULL) return; // bad pointers + miny = 100; maxy = 0; + while (szMsg[i]) { + c = szMsg[i++]; + if (c < pFont->first || c > pFont->last) // undefined character + continue; // skip it + c -= pFont->first; // first char of font defined + pGlyph = &pFont->glyph[c]; + cx += pGlyph->xAdvance; + if (pGlyph->yOffset < miny) miny = pGlyph->yOffset; + if (pGlyph->height+pGlyph->yOffset > maxy) maxy = pGlyph->height+pGlyph->yOffset; + } + *width = cx; + *top = miny; + *bottom = maxy; +} /* obdGetStringBox() */ + +// +// Draw a string of characters in a custom font +// A back buffer must be defined +// +int obdWriteStringCustom(OBDISP *pOBD, GFXfont *pFont, int x, int y, char *szMsg, uint8_t ucColor) +{ +int i, end_y, dx, dy, tx, ty, iBitOff; +unsigned int c; +uint8_t *s, *d, bits, ucFill=0, ucMask, uc; +GFXfont font; +GFXglyph glyph, *pGlyph; +int iPitch, iRedOffset = 0; + + if (pOBD == NULL || pFont == NULL) + return OBD_ERROR_BAD_PARAMETER; + if (pOBD->ucScreen == NULL && pOBD->type >= EPD42_400x300) { + // EPD direct draw mode; colors are inverted + if (ucColor == OBD_BLACK) + ucColor = 1-ucColor; + ucFill = 0xff; + } + if (x == -1) + x = pOBD->iCursorX; + if (y == -1) + y = pOBD->iCursorY; + if (ucColor >= OBD_YELLOW && pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) { + // use the second half of the image buffer + iRedOffset = pOBD->width * ((pOBD->height+7)/8); + ucFill = 0x00; + ucColor = OBD_BLACK; + } + if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence + uint8_t *d = pOBD->ucScreen; + dx = (int)strlen(szMsg); + // The font pointer is really the integer font index + i = (int)pFont; + if (i >= FONT_CUSTOM0) { // must be a valid index + obdWriteCmdByte(pOBD, OBD_DRAWTEXT | ((ucColor & 1) << 7) | ((i & 7) << 4)); + obdWriteCmdByte(pOBD, (uint8_t) dx); + obdWriteCmdInt(pOBD, x); + obdWriteCmdInt(pOBD, y); + i = pOBD->iScreenOffset; + memcpy(&d[i], szMsg, dx); + i += dx; + pOBD->iScreenOffset = i; // store new length + } + return OBD_SUCCESS; // done + } + iPitch = pOBD->width; + // in case of running on Harvard architecture, get copy of data from FLASH + memcpy_P(&font, pFont, sizeof(font)); + pGlyph = &glyph; + + i = 0; + while (szMsg[i] && x < pOBD->width) + { + c = szMsg[i++]; + if (c < font.first || c > font.last) // undefined character + continue; // skip it + c -= font.first; // first char of font defined + memcpy_P(&glyph, &font.glyph[c], sizeof(glyph)); + dx = x + pGlyph->xOffset; // offset from character UL to start drawing + dy = y + pGlyph->yOffset; + s = font.bitmap + pGlyph->bitmapOffset; // start of bitmap data + // Bitmap drawing loop. Image is MSB first and each pixel is packed next + // to the next (continuing on to the next character line) + iBitOff = 0; // bitmap offset (in bits) + bits = uc = 0; // bits left in this font byte + end_y = dy + pGlyph->height; + if (dy < 0) { // skip these lines + iBitOff += (pGlyph->width * (-dy)); + dy = 0; + } + if (!pOBD->ucScreen) { + memset(u8Cache, ucFill, sizeof(u8Cache)); + } + for (ty=dy; tyheight; ty++) { + ucMask = 1<<(ty & 7); // destination bit number for this line + if (pOBD->ucScreen) { + d = &pOBD->ucScreen[iRedOffset + (ty >> 3) * iPitch + dx]; // internal buffer dest + } else { + d = u8Cache; // no ram; buffer 8 lines at a time + } + for (tx=0; txwidth; tx++) { + if (bits == 0) { // need to read more font data + uc = pgm_read_byte(&s[iBitOff>>3]); // get more font bitmap data + bits = 8 - (iBitOff & 7); // we might not be on a byte boundary + iBitOff += bits; // because of a clipped line + uc <<= (8-bits); + } // if we ran out of bits + if ((dx+tx) < pOBD->width) { // foreground pixel + if (uc & 0x80) { + if (ucColor == OBD_BLACK) + d[tx] |= ucMask; + else + d[tx] &= ~ucMask; + } else { + if (ucColor == OBD_BLACK) + d[tx] &= ~ucMask; + else + d[tx] |= ucMask; + } + } + bits--; // next bit + uc <<= 1; + } // for x + if (!pOBD->ucScreen && (ucMask == 0x80 || ty == end_y-1)) { // dump this line + obdSetPosition(pOBD, dx, (ty & 0xfff8), 1); + obdWriteDataBlock(pOBD, u8Cache, pGlyph->width, 1); + memset(u8Cache, ucFill, sizeof(u8Cache)); // NB: assume no DMA + } + } // for y + x += pGlyph->xAdvance; // width of this character + } // while drawing characters + pOBD->iCursorX = x; + pOBD->iCursorY = y; + return OBD_SUCCESS; +} /* obdWriteStringCustom() */ + +// +// Render a sprite/rectangle of pixels from a provided buffer to the display. +// The row values refer to byte rows, not pixel rows due to the memory +// layout of OLEDs and LCDs. +// returns 0 for success, -1 for invalid parameter +// +int obdDrawGFX(OBDISP *pOBD, uint8_t *pBuffer, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch) +{ + int y; + + if (iSrcCol < 0 || iSrcCol >= pOBD->width || iSrcRow < 0 || iSrcRow > (pOBD->height/8)-1 || iDestCol < 0 || iDestCol >= pOBD->width || iDestRow < 0 || iDestRow >= (pOBD->height >> 3) || iSrcPitch <= 0) + return OBD_ERROR_BAD_PARAMETER; // invalid + + for (y=iSrcRow; ytype == DISPLAY_COMMANDS) { // encode this as a command sequence + obdWriteCmdByte(pOBD, OBD_FILL); + obdWriteCmdByte(pOBD, ucData); + return; + } + + pOBD->iCursorX = pOBD->iCursorY = 0; + if (pOBD->type == LCD_VIRTUAL || pOBD->type >= SHARP_144x168) // special display types + { + if (pOBD->ucScreen) { + int iSize = pOBD->width * ((pOBD->height+7)/8); + if (ucData == OBD_WHITE) { + memset(pOBD->ucScreen, ucData, iSize); + if (pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) + memset(&pOBD->ucScreen[iSize], ucData, iSize); + } else if (ucData == OBD_BLACK) { + memset(pOBD->ucScreen, 0xff, iSize); + if (pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) + memset(&pOBD->ucScreen[iSize], 0, iSize); + } else if (ucData == OBD_RED) { + memset(pOBD->ucScreen, 0, iSize); + if (pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) + memset(&pOBD->ucScreen[iSize], 0xff, iSize); + } else { // write pattern + if (pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) iSize *= 2; + memset(pOBD->ucScreen, ucData, iSize); + } + } +#ifndef MEMORY_ONLY + else if (pOBD->type >= EPD42_400x300) { + uint8_t ucPattern1 = 0xff, ucPattern2 = 0xff; // assume white + uint8_t ucRAM1, ucRAM2; + int iOldRotation; + if (pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) { + ucPattern2 = 0x00; // red plane is not inverted + if (ucData == OBD_BLACK) + ucPattern1 = 0x00; + else if (ucData == OBD_RED) + ucPattern2 = 0xff; + } else { // black/white + if (ucData == OBD_BLACK) + ucPattern1 = ucPattern2 = 0x00; + } + ucRAM1 = (pOBD->chip_type == OBD_CHIP_UC8151) ? (uint8_t)UC8151_DTM1 : (uint8_t)SSD1608_WRITE_RAM; + ucRAM2 = (pOBD->chip_type == OBD_CHIP_UC8151) ? (uint8_t)UC8151_DTM2 : (uint8_t)SSD1608_WRITE_ALTRAM; + // Force 0 rotation because bufferless operation + // will miss pixel rows not a multiple of 8 + // when rotated 90 (e.g. 122x250 resolution) + iOldRotation = pOBD->iOrientation; + pOBD->iOrientation = 0; + EPDSetPosition(pOBD, 0,0,pOBD->native_width, pOBD->native_height); + if (pOBD->type == EPD579_792x272) { + EPDFill(pOBD, ucRAM1, ucPattern1); + EPDFill(pOBD, ucRAM2, ~ucPattern1); + EPDFill(pOBD, ucRAM1 | 0x80, ucPattern1); + EPDFill(pOBD, ucRAM2 | 0x80, ~ucPattern1); + } else if (pOBD->type == EPD74R_640x384) { + if (ucData == OBD_BLACK) + ucPattern1 = 0; + else if (ucData == OBD_WHITE) + ucPattern1 = 0x33; + else ucPattern1 = 0x44; // red + EPDFill(pOBD, 0x10, ucPattern1); + } else { + EPDFill(pOBD, ucRAM1, ucPattern1); + EPDFill(pOBD, ucRAM2, ucPattern2); + } + pOBD->iOrientation = iOldRotation; + } +#endif // !MEMORY_ONLY + return; + } + if (pOBD->iOrientation == 0 || pOBD->iOrientation == 180) { + iLines = (pOBD->height+7) >> 3; + iCols = pOBD->width; + } else { // rotated + iLines = (pOBD->width+7) >> 3; + iCols = pOBD->height; + } + memset(u8Cache, ucData, iCols); + + if (bRender) { // write to the physical display if render = true + for (y=0; yucScreen) { + memset(pOBD->ucScreen, ucData, (pOBD->width * pOBD->height)/8); + } +} /* obdFill() */ + +// +// Provide or revoke a back buffer for your OLED graphics +// This allows you to manage the RAM used by ss_oled on tiny +// embedded platforms like the ATmega series +// Pass NULL to revoke the buffer. Make sure you provide a buffer +// large enough for your display (e.g. 128x64 needs 1K - 1024 bytes) +// +void obdSetBackBuffer(OBDISP *pOBD, uint8_t *pBuffer) +{ + if (pOBD == NULL || pBuffer == NULL) return; + + pOBD->ucScreen = pBuffer; + pOBD->iScreenOffset = 0; + if (pOBD->type >= LCD_COUNT) // invalid type, set to command output + pOBD->type = DISPLAY_COMMANDS; +} /* obdSetBackBuffer() */ + +void obdAllocBuffer(OBDISP *pOBD) +{ + pOBD->ucScreen = (uint8_t *)malloc(pOBD->width * ((pOBD->height+7)>>3)); + pOBD->iScreenOffset = 0; +} /* obdAllocBuffer() */ + +void obdDrawLine(OBDISP *pOBD, int x1, int y1, int x2, int y2, uint8_t ucColor, int bRender) +{ + int temp; + int dx = x2 - x1; + int dy = y2 - y1; + int error; + uint8_t *p, *pStart, ucFill = 0, mask, bOld, bNew; + int xinc, yinc; + int y, x; + int iPitch = pOBD->width; + int iRedOffset = 0; + + if (pOBD == NULL) return; + if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence + obdWriteCmdByte(pOBD, OBD_DRAWLINE | ((ucColor & 1) << 4)); + obdWriteCmdInt(pOBD, x1); + obdWriteCmdInt(pOBD, y1); + obdWriteCmdInt(pOBD, x2); + obdWriteCmdInt(pOBD, y2); + return; // done + } + + if (x1 < 0 || x2 < 0 || y1 < 0 || y2 < 0 || x1 >= pOBD->width || x2 >= pOBD->width || y1 >= pOBD->height || y2 >= pOBD->height) + return; + if (ucColor >= OBD_YELLOW && pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) { + // use the second half of the image buffer + iRedOffset = pOBD->width * ((pOBD->height+7)/8); + } + if (!pOBD->ucScreen) { // no back buffer, draw in local buffer + bRender = 1; // make sure it gets transmitted + if (pOBD->type >= EPD42_400x300) { + // no back buffer on EPD means we may need to invert the color + if (ucColor >= OBD_YELLOW) + ucColor = OBD_BLACK; + else + ucColor = 1-ucColor; // swap black/white + } + if (ucColor == OBD_BLACK) // fill with opposite color + ucFill = 0; + else + ucFill = 0xff; + memset(u8Cache, ucFill, sizeof(u8Cache)); + } + if(abs(dx) > abs(dy)) { + // X major case + if(x2 < x1) { + dx = -dx; + temp = x1; + x1 = x2; + x2 = temp; + temp = y1; + y1 = y2; + y2 = temp; + } + + y = y1; + dy = (y2 - y1); + error = dx >> 1; + yinc = 1; + if (dy < 0) + { + dy = -dy; + yinc = -1; + } + if (pOBD->ucScreen) { + p = pStart = &pOBD->ucScreen[iRedOffset + x1 + ((y >> 3) * iPitch)]; // point to current spot in back buffer + } else { // no back buffer, draw directly to the display RAM + p = pStart = u8Cache; + } + mask = 1 << (y & 7); // current bit offset + for(x=x1; x1 <= x2; x1++) { + if (ucColor == OBD_BLACK) + *p++ |= mask; // set pixel and increment x pointer + else + *p++ &= ~mask; + error -= dy; + if (error < 0) + { + error += dx; + if (yinc > 0) + mask <<= 1; + else + mask >>= 1; + if (mask == 0) // we've moved outside the current row, write the data we changed + { + if (bRender) { + obdSetPosition(pOBD, x, y, bRender); + obdWriteDataBlock(pOBD, pStart, (int)(p-pStart), bRender); // write the row we changed + } + x = x1+1; // we've already written the byte at x1 + y1 = y+yinc; + if (pOBD->ucScreen) { + p += (yinc > 0) ? iPitch : -iPitch; + pStart = p; + } else { // no buffer + memset(pStart, ucFill, (int)(p-pStart)); + p = pStart; + } + mask = 1 << (y1 & 7); + } + y += yinc; + } + } // for x1 + if (p != pStart && bRender) // some data needs to be written + { + obdSetPosition(pOBD, x, y, bRender); + obdWriteDataBlock(pOBD, pStart, (int)(p-pStart), bRender); + } + } else { + // Y major case + if(y1 > y2) { + dy = -dy; + temp = x1; + x1 = x2; + x2 = temp; + temp = y1; + y1 = y2; + y2 = temp; + } + if (pOBD->ucScreen) { + p = &pOBD->ucScreen[iRedOffset + x1 + ((y1 >> 3) * iPitch)]; // point to current spot in back buffer + bOld = bNew = p[0]; // current pixels + } else { + p = u8Cache; + bOld = bNew = ucFill; + } + mask = 1 << (y1 & 7); // current bit offset + dx = (x2 - x1); + error = dy >> 1; + xinc = 1; + if (dx < 0) + { + dx = -dx; + xinc = -1; + } + for(x = x1; y1 <= y2; y1++) { + if (ucColor == OBD_BLACK) + bNew |= mask; // set the pixel + else + bNew &= ~mask; + error -= dx; + mask <<= 1; // y1++ + if (mask == 0) // we're done with this byte, write it if necessary + { + if (bOld != bNew) + { + p[0] = bNew; // save to RAM + if (bRender) { + obdSetPosition(pOBD, x, y1, bRender); + obdWriteDataBlock(pOBD, &bNew, 1, bRender); + } + } // data changed + if (pOBD->ucScreen) { + p += iPitch; // next line + bOld = bNew = p[0]; + } else { + bOld = bNew = ucFill; + } + mask = 1; // start at LSB again + } + if (error < 0) + { + error += dy; + if (bOld != bNew) // write the last byte we modified if it changed + { + p[0] = bNew; // save to RAM + if (bRender) { + obdSetPosition(pOBD, x, y1, bRender); + obdWriteDataBlock(pOBD, &bNew, 1, bRender); + } + } + x += xinc; + if (pOBD->ucScreen) { + p += xinc; + bOld = bNew = p[0]; + } else { + bOld = bNew = ucFill; + } + } + } // for y + if (bOld != bNew) // write the last byte we modified if it changed + { + p[0] = bNew; // save to RAM + if (bRender) { + obdSetPosition(pOBD, x, y2, bRender); + obdWriteDataBlock(pOBD, &bNew, 1, bRender); + } + } + } // y major case +} /* obdDrawLine() */ + +// +// For drawing ellipses, a circle is drawn and the x and y pixels are scaled by a 16-bit integer fraction +// This function draws a single pixel and scales its position based on the x/y fraction of the ellipse +// +static void DrawScaledPixel(OBDISP *pOBD, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor) +{ + uint8_t *d, ucMask; + int iPitch; + int iRedOffset = 0; + + iPitch = pOBD->width; + if (ucColor >= OBD_YELLOW && pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) { + // use the second half of the image buffer + iRedOffset = pOBD->width * ((pOBD->height+7)/8); + } + if (iXFrac != 0x10000) x = ((x * iXFrac) >> 16); + if (iYFrac != 0x10000) y = ((y * iYFrac) >> 16); + x += iCX; y += iCY; + if (x < 0 || x >= pOBD->width || y < 0 || y >= pOBD->height) + return; // off the screen + d = &pOBD->ucScreen[iRedOffset + ((y >> 3)*iPitch) + x]; + ucMask = 1 << (y & 7); + if (ucColor) + *d |= ucMask; + else + *d &= ~ucMask; +} /* DrawScaledPixel() */ +// +// For drawing filled ellipses +// +static void DrawScaledLine(OBDISP *pOBD, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor) +{ + int iLen, x2; + uint8_t *d, ucMask; + int iPitch; + int iRedOffset = 0; + + if (ucColor >= OBD_YELLOW && pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) { + // use the second half of the image buffer + iRedOffset = pOBD->width * ((pOBD->height+7)/8); + } + iPitch = pOBD->width; + if (iXFrac != 0x10000) x = ((x * iXFrac) >> 16); + if (iYFrac != 0x10000) y = ((y * iYFrac) >> 16); + iLen = x*2; + x = iCX - x; y += iCY; + x2 = x + iLen; + if (y < 0 || y >= pOBD->height) + return; // completely off the screen + if (x < 0) x = 0; + if (x2 >= pOBD->width) x2 = pOBD->width-1; + iLen = x2 - x + 1; // new length + d = &pOBD->ucScreen[iRedOffset + ((y >> 3)*iPitch) + x]; + ucMask = 1 << (y & 7); + if (ucColor) // white + { + for (; iLen > 0; iLen--) + *d++ |= ucMask; + } + else // black + { + for (; iLen > 0; iLen--) + *d++ &= ~ucMask; + } +} /* DrawScaledLine() */ +// +// Draw the 8 pixels around the Bresenham circle +// (scaled to make an ellipse) +// +static void BresenhamCircle(OBDISP *pOBD, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor, uint8_t bFill) +{ + if (bFill) // draw a filled ellipse + { + // for a filled ellipse, draw 4 lines instead of 8 pixels + DrawScaledLine(pOBD, iCX, iCY, x, y, iXFrac, iYFrac, ucColor); + DrawScaledLine(pOBD, iCX, iCY, x, -y, iXFrac, iYFrac, ucColor); + DrawScaledLine(pOBD, iCX, iCY, y, x, iXFrac, iYFrac, ucColor); + DrawScaledLine(pOBD, iCX, iCY, y, -x, iXFrac, iYFrac, ucColor); + } + else // draw 8 pixels around the edges + { + DrawScaledPixel(pOBD, iCX, iCY, x, y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, -x, y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, x, -y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, -x, -y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, y, x, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, -y, x, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, y, -x, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOBD, iCX, iCY, -y, -x, iXFrac, iYFrac, ucColor); + } +} /* BresenhamCircle() */ + +// +// Draw an outline or filled ellipse +// +void obdEllipse(OBDISP *pOBD, int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled) +{ + int32_t iXFrac, iYFrac; + int iRadius, iDelta, x, y; + + if (pOBD == NULL || pOBD->ucScreen == NULL) + return; // must have back buffer defined + if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence + obdWriteCmdByte(pOBD, OBD_DRAWELLIPSE | ((ucColor & 1) << 4) | ((bFilled & 1) << 5)); + obdWriteCmdInt(pOBD, iCenterX); + obdWriteCmdInt(pOBD, iCenterY); + obdWriteCmdInt(pOBD, iRadiusX); + obdWriteCmdInt(pOBD, iRadiusY); + return; // done + } + + if (iRadiusX <= 0 || iRadiusY <= 0) return; // invalid radii + + if (iRadiusX > iRadiusY) // use X as the primary radius + { + iRadius = iRadiusX; + iXFrac = 65536; + iYFrac = (iRadiusY * 65536) / iRadiusX; + } + else + { + iRadius = iRadiusY; + iXFrac = (iRadiusX * 65536) / iRadiusY; + iYFrac = 65536; + } + iDelta = 3 - (2 * iRadius); + x = 0; y = iRadius; + while (x <= y) + { + BresenhamCircle(pOBD, iCenterX, iCenterY, x, y, iXFrac, iYFrac, ucColor, bFilled); + x++; + if (iDelta < 0) + { + iDelta += (4*x) + 6; + } + else + { + iDelta += 4 * (x-y) + 10; + y--; + } + } +} /* obdEllipse() */ +// +// Draw an outline or filled rectangle +// +void obdRectangle(OBDISP *pOBD, int x1, int y1, int x2, int y2, uint8_t ucColor, uint8_t bFilled) +{ + uint8_t *d, ucMask, ucMask2; + int tmp, iOff; + int iPitch; + int iRedOffset = 0; + + if (ucColor >= OBD_YELLOW) { + if (pOBD->iFlags & (OBD_3COLOR | OBD_4COLOR)) { + // use the second half of the image buffer + iRedOffset = pOBD->width * ((pOBD->height+7)/8); + } else { // force red to black if not present + ucColor = OBD_BLACK; + } + } + + if (pOBD == NULL || pOBD->ucScreen == NULL) + return; // only works with a back buffer + if (pOBD->type == DISPLAY_COMMANDS) { // encode this as a command sequence + obdWriteCmdByte(pOBD, OBD_DRAWRECT | ((ucColor & 1) << 4) | ((bFilled & 1) << 5)); + obdWriteCmdInt(pOBD, x1); + obdWriteCmdInt(pOBD, y1); + obdWriteCmdInt(pOBD, x2); + obdWriteCmdInt(pOBD, y2); + return; // done + } + + if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0 || + x1 >= pOBD->width || y1 >= pOBD->height || x2 >= pOBD->width || y2 >= pOBD->height) return; // invalid coordinates + iPitch = pOBD->width; + // Make sure that X1/Y1 is above and to the left of X2/Y2 + // swap coordinates as needed to make this true + if (x2 < x1) + { + tmp = x1; + x1 = x2; + x2 = tmp; + } + if (y2 < y1) + { + tmp = y1; + y1 = y2; + y2 = tmp; + } + if (bFilled) + { + int x, y, iMiddle; + iMiddle = (y2 >> 3) - (y1 >> 3); + ucMask = 0xff << (y1 & 7); + if (iMiddle == 0) // top and bottom lines are in the same row + ucMask &= (0xff >> (7-(y2 & 7))); + d = &pOBD->ucScreen[iRedOffset + (y1 >> 3)*iPitch + x1]; + // Draw top + for (x = x1; x <= x2; x++) + { + if (ucColor) + *d |= ucMask; + else + *d &= ~ucMask; + d++; + } + if (iMiddle > 1) // need to draw middle part + { + ucMask = (ucColor) ? 0xff : 0x00; + for (y=1; yucScreen[iRedOffset + (y1 >> 3)*iPitch + x1 + (y*iPitch)]; + for (x = x1; x <= x2; x++) + *d++ = ucMask; + } + } + if (iMiddle >= 1) // need to draw bottom part + { + ucMask = 0xff >> (7-(y2 & 7)); + d = &pOBD->ucScreen[iRedOffset + (y2 >> 3)*iPitch + x1]; + for (x = x1; x <= x2; x++) + { + if (ucColor) + *d++ |= ucMask; + else + *d++ &= ~ucMask; + } + } + } + else // outline + { + // see if top and bottom lines are within the same byte rows + d = &pOBD->ucScreen[iRedOffset + (y1 >> 3)*iPitch + x1]; + if ((y1 >> 3) == (y2 >> 3)) + { + ucMask2 = 0xff << (y1 & 7); // L/R end masks + ucMask = 1 << (y1 & 7); + ucMask |= 1 << (y2 & 7); + ucMask2 &= (0xff >> (7-(y2 & 7))); + if (ucColor) + { + *d++ |= ucMask2; // start + x1++; + for (; x1 < x2; x1++) + *d++ |= ucMask; + if (x1 <= x2) + *d++ |= ucMask2; // right edge + } + else + { + *d++ &= ~ucMask2; + x1++; + for (; x1 < x2; x1++) + *d++ &= ~ucMask; + if (x1 <= x2) + *d++ &= ~ucMask2; // right edge + } + } + else + { + int y; + // L/R sides + iOff = (x2 - x1); + ucMask = 1 << (y1 & 7); + for (y=y1; y <= y2; y++) + { + if (ucColor) { + *d |= ucMask; + d[iOff] |= ucMask; + } else { + *d &= ~ucMask; + d[iOff] &= ~ucMask; + } + ucMask <<= 1; + if (ucMask == 0) { + ucMask = 1; + d += iPitch; + } + } + // T/B sides + ucMask = 1 << (y1 & 7); + ucMask2 = 1 << (y2 & 7); + x1++; + d = &pOBD->ucScreen[iRedOffset + (y1 >> 3)*iPitch + x1]; + iOff = (y2 >> 3) - (y1 >> 3); + iOff *= iPitch; + for (; x1 < x2; x1++) + { + if (ucColor) { + *d |= ucMask; + d[iOff] |= ucMask2; + } else { + *d &= ~ucMask; + d[iOff] &= ~ucMask2; + } + d++; + } + } + } // outline +} /* obdRectangle() */ +// +// Copy the current bitmap buffer from its native form (LSB_FIRST, VERTICAL_BYTES) to the requested form +// returns 0 for success, -1 for error +// Constants to be combined for the iFlags parameter: +// Output format options - +// OBD_LSB_FIRST 0x001 +// OBD_MSB_FIRST 0x002 +// OBD_VERT_BYTES 0x004 +// OBD_HORZ_BYTES 0x008 +// Orientation Options - +// OBD_ROTATE_90 0x010 +// OBD_FLIP_VERT 0x020 +// OBD_FLIP_HORZ 0x040 +// Polarity Options - +// OBD_INVERT 0x080 +int obdCopy(OBDISP *pOBD, int iFlags, uint8_t *pDestination) +{ +int i, x, y, iPitch, iSize; +int xStart, xEnd, yStart, yEnd, yDst, xDst, dx, dy; +uint8_t ucSrcMask, ucDstMask, *s, *d; + + iPitch = pOBD->width; + + if (pDestination == NULL || pOBD == NULL || pOBD->ucScreen == NULL) + return OBD_ERROR_BAD_PARAMETER; + // calculate output buffer size + if (iFlags & OBD_HORZ_BYTES) { + if (iFlags & OBD_ROTATE_90) + iSize = ((pOBD->height + 7)>>3) * pOBD->width; + else + iSize = ((pOBD->width + 7)>>3) * pOBD->height; + } else { + if (iFlags & OBD_ROTATE_90) + iSize = (pOBD->height * ((pOBD->width+7)>>3)); + else + iSize = (pOBD->width * ((pOBD->height+7)>>3)); + } + memset(pDestination, 0, iSize); // start with 0 in dest + // Prepare vars for walking through the source image + if (iFlags & OBD_ROTATE_90) { + if (iFlags & OBD_FLIP_HORZ) { + dy = 1; + yStart = 0; + yEnd = pOBD->height; + } else { + dy = -1; + yStart = pOBD->height-1; + yEnd = -1; + } + if (iFlags & OBD_FLIP_VERT) { + dx = -1; + xStart = pOBD->width-1; + xEnd = -1; + } else { + dx = 1; + xStart = 0; + xEnd = pOBD->width; + } + } else { // not rotated + if (iFlags & OBD_FLIP_HORZ) { + dx = -1; + xStart = pOBD->width-1; + xEnd = -1; + } else { + dx = 1; + xStart = 0; + xEnd = pOBD->width; + } + if (iFlags & OBD_FLIP_VERT) { + dy = -1; + yStart = pOBD->height-1; + yEnd = -1; + } else { + dy = 1; + yStart = 0; + yEnd = pOBD->height; + } + } + // Due to the possible number of permutations, the different loops + // are more generic and handle flips/bit-direction with a more general + // approach which moves individual pixels even when a more efficient + // method is possible. More cycles, but able to do EVERYTHING +// Separate output by byte orientation + // Vertical bytes here + if (iFlags & OBD_VERT_BYTES) + { + if (iFlags & OBD_ROTATE_90) + { + xDst = 0; + for (y=yStart; y!=yEnd; y += dy, xDst++) { + ucSrcMask = (1 << (y & 7)); + yDst = 0; + s = &pOBD->ucScreen[(y >> 3) * iPitch]; + d = &pDestination[xDst]; + for (x=xStart; x!=xEnd; x += dx, yDst++) { + if (s[x] & ucSrcMask) {// set pixel, copy to dest + if (iFlags & OBD_LSB_FIRST) + d[(yDst >> 3)*pOBD->height] |= (1 << (yDst & 7)); + else + d[(yDst >> 3)*pOBD->height] |= (0x80 >> (yDst & 7)); + } + } // for x + } // for y + } // rotate 90 + else // normally oriented + { + yDst = 0; + for (y=yStart; y!=yEnd; y += dy, yDst++) { + ucSrcMask = (1 << (y & 7)); + if (iFlags & OBD_LSB_FIRST) + ucDstMask = (1 << (y & 7)); + else + ucDstMask = (0x80 >> (y & 7)); + xDst = 0; + s = &pOBD->ucScreen[(y >> 3) * iPitch]; + d = &pDestination[(yDst >> 3) * iPitch]; + for (x=xStart; x!=xEnd; x += dx, xDst++) { + if (s[x] & ucSrcMask) // set pixel, copy to dest + d[xDst] |= ucDstMask; + } // for x + } // for y + } // normal orientation + } // vertical bytes + else // Horizontal bytes here + { + if (iFlags & OBD_ROTATE_90) + { + int iDstPitch = (pOBD->height + 7)>>3; // dest bytes per line + xDst = 0; + for (y=yStart; y!=yEnd; y += dy, xDst++) { + ucSrcMask = (1 << (y & 7)); + yDst = 0; + s = &pOBD->ucScreen[(y >> 3) * iPitch]; + d = &pDestination[xDst >> 3]; + ucDstMask = (iFlags & OBD_LSB_FIRST) ? (1 << (xDst & 7)) : (0x80 >> (xDst & 7)); + for (x=xStart; x!=xEnd; x += dx, yDst++) { + if (s[x] & ucSrcMask) // set pixel, copy to dest + d[yDst * iDstPitch] |= ucDstMask; + } // for x + } // for y + } // rotate 90 + else // normally oriented + { + int iDstPitch = (pOBD->width + 7)>>3; // dest bytes per line + yDst = 0; + for (y=yStart; y!=yEnd; y += dy, yDst++) { + ucSrcMask = (1 << (y & 7)); + xDst = 0; + s = &pOBD->ucScreen[(y >> 3) * iPitch]; + d = &pDestination[yDst * iDstPitch]; + ucDstMask = (iFlags & OBD_LSB_FIRST) ? 0x1 : 0x80; + for (x=xStart; x!=xEnd; x += dx, xDst++) { + if (s[x] & ucSrcMask) // set pixel, copy to dest + d[(xDst>>3)] |= ucDstMask; + if (iFlags & OBD_LSB_FIRST) + ucDstMask <<= 1; + else + ucDstMask >>= 1; + if (ucDstMask == 0) + ucDstMask = (iFlags & OBD_LSB_FIRST) ? 0x1 : 0x80; + } // for x + } // for y + } // normal orientation + } + // Invert all pixels? + if (iFlags & OBD_INVERT) { + for (i=0; i +#include + +void bbeiInitIO(BBEIDISP *pBBEI, uint32_t u32Speed) +{ + iDCPin, iMOSIPin, iCLKPin, iCSPin, iRSTPin, iBUSYPin + pinMode(pBBEI->iDCPin, OUTPUT); + if (pBBEI->iRSTPin != 0xff) { + pinMode(pBBEI->iRSTPin, OUTPUT); + digitalWrite(pBBEI->iRSTPin, LOW); + delay(100); + digitalWrite(pBBEI->iRSTPin, HIGH); + delay(1000); + } + if (pBBEI->iBUSYPin != 0xff) { + pinMode(pBBEI->iBUSYPin, INPUT); + } + pBBEI->iSpeed = u32Speed; + SPI.begin(pBBEI->iCLKPin, -1, pBBEI->iMOSIPin, pBBEI->iCSPin); + SPI.beginTransaction(SPISettings(u32Speed, MSBFIRST, SPI_MODE0)); + SPI.endTransaction(); // N.B. - if you call beginTransaction() again without a matching endTransaction(), it will hang on ESP32 +} /* bbeiInitIO() */ + +void bbeiCMD2(BBEIDISP *pBBEI, uint8_t cmd1, uint8_t cmd2) +{ + digitalWrite(pBBEI->iDCPin, LOW); + SPI.transfer(cmd1); + SPI.transfer(cmd2); + digitalWrite(pBBEI->iDCPin, HIGH); // leave data mode as the default + +} /* bbeiCMD2() */ + +void bbeiWriteCmd(BBEIDISP *pBBEI, uint8_t cmd) +{ + digitalWrite(pBBEI->iDCPin, LOW); + SPI.transfer(cmd); + digitalWrite(pBBEI->iDCPin, HIGH); // leave data mode as the default +} /* bbeiWriteCmd() */ + +void bbeiWriteData(BBEIDISP *pBBEI, uint8_t *pData, int iLen) +{ + SPI.transferBytes(pData, NULL, iLen); +} /* bbeiWriteData() */ + +#else // LINUX +#endif // ARDUINO +#endif // __BB_EI_IO__ diff --git a/src/bb_eink.cpp b/src/bb_eink.cpp new file mode 100644 index 0000000..9b4ef06 --- /dev/null +++ b/src/bb_eink.cpp @@ -0,0 +1,596 @@ +// +// bb_eink +// Copyright (c) 2024 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 9/11/2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifdef _LINUX_ +#include +#include +#include +#include +#include +#include +// convert wire library constants into ArmbianIO values +#define OUTPUT GPIO_OUT +#define INPUT GPIO_IN +#define INPUT_PULLUP GPIO_IN_PULLUP +#define HIGH 1 +#define LOW 0 +void delay(int); +#else // Arduino + +#endif // _LINUX_ +#include "bb_eink.h" +#include "bb_ei_io.inl" // I/O (non-portable) code is in here +#include "bb_ei.inl" // All of the display interface code is in here +#include "bb_ei_gfx.inl" // drawing code +#ifdef __cplusplus +// +// C++ Class implementation +// +uint32_t BBEINK::getRefreshTime(void) +{ + return _bbei.iTimeout; +} + +void BBEINK::setPosition(int x, int y, int w, int h) +{ + bbeiSetPosition(&_bbei, x, y, w, h); +} +int BBEINK::setPanelType(int iPanel) +{ + return bbeiSetPanelType(&_bbei, iPanel); +} +void BBEINK::initIO(int iCS, int iMOSI, int iSCLK, int iDC, int iReset, int iBusy, uint32_t u32Speed) +{ + _bbei.iCSPin = iCS; + _bbei.iMOSIPin = iMOSI; + _bbei.iCLKPin = iSCLK; + _bbei.iDCPin = iDC; + _bbei.iRSTPin = iReset; + _bbei.iBusyPin = iBusy; + bbeiInitIO(&_bbei, u32Speed); +} /* initIO() */ + +int BBEINK::getFlags(void) +{ + return _bbei.iFlags; +} + +void BBEINK::setFlags(int iFlags) +{ + _bbei.iFlags = iFlags; + _bbei.invert = iFlags & OBD_INVERTED; + _bbei.flip = iFlags & OBD_FLIP180; +} + +void BBEINK::setContrast(uint8_t ucContrast) +{ + obdSetContrast(&_bbei, ucContrast); +} + +int BBEINK::I2Cbegin(int iType, int iAddr, int32_t iSpeed) +{ + return obdI2CInit(&_bbei, iType, iAddr, _bbei.flip, _bbei.invert, !_bbei.bBitBang, _bbei.iSDAPin, _bbei.iSCLPin, _bbei.iRSTPin, iSpeed); +} /* I2Cbegin() */ + +void BBEINK::setBuffer(uint8_t *pBuffer) +{ + _bbei.ucScreen = pBuffer; +} + +int BBEINK::allocBuffer(void) +{ + int iSize = _bbei.width * ((_bbei.height+7)>>3); + if (_bbei.iFlags & (OBD_3COLOR | OBD_4COLOR) || _bbei.chip_type == OBD_CHIP_UC8151) + iSize *= 2; // 2 bit planes + _bbei.ucScreen = (uint8_t *)malloc(iSize); + if (_bbei.ucScreen != NULL) { + _bbei.render = false; // draw into RAM only + memset(_bbei.ucScreen, 0xff, iSize); + return OBD_SUCCESS; + } + return OBD_ERROR_NO_MEMORY; // failed +} /* allocBuffer() */ + +uint32_t BBEINK::capabilities(void) +{ + return _bbei.iFlags; +} + +void * BBEINK::getBuffer(void) +{ + return (void *)_bbei.ucScreen; +} /* getBuffer() */ + +void BBEINK::freeBuffer(void) +{ + if (_bbei.ucScreen) { + free(_bbei.ucScreen); + _bbei.ucScreen = NULL; + } +} /* freeBuffer() */ + +void BBEINK::setScroll(bool bScroll) +{ + _bbei.bScroll = bScroll; +} + +void BBEINK::setRotation(int iRotation) +{ + obdSetRotation(&_bbei, iRotation); +} /* setRotation() */ + +void BBEINK::fillScreen(int iColor) +{ + obdFill(&_bbei, iColor, (_bbei.ucScreen == NULL)); +} /* fillScreen() */ + +void BBEINK::drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) +{ + obdRectangle(&_bbei, x, y, x+w-1, y+h-1, color, 0); +} /* drawRect() */ + +void BBEINK::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) +{ + obdRectangle(&_bbei, x, y, x+w-1, y+h-1, color, 1); +} /* fillRect() */ + +void BBEINK::setTextWrap(bool bWrap) +{ + _bbei.wrap = (int)bWrap; +} + +void BBEINK::setTextColor(int iFG, int iBG) +{ + if (iFG > OBD_RED) iFG = OBD_BLACK; + if (iBG > OBD_RED) iBG = OBD_BLACK; + if ((_bbei.iFlags & (OBD_3COLOR | OBD_4COLOR)) == 0) { + if (iFG == OBD_RED) iFG = OBD_BLACK; // can't set red color + if (iBG == OBD_RED) iBG = OBD_BLACK; + } + _bbei.iFG = iFG; + _bbei.iBG = (iBG == -1) ? iFG : iBG; +} /* setTextColor() */ + +void BBEINK::setCursor(int x, int y) +{ + if (x >= 0) + _bbei.iCursorX = x; + if (y >= 0) + _bbei.iCursorY = y; +} /* setCursor() */ + +int BBEINK::loadBMP(const uint8_t *pBMP, int x, int y, int iFG, int iBG) +{ + return obdLoadBMP(&_bbei, pBMP, x, y, iFG, iBG); +} /* loadBMP() */ + +int BBEINK::drawEPDGFX(int x, int y, int cx, int cy, uint8_t *pPlane0, uint8_t *pPlane1) +{ + return obdDrawEPDGFX(&_bbei, x, y, cx, cy, pPlane0, pPlane1); +} /* drawEPDGFX() */ + +int BBEINK::loadBMP3(const uint8_t *pBMP, int x, int y) +{ + return obdLoadBMP3(&_bbei, pBMP, x, y); +} /* loadBMP3() */ + +void BBEINK::setFont(int iFont) +{ + _bbei.iFont = iFont; + _bbei.pFreeFont = NULL; +} /* setFont() */ + +void BBEINK::setTextSize(uint8_t s) +{ + _bbei.u32FontScaleX = _bbei.u32FontScaleY = 256 * s; + _bbei.iFont = -1; + _bbei.pFreeFont = NULL; +} +void BBEINK::setTextSize(uint8_t sx, uint8_t sy) +{ + _bbei.u32FontScaleX = 256 * sx; + _bbei.u32FontScaleY = 256 * sy; + _bbei.iFont = -1; + _bbei.pFreeFont = NULL; +} +int BBEINK::scrollBuffer(int iStartCol, int iEndCol, int iStartRow, int iEndRow, int bUp) +{ + return obdScrollBuffer(&_bbei, iStartCol, iEndCol, iStartRow, iEndRow, bUp); +} /* scrollBuffer() */ + +void BBEINK::setFreeFont(const GFXfont *pFont) +{ + _bbei.pFreeFont = (GFXfont *)pFont; +} /* setFreeFont() */ + +void BBEINK::drawLine(int x1, int y1, int x2, int y2, int iColor) +{ + obdDrawLine(&_bbei, x1, y1, x2, y2, iColor, 1); +} /* drawLine() */ + +inline GFXglyph *pgm_read_glyph_ptr(const GFXfont *gfxFont, uint8_t c) { +#ifdef __AVR__ + return &(((GFXglyph *)pgm_read_ptr(&gfxFont->glyph))[c]); +#else + // expression in __AVR__ section may generate "dereferencing type-punned + // pointer will break strict-aliasing rules" warning In fact, on other + // platforms (such as STM32) there is no need to do this pointer magic as + // program memory may be read in a usual way So expression may be simplified + return gfxFont->glyph + c; +#endif //__AVR__ +} + +void obdScroll1Line(OBDISP *pOBD, int iAmount) +{ +int y, iLines; + + if (pOBD == NULL || pOBD->ucScreen == NULL) + return; + iLines = (pOBD->height+7)>>3; + for (y=0; yucScreen[y*pOBD->width], &pOBD->ucScreen[(y+iAmount)*pOBD->width], pOBD->width*iAmount); + } + memset(&pOBD->ucScreen[(iLines-iAmount) * pOBD->width], (char)pOBD->iBG, pOBD->width*iAmount); +} /* obdScroll1Line() */ +#ifdef _LINUX_ +void BBEINK::print(const string &str) +{ + print(str.c_str()); +} /* print() */ + +void BBEINK::println(const string &str) +{ +char ucTemp[4]; + + print(str); + ucTemp[0] = '\n'; + ucTemp[1] = '\r'; + ucTemp[2] = 0; + print((const char *)ucTemp); +} /* print() */ + +void BBEINK::print(const char *pString) +{ +uint8_t *s = (uint8_t *)pString; + + while (*s != 0) { + write(*s++); + } +} /* print() */ + +void BBEINK::println(const char *pString) +{ +char ucTemp[4]; + + print(pString); + ucTemp[0] = '\n'; + ucTemp[1] = '\r'; + ucTemp[2] = 0; + print((const char *)ucTemp); +} /* println() */ +void BBEINK::print(int value, int format) +{ +char c, ucTemp[32]; +char *d = &ucTemp[31]; + + if (value) { + d[0] = 0; + switch(format) { + case DEC: + while (value) { + d--; + *d = '0' + (value % 10); + value /= 10; + } + break; + case HEX: + while (value) { + d--; + c = value & 0xf; + if (c < 10) + *d = '0' + c; + else + *d = 'A' + (c-10); + value >>= 4; + } + break; + case OCT: + while (value) { + d--; + *d = '0' + (value & 7); + value >>= 3; + } + break; + case BIN: + while (value) { + d--; + *d = '0' + (value & 1); + value >>= 1; + } + break; + default: + break; + } + } else { // if zero value + d--; + *d = '0'; + } + print((const char *)d); +} /* print() */ + +void BBEINK::println(int value, int format) +{ +char ucTemp[4]; + + print(value, format); + ucTemp[0] = '\n'; + ucTemp[1] = '\r'; + ucTemp[2] = 0; + print((const char *)ucTemp); +} /* println() */ + +#endif // _LINUX_ +// +// write (Arduino Print friend class) +// +#ifndef __AVR__ +size_t BBEINK::write(uint8_t c) { +char szTemp[2]; // used to draw 1 character at a time to the C methods +int w, h; + + szTemp[0] = c; szTemp[1] = 0; + if (_bbei.pFreeFont == NULL) { // use built-in fonts + if (_bbei.iFont == -1) { // scaled 5x7 font + h = (int)(_bbei.u32FontScaleY >> 8) * 8; + w = (int)(_bbei.u32FontScaleX >> 8) * 6; + } else if (_bbei.iFont == FONT_8x8 || _bbei.iFont == FONT_6x8) { + h = 8; + w = (_bbei.iFont == FONT_8x8) ? 8 : 6; + } else if (_bbei.iFont == FONT_12x16 || _bbei.iFont == FONT_16x16) { + h = 16; + w = (_bbei.iFont == FONT_12x16) ? 12 : 16; + } else { w = 16; h = 32; } + + if (c == '\n') { // Newline? + _bbei.iCursorX = 0; // Reset x to zero, + _bbei.iCursorY += h; // advance y one line + // should we scroll the screen up 1 line? + if (_bbei.iCursorY >= _bbei.height && _bbei.ucScreen && _bbei.bScroll) { + obdScroll1Line(&_bbei, h/8); + if (_bbei.render) { + obdDumpBuffer(&_bbei, NULL, false, false, false); + } + _bbei.iCursorY -= h; + } + } else if (c != '\r') { // Ignore carriage returns + if (_bbei.wrap && ((_bbei.iCursorX + w) > _bbei.width)) { // Off right? + _bbei.iCursorX = 0; // Reset x to zero, + _bbei.iCursorY += h; // advance y one line + // should we scroll the screen up 1 line? + if (_bbei.iCursorY >= _bbei.height && _bbei.ucScreen && _bbei.bScroll) { + obdScroll1Line(&_bbei, h/8); + if (_bbei.render) { + obdDumpBuffer(&_bbei, NULL, false, false, false); + } + _bbei.iCursorY -= h; + } + } + if (_bbei.iFont == -1) { // scaled drawing + obdScaledString(&_bbei, _bbei.iCursorX, _bbei.iCursorY, szTemp, FONT_6x8, _bbei.iFG, _bbei.u32FontScaleX, _bbei.u32FontScaleY, 0); + _bbei.iCursorX += w; + } else { + obdWriteString(&_bbei, 0, -1, -1, szTemp, _bbei.iFont, _bbei.iFG, _bbei.render); + } + } + } else { // Custom font + if (c == '\n') { + _bbei.iCursorX = 0; + _bbei.iCursorY += (uint8_t)pgm_read_byte(&_bbei.pFreeFont->yAdvance); + } else if (c != '\r') { + uint8_t first = pgm_read_word((const uint8_t*)&_bbei.pFreeFont->first); + if ((c >= first) && (c <= (uint8_t)pgm_read_word((const uint8_t*)&_bbei.pFreeFont->last))) { + GFXglyph *glyph = pgm_read_glyph_ptr(_bbei.pFreeFont, c - first); + w = pgm_read_byte(&glyph->width); + h = pgm_read_byte(&glyph->height); + if ((w > 0) && (h > 0)) { // Is there an associated bitmap? + int16_t xo = (int8_t)pgm_read_word((const uint8_t*)&glyph->xOffset); + w += xo; // xadvance + h = (uint8_t)pgm_read_byte(&_bbei.pFreeFont->yAdvance); + if (_bbei.wrap && ((_bbei.iCursorX + w) > _bbei.width)) { + _bbei.iCursorX = 0; + _bbei.iCursorY += h; + } + obdWriteStringCustom(&_bbei, _bbei.pFreeFont, -1, -1, szTemp, _bbei.iFG); + } + } + } + } + return 1; +} /* write() */ +#endif // !__AVR__ + +void BBEINK::drawPixel(int16_t x, int16_t y, uint16_t color) +{ + obdSetPixel(&_bbei, x, y, color, _bbei.render); +} +int16_t BBEINK::getCursorX(void) +{ + return _bbei.iCursorX; +} +int16_t BBEINK::getCursorY(void) +{ + return _bbei.iCursorY; +} +uint8_t BBEINK::getRotation(void) +{ + return _bbei.iOrientation; +} +void BBEINK::wake(void) +{ + if (_bbei.type >= EPD42_400x300) { + EPDWakeUp(&_bbei, 1); + } +} +void BBEINK::sleep(int bDeep) +{ + if (_bbei.type >= EPD42_400x300) { + EPDSleep(&_bbei, bDeep); + } +} + +void BBEINK::getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h) +{ + if (_bbei.pFreeFont) { + int width, top, bottom; + obdGetStringBox(_bbei.pFreeFont, (char *)string, &width, &top, &bottom); + *x1 = x; + *w = width; + *y1 = y + top; + *h = (bottom - top + 1); + } +} +void BBEINK::getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h) +{ + getTextBounds(str.c_str(), x, y, x1, y1, w, h); +} +int BBEINK::dataTime(void) +{ + return _bbei.iDataTime; +} +int BBEINK::opTime(void) +{ + return _bbei.iOpTime; +} +int16_t BBEINK::width(void) +{ + return _bbei.width; +} +int16_t BBEINK::height(void) +{ + return _bbei.height; +} +void BBEINK::drawSprite(uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority) +{ + obdDrawSprite(&_bbei, pSprite, cx, cy, iPitch, x, y, iPriority); +} +int BBEINK::drawGFX(uint8_t *pSrc, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch) +{ + return obdDrawGFX(&_bbei, pSrc, iSrcCol, iSrcRow, iDestCol, iDestRow, iWidth, iHeight, iSrcPitch); +} /* drawGFX() */ + +void BBEINK::drawTile(const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender) +{ + obdDrawTile(&_bbei, pTile, x, y, iRotation, bInvert, bRender); +} +void BBEINK::drawCircle(int32_t x, int32_t y, int32_t r, uint32_t color) +{ + obdEllipse(&_bbei, x, y, r, r, color, 0); +} +void BBEINK::fillCircle(int32_t x, int32_t y, int32_t r, uint32_t color) +{ + obdEllipse(&_bbei, x, y, r, r, color, 1); +} +void BBEINK::drawEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color) +{ + obdEllipse(&_bbei, x, y, rx, ry, color, 0); +} +void BBEINK::fillEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color) +{ + obdEllipse(&_bbei, x, y, rx, ry, color, 1); +} +void BBEINK::setPosition(int x, int y, int w, int h) +{ + if (_bbei.type >= EPD42_400x300) + EPDSetPosition(&_bbei, x, y, w, h); + else + obdSetPosition(&_bbei, x, y, 1); +} /* setPosition() */ +void BBEINK::writeRaw(uint8_t *pData, int iLen) +{ + RawWrite(&_bbei, pData, iLen); +} + +void BBEINK::writeCommand(uint8_t ucCMD) +{ + obdWriteCommand(&_bbei, ucCMD); +} +void BBEINK::pushPixels(uint8_t *pPixels, int iCount) +{ + RawWriteData(&_bbei, pPixels, iCount); +} /* pushPixels() */ + +void BBEINK::pushImage(int x, int y, int w, int h, uint16_t *pixels) +{ + // DEBUG + (void)x; (void)y; (void)w; (void)h; (void)pixels; +} + +void BBEINK::displayLines(int iStartLine, int iLineCount) +{ + obdWriteLCDLines(&_bbei, iStartLine, iLineCount); +} /* displayLines() */ + +int BBEINK::display(bool bRefresh, bool bWait, bool bFast) +{ + return obdDumpBuffer(&_bbei, NULL, bRefresh, bWait, bFast); +} + +void BBEINK::wait(bool bQuick) +{ + EPDWaitBusy(&_bbei, bQuick); +} +int BBEINK::displayPartial() +{ + if (_bbei.type >= EPD42_400x300 && (_bbei.iFlags & OBD_HAS_PARTIAL_UPDATE)) { + return EPDDumpPartial(&_bbei, NULL, 0, 0, _bbei.width, _bbei.height); + } + return OBD_ERROR_NOT_SUPPORTED; +} +int BBEINK::displayPartial(int x, int y, int w, int h, uint8_t *pBuffer) +{ + if (_bbei.type >= EPD42_400x300 && (_bbei.iFlags & OBD_HAS_PARTIAL_UPDATE)) { + return obdDumpPartial(&_bbei, x, y, w, h, pBuffer); + } else { + return OBD_ERROR_NOT_SUPPORTED; + } +} +void BBEINK::drawString(const char *pText, int x, int y) +{ + setCursor(x,y); + if (_bbei.pFreeFont != NULL) + obdWriteStringCustom(&_bbei, _bbei.pFreeFont, x, y, (char *)pText, _bbei.iFG); + else + obdWriteString(&_bbei, 0, x, y, (char *)pText, _bbei.iFont, _bbei.iFG, 1); +} /* drawString() */ +void BBEINK::drawString(String text, int x, int y) +{ + drawString(text.c_str(), x, y); +} /* drawString() */ + +void BBEINK::backlight(int bOn) +{ + obdBacklight(&_bbei, bOn); +} +OBDISP * BBEINK::getOBD() +{ + return &_bbei; +} +void BBEINK::setRender(bool bRAMOnly) +{ + _bbei.render = !bRAMOnly; +} +#endif // __cplusplus diff --git a/src/bb_eink.h b/src/bb_eink.h new file mode 100644 index 0000000..2a2d4aa --- /dev/null +++ b/src/bb_eink.h @@ -0,0 +1,627 @@ +// +// bb_eink +// Copyright (c) 2024 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 9/11/2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __BB_EINK__ +#define __BB_EINK__ + +#ifndef MEMORY_ONLY +#include +#endif + +#ifdef _LINUX_ +// for Print support +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 +#define PROGMEM +#include +#include +#include +#endif // _LINUX_ + +// error messages +enum { + BBEI_SUCCESS, + BBEI_ERROR_BAD_PARAMETER, + BBEI_ERROR_BAD_DATA, + BBEI_ERROR_NOT_SUPPORTED, + BBEI_ERROR_NO_MEMORY, + BBEI_ERROR_COUNT +}; + +// Display refresh modes +enum { + REFRESH_FULL=0, + REFRESH_FAST, + REFRESH_PARTIAL +}; +// controller chip types +enum { + BBEI_CHIP_NOT_DEFINED = 0, + BBEI_CHIP_SSD16xx, + BBEI_CHIP_UC81xx, + BBEI_CHIP_COUNT +}; + +// 5 possible font sizes: 8x8, 16x32, 6x8, 12x16 (stretched from 6x8 with smoothing), 16x16 (stretched from 8x8) +enum { + FONT_6x8 = 0, + FONT_8x8, + FONT_12x16, + FONT_16x16, + FONT_16x32, + FONT_CUSTOM0, + FONT_CUSTOM1, + FONT_CUSTOM2, + FONT_COUNT +}; + +typedef struct epd_panel { + uint16_t width; + uint16_t height; + uint8_t *pInitFull; + uint8_t *pInitFast; + uint8_t *pInitPart; + uint8_t flags; + uint8_t chip_type; +} EPD_PANEL; + +// Display types +enum { + EPD_PANEL_UNDEFINED=0, + EPD42_400x300, // WFT0420CZ15 + EPD42B_400x300, // DEPG0420BN / GDEY042T81 + EPD213B_104x212, + EPD293_128x296, + EPD294_128x296, // Waveshare newer 2.9" 1-bit 128x296 + EPD295_128x296, // harvested from Solum 2.9" BW ESLs + EPD266_152x296, // GDEY0266T90 + EPD102_80x128, // GDEW0102T4 + EPD27B_176x264 // GDEY027T91 +}; +#ifdef FUTURE + EPD42_4GRAY_400x300, // WFT0420CZ15 + EPD42Y_400x300, // DEPG0420YN + EPD29_128x296, + EPD29B_128x296, + EPD29R_128x296, + EPD29Y_128x296, // DEPG0290YN + EPD42R_400x300, + EPD42R2_400x300, // GDEQ042Z21 + EPD213R_104x212, + EPD213R2_122x250, // DEPG0213RW + EPD213R_104x212_d, + EPD213_104x212, + EPD213_122x250, // waveshare + EPD213B_122x250, // GDEY0213B74 + EPD154_152x152, // GDEW0154M10 + EPD154R_152x152, + EPD154Y_152x152, // DEPG0154YN + EPD154_200x200, // waveshare + EPD154B_200x200, // DEPG01540BN + EPD27_176x264, // waveshare + EPD266B_152x296, // DEPG0266BN + EPD266Y_152x296, // DEPG0266YN + EPD31R_168x296, // DEPG0310RW + EPD37Y_240x416, // DEPG0370YN + EPD37_240x416, // GDEY037T03 + EPD579_792x272, // GDEY0579T93 + EPD583R_600x448, + EPD75_800x480, // GDEY075T7 + EPD74R_640x384, + EPD583_648x480, // DEPG0583BN + EPD29_BWYR_128x296, // GDEY029F51 + EPD29_BWYR_168x384, // GDEY029F51H + EPD266_BWYR_184x360, // GDEY0266F51 + EPD30_BWYR_168x400, // Waveshare 3" B/W/Y/R + EPD164_BWYR_168x168, // Waveshare 1.64" B/W/Y/R + EPD236_BWYR_168x296, // Waveshare 2.36" B/W/Y/R + EPD47_540x960, // not working yet + EPD35R_184x384, // Hanshow Nebular 3.5" BWR + EPD_PANEL_COUNT +}; +#endif // FUTURE +// flag bits +#define BBEI_INVERTED 0x0001 +#define BBEI_BITBANG 0x0002 +#define BBEI_3COLOR 0x0004 +#define BBEI_4COLOR 0x0008 +#define BBEI_4GRAY 0x0010 +#define BBEI_CS_EVERY_BYTE 0x0020 + +#define BBEI_WHITE 0 +#define BBEI_BLACK 1 +#define BBEI_YELLOW 2 +#define BBEI_RED 3 + +// 4 gray levels +#define BBEI_GRAY0 0 +#define BBEI_GRAY1 1 +#define BBEI_GRAY2 2 +#define BBEI_GRAY3 3 + +// (UC81xx commands) +enum reg_uc81xx { + UC8151_PSR = 0x00, + UC8151_PWR = 0x01, + UC8151_POFF = 0x02, + UC8151_PFS = 0x03, + UC8151_PON = 0x04, + UC8151_PMES = 0x05, + UC8151_BTST = 0x06, + UC8151_DSLP = 0x07, + UC8151_DTM1 = 0x10, + UC8151_DSP = 0x11, + UC8151_DRF = 0x12, + UC8151_DTM2 = 0x13, + UC8151_LUT_VCOM = 0x20, + UC8151_LUT_WW = 0x21, + UC8151_LUT_BW = 0x22, + UC8151_LUT_WB = 0x23, + UC8151_LUT_BB = 0x24, + UC8151_LUT_VCOM2 = 0x25, + UC8151_PLL = 0x30, + UC8151_TSC = 0x40, + UC8151_TSE = 0x41, + UC8151_TSR = 0x43, + UC8151_TSW = 0x42, + UC8151_CDI = 0x50, + UC8151_LPD = 0x51, + UC8151_TCON = 0x60, + UC8151_TRES = 0x61, + UC8151_REV = 0x70, + UC8151_FLG = 0x71, + UC8151_AMV = 0x80, + UC8151_VV = 0x81, + UC8151_VDCS = 0x82, + UC8151_PTL = 0x90, + UC8151_PTIN = 0x91, + UC8151_PTOU = 0x92, + UC8151_PGM = 0xa0, + UC8151_APG = 0xa1, + UC8151_ROTP = 0xa2, + UC8151_CCSET = 0xe0, + UC8151_PWS = 0xe3, + UC8151_TSSET = 0xe5 + }; + +enum reg_ssd16xx { + SSD1608_DRIVER_CONTROL = 0x01, + SSD1608_GATE_VOLTAGE = 0x03, + SSD1608_SOURCE_VOLTAGE = 0x04, + SSD1608_DISPLAY_CONTROL = 0x07, + SSD1608_NON_OVERLAP = 0x0B, + SSD1608_BOOSTER_SOFT_START = 0x0C, + SSD1608_GATE_SCAN_START = 0x0F, + SSD1608_DEEP_SLEEP = 0x10, + SSD1608_DATA_MODE = 0x11, + SSD1608_SW_RESET = 0x12, + SSD1608_TEMP_WRITE = 0x1A, + SSD1608_TEMP_READ = 0x1B, + SSD1608_TEMP_CONTROL = 0x1C, + SSD1608_TEMP_LOAD = 0x1D, + SSD1608_MASTER_ACTIVATE = 0x20, + SSD1608_DISP_CTRL1 = 0x21, + SSD1608_DISP_CTRL2 = 0x22, + SSD1608_WRITE_RAM = 0x24, + SSD1608_WRITE_ALTRAM = 0x26, + SSD1608_READ_RAM = 0x25, + SSD1608_VCOM_SENSE = 0x28, + SSD1608_VCOM_DURATION = 0x29, + SSD1608_WRITE_VCOM = 0x2C, + SSD1608_READ_OTP = 0x2D, + SSD1608_WRITE_LUT = 0x32, + SSD1608_WRITE_DUMMY = 0x3A, + SSD1608_WRITE_GATELINE = 0x3B, + SSD1608_WRITE_BORDER = 0x3C, + SSD1608_SET_RAMXPOS = 0x44, + SSD1608_SET_RAMYPOS = 0x45, + SSD1608_SET_RAMXCOUNT = 0x4E, + SSD1608_SET_RAMYCOUNT = 0x4F, + SSD1608_NOP = 0xFF, +}; + +#define BUSY_WAIT 0xff + +typedef struct bbeistruct +{ +#ifndef MEMORY_ONLY +BBI2C bbi2c; +#endif +uint8_t wrap, flip, invert, type, render, can_flip, chip_type; +uint8_t *ucScreen; +int iCursorX, iCursorY; +int width, height, native_width, native_height; +int iScreenOffset, iOrientation; +int iFG, iBG; //current color +int iFont, iFlags; +int iDataTime, iOpTime; // time in milliseconds for data transmission and operation +uint32_t u32FontScaleX, u32FontScaleY; +uint32_t iSpeed; +uint32_t iTimeout; // for e-ink panels +uint8_t iDCPin, iMOSIPin, iCLKPin, iCSPin, iRSTPin, iBUSYPin; +uint8_t x_offset, y_offset; // memory offsets +uint8_t bBitBang; +const uint8_t *pInitFull; // full update init sequence +const uint8_t *pInitFast; // fast update init sequence +const uint8_t *pInitPart; // partial update init sequence +} BBEIDISP; + +#ifdef __cplusplus +#ifdef _LINUX_ +#include +using namespace std; +class BBEINK +#else // Arduino +#ifndef __AVR__ +class BBEINK : public Print +#else +class BBEINK +#endif // !__AVR__ +#endif // _LINUX_ +{ + public: + BBEINK() { memset(&_bbei, 0, sizeof(_bbei)); _bbei.iFG = BBEI_BLACK; _bbei.render = 1; _bbei.type = EPD_UNDEFINED;} + void initIO(int iCS, int iMOSI, int iSCLK, int iDC, int iReset=-1, int iBusy=-1, uint32_t u32Speed = 8000000); + int setPanelType(int iPanel); + uint32_t capabilities(); + int16_t height(void); + int16_t width(void); + void setPosition(int x, int y, int w, int h); + void sleep(int bDeep); + void wait(bool bQuick = false); + int dataTime(); + int opTime(); + int allocBuffer(void); + void * getBuffer(void); + void setBuffer(uint8_t *pBuffer); + void freeBuffer(void); + void fillScreen(int iColor); + void setRotation(int iAngle); + int getRotation(void); + void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + int16_t getCursorX(void); + int16_t getCursorY(void); + void setCursor(int x, int y); + void setPlane(int iPlane); + void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + void drawCircle(int32_t x, int32_t y, int32_t r, uint32_t color); + void fillCircle(int32_t x, int32_t y, int32_t r, uint32_t color); + void drawEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color); + void fillEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color); + int drawGFX(uint8_t *pSrc, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch); + void drawPixel(int16_t x, int16_t y, uint16_t color); + void drawSprite(uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority); + void drawTile(const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender); + void setTextColor(int iFG, int iBG = -1); + int refresh(int iMode); + int drawEPDGFX(int x, int y, int cx, int cy, uint8_t *pPlane0, uint8_t *pPlane1); + int loadBMP(const uint8_t *pBMP, int x, int y, int iFG, int iBG); + int loadBMP3(const uint8_t *pBMP, int x, int y); + void getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h); + void getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h); + void setTextWrap(bool bWrap); + void setFont(int iFont); + void setFreeFont(const BB_FONT *pFont); + void pushBytes(uint8_t *pByte, int iCount); + void writeCommand(uint8_t ucCMD); + void writeRaw(uint8_t *pData, int iLen); + void pushImage(int x, int y, int w, int h, uint16_t *pixels); + void drawString(const char *pText, int x, int y); + void drawString(String text, int x, int y); + void drawLine(int x1, int y1, int x2, int y2, int iColor); +#ifdef _LINUX_ + void print(const char *pString); + void println(const char *pString); + void print(int, int); + void println(int, int); + void print(const string &); + void println(const string &); + size_t write(uint8_t ucChar); + void delayMicroseconds(int iTime); +#else + using Print::write; + virtual size_t write(uint8_t); +#endif // _LINUX_ + + private: + BBEIDISP _bbei; +}; // class BBEINK +#endif // __cplusplus + +// Make the Linux library interface C instead of C++ +#if defined(_LINUX_) && defined(__cplusplus) +extern "C" { +#endif + +#if !defined(BITBANK_LCD_MODES) +#define BITBANK_LCD_MODES +typedef enum +{ + MODE_DATA = 0, + MODE_COMMAND +} DC_MODE; +#endif + +void EPDFill(OBDISP *pOBD, uint8_t ucCMD, uint8_t ucPattern); +void EPDSetPosition(OBDISP *pOBD, int x, int y, int cx, int cy); + +// +// Create a virtual display of any size +// The memory buffer must be provided at the time of creation +// +void obdCreateVirtualDisplay(OBDISP *pOBD, int width, int height, uint8_t *buffer); +// +// Draw the contents of a memory buffer onto a display +// The sub-window will be clipped if it specifies too large an area +// for the destination display. The source OBDISP structure must have +// a valid back buffer defined +// The top and bottom destination edges will be drawn on byte boundaries (8 rows) +// The source top/bot edges can be on pixel boundaries +// +void obdDumpWindow(OBDISP *pOBDSrc, OBDISP *pOBDDest, int srcx, int srcy, int destx, int desty, int width, int height); +// +// Write one of more lines to a Sharp memory LCD +// +void obdWriteLCDLines(OBDISP *pOBD, int iStart, int iCount); +// +// Initializes the display controller into "page mode" on I2C +// If SDAPin and SCLPin are not -1, then bit bang I2C on those pins +// Otherwise use the Wire library. +// If you don't need to use a separate reset pin, set it to -1 +// +int obdI2CInit(OBDISP *pOBD, int iType, int iAddr, int bFlip, int bInvert, int bWire, int iSDAPin, int iSCLPin, int iResetPin, int32_t iSpeed); +// +// Initialize an SPI version of the display +// +void obdSPIInit(OBDISP *pOBD, int iType, int iDC, int iCS, int iReset, int iMOSI, int iCLK, int iLED, int bFlip, int bInvert, int iBitBang, int32_t iSpeed); +// +// Set the drawing direction in 90 degree increments +// +void obdSetRotation(OBDISP *pOBD, int iRotation); +// +// Set the memory configuration to display the pixels at 0 or 180 degrees (flipped) +// pass true (1) to flip 180, false (0) to set to 0 +void obdSetFlip(OBDISP *pOBD, int iOnOff); +// +// Provide or revoke a back buffer for your OLED graphics +// This allows you to manage the RAM used by ss_oled on tiny +// embedded platforms like the ATmega series +// Pass NULL to revoke the buffer. Make sure you provide a buffer +// large enough for your display (e.g. 128x64 needs 1K - 1024 bytes) +// +void obdSetBackBuffer(OBDISP *pOBD, uint8_t *pBuffer); +void obdAllocBuffer(OBDISP *pOBD); +// +// Sets the brightness (0=off, 255=brightest) +// +void obdSetContrast(OBDISP *pOBD, unsigned char ucContrast); +// +// Load a 1-bpp Windows bitmap +// Pass the pointer to the beginning of the BMP file +// First pass version assumes a full screen bitmap +// +int obdLoadBMP(OBDISP *pOBD, const uint8_t *pBMP, int x, int y, int iFG, int iBG); +// +// load a 4-bpp Windows bitmap +// into memory for 3-color (BLACK/WHITE/RED) +// e-paper displays +// It does a 'best match' of the colors to +// B/W/R +// +int obdLoadBMP3(OBDISP *pOBD, const uint8_t *pBMP, int dx, int dy); +// +// Power up/down the display +// useful for low power situations +// +void obdPower(OBDISP *pOBD, int bOn); +// +// Set the current cursor position +// The column represents the pixel column (0-127) +// The row represents the text row (0-7) +// +void obdSetCursor(OBDISP *pOBD, int x, int y); + +// +// Turn text wrap on or off for the obdWriteString() function +// +void obdSetTextWrap(OBDISP *pOBD, int bWrap); +// +// Advance to the next line +// +void obdNextLine(OBDISP *pOBD); +// +// Draw a string of normal (8x8), small (6x8) or large (16x32) characters +// At the given col+row with the given scroll offset. The scroll offset allows you to +// horizontally scroll text which does not fit on the width of the display. The offset +// represents the pixels to skip when drawing the text. An offset of 0 starts at the beginning +// of the text. +// The system remembers where the last text was written (the cursor position) +// To continue writing from the last position, set the x,y values to -1 +// The text can optionally wrap around to the next line by calling oledSetTextWrap(true); +// otherwise text which would go off the right edge will not be drawn and the cursor will +// be left "off screen" until set to a new position explicitly +// +// Returns 0 for success, -1 for invalid parameter +// +int obdWriteString(OBDISP *pOBD, int iScrollX, int x, int y, char *szMsg, int iSize, int iColor, int bRender); +// +// Draw a string with a fractional scale in both dimensions +// the scale is a 16-bit integer with and 8-bit fraction and 8-bit mantissa +// To draw at 1x scale, set the scale factor to 256. To draw at 2x, use 512 +// The output must be drawn into a memory buffer, not directly to the display +// The string can be drawn in one of 4 rotations (ROT_0, ROT_90, ROT_180, ROT_270) +// +int obdScaledString(OBDISP *pOBD, int x, int y, char *szMsg, int iSize, int iColor, int iXScale, int iYScale, int iRotation); +// +// Draw a string in a proportional font you supply +// Requires a back buffer +// +int obdWriteStringCustom(OBDISP *pOBD, GFXfont *pFont, int x, int y, char *szMsg, uint8_t ucColor); +// +// Get the width of text in a custom font +// +void obdGetStringBox(GFXfont *pFont, char *szMsg, int *width, int *top, int *bottom); +// +// Fill the frame buffer with a byte pattern +// e.g. all off (0x00) or all on (0xff) +// +void obdFill(OBDISP *pOBD, unsigned char ucData, int bRender); +// +// Set (or clear) an individual pixel +// The local copy of the frame buffer is used to avoid +// reading data from the display controller +// (which isn't possible in most configurations) +// This function needs the USE_BACKBUFFER macro to be defined +// otherwise, new pixels will erase old pixels within the same byte +// +int obdSetPixel(OBDISP *pOBD, int x, int y, unsigned char ucColor, int bRender); +// +// Dump a portion of the screen +// optional buffer pointer if no back buffer +// +int obdDumpPartial(OBDISP *pOBD, int startx, int starty, int width, int height, uint8_t *pBuffer); +// +// Dump an entire custom buffer to the display +// useful for custom animation effects +// +int obdDumpBuffer(OBDISP *pOBD, uint8_t *pBuffer, int bRefresh, int bWait, int bFast); +// +// Render a window of pixels from a provided buffer or the library's internal buffer +// to the display. The row values refer to byte rows, not pixel rows due to the memory +// layout of OLEDs. Pass a src pointer of NULL to use the internal backing buffer +// returns 0 for success, -1 for invalid parameter +// +int obdDrawGFX(OBDISP *pOBD, uint8_t *pSrc, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch); +// +// Set the current custom font pointers for playing back +// bytewise commands +// +void obdSetCustomFont(OBDISP *pOBD, GFXfont *pFont, uint8_t ucFont); +// +// Execute a set of bytewise command bytes +// and execute the drawing instructions on the current display/buffer +// Optionally render on backbuffer or physical display +// +void obdExecCommands(uint8_t *pData, int iLen, OBDISP *pOBD, int bRender); +// +// Return the number of bytes accumulated as commands +// +int obdGetCommandLen(OBDISP *pOBD); +// +// Draw a line between 2 points +// +void obdDrawLine(OBDISP *pOBD, int x1, int y1, int x2, int y2, uint8_t ucColor, int bRender); +// +// Play a frame of animation data +// The animation data is assumed to be encoded for a full frame of the display +// Given the pointer to the start of the compressed data, +// it returns the pointer to the start of the next frame +// Frame rate control is up to the calling program to manage +// When it finishes the last frame, it will start again from the beginning +// +uint8_t * obdPlayAnimFrame(OBDISP *pOBD, uint8_t *pAnimation, uint8_t *pCurrent, int iLen); +void obdWriteCommand(OBDISP *pOBD, unsigned char c); +void obdSetPosition(OBDISP *pOBD, int x, int y, int bRender); +void obdWriteDataBlock(OBDISP *pOBD, unsigned char *ucBuf, int iLen, int bRender); +void RawWriteData(OBDISP *pOBD, unsigned char *ucBuf, int iLen); +// +// Scroll the internal buffer by 1 scanline (up/down) +// width is in pixels, lines is group of 8 rows +// Returns 0 for success, -1 for invalid parameter +// +int obdScrollBuffer(OBDISP *pOBD, int iStartCol, int iEndCol, int iStartRow, int iEndRow, int bUp); +// +// Draw a sprite of any size in any position +// If it goes beyond the left/right or top/bottom edges +// it's trimmed to show the valid parts +// This function requires a back buffer to be defined +// The priority color (0 or 1) determines which color is painted +// when a 1 is encountered in the source image. +// e.g. when 0, the input bitmap acts like a mask to clear +// the destination where bits are set. +// +void obdDrawSprite(OBDISP *pOBD, uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority); + +// +// Draw 1 or 2 planes of raw image into a specific spot +// in e-paper memory +// +int obdDrawEPDGFX(OBDISP *pOBD, int x, int y, int cx, int cy, uint8_t *pPlane0, uint8_t *pPlane1); + +// +// Draw a 16x16 tile in any of 4 rotated positions +// Assumes input image is laid out like "normal" graphics with +// the MSB on the left and 2 bytes per line +// On AVR, the source image is assumed to be in FLASH memory +// The function can draw the tile on byte boundaries, so the x value +// can be from 0 to 112 and y can be from 0 to 6 +// +void obdDrawTile(OBDISP *pOBD, const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender); +// +// Draw an outline or filled ellipse +// +void obdEllipse(OBDISP *pOBD, int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled); +// +// Draw an outline or filled rectangle +// +void obdRectangle(OBDISP *pOBD, int x1, int y1, int x2, int y2, uint8_t ucColor, uint8_t bFilled); + +// +// Turn the LCD backlight on or off +// +void obdBacklight(OBDISP *pODB, int bOn); + +// +// Menu functions +// +// Initialize the simple menu structure +// +int obdMenuInit(OBDISP *pOBD, SIMPLEMENU *sm, char **pText, int iFontSize, int bCenter, int btnUp, int btnDn, int btnEnter, int iPressedState, int bIsRotary); +// +// Erase the display and show the given menu +// +void obdMenuShow(SIMPLEMENU *sm, int iItem); +// +// Set a callback function to return custom info/status +// for each menu item +// +void obdMenuSetCallback(SIMPLEMENU *sm, SIMPLECALLBACK pfnCallBack); +// +// Display the text of a single menu item +// optionally erases what's under it to prevent left-over text when the length changes +// +void obdMenuShowItem(OBDISP *pOBD, int x, int y, char *szText, int bInvert, int bErase, int iFontSize, int bRender); +// +// Change the menu index incrementally +// redraws the minimum amount of screen to show the new info +// (this prevents flicker/flash and saves battery life) +// returns the new menu index +// +int obdMenuDelta(SIMPLEMENU *sm, int iDelta); +// +// With the given setup, check for button presses +// and act accordingly +// returns -1 for normal interactions and the menu item index if the user presses the ENTER button +// +int obdMenuRun(SIMPLEMENU *sm); + +#if defined(_LINUX_) && defined(__cplusplus) +} +#endif // _LINUX_ + +#endif // __ONEBITDISPLAY__ +