structure libary & code cleanup

This commit is contained in:
Hendrik Schutter 2020-11-03 23:53:19 +01:00
parent 93f477f55b
commit ec5f642e81
8 changed files with 395 additions and 279 deletions

View File

@ -1,10 +1,10 @@
# ESP32-WROVER-KIT-LCD-DRIVER # ESP32-WROVER-KIT-LCD-DRIVER
LCD Driver for ST7789V used ESP32-WROVER-KIT V4.1 LCD Driver for ST7789V/ILI9341 used ESP32-WROVER-KIT V4.1
## WIP ## WIP
Using parts from: Using parts from:
https://github.com/Everlers/demo_esp_lcd_st7789v https://github.com/Everlers/demo_esp_lcd_st7789v
https://github.com/espressif/esp-idf/blob/master/examples/peripherals/spi_master/lcd https://github.com/espressif/esp-idf/blob/master/examples/peripherals/spi_master/lcd

16
main/.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}

View File

@ -1,6 +1,6 @@
/*! @file Driver.c /*! @file Driver.c
@brief @brief write the Framebuffer
@author Hendrik Schutter @author Hendrik Schutter
@version V1.0 @version V1.0
@date 03.11.2020 @date 03.11.2020
@ -10,6 +10,20 @@
#include "Driver.h" #include "Driver.h"
spi_device_handle_t spi; //SPI
uint16_t *lines[2];
int16_t sending_line=-1;
int16_t calc_line=0;
uint32_t vDriver_GetId(void);
static esp_err_t iDriver_sendLineFinish(void);
void vDriver_spiPreTransferCallback(spi_transaction_t *t);
esp_err_t iDriver_sendCmd(const uint8_t u8Cmd);
static esp_err_t iDriver_sendLines(uint16_t u16Ypos, uint16_t *pu16Linedata);
esp_err_t iDriver_SendData(const uint8_t *pu8Data, uint16_t u16Len);
void vDriver_getFramenufferPerLine(uint16_t *pu16Dest, uint16_t u16Line, uint16_t u16Linect, uint16_t ***pu16Framebuffer);
//Place data into DRAM. Constant data gets placed into DROM by default, which is not accessible by DMA. //Place data into DRAM. Constant data gets placed into DROM by default, which is not accessible by DMA.
DRAM_ATTR static const lcd_init_cmd_t st_init_cmds[]= { DRAM_ATTR static const lcd_init_cmd_t st_init_cmds[]= {
/* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */ /* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */
@ -105,75 +119,46 @@ DRAM_ATTR static const lcd_init_cmd_t ili_init_cmds[]= {
{0, {0}, 0xff}, {0, {0}, 0xff},
}; };
/**
* @fn esp_err_t vDriver_init(void)
/* Send a command to the LCD. Uses spi_device_polling_transmit, which waits * @brief Initialize the driver
* until the transfer is complete. * @param void
* * @return esp error code
* Since command transactions are usually small, they are handled in polling * @author Hendrik Schutter
* mode for higher speed. The overhead of interrupt transactions is more than * @date 3.11.2020
* just waiting for the transaction to complete.
*/ */
void vDriver_cmd(spi_device_handle_t spi, const uint8_t cmd) esp_err_t vDriver_init(void)
{ {
esp_err_t ret; esp_err_t ret; //store esp error code
spi_transaction_t t;
memset(&t, 0, sizeof(t)); //Zero out the transaction
t.length=8; //Command is 8 bits
t.tx_buffer=&cmd; //The data is the cmd itself
t.user=(void*)0; //D/C needs to be set to 0
ret=spi_device_polling_transmit(spi, &t); //Transmit!
assert(ret==ESP_OK); //Should have had no issues.
}
/* Send data to the LCD. Uses spi_device_polling_transmit, which waits until the spi_bus_config_t buscfg= {
* transfer is complete. .miso_io_num=PIN_NUM_MISO,
* .mosi_io_num=PIN_NUM_MOSI,
* Since data transactions are usually small, they are handled in polling .sclk_io_num=PIN_NUM_CLK,
* mode for higher speed. The overhead of interrupt transactions is more than .quadwp_io_num=-1,
* just waiting for the transaction to complete. .quadhd_io_num=-1,
*/ .max_transfer_sz=PARALLEL_LINES*320*2+8
void vDriver_data(spi_device_handle_t spi, const uint8_t *data, int len) };
{
esp_err_t ret;
spi_transaction_t t;
if (len==0) return; //no need to send anything
memset(&t, 0, sizeof(t)); //Zero out the transaction
t.length=len*8; //Len is in bytes, transaction length is in bits.
t.tx_buffer=data; //Data
t.user=(void*)1; //D/C needs to be set to 1
ret=spi_device_polling_transmit(spi, &t); //Transmit!
assert(ret==ESP_OK); //Should have had no issues.
}
//This function is called (in irq context!) just before a transmission starts. It will spi_device_interface_config_t devcfg= {
//set the D/C line to the value indicated in the user field. #ifdef CONFIG_LCD_OVERCLOCK
void vDriver_spi_pre_transfer_callback(spi_transaction_t *t) .clock_speed_hz=26*1000*1000, //Clock out at 26 MHz
{ #else
int dc=(int)t->user; .clock_speed_hz=10*1000*1000, //Clock out at 10 MHz
gpio_set_level(PIN_NUM_DC, dc); #endif
} .mode=0, //SPI mode 0
.spics_io_num=PIN_NUM_CS, //CS pin
.queue_size=7, //We want to be able to queue 7 transactions at a time
.pre_cb=vDriver_spiPreTransferCallback, //Specify pre-transfer callback to handle D/C line
};
uint32_t vDriver_get_id(spi_device_handle_t spi) //Initialize the SPI bus
{ ret=spi_bus_initialize(LCD_HOST, &buscfg, DMA_CHAN);
//get_id cmd ESP_ERROR_CHECK(ret);
vDriver_cmd(spi, 0x04); //Attach the LCD to the SPI bus
ret=spi_bus_add_device(LCD_HOST, &devcfg, &spi);
ESP_ERROR_CHECK(ret);
spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length=8*3;
t.flags = SPI_TRANS_USE_RXDATA;
t.user = (void*)1;
esp_err_t ret = spi_device_polling_transmit(spi, &t);
assert( ret == ESP_OK );
return *(uint32_t*)t.rx_data;
}
//Initialize the display
void vDriver_init(spi_device_handle_t spi)
{
int cmd=0; int cmd=0;
const lcd_init_cmd_t* lcd_init_cmds; const lcd_init_cmd_t* lcd_init_cmds;
@ -189,7 +174,7 @@ void vDriver_init(spi_device_handle_t spi)
vTaskDelay(100 / portTICK_RATE_MS); vTaskDelay(100 / portTICK_RATE_MS);
//detect LCD type //detect LCD type
uint32_t lcd_id = vDriver_get_id(spi); uint32_t lcd_id = vDriver_GetId();
int lcd_detected_type = 0; int lcd_detected_type = 0;
int lcd_type; int lcd_type;
@ -223,14 +208,261 @@ void vDriver_init(spi_device_handle_t spi)
//Send all the commands //Send all the commands
while (lcd_init_cmds[cmd].databytes!=0xff) { while (lcd_init_cmds[cmd].databytes!=0xff) {
vDriver_cmd(spi, lcd_init_cmds[cmd].cmd); ret = iDriver_sendCmd(lcd_init_cmds[cmd].cmd);
vDriver_data(spi, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F); assert(ret==ESP_OK);
ret = iDriver_SendData(lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F);
if (lcd_init_cmds[cmd].databytes&0x80) { if (lcd_init_cmds[cmd].databytes&0x80) {
vTaskDelay(100 / portTICK_RATE_MS); vTaskDelay(100 / portTICK_RATE_MS);
} }
cmd++; cmd++;
} }
//Allocate memory for the line buffers
for (int i=0; i<2; i++) {
lines[i]=heap_caps_malloc(320*PARALLEL_LINES*sizeof(uint16_t), MALLOC_CAP_DMA);
assert(lines[i]!=NULL);
}
///Enable backlight ///Enable backlight
gpio_set_level(PIN_NUM_BCKL, 0); gpio_set_level(PIN_NUM_BCKL, 0);
return ret;
} }
/**
* @fn void iDriver_sendCmd(const uint8_t u8Cmd)
* @brief Send a command to the LCD.
* @param const uint8_t u8Cmd
* @return esp error code
* @author Hendrik Schutter
* @date 3.11.2020
*
* Send a command to the LCD. Uses spi_device_polling_transmit, which waits
* until the transfer is complete.
*
* Since command transactions are usually small, they are handled in polling
* mode for higher speed. The overhead of interrupt transactions is more than
* just waiting for the transaction to complete.
*/
esp_err_t iDriver_sendCmd(const uint8_t u8Cmd)
{
esp_err_t ret;
spi_transaction_t t;
memset(&t, 0, sizeof(t)); //Zero out the transaction
t.length=8; //Command is 8 bits
t.tx_buffer=&u8Cmd; //The data is the cmd itself
t.user=(void*)0; //D/C needs to be set to 0
ret=spi_device_polling_transmit(spi, &t); //Transmit!
assert(ret==ESP_OK); //Should have had no issues.
return ret;
}
/**
* @fn esp_err_t iDriver_SendData(const uint8_t *pu8Data, uint16_t u16Len)
* @brief Send data to the LCD.
* @param const uint8_t *pu8Data
* @param uint16_t u16Len
* @return esp error code
* @author Hendrik Schutter
* @date 3.11.2020
*
* Send data to the LCD. Uses spi_device_polling_transmit, which waits until the
* transfer is complete.
*
* Since data transactions are usually small, they are handled in polling
* mode for higher speed. The overhead of interrupt transactions is more than
* just waiting for the transaction to complete.
*/
esp_err_t iDriver_SendData(const uint8_t *pu8Data, uint16_t u16Len)
{
esp_err_t ret;
spi_transaction_t t;
if (u16Len==0) return ESP_FAIL; //no need to send anything
memset(&t, 0, sizeof(t)); //Zero out the transaction
t.length=u16Len*8; //Len is in bytes, transaction length is in bits.
t.tx_buffer=pu8Data; //Data
t.user=(void*)1; //D/C needs to be set to 1
ret=spi_device_polling_transmit(spi, &t); //Transmit!
return ret;
}
/**
* @fn void vDriver_spiPreTransferCallback(spi_transaction_t *t)
* @brief Pre Transfer Callback
* @param spi_transaction_t *t
* @return void
* @author Hendrik Schutter
* @date 3.11.2020
*
* This function is called (in irq context!) just before a transmission starts. It will
* set the D/C line to the value indicated in the user field.
*/
void vDriver_spiPreTransferCallback(spi_transaction_t *t)
{
int dc=(int)t->user;
gpio_set_level(PIN_NUM_DC, dc);
}
/**
* @fn uint32_t vDriver_GetId(void)
* @brief get LCD driver chip id
* @param void
* @return uint32_t
* @author Hendrik Schutter
* @date 3.11.2020
*/
uint32_t vDriver_GetId(void)
{
esp_err_t ret;
//get_id cmd
ret = iDriver_sendCmd(0x04);
assert(ret==ESP_OK);
spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length=8*3;
t.flags = SPI_TRANS_USE_RXDATA;
t.user = (void*)1;
ret = spi_device_polling_transmit(spi, &t);
assert( ret == ESP_OK );
return *(uint32_t*)t.rx_data;
}
/**
* @fn esp_err_t iDriver_writeFramebuffer(uint16_t ***pu16Framebuffer)
* @brief write framebuffer to LCD via SPI
* @param uint16_t ***pu16Framebuffer
* @return esp error code
* @author Hendrik Schutter
* @date 3.11.2020
*/
esp_err_t iDriver_writeFramebuffer(uint16_t ***pu16Framebuffer)
{
esp_err_t ret;
for (int y=0; y<240; y+=PARALLEL_LINES) {
vDriver_getFramenufferPerLine(lines[calc_line], y, PARALLEL_LINES, pu16Framebuffer);
//Finish up the sending process of the previous line, if any
if (sending_line!=-1)
{
ret = iDriver_sendLineFinish();
}
//Swap sending_line and calc_line
sending_line=calc_line;
calc_line=(calc_line==1)?0:1;
//Send the line we currently calculated.
iDriver_sendLines(y, lines[sending_line]);
//The line set is queued up for sending now; the actual sending happens in the
//background. We can go on to calculate the next line set as long as we do not
//touch line[sending_line]; the SPI sending process is still reading from that.
}
return ret;
}
/**
* @fn void vDriver_getFramenufferPerLine(uint16_t *pu16Dest, uint16_t u16Line, uint16_t u16Linect, uint16_t ***pu16Framebuffer)
* @brief Get the pixel data for a set of lines (with implied line size of 320)
* @param uint16_t *pu16Dest
* @param uint16_t u16Line
* @param uint16_t u16Linect
* @param uint16_t ***pu16Framebuffer
* @return void
* @author Hendrik Schutter
* @date 3.11.2020
*/
void vDriver_getFramenufferPerLine(uint16_t *pu16Dest, uint16_t u16Line, uint16_t u16Linect, uint16_t ***pu16Framebuffer)
{
for (uint16_t y=u16Line; y<u16Line+u16Linect; y++) {
for (uint16_t x=0; x<320; x++) {
*pu16Dest++=(*pu16Framebuffer)[y][x];
}
}
}
/**
* @fn static esp_err_t iDriver_sendLines(uint16_t u16Ypos, uint16_t *pu16Linedata)
* @brief send line to lcd chip
* @param uint16_t u16Ypos
* @param uint16_t *pu16Linedata
* @return esp error code
* @author Hendrik Schutter
* @date 3.11.2020
*
* To send a set of lines we have to send a command, 2 data bytes, another command, 2 more data bytes and another command
* before sending the line data itself; a total of 6 transactions. (We can't put all of this in just one transaction
* because the D/C line needs to be toggled in the middle.)
* This routine queues these commands up as interrupt transactions so they get
* sent faster (compared to calling spi_device_transmit several times), and at
* the mean while the lines for next transactions can get calculated.
*/
static esp_err_t iDriver_sendLines(uint16_t u16Ypos, uint16_t *pu16Linedata)
{
esp_err_t ret;
//Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
//function is finished because the SPI driver needs access to it even while we're already calculating the next line.
static spi_transaction_t trans[6];
//In theory, it's better to initialize trans and data only once and hang on to the initialized
//variables. We allocate them on the stack, so we need to re-init them each call.
for (uint8_t x=0; x<6; x++) {
memset(&trans[x], 0, sizeof(spi_transaction_t));
if ((x&1)==0) {
//Even transfers are commands
trans[x].length=8;
trans[x].user=(void*)0;
} else {
//Odd transfers are data
trans[x].length=8*4;
trans[x].user=(void*)1;
}
trans[x].flags=SPI_TRANS_USE_TXDATA;
}
trans[0].tx_data[0]=0x2A; //Column Address Set
trans[1].tx_data[0]=0; //Start Col High
trans[1].tx_data[1]=0; //Start Col Low
trans[1].tx_data[2]=(320)>>8; //End Col High
trans[1].tx_data[3]=(320)&0xff; //End Col Low
trans[2].tx_data[0]=0x2B; //Page address set
trans[3].tx_data[0]=u16Ypos>>8; //Start page high
trans[3].tx_data[1]=u16Ypos&0xff; //start page low
trans[3].tx_data[2]=(u16Ypos+PARALLEL_LINES)>>8; //end page high
trans[3].tx_data[3]=(u16Ypos+PARALLEL_LINES)&0xff; //end page low
trans[4].tx_data[0]=0x2C; //memory write
trans[5].tx_buffer=pu16Linedata; //finally send the line data
trans[5].length=320*2*8*PARALLEL_LINES; //Data length, in bits
trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag
//Queue all transactions.
for (uint8_t x=0; x<6; x++) {
ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
assert(ret==ESP_OK);
}
//When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens
//mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to
//finish because we may as well spend the time calculating the next line. When that is done, we can call
//send_line_finish, which will wait for the transfers to be done and check their status.
return ret;
}
/**
* @fn static esp_err_t iDriver_sendLineFinish(void)
* @brief send finish after line
* @param void
* @return esp error code
* @author Hendrik Schutter
* @date 3.11.2020
*/
static esp_err_t iDriver_sendLineFinish(void)
{
spi_transaction_t *rtrans;
esp_err_t ret;
//Wait for all 6 transactions to be done and get back the results.
for (uint8_t x=0; x<6; x++) {
ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
assert(ret==ESP_OK);
//We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though.
}
return ret;
}

