A lib to handle a E-Paper display from Pervasive Displays. There is a interface board from Embedded Artists. The lib can handle graphic and text drawing and is using external fonts.

Dependents:   epaper_mbed_130411_KL25Z epaper_mbed_test epaper_KL25Z_2 test_he10 ... more

/media/uploads/dreschpe/epaper.jpg

The E-Paper display from Pervasive Displays with a interface board is available from Embedded Artists : http://www.embeddedartists.com/products/displays/lcd_27_epaper.php The 2.7 inch display have 264*176 pixel, monochrome.

Technology

You can look at the webside from Pervasive to see how the display works. http://www.pervasivedisplays.com/technology/home

This type of display have ultra low power consumption - due to its bi-stable nature. It requires only very little power to update the display and needs no power to maintain an image. You can disconnect the display - the image is still there. The viewing angle is like real paper - near 180°.

There are also some disadvantages of this technology. To change the image, we have to rewrite the full display in 4 steps. Invert, clear, invert new, new image. This process is visible and take a while -2s at room temperature. If it gets colder the display reacts slower and the interface timing has to be slow down. To compensate this, there is a LM75 temp sensor on the interface board. We also need ram to double buffer the display. 264 * 176 / 8 = 5808 Byte. To double buffer we need 11616 byte of ram. This is no problem for most mbed devices, but it will not run on the LPC11U24 or LPC800-MAX.

Interface

The graphic data is transferred to the display via spi. The maximum speed is 12Mhz. There are also some control signal and the I2C for the temperature sensor. Together we need 12 signals.

Displaymbed LPC1768mbed KL25Zsignal type
1 GNDGNDGNDGND
2 3V3VOUTP3V33.3 V power
3 SCKp7PTD1SCK
4 MOSIp5PTD2MOSI
5 MISOp6PTD3MISO
6 SSELp8PTC17GPIO
7 Busyp13PTA16GPIO
8 Borderp10PTD6GPIO
9 SCLp27PTE1SCL
10 SDAp28PTE0SDA
11 PWMp26PTD4PWM
12 Resetp12PTA17GPIO
13 Power controlp9PTD7GPIO
14 Dischargep11PTE31GPIO

Software

Fonts

How to get nice looking fonts ?

To print characters to a graphic screen we need a font. To code a font by paper is ok for a small lcd, but for a 264*176 pixel display we need bigger fonts. A 12*12 pixel font is readable, but it a lot of work to construct it by hand.

Fonts can be made with the GLCD Font Creator also from http://www.mikroe.com .

With this program you can load a window font and convert it into a c-array. To use this Font with my lib you have to add 4 parameter at the beginning of the font array. - the number of byte / char - the vertial size in pixel - the horizontal size in pixel - the number of byte per vertical line (it is vertical size / 8 ) You also have to change the type of array to char[]. After that you can switch between different fonts with set_font(unsigned char* font); The horizontal size of each character is also stored in the font. It look better if you use bigger fonts or italic. The letter M is wider than a l.

Here are some Fonts from me : http://mbed.org/users/dreschpe/code/TFT_fonts/

The small made for the mbed lab board can also be used : http://mbed.org/users/dreschpe/code/LCD_fonts/

And from Peter Holzleitner : http://mbed.org/users/pholzleitner/code/SourceCodePro31-SB/

Text commands :

You can use the claim() function to redirect the output to stdout or stderr to the display. After claim(stdout) you can simply use the printf function without the classname to print to the display. All other printf from other libs are also redirected to the display if you use this.

  • printf(...); print text and variables to the buffer with format options.
  • locate(x,y); function is used to setup the cursor position. x,y are the pixel position.

Graphics

Graphic commands :

  • cls(); Fill the screen with background color
  • pixel(x,y,color); set a single pixel at x,y with 1 : black or 0 : white
  • line(x0,y0,x1,y1,color); draw a line from x0,y0 to x1,y1 with color
  • rect(x0,y0,x1,y1,color); draw a rectangle x0,y0 to x1,y1 with color
  • fillrect(x0,y0,x1,y1,color); draw a filled rectangle
  • circle( x0,y0,radius ,color); draw a circle around x0,y0 with radius
  • fillcircle(x0,y0,radius ,color); draw a filled circle around x0,y0 with radius
  • setmode(mode); Set the drawing mode for all functions. mode can be NORMAL -> 0 is white and 1 is black or XOR -> the new pixel is a xor between the old display and the new. This mode will invert if a black pixel is draw over a black pixel.
  • print_bm(Bitmap ,x0,x0); Print a monochrome bitmap array. This graphic is defined by a Bitmap struct :

