WaveShare e-paper module (epd1in54). Monochrome version

e-Paper Module

Description

Waveshare 1.54 e-paper module imported from offiicial code https://www.waveshare.com/wiki/1.54inch_e-Paper_Module Codes are referenced from official Arduino code and Raspberry pi code.

SPI is used for communication between ePaper module and mbed.

Following shapes can be used:

  • primitive shape (square, circle, line)
  • font (variable sizes)

Pull request are appreciated (Note that the original code is copyrighted by (C) Waveshare )

Example Output

Display output with example code will be like following image:

/media/uploads/imachooon/img_20180216_223238.jpg

Example Code

include the mbed library with this snippet

#include "mbed.h"
#include "epd1in54.h"
#include "m41t62_rtc.h"
// Control
PinName rst;
PinName dc;
PinName busy;
// SPI communication
PinName mosi;
PinName miso;
PinName sclk;
PinName cs;

DigitalOut myled(LED1);

unsigned char frame_black[EPD_HEIGHT*EPD_WIDTH/8];


int main() {
    mosi = p5;
    miso = p6;
    sclk = p7;
    cs = p8;
    rst = p9;
    dc = p10;
    busy = p11;
    
    memset(frame_black, 0xFF, sizeof(unsigned char)*EPD_HEIGHT*EPD_WIDTH/8);

    Epd epd = Epd(mosi, miso, sclk, cs, dc, rst, busy);
    if (epd.Init(lut_full_update) != 0){
        return -1;
    }

    /* Draw something to the frame buffer */
    // For simplicity, the arguments are explicit numerical coordinates
    epd.DrawRectangle(frame_black, 10, 60, 50, 110, COLORED);
    epd.DrawLine(frame_black, 10, 60, 50, 110, COLORED);
    epd.DrawLine(frame_black, 50, 60, 10, 110, COLORED);
    epd.DrawCircle(frame_black, 120, 80, 30, COLORED);
    epd.DrawFilledRectangle(frame_black, 10, 130, 50, 180, COLORED);
    epd.DrawFilledRectangle(frame_black, 0, 6, 200, 26, COLORED);
    epd.DrawFilledCircle(frame_black, 120, 150, 30, COLORED);

    /*Write strings to the buffer */
    epd.DrawStringAt(frame_black, 30, 30, "e-Paper Demo", &Font16, COLORED);
    epd.DrawStringAt(frame_black, 28, 10, "Hello world!", &Font16, UNCOLORED);


    
    /* Display the frame_buffer */
    epd.SetFrameMemory(frame_black, 0, 0, epd.width, epd.height);
    epd.DisplayFrame();
    epd.Sleep();

    
    while(1) {
        myled = 1;
        wait(0.5);
        myled = 0;
        wait(0.5);
    }
}

