В предыдущей части урока мы подготовили функции по работе с шиной SPI, а также написали функцию инициализации контроллера дисплея.
Выше функции TFT9341_ini добавим функцию заполнения одним цветом всего экрана полностью
1 2 3 4 5 |
//------------------------------------------------------------------- void TFT9341_FillScreen(spi_device_handle_t spi, uint16_t color) { } //------------------------------------------------------------------- |
Заполнять экран мы будем блоками максимального размера, который мы определили при инициализации модуля SPI.
Поэтому объявим указатель на блок памяти и запросим её у кучи
1 2 3 4 |
void TFT9341_FillScreen(spi_device_handle_t spi, uint16_t color) { uint16_t *blck; blck=heap_caps_malloc(TFT9341_WIDTH*16*sizeof(uint16_t), MALLOC_CAP_DMA); |
Поменяем байты в цвете и заполним значениями цвета весь блок
1 2 3 4 5 6 7 8 9 10 |
blck=heap_caps_malloc(TFT9341_WIDTH*16*sizeof(uint16_t), MALLOC_CAP_DMA); //swap bytes; color = color<<8 | color>>8; for(int y=0; y<16; y++) { for(int x=0; x<TFT9341_WIDTH; x++) { blck[y*TFT9341_WIDTH+x] = color; } } |
Выше добавим функцию отправки данных всего блока в шину
1 2 3 4 5 |
//------------------------------------------------------------------- static void send_hor_blocks(spi_device_handle_t spi, int ypos, uint16_t *data) { } //------------------------------------------------------------------- |
В данной функции объявим переменную для кода ошибки, ещё одну целочисленную переменную и массив переменных типа структуры транзакии
1 2 3 4 5 |
static void send_hor_blocks(spi_device_handle_t spi, int ypos, uint16_t *data) { esp_err_t ret; int x; static spi_transaction_t trans[6]; |
Произведём начальную инициализацию полей транзакций
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static spi_transaction_t trans[6]; 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; } |
Здесь также мы в транзакциях для данных устанавливаем высокий уровень ножке, а в транзакциях для команд — низкий.
Проинициализируем остальные поля
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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]=(TFT9341_WIDTH)>>8; //End Col High trans[1].tx_data[3]=(TFT9341_WIDTH)&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+16)>>8; //end page high trans[3].tx_data[3]=(ypos+16)&0xff; //end page low trans[4].tx_data[0]=0x2C; //memory write trans[5].tx_buffer=data; //finally send the line data trans[5].length=TFT9341_WIDTH*2*8*16; //Data length, in bits trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag |
Мы передаём с помощью специальных команд координаты верхнего левого и нижнего правого углов, затем отправляем команду передачи данных в данную область памяти дисплея и затем сами данные.
Вызовем функцию передачи данных команд в шину в цикле
1 2 3 4 5 |
trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag for (x=0; x<6; x++) { ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY); assert(ret==ESP_OK); } |
Ниже добавим функцию для ожидания окончания передачи данных
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//------------------------------------------------------------------- static void send_block_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. } } //------------------------------------------------------------------- |
Воспользовавшись данными функциями, в функции TFT9341_FillScreen отобразим все полоски на дисплее
1 2 3 4 5 6 7 |
blck[y*TFT9341_WIDTH+x] = color; } } for (int i=0; i<TFT9341_HEIGHT/16; i++) { send_hor_blocks(spi, i*16, blck); send_block_finish(spi); } |
Освободим память, а то её хватит ненадолго
1 2 3 |
send_block_finish(spi); } heap_caps_free(blck); |
В заголовочном файле объявим на данную функцию прототип, а также объявим макросы для различных цветов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
uint16_t TFT9341_HEIGHT; //--------------------------------------------------------------------- #define TFT9341_MADCTL_MY 0x80 #define TFT9341_MADCTL_MX 0x40 #define TFT9341_MADCTL_MV 0x20 #define TFT9341_MADCTL_ML 0x10 #define TFT9341_MADCTL_RGB 0x00 #define TFT9341_MADCTL_BGR 0x08 #define TFT9341_MADCTL_MH 0x04 #define TFT9341_BLACK 0x0000 #define TFT9341_BLUE 0x001F #define TFT9341_RED 0xF800 #define TFT9341_GREEN 0x07E0 #define TFT9341_CYAN 0x07FF #define TFT9341_MAGENTA 0xF81F #define TFT9341_YELLOW 0xFFE0 #define TFT9341_WHITE 0xFFFF //--------------------------------------------------------------------- |
В функции app_main файла main.c окрасим весь экран сначала в чёрный, а потом в красный цвет
1 2 3 4 |
TFT9341_ini(spi, 320, 240); TFT9341_FillScreen(spi, TFT9341_BLACK); vTaskDelay(500 / portTICK_PERIOD_MS); TFT9341_FillScreen(spi, TFT9341_RED); |
Подключим контроллер и анализатор к ПК, соберём код, зальём его в плату и посмотрим результат
Давайте посмотрим в программе логического анализа, как отправляются данные в шину.
Вот так, например, отправляется самая первая команда программной перезагрузки контроллера дисплея
Посмотрим, как идёт процесс заливки цветом экрана
И вот собственно сами данные (чёрный цвет)
И красный цвет
Мы видим, что данные передаются непрерывно. Также и между блоками практически нет пауз
С красным цветом теперь строку можно будет удалить
TFT9341_FillScreen(spi, TFT9341_RED);
Объявим две локальные переменные
1 2 3 |
void app_main(void) { uint16_t i,j; |
В бесконечном цикле напишем небольшой тест с заливкой экрана в случайные цвета
1 2 3 4 5 6 7 8 |
while (1) { for(i=0;i<20;i++) { TFT9341_FillScreen(spi, rand()&0x0000FFFF); vTaskDelay(150 / portTICK_PERIOD_MS); } vTaskDelay(500 / portTICK_PERIOD_MS); TFT9341_FillScreen(spi, TFT9341_BLACK); |
Прошьём контроллер и посмотрим результат
Всё работает.
В файле spi_ili9341.h добавим обмен значений между переменными
1 2 3 4 |
void TFT9341_ini(spi_device_handle_t spi, uint16_t w_size, uint16_t h_size); //--------------------------------------------------------------------- #define swap(a,b) {int16_t t=a;a=b;b=t;} //--------------------------------------------------------------------- |
Перейдём в файл spi_ili9341.c и выше функции TFT9341_ini добавим функцию заливки одним цветом прямоугольной области экрана с заданными координатами
1 2 3 4 5 |
//------------------------------------------------------------------- void TFT9341_FillRect(spi_device_handle_t spi, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { } //------------------------------------------------------------------- |
Выйдем из функции, если значение координат будет за областью экрана
1 2 3 |
void TFT9341_FillRect(spi_device_handle_t spi, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { if((x1 >= TFT9341_WIDTH) || (y1 >= TFT9341_HEIGHT) || (x2 >= TFT9341_WIDTH) || (y2 >= TFT9341_HEIGHT)) return; |
Поменяем координаты местами, если левая будет правее правой, а верхняя — ниже нижней
1 2 3 |
if((x1 >= TFT9341_WIDTH) || (y1 >= TFT9341_HEIGHT) || (x2 >= TFT9341_WIDTH) || (y2 >= TFT9341_HEIGHT)) return; if(x1>x2) swap(x1,x2); if(y1>y2) swap(y1,y2); |
Объявим и проинициализируем переменные, в которых будет ширина и высота прямоугольной области
1 2 |
if(y1>y2) swap(y1,y2); uint16_t xsize = (x2-x1+1), ysize = (y2-y1+1); |
Объявим указатель данных для блока
1 2 |
uint16_t xsize = (x2-x1+1), ysize = (y2-y1+1); uint16_t *blck; |
Также объявим переменную для для высоты блока, так как блоки могут быть меньше, чем вся область, так как область может превышать максимальный объём буфера, который мы объявили при инициализации модуля SPI
1 2 |
uint16_t *blck; uint16_t ysize_block; |
Объявим переменную и проинициализируем её максимальным объёмом блока
1 2 |
uint16_t ysize_block; uint32_t size_max = TFT9341_WIDTH*16; |
Запросим память для блока
1 2 |
uint32_t size_max = TFT9341_WIDTH*16; blck=heap_caps_malloc(size_max*sizeof(uint16_t), MALLOC_CAP_DMA); |
Поменяем местами байты цвета
1 2 3 |
blck=heap_caps_malloc(size_max*sizeof(uint16_t), MALLOC_CAP_DMA); //swap bytes; color = color<<8 | color>>8; |
Объявим и проинициализируем переменную для объёма всей заливаемой области, а также объявим переменную для текущего блока
1 2 3 |
color = color<<8 | color>>8; uint32_t size = xsize*ysize; uint32_t size_block; |
Далее организуем бесконечный цикл, в котором начнём закрашивать блоки, в котором, если размер области больше максимального, то для начала уберём остаток
1 2 3 4 5 6 7 |
uint32_t size_block; while(1){ if(size>size_max) { size_block = size_max - (size_max % xsize); //убираем остаток, чтобы полностью закрасилась линия } } |
Из размера вычтем размер, который мы вычислили, для следующего цикла
1 2 |
size_block = size_max - (size_max % xsize); //убираем остаток, чтобы полностью закрасилась линия size -= size_block; |
Узнаем количество полных линий, которые мы можем закрасить, не превышая максимальный размер блока
1 2 |
size -= size_block; ysize_block = size_max / xsize; |
Заполним блок значениями цвета
1 2 3 4 5 6 7 8 |
ysize_block = size_max / xsize; for(int y=0; y<ysize_block; y++) { for(int x=0; x<xsize; x++) { blck[y*xsize+x] = color; } } |
Чтобы нам писать дальше код, нам нужна будет функция заполнения блока, так как это уже блоки с неполной шириной и с определёнными координатами углов.
Добавим такую функцию выше функции send_block_finish. Начало функции такое же
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//------------------------------------------------------------------- static void send_blocks(spi_device_handle_t spi, int x1, int y1, int x2, int y2, uint16_t *data) { esp_err_t ret; int x; static spi_transaction_t trans[6]; 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; } } //------------------------------------------------------------------- |
Далее будут небольшие отличия в координатах, принцип такой же
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
trans[x].flags=SPI_TRANS_USE_TXDATA; } trans[0].tx_data[0]=0x2A; //Column Address Set trans[1].tx_data[0]=(x1 >> 8) & 0xFF;//Start Col High trans[1].tx_data[1]=x1 & 0xFF; //Start Col Low trans[1].tx_data[2]=(x2 >> 8) & 0xFF;//End Col High trans[1].tx_data[3]=x2 & 0xFF; //End Col Low trans[2].tx_data[0]=0x2B; //Page address set trans[3].tx_data[0]=(y1 >> 8) & 0xFF;//Start page high trans[3].tx_data[1]=y1 & 0xFF; //start page low trans[3].tx_data[2]=(y2 >> 8) & 0xFF;//end page high trans[3].tx_data[3]=y2 & 0xFF; //end page low trans[4].tx_data[0]=0x2C; //memory write trans[5].tx_buffer=data; //finally send the line data trans[5].length=(x2-x1+1)*(y2-y1+1)*2*8;//Data length, in bits trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag for (x=0; x<6; x++) { ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY); assert(ret==ESP_OK); } |
Вернёмся в функцию TFT9341_FillRect и отправим блок в дисплей
1 2 3 4 5 |
blck[y*xsize+x] = color; } } send_blocks(spi, x1, y1, x2, y1 + ysize_block - 1, blck); send_block_finish(spi); |
Сдвинем вертикальную координату на высоту уже отправленного блока
1 2 |
send_block_finish(spi); y1 += ysize_block; |
Теперь добавим блок кода, который будет работать, если размер блока данных не больше максимального, в котором для начала узнаем высоту данного блока
1 2 3 4 5 |
y1 += ysize_block; } else{ ysize_block = size / xsize; } |
Затем аналогично заполним память цветом, отправим блок в дисплей и выйдем из бесконечного цикла
1 2 3 4 5 6 7 8 9 10 11 |
ysize_block = size / xsize; for(int y=0; y<ysize_block; y++) { for(int x=0; x<xsize; x++) { blck[y*xsize+x] = color; } } send_blocks(spi, x1, y1, x2, y1 + ysize_block - 1, blck); send_block_finish(spi); break; |
Не забываем про освобождение памяти
1 2 3 4 |
send_block_finish(spi); } } heap_caps_free(blck); |
Объявим прототип на данную функцию в заголовочном файле и в функции app_main фала main.c в бесконечном цикле добавим ещё один тест
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
TFT9341_FillScreen(spi, TFT9341_BLACK); for(i=0;i<20;i++) { TFT9341_FillRect(spi, 0, 0, TFT9341_WIDTH/2-1, TFT9341_HEIGHT/2-1, rand()&0x0000FFFF); vTaskDelay(100 / portTICK_PERIOD_MS); TFT9341_FillRect(spi, TFT9341_WIDTH/2, 0, TFT9341_WIDTH-1, TFT9341_HEIGHT/2-1, rand()&0x0000FFFF); vTaskDelay(100 / portTICK_PERIOD_MS); TFT9341_FillRect(spi, 0, TFT9341_HEIGHT/2, TFT9341_WIDTH/2-1, TFT9341_HEIGHT-1, rand()&0x0000FFFF); vTaskDelay(100 / portTICK_PERIOD_MS); TFT9341_FillRect(spi, TFT9341_WIDTH/2, TFT9341_HEIGHT/2, TFT9341_WIDTH-1, TFT9341_HEIGHT-1, rand()&0x0000FFFF); vTaskDelay(100 / portTICK_PERIOD_MS); } vTaskDelay(500 / portTICK_PERIOD_MS); TFT9341_FillScreen(spi, TFT9341_BLACK); |
После заливки кода мы должны получить вот такую картину
Добавим ещё один тест по заливке экрана прямоугольниками случайного цвета, но уже и со случайными координатами
1 2 3 4 5 6 7 8 9 10 11 12 |
TFT9341_FillScreen(spi, TFT9341_BLACK); for(i=0;i<300;i++) { TFT9341_FillRect(spi, rand()%TFT9341_WIDTH, rand()%TFT9341_HEIGHT, rand()%TFT9341_WIDTH, rand()%TFT9341_HEIGHT, rand()&0x0000FFFF); vTaskDelay(10 / portTICK_PERIOD_MS);; } vTaskDelay(500 / portTICK_PERIOD_MS); TFT9341_FillScreen(spi, TFT9341_BLACK); |
Проверим, как оно работает
Всё работает.
Вернёмся в файл spi_ili9341.c и и выше функции TFT9341_ini добавим функцию отправки буфера в шину
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//------------------------------------------------------------------- static void TFT9341_WriteData(spi_device_handle_t spi, uint8_t* buff, size_t buff_size) { esp_err_t ret; spi_transaction_t t; while(buff_size > 0) { uint16_t chunk_size = buff_size > 32768 ? 32768 : buff_size; if (chunk_size==0) return; //no need to send anything memset(&t, 0, sizeof(t)); //Zero out the transaction t.length=chunk_size*8; //Len is in bytes, transaction length is in bits. t.tx_buffer=buff; //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. buff += chunk_size; buff_size -= chunk_size; } } //------------------------------------------------------------------- |
Ниже добавим функцию установки адреса углов области заливки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//------------------------------------------------------------------- static void TFT9341_SetAddrWindow(spi_device_handle_t spi, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { // column address set lcd_cmd(spi, 0x2A); // CASET { uint8_t data[] = { (x0 >> 8) & 0xFF, x0 & 0xFF, (x1 >> 8) & 0xFF, x1 & 0xFF }; TFT9341_WriteData(spi, data, sizeof(data)); } // row address set lcd_cmd(spi, 0x2B); // RASET { uint8_t data[] = { (y0 >> 8) & 0xFF, y0 & 0xFF, (y1 >> 8) & 0xFF, y1 & 0xFF }; TFT9341_WriteData(spi, data, sizeof(data)); } // write to RAM lcd_cmd(spi, 0x2C); // RAMWR } //------------------------------------------------------------------- |
Ниже добавим вывода точки на экран
1 2 3 4 5 6 7 8 9 10 11 12 |
//------------------------------------------------------------------- void TFT9341_DrawPixel(spi_device_handle_t spi, int x, int y, uint16_t color) { uint8_t data[2]; if((x<0)||(y<0)||(x>=TFT9341_WIDTH)||(y>=TFT9341_HEIGHT)) return; data[0] = color>>8; data[1] = color & 0xFF; TFT9341_SetAddrWindow(spi, x,y,x,y); lcd_cmd(spi, 0x2C); lcd_data(spi, data, 2); } //------------------------------------------------------------------ |
Здесь обычный вывод прямоугольника единичной ширины и высоты.
Объявим прототип на данную функцию в заголовочном файле и добавим в бесконечном цикле в функции app_main файла main.c ещё один тест по выводу точек в случайные места экрана
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
TFT9341_FillScreen(spi, TFT9341_BLACK); for(i=0;i<500;i++) { for(j=0;j<100;j++) { TFT9341_DrawPixel(spi, rand()%TFT9341_WIDTH, rand()%TFT9341_HEIGHT, TFT9341_BLACK); } TFT9341_DrawPixel(spi, rand()%TFT9341_WIDTH, rand()%TFT9341_HEIGHT, rand()&0x0000FFFF); usleep(50); } vTaskDelay(500 / portTICK_PERIOD_MS); TFT9341_FillScreen(spi, TFT9341_BLACK); |
Проверим работу теста
В следующей части урока мы продолжим писать наши тесты для проверки работы дисплея, также научимся выводить на его экран текстовую информацию.
Предыдущая часть Программирование МК ESP32 Следующая часть
Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32
Дисплей 2,8 дюймов 240×320 SPI TFT LCD
Логический анализатор 16 каналов можно приобрести здесь
Многофункциональный переходник CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добрый день, спасибо за урок, что-то не нашел исходный код к уроку?
Здравствуйте. Если урок состоит из нескольких частей, то в последней части урока.