diff --git a/examples/peripherals/spi_master/lcd/CMakeLists.txt b/examples/peripherals/spi_master/lcd/CMakeLists.txt new file mode 100644 index 0000000000..6494ac1fda --- /dev/null +++ b/examples/peripherals/spi_master/lcd/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(spi_master) diff --git a/examples/peripherals/spi_master/lcd/Makefile b/examples/peripherals/spi_master/lcd/Makefile new file mode 100644 index 0000000000..bb969cd5d3 --- /dev/null +++ b/examples/peripherals/spi_master/lcd/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := spi_master + +include $(IDF_PATH)/make/project.mk diff --git a/examples/peripherals/spi_master/lcd/README.md b/examples/peripherals/spi_master/lcd/README.md new file mode 100644 index 0000000000..e848e1938c --- /dev/null +++ b/examples/peripherals/spi_master/lcd/README.md @@ -0,0 +1,41 @@ +# SPI Host Driver Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example aims to show how to use SPI Host driver API, like `spi_transaction_t` and spi_device_queue. + +If you are looking for code to drive LCDs in general, rather than code that uses the SPI master, that may be a better example to look at as it uses ESP-IDFs built-in LCD support rather than doing all the low-level work itself, which can be found at `examples/peripherals/lcd/tjpgd/` + +## How to Use Example + +### Hardware Required + +* An ESP development board, with SPI LCD + +Connection : + +Depends on boards. Refer to `spi_master_example_main.c` No wiring is required on ESP-WROVER-KIT + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +On ESP-WROVER-KIT there will be: + +``` +LCD ID: 00000000 +ILI9341 detected. +LCD ILI9341 initialization. +``` + +At the meantime `ESP32` will be displayed on the connected LCD screen. + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/spi_master/lcd/main/CMakeLists.txt b/examples/peripherals/spi_master/lcd/main/CMakeLists.txt new file mode 100644 index 0000000000..0d34bee60e --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(srcs "pretty_effect.c" + "spi_master_example_main.c" + "decode_image.c" + ) + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS "." + EMBED_FILES image.jpg) diff --git a/examples/peripherals/spi_master/lcd/main/Kconfig.projbuild b/examples/peripherals/spi_master/lcd/main/Kconfig.projbuild new file mode 100644 index 0000000000..bab78fc6bf --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/Kconfig.projbuild @@ -0,0 +1,26 @@ +menu "Example Configuration" + + choice LCD_TYPE + prompt "LCD module type" + default LCD_TYPE_AUTO + help + The type of LCD on the evaluation board. + + config LCD_TYPE_AUTO + bool "Auto detect" + config LCD_TYPE_ST7789V + bool "ST7789V (WROVER Kit v2 or v3)" + config LCD_TYPE_ILI9341 + bool "ILI9341 (WROVER Kit v1 or DevKitJ v1)" + endchoice + + config LCD_OVERCLOCK + bool + prompt "Run LCD at higher clock speed than allowed" + default "n" + help + The ILI9341 and ST7789 specify that the maximum clock speed for the SPI interface is 10MHz. However, + in practice the driver chips work fine with a higher clock rate, and using that gives a better framerate. + Select this to try using the out-of-spec clock rate. + +endmenu diff --git a/examples/peripherals/spi_master/lcd/main/component.mk b/examples/peripherals/spi_master/lcd/main/component.mk new file mode 100644 index 0000000000..4ffe9e8ca2 --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/component.mk @@ -0,0 +1,8 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + + +#Compile image file into the resulting firmware binary +COMPONENT_EMBED_FILES := image.jpg diff --git a/examples/peripherals/spi_master/lcd/main/decode_image.c b/examples/peripherals/spi_master/lcd/main/decode_image.c new file mode 100644 index 0000000000..a9a460bc39 --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/decode_image.c @@ -0,0 +1,149 @@ +/* SPI Master example: jpeg decoder. + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* +The image used for the effect on the LCD in the SPI master example is stored in flash +as a jpeg file. This file contains the decode_image routine, which uses the tiny JPEG +decoder library to decode this JPEG into a format that can be sent to the display. + +Keep in mind that the decoder library cannot handle progressive files (will give +``Image decoder: jd_prepare failed (8)`` as an error) so make sure to save in the correct +format if you want to use a different image file. +*/ + +#include "decode_image.h" +#include "esp_rom_tjpgd.h" +#include "esp_log.h" +#include + +//Reference the binary-included jpeg file +extern const uint8_t image_jpg_start[] asm("_binary_image_jpg_start"); +extern const uint8_t image_jpg_end[] asm("_binary_image_jpg_end"); +//Define the height and width of the jpeg file. Make sure this matches the actual jpeg +//dimensions. +#define IMAGE_W 336 +#define IMAGE_H 256 + +const char *TAG = "ImageDec"; + +//Data that is passed from the decoder function to the infunc/outfunc functions. +typedef struct { + const unsigned char *inData; //Pointer to jpeg data + uint16_t inPos; //Current position in jpeg data + uint16_t **outData; //Array of IMAGE_H pointers to arrays of IMAGE_W 16-bit pixel values + int outW; //Width of the resulting file + int outH; //Height of the resulting file +} JpegDev; + +//Input function for jpeg decoder. Just returns bytes from the inData field of the JpegDev structure. +static uint32_t infunc(esp_rom_tjpgd_dec_t *decoder, uint8_t *buf, uint32_t len) +{ + //Read bytes from input file + JpegDev *jd = (JpegDev *)decoder->device; + if (buf != NULL) { + memcpy(buf, jd->inData + jd->inPos, len); + } + jd->inPos += len; + return len; +} + +//Output function. Re-encodes the RGB888 data from the decoder as big-endian RGB565 and +//stores it in the outData array of the JpegDev structure. +static uint32_t outfunc(esp_rom_tjpgd_dec_t *decoder, void *bitmap, esp_rom_tjpgd_rect_t *rect) +{ + JpegDev *jd = (JpegDev *)decoder->device; + uint8_t *in = (uint8_t *)bitmap; + for (int y = rect->top; y <= rect->bottom; y++) { + for (int x = rect->left; x <= rect->right; x++) { + //We need to convert the 3 bytes in `in` to a rgb565 value. + uint16_t v = 0; + v |= ((in[0] >> 3) << 11); + v |= ((in[1] >> 2) << 5); + v |= ((in[2] >> 3) << 0); + //The LCD wants the 16-bit value in big-endian, so swap bytes + v = (v >> 8) | (v << 8); + jd->outData[y][x] = v; + in += 3; + } + } + return 1; +} + +//Size of the work space for the jpeg decoder. +#define WORKSZ 3100 + +//Decode the embedded image into pixel lines that can be used with the rest of the logic. +esp_err_t decode_image(uint16_t ***pixels) +{ + char *work = NULL; + int r; + esp_rom_tjpgd_dec_t decoder; + JpegDev jd; + *pixels = 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. + *pixels = calloc(IMAGE_H, sizeof(uint16_t *)); + if (*pixels == NULL) { + ESP_LOGE(TAG, "Error allocating memory for lines"); + ret = ESP_ERR_NO_MEM; + goto err; + } + for (int i = 0; i < IMAGE_H; i++) { + (*pixels)[i] = malloc(IMAGE_W * sizeof(uint16_t)); + if ((*pixels)[i] == NULL) { + ESP_LOGE(TAG, "Error allocating memory for line %d", i); + ret = ESP_ERR_NO_MEM; + goto err; + } + } + + //Allocate the work space for the jpeg decoder. + work = calloc(WORKSZ, 1); + if (work == NULL) { + ESP_LOGE(TAG, "Cannot allocate workspace"); + ret = ESP_ERR_NO_MEM; + goto err; + } + + //Populate fields of the JpegDev struct. + jd.inData = image_jpg_start; + jd.inPos = 0; + jd.outData = *pixels; + jd.outW = IMAGE_W; + jd.outH = IMAGE_H; + + //Prepare and decode the jpeg. + r = esp_rom_tjpgd_prepare(&decoder, infunc, work, WORKSZ, (void *)&jd); + if (r != JDR_OK) { + ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", r); + ret = ESP_ERR_NOT_SUPPORTED; + goto err; + } + r = esp_rom_tjpgd_decomp(&decoder, outfunc, 0); + if (r != JDR_OK && r != JDR_FMT1) { + ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", r); + ret = ESP_ERR_NOT_SUPPORTED; + goto err; + } + + //All done! Free the work area (as we don't need it anymore) and return victoriously. + free(work); + return ret; +err: + //Something went wrong! Exit cleanly, de-allocating everything we allocated. + if (*pixels != NULL) { + for (int i = 0; i < IMAGE_H; i++) { + free((*pixels)[i]); + } + free(*pixels); + } + free(work); + return ret; +} diff --git a/examples/peripherals/spi_master/lcd/main/decode_image.h b/examples/peripherals/spi_master/lcd/main/decode_image.h new file mode 100644 index 0000000000..1e31e3d1cd --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/decode_image.h @@ -0,0 +1,30 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Decode the jpeg ``image.jpg`` embedded into the program file into pixel data. + * + * @param pixels A pointer to a pointer for an array of rows, which themselves are an array of pixels. + * Effectively, you can get the pixel data by doing ``decode_image(&myPixels); pixelval=myPixels[ypos][xpos];`` + * @return - ESP_ERR_NOT_SUPPORTED if image is malformed or a progressive jpeg file + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on succesful decode + */ +esp_err_t decode_image(uint16_t ***pixels); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/spi_master/lcd/main/image.jpg b/examples/peripherals/spi_master/lcd/main/image.jpg new file mode 100644 index 0000000000..803ca2cdca Binary files /dev/null and b/examples/peripherals/spi_master/lcd/main/image.jpg differ diff --git a/examples/peripherals/spi_master/lcd/main/pretty_effect.c b/examples/peripherals/spi_master/lcd/main/pretty_effect.c new file mode 100644 index 0000000000..2923b7cf6f --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/pretty_effect.c @@ -0,0 +1,60 @@ +/* + This code generates an effect that should pass the 'fancy graphics' qualification + as set in the comment in the spi_master code. + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include "pretty_effect.h" +#include "sdkconfig.h" +#include "decode_image.h" + +uint16_t **pixels; + +//Grab a rgb16 pixel from the esp32_tiles image +static inline uint16_t get_bgnd_pixel(int x, int y) +{ + //Image has an 8x8 pixel margin, so we can also resolve e.g. [-3, 243] + x+=8; + y+=8; + return pixels[y][x]; +} +//This variable is used to detect the next frame. +static int prev_frame=-1; + +//Instead of calculating the offsets for each pixel we grab, we pre-calculate the valueswhenever a frame changes, then re-use +//these as we go through all the pixels in the frame. This is much, much faster. +static int8_t xofs[320], yofs[240]; +static int8_t xcomp[320], ycomp[240]; + +//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 pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect) +{ + if (frame!=prev_frame) { + //We need to calculate a new set of offset coefficients. Take some random sines as offsets to make everything + //look pretty and fluid-y. + for (int x=0; x<320; x++) xofs[x]=sin(frame*0.15+x*0.06)*4; + for (int y=0; y<240; y++) yofs[y]=sin(frame*0.1+y*0.05)*4; + for (int x=0; x<320; x++) xcomp[x]=sin(frame*0.11+x*0.12)*4; + for (int y=0; y<240; y++) ycomp[y]=sin(frame*0.07+y*0.15)*4; + prev_frame=frame; + } + for (int y=line; y +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Calculate the effect for a bunch of lines. + * + * @param dest Destination for the pixels. Assumed to be LINECT * 320 16-bit pixel values. + * @param line Starting line of the chunk of lines. + * @param frame Current frame, used for animation + * @param linect Amount of lines to calculate + */ +void pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect); + + +/** + * @brief Initialize the effect + * + * @return ESP_OK on success, an error from the jpeg decoder otherwise. + */ +esp_err_t pretty_effect_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c b/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c new file mode 100644 index 0000000000..f25d0787f8 --- /dev/null +++ b/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c @@ -0,0 +1,452 @@ +/* SPI Master example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "driver/spi_master.h" +#include "driver/gpio.h" + +#include "pretty_effect.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. +*/ + +#ifdef CONFIG_IDF_TARGET_ESP32 +#define LCD_HOST HSPI_HOST + +#define PIN_NUM_MISO 25 +#define PIN_NUM_MOSI 23 +#define PIN_NUM_CLK 19 +#define PIN_NUM_CS 22 + +#define PIN_NUM_DC 21 +#define PIN_NUM_RST 18 +#define PIN_NUM_BCKL 5 +#elif defined CONFIG_IDF_TARGET_ESP32S2 +#define LCD_HOST SPI2_HOST + +#define PIN_NUM_MISO 37 +#define PIN_NUM_MOSI 35 +#define PIN_NUM_CLK 36 +#define PIN_NUM_CS 34 + +#define PIN_NUM_DC 4 +#define PIN_NUM_RST 5 +#define PIN_NUM_BCKL 6 +#elif defined CONFIG_IDF_TARGET_ESP32C3 +#define LCD_HOST SPI2_HOST + +#define PIN_NUM_MISO 2 +#define PIN_NUM_MOSI 7 +#define PIN_NUM_CLK 6 +#define PIN_NUM_CS 10 + +#define PIN_NUM_DC 9 +#define PIN_NUM_RST 4 +#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. +*/ +typedef struct { + uint8_t cmd; + uint8_t data[16]; + uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds. +} lcd_init_cmd_t; + +typedef enum { + LCD_TYPE_ILI = 1, + LCD_TYPE_ST, + LCD_TYPE_MAX, +} type_lcd_t; + +//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[]={ + /* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */ + {0x36, {(1<<5)|(1<<6)}, 1}, + /* Interface Pixel Format, 16bits/pixel for RGB/MCU interface */ + {0x3A, {0x55}, 1}, + /* Porch Setting */ + {0xB2, {0x0c, 0x0c, 0x00, 0x33, 0x33}, 5}, + /* Gate Control, Vgh=13.65V, Vgl=-10.43V */ + {0xB7, {0x45}, 1}, + /* VCOM Setting, VCOM=1.175V */ + {0xBB, {0x2B}, 1}, + /* LCM Control, XOR: BGR, MX, MH */ + {0xC0, {0x2C}, 1}, + /* VDV and VRH Command Enable, enable=1 */ + {0xC2, {0x01, 0xff}, 2}, + /* VRH Set, Vap=4.4+... */ + {0xC3, {0x11}, 1}, + /* VDV Set, VDV=0 */ + {0xC4, {0x20}, 1}, + /* Frame Rate Control, 60Hz, inversion=0 */ + {0xC6, {0x0f}, 1}, + /* Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V */ + {0xD0, {0xA4, 0xA1}, 1}, + /* Positive Voltage Gamma Control */ + {0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14}, + /* Negative Voltage Gamma Control */ + {0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14}, + /* Sleep Out */ + {0x11, {0}, 0x80}, + /* Display On */ + {0x29, {0}, 0x80}, + {0, {0}, 0xff} +}; + +DRAM_ATTR static const lcd_init_cmd_t ili_init_cmds[]={ + /* Power contorl B, power control = 0, DC_ENA = 1 */ + {0xCF, {0x00, 0x83, 0X30}, 3}, + /* Power on sequence control, + * cp1 keeps 1 frame, 1st frame enable + * vcl = 0, ddvdh=3, vgh=1, vgl=2 + * DDVDH_ENH=1 + */ + {0xED, {0x64, 0x03, 0X12, 0X81}, 4}, + /* Driver timing control A, + * non-overlap=default +1 + * EQ=default - 1, CR=default + * pre-charge=default - 1 + */ + {0xE8, {0x85, 0x01, 0x79}, 3}, + /* Power control A, Vcore=1.6V, DDVDH=5.6V */ + {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5}, + /* Pump ratio control, DDVDH=2xVCl */ + {0xF7, {0x20}, 1}, + /* Driver timing control, all=0 unit */ + {0xEA, {0x00, 0x00}, 2}, + /* Power control 1, GVDD=4.75V */ + {0xC0, {0x26}, 1}, + /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */ + {0xC1, {0x11}, 1}, + /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */ + {0xC5, {0x35, 0x3E}, 2}, + /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */ + {0xC7, {0xBE}, 1}, + /* Memory access contorl, MX=MY=0, MV=1, ML=0, BGR=1, MH=0 */ + {0x36, {0x28}, 1}, + /* Pixel format, 16bits/pixel for RGB/MCU interface */ + {0x3A, {0x55}, 1}, + /* Frame rate control, f=fosc, 70Hz fps */ + {0xB1, {0x00, 0x1B}, 2}, + /* Enable 3G, disabled */ + {0xF2, {0x08}, 1}, + /* Gamma set, curve 1 */ + {0x26, {0x01}, 1}, + /* Positive gamma correction */ + {0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15}, + /* Negative gamma correction */ + {0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15}, + /* Column address set, SC=0, EC=0xEF */ + {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4}, + /* Page address set, SP=0, EP=0x013F */ + {0x2B, {0x00, 0x00, 0x01, 0x3f}, 4}, + /* Memory write */ + {0x2C, {0}, 0}, + /* Entry mode set, Low vol detect disabled, normal display */ + {0xB7, {0x07}, 1}, + /* Display function control */ + {0xB6, {0x0A, 0x82, 0x27, 0x00}, 4}, + /* Sleep out */ + {0x11, {0}, 0x80}, + /* Display on */ + {0x29, {0}, 0x80}, + {0, {0}, 0xff}, +}; + +/* 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. + */ +void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd) +{ + 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=&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 + * 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. + */ +void lcd_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 +//set the D/C line to the value indicated in the user field. +void lcd_spi_pre_transfer_callback(spi_transaction_t *t) +{ + int dc=(int)t->user; + gpio_set_level(PIN_NUM_DC, dc); +} + +uint32_t lcd_get_id(spi_device_handle_t spi) +{ + //get_id cmd + lcd_cmd(spi, 0x04); + + 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 lcd_init(spi_device_handle_t spi) +{ + int cmd=0; + const lcd_init_cmd_t* lcd_init_cmds; + + //Initialize non-SPI GPIOs + gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT); + gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT); + gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT); + + //Reset the display + gpio_set_level(PIN_NUM_RST, 0); + vTaskDelay(100 / portTICK_RATE_MS); + gpio_set_level(PIN_NUM_RST, 1); + vTaskDelay(100 / portTICK_RATE_MS); + + //detect LCD type + uint32_t lcd_id = lcd_get_id(spi); + int lcd_detected_type = 0; + int lcd_type; + + printf("LCD ID: %08X\n", lcd_id); + if ( lcd_id == 0 ) { + //zero, ili + lcd_detected_type = LCD_TYPE_ILI; + printf("ILI9341 detected.\n"); + } else { + // none-zero, ST + lcd_detected_type = LCD_TYPE_ST; + printf("ST7789V detected.\n"); + } + +#ifdef CONFIG_LCD_TYPE_AUTO + lcd_type = lcd_detected_type; +#elif defined( CONFIG_LCD_TYPE_ST7789V ) + printf("kconfig: force CONFIG_LCD_TYPE_ST7789V.\n"); + lcd_type = LCD_TYPE_ST; +#elif defined( CONFIG_LCD_TYPE_ILI9341 ) + printf("kconfig: force CONFIG_LCD_TYPE_ILI9341.\n"); + lcd_type = LCD_TYPE_ILI; +#endif + if ( lcd_type == LCD_TYPE_ST ) { + printf("LCD ST7789V initialization.\n"); + lcd_init_cmds = st_init_cmds; + } else { + printf("LCD ILI9341 initialization.\n"); + lcd_init_cmds = ili_init_cmds; + } + + //Send all the commands + while (lcd_init_cmds[cmd].databytes!=0xff) { + lcd_cmd(spi, lcd_init_cmds[cmd].cmd); + lcd_data(spi, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F); + if (lcd_init_cmds[cmd].databytes&0x80) { + vTaskDelay(100 / portTICK_RATE_MS); + } + cmd++; + } + + ///Enable backlight + gpio_set_level(PIN_NUM_BCKL, 0); +} + + +/* 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. + } +} + + +//Simple routine to generate some patterns and send them to the LCD. Don't expect anything too +//impressive. Because the SPI driver handles transactions in the background, we can calculate the next line +//while the previous one is being sent. +static void display_pretty_colors(spi_device_handle_t spi) +{ + uint16_t *lines[2]; + //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); + } + int frame=0; + //Indexes of the line currently being sent to the LCD and the line we're calculating. + int sending_line=-1; + int calc_line=0; + + while(1) { + frame++; + for (int y=0; y<240; y+=PARALLEL_LINES) { + //Calculate a line. + pretty_effect_calc_lines(lines[calc_line], y, frame, 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. + } + } +} + +void app_main(void) +{ + esp_err_t ret; + spi_device_handle_t spi; + 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=lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line + }; + //Initialize the SPI bus + ret=spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO); + 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 + lcd_init(spi); + //Initialize the effect displayed + ret=pretty_effect_init(); + ESP_ERROR_CHECK(ret); + + //Go do nice stuff. + display_pretty_colors(spi); +}