View File

@ -21,7 +21,6 @@
#include "freertos/task.h" #include "freertos/task.h"
#ifdef CONFIG_IDF_TARGET_ESP32
#define LCD_HOST HSPI_HOST #define LCD_HOST HSPI_HOST
#define DMA_CHAN 2 #define DMA_CHAN 2
@ -33,7 +32,10 @@
#define PIN_NUM_DC 21 #define PIN_NUM_DC 21
#define PIN_NUM_RST 18 #define PIN_NUM_RST 18
#define PIN_NUM_BCKL 5 #define PIN_NUM_BCKL 5
#endif
//To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. More means more memory use,
//but less overhead for setting up / finishing transfers. Make sure 240 is dividable by this.
#define PARALLEL_LINES 16
/* /*
The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct. The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct.
@ -51,7 +53,7 @@ typedef enum {
} type_lcd_t; } type_lcd_t;
void vDriver_init(spi_device_handle_t spi); esp_err_t vDriver_init(void);
esp_err_t iDriver_writeFramebuffer(uint16_t ***pu16Framebuffer);
void vDriver_spi_pre_transfer_callback(spi_transaction_t *t);
#endif /* __DRIVER_H */ #endif /* __DRIVER_H */

View File

@ -13,35 +13,38 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "LCD.h" #include "LCD.h"
/**
* @fn void app_main(void)
* @brief main task
* @param void
* @return void
* @author Hendrik Schutter
* @date 3.11.2020
*/
void app_main(void) void app_main(void)
{ {
printf("Hello World!\n"); printf("Hello World!\n");
vLCD_init(); iLCD_init();
while(1) {
while(1){ iLCD_clearFramebuffer(COLOR_RED);
vTaskDelay(1000 / portTICK_RATE_MS);
clear_framebuffer(COLOR_RED);
vTaskDelay(1000 / portTICK_RATE_MS); iLCD_clearFramebuffer(COLOR_GREEN);
vTaskDelay(1000 / portTICK_RATE_MS);
clear_framebuffer(COLOR_GREEN);
vTaskDelay(1000 / portTICK_RATE_MS); iLCD_clearFramebuffer(COLOR_BLUE);
vTaskDelay(1000 / portTICK_RATE_MS);
clear_framebuffer(COLOR_BLUE); iLCD_clearFramebuffer(COLOR_WHITE);
vTaskDelay(1000 / portTICK_RATE_MS); vTaskDelay(1000 / portTICK_RATE_MS);
clear_framebuffer(COLOR_WHITE); iLCD_clearFramebuffer(COLOR_BLACK);
vTaskDelay(1000 / portTICK_RATE_MS); vTaskDelay(1000 / portTICK_RATE_MS);
clear_framebuffer(COLOR_BLACK);
vTaskDelay(1000 / portTICK_RATE_MS);
} }
printf("end\n"); printf("end\n");
} }