Revision:
0:ac97d71fe296
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/epd1in54.cpp	Fri Feb 16 21:30:08 2018 +0000
@@ -0,0 +1,488 @@
+/**
+ *  @filename   :   epd1in54.cpp
+ *  @brief      :   Implements for e-paper library
+ *  @author     :   Yehui from Waveshare
+ *
+ *  Copyright (C) Waveshare     August 10 2017
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documnetation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to  whom the Software is
+ * furished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include "epd1in54.h"
+
+const unsigned char lut_full_update[] =
+{
+    0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 
+    0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 
+    0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, 
+    0x35, 0x51, 0x51, 0x19, 0x01, 0x00
+};
+
+const unsigned char lut_partial_update[] =
+{
+    0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+
+
+Epd::~Epd() {
+};
+
+
+Epd::Epd(PinName mosi,
+         PinName miso,
+         PinName sclk, 
+         PinName cs, 
+         PinName dc, 
+         PinName rst, 
+         PinName busy
+         ):EpdIf(mosi, miso, sclk, cs, dc, rst, busy){
+             
+             width = EPD_WIDTH;
+             height= EPD_HEIGHT;
+             rotate = ROTATE_0;
+             
+}
+
+int Epd::Init(const unsigned char* lut) {
+
+    if(IfInit() != 0){
+        return -1;
+    }
+
+    /* EPD hardware init start */
+    Reset();
+    SendCommand(DRIVER_OUTPUT_CONTROL);
+    SendData((EPD_HEIGHT - 1) & 0xFF);
+    SendData(((EPD_HEIGHT - 1) >> 8) & 0xFF);
+    SendData(0x00);                     // GD = 0; SM = 0; TB = 0;
+    SendCommand(BOOSTER_SOFT_START_CONTROL);
+    SendData(0xD7);
+    SendData(0xD6);
+    SendData(0x9D);
+    SendCommand(WRITE_VCOM_REGISTER);
+    SendData(0xA8);                     // VCOM 7C
+    SendCommand(SET_DUMMY_LINE_PERIOD);
+    SendData(0x1A);                     // 4 dummy lines per gate
+    SendCommand(SET_GATE_TIME);
+    SendData(0x08);                     // 2us per line
+    SendCommand(DATA_ENTRY_MODE_SETTING);
+    SendData(0x03);                     // X increment; Y increment
+    SetLut(lut);
+    /* EPD hardware init end */
+    return 0;
+}
+
+/**
+ *  @brief: basic function for sending commands
+ */
+void Epd::SendCommand(unsigned char command) {
+    DigitalWrite(m_dc, LOW);
+    SpiTransfer(command);
+}
+
+/**
+ *  @brief: basic function for sending data
+ */
+void Epd::SendData(unsigned char data) {
+    DigitalWrite(m_dc, HIGH);
+    SpiTransfer(data);
+}
+
+/**
+ *  @brief: Wait until the m_busy goes HIGH
+ */
+void Epd::WaitUntilIdle(void) {
+    while(DigitalRead(m_busy) == 1) {      //0: busy, 1: idle
+        DelayMs(100);
+    }      
+}
+
+/**
+ *  @brief: module reset.
+ *          often used to awaken the module in deep sleep,
+ *          see Epd::Sleep();
+ */
+void Epd::Reset(void) {
+    DigitalWrite(m_rst, LOW);                //module reset    
+    DelayMs(200);
+    DigitalWrite(m_rst, HIGH);
+    DelayMs(200);    
+}
+
+
+/**
+ *  @brief: set the look-up table register
+ */
+void Epd::SetLut(const unsigned char* lut) {
+    SendCommand(WRITE_LUT_REGISTER);
+    /* the length of look-up table is 30 bytes */
+    for (int i = 0; i < 30; i++) {
+        SendData(lut[i]);
+    }
+}
+
+
+/**
+ *  @brief: put an image buffer to the frame memory.
+ *          this won't update the display.
+ */
+void Epd::SetFrameMemory(
+    const unsigned char* image_buffer,
+    int x,
+    int y,
+    int image_width,
+    int image_height
+) {
+    int x_end;
+    int y_end;
+
+    if (
+        image_buffer == NULL ||
+        x < 0 || image_width < 0 ||
+        y < 0 || image_height < 0
+    ) {
+        return;
+    }
+    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
+    x &= 0xF8;
+    image_width &= 0xF8;
+    if (x + image_width >= this->width) {
+        x_end = this->width - 1;
+    } else {
+        x_end = x + image_width - 1;
+    }
+    if (y + image_height >= this->height) {
+        y_end = this->height - 1;
+    } else {
+        y_end = y + image_height - 1;
+    }
+    SetMemoryArea(x, y, x_end, y_end);
+    SetMemoryPointer(x, y);
+    SendCommand(WRITE_RAM);
+    /* send the image data */
+    for (int j = 0; j < y_end - y + 1; j++) {
+        for (int i = 0; i < (x_end - x + 1) / 8; i++) {
+            SendData(image_buffer[i + j * (image_width / 8)]);
+        }
+    }
+}
+
+/**
+ *  @brief: clear the frame memory with the specified color.
+ *          this won't update the display.
+ */
+void Epd::ClearFrameMemory(unsigned char color) {
+    SetMemoryArea(0, 0, this->width - 1, this->height - 1);
+    SetMemoryPointer(0, 0);
+    SendCommand(WRITE_RAM);
+    /* send the color data */
+    for (int i = 0; i < this->width / 8 * this->height; i++) {
+        SendData(color);
+    }
+}
+
+/**
+ *  @brief: update the display
+ *          there are 2 memory areas embedded in the e-paper display
+ *          but once this function is called,
+ *          the the next action of SetFrameMemory or ClearFrame will 
+ *          set the other memory area.
+ */
+void Epd::DisplayFrame(void) {
+    SendCommand(DISPLAY_UPDATE_CONTROL_2);
+    SendData(0xC4);
+    SendCommand(MASTER_ACTIVATION);
+    SendCommand(TERMINATE_FRAME_READ_WRITE);
+    WaitUntilIdle();
+}
+
+/**
+ *  @brief: private function to specify the memory area for data R/W
+ */
+void Epd::SetMemoryArea(int x_start, int y_start, int x_end, int y_end) {
+    SendCommand(SET_RAM_X_ADDRESS_START_END_POSITION);
+    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
+    SendData((x_start >> 3) & 0xFF);
+    SendData((x_end >> 3) & 0xFF);
+    SendCommand(SET_RAM_Y_ADDRESS_START_END_POSITION);
+    SendData(y_start & 0xFF);
+    SendData((y_start >> 8) & 0xFF);
+    SendData(y_end & 0xFF);
+    SendData((y_end >> 8) & 0xFF);
+}
+
+/**
+ *  @brief: private function to specify the start point for data R/W
+ */
+void Epd::SetMemoryPointer(int x, int y) {
+    SendCommand(SET_RAM_X_ADDRESS_COUNTER);
+    /* x point must be the multiple of 8 or the last 3 bits will be ignored */
+    SendData((x >> 3) & 0xFF);
+    SendCommand(SET_RAM_Y_ADDRESS_COUNTER);
+    SendData(y & 0xFF);
+    SendData((y >> 8) & 0xFF);
+    WaitUntilIdle();
+}
+
+
+/**
+ *  @brief: After this command is transmitted, the chip would enter the 
+ *          deep-sleep mode to save power. 
+ *          The deep sleep mode would return to standby by hardware reset. 
+ *          You can use Epd::Init() to awaken
+ */
+void Epd::Sleep() {
+    SendCommand(DEEP_SLEEP_MODE);
+    WaitUntilIdle();
+}
+
+
+void Epd::SetRotate(int rotate){
+    if (rotate == ROTATE_0){
+        rotate = ROTATE_0;
+        width = EPD_WIDTH;
+        height = EPD_HEIGHT;
+    }
+    else if (rotate == ROTATE_90){
+        rotate = ROTATE_90;
+        width = EPD_HEIGHT;
+        height = EPD_WIDTH;
+    }
+    else if (rotate == ROTATE_180){
+        rotate = ROTATE_180;
+        width = EPD_WIDTH;
+        height = EPD_HEIGHT;
+    }
+    else if (rotate == ROTATE_270){ 
+        rotate = ROTATE_270;
+        width = EPD_HEIGHT;
+        height = EPD_WIDTH;
+    }
+}
+
+
+void Epd::SetPixel(unsigned char* frame_buffer, int x, int y, int colored){
+    if (x < 0 || x >= width || y < 0 || y >= height){
+        return;
+    }
+    if (rotate == ROTATE_0){
+        SetAbsolutePixel(frame_buffer, x, y, colored);
+    }
+    else if (rotate == ROTATE_90){
+        int point_temp = x;
+        x = EPD_WIDTH - y;
+        y = point_temp;
+        SetAbsolutePixel(frame_buffer, x, y, colored);
+    }
+    else if (rotate == ROTATE_180){
+        x = EPD_WIDTH - x;
+        y = EPD_HEIGHT- y;
+        SetAbsolutePixel(frame_buffer, x, y, colored);
+    }
+    else if (rotate == ROTATE_270){
+        int point_temp = x;
+        x = y;
+        y = EPD_HEIGHT - point_temp;
+        SetAbsolutePixel(frame_buffer, x, y, colored);
+    }
+}
+
+void Epd::SetAbsolutePixel(unsigned char *frame_buffer, int x, int y, int colored){
+    // To avoid display orientation effects
+    // use EPD_WIDTH instead of self.width
+    // use EPD_HEIGHT instead of self.height
+    if (x < 0 || x >= EPD_WIDTH || y < 0 || y >= EPD_HEIGHT){
+        return;
+    }
+    if (colored){
+        frame_buffer[(x + y * EPD_WIDTH) / 8] &= ~(0x80 >> (x % 8));
+    }
+    else{
+        frame_buffer[(x + y * EPD_WIDTH) / 8] |= 0x80 >> (x % 8);
+    }
+}
+
+void Epd::DrawLine(unsigned char*frame_buffer, int x0, int y0, int x1, int y1, int colored){
+    // Bresenham algorithm
+    int dx = x1 - x0 >= 0 ? x1 - x0 : x0 - x1;
+    int sx = x0 < x1 ? 1 : -1;
+    int dy = y1 - y0 <= 0 ? y1 - y0 : y0 - y1;
+    int sy = y0 < y1 ? 1 : -1;
+    int err = dx + dy;
+    while((x0 != x1) && (y0 != y1)){
+        SetPixel(frame_buffer, x0, y0 , colored);
+        if (2 * err >= dy){
+            err += dy;
+            x0 += sx;
+        }
+        if (2 * err <= dx){
+            err += dx;
+            y0 += sy;
+        }
+    }
+}
+
+ void Epd::DrawHorizontalLine(unsigned char *frame_buffer, int x, int y, int width, int colored){
+    for (int i=x; i<x + width; i++){
+        SetPixel(frame_buffer, i, y, colored);
+    }
+}
+
+ void Epd::DrawVerticalLine(unsigned char *frame_buffer, int x, int y, int height, int colored){
+    for (int i=y; i<y + height; i++){
+        SetPixel(frame_buffer, x, i, colored);
+    }
+}
+
+ void Epd::DrawRectangle(unsigned char *frame_buffer, int x0, int y0, int x1, int y1, int colored){
+    int min_x = x1 > x0 ? x0 : x1;
+    int max_x = x1 > x0 ? x1 : x0;
+    int min_y = y1 > y0 ? y0 : y1;
+    int max_y = y1 > y0 ? y1 : y0;
+    DrawHorizontalLine(frame_buffer, min_x, min_y, max_x - min_x + 1, colored);
+    DrawHorizontalLine(frame_buffer, min_x, max_y, max_x - min_x + 1, colored);
+    DrawVerticalLine(frame_buffer, min_x, min_y, max_y - min_y + 1, colored);
+    DrawVerticalLine(frame_buffer, max_x, min_y, max_y - min_y + 1, colored);
+}
+
+
+void Epd::DrawFilledRectangle(unsigned char *frame_buffer, int x0, int y0, int x1, int y1, int colored){
+    int min_x = x1 > x0 ? x0 : x1;
+    int max_x = x1 > x0 ? x1 : x0;
+    int min_y = y1 > y0 ? y0 : y1;
+    int max_y = y1 > y0 ? y1 : y0;
+
+    for (int i=min_x; i < max_x+1; i++){
+        DrawVerticalLine(frame_buffer, i, min_y, max_y - min_y + 1, colored);
+    }
+}
+
+void Epd::DrawCircle(unsigned char *frame_buffer, int x, int y, int radius, int colored){
+    // Bresenham algorithm
+    int x_pos = -radius;
+    int y_pos = 0;
+    int err = 2 - 2 * radius;
+    if (x >= width || y >= height){
+        return;
+    }
+    while ( 1 ){
+        SetPixel(frame_buffer, x - x_pos, y + y_pos, colored);
+        SetPixel(frame_buffer, x + x_pos, y + y_pos, colored);
+        SetPixel(frame_buffer, x + x_pos, y - y_pos, colored);
+        SetPixel(frame_buffer, x - x_pos, y - y_pos, colored);
+        int e2 = err;
+        if (e2 <= y_pos){
+            y_pos += 1;
+            err += y_pos * 2 + 1;
+            if(-x_pos == y_pos && e2 <= x_pos){
+                e2 = 0;
+            }
+        }
+        if (e2 > x_pos){
+            x_pos += 1;
+            err += x_pos * 2 + 1;
+        }
+        if (x_pos > 0){
+            break;
+        }
+    }
+}
+
+void Epd::DrawFilledCircle(unsigned char* frame_buffer, int x, int y, int radius, int colored){
+    // Bresenham algorithm
+    int x_pos = -radius;
+    int y_pos = 0;
+    int err = 2 - 2 * radius;
+    if (x >= width || y >= height){
+        return;
+    }
+    while ( 1 ){
+        SetPixel(frame_buffer, x - x_pos, y + y_pos, colored);
+        SetPixel(frame_buffer, x + x_pos, y + y_pos, colored);
+        SetPixel(frame_buffer, x + x_pos, y - y_pos, colored);
+        SetPixel(frame_buffer, x - x_pos, y - y_pos, colored);
+        DrawHorizontalLine(frame_buffer, x + x_pos, y + y_pos, 2 * (-x_pos) + 1, colored);
+        DrawHorizontalLine(frame_buffer, x + x_pos, y - y_pos, 2 * (-x_pos) + 1, colored);
+        int e2 = err;
+        if (e2 <= y_pos){
+            y_pos += 1;
+            err += y_pos * 2 + 1;
+            if(-x_pos == y_pos && e2 <= x_pos){
+                e2 = 0;
+            }
+        }
+        if (e2 > x_pos){
+            x_pos  += 1;
+            err += x_pos * 2 + 1;
+        }
+        if (x_pos > 0){
+            break;
+        }
+    }
+}
+
+
+
+/**
+ *  @brief: this draws a charactor on the frame buffer but not refresh
+ */
+void Epd::DrawCharAt(unsigned char *frame_buffer, int x, int y, char ascii_char, sFONT* font, int colored) {
+    int i, j;
+    unsigned int char_offset = (ascii_char - ' ') * font->Height * (font->Width / 8 + (font->Width % 8 ? 1 : 0));
+    const unsigned char* ptr = &font->table[char_offset];
+
+    for (j = 0; j < font->Height; j++) {
+        for (i = 0; i < font->Width; i++) {
+            if (*ptr & (0x80 >> (i % 8))) {
+                SetPixel(frame_buffer, x + i, y + j, colored);
+            }
+            if (i % 8 == 7) {
+                ptr++;
+            }
+        }
+        if (font->Width % 8 != 0) {
+            ptr++;
+        }
+    }
+}
+
+/**
+*  @brief: this displays a string on the frame buffer but not refresh
+*/
+void Epd::DrawStringAt(unsigned char *frame_buffer, int x, int y, const char* text, sFONT* font, int colored) {
+    const char* p_text = text;
+    unsigned int counter = 0;
+    int refcolumn = x;
+    
+    /* Send the string character by character on EPD */
+    while (*p_text != 0) {
+        /* Display one character on EPD */
+        DrawCharAt(frame_buffer, refcolumn, y, *p_text, font, colored);
+        /* Decrement the column position by 16 */
+        refcolumn += font->Width;
+        /* Point on the next character */
+        p_text++;
+        counter++;
+    }
+}
\ No newline at end of file