The pixel date array :

static char arm_logo[]={
0x00,0x00...
};

and a Bitmap struct:

Bitmap bitmARM = {
  48, // XSize
  48, // YSize 
  6,  // Bytes in Line
  arm_logo // Pointer to picture data 
};

To convert a graphic into a byte array we can use the tool Picture Converter 1bpp from http://www.embedded-tools.de.vu/ With this tool we load a image, press the convert button and save it as C-Header file. We have to save with horizontal orientation, so we have to press "No". Inside this file we find the data array which we can copy into a header file.

All this commands are writing to the frame buffer only ! To change the active display we have to call

  • write_disp(); This will refresh the display.

Sample code

test code for the LPC1768: http://mbed.org/users/dreschpe/code/epaper_mbed_test/

test code for KL25Z: http://mbed.org/users/dreschpe/code/epaper_mbed_130411_KL25Z/

#include "mbed.h"
#include "EaEpaper.h"
#include "Arial28x28.h"
#include "Arial12x12.h"
#include "font_big.h"
#include "graphics.h"

EaEpaper epaper(
                PTD7,            // PWR_CTRL
                PTD6,            // BORDER
                PTE31,           // DISCHARGE
                PTA17,           // RESET_DISP
                PTA16,           // BUSY
                PTC17,           // SSEL
                PTD4,            // PWM
                PTD2,PTD3,PTD1,  // MOSI,MISO,SCLK
                PTE0,PTE1);      // SDA,SDL 
 
int main() {

    epaper.cls();                                      // clear screen
    epaper.set_font((unsigned char*) Arial28x28);  // select the font
    epaper.locate(5,20);                           // set cursor
    epaper.printf("Hello Mbed");                  // print  text
    epaper.rect(3,15,150,50,1);                  // print a frame 
     
    epaper.set_font((unsigned char*) Arial12x12);  // change font
    epaper.locate(5,60);                               // set cursor
    epaper.printf("small Font");                    // print text
    epaper.set_font((unsigned char*) Neu42x35);  // change font
    epaper.locate(5,70);                               //set cursor
    epaper.printf("big Font");                        // change font
    
    epaper.write_disp(); // update screen       // update display
    
    wait(5);                                           // wait 5 s
    epaper.fillcircle(180,30,22,1);              // paint filled circle
    epaper.circle(160,150,20,1);               // paint circle
    epaper.write_disp(); // update screen      // update display
    
}
  

EPD.cpp

Committer:
dreschpe
Date:
2014-06-25
Revision:
3:1371614703cd
Parent:
0:fedcef5319f5

File content as of revision 3:1371614703cd:

// Copyright 2013 Pervasive Displays, Inc.
//
// 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.



#include <limits.h>

#include "EPD.h"
#include "mbed.h"

// delays - more consistent naming
#define Delay_ms(ms) wait_ms(ms)
#define Delay_us(us) wait_us(us)

// inline arrays
#define ARRAY(type, ...) ((type[]){__VA_ARGS__})
#define CU8(...) (ARRAY(const uint8_t, __VA_ARGS__))

#define LOW  (0)
#define HIGH (1)
#define digitalWrite(pin, state) (pin) = (state)
#define digitalRead(pin) (pin)

Timer _time;
#define millis() _time.read_ms()
#define millis_start() _time.start()


//static void PWM_start(int pin);
//static void PWM_stop(int pin);

//static void SPI_put(uint8_t c);
//static void SPI_put_wait(uint8_t c, int busy_pin);
//static void SPI_send(uint8_t cs_pin, const uint8_t *buffer, uint16_t length);


