Просмотр исходного кода

Merge branch 'feature/restore_spi_lcd_example' into 'master'

example/spi_master: bring back lcd example

Closes IDF-3856

See merge request espressif/esp-idf!15112
morris 4 лет назад
Родитель
Сommit
882d6a9d07

+ 6 - 0
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)

+ 8 - 0
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

+ 41 - 0
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.

+ 8 - 0
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)

+ 26 - 0
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

+ 8 - 0
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

+ 149 - 0
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 <string.h>
+
+//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;
+}

+ 30 - 0
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 <stdint.h>
+#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

BIN
examples/peripherals/spi_master/lcd/main/image.jpg


+ 60 - 0
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 <math.h>
+#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<line+linect; y++) {
+        for (int x=0; x<320; x++) {
+            *dest++=get_bgnd_pixel(x+yofs[y]+xcomp[x], y+xofs[x]+ycomp[y]);
+        }
+    }
+}
+
+
+esp_err_t pretty_effect_init(void)
+{
+    return decode_image(&pixels);
+}

+ 38 - 0
examples/peripherals/spi_master/lcd/main/pretty_effect.h

@@ -0,0 +1,38 @@
+/*
+   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 <stdint.h>
+#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

+ 452 - 0
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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);
+}