Peter Drescher's library for the Embedded Artists E-paper display, but modified to also support the LPC4088 QuickStart Board
Fork of EaEpaper by
EPD.cpp
- Committer:
- embeddedartists
- Date:
- 2013-12-04
- Revision:
- 3:6fb3e296a6fd
- Parent:
- 0:fedcef5319f5
File content as of revision 3:6fb3e296a6fd:
// 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" #if !defined(TARGET_LPC4088) #include "BurstSPI.h" #endif // 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() 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(); 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(); // 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) { #if defined(TARGET_LPC4088) spi_.write(*buffer++); #elif spi_.fastWrite(*buffer++); spi_.clearRX(); #endif } // CS high digitalWrite(cs_pin, HIGH); } #if defined(TARGET_LPC4088) void EPD_Class::PWM_flip() { EPD_Pin_PWM = !EPD_Pin_PWM; } #endif void EPD_Class::PWM_start() { #if defined(TARGET_LPC4088) pwmTicker.attach_us(this, &EPD_Class::PWM_flip, 5); #elif // we need at least a 100kHz signal -> 10us period EPD_Pin_PWM.period_us(10); // 50% duty cycle EPD_Pin_PWM = 0.5; #endif } void EPD_Class::PWM_stop() { #if defined(TARGET_LPC4088) pwmTicker.detach(); #elif EPD_Pin_PWM = 0; #endif }