2020-01-29 17:30:50 +00:00
|
|
|
|
/*
|
|
|
|
|
* Author: LoBo (loboris@gmail.com, loboris.github)
|
|
|
|
|
*
|
|
|
|
|
* Module supporting SPI ePaper displays
|
|
|
|
|
*
|
|
|
|
|
* HIGH SPEED LOW LEVEL DISPLAY FUNCTIONS
|
|
|
|
|
* USING DIRECT or DMA SPI TRANSFER MODEs
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "spi_master_lobo.h"
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include "esp_system.h"
|
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
#include "freertos/task.h"
|
|
|
|
|
#include "esp_heap_alloc_caps.h"
|
|
|
|
|
#include "soc/spi_reg.h"
|
|
|
|
|
#include "EPDspi.h"
|
|
|
|
|
|
|
|
|
|
#define EPD_DEBUG 1
|
|
|
|
|
|
2020-01-31 21:17:46 +00:00
|
|
|
|
#define EPD_BORDER_WHITE 0x61
|
|
|
|
|
#define EPD_BORDER_BLACK 0x51
|
|
|
|
|
#define EPD_BORDER EPD_BORDER_BLACK
|
|
|
|
|
|
2020-01-29 17:30:50 +00:00
|
|
|
|
#define EPD2X9 1
|
|
|
|
|
|
|
|
|
|
#define xDot 128
|
|
|
|
|
#define yDot 296
|
|
|
|
|
#define DELAYTIME 1500
|
|
|
|
|
|
2020-01-29 20:51:49 +00:00
|
|
|
|
// ===== Global ======
|
|
|
|
|
spi_lobo_device_handle_t disp_spi;
|
|
|
|
|
uint8_t *gs_disp_buffer;
|
|
|
|
|
uint8_t *disp_buffer;
|
|
|
|
|
uint8_t *gs_drawBuff;
|
|
|
|
|
uint8_t *drawBuff;
|
|
|
|
|
int _width;
|
|
|
|
|
int _height;
|
|
|
|
|
uint16_t gs_used_shades;
|
|
|
|
|
uint8_t _gs;
|
|
|
|
|
uint8_t *LUT_part;
|
|
|
|
|
uint8_t LUTDefault_fastest[31];
|
|
|
|
|
uint8_t LUTDefault_part[31];
|
|
|
|
|
uint8_t LUT_gs[31];
|
|
|
|
|
uint8_t LUTDefault_full[31];
|
|
|
|
|
uint8_t lvl_buf[16];
|
|
|
|
|
uint8_t lvl_buf_jpg[16];
|
|
|
|
|
// =====
|
|
|
|
|
|
2020-01-29 17:30:50 +00:00
|
|
|
|
static uint8_t GDOControl[] = {0x01, (yDot-1)%256, (yDot-1)/256, 0x00};
|
|
|
|
|
static uint8_t softstart[4] = {0x0c, 0xd7, 0xd6, 0x9d};
|
|
|
|
|
static uint8_t VCOMVol[2] = {0x2c, 0xa8}; // VCOM 7c
|
|
|
|
|
static uint8_t DummyLine[2] = {0x3a, 0x1a}; // 4 dummy line per gate
|
|
|
|
|
static uint8_t Gatetime[2] = {0x3b, 0x08}; // 2us per line
|
|
|
|
|
static uint8_t RamDataEntryMode[2] = {0x11, 0x01}; // Ram data entry mode
|
2020-01-31 21:17:46 +00:00
|
|
|
|
static uint8_t Border[2] = {0x3c, EPD_BORDER}; // Border control ( 0x61: white border; 0x51: black border
|
2020-01-29 17:30:50 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
There are totally 20 phases for programmable Source waveform of different phase length.
|
|
|
|
|
The phase period defined as TP [n] * T FRAME , where TP [n] range from 0 to 15.
|
|
|
|
|
TP [n] = 0 indicates phase skipped
|
|
|
|
|
Source Voltage Level: VS [n-XY] is constant in each phase
|
|
|
|
|
VS [n-XY] indicates the voltage in phase n for transition from GS X to GS Y
|
|
|
|
|
00 – VSS
|
|
|
|
|
01 – VSH
|
|
|
|
|
10 – VSL
|
|
|
|
|
11 – NA
|
|
|
|
|
VS [n-XY] and TP[n] are stored in waveform lookup table register [LUT].
|
|
|
|
|
|
|
|
|
|
VS coding: VS[0-11] VS[0-10] VS[0-01] VS[0-00]
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
// --- VS ---- ---- TP ----
|
|
|
|
|
//uint8_t LUTDefault_full[31] = {0x32, 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};
|
|
|
|
|
uint8_t LUTDefault_full[31] = {0x32, 0x11,0x11,0x10,0x02,0x02,0x22,0x22,0x22,0x22,0x22,0x51,0x51,0x55,0x88,0x08,0x08,0x88,0x88,0x00,0x00, 0x34,0x23,0x12,0x21,0x24,0x28,0x22,0x21,0xA1,0x01};
|
|
|
|
|
uint8_t LUTDefault_part[31] = {0x32, 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};
|
|
|
|
|
uint8_t LUT_gs[31] = {0x32, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
|
|
|
|
|
uint8_t LUTFastest[31] = {0x32, 0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
|
|
|
|
|
|
|
|
|
|
uint8_t lvl_buf[16] = {32,70,110,150,185,210,220,225,230,235,240,243,248,251,253,255};
|
|
|
|
|
uint8_t lvl_buf_jpg[16] = {4,8,12,16,22,30,40,60,80,110,140,180,220,240,250,255};
|
|
|
|
|
|
|
|
|
|
uint8_t *LUT_part = LUTDefault_part;
|
|
|
|
|
spi_lobo_device_handle_t disp_spi = NULL;
|
|
|
|
|
uint8_t *gs_disp_buffer = NULL;
|
|
|
|
|
uint8_t *disp_buffer = NULL;
|
|
|
|
|
uint8_t *drawBuff = NULL;
|
|
|
|
|
uint8_t *gs_drawBuff = NULL;
|
|
|
|
|
int _width = EPD_DISPLAY_WIDTH;
|
|
|
|
|
int _height = EPD_DISPLAY_HEIGHT;
|
|
|
|
|
uint8_t _gs = 0;
|
|
|
|
|
|
|
|
|
|
uint16_t gs_used_shades = 0;
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------
|
|
|
|
|
static void IRAM_ATTR _dma_send(uint8_t *data, uint32_t size)
|
|
|
|
|
{
|
|
|
|
|
//Fill DMA descriptors
|
|
|
|
|
spi_lobo_dmaworkaround_transfer_active(disp_spi->host->dma_chan); //mark channel as active
|
|
|
|
|
spi_lobo_setup_dma_desc_links(disp_spi->host->dmadesc_tx, size, data, false);
|
|
|
|
|
disp_spi->host->hw->user.usr_mosi_highpart=0;
|
|
|
|
|
disp_spi->host->hw->dma_out_link.addr=(int)(&disp_spi->host->dmadesc_tx[0]) & 0xFFFFF;
|
|
|
|
|
disp_spi->host->hw->dma_out_link.start=1;
|
|
|
|
|
disp_spi->host->hw->user.usr_mosi_highpart=0;
|
|
|
|
|
|
|
|
|
|
disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = (size * 8) - 1;
|
|
|
|
|
|
|
|
|
|
// Start transfer
|
|
|
|
|
disp_spi->host->hw->cmd.usr = 1;
|
|
|
|
|
// Wait for SPI bus ready
|
|
|
|
|
while (disp_spi->host->hw->cmd.usr);
|
|
|
|
|
|
|
|
|
|
//Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset.
|
|
|
|
|
if (disp_spi->host->dma_chan) spi_lobo_dmaworkaround_idle(disp_spi->host->dma_chan);
|
|
|
|
|
|
|
|
|
|
// Reset DMA
|
|
|
|
|
disp_spi->host->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST;
|
|
|
|
|
disp_spi->host->hw->dma_out_link.start=0;
|
|
|
|
|
disp_spi->host->hw->dma_in_link.start=0;
|
|
|
|
|
disp_spi->host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST);
|
|
|
|
|
disp_spi->host->hw->dma_conf.out_data_burst_en=1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
static void IRAM_ATTR _direct_send(uint8_t *data, uint32_t len, uint8_t rep)
|
|
|
|
|
{
|
|
|
|
|
uint32_t cidx = 0; // buffer index
|
|
|
|
|
uint32_t wd = 0;
|
|
|
|
|
int idx = 0;
|
|
|
|
|
int bits = 0;
|
|
|
|
|
int wbits = 0;
|
|
|
|
|
|
|
|
|
|
taskDISABLE_INTERRUPTS();
|
|
|
|
|
|
|
|
|
|
while (len) {
|
|
|
|
|
|
|
|
|
|
wd |= (uint32_t)data[idx] << wbits;
|
|
|
|
|
wbits += 8;
|
|
|
|
|
if (wbits == 32) {
|
|
|
|
|
bits += wbits;
|
|
|
|
|
wbits = 0;
|
|
|
|
|
disp_spi->host->hw->data_buf[idx++] = wd;
|
|
|
|
|
wd = 0;
|
|
|
|
|
}
|
|
|
|
|
len--; // Decrement data counter
|
|
|
|
|
if (rep == 0) cidx++; // if not repeating data, increment buffer index
|
|
|
|
|
}
|
|
|
|
|
if (bits) {
|
|
|
|
|
while (disp_spi->host->hw->cmd.usr); // Wait for SPI bus ready
|
|
|
|
|
// Load send buffer
|
|
|
|
|
disp_spi->host->hw->user.usr_mosi_highpart = 0;
|
|
|
|
|
disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = bits-1;
|
|
|
|
|
disp_spi->host->hw->user.usr_mosi = 1;
|
|
|
|
|
disp_spi->host->hw->miso_dlen.usr_miso_dbitlen = 0;
|
|
|
|
|
disp_spi->host->hw->user.usr_miso = 0;
|
|
|
|
|
disp_spi->host->hw->cmd.usr = 1; // Start transfer
|
|
|
|
|
}
|
|
|
|
|
// Wait for SPI bus ready
|
|
|
|
|
while (disp_spi->host->hw->cmd.usr);
|
|
|
|
|
taskENABLE_INTERRUPTS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
// === Main function to send data to display ======================
|
|
|
|
|
// If rep==true: repeat sending data to display 'len' times
|
|
|
|
|
// If rep==false: send 'len' data bytes from buffer to display
|
|
|
|
|
// ** Device must already be selected and address window set **
|
|
|
|
|
// ================================================================
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
static void IRAM_ATTR SPI_send_data(uint8_t *data, uint32_t len, uint8_t rep)
|
|
|
|
|
{
|
|
|
|
|
if (len == 0) return;
|
|
|
|
|
|
|
|
|
|
if ((len*8) <= 512) _direct_send(data, len, rep);
|
|
|
|
|
else if (rep == 0) _dma_send(data, len);
|
|
|
|
|
else {
|
|
|
|
|
// ==== Repeat data, more than 512 bits total ====
|
|
|
|
|
uint8_t *transbuf = pvPortMallocCaps(len, MALLOC_CAP_DMA);
|
|
|
|
|
if (transbuf == NULL) return;
|
|
|
|
|
|
|
|
|
|
memset(transbuf, data[0], len);
|
|
|
|
|
_dma_send(transbuf, len);
|
|
|
|
|
free(transbuf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send one byte to display
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
void IRAM_ATTR SPI_Write(uint8_t value)
|
|
|
|
|
{
|
|
|
|
|
disp_spi->host->hw->data_buf[0] = (uint32_t)value;
|
|
|
|
|
// Load send buffer
|
|
|
|
|
disp_spi->host->hw->user.usr_mosi_highpart = 0;
|
|
|
|
|
disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 7;
|
|
|
|
|
disp_spi->host->hw->user.usr_mosi = 1;
|
|
|
|
|
disp_spi->host->hw->miso_dlen.usr_miso_dbitlen = 0;
|
|
|
|
|
disp_spi->host->hw->user.usr_miso = 0;
|
|
|
|
|
// Start transfer
|
|
|
|
|
disp_spi->host->hw->cmd.usr = 1;
|
|
|
|
|
// Wait for SPI bus ready
|
|
|
|
|
while (disp_spi->host->hw->cmd.usr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check display busy line and wait while busy
|
|
|
|
|
//-----------------------
|
|
|
|
|
static uint8_t ReadBusy()
|
|
|
|
|
{
|
|
|
|
|
for (int i=0; i<400; i++){
|
|
|
|
|
if (isEPD_BUSY == EPD_BUSY_LEVEL) return 1;
|
|
|
|
|
vTaskDelay(10 / portTICK_RATE_MS);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------
|
|
|
|
|
static uint8_t WaitBusy()
|
|
|
|
|
{
|
|
|
|
|
if (isEPD_BUSY != EPD_BUSY_LEVEL) return 1;
|
|
|
|
|
vTaskDelay(10 / portTICK_RATE_MS);
|
|
|
|
|
if (isEPD_BUSY != EPD_BUSY_LEVEL) return 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write one command without parameters
|
|
|
|
|
//---------------------------------------
|
|
|
|
|
static void EPD_WriteCMD(uint8_t command)
|
|
|
|
|
{
|
|
|
|
|
spi_lobo_device_select(disp_spi, 0);
|
|
|
|
|
EPD_DC_0; // command write
|
|
|
|
|
SPI_Write(command);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write command with one paramet
|
|
|
|
|
//---------------------------------------
|
|
|
|
|
static void EPD_WriteCMD_p1(uint8_t command,uint8_t para)
|
|
|
|
|
{
|
|
|
|
|
spi_lobo_device_select(disp_spi, 0);
|
|
|
|
|
//ReadBusy();
|
|
|
|
|
EPD_DC_0; // command write
|
|
|
|
|
SPI_Write(command);
|
|
|
|
|
EPD_DC_1; // data write
|
|
|
|
|
SPI_Write(para);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//----------------
|
|
|
|
|
void EPD_PowerOn()
|
|
|
|
|
{
|
|
|
|
|
EPD_WriteCMD_p1(0x22,0xc0);
|
|
|
|
|
EPD_WriteCMD(0x20);
|
|
|
|
|
//EPD_WriteCMD(0xff);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n");
|
|
|
|
|
if (!ReadBusy()) printf("[EPD] NOT READY\r\n");
|
|
|
|
|
#else
|
|
|
|
|
WaitBusy();
|
|
|
|
|
ReadBusy();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------
|
|
|
|
|
void EPD_PowerOff()
|
|
|
|
|
{
|
|
|
|
|
EPD_WriteCMD_p1(0x22,0x03);
|
|
|
|
|
EPD_WriteCMD(0x20);
|
|
|
|
|
//EPD_WriteCMD(0xff);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n");
|
|
|
|
|
if (!ReadBusy()) printf("[EPD] NOT READY\r\n");
|
|
|
|
|
#else
|
|
|
|
|
WaitBusy();
|
|
|
|
|
ReadBusy();
|
|
|
|
|
#endif
|
2020-01-29 20:51:49 +00:00
|
|
|
|
#ifdef POWER_Pin
|
2020-01-29 17:30:50 +00:00
|
|
|
|
gpio_set_level(DC_Pin, 0);
|
|
|
|
|
gpio_set_level(MOSI_Pin, 0);
|
|
|
|
|
gpio_set_level(SCK_Pin, 0);
|
|
|
|
|
gpio_set_level(RST_Pin, 0);
|
|
|
|
|
gpio_set_level(CS_Pin, 0);
|
|
|
|
|
gpio_set_level(POWER_Pin, 0);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send command with multiple parameters
|
|
|
|
|
//----------------------------------------------------
|
|
|
|
|
static void EPD_Write(uint8_t *value, uint8_t datalen)
|
|
|
|
|
{
|
|
|
|
|
uint8_t i = 0;
|
|
|
|
|
uint8_t *ptemp;
|
|
|
|
|
|
|
|
|
|
ptemp = value;
|
|
|
|
|
spi_lobo_device_select(disp_spi, 0);
|
|
|
|
|
//ReadBusy();
|
|
|
|
|
EPD_DC_0; // When DC is 0, write command
|
|
|
|
|
SPI_Write(*ptemp); //The first byte is written with the command value
|
|
|
|
|
ptemp++;
|
|
|
|
|
EPD_DC_1; // When DC is 1, write data
|
|
|
|
|
for(i= 0;i<datalen-1;i++){ // sub the data
|
|
|
|
|
SPI_Write(*ptemp);
|
|
|
|
|
ptemp++;
|
|
|
|
|
}
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send data buffer to display
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
static void EPD_WriteDispRam(uint8_t XSize, uint16_t YSize, uint8_t *Dispbuff)
|
|
|
|
|
{
|
|
|
|
|
if (XSize%8 != 0) XSize = XSize+(8-XSize%8);
|
|
|
|
|
XSize = XSize/8;
|
|
|
|
|
|
|
|
|
|
spi_lobo_device_select(disp_spi, 0);
|
|
|
|
|
//ReadBusy();
|
|
|
|
|
EPD_DC_0; //command write
|
|
|
|
|
SPI_Write(0x24);
|
|
|
|
|
EPD_DC_1; //data write
|
|
|
|
|
SPI_send_data(Dispbuff, XSize*YSize, 0);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill the display with value
|
|
|
|
|
//-------------------------------------------------------------------------------
|
|
|
|
|
static void EPD_WriteDispRamMono(uint8_t XSize, uint16_t YSize, uint8_t dispdata)
|
|
|
|
|
{
|
|
|
|
|
if (XSize%8 != 0) XSize = XSize+(8-XSize%8);
|
|
|
|
|
XSize = XSize/8;
|
|
|
|
|
|
|
|
|
|
spi_lobo_device_select(disp_spi, 0);
|
|
|
|
|
//ReadBusy();
|
|
|
|
|
EPD_DC_0; // command write
|
|
|
|
|
SPI_Write(0x24);
|
|
|
|
|
EPD_DC_1; // data write
|
|
|
|
|
SPI_send_data(&dispdata, XSize*YSize, 1);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=== Set RAM X - Address Start / End Position (44h) ===
|
|
|
|
|
Specify the start/end positions of the window address in the X direction by 8 times address unit.
|
|
|
|
|
Data is written to the RAM within the area determined by the addresses specified by XSA [4:0] and XEA [4:0].
|
|
|
|
|
These addresses must be set before the RAM write. It allows on XEA [4:0] ≤ XSA [4:0].
|
|
|
|
|
The settings follow the condition on 00h ≤ XSA [4:0], XEA [4:0] ≤ 1Dh.
|
|
|
|
|
The windows is followed by the control setting of Data Entry Setting (R11h)
|
|
|
|
|
|
|
|
|
|
=== Set RAM Y - Address Start / End Position (45h) ===
|
|
|
|
|
Specify the start/end positions of the window address in the Y direction by an address unit.
|
|
|
|
|
Data is written to the RAM within the area determined by the addresses specified by YSA [8:0] and YEA [8:0].
|
|
|
|
|
These addresses must be set before the RAM write.
|
|
|
|
|
It allows YEA [8:0] ≤ YSA [8:0].
|
|
|
|
|
The settings follow the condition on 00h ≤ YSA [8:0], YEA [8:0] ≤ 13Fh.
|
|
|
|
|
The windows is followed by the control setting of Data Entry Setting (R11h)
|
|
|
|
|
*/
|
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
|
static void EPD_SetRamArea(uint8_t Xstart, uint8_t Xend, uint16_t Ystart, uint16_t Yend)
|
|
|
|
|
{
|
|
|
|
|
uint8_t RamAreaX[3]; // X start and end
|
|
|
|
|
uint8_t RamAreaY[5]; // Y start and end
|
|
|
|
|
RamAreaX[0] = 0x44; // command
|
|
|
|
|
RamAreaX[1] = Xstart;
|
|
|
|
|
RamAreaX[2] = Xend;
|
|
|
|
|
RamAreaY[0] = 0x45; // command
|
|
|
|
|
RamAreaY[1] = Ystart & 0xFF;
|
|
|
|
|
RamAreaY[2] = Ystart >> 8;
|
|
|
|
|
RamAreaY[3] = Yend & 0xFF;
|
|
|
|
|
RamAreaY[4] = Yend >> 8;
|
|
|
|
|
EPD_Write(RamAreaX, sizeof(RamAreaX));
|
|
|
|
|
EPD_Write(RamAreaY, sizeof(RamAreaY));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Set RAM X and Y address counter
|
|
|
|
|
/*
|
|
|
|
|
=== Set RAM Address Counter (4Eh-4Fh) ===
|
|
|
|
|
adrX[4:0]: Make initial settings for the RAM X address in the address counter (AC).
|
|
|
|
|
adrY[8:0]: Make initial settings for the RAM Y address in the address counter (AC).
|
|
|
|
|
After RAM data is written, the address counter is automatically updated according to the settings with AM, ID
|
|
|
|
|
bits and setting for a new RAM address is not required in the address counter.
|
|
|
|
|
Therefore, data is written consecutively without setting an address.
|
|
|
|
|
The address counter is not automatically updated when data is read out from the RAM.
|
|
|
|
|
RAM address setting cannot be made during the standby mode.
|
|
|
|
|
The address setting should be made within the area designated with window addresses which is controlled
|
|
|
|
|
by the Data Entry Setting (R11h) {AM, ID[1:0]} ; RAM Address XStart / XEnd Position (R44h) and RAM Address Ystart /Yend Position (R45h).
|
|
|
|
|
Otherwise undesirable image will be displayed on the Panel.
|
|
|
|
|
*/
|
|
|
|
|
//----------------------------------------------------------
|
|
|
|
|
static void EPD_SetRamPointer(uint8_t addrX, uint16_t addrY)
|
|
|
|
|
{
|
|
|
|
|
uint8_t RamPointerX[2]; // default (0,0)
|
|
|
|
|
uint8_t RamPointerY[3];
|
|
|
|
|
//Set RAM X address counter
|
|
|
|
|
RamPointerX[0] = 0x4e;
|
|
|
|
|
RamPointerX[1] = addrX;
|
|
|
|
|
//Set RAM Y address counter
|
|
|
|
|
RamPointerY[0] = 0x4f;
|
|
|
|
|
RamPointerY[1] = addrY & 0xFF;
|
|
|
|
|
RamPointerY[2] = addrY >> 8;
|
|
|
|
|
|
|
|
|
|
EPD_Write(RamPointerX, sizeof(RamPointerX));
|
|
|
|
|
EPD_Write(RamPointerY, sizeof(RamPointerY));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Set RAM X and Y address Start / End position
|
|
|
|
|
//Set RAM X and Y address counter
|
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
|
|
|
static void part_display(uint8_t RAM_XST, uint8_t RAM_XEND ,uint16_t RAM_YST, uint16_t RAM_YEND)
|
|
|
|
|
{
|
|
|
|
|
EPD_SetRamArea(RAM_XST, RAM_XEND, RAM_YST, RAM_YEND);
|
|
|
|
|
EPD_SetRamPointer (RAM_XST, RAM_YST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Initialize the display
|
|
|
|
|
//--------------------
|
|
|
|
|
static void EPD_Init()
|
|
|
|
|
{
|
2020-01-29 20:51:49 +00:00
|
|
|
|
#ifdef POWER_Pin
|
2020-01-29 17:30:50 +00:00
|
|
|
|
gpio_set_level(POWER_Pin, 1);
|
|
|
|
|
vTaskDelay(100 / portTICK_RATE_MS);
|
|
|
|
|
#else
|
|
|
|
|
vTaskDelay(10 / portTICK_RATE_MS);
|
|
|
|
|
#endif
|
|
|
|
|
// reset
|
|
|
|
|
EPD_RST_0;
|
|
|
|
|
vTaskDelay(10 / portTICK_RATE_MS);
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
#endif
|
|
|
|
|
EPD_RST_1;
|
|
|
|
|
for (int n=0; n<50; n++) {
|
|
|
|
|
vTaskDelay(10 / portTICK_RATE_MS);
|
|
|
|
|
if (isEPD_BUSY == EPD_BUSY_LEVEL) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SPI_Write(0x12); // software reset
|
|
|
|
|
vTaskDelay(10 / portTICK_RATE_MS);
|
|
|
|
|
ReadBusy();
|
|
|
|
|
|
|
|
|
|
// set registers
|
|
|
|
|
EPD_Write(GDOControl, sizeof(GDOControl)); // Panel configuration, Gate selection
|
|
|
|
|
EPD_Write(softstart, sizeof(softstart)); // X decrease, Y decrease
|
|
|
|
|
EPD_Write(VCOMVol, sizeof(VCOMVol)); // VCOM setting
|
|
|
|
|
EPD_Write(DummyLine, sizeof(DummyLine)); // dummy line per gate
|
|
|
|
|
EPD_Write(Gatetime, sizeof(Gatetime)); // Gate time setting
|
|
|
|
|
EPD_Write(Border, sizeof(Border));
|
|
|
|
|
EPD_Write(RamDataEntryMode, sizeof(RamDataEntryMode)); // X increase, Y decrease
|
|
|
|
|
|
|
|
|
|
EPD_SetRamArea(0x00, (xDot-1)/8, yDot-1, 0);
|
|
|
|
|
EPD_SetRamPointer(0x00, yDot-1);
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Init: %u ms\r\n", t1);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------
|
|
|
|
|
static void EPD_UpdateFull(void)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
+ Enable Clock Signal,
|
|
|
|
|
+ Then Enable CP
|
|
|
|
|
- Then Load Temperature value
|
|
|
|
|
- Then Load LUT
|
|
|
|
|
- Then INITIAL DISPLAY
|
|
|
|
|
+ Then PATTERN DISPLAY
|
|
|
|
|
+ Then Disable CP
|
|
|
|
|
+ Then Disable OSC
|
|
|
|
|
*/
|
|
|
|
|
EPD_WriteCMD_p1(0x22,0xC7);
|
|
|
|
|
EPD_WriteCMD(0x20);
|
|
|
|
|
//EPD_WriteCMD(0xff);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n");
|
|
|
|
|
if (!ReadBusy()) printf("[EPD] NOT READY\r\n");
|
|
|
|
|
#else
|
|
|
|
|
WaitBusy();
|
|
|
|
|
ReadBusy();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------
|
|
|
|
|
static void EPD_Update_Part(void)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
- Enable Clock Signal,
|
|
|
|
|
- Then Enable CP
|
|
|
|
|
- Then Load Temperature value
|
|
|
|
|
- Then Load LUT
|
|
|
|
|
- Then INITIAL DISPLAY
|
|
|
|
|
+ Then PATTERN DISPLAY
|
|
|
|
|
- Then Disable CP
|
|
|
|
|
- Then Disable OSC
|
|
|
|
|
*/
|
|
|
|
|
EPD_WriteCMD_p1(0x22,0x04);
|
|
|
|
|
EPD_WriteCMD(0x20);
|
|
|
|
|
//EPD_WriteCMD(0xff);
|
|
|
|
|
spi_lobo_device_deselect(disp_spi);
|
|
|
|
|
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n");
|
|
|
|
|
if (!ReadBusy()) printf("[EPD] NOT READY\r\n");
|
|
|
|
|
#else
|
|
|
|
|
WaitBusy();
|
|
|
|
|
ReadBusy();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
Full screen initialization
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
static void EPD_init_Full(void)
|
|
|
|
|
{
|
|
|
|
|
EPD_Init(); // Reset and set register
|
|
|
|
|
EPD_Write((uint8_t *)LUTDefault_full,sizeof(LUTDefault_full));
|
|
|
|
|
|
|
|
|
|
EPD_PowerOn();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
Part screen initialization
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
static void EPD_init_Part(void)
|
|
|
|
|
{
|
|
|
|
|
EPD_Init(); // display
|
|
|
|
|
EPD_Write((uint8_t *)LUT_part, 31);
|
|
|
|
|
EPD_PowerOn();
|
|
|
|
|
}
|
2020-01-31 21:17:46 +00:00
|
|
|
|
|
|
|
|
|
void EPD_wake()
|
|
|
|
|
{
|
|
|
|
|
EPD_Init();
|
|
|
|
|
EPD_PowerOn();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-29 17:30:50 +00:00
|
|
|
|
/********************************************************************************
|
|
|
|
|
parameter:
|
|
|
|
|
Label :
|
|
|
|
|
=1 Displays the contents of the DisBuffer
|
|
|
|
|
=0 Displays the contents of the first byte in DisBuffer,
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
static void EPD_Dis_Full(uint8_t *DisBuffer,uint8_t type)
|
|
|
|
|
{
|
|
|
|
|
EPD_SetRamPointer(0x00, yDot-1); // set ram pointer
|
|
|
|
|
if (type == 0){
|
|
|
|
|
// Fill screen with white
|
|
|
|
|
EPD_WriteDispRamMono(xDot, yDot, 0xff);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Fill screen from buffer
|
|
|
|
|
EPD_WriteDispRam(xDot, yDot, (uint8_t *)DisBuffer);
|
|
|
|
|
}
|
|
|
|
|
EPD_UpdateFull();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/********************************************************************************
|
|
|
|
|
WARNING: X is smaller screen dimension (0~127) !
|
|
|
|
|
Y is larger screen dimension (0~295) !
|
|
|
|
|
parameter:
|
|
|
|
|
xStart : X direction Start coordinate
|
|
|
|
|
xEnd : X direction end coordinate
|
|
|
|
|
yStart : Y direction Start coordinate
|
|
|
|
|
yEnd : Y direction end coordinate
|
|
|
|
|
DisBuffer : Display content
|
|
|
|
|
type :
|
|
|
|
|
=1 Displays the contents of the DisBuffer
|
|
|
|
|
=0 Displays the contents of the first byte in DisBuffer,
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
static void EPD_Dis_Part(uint8_t xStart, uint8_t xEnd, uint16_t yStart, uint16_t yEnd, uint8_t *DisBuffer, uint8_t type)
|
|
|
|
|
{
|
|
|
|
|
if (type == 0) {
|
|
|
|
|
// Repeated color
|
|
|
|
|
part_display(xStart/8, xEnd/8, yEnd, yStart);
|
|
|
|
|
EPD_WriteDispRamMono(xEnd-xStart+1, yEnd-yStart+1, DisBuffer[0]);
|
|
|
|
|
EPD_Update_Part();
|
|
|
|
|
part_display(xStart/8, xEnd/8, yEnd, yStart);
|
|
|
|
|
EPD_WriteDispRamMono(xEnd-xStart+1, yEnd-yStart+1, DisBuffer[0]);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// From buffer
|
|
|
|
|
part_display(xStart/8, xEnd/8, yEnd, yStart);
|
|
|
|
|
EPD_WriteDispRam(xEnd-xStart+1, yEnd-yStart+1,DisBuffer);
|
|
|
|
|
EPD_Update_Part();
|
|
|
|
|
part_display(xStart/8, xEnd/8, yEnd, yStart);
|
|
|
|
|
EPD_WriteDispRam(xEnd-xStart+1, yEnd-yStart+1,DisBuffer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//======================================================================================================================================
|
|
|
|
|
|
|
|
|
|
// Clear full screen
|
|
|
|
|
//=========================
|
|
|
|
|
void EPD_DisplayClearFull()
|
|
|
|
|
{
|
|
|
|
|
uint8_t m;
|
|
|
|
|
EPD_init_Full();
|
|
|
|
|
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
#endif
|
|
|
|
|
m = 0x00;
|
|
|
|
|
EPD_Dis_Full(&m, 0); //all black
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Clear black: %u ms\r\n", t1);
|
|
|
|
|
t1 = clock();
|
|
|
|
|
#endif
|
|
|
|
|
m = 0xff;
|
|
|
|
|
EPD_Dis_Full(&m, 0); //all white
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Clear white: %u ms\r\n", t1);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Partial clear screen
|
|
|
|
|
//=========================
|
|
|
|
|
void EPD_DisplayClearPart()
|
|
|
|
|
{
|
|
|
|
|
uint8_t m = 0xFF;
|
|
|
|
|
EPD_init_Part();
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white
|
|
|
|
|
m = 0x00;
|
|
|
|
|
EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all black
|
|
|
|
|
m = 0xFF;
|
|
|
|
|
EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Part Clear: %u ms\r\n", t1);
|
|
|
|
|
#else
|
|
|
|
|
EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white
|
|
|
|
|
m = 0x00;
|
|
|
|
|
EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all black
|
|
|
|
|
m = 0xFF;
|
|
|
|
|
EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//==================================
|
|
|
|
|
void EPD_DisplaySetFull(uint8_t val)
|
|
|
|
|
{
|
|
|
|
|
EPD_Write((uint8_t *)LUTDefault_full,sizeof(LUTDefault_full));
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
EPD_Dis_Full(&val, 0);
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Display Set Full: %u ms [%02x]\r\n", t1, val);
|
|
|
|
|
#else
|
|
|
|
|
EPD_Dis_Full(&val, 0);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//======================================================================================
|
|
|
|
|
void EPD_DisplaySetPart(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t val)
|
|
|
|
|
{
|
|
|
|
|
EPD_Write((uint8_t *)LUT_part, 31);
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
EPD_Dis_Part(yStart,yEnd,xStart,xEnd, &val,0);
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Display Set Part: %u ms [%02x]\r\n", t1, val);
|
|
|
|
|
#else
|
|
|
|
|
EPD_Dis_Part(yStart,yEnd,xStart,xEnd, &val,0);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//======================================
|
|
|
|
|
void EPD_DisplayFull(uint8_t *DisBuffer)
|
|
|
|
|
{
|
|
|
|
|
EPD_Write((uint8_t *)LUTDefault_full,sizeof(LUTDefault_full));
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
EPD_Dis_Full((uint8_t *)DisBuffer,1);
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Display Full: %u ms\r\n", t1);
|
|
|
|
|
#else
|
|
|
|
|
EPD_Dis_Full((uint8_t *)DisBuffer,1);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//==========================================================================================
|
|
|
|
|
void EPD_DisplayPart(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t *DisBuffer)
|
|
|
|
|
{
|
|
|
|
|
EPD_Write((uint8_t *)LUT_part, 31);
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
uint32_t t1 = clock();
|
|
|
|
|
EPD_Dis_Part(yStart,yEnd,xStart,xEnd,(uint8_t *)DisBuffer,1);
|
|
|
|
|
t1 = clock() - t1;
|
|
|
|
|
printf("[EPD] Display Part: %u ms [%02x:%02x]\r\n", t1, LUT_gs[1], LUT_gs[21]);
|
|
|
|
|
#else
|
|
|
|
|
EPD_Dis_Part(yStart,yEnd,xStart,xEnd,(uint8_t *)DisBuffer,1);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//============
|
|
|
|
|
void EPD_Cls()
|
|
|
|
|
{
|
|
|
|
|
EPD_DisplaySetPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, 0xFF);
|
|
|
|
|
memset(disp_buffer, 0xFF, _width * (_height/8));
|
|
|
|
|
memset(gs_disp_buffer, 0, _width * _height);
|
|
|
|
|
gs_used_shades = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------
|
|
|
|
|
void EPD_gsUpdate(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t gs)
|
|
|
|
|
{
|
|
|
|
|
uint8_t val, buf_val, new_val;
|
|
|
|
|
int count=0, changed=0;
|
|
|
|
|
int x;
|
|
|
|
|
uint8_t y;
|
|
|
|
|
for (x=xStart; x<=xEnd; x++) {
|
|
|
|
|
for (y=yStart; y<=yEnd; y++) {
|
|
|
|
|
val = gs_drawBuff[(y * (xEnd-xStart+1)) + x];
|
|
|
|
|
if (val > 15) val >>= 4;
|
|
|
|
|
if (val == gs) {
|
|
|
|
|
buf_val = drawBuff[(x * ((yEnd-yStart+1)>>3)) + (y>>3)];
|
|
|
|
|
new_val = buf_val;
|
|
|
|
|
if (gs > 0) new_val &= (0x80 >> (y % 8)) ^ 0xFF;
|
|
|
|
|
else new_val |= (0x80 >> (y % 8));
|
|
|
|
|
if (new_val != buf_val) {
|
|
|
|
|
drawBuff[(x * (_height>>3)) + (y>>3)] = new_val;
|
|
|
|
|
changed++;
|
|
|
|
|
}
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
#if EPD_DEBUG
|
|
|
|
|
printf("[EPD] GS Update %02x, count=%d changed=%d\r\n", gs, count, changed);
|
|
|
|
|
#endif
|
|
|
|
|
uint8_t *_lutPart = LUT_part;
|
|
|
|
|
memset(LUT_gs+1, 0, 30);
|
|
|
|
|
if (gs > 0) {
|
|
|
|
|
if (gs > 0) {
|
|
|
|
|
LUT_gs[1] = 0x18;
|
|
|
|
|
LUT_gs[21] = gs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
LUT_gs[1] = 0x28;
|
|
|
|
|
LUT_gs[2] = 0x00;
|
|
|
|
|
LUT_gs[21] = 15;
|
|
|
|
|
}
|
|
|
|
|
LUT_part = LUT_gs;
|
|
|
|
|
EPD_DisplayPart(xStart, xEnd, yStart, yEnd, drawBuff);
|
|
|
|
|
|
|
|
|
|
LUT_part = _lutPart;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
|
void EPD_Update(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd)
|
|
|
|
|
{
|
|
|
|
|
if (_gs == 0) EPD_DisplayPart(xStart, xEnd, yStart, yEnd, drawBuff);
|
|
|
|
|
else {
|
|
|
|
|
for (int n=0; n<16; n++) {
|
|
|
|
|
if (gs_used_shades & (1<<n)) EPD_gsUpdate(xStart, xEnd, yStart, yEnd, n);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------
|
|
|
|
|
void EPD_UpdateScreen()
|
|
|
|
|
{
|
|
|
|
|
EPD_Update(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------
|
|
|
|
|
void EPD_wait(uint32_t ms)
|
|
|
|
|
{
|
|
|
|
|
if (ms < 100) ms = 100;
|
|
|
|
|
uint32_t n = 0;
|
|
|
|
|
while (n < ms) {
|
|
|
|
|
vTaskDelay(100 / portTICK_RATE_MS);
|
|
|
|
|
n += 100;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|