#include "epaper.h" #include "lptmr.h" #include "fsl_debug_console.h" #include "fsl_gpio_driver.h" #include "fsl_spi_master_driver.h" typedef enum { /* Image pixel -> Display pixel */ EPD_compensate, /* B -> W, W -> B (Current Image) */ EPD_white, /* B -> N, W -> W (Current Image) */ EPD_inverse, /* B -> N, W -> B (New Image) */ EPD_normal /* B -> B, W -> W (New Image) */ } EPD_stage; #ifdef BADGE_V2 static const gpio_output_pin_user_config_t pinBorder = { .pinName = GPIO_MAKE_PIN(GPIOD_IDX, 7), .config.outputLogic = 0, }; static const gpio_output_pin_user_config_t pinReset = { .pinName = GPIO_MAKE_PIN(GPIOA_IDX, 1), .config.outputLogic = 0, }; #endif static const gpio_output_pin_user_config_t pinCS = { #ifdef BADGE_V1 .pinName = GPIO_MAKE_PIN(GPIOD_IDX, 4), #else .pinName = GPIO_MAKE_PIN(GPIOD_IDX, 0), #endif .config.outputLogic = 1, }; static const spi_master_user_config_t spiConfig = { .bitsPerSec = 20000000, /* max G2 COG supports is 20 MHz */ .polarity = kSpiClockPolarity_ActiveHigh, .phase = kSpiClockPhase_FirstEdge, .direction = kSpiMsbFirst, .bitCount = kSpi8BitMode, }; static const int LINES_PER_DISPLAY = EPD_H; static const int BYTES_PER_SCAN = EPD_H / 4 / 2; static const int BYTES_PER_LINE = EPD_W / 8; #ifdef BADGE_V1 static const uint8_t channel_select[] = { 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0xff }; static const int SPI_UNIT = 1; #else static const uint8_t channel_select[] = { 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x00 }; static const int SPI_UNIT = 0; #endif static spi_master_state_t spiState; void EPD_Init() { #ifdef BADGE_V1 PORT_HAL_SetMuxMode(g_portBase[GPIOD_IDX], 5, kPortMuxAlt2); PORT_HAL_SetMuxMode(g_portBase[GPIOD_IDX], 6, kPortMuxAlt2); PORT_HAL_SetMuxMode(g_portBase[GPIOD_IDX], 7, kPortMuxAlt2); #else PORT_HAL_SetMuxMode(g_portBase[GPIOD_IDX], 1, kPortMuxAlt2); PORT_HAL_SetMuxMode(g_portBase[GPIOD_IDX], 2, kPortMuxAlt2); PORT_HAL_SetMuxMode(g_portBase[GPIOD_IDX], 3, kPortMuxAlt2); GPIO_DRV_OutputPinInit(&pinBorder); GPIO_DRV_OutputPinInit(&pinReset); #endif GPIO_DRV_OutputPinInit(&pinCS); } static spi_status_t SPI_Transfer(const uint8_t *tx, uint8_t *rx, size_t count) { spi_status_t rc; rc = SPI_DRV_MasterTransfer(SPI_UNIT, NULL, tx, rx, count); if (rc != kStatus_SPI_Success) { return rc; } int i, timeout = count * 4; for (i = 0; i < timeout; ++i) { rc = SPI_DRV_MasterGetTransferStatus(SPI_UNIT, NULL); if (rc == kStatus_SPI_Success) { return rc; } } return rc; } static void EPD_WriteCommandBuffer(uint8_t index, const uint8_t *data, size_t length) { uint8_t tx[3] = { 0x70, index, 0x72 }; GPIO_DRV_WritePinOutput(pinCS.pinName, 0); SPI_Transfer(tx, NULL, 2); GPIO_DRV_WritePinOutput(pinCS.pinName, 1); GPIO_DRV_WritePinOutput(pinCS.pinName, 0); SPI_Transfer(&tx[2], NULL, 1); SPI_Transfer(data, NULL, length); GPIO_DRV_WritePinOutput(pinCS.pinName, 1); } static void EPD_WriteCommandByte(uint8_t index, uint8_t data) { EPD_WriteCommandBuffer(index, &data, 1); } static uint8_t EPD_ReadCommand(uint8_t index) { uint8_t tx[4] = { 0x70, index, 0x73, 0x00 }; uint8_t rx[2]; GPIO_DRV_WritePinOutput(pinCS.pinName, 0); SPI_Transfer(tx, NULL, 2); GPIO_DRV_WritePinOutput(pinCS.pinName, 1); GPIO_DRV_WritePinOutput(pinCS.pinName, 0); SPI_Transfer(&tx[2], rx, 2); GPIO_DRV_WritePinOutput(pinCS.pinName, 1); return rx[1]; } static uint8_t EPD_ReadCogID() { uint8_t tx[2] = { 0x71, 0x00 }; uint8_t rx[2]; GPIO_DRV_WritePinOutput(pinCS.pinName, 0); SPI_Transfer(tx, rx, 2); GPIO_DRV_WritePinOutput(pinCS.pinName, 1); return rx[1]; } static void EPD_Delay(uint32_t ms) { delay_ms(ms); } static void EPD_line(int line, const uint8_t *data, uint8_t fixed_value, EPD_stage stage) { size_t len = BYTES_PER_SCAN * 2 + BYTES_PER_LINE * 2 + 1; uint8_t buf[len], *p = buf; int i; #ifdef BADGE_V1 for (i = 0; i < BYTES_PER_SCAN; ++i) { if (0 != (line & 0x01) && line / 8 == i) { *p++ = 0xc0 >> (line & 0x06); } else { *p++ = 0x00; } } for (i = BYTES_PER_LINE; i > 0; --i) { if (data) { uint16_t pixels = data[i - 1]; /* interleave bits with zeros */ pixels = (pixels | (pixels << 4)) & 0x0f0f; pixels = (pixels | (pixels << 2)) & 0x3333; pixels = (pixels | (pixels << 1)) & 0x5555; switch (stage) { case EPD_compensate: pixels = 0xaaaa | (pixels ^ 0x5555); break; case EPD_white: pixels = 0x5555 + (pixels ^ 0x5555); break; case EPD_inverse: pixels = 0x5555 | ((pixels ^ 0x5555) << 1); break; case EPD_normal: pixels = 0xaaaa | pixels; break; } *p++ = pixels >> 8; *p++ = pixels; } else { *p++ = fixed_value; *p++ = fixed_value; } } for (i = BYTES_PER_SCAN; i > 0; --i) { if (0 == (line & 0x01) && line / 8 == i - 1) { *p++ = 0x03 << (line & 0x06); } else { *p++ = 0x00; } } /* border byte */ if (EPD_normal == stage) { *p++ = 0xaa; } else { *p++ = 0x00; } #else /* zero byte */ *p++ = 0; /* "Odd" pixels counting down */ for (i = BYTES_PER_LINE; i > 0; --i) { if (data) { /* i is off-by-one to match datasheet's 1-based counting */ uint8_t pixels = data[i - 1]; /* Take even pixels (1-based counting again) */ pixels = pixels & 0x55; switch (stage) { case EPD_compensate: pixels = 0xaa | (pixels ^ 0x55); break; case EPD_white: pixels = 0x55 + (pixels ^ 0x55); break; case EPD_inverse: pixels = 0x55 | ((pixels ^ 0x55) << 1); break; case EPD_normal: pixels = 0xaa | pixels; break; } *p++ = pixels; } else { *p++ = fixed_value; } } /* All scanlines counting down */ for (i = BYTES_PER_SCAN * 2; i > 0; --i) { if (line / 4 == i - 1) { *p++ = 0x03 << ((line & 0x03) * 2); } else { *p++ = 0x00; } } /* "Even" pixels counting up */ for (i = 1; i <= BYTES_PER_LINE; ++i) { if (data) { /* i is off-by-one to match datasheet's 1-based counting */ uint8_t pixels = data[i - 1]; /* Take odd pixels in reverse order (yes odd) */ pixels = (pixels & 0xaa) >> 1; pixels = ((pixels & 0xcc) >> 2) | ((pixels & 0x33) << 2); pixels = ((pixels & 0xf0) >> 4) | ((pixels & 0x0f) << 4); switch (stage) { case EPD_compensate: pixels = 0xaa | (pixels ^ 0x55); break; case EPD_white: pixels = 0x55 + (pixels ^ 0x55); break; case EPD_inverse: pixels = 0x55 | ((pixels ^ 0x55) << 1); break; case EPD_normal: pixels = 0xaa | pixels; break; } *p++ = pixels; } else { *p++ = fixed_value; } } #endif EPD_WriteCommandBuffer(0x0a, buf, len); /* turn on OE */ EPD_WriteCommandByte(0x02, 0x07); } static void EPD_frame(const uint8_t *data, uint8_t fixed_value, EPD_stage stage) { int i; if (data) { for (i = 0; i < LINES_PER_DISPLAY; ++i) { EPD_line(i, &data[i * BYTES_PER_LINE], 0, stage); } } else { for (i = 0; i < LINES_PER_DISPLAY; ++i) { EPD_line(i, NULL, fixed_value, stage); } } } static void EPD_frame_repeat(const uint8_t *data, uint8_t fixed_value, EPD_stage stage) { /* this needs to repeat for about 630ms */ uint32_t until = get_ms() + 630; do { EPD_frame(data, fixed_value, stage); } while (get_ms() < until); } int EPD_Draw(const uint8_t *old_image, const uint8_t *new_image) { int rc = 0; /* Configure SPI1 */ SPI_DRV_MasterInit(SPI_UNIT, &spiState); uint32_t calculatedBaudRate; SPI_DRV_MasterConfigureBus(SPI_UNIT, &spiConfig, &calculatedBaudRate); debug_printf("EPD baud rate %u Hz\r\n", calculatedBaudRate); #ifdef BADGE_V2 /* Turn off BORDER_CONTROL */ GPIO_DRV_WritePinOutput(pinBorder.pinName, 1); /* Reset */ GPIO_DRV_WritePinOutput(pinReset.pinName, 1); EPD_Delay(5); GPIO_DRV_WritePinOutput(pinReset.pinName, 0); EPD_Delay(5); GPIO_DRV_WritePinOutput(pinReset.pinName, 1); EPD_Delay(5); #endif /* read the COG ID */ uint8_t id = EPD_ReadCogID(); if ((id & 0x0f) != 0x02) { rc = -1; goto out; } /* disable OE */ EPD_WriteCommandByte(0x02, 0x40); /* check breakage */ uint8_t broken_panel = EPD_ReadCommand(0x0f); if (0x00 == (0x80 & broken_panel)) { rc = -2; goto out; } /* power saving mode */ EPD_WriteCommandByte(0x0b, 0x02); /* channel select */ EPD_WriteCommandBuffer(0x01, channel_select, sizeof channel_select); /* high power mode osc */ EPD_WriteCommandByte(0x07, 0xd1); /* power setting */ EPD_WriteCommandByte(0x08, 0x02); /* Vcom level */ EPD_WriteCommandByte(0x09, 0xc2); /* power setting */ EPD_WriteCommandByte(0x04, 0x03); /* driver latch on */ EPD_WriteCommandByte(0x03, 0x01); /* driver latch off */ EPD_WriteCommandByte(0x03, 0x00); EPD_Delay(5); int i; for (i = 0; i < 4; ++i) { /* charge pump positive voltage on - VGH/VDL on */ EPD_WriteCommandByte(0x05, 0x01); EPD_Delay(150); /* charge pump negative voltage on - VGL/VDL on */ EPD_WriteCommandByte(0x05, 0x03); EPD_Delay(90); /* charge pump Vcom on - Vcom driver on */ EPD_WriteCommandByte(0x05, 0x0f); EPD_Delay(40); /* check DC/DC */ uint8_t dc_state = EPD_ReadCommand(0x0f); if (0x40 & dc_state) break; } if (4 == i) { rc = -3; goto out; } /* output enable to disable */ EPD_WriteCommandByte(0x02, 0x06); /* Draw something */ /* TODO I'd like to call a callback at this point with a "graphics context" * that contains a buffer and the screen coordinates that correspond to * the part of the screen to be drawn. That way we can have as little or * as much of the image in RAM as we want while drawing. The callback would * be called for each portion of the screen we're "caching" in memory while * pushing bits to the display. */ EPD_frame_repeat(old_image, 0xff, EPD_compensate); EPD_frame_repeat(old_image, 0xaa, EPD_white); EPD_frame_repeat(new_image, 0, EPD_inverse); EPD_frame_repeat(new_image, 0, EPD_normal); /* Write a "nothing" frame */ EPD_frame(NULL, 0x00, EPD_normal); /* Write a dummy line */ EPD_line(-1, NULL, 0x00, EPD_normal); EPD_Delay(25); #ifdef BADGE_V2 /* Turn on BORDER_CONTROL */ GPIO_DRV_WritePinOutput(pinBorder.pinName, 0); EPD_Delay(100); /* Turn off BORDER_CONTROL */ GPIO_DRV_WritePinOutput(pinBorder.pinName, 1); #else EPD_Delay(75); #endif out: /* ??? */ EPD_WriteCommandByte(0x0b, 0x00); /* latch reset turn on */ EPD_WriteCommandByte(0x03, 0x01); /* power off charge pump Vcom */ EPD_WriteCommandByte(0x05, 0x03); /* power off charge pump neg voltage */ EPD_WriteCommandByte(0x05, 0x01); EPD_Delay(300); /* discharge internal */ EPD_WriteCommandByte(0x04, 0x80); /* power off all charge pumps */ EPD_WriteCommandByte(0x05, 0x00); /* turn off osc */ EPD_WriteCommandByte(0x07, 0x01); EPD_Delay(50); #ifdef BADGE_V2 /* Turn on BORDER_CONTROL */ GPIO_DRV_WritePinOutput(pinBorder.pinName, 0); /* TODO should RESET go low to save power? */ GPIO_DRV_WritePinOutput(pinReset.pinName, 0); #endif SPI_DRV_MasterDeinit(SPI_UNIT); return rc; } /* vim: set expandtab ts=4 sw=4: */