EPD_Class::EPD_Class(EPD_size size,
             PinName panel_on_pin,
             PinName border_pin,
             PinName discharge_pin,
             PinName pwm_pin,
             PinName reset_pin,
             PinName busy_pin,
             PinName chip_select_pin,
             PinName mosi,
             PinName miso,
             PinName sck) :
    EPD_Pin_PANEL_ON(panel_on_pin),
    EPD_Pin_BORDER(border_pin),
    EPD_Pin_DISCHARGE(discharge_pin),
    EPD_Pin_PWM(pwm_pin),
    EPD_Pin_RESET(reset_pin),
    EPD_Pin_BUSY(busy_pin),
    EPD_Pin_EPD_CS(chip_select_pin),
    spi_(mosi,miso,sck) {

    this->size = size;
    this->stage_time = 480; // milliseconds
    this->lines_per_display = 96;
    this->dots_per_line = 128;
    this->bytes_per_line = 128 / 8;
    this->bytes_per_scan = 96 / 4;
    this->filler = false;
    spi_.frequency(12000000);   // 12 MHz SPI clock

    // display size dependant items
    {
        static uint8_t cs[] = {0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00};
        static uint8_t gs[] = {0x72, 0x03};
        this->channel_select = cs;
        this->channel_select_length = sizeof(cs);
        this->gate_source = gs;
        this->gate_source_length = sizeof(gs);
    }

    // set up size structure
    switch (size) {
    default:
    case EPD_1_44:  // default so no change
        break;

    case EPD_2_0: {
        this->lines_per_display = 96;
        this->dots_per_line = 200;
        this->bytes_per_line = 200 / 8;
        this->bytes_per_scan = 96 / 4;
        this->filler = true;
        static uint8_t cs[] = {0x72, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xe0, 0x00};
        static uint8_t gs[] = {0x72, 0x03};
        this->channel_select = cs;
        this->channel_select_length = sizeof(cs);
        this->gate_source = gs;
        this->gate_source_length = sizeof(gs);
        break;
    }

    case EPD_2_7: {
        this->stage_time = 630; // milliseconds
        this->lines_per_display = 176;
        this->dots_per_line = 264;
        this->bytes_per_line = 264 / 8;
        this->bytes_per_scan = 176 / 4;
        this->filler = true;
        static uint8_t cs[] = {0x72, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x00};
        static uint8_t gs[] = {0x72, 0x00};
        this->channel_select = cs;
        this->channel_select_length = sizeof(cs);
        this->gate_source = gs;
        this->gate_source_length = sizeof(gs);
        break;
    }
    }

    this->factored_stage_time = this->stage_time;
}


void EPD_Class::begin() {

    // power up sequence
    SPI_put(0x00);

    digitalWrite(this->EPD_Pin_RESET, LOW);
    digitalWrite(this->EPD_Pin_PANEL_ON, LOW);
    digitalWrite(this->EPD_Pin_DISCHARGE, LOW);
    digitalWrite(this->EPD_Pin_BORDER, LOW);
    digitalWrite(this->EPD_Pin_EPD_CS, LOW);

    //PWM_start(this->EPD_Pin_PWM);
    EPD_Pin_PWM = 0.5;
    Delay_ms(5);
    digitalWrite(this->EPD_Pin_PANEL_ON, HIGH);
    Delay_ms(10);

    digitalWrite(this->EPD_Pin_RESET, HIGH);
    digitalWrite(this->EPD_Pin_BORDER, HIGH);
    digitalWrite(this->EPD_Pin_EPD_CS, HIGH);
    Delay_ms(5);

    digitalWrite(this->EPD_Pin_RESET, LOW);
    Delay_ms(5);

    digitalWrite(this->EPD_Pin_RESET, HIGH);
    Delay_ms(5);

    // wait for COG to become ready
    while (HIGH == digitalRead(this->EPD_Pin_BUSY)) {
    }

    // channel select
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x01), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, this->channel_select, this->channel_select_length);

    // DC/DC frequency
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x06), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0xff), 2);

    // high power mode osc
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x07), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x9d), 2);


    // disable ADC
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x08), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);

    // Vcom level
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x09), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0xd0, 0x00), 3);

    // gate and source voltage levels
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, this->gate_source, this->gate_source_length);

    Delay_ms(5);  //???

    // driver latch on
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x03), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

    // driver latch off
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x03), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);

    Delay_ms(5);

    // charge pump positive voltage on
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

    // final delay before PWM off
    Delay_ms(30);
    //PWM_stop(this->EPD_Pin_PWM);
    EPD_Pin_PWM = 0.0;

    // charge pump negative voltage on
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x03), 2);

    Delay_ms(30);

    // Vcom driver on
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x0f), 2);

    Delay_ms(30);

    // output enable to disable
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x24), 2);
}


void EPD_Class::end() {

    this->frame_fixed(0x55, EPD_normal); // dummy frame
    this->line(0x7fffu, 0, 0x55, false, EPD_normal); // dummy_line

    Delay_ms(25);

    digitalWrite(this->EPD_Pin_BORDER, LOW);
    Delay_ms(30);

    digitalWrite(this->EPD_Pin_BORDER, HIGH);

    // latch reset turn on
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x03), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x01), 2);

    // output enable off
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x05), 2);

    // Vcom power off
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x0e), 2);

    // power off negative charge pump
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x02), 2);

    // discharge
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x0c), 2);

    Delay_ms(120);

    // all charge pumps off
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x05), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);

    // turn of osc
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x07), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x0d), 2);

    // discharge internal - 1
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x50), 2);

    Delay_ms(40);

    // discharge internal - 2
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0xA0), 2);

    Delay_ms(40);

    // discharge internal - 3
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x00), 2);

    // turn of power and all signals
    digitalWrite(this->EPD_Pin_RESET, LOW);
    digitalWrite(this->EPD_Pin_PANEL_ON, LOW);
    digitalWrite(this->EPD_Pin_BORDER, LOW);
    digitalWrite(this->EPD_Pin_EPD_CS, LOW);

    digitalWrite(this->EPD_Pin_DISCHARGE, HIGH);

    SPI_put(0x00);

    Delay_ms(150);

    digitalWrite(this->EPD_Pin_DISCHARGE, LOW);
}


