Last active
June 5, 2024 15:18
-
-
Save stecman/847dd15e80ba292c29da28ba62e972fe to your computer and use it in GitHub Desktop.
Good Display epaper/eink screen testing code - image data from computer over USART
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "GDEPaper.h" | |
// AVR | |
extern "C" { | |
#include <util/delay.h> | |
} | |
#define DDR_SPI DDRB | |
#define DD_MOSI PB2 | |
#define DD_SCK PB1 | |
#define DDR_BASE DDRB | |
#define PORT_BASE PORTB | |
#define PT_CS PB0 | |
#define PT_DC PB6 | |
#define PT_RES PB4 | |
#define PIN_BUSY PIND | |
#define PORT_BUSY PORTD | |
#define DDR_BUSY DDRD | |
#define PT_BUSY PD2 | |
GoodDisplayEpaper::GoodDisplayEpaper(void) | |
{ | |
init_pins(); | |
// Default internal active state to false, though this is not guaranteed until reset | |
m_active = false; | |
} | |
void GoodDisplayEpaper::wake_panel(void) | |
{ | |
// Put the device in a known state | |
// This also wakes the screen up if it was put in deep sleep | |
send_reset(); | |
// Chip select enable (low) | |
PORT_BASE &= ~(1<<PT_CS); | |
_delay_us(1); // CS setup time is ~40ns on the display | |
// Soft start the panel's booster circuitry | |
// The argument values here are taken from the datasheet's reference program | |
// These appear to be balanced settings between speed vs power saving | |
const uint8_t btst_args[] = { 0x17, 0x17, 0x17 }; | |
send_command(GD_BTST, btst_args, 3); | |
} | |
void GoodDisplayEpaper::sleep_panel(void) | |
{ | |
// Set border to floating | |
send_command(GD_CDI, 0x0); | |
// Stop onboard voltage converters | |
send_command(GD_POWER_OFF); | |
// Put the display in deep sleep mode | |
// The argument here is a required check-code to confirm the command | |
send_command(GD_DSLP, 0xA5); | |
// Chip select disable (high) | |
PORT_BASE |= (1<<PT_CS); | |
} | |
void GoodDisplayEpaper::send_command(const uint8_t data) | |
{ | |
wait_for_panel_ready(); | |
// Data/Command line goes low for sending command bytes | |
PORT_BASE &= ~(1<<PT_DC); | |
send_spi(data); | |
} | |
void GoodDisplayEpaper::send_command(const uint8_t command, const uint8_t argument) | |
{ | |
send_command(command); | |
send_data(argument); | |
} | |
void GoodDisplayEpaper::send_command(const uint8_t command, const uint8_t* arguments, uint8_t length) | |
{ | |
send_command(command); | |
for (uint8_t i = 0; i < length; ++i) { | |
send_data(arguments[i]); | |
} | |
} | |
void GoodDisplayEpaper::send_data(const uint8_t* data, uint8_t length) | |
{ | |
for (uint8_t i = 0; i < length; ++i) { | |
send_data(data[i]); | |
} | |
} | |
void GoodDisplayEpaper::send_data(const uint8_t data) | |
{ | |
// wait_for_panel_ready(); | |
// Data/Command line goes high for sending data | |
PORT_BASE |= (1<<PT_DC); | |
send_spi(data); | |
} | |
bool GoodDisplayEpaper::is_ready(void) | |
{ | |
return (PIN_BUSY & _BV(PT_BUSY)) > 0; | |
} | |
// Internal | |
void GoodDisplayEpaper::init_pins(void) | |
{ | |
// Control pins as outputs | |
DDR_BASE |= (1<<PT_CS) | (1<<PT_DC) | (1<<PT_RES); | |
// Drive reset high for normal state (reset occurs on RES low) | |
PORT_BASE |= (1<<PT_RES); | |
// Set SPI's SS pin high to prevent the SPI controller going into slave mode | |
// See http://www.avrfreaks.net/forum/avr-spi-problem | |
//PORT_BASE |= (1<<PT_DC); | |
// Busy pin as input | |
DDR_BUSY &= ~(1<<PT_BUSY); | |
// Start in command mode (DC low), with chip select disabled (CS high) | |
PORT_BASE &= ~(1<<PT_DC); | |
PORT_BASE |= (1<<PT_CS); | |
// Set SPI pins as output | |
DDR_SPI |= (1<<DD_MOSI) | (1<<DD_SCK); | |
// Enable SPI as master, set clock rate fck/2 | |
SPCR = (1<<SPE) | (1<<MSTR); | |
SPSR = (1<<SPI2X); | |
} | |
void GoodDisplayEpaper::send_spi(uint8_t data) | |
{ | |
// Send the data | |
SPDR = data; | |
// Wait for the transmission to complete | |
while (!(SPSR & (1<<SPIF))); | |
} | |
inline void GoodDisplayEpaper::wait_for_panel_ready(void) | |
{ | |
DDRD |= (1<<PD3); // Debug port as output | |
// Wait for the panel to become ready | |
// TODO: Timeout here and retry, or abort and return to caller | |
while ( (PIN_BUSY & _BV(PT_BUSY)) == 0 ){ | |
PORTD ^= (1<<PD3); | |
} | |
} | |
void GoodDisplayEpaper::send_reset(void) | |
{ | |
// Reset panel by pulling reset line low | |
PORT_BASE &= ~(1<<PT_RES); | |
_delay_us(50); | |
PORT_BASE |= (1<<PT_RES); | |
_delay_us(50); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
// AVR | |
extern "C" { | |
#include <avr/io.h> | |
} | |
#define GD_PSR 0x00 // Panel settings | |
#define GD_PWR 0x01 // Power setting | |
#define GD_BTST 0x06 // Booster soft start | |
#define GD_POWER_ON 0x04 | |
#define GD_POWER_OFF 0x02 | |
#define GD_DSLP 0x07 // Deep sleep | |
#define GD_CDI 0x50 // VCOM and data interval setting | |
#define GD_PLL 0x30 // PLL control (panel refresh rate?) | |
#define GD_TRES 0x61 // Resolution settings | |
#define GD_DTM1 0x10 // Start data transmission (old frame for BW mode, or black in BWR mode) | |
#define GD_DTM2 0x13 // Start data transmission (new frame for BW mode, or red in BWR mode) | |
#define GD_DSTOP 0x11 // End data transmission | |
#define GD_REFRESH 0x12 // Display refresh (DRF in datasheet) | |
/** | |
* Base class for Good Display E-paper products | |
*/ | |
class GoodDisplayEpaper { | |
public: | |
GoodDisplayEpaper(void); | |
/// Ensure the display is in the required modes for operation | |
void wake_panel(void); | |
/// Power down the panel | |
void sleep_panel(void); | |
// Basic communication methods | |
void send_command(const uint8_t data); | |
void send_data(const uint8_t data); | |
// Helper methods for communication | |
void send_command(const uint8_t command, const uint8_t argument); | |
void send_command(const uint8_t command, const uint8_t* arguments, uint8_t length); | |
void send_data(const uint8_t* data, uint8_t length); | |
bool is_ready(void); | |
protected: | |
/// Ensure pins are setup correctly for operation | |
void init_pins(void); | |
/// Send a single byte over SPI | |
void send_spi(const uint8_t data); | |
/// Hardware reset | |
void send_reset(void); | |
/// Wait for the panel's busy line to go high ( = ready to accept input) | |
inline void wait_for_panel_ready(void); | |
/// Border black/white fill | |
bool m_active; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "GDEW042.h" | |
// Border fill masks | |
#define CDI_BORDER_BLACK 0b10000000 | |
#define CDI_BORDER_WHITE 0b01000000 | |
const uint8_t lut_vcom0[] = | |
{ | |
0x00, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x00, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, | |
}; | |
const uint8_t lut_ww[] = { | |
0x40, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
}; | |
const uint8_t lut_bw[] = { | |
0x40, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
}; | |
const uint8_t lut_wb[] = { | |
0x80, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
}; | |
const uint8_t lut_bb[] = { | |
0x80, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
}; | |
void GDEW042::wake_panel() | |
{ | |
GoodDisplayEpaper::wake_panel(); | |
// Configure controller to use it's onboard voltage conversion | |
const uint8_t pwr_args[] = { | |
0x03, // Use internal VDG and VDS DC/DC converters | |
0x00, // -16V/16V VGL/VGH voltage | |
0x2b, // VDH B/W pixel power selection 11V | |
0x2b, // VDL B/W pixel power selection -11V | |
0xff, // red pixel power selection (irrelevant but required) | |
}; | |
send_command(GD_PWR, pwr_args, 5); | |
// Power on the panel | |
send_command(GD_POWER_ON); | |
// Configure panel with default resolution, B/W pixels, LUT from register | |
send_command(GD_PSR, (0b00111111)); | |
// Set the refresh framerate to 200Hz | |
send_command(GD_PLL, 0b00111001); | |
// Set gate/source non-overlap period | |
send_command(0x60, 0b00000000); | |
// Set panel resolution | |
// This is marked in the datasheet as an override, but it appears to be required | |
// Arugments are two bytes each for HRES and VRES | |
const uint8_t tres_args[] = { 0x01, 0x90, 0x01, 0x2c }; | |
send_command(GD_TRES, tres_args, 4); | |
// VCOM DC settings (VCM_DC) | |
send_command(0x82, 0b00010010); | |
// VCOM and data interval setting + border fill | |
send_command(GD_CDI, 0b00001111 | CDI_BORDER_WHITE); | |
// Send custom look-up tables | |
send_command(0x20, lut_vcom0, sizeof(lut_vcom0)); | |
send_command(0x21, lut_ww, sizeof(lut_ww)); | |
send_command(0x22, lut_bw, sizeof(lut_bw)); | |
send_command(0x23, lut_wb, sizeof(lut_wb)); | |
send_command(0x24, lut_bb, sizeof(lut_bb)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "GDEPaper.h" | |
class GDEW0213 : public GoodDisplayEpaper { | |
public: | |
void wake_panel(void); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C | |
#include <stdlib.h> | |
// AVR | |
extern "C" { | |
#include <avr/io.h> | |
#include <avr/wdt.h> | |
#include <util/delay.h> | |
} | |
#include "GDEW042.h" | |
#define BAUD 9600 | |
#define MYUBRR F_CPU/16/BAUD-1 | |
void init_usart(void) | |
{ | |
cli(); | |
// Set baud rate to 76.8K (with 16MHz clock) | |
// This is hard-coded for now as I was having issues with the calculated value | |
UBRR0H = (uint8_t)(12 >> 8); | |
UBRR0L = (uint8_t)(12); | |
// Enable receiver and transmitter | |
UCSR0B = _BV(RXEN0) | _BV(TXEN0); | |
// Set frame format: 8 data, 1 stop bit | |
UCSR0C = (3<<UCSZ00); | |
UCSR0C &= ~(1<<USBS0); | |
sei(); | |
} | |
void usart_transmit(uint8_t data) | |
{ | |
// Wait for empty transmit buffer | |
while ( !( UCSR0A & (1<<UDRE0)) ); | |
// Put data into buffer, sends the data | |
UDR0 = data; | |
} | |
uint8_t usart_receive(void) | |
{ | |
// Wait for data to be received | |
while ( !(UCSR0A & (1<<RXC0)) ); | |
// Get and return received data from buffer | |
return UDR0; | |
} | |
int main(void) | |
{ | |
// Recover from any watchdog timer reset mishaps | |
wdt_reset(); | |
wdt_disable(); | |
// select minimal prescaler (max system speed) | |
CLKPR = 0x80; | |
CLKPR = 0x00; | |
init_usart(); | |
GDEW042 display; | |
uint8_t datin; | |
for (;;) { | |
display.wake_panel(); | |
display.send_command(GD_DTM1); | |
for (uint16_t i = 0; i < 15000; ++i) { | |
display.send_data( usart_receive() ); | |
// usart_transmit( datin ); | |
} | |
display.send_command(GD_DSTOP, 0x00); // Data stop | |
display.send_command(GD_DTM2); | |
for (uint16_t i = 0; i < 15000; ++i) { | |
display.send_data( usart_receive() ); | |
} | |
display.send_command(GD_DSTOP, 0x00); // Data stop | |
display.send_command(GD_REFRESH); // Refresh display | |
display.sleep_panel(); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DEVICE = atmega64a | |
DEVICEDUDE = m64 # device passed to avrdude (this differs sometimes from the avr-gcc device name) | |
CLOCK = 12000000 | |
PROGRAMMER = -c dragon_jtag | |
FUSES = -U hfuse:w:0xd9:m -U lfuse:w:0x24:m | |
SOURCES = $(shell find -name '*.cpp' -or -name '*.S') | |
OBJECTS = $(SOURCES:.cpp=.o) | |
AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICEDUDE) | |
COMPILE = avr-g++ -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) | |
COMPILE += -I -I. -I./lib/ | |
COMPILE += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums | |
COMPILE += -ffunction-sections -fdata-sections -Wl,--gc-sections | |
COMPILE += -Wl,--relax -mcall-prologues | |
COMPILE += -fno-exceptions -std=c++14 | |
C_OPTIONS = -std=gnu99 -Wstrict-prototypes | |
# symbolic targets: | |
all: $(SOURCES) main.hex | |
.cpp.o: | |
$(COMPILE) -c $< -o $@ | |
.S.o: | |
$(COMPILE) -x assembler-with-cpp -c $< -o $@ | |
# "-x assembler-with-cpp" should not be necessary since this is the default | |
# file type for the .S (with capital S) extension. However, upper case | |
# characters are not always preserved on Windows. To ensure WinAVR | |
# compatibility define the file type manually. | |
.cpp.s: | |
$(COMPILE) -S $< -o $@ | |
flash: all | |
$(AVRDUDE) -U flash:w:main.hex:i | |
# if you use a bootloader, change the command below appropriately: | |
load: all | |
bootloadHID main.hex | |
clean: | |
find -name '*.d' -exec rm {} + | |
find -name '*.o' -exec rm {} + | |
rm -f main.hex main.elf | |
# file targets: | |
main.elf: $(OBJECTS) | |
$(COMPILE) -o main.elf $(OBJECTS) | |
main.hex: main.elf | |
rm -f main.hex | |
avr-objcopy -j .text -j .data -O ihex main.elf main.hex | |
avr-size --format=avr --mcu=$(DEVICE) main.elf | |
# If you have an EEPROM section, you must also create a hex file for the | |
# EEPROM and add it to the "flash" target. | |
# Targets for code debugging and analysis: | |
disasm: main.elf | |
avr-objdump -d main.elf | |
cpp: | |
$(COMPILE) -E main.c |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment