├── README.md ├── rsc ├── ... ├── board-pinout1.jpg ├── board-pinout2.jpg └── lcd-pinout.jpg └── sources ├── ... ├── Esp32 ├── ... └── UART-Load-Bitmap │ ├── CMakeLists.txt │ ├── include │ └── lcd.h │ ├── main │ ├── lcd.c │ └── uart_load_bitmap.c │ ├── sdkconfig │ └── sdkconfig.old └── Win ├── ... └── UartTest ├── UartTest.sln └── UartTest ├── 9460735_Sz9_icon.ico ├── App.config ├── App.xaml ├── App.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── UartTest.csproj ├── bin └── Release │ ├── UartTest.exe │ ├── UartTest.exe.config │ └── UartTest.pdb └── espressiflogofullglow_9Q4_icon.ico /README.md: -------------------------------------------------------------------------------- 1 | ## *Load an image bitmap via COM port and display it on LCD (ILI9341)* 2 | The following example demonstrates how to load any image via UART0 (onboard USB-TTL on esp32) that is usually an onboard USB to TTL connection so it is easier for user to connect Esp device right away. 3 | 4 | ## Setup and Test Guide 5 | 6 | - [Hardware parts](#hardware-parts) 7 | - [LCD 320x240 ILI9341 (SPI)](#lcd-display) 8 | - [ESP32 Board](#esp32-board) 9 | - [Code of windows app and esp firmware](#code-of-windows-app-and-esp-firmware) 10 | - [WPF Application in C#](#wpf-application-c) 11 | - [IDF Project in C/C++](#idf-project) 12 | - [Connect Pins](#connect-pins) 13 | - [Compile code](#compile-code) 14 | - [Run and Test](#run-and-test) 15 | 16 | - [How it works](#how-it-works) 17 | 18 | ## Hardware Parts 19 | 20 | ### LCD Display 21 | 22 | I used a popular TFT 32x240 ILI9341 display with SPI interface 23 | 24 | ### ESP32 Board 25 | 26 | ESP32 module used was brown WROOM board with 4Mb flash but you can use any board 27 | like WROVER which will increase speed of transfer if you use PSI RAM (see notes) 28 | 29 | ## Code of Windows app and ESP firmware 30 | 31 | We essentially need a client-server or client-client communication in this case 32 | The data from PC is sent after selecting an image from very simple picture list that points to your folder with pictures 33 | By clicking on the picture the image is instantly sent to ESP32 34 | 35 | ### WPF Application (C#) 36 | It is a very basic application based on WPF written in C#.NET 37 | The solution uses Visual Studio 2017 but feel welcome to upgrade or modify it so it compiles and works 38 | 39 | ### IDF Project 40 | The project follows code example included with idf framework. It uses CMake. The program awaits for UART event when COM data arrives from PC and sends it to TFT via SPI pins. 41 | 42 | ## Connect Pins 43 | 57 |
44 | 45 | | LCD | PIN | 46 | | --- | --- | 47 | | CS | 15 | 48 | | MOSI | 13 | 49 | | CLK | 14 | 50 | | DC | 21 | 51 | | RST | 22 | 52 | | LED | 5 | 53 | 54 | 55 | 56 |
58 | 59 | ## Compile Code 60 | 61 | Application tosend bitmap is compiled with Visual Studio 2017 as C# solution *(.NET 4.6.1). 62 | Just load the project (.sln) and run debugging or execute binary exe included with the source code. 63 | Esp code requires CMake to compile - *idf.py -p COMX flash* 64 | **Important: Do not run monitor as it blocks the acces to the com port** 65 | 66 | 67 | ## Run and Test 68 | First just connect esp onboard usb to PC and make sure it is recognized 69 | Next run Windoes wpf app called UartTest and select your picture to be lozaded 70 | Note: it may take a second to load a full bitmap because UART is very slow 71 | 115200 bits/s is only 14kb/s so be patient during load) 72 | 73 | ## How it works 74 | In brief - windows app acts as a data server sending bytes instantly after clicking image. 75 | Esp program waits for an *UART_DATA* event and starts loading the bitmap in screen portions because of memory limits. 76 | It is defined as *BLOCK_LINES* which is *DISPLAY_HEIGHT / (n)*. *n* is number of loaded image portions. 77 | On 4Mb boards *n*>=2 however if you use PSRAM board *n* could be set to 1 78 | If *BLOCK_LINES=DISPLAY_HEIGHT* you get maximum performance. 79 | I used 565 bitmap pixel format (*Format16bppRgb565*) which is sent already converted and resized on PC side. 80 | All the logging output is disabled and redirected to win app small port monitoring code. 81 | The monitor is very simple and because of ANSI standard being processed there might be some ascii artifacts. 82 | Feel free to experiment with the code - it may turn out to be usefull :wink: 83 | 84 | Here is a result: 85 | 86 | 87 | -------------------------------------------------------------------------------- /rsc/...: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rsc/board-pinout1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deous/ESP32-UART-Load-Bitmap/ea0f42a211b1aea33a15ff212504fa9fb211bc8c/rsc/board-pinout1.jpg -------------------------------------------------------------------------------- /rsc/board-pinout2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deous/ESP32-UART-Load-Bitmap/ea0f42a211b1aea33a15ff212504fa9fb211bc8c/rsc/board-pinout2.jpg -------------------------------------------------------------------------------- /rsc/lcd-pinout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deous/ESP32-UART-Load-Bitmap/ea0f42a211b1aea33a15ff212504fa9fb211bc8c/rsc/lcd-pinout.jpg -------------------------------------------------------------------------------- /sources/...: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sources/Esp32/...: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sources/Esp32/UART-Load-Bitmap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following four lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(MAIN_SRCS main/uart_load_bitmap.c main/lcd.c) 6 | 7 | include_directories(include/) 8 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 9 | project(uart_load_bitmap) 10 | -------------------------------------------------------------------------------- /sources/Esp32/UART-Load-Bitmap/include/lcd.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __LCD_H_ 3 | #define __LCD_H_ 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include "freertos/FreeRTOS.h" 10 | #include "driver/spi_master.h" 11 | 12 | /* 13 | HSPI VSPI 14 | Pin Name GPIO Number 15 | ------------------------ 16 | CS0 15 5 17 | SCLK 14 18 18 | MISO 12 19 19 | MOSI 13 23 20 | */ 21 | 22 | #define PIN_NUM_MISO 12 23 | #define PIN_NUM_MOSI 13 24 | #define PIN_NUM_CLK 14 25 | #define PIN_NUM_CS 15 26 | 27 | #define PIN_NUM_DC 21 28 | #define PIN_NUM_RST 22 29 | #define PIN_NUM_BCKL 5 30 | 31 | #define DISPLAY_WIDTH 320 32 | #define DISPLAY_HEIGHT 240 33 | 34 | #define BLOCK_LINES (DISPLAY_HEIGHT / 2) 35 | 36 | 37 | typedef struct { 38 | uint8_t cmd; 39 | uint8_t data[16]; 40 | uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds. 41 | } lcd_init_cmd_t; 42 | 43 | 44 | volatile spi_device_handle_t spi; 45 | 46 | 47 | void initialise_lcd(void); 48 | void display_image_block(spi_device_handle_t spi, int ypos, void *image_data); 49 | void wait_data_done(spi_device_handle_t spi) ; 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /sources/Esp32/UART-Load-Bitmap/main/lcd.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/task.h" 7 | #include "esp_system.h" 8 | #include "driver/spi_master.h" 9 | #include "soc/gpio_struct.h" 10 | #include "driver/gpio.h" 11 | #include "lcd.h" 12 | 13 | DRAM_ATTR static const lcd_init_cmd_t ili_init_cmds[]={ 14 | {0xCF, {0x00, 0x83, 0X30}, 3}, 15 | {0xED, {0x64, 0x03, 0X12, 0X81}, 4}, 16 | {0xE8, {0x85, 0x01, 0x79}, 3}, 17 | {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5}, 18 | {0xF7, {0x20}, 1}, 19 | {0xEA, {0x00, 0x00}, 2}, 20 | {0xC0, {0x26}, 1}, 21 | {0xC1, {0x11}, 1}, 22 | {0xC5, {0x35, 0x3E}, 2}, 23 | {0xC7, {0xBE}, 1}, 24 | {0x36, {0x28}, 1}, 25 | {0x3A, {0x55}, 1}, 26 | {0xB1, {0x00, 0x1B}, 2}, 27 | {0xF2, {0x08}, 1}, 28 | {0x26, {0x01}, 1}, 29 | {0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15}, 30 | {0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15}, 31 | {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4}, 32 | {0x2B, {0x00, 0x00, 0x01, 0x3f}, 4}, 33 | {0x2C, {0}, 0}, 34 | {0xB7, {0x07}, 1}, 35 | {0xB6, {0x0A, 0x82, 0x27, 0x00}, 4}, 36 | {0x11, {0}, 0x80}, 37 | {0x29, {0}, 0x80}, 38 | {0, {0}, 0xff}, 39 | }; 40 | 41 | //Send a command to the LCD. Uses spi_device_transmit, which waits until the transfer is complete. 42 | void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd) 43 | { 44 | esp_err_t ret; 45 | spi_transaction_t t; 46 | memset(&t, 0, sizeof(t)); //Zero out the transaction 47 | t.length=8; //Command is 8 bits 48 | t.tx_buffer=&cmd; //The data is the cmd itself 49 | t.user=0L;//(void*)0; //D/C needs to be set to 0 50 | ret=spi_device_transmit(spi, &t); //Transmit! 51 | assert(ret==ESP_OK); //Should have had no issues. 52 | } 53 | 54 | //Send data to the LCD. Uses spi_device_transmit, which waits until the transfer is complete. 55 | void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len) 56 | { 57 | esp_err_t ret; 58 | spi_transaction_t t; 59 | if (len==0) return; //no need to send anything 60 | memset(&t, 0, sizeof(t)); //Zero out the transaction 61 | t.length=len*8; //Len is in bytes, transaction length is in bits. 62 | t.tx_buffer=data; //Data 63 | t.user=(void*)1; //D/C needs to be set to 1 64 | ret=spi_device_transmit(spi, &t); //Transmit! 65 | assert(ret==ESP_OK); //Should have had no issues. 66 | } 67 | 68 | //This function is called (in irq context!) just before a transmission starts. It will 69 | //set the D/C line to the value indicated in the user field. 70 | void lcd_spi_pre_transfer_callback(spi_transaction_t *t) 71 | { 72 | int dc=(int)t->user; 73 | gpio_set_level(PIN_NUM_DC, dc); 74 | } 75 | 76 | uint32_t lcd_get_id(spi_device_handle_t spi) 77 | { 78 | //get_id cmd 79 | lcd_cmd( spi, 0x04); 80 | 81 | spi_transaction_t t; 82 | memset(&t, 0, sizeof(t)); 83 | t.length=8*3; 84 | t.flags = SPI_TRANS_USE_RXDATA; 85 | t.user = (void*)1; 86 | 87 | esp_err_t ret = spi_device_transmit(spi, &t); 88 | assert( ret == ESP_OK ); 89 | return *(uint32_t*)t.rx_data; 90 | } 91 | 92 | // somehow this does not work... 93 | uint32_t lcd_get_scanline(spi_device_handle_t spi) 94 | { 95 | lcd_cmd( spi, 0x45); 96 | 97 | spi_transaction_t t; 98 | memset(&t, 0, sizeof(t)); 99 | t.length=1; 100 | t.flags = SPI_TRANS_USE_RXDATA; 101 | t.user = (void*)1; 102 | esp_err_t ret = spi_device_transmit(spi, &t); 103 | assert( ret == ESP_OK ); 104 | 105 | t.flags = SPI_TRANS_USE_RXDATA; 106 | t.length=10; 107 | ret = spi_device_transmit(spi, &t); 108 | 109 | assert( ret == ESP_OK ); 110 | return *(uint32_t*)t.rx_data; 111 | } 112 | 113 | 114 | //Initialize the display 115 | void lcd_init(spi_device_handle_t spi) 116 | { 117 | int cmd=0; 118 | const lcd_init_cmd_t* lcd_init_cmds; 119 | 120 | //Initialize non-SPI GPIOs 121 | gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT); 122 | gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT); 123 | gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT); 124 | 125 | //Reset the display 126 | gpio_set_level(PIN_NUM_RST, 0); 127 | vTaskDelay(100 / portTICK_RATE_MS); 128 | gpio_set_level(PIN_NUM_RST, 1); 129 | vTaskDelay(100 / portTICK_RATE_MS); 130 | 131 | //detect LCD type 132 | //uint32_t lcd_id = lcd_get_id(spi); 133 | 134 | lcd_init_cmds = ili_init_cmds; 135 | 136 | 137 | //Send all the commands 138 | while (lcd_init_cmds[cmd].databytes!=0xff) { 139 | lcd_cmd(spi, lcd_init_cmds[cmd].cmd); 140 | lcd_data(spi, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F); 141 | if (lcd_init_cmds[cmd].databytes&0x80) { 142 | vTaskDelay(100 / portTICK_RATE_MS); 143 | } 144 | cmd++; 145 | } 146 | 147 | ///Enable backlight 148 | gpio_set_level(PIN_NUM_BCKL, 1); 149 | 150 | } 151 | 152 | void display_image_block(spi_device_handle_t spi, int ypos, void *image_data) 153 | { 154 | esp_err_t ret; 155 | static spi_transaction_t trans[6]; 156 | 157 | for (int x=0; x<6; x++) { 158 | memset(&trans[x], 0, sizeof(spi_transaction_t)); 159 | if ((x&1)==0) { 160 | //Even transfers are commands 161 | trans[x].length=8; 162 | trans[x].user=(void*)0; 163 | } else { 164 | //Odd transfers are data 165 | trans[x].length=8*4; 166 | trans[x].user=(void*)1; 167 | } 168 | trans[x].flags=SPI_TRANS_USE_TXDATA; 169 | } 170 | 171 | trans[0].tx_data[0]=0x2A; //Column Address Set 172 | trans[1].tx_data[0]=0; //Start Col High 173 | trans[1].tx_data[1]=0; //Start Col Low 174 | trans[1].tx_data[2]=(DISPLAY_WIDTH)>>8; //End Col High 175 | trans[1].tx_data[3]=(DISPLAY_WIDTH)&0xff; //End Col Low 176 | trans[2].tx_data[0]=0x2B; //Page address set 177 | trans[3].tx_data[0]=ypos>>8; //Start page high 178 | trans[3].tx_data[1]=ypos&0xff; //start page low 179 | trans[3].tx_data[2]=(ypos+BLOCK_LINES)>>8; //end page high 180 | trans[3].tx_data[3]=(ypos+BLOCK_LINES)&0xff; //end page low 181 | trans[4].tx_data[0]=0x2C; //memory write 182 | trans[5].tx_buffer=image_data; //finally send the line data 183 | trans[5].length=DISPLAY_WIDTH*2*8*BLOCK_LINES; //Data length, in bits 184 | trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag 185 | 186 | //Queue all transactions. 187 | for (int x=0; x<6; x++) { 188 | ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY); 189 | assert(ret==ESP_OK); 190 | } 191 | } 192 | 193 | void wait_data_done(spi_device_handle_t spi) 194 | { 195 | spi_transaction_t *rtrans; 196 | esp_err_t ret=-1; 197 | for (int x=0; x<6; x++) { 198 | ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY); 199 | assert(ret==ESP_OK); 200 | //We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though. 201 | } 202 | uint32_t scl = lcd_get_scanline(spi); 203 | printf("~~scanline: 0x%x\n",scl); 204 | 205 | } 206 | 207 | void initialise_lcd() 208 | { 209 | esp_err_t ret; 210 | spi_bus_config_t buscfg={ 211 | .miso_io_num=PIN_NUM_MISO, 212 | .mosi_io_num=PIN_NUM_MOSI, 213 | .sclk_io_num=PIN_NUM_CLK, 214 | .quadwp_io_num=-1, 215 | .quadhd_io_num=-1, 216 | .max_transfer_sz=BLOCK_LINES*DISPLAY_WIDTH*2+8 217 | }; 218 | spi_device_interface_config_t devcfg={ 219 | .clock_speed_hz=60*1000*1000, //Clock out at 26 MHz 220 | .mode=0, //SPI mode 0 221 | .spics_io_num=PIN_NUM_CS, //CS pin 222 | .queue_size=7, //We want to be able to queue 7 transactions at a time 223 | .pre_cb=lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line 224 | .flags=SPI_DEVICE_NO_DUMMY 225 | }; 226 | //Initialize the SPI bus 227 | ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1); 228 | ESP_ERROR_CHECK(ret); 229 | //Attach the LCD to the SPI bus 230 | ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi); 231 | ESP_ERROR_CHECK(ret); 232 | //Initialize the LCD 233 | lcd_init(spi); 234 | 235 | } 236 | -------------------------------------------------------------------------------- /sources/Esp32/UART-Load-Bitmap/main/uart_load_bitmap.c: -------------------------------------------------------------------------------- 1 | /* UART Load Bitmap Example 2 | Below code loads a butmap image sent from Windows client 3 | after reading bytes in form of TFT 4 | 5 | This example code is in the Public Domain (or CC0 licensed, at your option.) 6 | 7 | Unless required by applicable law or agreed to in writing, this 8 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | CONDITIONS OF ANY KIND, either express or implied. 10 | */ 11 | #include 12 | #include "freertos/FreeRTOS.h" 13 | #include "freertos/task.h" 14 | #include "driver/uart.h" 15 | #include "esp_log.h" 16 | #include "lcd.h" 17 | 18 | // this is an LCD line block size to be displayed by one iteration of spi 19 | #define BUF_SIZE (DISPLAY_WIDTH*2*BLOCK_LINES) 20 | 21 | // we substitute esp log functions with our own 22 | int printf_func(const char* str, va_list vl) 23 | { 24 | return 0; 25 | } 26 | 27 | static QueueHandle_t uart0_queue; 28 | 29 | static void uart_task() 30 | { 31 | uart_event_t event; 32 | int n_received=0, n_total=0; 33 | 34 | uint8_t *data = (uint8_t *)malloc(BUF_SIZE); 35 | 36 | for(;;) 37 | { 38 | if(xQueueReceive(uart0_queue, (void*)&event, portMAX_DELAY)) 39 | { 40 | if(event.type==UART_DATA) 41 | { 42 | for (int y=0; y 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sources/Win/UartTest/UartTest/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sources/Win/UartTest/UartTest/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace UartTest 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sources/Win/UartTest/UartTest/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |