Merge branch 'bugfix/spi_master_example_gfx' into 'master'

Spi_master: spiffier graphics

See merge request idf/esp-idf!2030
This commit is contained in:
Jeroen Domburg 2018-04-10 12:08:01 +08:00
commit e23cfb4038
8 changed files with 303 additions and 28 deletions

View file

@ -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

View file

@ -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

View file

@ -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 <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
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<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=jd_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=jd_decomp(&decoder, outfunc, 0);
if (r!=JDR_OK) {
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;
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
#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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -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 <math.h>
#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<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()
{
return decode_image(&pixels);
}

View file

@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#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();

View file

@ -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
@ -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={
#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);
}