На данном уроке мы попробуем к контроллеру STM32 подключить дисплей TFT разрешением 240×320.
С подобным дисплеем мы уже работали в далёком уроке 37, только это был модуль, в котором подключение дисплея к контроллеру происходило по 8-битной шине. Тогда мы использовали для этого FSMC контроллера STM32. Это такой удобный контроллер памяти.
А теперь мы попытаемся подключить модуль с подобным дисплеем, который подключается к контроллеру уже по шине SPI. Данный дисплей также выполнен на контроллере ILI9341. Такой дисплей у меня наконец-то появился. Подключим мы его также к плате STM32F4-Discovery, с которой мы когда-то очень долго работали, причём с дисплеем, подключенным по 8-битной шине, мы также работали, подключая его к такой же отладочной плате. Не каждому удалось заиметь такой дисплей, многие по ошибке, а многие и не по ошибке приобрели дисплей, именно подключенный по шине SPI и просили меня сделать такой урок. Конечно же на такие массовые просьбы я просто не мог не откликнуться, мне прислали именно такой дисплей и мы теперь с ним поработаем, начнём с библиотеки HAL, причём сначала без DMA, потом применим DMA для того, чтобы сравнить, что нам даст эта периферия.
Дисплей выглядит следующим образом
У меня дисплей размером диагонали 2,8 дюймов, могут быть другие размеры, главное, чтобы разрешение было 320×240 и контроллер чтобы был такой же.
Верхние 5 ножек относятся к работе с сенсорной панелью (TouchScreen), поэтому мы их использовать не будем, следующая 6 ножка — MISO тоже не будет нами использована, так как нам нет смысла ничего читать из дисплея, мы знаем, что у нас именно ILI9341, да и не совсем корректно контроллер работает с MISO.
Подключим к нашей отладочной плате только нижние следующим образом
Вот соответствие ножек на плате и на дисплее
STM32F4-Discovery |
LCD |
5V | VCC |
GND | GND |
PA4 | CS |
PA2 | RESET |
PA3 | DC |
PA7 | SDI (MOSI) |
PA5 | SCK |
3V |
LED |
Питание мы подаём 5 вольт, так как на плате модуля есть стабилизатор, а подсветку питаем 3-мя вольтами. С шиной SPI мы работаем постоянно, причём на контроллере STM32F1 мы её прошли вдоль и поперёк, применяя различные библиотеки. Думаю, F4 также не станет исключением и мы с ним также будем работать не только с применением библиотеки HAL, но так как с данной библиотекой мы уже работали с шиной SPI на контроллере STM32F4, то, думаю, нам следует пока её и использовать, чтобы не наделать ошибок.
Запустим проектогенератор Cube MX и создадим новый проект, выбрав соответствующий контроллер
Включим кварцевый резонатор
Выберем отладчик
Настроим тактирование в Closk Configuration
Включим SPI1 и настроим пока небольшую скорость, чтобы не думать, что ошибки из-за слишком высокой скорости, потом прибавим
У нас включились 3 основные ножки нашей шины. Включим на выход остальные ножки, которые присутствуют в нашей таблице
Добавим нашим ножкам скорости
Включим RNG для лучшего формирования случайных чисел
Немного перестроим частоты в Clock Configuration, чтобы получить вот тут 48, только так, чтобы основная частота осталась максимальной
Присвоим имя проекту, выберем среду разработки и удвоим размер стека и кучи
Сгенерируем проект и откроем его в Keil.
Настроим программатор на автоперезагрузку, отключим оптимизацию и создадим два файла — spi_ili9341.c и spi_ili9341.h для работы с нашим дисплеем следующего содержания
1 2 3 4 5 6 |
#ifndef __SPI_ILI9341_H #define __SPI_ILI9341_H //------------------------------------------------------------------- #include "stm32f4xx_hal.h" //------------------------------------------------------------------- #endif /* __SPI_ILI9341_H */ |
1 2 |
#include "spi_ili9341.h" //------------------------------------------------------------------- |
Подключим данную библиотеку в файле main.c
1 2 |
/* USER CODE BEGIN Includes */ #include "spi_ili9341.h" |
Перейдём в файл spi_ili9341.c и добавим там функцию инициализации нашего дисплея, объявив в ней пока небольшой локальный массив
1 2 3 4 5 6 |
//------------------------------------------------------------------- void TFT9341_ini(uint16_t w_size, uint16_t h_size) { uint8_t data[15]; } //------------------------------------------------------------------- |
Добавим на данную функцию прототип в заголовочном файле и вызовем её в функции main() файла main.c
1 2 |
/* USER CODE BEGIN 2 */ TFT9341_ini(240, 320); |
В файле spi_ili9341.h и добавим несколько макросов для управления нашими управляющими ножками
1 2 3 4 5 6 7 8 9 |
#include "stm32f4xx_hal.h" //------------------------------------------------------------------- #define RESET_ACTIVE() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET) #define RESET_IDLE() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET) #define CS_ACTIVE() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET) #define CS_IDLE() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET) #define DC_COMMAND() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET) #define DC_DATA() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET) //------------------------------------------------------------------- |
В файле spi_ili9341.c опустим ножку выбора в функции инициализации
1 2 |
uint8_t data[15]; CS_ACTIVE(); |
Выше функции инициализации добавим функцию перезагрузки дисплея
1 2 3 4 5 6 7 8 |
//------------------------------------------------------------------- void TFT9341_reset(void) { RESET_ACTIVE(); HAL_Delay(5); RESET_IDLE(); } //------------------------------------------------------------------- |
Вызовем её в функции инициализации
1 2 |
CS_ACTIVE(); TFT9341_reset(); |
Подключим переменную структуры шины SPI и RNG
1 2 3 4 5 |
#include "spi_ili9341.h" //------------------------------------------------------------------- extern SPI_HandleTypeDef hspi1; extern RNG_HandleTypeDef hrng; //------------------------------------------------------------------- |
Выше функции TFT9341_reset добавим функцию отправки команды в дисплей по шине SPI
1 2 3 4 5 6 7 |
//------------------------------------------------------------------- void TFT9341_SendCommand(uint8_t cmd) { DC_COMMAND(); HAL_SPI_Transmit (&hspi1, &cmd, 1, 5000); } //------------------------------------------------------------------- |
Ниже добавим подобную функцию отправки данных в дисплей
1 2 3 4 5 6 7 |
//------------------------------------------------------------------- void TFT9341_SendData(uint8_t dt) { DC_DATA(); HAL_SPI_Transmit (&hspi1, &dt, 1, 5000); } //------------------------------------------------------------------- |
Теперь ниже добавим функцию отправки данных сразу пакетом в определённом количестве
1 2 3 4 5 6 7 8 9 10 |
static void TFT9341_WriteData(uint8_t* buff, size_t buff_size) { DC_DATA(); while(buff_size > 0) { uint16_t chunk_size = buff_size > 32768 ? 32768 : buff_size; HAL_SPI_Transmit(&hspi1, buff, chunk_size, HAL_MAX_DELAY); buff += chunk_size; buff_size -= chunk_size; } } //------------------------------------------------------------------- |
В функции инициализации дисплея произведём также программную перезагрузку и подождём 1 секунду
1 2 3 4 |
TFT9341_reset(); //Software Reset TFT9341_SendCommand(0x01); HAL_Delay(1000); |
Передадим ряд команд с определёнными параметрами в контроллер дисплея для первоначальной настройки дисплея
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
HAL_Delay(1000); //Power Control A data[0] = 0x39; data[1] = 0x2C; data[2] = 0x00; data[3] = 0x34; data[4] = 0x02; TFT9341_SendCommand(0xCB); TFT9341_WriteData(data, 5); //Power Control B data[0] = 0x00; data[1] = 0xC1; data[2] = 0x30; TFT9341_SendCommand(0xCF); TFT9341_WriteData(data, 3); //Driver timing control A data[0] = 0x85; data[1] = 0x00; data[2] = 0x78; TFT9341_SendCommand(0xE8); TFT9341_WriteData(data, 3); //Driver timing control B data[0] = 0x00; data[1] = 0x00; TFT9341_SendCommand(0xEA); TFT9341_WriteData(data, 2); //Power on Sequence control data[0] = 0x64; data[1] = 0x03; data[2] = 0x12; data[3] = 0x81; TFT9341_SendCommand(0xED); TFT9341_WriteData(data, 4); //Pump ratio control data[0] = 0x20; TFT9341_SendCommand(0xF7); TFT9341_WriteData(data, 1); //Power Control,VRH[5:0] data[0] = 0x10; TFT9341_SendCommand(0xC0); TFT9341_WriteData(data, 1); //Power Control,SAP[2:0];BT[3:0] data[0] = 0x10; TFT9341_SendCommand(0xC1); TFT9341_WriteData(data, 1); //VCOM Control 1 data[0] = 0x3E; data[1] = 0x28; TFT9341_SendCommand(0xC5); TFT9341_WriteData(data, 2); //VCOM Control 2 data[0] = 0x86; TFT9341_SendCommand(0xC7); TFT9341_WriteData(data, 1); //Memory Acsess Control data[0] = 0x48; TFT9341_SendCommand(0x36); TFT9341_WriteData(data, 1); //Pixel Format Set data[0] = 0x55;//16bit TFT9341_SendCommand(0x3A); TFT9341_WriteData(data, 1); //Frame Rratio Control, Standard RGB Color data[0] = 0x00; data[1] = 0x18; TFT9341_SendCommand(0xB1); TFT9341_WriteData(data, 2); //Display Function Control data[0] = 0x08; data[1] = 0x82; data[2] = 0x27;//320 строк TFT9341_SendCommand(0xB6); TFT9341_WriteData(data, 3); //Enable 3G (пока не знаю что это за режим) data[0] = 0x00;//не включаем TFT9341_SendCommand(0xF2); TFT9341_WriteData(data, 1); //Gamma set data[0] = 0x01;//Gamma Curve (G2.2) (Кривая цветовой гаммы) TFT9341_SendCommand(0x26); TFT9341_WriteData(data, 1); //Positive Gamma Correction data[0] = 0x0F; data[1] = 0x31; data[2] = 0x2B; data[3] = 0x0C; data[4] = 0x0E; data[5] = 0x08; data[6] = 0x4E; data[7] = 0xF1; data[8] = 0x37; data[9] = 0x07; data[10] = 0x10; data[11] = 0x03; data[12] = 0x0E; data[13] = 0x09; data[14] = 0x00; TFT9341_SendCommand(0xE0); TFT9341_WriteData(data, 15); //Negative Gamma Correction data[0] = 0x00; data[1] = 0x0E; data[2] = 0x14; data[3] = 0x03; data[4] = 0x11; data[5] = 0x07; data[6] = 0x31; data[7] = 0xC1; data[8] = 0x48; data[9] = 0x08; data[10] = 0x0F; data[11] = 0x0C; data[12] = 0x31; data[13] = 0x36; data[14] = 0x0F; TFT9341_SendCommand(0xE1); TFT9341_WriteData(data, 15); TFT9341_SendCommand(0x11);//Выйдем из спящего режима |
Чтобы было более или менее понятно, какие настройки мы проделали, я добавил перед каждой командой комментарии. Да и практически в настройках ничего не изменилось со времён урока 37.
Подождём 120 милисекунд
1 2 |
TFT9341_SendCommand(0x11);//Выйдем из спящего режима HAL_Delay(120); |
Добавим несколько нужных макросов в файл spi_ili9341.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#define DC_DATA() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET) //------------------------------------------------------------------- #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_ROTATION (TFT9341_MADCTL_MX | TFT9341_MADCTL_BGR) #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 //------------------------------------------------------------------- #define swap(a,b) {int16_t t=a;a=b;b=t;} //------------------------------------------------------------------- |
Вернёмся в функцию TFT9341_ini файла spi_ili9341.c и включим дисплей, настроив нужную ориентацию экрана
1 2 3 4 5 |
HAL_Delay(120); //Display ON data[0] = TFT9341_ROTATION; TFT9341_SendCommand(0x29); TFT9341_WriteData(data, 1); |
Добавим две глобальные переменные для хранения размеров экрана
1 2 3 4 5 |
extern RNG_HandleTypeDef hrng; //------------------------------------------------------------------- uint16_t TFT9341_WIDTH; uint16_t TFT9341_HEIGHT; //------------------------------------------------------------------- |
В функции TFT9341_ini проинициализируем их данными из аргументов функции
1 2 3 |
TFT9341_WriteData(data, 1); TFT9341_WIDTH = w_size; TFT9341_HEIGHT = h_size; |
Выше функции инициализации добавим функцию установки адреса, с которого затем будут записываться данные в память дисплея, а также функцию заливки прямоугольника. Данные функции со времён урока 37 у нас также особых изменений не претерпели
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
//------------------------------------------------------------------- static void TFT9341_SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { // column address set TFT9341_SendCommand(0x2A); // CASET { uint8_t data[] = { (x0 >> 8) & 0xFF, x0 & 0xFF, (x1 >> 8) & 0xFF, x1 & 0xFF }; TFT9341_WriteData(data, sizeof(data)); } // row address set TFT9341_SendCommand(0x2B); // RASET { uint8_t data[] = { (y0 >> 8) & 0xFF, y0 & 0xFF, (y1 >> 8) & 0xFF, y1 & 0xFF }; TFT9341_WriteData(data, sizeof(data)); } // write to RAM TFT9341_SendCommand(0x2C); // RAMWR }//------------------------------------------------------------------- void TFT9341_FillRect(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; if(x1>x2) swap(x1,x2); if(y1>y2) swap(y1,y2); TFT9341_SetAddrWindow(x1, y1, x2, y2); uint8_t data[] = { color >> 8, color & 0xFF }; DC_DATA(); for(uint32_t i = 0; i < (x2-x1+1)*(y2-y1+1); i++) { HAL_SPI_Transmit(&hspi1, data, 2, HAL_MAX_DELAY); } } //------------------------------------------------------------------- |
Ниже добавим функцию заливки всего экрана определённым цветом
1 2 3 4 5 6 |
//------------------------------------------------------------------- void TFT9341_FillScreen(uint16_t color) { TFT9341_FillRect(0, 0, TFT9341_WIDTH-1, TFT9341_HEIGHT-1, color); } //------------------------------------------------------------------- |
Добавим на 2 последние функции прототипы в заголовочный файл
1 2 3 |
void TFT9341_ini(uint16_t w_size, uint16_t h_size); void TFT9341_FillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color); void TFT9341_FillScreen(uint16_t color); |
В функции main() файла main.c зальнём экран каким-нибудь цветом, например, зелёным
1 2 |
TFT9341_ini(240, 320); TFT9341_FillScreen(TFT9341_GREEN); |
Соберём код, прошьём контроллер и посмотрим результат
Всё работает, значит мы правильно настроили наш дисплей.
Теперь мы можем немного прибавить скорость шины SPI в Cube MX и перегенерировать проект
Теперь отправка в дисплей данных будет идти гораздо быстрее.
Давайте теперь попробуем написать какие-нибудь тесты в бесконечном цикле. В принципе, они у нас уже написаны в уроке 37, нам останется только их добавить. Но пойдём по порядку.
Исправим заливку зелёным цветом в чёрный цвет
TFT9341_FillScreen(TFT9341_BLACK);
Добавим небольшую задержку
1 2 |
TFT9341_FillScreen(TFT9341_BLACK); HAL_Delay(500); |
Вернёмся в файл spi_ili9341.c и над функцией инициализации добавим функцию формирования случайного числа с использованием генератора RNG
1 2 3 4 5 6 |
//------------------------------------------------------------------- uint16_t TFT9341_RandColor(void) { return HAL_RNG_GetRandomNumber(&hrng)&0x0000FFFF; } //------------------------------------------------------------------- |
Мы накладываем маску на младшие 16 бит числа, так как мы используем 16-разрядные цвета.
Добавим на данную функцию прототип в заголовочном файле, вернёмся в main.c и добавим пару локальных переменных
1 2 |
/* USER CODE BEGIN 1 */ uint16_t i,j; |
В бесконечном цикле функции main() попробуем позакрашивать экран дисплея различными случайными цветами
1 2 3 4 5 6 7 8 |
/* USER CODE BEGIN 3 */ for(i=0;i<20;i++) { TFT9341_FillScreen(TFT9341_RandColor()); HAL_Delay(150); } HAL_Delay(500); TFT9341_FillScreen(TFT9341_BLACK); |
Соберём код, прошьём контроллер и посмотрим результат
Всё отлично заливается. Скорость, конечно, не такая, как при параллельном подключении, но всё же не так уж и медленнее. Использование DMA, я думаю, нам ещё прибавит скорости.
Подключим наши глобальные переменные размера экрана
1 2 3 |
/* USER CODE BEGIN PV */ extern uint16_t TFT9341_WIDTH; extern uint16_t TFT9341_HEIGHT; |
Закомментируем предыдущий тест и добавим следующий по заливке экрана прямоугольниками случайного цвета в 4 области экрана. Мы будем при добавлении очередных тестов комментировать предыдущий, чтобы не ждать новых
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
*/ for(i=0;i<20;i++) { TFT9341_FillRect(0, 0, TFT9341_WIDTH/2-1, TFT9341_HEIGHT/2-1, TFT9341_RandColor()); HAL_Delay(100); TFT9341_FillRect(TFT9341_WIDTH/2, 0, TFT9341_WIDTH-1, TFT9341_HEIGHT/2-1, TFT9341_RandColor()); HAL_Delay(100); TFT9341_FillRect(0, TFT9341_HEIGHT/2, TFT9341_WIDTH/2-1, TFT9341_HEIGHT-1, TFT9341_RandColor()); HAL_Delay(100); TFT9341_FillRect(TFT9341_WIDTH/2, TFT9341_HEIGHT/2, TFT9341_WIDTH-1, TFT9341_HEIGHT-1, TFT9341_RandColor()); HAL_Delay(100); } HAL_Delay(500); TFT9341_FillScreen(TFT9341_BLACK); |
Проверим, как работает тест
Следующий тест будет заливать прямоугольники случайного цвета, случайного размера и в случайные места
1 2 3 4 5 6 7 8 9 10 11 12 |
*/ for(i=0;i<1000;i++) { TFT9341_FillRect(HAL_RNG_GetRandomNumber(&hrng)%TFT9341_WIDTH, HAL_RNG_GetRandomNumber(&hrng)%TFT9341_HEIGHT, HAL_RNG_GetRandomNumber(&hrng)%TFT9341_WIDTH, HAL_RNG_GetRandomNumber(&hrng)%TFT9341_HEIGHT, TFT9341_RandColor()); HAL_Delay(10); } HAL_Delay(500); TFT9341_FillScreen(TFT9341_BLACK); |
Посмотрим работу теста
В следующей части урока мы напишем ещё несколько функций по работе с дисплеем, а также и проведём несколько тестов данных функций на практической схеме.
Предыдущий урок Программирование МК STM32 Следующая часть
Отладочную плату и дисплей можно приобрести здесь:
2,8 дюймов 240×320 SPI TFT LCD
Смотреть ВИДЕОУРОК (нажмите на картинку)
день добрый, посмотрел урок 180 с ДМА, где вы показали задержку в заливке экрана. У вас долгая заливка экрана из-за отправки пакета по 2 байта, попробуйте такую реализацию отправки пакета. думается мне что это будет равнозначно отправки по ДМА, разумеется не считая что во время отправки по ДМА можно заниматься чем то полезным.
void TFT9341_FillRect(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;
if(x1>x2) swap(x1,x2);
if(y1>y2) swap(y1,y2);
TFT9341_SetAddrWindow(x1, y1, x2, y2);
uint8_t data[] = { color >> 8, color & 0xFF };
uint32_t data_cnt = (x2-x1+1)*(y2-y1+1);
uint16_t tx_cnt;
DC_DATA();
while(data_cnt)
{
if(data_cnt > 0xFFFF)
{
tx_cnt = 0xFFFF;
}
else
{
tx_cnt = data_cnt;
}
HAL_SPI_Transmit(&hspi1, data, tx_cnt, HAL_MAX_DELAY);
data_cnt -= tx_cnt;
}
}
Здравствуйте.
Подскажите пожалуйста , Keil5 выдает в этих строках ошибку
if(x1>x2) swap(x1,x2); error: expected ';' alfter expression
if(y1>y2) swap(y1,y2); error: expected ';' alfter expression
в чем может быть дело?
С уважением
Сергей