
74 changed files with 20029 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||
.pio |
|||
.vscode |
@ -0,0 +1,3 @@ |
|||
cmake_minimum_required(VERSION 3.16.0) |
|||
include($ENV{IDF_PATH}/tools/cmake/project.cmake) |
|||
project(ESP-IDF-InkPlate) |
@ -1 +1,68 @@ |
|||
# ESP-IDF-InkPlate |
|||
|
|||
A porting effort for the e-Radionica InkPlate software that can be find [here](https://github.com/e-radionicacom/Inkplate-Arduino-library) to the ESP-IDF framework. |
|||
|
|||
--> Work in progress. Not ready yet. <-- |
|||
|
|||
Beware that these classes are **not** re-entrant. That means that it is not possible to use them in a multi-thread context without proper mutual exclusion access control. |
|||
|
|||
This project is a working example. You can build it using PlatformIO. It will draw a square and a straight line on the device display. |
|||
|
|||
## Modifications done to the Arduino Inkplate Source code |
|||
|
|||
As you can expect, the ESP-IDF framework is quit different than the Arduino framework. The porting effort of the Inkplate source code done here is not just a transformation to make it runs, but also getting it inline with the available packages available with the ESP-IDF. Many aspects must be transformed in this regard. Here is a list of the changes being done: |
|||
|
|||
### Global changes |
|||
|
|||
- Give the overall source code a transformation to use what is called *modern C++*, alligned with C++17 |
|||
- All .h files are renamed .hpp |
|||
- macro definitions (`#define`) are reduced to a minimum |
|||
- `enum class` are used everywhere possible |
|||
- source code filenames are all in lowercase |
|||
|
|||
### SdCard |
|||
|
|||
- The module is used only to initialize the ESP-IDF drivers (SPI and FAT filesystem are used). The card can then be accessed through the standard C++/C capabilities. All filenames located on the card must be prefixed with `/sdcard`. |
|||
|
|||
### Image (.hpp, .cpp) |
|||
|
|||
- the `drawXXXXFromWeb(WiFiClient *s, ...)` methods are no longer available. |
|||
- the `bool drawXXXXFromSd(SdFile *p, ...)` methods are renamed `bool drawXXXXFromFile(File *p, ...)`. |
|||
|
|||
## ESP-IDF configuration specifics |
|||
|
|||
An InkPlate application requires some functionalities to be properly set up within the ESP-IDF. The following elements have been done for this project and can be used as a reference for other projects: |
|||
|
|||
- **PSRAM memory management**: The PSRAM is an extension to the ESP32 memory that offers 4MB+4MB of additional RAM. The first 4MB is readily available to integrate into the dynamic memory allocation of the ESP-IDF SDK. To do so, some parameters located in the `sdkconfig` file must be set accordingly. This must be done using the menuconfig application that is part of the ESP-IDF. The following command will launch the application (the current folder must be the main folder of EPub-InkPlate): |
|||
|
|||
``` |
|||
$ idf.py menuconfig |
|||
``` |
|||
|
|||
The application will show a list of configuration aspects. To configure PSRAM: |
|||
|
|||
- Select `Component Config` > `ESP32-Specific` > `Support for external, SPI-Connected RAM` |
|||
- Select `SPI RAM config` > `Initialize SPI RAM during startup` |
|||
- Select `Run memory test on SPI RAM Initialization` |
|||
- Select `Enable workaround for bug in SPI RAM cache for Rev 1 ESP32s` |
|||
- Select `SPI RAM access method` > `Make RAM allocatable using malloc() as well` |
|||
|
|||
Leave the other options as they are. |
|||
|
|||
- **ESP32 processor speed**: The processor must be run at 240MHz. The following line in `platformio.ini` request this speed: |
|||
|
|||
``` |
|||
board_build.f_cpu = 240000000L |
|||
``` |
|||
You can also select the speed in the sdkconfig file: |
|||
|
|||
- Select `Component config` > `ESP32-Specific` > `CPU frequency` > `240 Mhz` |
|||
|
|||
- **FAT Filesystem Support**: If the application requires the usage of the micro SD card. This card must be formatted on a computer (Linux or Windows) with a FAT 32 partition. The InkPlate6Ctrl class requires this in its current configuration. The following parameters must be adjusted in `sdkconfig`: |
|||
|
|||
- Select `Component config` > `FAT Filesystem support` > `Max Long filename length` > `255` |
|||
- Select `Number of simultaneously open files protected by lock function` > `5` |
|||
- Select `Prefer external RAM when allocating FATFS buffer` |
|||
|
|||
- **Flash memory partitioning (optional)**: the file `partitions.csv` contains the table of partitions definition for the 4MB flash memory, required to support the largest applications size in an OTA context. The partitions OTA_0 and OTA_1 have been set to be 1.3MB in size. In the `platformio.ini` file, the line `board_build.partitions=...` is directing the use of these partitions configuration. |
|||
|
@ -0,0 +1,47 @@ |
|||
@startuml "Inkplate 6 Arduino Library" |
|||
|
|||
package "Inkplate6-Arduino" <<rectangle>> { |
|||
|
|||
package "Arduino" <<frame>> { |
|||
class Print |
|||
class Wire |
|||
} |
|||
|
|||
package "Adafruit-GFX" <<folder>> { |
|||
abstract class Adafruit_GFX |
|||
Print <|-- Adafruit_GFX |
|||
} |
|||
|
|||
package "Include" <<folder>> { |
|||
together group2 { |
|||
abstract class Graphics |
|||
abstract class Image |
|||
abstract class Shapes |
|||
} |
|||
|
|||
together group1 { |
|||
class NetworkClient |
|||
class MCP |
|||
class System |
|||
} |
|||
|
|||
group1 -[hidden]- group2 |
|||
Adafruit_GFX <|-- Image |
|||
NetworkClient <|-- Image |
|||
Image <|-- Graphics |
|||
Shapes <|-- Graphics |
|||
MCP <|-- System |
|||
Wire <.. MCP |
|||
NetworkClient <|-- System |
|||
} |
|||
|
|||
package "Src" <<folder>> { |
|||
class Inkplate |
|||
Graphics <|-- Inkplate |
|||
System <|-- Inkplate |
|||
} |
|||
|
|||
group1 -[hidden]-> Inkplate |
|||
} |
|||
|
|||
@enduml |
@ -0,0 +1,115 @@ |
|||
@startuml "Inkplate ESP-IDF Library" |
|||
|
|||
package "Inkplate-ESP-IDF" <<rectangle>> { |
|||
|
|||
package "ESP-IDF Framework" <<rectangle>> { |
|||
} |
|||
|
|||
package "Graphical" <<folder>> { |
|||
class Print |
|||
abstract class Adafruit_GFX |
|||
together group2 { |
|||
abstract class Graphics |
|||
abstract class Image |
|||
class Shapes |
|||
} |
|||
Print <|-- Adafruit_GFX |
|||
} |
|||
|
|||
package "ESP-IDF Services" <<folder>> { |
|||
class NetworkClient |
|||
class SDCard << (S,#FF7700) Singleton >> |
|||
class Wire << (S,#FF7700) Singleton >> |
|||
} |
|||
|
|||
"ESP-IDF Framework" <.. "ESP-IDF Services" |
|||
|
|||
package "Drivers" <<folder>> { |
|||
|
|||
together { |
|||
class MCP23017 << (S,#FF7700) Singleton >> |
|||
class TPS65186 << (S,#FF7700) Singleton >> |
|||
} |
|||
together { |
|||
class TouchScreen << (S,#FF7700) Singleton >> |
|||
class TouchKeys << (S,#FF7700) Singleton >> |
|||
} |
|||
|
|||
together { |
|||
abstract class EInk |
|||
|
|||
package "EInkKind" <<folder>> { |
|||
class EInk6 << (S,#FF7700) Singleton >> |
|||
class EInk6Plus << (S,#FF7700) Singleton >> |
|||
class EInk10 << (S,#FF7700) Singleton >> |
|||
EInk6 -[hidden]-> EInk10 |
|||
EInk10 -[hidden]-> EInk6Plus |
|||
} |
|||
} |
|||
' MCP23017 -[hidden]-> TouchKeys |
|||
|
|||
EInk -[hidden]> TouchKeys |
|||
TouchKeys -[hidden]- TouchScreen |
|||
|
|||
Wire <.. MCP23017 |
|||
Wire <.. TPS65186 |
|||
TPS65186 <.. EInk |
|||
MCP23017 <.. EInk |
|||
MCP23017 <.. TouchKeys |
|||
MCP23017 <.. TouchScreen |
|||
EInk <|-- EInkKind |
|||
|
|||
} |
|||
|
|||
"EInkKind" -right[hidden] EInk |
|||
|
|||
package "InkPlate" <<folder>> { |
|||
|
|||
together group1 { |
|||
class InkPlatePlatform << (S,#FF7700) Singleton >> |
|||
class Inkplate << (S,#FF7700) Singleton >> |
|||
} |
|||
|
|||
Graphics <|-- Inkplate |
|||
InkPlatePlatform <.. Inkplate |
|||
Adafruit_GFX <|-- Image |
|||
Image <|-- Graphics |
|||
Shapes <|-- Graphics |
|||
Drivers <.. InkPlatePlatform |
|||
"ESP-IDF Services" <.. InkPlatePlatform |
|||
|
|||
' EInkKind <.. InkPlatePlatform |
|||
|
|||
' EInk <.. Inkplate |
|||
} |
|||
|
|||
"Drivers" -left[hidden]- "Inkplate" |
|||
|
|||
package "User App" <<folder>> { |
|||
class MainApp |
|||
} |
|||
|
|||
Inkplate --o MainApp |
|||
|
|||
package "Bitmaps" <<folder>> { |
|||
abstract class Bitmap |
|||
abstract class Bitmap1Bit |
|||
abstract class Bitmap3Bit |
|||
class Bitmap1BitX |
|||
class Bitmap3BitX |
|||
Bitmap <|-- Bitmap1Bit |
|||
Bitmap <|-- Bitmap3Bit |
|||
Bitmap1Bit <|-- Bitmap1BitX |
|||
Bitmap3Bit <|-- Bitmap3BitX |
|||
} |
|||
|
|||
' Bitmaps <|-- EInkKind |
|||
' Bitmaps -right[hidden]- EInkKind |
|||
Bitmap1Bit --o Graphics |
|||
Graphical -left[hidden]- Bitmaps |
|||
' Inkplate -left[hidden]- Graphical |
|||
' Inkplate ..> EInk |
|||
|
|||
} |
|||
|
|||
@enduml |
@ -0,0 +1,37 @@ |
|||
@startuml "ESP-IDF-InkPlate-Port" |
|||
package "InkPlate Low Level" <<folder>> { |
|||
abstract class NonCopyable |
|||
|
|||
class Wire << (S,#FF7700) Singleton >> |
|||
Wire : setup() |
|||
Wire : begin_transmission(addr) |
|||
Wire : read() |
|||
Wire : write(val) |
|||
Wire : end_transmission() |
|||
Wire : request_from(addr, size) |
|||
|
|||
class MCP << (S,#FF7700) Singleton >> |
|||
|
|||
class EInk << (S,#FF7700) Singleton >> |
|||
EInk : WIDTH |
|||
EInk : HEIGHT |
|||
EInk : typedef Bitmap1Bit |
|||
EInk : typedef Bitmap3Bit |
|||
EInk : setup() |
|||
EInk : update(Bitmap1bit &) |
|||
EInk : update(Bitmap3bit &) |
|||
EInk : partial_update(Bitmap1bit &) |
|||
EInk : partial_update(Bitmap3bit &) |
|||
EInk : clear_bitmap(Bitmap1bit &) |
|||
EInk : clear_bitmap(Bitmap3bit &) |
|||
EInk : clean() |
|||
EInk : read_power_good() |
|||
EInk : read_temperature() |
|||
|
|||
NonCopyable <|-- Wire |
|||
NonCopyable <|-- MCP |
|||
NonCopyable <|-- EInk |
|||
Wire o-- MCP |
|||
MCP o-- EInk |
|||
} |
|||
@enduml |
@ -0,0 +1,39 @@ |
|||
|
|||
This directory is intended for project header files. |
|||
|
|||
A header file is a file containing C declarations and macro definitions |
|||
to be shared between several project source files. You request the use of a |
|||
header file in your project source file (C, C++, etc) located in `src` folder |
|||
by including it, with the C preprocessing directive `#include'. |
|||
|
|||
```src/main.c |
|||
|
|||
#include "header.h" |
|||
|
|||
int main (void) |
|||
{ |
|||
... |
|||
} |
|||
``` |
|||
|
|||
Including a header file produces the same results as copying the header file |
|||
into each source file that needs it. Such copying would be time-consuming |
|||
and error-prone. With a header file, the related declarations appear |
|||
in only one place. If they need to be changed, they can be changed in one |
|||
place, and programs that include the header file will automatically use the |
|||
new version when next recompiled. The header file eliminates the labor of |
|||
finding and changing all the copies as well as the risk that a failure to |
|||
find one copy will result in inconsistencies within a program. |
|||
|
|||
In C, the usual convention is to give header files names that end with `.h'. |
|||
It is most portable to use only letters, digits, dashes, and underscores in |
|||
header file names, and at most one dot. |
|||
|
|||
Read more about using header files in official GCC documentation: |
|||
|
|||
* Include Syntax |
|||
* Include Operation |
|||
* Once-Only Headers |
|||
* Computed Includes |
|||
|
|||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html |
@ -0,0 +1,46 @@ |
|||
|
|||
This directory is intended for project specific (private) libraries. |
|||
PlatformIO will compile them to static libraries and link into executable file. |
|||
|
|||
The source code of each library should be placed in a an own separate directory |
|||
("lib/your_library_name/[here are source files]"). |
|||
|
|||
For example, see a structure of the following two libraries `Foo` and `Bar`: |
|||
|
|||
|--lib |
|||
| | |
|||
| |--Bar |
|||
| | |--docs |
|||
| | |--examples |
|||
| | |--src |
|||
| | |- Bar.c |
|||
| | |- Bar.h |
|||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html |
|||
| | |
|||
| |--Foo |
|||
| | |- Foo.c |
|||
| | |- Foo.h |
|||
| | |
|||
| |- README --> THIS FILE |
|||
| |
|||
|- platformio.ini |
|||
|--src |
|||
|- main.c |
|||
|
|||
and a contents of `src/main.c`: |
|||
``` |
|||
#include <Foo.h> |
|||
#include <Bar.h> |
|||
|
|||
int main (void) |
|||
{ |
|||
... |
|||
} |
|||
|
|||
``` |
|||
|
|||
PlatformIO Library Dependency Finder will find automatically dependent |
|||
libraries scanning project source files. |
|||
|
|||
More information about PlatformIO Library Dependency Finder |
|||
- https://docs.platformio.org/page/librarymanager/ldf.html |
@ -0,0 +1,17 @@ |
|||
{ |
|||
"name": "inkplate_drivers", |
|||
"frameworks": "espidf", |
|||
"platforms": "espressif32", |
|||
|
|||
"export": { |
|||
"include": [ |
|||
"src/*.hpp", |
|||
"src/*.h" |
|||
] |
|||
}, |
|||
|
|||
"dependencies": [ |
|||
{ "name": "services" }, |
|||
{ "name": "tools" } |
|||
] |
|||
} |
@ -0,0 +1,35 @@ |
|||
#define __BATTERY__ 1 |
|||
#include "battery.hpp" |
|||
|
|||
#include "wire.hpp" |
|||
|
|||
Battery Battery::singleton; |
|||
|
|||
bool |
|||
Battery::setup() |
|||
{ |
|||
mcp.set_direction(BATTERY_SWITCH, MCP::PinMode::OUTPUT); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
double |
|||
Battery::read_level() |
|||
{ |
|||
Wire::enter(); |
|||
mcp.digital_write(BATTERY_SWITCH, MCP::SignalLevel::HIGH); |
|||
Wire::leave(); |
|||
|
|||
ESP::delay(1); |
|||
|
|||
adc1_config_width(ADC_WIDTH_BIT_12); |
|||
adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11); |
|||
|
|||
int16_t adc = ESP::analog_read(ADC1_CHANNEL_7); // ADC 1 Channel 7 is GPIO port 35
|
|||
|
|||
Wire::enter(); |
|||
mcp.digital_write(BATTERY_SWITCH, MCP::SignalLevel::LOW); |
|||
Wire::leave(); |
|||
|
|||
return (double(adc) * 3.3 * 2) / 4095.0; |
|||
} |
@ -0,0 +1,30 @@ |
|||
#ifndef __BATTERY_HPP__ |
|||
#define __BATTERY_HPP__ |
|||
|
|||
#include "non_copyable.hpp" |
|||
#include "mcp.hpp" |
|||
|
|||
class Battery : NonCopyable |
|||
{ |
|||
public: |
|||
static inline Battery & get_singleton() noexcept { return singleton; } |
|||
|
|||
bool setup(); |
|||
|
|||
double read_level(); |
|||
|
|||
private: |
|||
static constexpr char const * TAG = "Battery"; |
|||
static Battery singleton; |
|||
Battery() {} |
|||
|
|||
const MCP::Pin BATTERY_SWITCH = MCP::Pin::IOPIN_9; |
|||
}; |
|||
|
|||
#ifdef __BATTERY__ |
|||
Battery & battery = Battery::get_singleton(); |
|||
#else |
|||
extern Battery & battery; |
|||
#endif |
|||
|
|||
#endif |
@ -0,0 +1,93 @@ |
|||
#ifndef __EINK_HPP__ |
|||
#define __EINK_HPP__ |
|||
|
|||
#include "logging.hpp" |
|||
|
|||
class Bitmap |
|||
{ |
|||
protected: |
|||
static constexpr char const * TAG = "Bitmap"; |
|||
const int32_t data_size; |
|||
const int16_t width, height, line_size; |
|||
const uint8_t init_value; |
|||
|
|||
public: |
|||
Bitmap(int16_t w, int16_t h, int32_t s, uint8_t i) : |
|||
data_size(s), width(w), height(h), line_size(s / h), init_value(i) {} |
|||
|
|||
inline int16_t get_width() { return width; } |
|||
inline int16_t get_height() { return height; } |
|||
inline int32_t get_data_size() { return data_size; } |
|||
inline int16_t get_line_size() { return line_size; } |
|||
inline uint8_t get_init_value() { return init_value; } |
|||
|
|||
void clear() { |
|||
// ESP_LOGD(TAG, "Clear: %08x, with: %02x, size: %d", (int)get_data(), (int)get_init_value(), (int)get_data_size());
|
|||
memset(get_data(), get_init_value(), get_data_size()); |
|||
} |
|||
|
|||
virtual uint8_t * get_data() = 0; |
|||
}; |
|||
|
|||
class Bitmap1Bit : public Bitmap |
|||
{ |
|||
public: |
|||
Bitmap1Bit(int16_t w, int16_t h, int32_t s) : Bitmap(w, h, s, 0) {} |
|||
}; |
|||
|
|||
class Bitmap3Bit : public Bitmap |
|||
{ |
|||
public: |
|||
Bitmap3Bit(int16_t w, int16_t h, int32_t s) : Bitmap(w, h, s, (uint8_t) 0x77) {} |
|||
}; |
|||
|
|||
class EInk |
|||
{ |
|||
public: |
|||
|
|||
enum class PanelState { OFF, ON }; |
|||
|
|||
inline PanelState get_panel_state() { return panel_state; } |
|||
inline bool is_initialized() { return initialized; } |
|||
|
|||
virtual inline Bitmap1Bit * new_bitmap1bit() = 0; |
|||
virtual inline Bitmap3Bit * new_bitmap3bit() = 0; |
|||
|
|||
virtual inline int16_t get_width() = 0; |
|||
virtual inline int16_t get_height() = 0; |
|||
|
|||
// All the following methods are protecting the I2C device interface trough
|
|||
// the Wire::enter() and Wire::leave() methods. These are implementing a
|
|||
// Mutex semaphore access control.
|
|||
//
|
|||
// If you ever add public methods, you *MUST* consider adding calls to Wire::enter()
|
|||
// and Wire::leave() and insure no deadlock will happen... or modify the mutex to use
|
|||
// a recursive mutex.
|
|||
|
|||
virtual bool setup() = 0; |
|||
|
|||
inline void update(Bitmap1Bit & bitmap) { update_1bit(bitmap); } |
|||
inline void update(Bitmap3Bit & bitmap) { update_3bit(bitmap); } |
|||
|
|||
virtual void partial_update(Bitmap1Bit & bitmap) = 0; |
|||
|
|||
virtual void clean() = 0; |
|||
|
|||
int8_t read_temperature(); |
|||
|
|||
protected: |
|||
|
|||
EInk() : |
|||
panel_state(PanelState::OFF), |
|||
initialized(false), |
|||
partial_allowed(false) {} |
|||
|
|||
PanelState panel_state; |
|||
bool initialized; |
|||
bool partial_allowed; |
|||
|
|||
virtual void update_1bit(Bitmap1Bit & bitmap) = 0; |
|||
virtual void update_3bit(Bitmap3Bit & bitmap) = 0; |
|||
}; |
|||
|
|||
#endif |
@ -0,0 +1,738 @@ |
|||
/*
|
|||
eink.cpp |
|||
Inkplate 10 ESP-IDF |
|||
|
|||
Modified by Guy Turcotte |
|||
December 23, 2020 |
|||
|
|||
from the Arduino Library: |
|||
|
|||
David Zovko, Borna Biro, Denis Vajak, Zvonimir Haramustek @ e-radionica.com |
|||
September 24, 2020 |
|||
https://github.com/e-radionicacom/Inkplate-6-Arduino-library
|
|||
|
|||
For support, please reach over forums: forum.e-radionica.com/en |
|||
For more info about the product, please check: www.inkplate.io |
|||
|
|||
This code is released under the GNU Lesser General Public License v3.0: https://www.gnu.org/licenses/lgpl-3.0.en.html
|
|||
Please review the LICENSE file included with this example. |
|||
If you have any questions about licensing, please contact techsupport@e-radionica.com |
|||
Distributed as-is; no warranty is given. |
|||
*/ |
|||
|
|||
#ifdef INKPLATE_10 |
|||
|
|||
#define __EINK10__ 1 |
|||
#include "eink10.hpp" |
|||
|
|||
#include "logging.hpp" |
|||
|
|||
#include "wire.hpp" |
|||
#include "mcp.hpp" |
|||
#include "esp.hpp" |
|||
|
|||
#include <iostream> |
|||
|
|||
EInk10 EInk10::singleton; |
|||
|
|||
const uint8_t EInk10::WAVEFORM_3BIT[8][8] = { |
|||
{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, |
|||
{0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, |
|||
{1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0} }; |
|||
|
|||
const uint32_t EInk10::WAVEFORM[50] = { |
|||
0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60A81898, 0x60A8A8A8, 0x60A8A8A8, 0x6068A868, 0x6868A868, |
|||
0x6868A868, 0x68686868, 0x6A686868, 0x5A686868, 0x5A686868, 0x5A586A68, 0x5A5A6A68, 0x5A5A6A68, 0x55566A68, |
|||
0x55565A64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595, |
|||
0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xA4949494, 0x9494A4A4, 0x84A49494, 0x84948484, 0x84848484, |
|||
0x84848484, 0x84848484, 0xA5A48484, 0xA9A4A4A8, 0xA9A8A8A8, 0xA5A9A9A4, 0xA5A5A5A4, 0xA1A5A5A1, 0xA9A9A9A9, |
|||
0xA9A9A9A9, 0xA9A9A9A9, 0xA9A9A9A9, 0x15151515, 0x11111111 }; |
|||
|
|||
const uint8_t EInk10::LUT2[16] = { |
|||
0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, |
|||
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55 }; |
|||
|
|||
const uint8_t EInk10::LUTW[16] = { |
|||
0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA, |
|||
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA }; |
|||
|
|||
const uint8_t EInk10::LUTB[16] = { |
|||
0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5, |
|||
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55 }; |
|||
|
|||
// PIN_LUT built from the following:
|
|||
//
|
|||
// for (uint32_t i = 0; i < 256; i++) {
|
|||
// PIN_LUT[i] = ((i & 0b00000011) << 4) |
|
|||
// (((i & 0b00001100) >> 2) << 18) |
|
|||
// (((i & 0b00010000) >> 4) << 23) |
|
|||
// (((i & 0b11100000) >> 5) << 25);
|
|||
// }
|
|||
|
|||
const uint32_t EInk10::PIN_LUT[256] = { |
|||
0x00000000, 0x00000010, 0x00000020, 0x00000030, 0x00040000, 0x00040010, 0x00040020, 0x00040030, |
|||
0x00080000, 0x00080010, 0x00080020, 0x00080030, 0x000c0000, 0x000c0010, 0x000c0020, 0x000c0030, |
|||
0x00800000, 0x00800010, 0x00800020, 0x00800030, 0x00840000, 0x00840010, 0x00840020, 0x00840030, |
|||
0x00880000, 0x00880010, 0x00880020, 0x00880030, 0x008c0000, 0x008c0010, 0x008c0020, 0x008c0030, |
|||
0x02000000, 0x02000010, 0x02000020, 0x02000030, 0x02040000, 0x02040010, 0x02040020, 0x02040030, |
|||
0x02080000, 0x02080010, 0x02080020, 0x02080030, 0x020c0000, 0x020c0010, 0x020c0020, 0x020c0030, |
|||
0x02800000, 0x02800010, 0x02800020, 0x02800030, 0x02840000, 0x02840010, 0x02840020, 0x02840030, |
|||
0x02880000, 0x02880010, 0x02880020, 0x02880030, 0x028c0000, 0x028c0010, 0x028c0020, 0x028c0030, |
|||
0x04000000, 0x04000010, 0x04000020, 0x04000030, 0x04040000, 0x04040010, 0x04040020, 0x04040030, |
|||
0x04080000, 0x04080010, 0x04080020, 0x04080030, 0x040c0000, 0x040c0010, 0x040c0020, 0x040c0030, |
|||
0x04800000, 0x04800010, 0x04800020, 0x04800030, 0x04840000, 0x04840010, 0x04840020, 0x04840030, |
|||
0x04880000, 0x04880010, 0x04880020, 0x04880030, 0x048c0000, 0x048c0010, 0x048c0020, 0x048c0030, |
|||
0x06000000, 0x06000010, 0x06000020, 0x06000030, 0x06040000, 0x06040010, 0x06040020, 0x06040030, |
|||
0x06080000, 0x06080010, 0x06080020, 0x06080030, 0x060c0000, 0x060c0010, 0x060c0020, 0x060c0030, |
|||
0x06800000, 0x06800010, 0x06800020, 0x06800030, 0x06840000, 0x06840010, 0x06840020, 0x06840030, |
|||
0x06880000, 0x06880010, 0x06880020, 0x06880030, 0x068c0000, 0x068c0010, 0x068c0020, 0x068c0030, |
|||
0x08000000, 0x08000010, 0x08000020, 0x08000030, 0x08040000, 0x08040010, 0x08040020, 0x08040030, |
|||
0x08080000, 0x08080010, 0x08080020, 0x08080030, 0x080c0000, 0x080c0010, 0x080c0020, 0x080c0030, |
|||
0x08800000, 0x08800010, 0x08800020, 0x08800030, 0x08840000, 0x08840010, 0x08840020, 0x08840030, |
|||
0x08880000, 0x08880010, 0x08880020, 0x08880030, 0x088c0000, 0x088c0010, 0x088c0020, 0x088c0030, |
|||
0x0a000000, 0x0a000010, 0x0a000020, 0x0a000030, 0x0a040000, 0x0a040010, 0x0a040020, 0x0a040030, |
|||
0x0a080000, 0x0a080010, 0x0a080020, 0x0a080030, 0x0a0c0000, 0x0a0c0010, 0x0a0c0020, 0x0a0c0030, |
|||
0x0a800000, 0x0a800010, 0x0a800020, 0x0a800030, 0x0a840000, 0x0a840010, 0x0a840020, 0x0a840030, |
|||
0x0a880000, 0x0a880010, 0x0a880020, 0x0a880030, 0x0a8c0000, 0x0a8c0010, 0x0a8c0020, 0x0a8c0030, |
|||
0x0c000000, 0x0c000010, 0x0c000020, 0x0c000030, 0x0c040000, 0x0c040010, 0x0c040020, 0x0c040030, |
|||
0x0c080000, 0x0c080010, 0x0c080020, 0x0c080030, 0x0c0c0000, 0x0c0c0010, 0x0c0c0020, 0x0c0c0030, |
|||
0x0c800000, 0x0c800010, 0x0c800020, 0x0c800030, 0x0c840000, 0x0c840010, 0x0c840020, 0x0c840030, |
|||
0x0c880000, 0x0c880010, 0x0c880020, 0x0c880030, 0x0c8c0000, 0x0c8c0010, 0x0c8c0020, 0x0c8c0030, |
|||
0x0e000000, 0x0e000010, 0x0e000020, 0x0e000030, 0x0e040000, 0x0e040010, 0x0e040020, 0x0e040030, |
|||
0x0e080000, 0x0e080010, 0x0e080020, 0x0e080030, 0x0e0c0000, 0x0e0c0010, 0x0e0c0020, 0x0e0c0030, |
|||
0x0e800000, 0x0e800010, 0x0e800020, 0x0e800030, 0x0e840000, 0x0e840010, 0x0e840020, 0x0e840030, |
|||
0x0e880000, 0x0e880010, 0x0e880020, 0x0e880030, 0x0e8c0000, 0x0e8c0010, 0x0e8c0020, 0x0e8c0030 |
|||
}; |
|||
|
|||
bool |
|||
EInk10::setup() |
|||
{ |
|||
ESP_LOGD(TAG, "Initializing..."); |
|||
|
|||
if (initialized) return true; |
|||
|
|||
wire.setup(); |
|||
|
|||
if (!mcp.setup()) { |
|||
ESP_LOGE(TAG, "Initialization not completed (MCP Issue)."); |
|||
return false; |
|||
} |
|||
else { |
|||
ESP_LOGD(TAG, "MCP initialized."); |
|||
} |
|||
|
|||
Wire::enter(); |
|||
|
|||
mcp.set_direction(VCOM, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(PWRUP, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(WAKEUP, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(GPIO0_ENABLE, MCP::PinMode::OUTPUT); |
|||
mcp.digital_write(GPIO0_ENABLE, MCP::SignalLevel::HIGH); |
|||
|
|||
wakeup_set(); |
|||
|
|||
//ESP_LOGD(TAG, "Power Mgr Init..."); fflush(stdout);
|
|||
|
|||
ESP::delay_microseconds(1800); |
|||
wire.begin_transmission(PWRMGR_ADDRESS); |
|||
wire.write(0x09); |
|||
wire.write(0b00011011); // Power up seq.
|
|||
wire.write(0b00000000); // Power up delay (3mS per rail)
|
|||
wire.write(0b00011011); // Power down seq.
|
|||
wire.write(0b00000000); // Power down delay (6mS per rail)
|
|||
wire.end_transmission(); |
|||
ESP::delay(1); |
|||
|
|||
//ESP_LOGD(TAG, "Power init completed");
|
|||
|
|||
wakeup_clear(); |
|||
|
|||
// CONTROL PINS
|
|||
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_32, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_33, GPIO_MODE_OUTPUT); |
|||
|
|||
mcp.set_direction(OE, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(GMOD, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(SPV, MCP::PinMode::OUTPUT); |
|||
|
|||
// DATA PINS
|
|||
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // D0
|
|||
gpio_set_direction(GPIO_NUM_5, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_18, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_19, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_23, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_25, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_26, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_27, GPIO_MODE_OUTPUT); // D7
|
|||
|
|||
d_memory_new = new_bitmap1bit(); |
|||
p_buffer = (uint8_t *) ESP::ps_malloc(120000); |
|||
|
|||
ESP_LOGD(TAG, "Memory allocation for bitmap buffers."); |
|||
ESP_LOGD(TAG, "d_memory_new: %08x p_buffer: %08x.", (unsigned int)d_memory_new, (unsigned int)p_buffer); |
|||
|
|||
if ((d_memory_new == nullptr) || |
|||
(p_buffer == nullptr)) { |
|||
do { |
|||
ESP_LOGE(TAG, "Unable to complete buffers allocation"); |
|||
ESP::delay(10000); |
|||
} while (true); |
|||
} |
|||
|
|||
Wire::leave(); |
|||
|
|||
d_memory_new->clear(); |
|||
|
|||
memset(p_buffer, 0, 120000); |
|||
|
|||
initialized = true; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void |
|||
EInk10::update_1bit(Bitmap1Bit & bitmap) |
|||
{ |
|||
ESP_LOGD(TAG, "update_1bit..."); |
|||
|
|||
const uint8_t * ptr; |
|||
uint32_t send; |
|||
uint8_t dram; |
|||
|
|||
Wire::enter(); |
|||
|
|||
turn_on(); |
|||
|
|||
clean_fast(0, 1); |
|||
clean_fast(1, 21); |
|||
clean_fast(2, 1); |
|||
clean_fast(0, 12); |
|||
clean_fast(2, 1); |
|||
clean_fast(1, 21); |
|||
clean_fast(2, 1); |
|||
clean_fast(0, 12); |
|||
|
|||
uint8_t * data = bitmap.get_data(); |
|||
|
|||
for (int8_t k = 0; k < 4; k++) { |
|||
ptr = &data[BITMAP_SIZE_1BIT - 1]; |
|||
vscan_start(); |
|||
|
|||
for (uint16_t i = 0; i < HEIGHT; i++) { |
|||
dram = *ptr--; |
|||
send = PIN_LUT[LUTB[dram >> 4]]; |
|||
hscan_start(send); |
|||
send = PIN_LUT[LUTB[dram & 0x0F]]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
|
|||
for (uint16_t j = 0; j < LINE_SIZE_1BIT - 1; j++) { |
|||
dram = *ptr--; |
|||
send = PIN_LUT[LUTB[dram >> 4]]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
send = PIN_LUT[LUTB[dram & 0x0F]]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
} |
|||
|
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
vscan_end(); |
|||
} |
|||
ESP::delay_microseconds(230); |
|||
} |
|||
|
|||
ptr = &data[BITMAP_SIZE_1BIT - 1]; |
|||
vscan_start(); |
|||
|
|||
for (uint16_t i = 0; i < HEIGHT; i++) { |
|||
dram = *ptr--; |
|||
send = PIN_LUT[LUT2[dram >> 4]]; |
|||
hscan_start(send); |
|||
send = PIN_LUT[LUT2[dram & 0x0F]]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
|
|||
for (uint16_t j = 0; j < LINE_SIZE_1BIT - 1; j++) { |
|||
dram = *ptr--; |
|||
send = PIN_LUT[LUT2[dram >> 4]]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
send = PIN_LUT[LUT2[dram & 0x0F]]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
} |
|||
|
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
vscan_end(); |
|||
} |
|||
ESP::delay_microseconds(230); |
|||
|
|||
vscan_start(); |
|||
|
|||
send = PIN_LUT[0]; |
|||
for (uint16_t i = 0; i < HEIGHT; i++) { |
|||
hscan_start(send); |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
|
|||
for (int j = 0; j < LINE_SIZE_1BIT - 1; j++) { |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
} |
|||
|
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
vscan_end(); |
|||
} |
|||
|
|||
ESP::delay_microseconds(230); |
|||
|
|||
vscan_start(); |
|||
turn_off(); |
|||
|
|||
Wire::leave(); |
|||
|
|||
memcpy(d_memory_new->get_data(), bitmap.get_data(), BITMAP_SIZE_1BIT); |
|||
partial_allowed = true; |
|||
} |
|||
|
|||
void |
|||
EInk10::update_3bit(Bitmap3Bit & bitmap) |
|||
{ |
|||
ESP_LOGD(TAG, "Update_3bit..."); |
|||
|
|||
Wire::enter(); |
|||
turn_on(); |
|||
|
|||
clean_fast(0, 1); |
|||
clean_fast(1, 21); |
|||
clean_fast(2, 1); |
|||
clean_fast(0, 12); |
|||
clean_fast(2, 1); |
|||
clean_fast(1, 21); |
|||
clean_fast(2, 1); |
|||
clean_fast(0, 12); |
|||
|
|||
uint8_t * data = bitmap.get_data(); |
|||
|
|||
for (int k = 0; k < 8; k++) { |
|||
|
|||
const uint8_t * dp = &data[BITMAP_SIZE_3BIT - 1]; |
|||
uint32_t send; |
|||
uint8_t pix1; |
|||
uint8_t pix2; |
|||
uint8_t pix3; |
|||
uint8_t pix4; |
|||
uint8_t pixel; |
|||
uint8_t pixel2; |
|||
|
|||
vscan_start(); |
|||
|
|||
for (int i = 0; i < HEIGHT; i++) { |
|||
pixel = 0; |
|||
pixel2 = 0; |
|||
pix1 = *(dp--); |
|||
pix2 = *(dp--); |
|||
pix3 = *(dp--); |
|||
pix4 = *(dp--); |
|||
|
|||
pixel |= (WAVEFORM_3BIT[ pix1 & 0x07][k] << 6) | |
|||
(WAVEFORM_3BIT[(pix1 >> 4) & 0x07][k] << 4) | |
|||
(WAVEFORM_3BIT[ pix2 & 0x07][k] << 2) | |
|||
(WAVEFORM_3BIT[(pix2 >> 4) & 0x07][k] << 0); |
|||
|
|||
pixel2 |= (WAVEFORM_3BIT[ pix3 & 0x07][k] << 6) | |
|||
(WAVEFORM_3BIT[(pix3 >> 4) & 0x07][k] << 4) | |
|||
(WAVEFORM_3BIT[ pix4 & 0x07][k] << 2) | |
|||
(WAVEFORM_3BIT[(pix4 >> 4) & 0x07][k] << 0); |
|||
|
|||
send = PIN_LUT[pixel]; |
|||
hscan_start(send); |
|||
send = PIN_LUT[pixel2]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
|
|||
for (int j = 0; j < (LINE_SIZE_3BIT >> 2) - 1; j++) { |
|||
pixel = 0; |
|||
pixel2 = 0; |
|||
pix1 = *(dp--); |
|||
pix2 = *(dp--); |
|||
pix3 = *(dp--); |
|||
pix4 = *(dp--); |
|||
|
|||
pixel |= (WAVEFORM_3BIT[ pix1 & 0x07][k] << 6) | |
|||
(WAVEFORM_3BIT[(pix1 >> 4) & 0x07][k] << 4) | |
|||
(WAVEFORM_3BIT[ pix2 & 0x07][k] << 2) | |
|||
(WAVEFORM_3BIT[(pix2 >> 4) & 0x07][k] << 0); |
|||
|
|||
pixel2 |= (WAVEFORM_3BIT[ pix3 & 0x07][k] << 6) | |
|||
(WAVEFORM_3BIT[(pix3 >> 4) & 0x07][k] << 4) | |
|||
(WAVEFORM_3BIT[ pix4 & 0x07][k] << 2) | |
|||
(WAVEFORM_3BIT[(pix4 >> 4) & 0x07][k] << 0); |
|||
|
|||
send = PIN_LUT[pixel]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
|
|||
send = PIN_LUT[pixel2]; |
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
} |
|||
|
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
vscan_end(); |
|||
} |
|||
|
|||
ESP::delay_microseconds(230); |
|||
} |
|||
|
|||
clean_fast(3, 1); |
|||
vscan_start(); |
|||
turn_off(); |
|||
|
|||
Wire::leave(); |
|||
} |
|||
|
|||
void |
|||
EInk10::partial_update(Bitmap1Bit & bitmap) |
|||
{ |
|||
if (!partial_allowed) { |
|||
update_1bit(bitmap); |
|||
return; |
|||
} |
|||
|
|||
Wire::enter(); |
|||
|
|||
ESP_LOGD(TAG, "Partial update..."); |
|||
|
|||
uint32_t send; |
|||
uint32_t n = 119999; |
|||
uint16_t pos = BITMAP_SIZE_1BIT - 1; |
|||
uint8_t diffw, diffb; |
|||
|
|||
uint8_t * idata = bitmap.get_data(); |
|||
uint8_t * odata = d_memory_new->get_data(); |
|||
|
|||
for (int i = 0; i < HEIGHT; i++) { |
|||
for (int j = 0; j < LINE_SIZE_1BIT; j++) { |
|||
diffw = odata[pos] & ~idata[pos]; |
|||
diffb = ~odata[pos] & idata[pos]; |
|||
pos--; |
|||
p_buffer[n--] = LUTW[diffw >> 4] & (LUTB[diffb >> 4]); |
|||
p_buffer[n--] = LUTW[diffw & 0x0F] & (LUTB[diffb & 0x0F]); |
|||
} |
|||
} |
|||
|
|||
turn_on(); |
|||
|
|||
for (int k = 0; k < 5; k++) { |
|||
vscan_start(); |
|||
n = 119999; |
|||
|
|||
for (int i = 0; i < HEIGHT; i++) { |
|||
send = PIN_LUT[p_buffer[n--]]; |
|||
hscan_start(send); |
|||
|
|||
for (int j = 0; j < 199; j++) { |
|||
send = PIN_LUT[p_buffer[n--]]; |
|||
GPIO.out_w1ts = send | CL; |
|||
GPIO.out_w1tc = DATA | CL; |
|||
} |
|||
|
|||
GPIO.out_w1ts = send | CL; |
|||
GPIO.out_w1tc = DATA | CL; |
|||
vscan_end(); |
|||
} |
|||
ESP::delay_microseconds(230); |
|||
} |
|||
|
|||
clean_fast(2, 2); |
|||
clean_fast(3, 1); |
|||
|
|||
vscan_start(); |
|||
turn_off(); |
|||
|
|||
Wire::leave(); |
|||
memcpy(d_memory_new->get_data(), bitmap.get_data(), BITMAP_SIZE_1BIT); |
|||
} |
|||
|
|||
void |
|||
EInk10::clean() |
|||
{ |
|||
ESP_LOGD(TAG, "Clean..."); |
|||
|
|||
Wire::enter(); |
|||
turn_on(); |
|||
|
|||
int m = 0; |
|||
clean_fast(0, 1); |
|||
m++; clean_fast((WAVEFORM[m] >> 30) & 3, 8); |
|||
m++; clean_fast((WAVEFORM[m] >> 24) & 3, 1); |
|||
m++; clean_fast( WAVEFORM[m] & 3, 8); |
|||
m++; clean_fast((WAVEFORM[m] >> 6) & 3, 1); |
|||
m++; clean_fast((WAVEFORM[m] >> 30) & 3, 10); |
|||
|
|||
Wire::leave(); |
|||
} |
|||
|
|||
void |
|||
EInk10::clean_fast(uint8_t c, uint8_t rep) |
|||
{ |
|||
static uint8_t byte[4] = { 0b10101010, 0b01010101, 0b00000000, 0b11111111 }; |
|||
|
|||
turn_on(); |
|||
|
|||
uint32_t send = PIN_LUT[byte[c]]; |
|||
|
|||
for (int8_t k = 0; k < rep; k++) { |
|||
|
|||
vscan_start(); |
|||
|
|||
for (uint16_t i = 0; i < HEIGHT; i++) { |
|||
|
|||
hscan_start(send); |
|||
|
|||
GPIO.out_w1ts = CL | send; |
|||
GPIO.out_w1tc = CL; |
|||
|
|||
for (uint16_t j = 0; j < LINE_SIZE_1BIT - 1; j++) { |
|||
GPIO.out_w1ts = CL; |
|||
GPIO.out_w1tc = CL; |
|||
GPIO.out_w1ts = CL; |
|||
GPIO.out_w1tc = CL; |
|||
} |
|||
GPIO.out_w1ts = CL; |
|||
GPIO.out_w1tc = CL; |
|||
|
|||
vscan_end(); |
|||
} |
|||
|
|||
ESP::delay_microseconds(230); |
|||
} |
|||
} |
|||
|
|||
// Turn off epaper power supply and put all digital IO pins in high Z state
|
|||
void |
|||
EInk10::turn_off() |
|||
{ |
|||
if (get_panel_state() == PanelState::OFF) return; |
|||
|
|||
oe_clear(); |
|||
gmod_clear(); |
|||
|
|||
GPIO.out &= ~(DATA | LE | CL); |
|||
|
|||
ckv_clear(); |
|||
sph_clear(); |
|||
spv_clear(); |
|||
vcom_clear(); |
|||
|
|||
ESP::delay(6); |
|||
|
|||
pwrup_clear(); |
|||
wakeup_clear(); |
|||
|
|||
unsigned long timer = ESP::millis(); |
|||
|
|||
do { |
|||
ESP::delay(1); |
|||
} while ((read_power_good() != 0) && (ESP::millis() - timer) < 250); |
|||
|
|||
pins_z_state(); |
|||
set_panel_state(PanelState::OFF); |
|||
} |
|||
|
|||
// Turn on supply for epaper display (TPS65186)
|
|||
// [+15 VDC, -15VDC, +22VDC, -20VDC, +3.3VDC, VCOM]
|
|||
void |
|||
EInk10::turn_on() |
|||
{ |
|||
if (get_panel_state() == PanelState::ON) return; |
|||
|
|||
wakeup_set(); |
|||
ESP::delay_microseconds(1800); |
|||
pwrup_set(); |
|||
|
|||
// Enable all rails
|
|||
wire.begin_transmission(PWRMGR_ADDRESS); |
|||
wire.write(0x01); |
|||
wire.write(0b00111111); |
|||
wire.end_transmission(); |
|||
|
|||
pins_as_outputs(); |
|||
|
|||
le_clear(); |
|||
oe_clear(); |
|||
cl_clear(); |
|||
sph_set(); |
|||
gmod_set(); |
|||
spv_set(); |
|||
ckv_clear(); |
|||
oe_clear(); |
|||
vcom_set(); |
|||
|
|||
unsigned long timer = ESP::millis(); |
|||
|
|||
do { |
|||
ESP::delay(1); |
|||
} while ((read_power_good() != PWR_GOOD_OK) && (ESP::millis() - timer) < 250); |
|||
|
|||
if ((ESP::millis() - timer) >= 250) { |
|||
wakeup_clear(); |
|||
vcom_clear(); |
|||
pwrup_clear(); |
|||
return; |
|||
} |
|||
|
|||
oe_set(); |
|||
set_panel_state(PanelState::ON); |
|||
} |
|||
|
|||
uint8_t |
|||
EInk10::read_power_good() |
|||
{ |
|||
wire.begin_transmission(PWRMGR_ADDRESS); |
|||
wire.write(0x0F); |
|||
wire.end_transmission(); |
|||
|
|||
wire.request_from(PWRMGR_ADDRESS, 1); |
|||
return wire.read(); |
|||
} |
|||
|
|||
// LOW LEVEL FUNCTIONS
|
|||
|
|||
void |
|||
EInk10::vscan_start() |
|||
{ |
|||
ckv_set(); ESP::delay_microseconds( 7); |
|||
spv_clear(); ESP::delay_microseconds(10); |
|||
ckv_clear(); ESP::delay_microseconds( 0); |
|||
ckv_set(); ESP::delay_microseconds( 8); |
|||
spv_set(); ESP::delay_microseconds(10); |
|||
ckv_clear(); ESP::delay_microseconds( 0); |
|||
ckv_set(); ESP::delay_microseconds(18); |
|||
ckv_clear(); ESP::delay_microseconds( 0); |
|||
ckv_set(); ESP::delay_microseconds(18); |
|||
ckv_clear(); ESP::delay_microseconds( 0); |
|||
ckv_set(); |
|||
} |
|||
|
|||
void |
|||
EInk10::hscan_start(uint32_t d) |
|||
{ |
|||
sph_clear(); |
|||
GPIO.out_w1ts = CL | d ; |
|||
GPIO.out_w1tc = CL | DATA; |
|||
sph_set(); |
|||
ckv_set(); |
|||
} |
|||
|
|||
void |
|||
EInk10::vscan_end() |
|||
{ |
|||
ckv_clear(); |
|||
le_set(); |
|||
le_clear(); |
|||
|
|||
ESP::delay_microseconds(0); |
|||
} |
|||
|
|||
void |
|||
EInk10::pins_z_state() |
|||
{ |
|||
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_2, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_32, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_33, GPIO_MODE_INPUT); |
|||
|
|||
mcp.set_direction(OE, MCP::PinMode::INPUT); |
|||
mcp.set_direction(GMOD, MCP::PinMode::INPUT); |
|||
mcp.set_direction(SPV, MCP::PinMode::INPUT); |
|||
|
|||
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_5, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_18, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_19, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_23, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_25, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_26, GPIO_MODE_INPUT); |
|||
gpio_set_direction(GPIO_NUM_27, GPIO_MODE_INPUT); |
|||
} |
|||
|
|||
void |
|||
EInk10::pins_as_outputs() |
|||
{ |
|||
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_32, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_33, GPIO_MODE_OUTPUT); |
|||
|
|||
mcp.set_direction(OE, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(GMOD, MCP::PinMode::OUTPUT); |
|||
mcp.set_direction(SPV, MCP::PinMode::OUTPUT); |
|||
|
|||
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_5, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_18, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_19, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_23, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_25, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_26, GPIO_MODE_OUTPUT); |
|||
gpio_set_direction(GPIO_NUM_27, GPIO_MODE_OUTPUT); |
|||
} |
|||
|
|||
int8_t |
|||
EInk10::read_temperature() |
|||
{ |
|||
int8_t temp; |
|||
|
|||
if (get_panel_state() == PanelState::OFF) { |
|||
Wire::enter(); |
|||
wakeup_set(); |
|||
ESP::delay_microseconds(1800); |
|||
pwrup_set(); |
|||
Wire::leave(); |
|||
|
|||
ESP::delay(5); |
|||
} |
|||
|
|||
Wire::enter(); |
|||
wire.begin_transmission(PWRMGR_ADDRESS); |
|||
wire.write(0x0D); |
|||
wire.write(0b10000000); |
|||
wire.end_transmission(); |
|||
Wire::leave(); |
|||
|
|||
ESP::delay(5); |
|||
|
|||
Wire::enter(); |
|||
wire.begin_transmission(PWRMGR_ADDRESS); |
|||
wire.write(0x00); |
|||
wire.end_transmission(); |
|||
|
|||
wire.request_from(PWRMGR_ADDRESS, 1); |
|||
temp = wire.read(); |
|||
|
|||
if (get_panel_state() == PanelState::OFF) { |
|||
pwrup_clear(); |
|||
wakeup_clear(); |
|||
Wire::leave(); |
|||
|
|||
ESP::delay(5); |
|||
} |
|||
else { |
|||
Wire::leave(); |
|||
} |
|||
|
|||
return temp; |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,198 @@ |
|||
/*
|
|||
eink.h |
|||
Inkplate 10 ESP-IDF |
|||
|
|||
Modified by Guy Turcotte |
|||
December 23, 2020 |
|||
|
|||
from the Arduino Library: |
|||
|
|||
David Zovko, Borna Biro, Denis Vajak, Zvonimir Haramustek @ e-radionica.com |
|||
September 24, 2020 |
|||
https://github.com/e-radionicacom/Inkplate-6-Arduino-library
|
|||
|
|||
For support, please reach over forums: forum.e-radionica.com/en |
|||
For more info about the product, please check: www.inkplate.io |
|||
|
|||
This code is released under the GNU Lesser General Public License v3.0: https://www.gnu.org/licenses/lgpl-3.0.en.html
|
|||
Please review the LICENSE file included with this example. |
|||
If you have any questions about licensing, please contact techsupport@e-radionica.com |
|||
Distributed as-is; no warranty is given. |
|||
*/ |
|||
#ifdef INKPLATE_10 |
|||
|
|||
#ifndef __EINK10_HPP__ |
|||
#define __EINK10_HPP__ |
|||
|
|||
#include <cinttypes> |
|||
#include <cstring> |
|||
|
|||
#include "non_copyable.hpp" |
|||
#include "driver/gpio.h" |
|||
#include "mcp.hpp" |
|||
#include "eink.hpp" |
|||
|
|||
/**
|
|||
* @brief Low level e-Ink display |
|||
* |
|||
* This class implements the low level methods required to control |
|||
* and access the e-ink display of the InkPlate-6 device. |
|||
* |
|||
* This is a singleton. It cannot be instanciated elsewhere. It is not |
|||
* instanciated in the heap. This is reinforced by the C++ construction |
|||
* below. It also cannot be copied through the NonCopyable derivation. |
|||
*/ |
|||
|
|||
class EInk10 : public EInk, NonCopyable |
|||
{ |
|||
public: |
|||
static const uint16_t WIDTH = 800; // In pixels
|
|||
static const uint16_t HEIGHT = 600; // In pixels
|
|||
static const uint16_t BITMAP_SIZE_1BIT = (WIDTH * HEIGHT) >> 3; // In bytes
|
|||
static const uint32_t BITMAP_SIZE_3BIT = ((uint32_t) WIDTH * HEIGHT) >> 1; // In bytes
|
|||
static const uint16_t LINE_SIZE_1BIT = WIDTH >> 3; // In bytes
|
|||
static const uint16_t LINE_SIZE_3BIT = WIDTH >> 1; // In bytes
|
|||
|
|||
static inline EInk6 & get_singleton() noexcept { return singleton; } |
|||
|
|||
inline int16_t get_width() { return WIDTH; } |
|||
inline int16_t get_height() { return HEIGHT; } |
|||
|
|||
private: |
|||
static constexpr char const * TAG = "EInk10"; |
|||
|
|||
static EInk6 singleton; |
|||
|
|||
static const uint8_t PWRMGR_ADDRESS = 0x48; |
|||
static const uint8_t PWR_GOOD_OK = 0b11111010; |
|||
|
|||
class Bitmap1Bit6 : public Bitmap1Bit { |
|||
private: |
|||
uint8_t data[BITMAP_SIZE_1BIT]; |
|||
public: |
|||
Bitmap1Bit6() : Bitmap1Bit(WIDTH, HEIGHT, BITMAP_SIZE_1BIT) {} |
|||
|
|||
uint8_t * get_data() { return data; } |
|||
}; |
|||
|
|||
class Bitmap3Bit6 : public Bitmap3Bit { |
|||
private: |
|||
uint8_t data[BITMAP_SIZE_3BIT]; |
|||
public: |
|||
Bitmap3Bit6() : Bitmap3Bit(WIDTH, HEIGHT, BITMAP_SIZE_3BIT) {} |
|||
|
|||
uint8_t * get_data() { return data; } |
|||
}; |
|||
|
|||
EInk10() : EInk() |
|||
{ } // Private constructor
|
|||
|
|||
void update_1bit(Bitmap1Bit & bitmap); |
|||
void update_3bit(Bitmap3Bit & bitmap); |
|||
|
|||
void vscan_start(); |
|||
void vscan_end(); |
|||
void hscan_start(uint32_t d = 0); |
|||
|
|||
void pins_z_state(); |
|||
void pins_as_outputs(); |
|||
|
|||
void turn_on(); |
|||
void turn_off(); |
|||
|
|||
void clean_fast(uint8_t c, uint8_t rep); |
|||
|
|||
uint8_t read_power_good(); |
|||
|
|||
inline void set_panel_state(PanelState s) { panel_state = s; } |
|||
|
|||
inline void allow_partial() { partial_allowed = true; } |
|||
inline void block_partial() { partial_allowed = false; } |
|||
inline bool is_partial_allowed() { return partial_allowed; } |
|||
|
|||
static const uint32_t PIN_LUT[256]; |
|||
|
|||
static const uint8_t WAVEFORM_3BIT[8][8]; |
|||
static const uint32_t WAVEFORM[50]; |
|||
static const uint8_t LUT2[16]; |
|||
static const uint8_t LUTW[16]; |
|||
static const uint8_t LUTB[16]; |
|||
|
|||
static const uint32_t CL = 0x01; |
|||
static const uint32_t CKV = 0x01; |
|||
static const uint32_t SPH = 0x02; |
|||
static const uint32_t LE = 0x04; |
|||
|
|||
static const uint32_t DATA = 0x0E8C0030; |
|||
|
|||
uint8_t * p_buffer; |
|||
Bitmap1Bit * d_memory_new; |
|||
|
|||
const MCP::Pin OE = MCP::Pin::IOPIN_0; |
|||
const MCP::Pin GMOD = MCP::Pin::IOPIN_1; |
|||
const MCP::Pin SPV = MCP::Pin::IOPIN_2; |
|||
const MCP::Pin WAKEUP = MCP::Pin::IOPIN_3; |
|||
const MCP::Pin PWRUP = MCP::Pin::IOPIN_4; |
|||
const MCP::Pin VCOM = MCP::Pin::IOPIN_5; |
|||
const MCP::Pin GPIO0_ENABLE = MCP::Pin::IOPIN_8; |
|||
|
|||
inline void cl_set() { GPIO.out_w1ts = CL; } |
|||
inline void cl_clear() { GPIO.out_w1tc = CL; } |
|||
|
|||
inline void ckv_set() { GPIO.out1_w1ts.val = CKV; } |
|||
inline void ckv_clear() { GPIO.out1_w1tc.val = CKV; } |
|||
|
|||
inline void sph_set() { GPIO.out1_w1ts.val = SPH; } |
|||
inline void sph_clear() { GPIO.out1_w1tc.val = SPH; } |
|||
|
|||
inline void le_set() { GPIO.out_w1ts = LE; } |
|||
inline void le_clear() { GPIO.out_w1tc = LE; } |
|||
|
|||
inline void oe_set() { mcp.digital_write(OE, MCP::SignalLevel::HIGH); } |
|||
inline void oe_clear() { mcp.digital_write(OE, MCP::SignalLevel::LOW ); } |
|||
|
|||
inline void gmod_set() { mcp.digital_write(GMOD, MCP::SignalLevel::HIGH); } |
|||
inline void gmod_clear() { mcp.digital_write(GMOD, MCP::SignalLevel::LOW ); } |
|||
|
|||
inline void spv_set() { mcp.digital_write(SPV, MCP::SignalLevel::HIGH); } |
|||
inline void spv_clear() { mcp.digital_write(SPV, MCP::SignalLevel::LOW ); } |
|||
|
|||
inline void wakeup_set() { mcp.digital_write(WAKEUP, MCP::SignalLevel::HIGH); } |
|||
inline void wakeup_clear() { mcp.digital_write(WAKEUP, MCP::SignalLevel::LOW ); } |
|||
|
|||
inline void pwrup_set() { mcp.digital_write(PWRUP, MCP::SignalLevel::HIGH); } |
|||
inline void pwrup_clear() { mcp.digital_write(PWRUP, MCP::SignalLevel::LOW ); } |
|||
|
|||
inline void vcom_set() { mcp.digital_write(VCOM, MCP::SignalLevel::HIGH); } |
|||
inline void vcom_clear() { mcp.digital_write(VCOM, MCP::SignalLevel::LOW ); } |
|||
|
|||
public: |
|||
|
|||
inline PanelState get_panel_state() { return panel_state; } |
|||
inline bool is_initialized() { return initialized; } |
|||
|
|||
virtual inline Bitmap1Bit * new_bitmap1bit() { return new Bitmap1Bit6; } |
|||
virtual inline Bitmap3Bit * new_bitmap3bit() { return new Bitmap3Bit6; } |
|||
|
|||
// All the following methods are protecting the I2C device trough
|
|||
// the Wire::enter() and Wire::leave() methods. These are implementing a
|
|||
// Mutex semaphore access control.
|
|||
//
|
|||
// If you ever add public methods, you *MUST* consider adding calls to Wire::enter()
|
|||
// and Wire::leave() and insure no deadlock will happen... or modifu the mutex to use
|
|||
// a recursive mutex.
|
|||
|
|||
bool setup(); |
|||
|
|||
inline void update(Bitmap1Bit & bitmap) { update_1bit(bitmap); } |
|||
inline void update(Bitmap3Bit & bitmap) { update_3bit(bitmap); } |
|||
|
|||
void partial_update(Bitmap1Bit & bitmap); |
|||
|
|||
void clean(); |
|||
|
|||
int8_t read_temperature(); |
|||
}; |
|||
|
|||
#endif |
|||
#endif |
@ -0,0 +1,738 @@ |
|||
/*
|
|||
eink.cpp |
|||
Inkplate 6 ESP-IDF |
|||
|
|||
Modified by Guy Turcotte |
|||
December 23, 2020 |
|||
|
|||
from the Arduino Library: |
|||
|
|||
David Zovko, Borna Biro, Denis Vajak, Zvonimir Haramustek @ e-radionica.com |
|||
September 24, 2020 |
|||
https://github.com/e-radionicacom/Inkplate-6-Arduino-library
|
|||
|
|||
For support, please reach over forums: forum.e-radionica.com/en |
|||
For more info about the product, please check: www.inkplate.io |
|||
|
|||
This code is released under the GNU Lesser General Public License v3.0: https://www.gnu.org/licenses/lgpl-3.0.en.html
|
|||
Please review the LICENSE file included with this example. |
|||
If you have any questions about licensing, please contact techsupport@e-radionica.com |
|||
Distributed as-is; no warranty is given. |
|||
*/ |
|||
|
|||
#ifdef INKPLATE_6 |
|||
|
|||
#define __EINK6__ 1 |
|||
#include "eink6.hpp" |
|||
|
|||
#include "logging.hpp" |
|||
|
|||
#include "wire.hpp" |
|||
#include "mcp.hpp" |
|||
#include "esp.hpp" |
|||
|
|||
#include <iostream> |
|||
|
|||
EInk6 EInk6::singleton; |
|||
|
|||
const uint8_t EInk6::WAVEFORM_3BIT[8][8] = { |
|||
{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, |
|||
{0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, |
|||
{1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0} }; |
|||
|
|||
const uint32_t EInk6::WAVEFORM[50] = { |
|||
0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60A81898, 0x60A8A8A8, 0x60A8A8A8, 0x6068A868, 0x6868A868, |
|||
0x6868A868, 0x68686868, 0x6A686868, 0x5A686868, 0x5A686868, 0x5A586A68, 0x5A5A6A68, 0x5A5A6A68, 0x55566A68, |
|||
0x55565A64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595, |
|||
0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xA4949494, 0x9494A4A4, 0x84A49494, 0x84948484, 0x84848484, |
|||
0x84848484, 0x84848484, 0xA5A48484, 0xA9A4A4A8, 0xA9A8A8A8, 0xA5A9A9A4, 0xA5A5A5A4, 0xA1A5A5A1, 0xA9A9A9A9, |
|||
0xA9A9A9A9, 0xA9A9A9A9, 0xA9A9A9A9, 0x15151515, 0x11111111 }; |
|||
|
|||
const uint8_t EInk6::LUT2[16] = { |
|||
0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, |
|||
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55 }; |
|||
|
|||
const uint8_t EInk6::LUTW[16] = { |
|||
0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA, |
|||
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA }; |
|||
|
|||
const uint8_t EInk6::LUTB[16] = { |
|||
0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5, |
|||
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55 }; |
|||
|
|||
// PIN_LUT built from the following:
|
|||
//
|
|||
// for (uint32_t i = 0; i < 256; i++) {
|
|||
// PIN_LUT[i] = ((i & 0b00000011) << 4) |
|
|||
// (((i & 0b00001100) >> 2) << 18) |
|
|||
// (((i & 0b00010000) >> 4) << 23) |
|
|||
// (((i & 0b11100000) >> 5) << 25);
|
|||
// }
|
|||
|
|||
const uint32_t EInk6::PIN_LUT[256] = { |
|||
0x00000000, 0x00000010, 0x00000020, 0x00000030, 0x00040000, 0x00040010, 0x00040020, 0x00040030, |
|||
0x00080000, 0x00080010, 0x00080020, 0x00080030, 0x000c0000, 0x000c0010, 0x000c0020, 0x000c0030, |
|||
0x00800000, 0x00800010, 0x00800020, 0x00800030, 0x00840000, 0x00840010, 0x00840020, 0x00840030, |
|||
0x00880000, 0x00880010, 0x00880020, 0x00880030, 0x008c0000, 0x008c0010, 0x008c0020, 0x008c0030, |
|||
0x02000000, 0x02000010, 0x02000020, 0x02000030, |