View File

@ -1,161 +1,59 @@
/*! @file LCD.c /*! @file LCD.c
@brief @brief sets and writes framebuffer
@author Hendrik Schutter @author Hendrik Schutter
@version V1.0 @version V1.0
@date 03.11.2020 @date 03.11.2020
*/ This code displays graphics on the 320x240 LCD on an ESP-WROVER_KIT board.
#include "LCD.h"
#include "Driver.h"
/*
This code displays some fancy graphics on the 320x240 LCD on an ESP-WROVER_KIT board.
This example demonstrates the use of both spi_device_transmit as well as
spi_device_queue_trans/spi_device_get_trans_result and pre-transmit callbacks.
Some info about the ILI9341/ST7789V: It has an C/D line, which is connected to a GPIO here. It expects this Some info about the ILI9341/ST7789V: It has an C/D line, which is connected to a GPIO here. It expects this
line to be low for a command and high for data. We use a pre-transmit callback here to control that line to be low for a command and high for data. We use a pre-transmit callback here to control that
line: every transaction has as the user-definable argument the needed state of the D/C line and just line: every transaction has as the user-definable argument the needed state of the D/C line and just
before the transaction is sent, the callback will set this line to the correct state. before the transaction is sent, the callback will set this line to the correct state.
*/ */
esp_err_t allocate_frame_buffer(uint16_t ***pPixels); #include "LCD.h"
void write_framebuffer(spi_device_handle_t* spi); #include "Driver.h"
uint16_t **pixels; //framebuffer esp_err_t iLCD_allocateFramebuffer(uint16_t ***pPixels);
spi_device_handle_t spi;
uint16_t *lines[2]; uint16_t **pu16Framebuffer; //framebuffer
//Indexes of the line currently being sent to the LCD and the line we're calculating.
int sending_line=-1;
int calc_line=0;
void vLCD_init(void) {
/**
* @fn esp_err_t iLCD_init(void)
* @brief init
* @param void
* @return int
* @author Hendrik Schutter
* @date 3.11.2020
*/
esp_err_t iLCD_init(void) {
esp_err_t ret; esp_err_t ret;
spi_bus_config_t buscfg= { //Initialize the Driver
.miso_io_num=PIN_NUM_MISO, vDriver_init();
.mosi_io_num=PIN_NUM_MOSI,
.sclk_io_num=PIN_NUM_CLK,
.quadwp_io_num=-1,
.quadhd_io_num=-1,
.max_transfer_sz=PARALLEL_LINES*320*2+8
};
spi_device_interface_config_t devcfg= {
#ifdef CONFIG_LCD_OVERCLOCK
.clock_speed_hz=26*1000*1000, //Clock out at 26 MHz
#else
.clock_speed_hz=10*1000*1000, //Clock out at 10 MHz
#endif
.mode=0, //SPI mode 0
.spics_io_num=PIN_NUM_CS, //CS pin
.queue_size=7, //We want to be able to queue 7 transactions at a time
.pre_cb=vDriver_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line
};
//Initialize the SPI bus
ret=spi_bus_initialize(LCD_HOST, &buscfg, DMA_CHAN);
ESP_ERROR_CHECK(ret);
//Attach the LCD to the SPI bus
ret=spi_bus_add_device(LCD_HOST, &devcfg, &spi);
ESP_ERROR_CHECK(ret);
//Initialize the LCD
vDriver_init(spi);
//Initialize the framebuffer //Initialize the framebuffer
ret=allocate_frame_buffer(&pixels); ret= iLCD_allocateFramebuffer(&pu16Framebuffer);
ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(ret);
//Allocate memory for the pixel buffers
for (int i=0; i<2; i++) {
lines[i]=heap_caps_malloc(320*PARALLEL_LINES*sizeof(uint16_t), MALLOC_CAP_DMA);
assert(lines[i]!=NULL);
}
return ret;
} }
/* To send a set of lines we have to send a command, 2 data bytes, another command, 2 more data bytes and another command /**
* before sending the line data itself; a total of 6 transactions. (We can't put all of this in just one transaction * @fn esp_err_t iLCD_allocateFramebuffer(uint16_t ***pPixels)
* because the D/C line needs to be toggled in the middle.) * @brief allocate framebuffer
* This routine queues these commands up as interrupt transactions so they get * @param pointer of framebuffer two dimensional array
* sent faster (compared to calling spi_device_transmit several times), and at * @return esp error code
* the mean while the lines for next transactions can get calculated. * @author Hendrik Schutter
* @date 3.11.2020
*/ */
static void send_lines(spi_device_handle_t spi, int ypos, uint16_t *linedata) esp_err_t iLCD_allocateFramebuffer(uint16_t ***pPixels)
{
esp_err_t ret;
int x;
//Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
//function is finished because the SPI driver needs access to it even while we're already calculating the next line.
static spi_transaction_t trans[6];
//In theory, it's better to initialize trans and data only once and hang on to the initialized
//variables. We allocate them on the stack, so we need to re-init them each call.
for (x=0; x<6; x++) {
memset(&trans[x], 0, sizeof(spi_transaction_t));
if ((x&1)==0) {
//Even transfers are commands
trans[x].length=8;
trans[x].user=(void*)0;
} else {
//Odd transfers are data
trans[x].length=8*4;
trans[x].user=(void*)1;
}
trans[x].flags=SPI_TRANS_USE_TXDATA;
}
trans[0].tx_data[0]=0x2A; //Column Address Set
trans[1].tx_data[0]=0; //Start Col High
trans[1].tx_data[1]=0; //Start Col Low
trans[1].tx_data[2]=(320)>>8; //End Col High
trans[1].tx_data[3]=(320)&0xff; //End Col Low
trans[2].tx_data[0]=0x2B; //Page address set
trans[3].tx_data[0]=ypos>>8; //Start page high
trans[3].tx_data[1]=ypos&0xff; //start page low
trans[3].tx_data[2]=(ypos+PARALLEL_LINES)>>8; //end page high
trans[3].tx_data[3]=(ypos+PARALLEL_LINES)&0xff; //end page low
trans[4].tx_data[0]=0x2C; //memory write
trans[5].tx_buffer=linedata; //finally send the line data
trans[5].length=320*2*8*PARALLEL_LINES; //Data length, in bits
trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag
//Queue all transactions.
for (x=0; x<6; x++) {
ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
assert(ret==ESP_OK);
}
//When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens
//mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to
//finish because we may as well spend the time calculating the next line. When that is done, we can call
//send_line_finish, which will wait for the transfers to be done and check their status.
}
static void send_line_finish(spi_device_handle_t spi)
{
spi_transaction_t *rtrans;
esp_err_t ret;
//Wait for all 6 transactions to be done and get back the results.
for (int x=0; x<6; x++) {
ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
assert(ret==ESP_OK);
//We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though.
}
}
esp_err_t allocate_frame_buffer(uint16_t ***pPixels)
{ {
*pPixels = NULL; *pPixels = NULL;
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
//Alocate pixel memory. Each line is an array of IMAGE_W 16-bit pixels; the `*pixels` array itself contains pointers to these lines. //Alocate pixel memory. Each line is an array of IMAGE_W 16-bit pixels; the `*pixels` array itself contains pointers to these lines.
*pPixels = calloc(240, sizeof(uint16_t *)); *pPixels = calloc(240, sizeof(uint16_t *));
if (*pPixels == NULL) { if (*pPixels == NULL) {
@ -170,53 +68,24 @@ esp_err_t allocate_frame_buffer(uint16_t ***pPixels)
} }
} }
return ret; return ret;
} //end fun
//Calculate the pixel data for a set of lines (with implied line size of 320). Pixels go in dest, line is the Y-coordinate of the
//first line to be calculated, linect is the amount of lines to calculate. Frame increases by one every time the entire image
//is displayed; this is used to go to the next frame of animation.
void get_framenuffer_per_line(uint16_t *dest, int line, int linect)
{
for (int y=line; y<line+linect; y++) {
for (int x=0; x<320; x++) {
*dest++=pixels[y][x];
}
}
} }
/**
void clear_framebuffer(uint16_t u16Color) { * @fn esp_err_t iLCD_clearFramebuffer(uint16_t u16Color)
* @brief clear complete framebuffer with a color
* @param RGB586 color code
* @return esp error code
* @author Hendrik Schutter
* @date 3.11.2020
*/
esp_err_t iLCD_clearFramebuffer(uint16_t u16Color) {
// uint8_t *in = (uint8_t *)bitmap; // uint8_t *in = (uint8_t *)bitmap;
for (int y = 0; y < 240; y++) { for (int y = 0; y < 240; y++) {
for (int x = 0; x < 320; x++) { for (int x = 0; x < 320; x++) {
//The LCD wants the 16-bit value in big-endian, so swap bytes //The LCD wants the 16-bit value in big-endian, so swap bytes
u16Color = (u16Color >> 8) | (u16Color << 8); u16Color = (u16Color >> 8) | (u16Color << 8);
pixels[y][x] = u16Color; pu16Framebuffer[y][x] = u16Color;
} }
} }
return iDriver_writeFramebuffer(&pu16Framebuffer);
write_framebuffer(&spi);
}
void write_framebuffer(spi_device_handle_t* spi) {
for (int y=0; y<240; y+=PARALLEL_LINES) {
//Calculate a line.
get_framenuffer_per_line(lines[calc_line], y, PARALLEL_LINES);
//Finish up the sending process of the previous line, if any
if (sending_line!=-1)
{
send_line_finish(*spi);
}
//Swap sending_line and calc_line
sending_line=calc_line;
calc_line=(calc_line==1)?0:1;
//Send the line we currently calculated.
send_lines(*spi, y, lines[sending_line]);
//The line set is queued up for sending now; the actual sending happens in the
//background. We can go on to calculate the next line set as long as we do not
//touch line[sending_line]; the SPI sending process is still reading from that.
}
} }

View File

@ -18,22 +18,13 @@
#include <string.h> #include <string.h>
#include "esp_system.h" #include "esp_system.h"
#define COLOR_RED 0xF800 #define COLOR_RED 0xF800
#define COLOR_GREEN 0x07E0 #define COLOR_GREEN 0x07E0
#define COLOR_BLUE 0x001F #define COLOR_BLUE 0x001F
#define COLOR_WHITE 0xFFFF #define COLOR_WHITE 0xFFFF
#define COLOR_BLACK 0x0000 #define COLOR_BLACK 0x0000
esp_err_t iLCD_init(void);
//To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. More means more memory use, esp_err_t iLCD_clearFramebuffer(uint16_t u16Color);
//but less overhead for setting up / finishing transfers. Make sure 240 is dividable by this.
#define PARALLEL_LINES 16
void vLCD_init(void);
void clear_framebuffer(uint16_t u16Color);
#endif /* __LCD_H */ #endif /* __LCD_H */

3
workspace.code-workspace Normal file
View File

@ -0,0 +1,3 @@
{
"folders": []
}