// convert a temperature in Celcius to
// the scale factor for frame_*_repeat methods
int EPD_Class::temperature_to_factor_10x(int temperature) {
    if (temperature <= -10) {
        return 170;
    } else if (temperature <= -5) {
        return 120;
    } else if (temperature <= 5) {
        return 80;
    } else if (temperature <= 10) {
        return 40;
    } else if (temperature <= 15) {
        return 30;
    } else if (temperature <= 20) {
        return 20;
    } else if (temperature <= 40) {
        return 10;
    }
    return 7;
}


// One frame of data is the number of lines * rows. For example:
// The 1.44” frame of data is 96 lines * 128 dots.
// The 2” frame of data is 96 lines * 200 dots.
// The 2.7” frame of data is 176 lines * 264 dots.

// the image is arranged by line which matches the display size
// so smallest would have 96 * 32 bytes

void EPD_Class::frame_fixed(uint8_t fixed_value, EPD_stage stage) {
    for (uint8_t line = 0; line < this->lines_per_display ; ++line) {
        this->line(line, 0, fixed_value, false, stage);
    }
}


void EPD_Class::frame_data(PROGMEM const uint8_t *image, EPD_stage stage){
    for (uint8_t line = 0; line < this->lines_per_display ; ++line) {
        this->line(line, &image[line * this->bytes_per_line], 0, true, stage);
    }
}


#if defined(EPD_ENABLE_EXTRA_SRAM)
void EPD_Class::frame_sram(const uint8_t *image, EPD_stage stage){
    for (uint8_t line = 0; line < this->lines_per_display ; ++line) {
        this->line(line, &image[line * this->bytes_per_line], 0, false, stage);
    }
}
#endif


void EPD_Class::frame_cb(uint32_t address, EPD_reader *reader, EPD_stage stage) {
    static uint8_t buffer[264 / 8];
    for (uint8_t line = 0; line < this->lines_per_display; ++line) {
        reader(buffer, address + line * this->bytes_per_line, this->bytes_per_line);
        this->line(line, buffer, 0, false, stage);
    }
}

void EPD_Class::frame_fixed_repeat(uint8_t fixed_value, EPD_stage stage) {
    long stage_time = this->factored_stage_time;
   
    do {         
        millis_start();
        unsigned long t_start = millis();
        this->frame_fixed(fixed_value, stage);        
        unsigned long t_end = millis();             
        if (t_end > t_start) {
            stage_time -= t_end - t_start;
        } else {
            stage_time -= t_start - t_end + 1 + ULONG_MAX;
        }
    } while (stage_time > 0);                
}


void EPD_Class::frame_data_repeat(PROGMEM const uint8_t *image, EPD_stage stage) {
    long stage_time = this->factored_stage_time;
    do {
        millis_start();
        unsigned long t_start = millis();
        this->frame_data(image, stage);
        unsigned long t_end = millis();
        if (t_end > t_start) {
            stage_time -= t_end - t_start;
        } else {
            stage_time -= t_start - t_end + 1 + ULONG_MAX;
        }
    } while (stage_time > 0);
}


#if defined(EPD_ENABLE_EXTRA_SRAM)
void EPD_Class::frame_sram_repeat(const uint8_t *image, EPD_stage stage) {
    long stage_time = this->factored_stage_time;
    do {
        millis_start();
        unsigned long t_start = millis();
        this->frame_sram(image, stage);
        unsigned long t_end = millis();
        if (t_end > t_start) {
            stage_time -= t_end - t_start;
        } else {
            stage_time -= t_start - t_end + 1 + ULONG_MAX;
        }
    } while (stage_time > 0);
}
#endif


void EPD_Class::frame_cb_repeat(uint32_t address, EPD_reader *reader, EPD_stage stage) {
    long stage_time = this->factored_stage_time;
    do {
        millis_start();
        unsigned long t_start = millis();
        this->frame_cb(address, reader, stage);
        unsigned long t_end = millis();
        if (t_end > t_start) {
            stage_time -= t_end - t_start;
        } else {
            stage_time -= t_start - t_end + 1 + ULONG_MAX;
        }
    } while (stage_time > 0);
}


