/*! @file LCD.c @brief @author Hendrik Schutter @version V1.0 @date 03.11.2020 */ #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 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 before the transaction is sent, the callback will set this line to the correct state. */ esp_err_t allocate_frame_buffer(uint16_t ***pPixels); void write_framebuffer(spi_device_handle_t* spi); uint16_t **pixels; //framebuffer spi_device_handle_t spi; uint16_t *lines[2]; //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) { esp_err_t ret; spi_bus_config_t buscfg= { .miso_io_num=PIN_NUM_MISO, .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 ret=allocate_frame_buffer(&pixels); 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); } } /* 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 void send_lines(spi_device_handle_t spi, int ypos, uint16_t *linedata) { 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; 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. *pPixels = calloc(240, sizeof(uint16_t *)); if (*pPixels == NULL) { printf("Error allocating memory for lines"); ret = ESP_ERR_NO_MEM; } for (int i = 0; i < 240; i++) { (*pPixels)[i] = malloc(320 * sizeof(uint16_t)); if ((*pPixels)[i] == NULL) { printf("Error allocating memory for line %d", i); ret = ESP_ERR_NO_MEM; } } 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> 8) | (u16Color << 8); pixels[y][x] = u16Color; } } 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. } }