diff --git a/examples/peripherals/spi_master/main/Kconfig.projbuild b/examples/peripherals/spi_master/main/Kconfig.projbuild index 8424d3f315..2a278f4334 100644 --- a/examples/peripherals/spi_master/main/Kconfig.projbuild +++ b/examples/peripherals/spi_master/main/Kconfig.projbuild @@ -14,4 +14,13 @@ 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/main/component.mk b/examples/peripherals/spi_master/main/component.mk index 4d3b30caf3..4ffe9e8ca2 100644 --- a/examples/peripherals/spi_master/main/component.mk +++ b/examples/peripherals/spi_master/main/component.mk @@ -3,3 +3,6 @@ # # (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/main/decode_image.c b/examples/peripherals/spi_master/main/decode_image.c new file mode 100644 index 0000000000..1e3c18c8c9 --- /dev/null +++ b/examples/peripherals/spi_master/main/decode_image.c @@ -0,0 +1,153 @@ +/* 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 in ROM 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 "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 + int 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 UINT infunc(JDEC *decoder, BYTE *buf, UINT 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 UINT outfunc(JDEC *decoder, void *bitmap, JRECT *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; + JDEC 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 +#include "esp_err.h" + +/** + * @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); \ No newline at end of file diff --git a/examples/peripherals/spi_master/main/image.jpg b/examples/peripherals/spi_master/main/image.jpg new file mode 100644 index 0000000000..803ca2cdca Binary files /dev/null and b/examples/peripherals/spi_master/main/image.jpg differ diff --git a/examples/peripherals/spi_master/main/pretty_effect.c b/examples/peripherals/spi_master/main/pretty_effect.c new file mode 100644 index 0000000000..9cd58c0667 --- /dev/null +++ b/examples/peripherals/spi_master/main/pretty_effect.c @@ -0,0 +1,61 @@ +/* + 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 "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" + + +/** + * @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(); diff --git a/examples/peripherals/spi_master/main/spi_master_example_main.c b/examples/peripherals/spi_master/main/spi_master_example_main.c index c6f472d26f..77bfe5fa97 100644 --- a/examples/peripherals/spi_master/main/spi_master_example_main.c +++ b/examples/peripherals/spi_master/main/spi_master_example_main.c @@ -16,14 +16,12 @@ #include "soc/gpio_struct.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. - It is not very fast, even when the SPI transfer itself happens at 8MHz and with DMA, because - the rest of the code is not very optimized. Especially calculating the image line-by-line - is inefficient; it would be quicker to send an entire screenful at once. This example does, however, - demonstrate the use of both spi_device_transmit as well as spi_device_queue_trans/spi_device_get_trans_result - as well as pre-transmit callbacks. + 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 @@ -40,6 +38,9 @@ #define PIN_NUM_RST 18 #define PIN_NUM_BCKL 5 +//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. @@ -182,11 +183,11 @@ void lcd_init(spi_device_handle_t spi) if ( lcd_id == 0 ) { //zero, ili lcd_detected_type = LCD_TYPE_ILI; - printf("ILI9341 detected...\n"); + printf("ILI9341 detected.\n"); } else { // none-zero, ST lcd_detected_type = LCD_TYPE_ST; - printf("ST7789V detected...\n"); + printf("ST7789V detected.\n"); } #ifdef CONFIG_LCD_TYPE_AUTO @@ -197,12 +198,12 @@ void lcd_init(spi_device_handle_t spi) #elif defined( CONFIG_LCD_TYPE_ILI9341 ) printf("kconfig: force CONFIG_LCD_TYPE_ILI9341.\n"); lcd_type = LCD_TYPE_ILI; -#endif +#endif if ( lcd_type == LCD_TYPE_ST ) { printf("LCD ST7789V initialization.\n"); lcd_init_cmds = st_init_cmds; } else { - printf("LCD ILI9341 initialization.\n"); + printf("LCD ILI9341 initialization.\n"); lcd_init_cmds = ili_init_cmds; } @@ -221,11 +222,11 @@ void lcd_init(spi_device_handle_t spi) } -//To send a line we have to send a command, 2 data bytes, another command, 2 more data bytes and another command +//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 so they get sent as quickly as possible. -static void send_line(spi_device_handle_t spi, int ypos, uint16_t *line) +static void send_lines(spi_device_handle_t spi, int ypos, uint16_t *linedata) { esp_err_t ret; int x; @@ -256,11 +257,11 @@ static void send_line(spi_device_handle_t spi, int ypos, uint16_t *line) 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+1)>>8; //end page high - trans[3].tx_data[3]=(ypos+1)&0xff; //end 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=line; //finally send the line data - trans[5].length=320*2*8; //Data length, in bits + 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. @@ -294,28 +295,31 @@ static void send_line_finish(spi_device_handle_t spi) //while the previous one is being sent. static void display_pretty_colors(spi_device_handle_t spi) { - uint16_t line[2][320]; - int x, y, frame=0; + 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 (y=0; y<240; y++) { + for (int y=0; y<240; y+=PARALLEL_LINES) { //Calculate a line. - for (x=0; x<320; x++) { - line[calc_line][x]=((x<<3)^(y<<3)^(frame+x*y)); - } + 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_line(spi, y, line[sending_line]); - //The line is queued up for sending now; the actual sending happens in the - //background. We can go on to calculate the next line as long as we do not + 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. } } @@ -330,10 +334,15 @@ void app_main() .mosi_io_num=PIN_NUM_MOSI, .sclk_io_num=PIN_NUM_CLK, .quadwp_io_num=-1, - .quadhd_io_num=-1 + .quadhd_io_num=-1, + .max_transfer_sz=PARALLEL_LINES*320*2+8 }; spi_device_interface_config_t devcfg={ - .clock_speed_hz=10*1000*1000, //Clock out at 10 MHz +#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 @@ -341,12 +350,16 @@ void app_main() }; //Initialize the SPI bus ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1); - assert(ret==ESP_OK); + ESP_ERROR_CHECK(ret); //Attach the LCD to the SPI bus ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi); - assert(ret==ESP_OK); + 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); }