void EPD_Class::line(uint16_t line, const uint8_t *data, uint8_t fixed_value, bool read_progmem, EPD_stage stage) {
    // charge pump voltage levels
    
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x04), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, this->gate_source, this->gate_source_length);

    // send data
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x0a), 2);
    Delay_us(10);

    // CS low
    digitalWrite(this->EPD_Pin_EPD_CS, LOW);
    SPI_put_wait(0x72, this->EPD_Pin_BUSY);

    // even pixels
    for (uint16_t b = this->bytes_per_line; b > 0; --b) {
        if (0 != data) {

            uint8_t pixels = data[b - 1] & 0xaa;

            switch(stage) {
            case EPD_compensate:  // B -> W, W -> B (Current Image)
                pixels = 0xaa | ((pixels ^ 0xaa) >> 1);
                break;
            case EPD_white:       // B -> N, W -> W (Current Image)
                pixels = 0x55 + ((pixels ^ 0xaa) >> 1);
                break;
            case EPD_inverse:     // B -> N, W -> B (New Image)
                pixels = 0x55 | (pixels ^ 0xaa);
                break;
            case EPD_normal:       // B -> B, W -> W (New Image)
                pixels = 0xaa | (pixels >> 1);
                break;
            }
            SPI_put_wait(pixels, this->EPD_Pin_BUSY);
        } else {
            SPI_put_wait(fixed_value, this->EPD_Pin_BUSY);
        }   }

    // scan line
    for (uint16_t b = 0; b < this->bytes_per_scan; ++b) {
        if (line / 4 == b) {
            SPI_put_wait(0xc0 >> (2 * (line & 0x03)), this->EPD_Pin_BUSY);
        } else {
            SPI_put_wait(0x00, this->EPD_Pin_BUSY);
        }
    }

    // odd pixels
    for (uint16_t b = 0; b < this->bytes_per_line; ++b) {
        if (0 != data) {

            uint8_t pixels = data[b] & 0x55;

            switch(stage) {
            case EPD_compensate:  // B -> W, W -> B (Current Image)
                pixels = 0xaa | (pixels ^ 0x55);
                break;
            case EPD_white:       // B -> N, W -> W (Current Image)
                pixels = 0x55 + (pixels ^ 0x55);
                break;
            case EPD_inverse:     // B -> N, W -> B (New Image)
                pixels = 0x55 | ((pixels ^ 0x55) << 1);
                break;
            case EPD_normal:       // B -> B, W -> W (New Image)
                pixels = 0xaa | pixels;
                break;
            }
            uint8_t p1 = (pixels >> 6) & 0x03;
            uint8_t p2 = (pixels >> 4) & 0x03;
            uint8_t p3 = (pixels >> 2) & 0x03;
            uint8_t p4 = (pixels >> 0) & 0x03;
            pixels = (p1 << 0) | (p2 << 2) | (p3 << 4) | (p4 << 6);
            SPI_put_wait(pixels, this->EPD_Pin_BUSY);
        } else {
            SPI_put_wait(fixed_value, this->EPD_Pin_BUSY);
        }
    }

    if (this->filler) {
        SPI_put_wait(0x00, this->EPD_Pin_BUSY);
    }

    // CS high
    digitalWrite(this->EPD_Pin_EPD_CS, HIGH);

    // output data to panel
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x70, 0x02), 2);
    Delay_us(10);
    SPI_send(this->EPD_Pin_EPD_CS, CU8(0x72, 0x2f), 2);
}


void EPD_Class::SPI_put(uint8_t c) {
    
    spi_.write(c);
    //spi_.fastWrite(c);
    
    
}



void EPD_Class::SPI_put_wait(uint8_t c, DigitalIn busy_pin) {

    SPI_put(c);

    // wait for COG ready
    while (HIGH == digitalRead(busy_pin)) {
    }
}


void EPD_Class::SPI_send(DigitalOut cs_pin, const uint8_t *buffer, uint16_t length) {

    // CS low
    digitalWrite(cs_pin, LOW);

    // send all data
    for (uint16_t i = 0; i < length; ++i) {
        spi_.write(*buffer++);
    }

    // CS high
    digitalWrite(cs_pin, HIGH);
}


//static void PWM_start(int pin) {
//    analogWrite(pin, 128);  // 50% duty cycle
//}


//static void PWM_stop(int pin) {
//    analogWrite(pin, 0);
//}