Merge pull request #41 from adafruit/acephacking

Add ACEP (7-color EPD) support
This commit is contained in:
Limor "Ladyada" Fried 2020-12-27 12:26:04 -05:00 committed by GitHub
commit b51d8c3783
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 476 additions and 45 deletions

View file

@ -19,8 +19,14 @@ jobs:
- name: pre-install
run: bash ci/actions_install.sh
# manually install SDFat
- name: extra libraries
run: |
git clone --quiet https://github.com/adafruit/SdFat.git /home/runner/Arduino/libraries/SdFat
git clone --quiet https://github.com/adafruit/Adafruit_SPIFlash.git /home/runner/Arduino/libraries/Adafruit_SPIFlash
- name: test platforms
run: python3 ci/build_platform.py main_platforms cpb cpx
run: python3 ci/build_platform.py main_platforms metro_m4_tinyusb cpb cpx
- name: clang
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .

View file

@ -0,0 +1,263 @@
/***************************************************
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
MIT license, all text above must be included in any redistribution
****************************************************/
#include <SPI.h>
#include <SdFat.h>
#include <Adafruit_SPIFlash.h>
#include "Adafruit_EPD.h"
#define EPD_CS 9
#define EPD_DC 10
#define SRAM_CS -1 // Use the build in memory, we need 133KB!
#define EPD_RESET 6 // can set to -1 and share with microcontroller Reset!
#define EPD_BUSY 5 // can set to -1 to not use a pin (will wait a fixed delay)
// Uncomment the following line if you are using ACeP 7 color 5.65"
Adafruit_ACEP display(600, 448, EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
Adafruit_FlashTransport_QSPI flashTransport;
Adafruit_SPIFlash flash(&flashTransport);
FatFileSystem fatfs;
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
Serial.println("Adafruit ACeP EPD test");
if (!flash.begin()) {
Serial.println("Error, failed to initialize flash chip!");
while(1) delay(1);
}
Serial.print("Flash chip JEDEC ID: 0x"); Serial.println(flash.getJEDECID(), HEX);
// First call begin to mount the filesystem. Check that it returns true
// to make sure the filesystem was mounted.
if (!fatfs.begin(&flash)) {
Serial.println("Error, failed to mount newly formatted filesystem!");
Serial.println("Was the flash chip formatted with the fatfs_format example?");
while(1) delay(1);
}
Serial.println("Mounted filesystem!");
File root = fatfs.open("/");
printDirectory(root, 0);
display.begin();
display.setRotation(0);
display.clearBuffer();
// draw 7 color bars
for (uint8_t color=ACEP_COLOR_BLACK; color<=ACEP_COLOR_ORANGE; color++) {
display.fillRect(display.width()*color/7, 0, display.width()/7, display.height(), color);
}
display.display();
}
void loop() {
bmpDraw("adafruit_characters.bmp", 0, 0);
bmpDraw("adabot.bmp", 0, 0);
}
// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates. It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel). Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster. 20 pixels seems a
// good balance.
#define BUFFPIXEL 20
bool bmpDraw(char *filename, int16_t x, int16_t y) {
File bmpFile;
int bmpWidth, bmpHeight; // W+H in pixels
uint8_t bmpDepth; // Bit depth (currently must be 24)
uint32_t bmpImageoffset; // Start of image data in file
uint32_t rowSize; // Not always = bmpWidth; may have padding
uint8_t sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
boolean goodBmp = false; // Set to true on valid header parse
boolean flip = true; // BMP is stored bottom-to-top
int w, h, row, col, x2, y2, bx1, by1;
uint8_t r, g, b;
uint32_t pos = 0, startTime = millis();
if((x >= display.width()) || (y >= display.height())) return false;
Serial.println();
Serial.print(F("Loading image '"));
Serial.print(filename);
Serial.println('\'');
// Open requested file on SD card
if ((bmpFile = fatfs.open(filename, FILE_READ)) == NULL) {
Serial.print(F("File not found"));
return false;
}
// Parse BMP header
if (read16(bmpFile) == 0x4D42) { // BMP signature
Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
(void)read32(bmpFile); // Read & ignore creator bytes
bmpImageoffset = read32(bmpFile); // Start of image data
Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
// Read DIB header
Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if(read16(bmpFile) == 1) { // # planes -- must be '1'
bmpDepth = read16(bmpFile); // bits per pixel
Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
goodBmp = true; // Supported BMP format -- proceed!
Serial.print(F("Image size: "));
Serial.print(bmpWidth);
Serial.print('x');
Serial.println(bmpHeight);
// BMP rows are padded (if needed) to 4-byte boundary
rowSize = (bmpWidth * 3 + 3) & ~3;
// If bmpHeight is negative, image is in top-down order.
// This is not canon but has been observed in the wild.
if(bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
// Crop area to be loaded
x2 = x + bmpWidth - 1; // Lower-right corner
y2 = y + bmpHeight - 1;
if((x2 >= 0) && (y2 >= 0)) { // On screen?
w = bmpWidth; // Width/height of section to load/display
h = bmpHeight;
bx1 = by1 = 0; // UL coordinate in BMP file
for (row=0; row<h; row++) { // For each scanline...
// Seek to start of scan line. It might seem labor-
// intensive to be doing this on every line, but this
// method covers a lot of gritty details like cropping
// and scanline padding. Also, the seek only takes
// place if the file position actually needs to change
// (avoids a lot of cluster math in SD library).
if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
pos = bmpImageoffset + (bmpHeight - 1 - (row + by1)) * rowSize;
else // Bitmap is stored top-to-bottom
pos = bmpImageoffset + (row + by1) * rowSize;
pos += bx1 * 3; // Factor in starting column (bx1)
if(bmpFile.position() != pos) { // Need seek?
bmpFile.seek(pos);
buffidx = sizeof(sdbuffer); // Force buffer reload
}
for (col=0; col<w; col++) { // For each pixel...
// Time to read more pixel data?
if (buffidx >= sizeof(sdbuffer)) { // Indeed
bmpFile.read(sdbuffer, sizeof(sdbuffer));
buffidx = 0; // Set index to beginning
}
// Convert pixel from BMP to EPD format, push to display
b = sdbuffer[buffidx++];
g = sdbuffer[buffidx++];
r = sdbuffer[buffidx++];
uint32_t color = r;
color <<= 8;
color |= g;
color <<= 8;
color |= b;
uint8_t c;
switch (color) {
case 0x000000: c = ACEP_COLOR_BLACK; break;
case 0xFFFFFF: c = ACEP_COLOR_WHITE; break;
case 0x00FF00: c = ACEP_COLOR_GREEN; break;
case 0x0000FF: c = ACEP_COLOR_BLUE; break;
case 0xFF0000: c = ACEP_COLOR_RED; break;
case 0xFFFF00: c = ACEP_COLOR_YELLOW; break;
case 0xFF8000: c = ACEP_COLOR_ORANGE; break;
default: {
Serial.print("Unknown color 0x");
Serial.println(color, HEX);
c = ACEP_COLOR_WHITE;
}
}
display.writePixel(col, row, c);
} // end pixel
} // end scanline
} // end onscreen
display.display();
Serial.print(F("Loaded in "));
Serial.print(millis() - startTime);
Serial.println(" ms");
} // end goodBmp
}
}
bmpFile.close();
if(!goodBmp) {
Serial.println(F("BMP format not recognized."));
return false;
}
return true;
}
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t read16(File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
void printDirectory(File dir, int numTabs) {
char filename[80];
while (true) {
File entry = dir.openNextFile();
if (! entry) {
// no more files
break;
}
for (uint8_t i = 0; i < numTabs; i++) {
Serial.print('\t');
}
entry.getName(filename, 80);
Serial.print(filename);
if (entry.isDirectory()) {
Serial.println("/");
printDirectory(entry, numTabs + 1);
} else {
// files have sizes, directories do not
Serial.print("\t\t");
Serial.println(entry.size(), DEC);
}
entry.close();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

View file

@ -290,7 +290,7 @@ void Adafruit_EPD::writeRAMFramebufferToEPD(uint8_t *framebuffer,
dcHigh();
// Serial.printf("Writing from RAM location %04x: \n", &framebuffer);
for (uint16_t i = 0; i < framebuffer_size; i++) {
for (uint32_t i = 0; i < framebuffer_size; i++) {
uint8_t d = framebuffer[i];
if (invertdata)
d = ~d;

View file

@ -155,8 +155,8 @@ protected:
uint8_t layer_colors[EPD_NUM_COLORS];
uint16_t buffer1_size; ///< size of the primary buffer
uint16_t buffer2_size; ///< size of the secondary buffer
uint32_t buffer1_size; ///< size of the primary buffer
uint32_t buffer2_size; ///< size of the secondary buffer
uint8_t *buffer1; ///< the pointer to the primary buffer if using on-chip ram
uint8_t
*buffer2; ///< the pointer to the secondary buffer if using on-chip ram

View file

@ -6,6 +6,7 @@
// clang-format off
const uint8_t acep_default_init_code[] {
0xFF, 10, // wait a lil bit
ACEP_PANEL_SETTING, 2, 0xEF, 0x08, // LUT from OTP
ACEP_POWER_SETTING, 4, 0x37, 0x00, 0x23, 0x23, // 0x05&0x05?
ACEP_POWER_OFF_SEQUENCE, 1, 0x00,
@ -16,6 +17,8 @@ const uint8_t acep_default_init_code[] {
ACEP_TCON, 1, 0x22,
ACEP_RESOLUTION, 4, 0x02, 0x58, 0x01, 0xC0,
ACEP_PWS, 1, 0xAA,
0xFF, 100, // 100 ms delay
ACEP_CDI, 1, 0x37,
0xFE};
// clang-format on
@ -79,7 +82,7 @@ Adafruit_ACEP::Adafruit_ACEP(int width, int height, int8_t DC, int8_t RST,
if ((height % 8) != 0) {
height += 8 - (height % 8);
}
buffer1_size = (uint16_t)width * (uint16_t)height / 2;
buffer1_size = width * height / 2;
buffer2_size = 0;
if (SRCS >= 0) {
@ -94,6 +97,127 @@ Adafruit_ACEP::Adafruit_ACEP(int width, int height, int8_t DC, int8_t RST,
singleByteTxns = true;
}
/**************************************************************************/
/*!
@brief clear all data buffers
*/
/**************************************************************************/
void Adafruit_ACEP::clearBuffer() {
if (use_sram) {
sram.erase(colorbuffer_addr, buffer1_size, 0x11);
} else {
memset(color_buffer, 0x11, buffer1_size);
}
}
/**************************************************************************/
/*!
@brief clear all data buffers
*/
/**************************************************************************/
void Adafruit_ACEP::deGhost() {
uint8_t buf[4];
buf[0] = 0x02;
buf[1] = 0x58;
buf[2] = 0x01;
buf[3] = 0xC0;
EPD_command(ACEP_RESOLUTION, buf, 4);
EPD_command(ACEP_DTM);
uint32_t remaining = (600UL * 448UL / 2);
while (remaining) {
uint8_t block[256];
uint32_t numbytes = min(remaining, sizeof(block));
memset(block, 0x77, numbytes);
EPD_data(block, numbytes);
remaining -= numbytes;
}
EPD_command(ACEP_POWER_ON);
busy_wait();
EPD_command(ACEP_DISPLAY_REFRESH);
busy_wait();
EPD_command(ACEP_POWER_OFF);
if (_busy_pin >= 0) {
while (digitalRead(_busy_pin)) { // wait for busy LOW
delay(10);
}
} else {
delay(BUSY_WAIT);
}
}
/**************************************************************************/
/*!
@brief clear the display twice to remove any spooky ghost images
*/
/**************************************************************************/
void Adafruit_ACEP::clearDisplay() {
clearBuffer();
display();
}
/**************************************************************************/
/*!
@brief draw a single pixel on the screen
@param x the x axis position
@param y the y axis position
@param color the color of the pixel
*/
/**************************************************************************/
void Adafruit_ACEP::drawPixel(int16_t x, int16_t y, uint16_t color) {
if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
return;
uint8_t *pBuf;
// deal with non-8-bit heights
uint16_t _HEIGHT = HEIGHT;
if (_HEIGHT % 8 != 0) {
_HEIGHT += 8 - (_HEIGHT % 8);
}
// check rotation, move pixel around if necessary
switch (getRotation()) {
case 1:
EPD_swap(x, y);
x = WIDTH - x - 1;
break;
case 2:
x = WIDTH - x - 1;
y = _HEIGHT - y - 1;
break;
case 3:
EPD_swap(x, y);
y = _HEIGHT - y - 1;
break;
}
uint32_t addr = ((uint32_t)x + (uint32_t)y * WIDTH) / 2;
bool lower_nibble = x % 2;
uint8_t color_c;
if (use_sram) {
color_c = sram.read8(colorbuffer_addr + addr);
pBuf = &color_c;
} else {
pBuf = color_buffer + addr;
}
if (lower_nibble) {
*pBuf &= 0xF0; // save higher nib
*pBuf |= (color & 0xF);
} else {
*pBuf &= 0x0F; // save lower nib
*pBuf |= (color & 0xF) << 4;
}
if (use_sram) {
sram.write8(colorbuffer_addr + addr, *pBuf);
}
}
/**************************************************************************/
/*!
@brief wait for busy signal to end
@ -117,9 +241,58 @@ void Adafruit_ACEP::busy_wait(void) {
/**************************************************************************/
void Adafruit_ACEP::begin(bool reset) {
Adafruit_EPD::begin(reset);
delay(100);
powerDown();
}
/**************************************************************************/
/*!
@brief Transfer the data stored in the buffer(s) to the display
*/
/**************************************************************************/
void Adafruit_ACEP::display(bool sleep) {
uint8_t c;
#ifdef EPD_DEBUG
Serial.println(" Powering Up");
#endif
powerUp();
#ifdef EPD_DEBUG
Serial.println(" De Ghosting");
#endif
deGhost();
delay(500);
#ifdef EPD_DEBUG
Serial.println(" Powering Up");
#endif
powerUp();
#ifdef EPD_DEBUG
Serial.println(" Write frame buffer");
#endif
if (use_sram) {
writeSRAMFramebufferToEPD(buffer1_addr, buffer1_size, 0);
} else {
writeRAMFramebufferToEPD(buffer1, buffer1_size, 0);
}
#ifdef EPD_DEBUG
Serial.println(" Update");
#endif
update();
partialsSinceLastFullUpdate = 0;
if (sleep) {
#ifdef EPD_DEBUG
Serial.println(" Powering Down");
#endif
powerDown();
}
}
/**************************************************************************/
@ -127,53 +300,22 @@ void Adafruit_ACEP::begin(bool reset) {
@brief signal the display to update
*/
/**************************************************************************/
void Adafruit_ACEP::update() {
void Adafruit_ACEP::update(void) {
uint8_t buf[4];
/*
// clear data
buf[0] = 0x02;
buf[1] = 0x58;
buf[2] = 0x01;
buf[3] = 0xC0;
EPD_command(ACEP_RESOLUTION, buf, 4);
EPD_command(ACEP_DTM);
for (int i=0; i< 134400/256; i++) {
uint8_t block[256];
memset(block, 0x77, 256);
EPD_data(block, 256);
}
EPD_command(ACEP_POWER_ON);
busy_wait();
EPD_command(ACEP_DISPLAY_REFRESH);
busy_wait();
EPD_command(ACEP_POWER_OFF);
if (_busy_pin >= 0) {
while (digitalRead(_busy_pin)) { // wait for busy LOW
delay(10);
}
} else {
delay(BUSY_WAIT);
}*/
// actual data
// clear data
buf[0] = 0x02;
buf[1] = 0x58;
buf[2] = 0x01;
buf[3] = 0xC0;
EPD_command(ACEP_RESOLUTION, buf, 4);
EPD_command(ACEP_DTM);
for (int i = 0; i < 134400 / 256; i++) {
uint8_t block[256];
memset(block, ((i % 6) << 4) | (i % 6), 256);
EPD_data(block, 256);
}
EPD_command(ACEP_POWER_ON);
busy_wait();
EPD_command(ACEP_DISPLAY_REFRESH);
busy_wait();
EPD_command(ACEP_POWER_OFF);
}
/**************************************************************************/
@ -193,9 +335,15 @@ void Adafruit_ACEP::powerUp() {
init_code = _epd_init_code;
}
EPD_commandList(init_code);
delay(1000);
buf[0] = 0x37;
EPD_command(ACEP_CDI, buf, 1);
// set resolution
buf[0] = 0x02;
buf[1] = 0x58;
buf[2] = 0x01;
buf[3] = 0xC0;
EPD_command(ACEP_RESOLUTION, buf, 4);
delay(100);
}
/**************************************************************************/

View file

@ -14,12 +14,20 @@
#define ACEP_DTM 0x10
#define ACEP_DISPLAY_REFRESH 0x12
#define ACEP_PLL 0x30
#define ACEP_TSE 0x41
#define ACEP_TSE 0x40
#define ACEP_CDI 0x50
#define ACEP_TCON 0x60
#define ACEP_RESOLUTION 0x61
#define ACEP_PWS 0xE3
#define ACEP_COLOR_BLACK 0x0 /// 000
#define ACEP_COLOR_WHITE 0x1 /// 001
#define ACEP_COLOR_GREEN 0x2 /// 010
#define ACEP_COLOR_BLUE 0x3 /// 011
#define ACEP_COLOR_RED 0x4 /// 100
#define ACEP_COLOR_YELLOW 0x5 /// 101
#define ACEP_COLOR_ORANGE 0x6 /// 110
/**************************************************************************/
/*!
@brief Class for interfacing with ACEP EPD drivers
@ -37,6 +45,12 @@ public:
void powerUp();
void powerDown();
void update();
void display(bool sleep = true);
void clearBuffer();
void clearDisplay();
void deGhost();
void drawPixel(int16_t x, int16_t y, uint16_t color);
protected:
uint8_t writeRAMCommand(uint8_t index);