Copyright (C)2019-2022 VadRov / www.youtube.com/@VadRov / www.dzen.ru/vadrov
Управление подключенным дисплеем (дисплеями) с интерфейсом spi к микроконтроллеру семейства stm32f4 с поддержкой DMA, плавным изменением яркости подсветки посредством pwm. Поддерживаются дисплеи как с выводом CS, так и без него. Доступен выбор механизма управления выделением памяти: статическое или динамическое (см. файл display.h).
В библиотеке (папка Display) представлены низкоуровневые драйвера для дисплеев с контроллерами st7789 и ili9341. Для подключения дисплея по spi c иным контроллером необходимо по примеру этих драйверов написать свой низкоуровневый драйвер со строками инициализации и т.п., обратившись к спецификации соответствующего контроллера дисплея.
Доступны графические примитивы (точка, линия, прямоугольник и т.д.), вывод текста, вывод блока данных (изображения) в заданную область экрана. Доступно подключение шрифтов пользователя (в составе библиотеки их 3).
В качестве примера, в коде проекта, созданного в среде STM32CudeIDE, представлено подключение дисплеев с контроллерами ST7789 и ILI9341 к микроконтроллеру STM32F401CCU6 по SPI с DMA. Демонстрируется преимущество использования DMA.
Как использовать библиотеку и настроить проект в среде STM32CudeIDE подробно рассказано в видео: Upd.: Замечание к видео (там старый релиз библиотеки). Новый релиз библиотеки требует:
- Настройки DMA не в режиме Circular, как в видео, а в режиме Normal.
- Создание обработчика нового дисплея осуществляется функцией LCD_DisplayAdd, создающей и добавляющей дисплей в список дисплеев. Этот список объявлен в библиотеке глобальной переменной LCD. После первого вызова указанной функции необходимо переназначать эту (LCD) переменную.
- Для варианта с динамическим выделением памяти:
LCD = LCD_DisplayAdd (LCD, параметры дисплея...);
- Для варианта со статическим выделением памяти:
LCD_Handler lcd1;
LCD = LCD_DisplayAdd (LCD, &lcd1, параметры дисплея...);
Получить адрес обработчика созданного первого дисплея можно для обоих вариантов так (хотя, для 2 варианта адрес обработчика заведомо изместен: &lcd1):
LCD_Handler *lcd = LCD;
Таким образом, lcd будет указывать на адрес созданного обработчика дисплея, он же первый дисплей в списке дисплеев LCD. Если требуется подключить еще один дисплей и создать обработчик для него, то после выполнения вышеуказанных действий, следует выполнить следующее:
- Для варианта с динамическим выделением памяти:
LCD_Handler *lcd2 = LCD_DisplayAdd (LCD, параметры второго дисплея...);
- Для варианта со статическим выделением памяти:
LCD_Handler lcd_2, *lcd2;
lcd2 = LCD_DisplayAdd (LCD, &lcd_2, параметры второго дисплея...);
Таким образом, получим указатель на обработчик второго дисплея lcd2. Причем, для второго варианта (статическое выделение памяти) при обращении к обработчику дисплея запись &lcd_2 эквивалентна записи lcd2. Также указатель на второй дисплей можно получить так:
LCD_Handler *lcd2_ptr = LCD->next;
В демо-проекте (см. файл main.c) показаны варианты инициализации дисплея при использовании динамического и статического выделения памяти. Определение механизма выделения памяти осуществляется параметром LCD_DYNAMIC_MEM в заголовочном файле драйвера display.h.
Два варианта прототипов функции LCD_DisplayAdd
- Для динамического выделения памяти:
LCD_Handler* LCD_DisplayAdd(LCD_Handler *lcds, //Список дисплеев (определен глобально, как LCD)
uint16_t resolution1, //Первая из двух физических размерностей матрицы дисплея в пикселях
uint16_t resolution2, //Вторая из двух физических размерностей матрицы дисплея в пикселях
//Размерности можно указывать в любом порядке
uint16_t width_controller, //Максимально поддерживаемое контроллером дисплея физическое разрешение матрицы дисплея по горизонтали, пикселей (в спецификациях обозначается, как H)
uint16_t height_controller, //Максимально поддерживаемое контроллером дисплея физическое разрешение матрицы дисплея по вертикали, пикселей (в спецификациях обозначается, как V)
//Параметры w_offs и h_offs используются для нестандартных и "кривых" дисплеев, у которых начало координат физической матрицы дисплея
//и поля контроллера дисплея не совпадают, т.е. смещены. Определяются исходя из конкретного образца, имеющегося на руках, и задают
//"центрирование изображения" на дисплее. "Расцентровка" проявляется в виде смещения изображения на дисплее по вертикали или/и по
//горизонтали. Характерна для дисплеев, разрешение которых меньше разрешения примененного контроллера дисплея.
int16_t w_offs, //Смещение по горизонтали матрицы дисплея в поле контроллера дисплея
int16_t h_offs, //Смещение по вертикали матрицы дисплея в поле контроллера дисплея
LCD_PageOrientation orientation, //Ориентация: портретная или альбомная, обычная или зеркальная
DisplayInitCallback init, //Функция инициализации дисплея
DisplaySetWindowCallback set_win, //Функция определения окна вывода дисплея
DisplaySleepInCallback sleep_in, //Функция включения режима сна дисплея
DisplaySleepOutCallback sleep_out,//Функция выхода из режима сна дисплея
void *connection_data, //Данные подключения контроллера дисплея к МК
LCD_DATA_BUS data_bus, //Ширина кадра данных spi (8 или 16 бит)
LCD_BackLight_data bkl_data); //Данные для управления подсветкой дисплея
- Для статического выделения памяти: Параметры аналогичны, за исключением одного дополнительного lcd.
LCD_Handler* LCD_DisplayAdd(LCD_Handler *lcds,
LCD_Handler *lcd, //Указатель на объявленный пользователем обработчик дисплея
uint16_t resolution1,
uint16_t resolution2,
uint16_t width_controller,
uint16_t height_controller,
int16_t w_offs,
int16_t h_offs,
LCD_PageOrientation orientation,
DisplayInitCallback init,
DisplaySetWindowCallback set_win,
DisplaySleepInCallback sleep_in,
DisplaySleepOutCallback sleep_out,
void *connection_data,
LCD_DATA_BUS data_bus,
LCD_BackLight_data bkl_data);
Параметры resolution1 и resolution2 определяются из маркировки приобретенного дисплея. Как правило, на оборотной стороне (или лицевой, либо на сайте продавца) есть указание на разрешение дисплея, а также на контроллер, управляющий дисплейной матрицей. Например, "ips display, 240x240, st7789v", означает, что имеем дело с дисплеем разрешением 240 на 240 пикселей. При этом, матрицей дисплея управляет контроллер st7789v. Таким образом, для указанного примера: resolution1 = 240, resolution2 = 240. Из информации о контроллере, обратившись к спецификации, получаем, что контроллер поддерживает дисплейные матрицы с разрешением до: 240x320 пикселей (H = 240, V = 320). Эта информация потребуется для следующих двух параметров: width_controller и height_controller, т.е. width_controller = 240 (параметр H из спецификации), height_controller = 320 (параметр V из спецификации). Вообще, сама библиотека "из коробки" поддерживает дисплеи на контроллерах ili9341 и st7789, т.е. в ее составе есть низкоуровневые драйвера для таких дисплеев.
Параметры w_offs и h_offs используются для нестандартных и "кривых" дисплеев, у которых начало координат физической матрицы дисплея и поля контроллера дисплея не совпадают, т.е. смещены. Определяются исходя из конкретного образца, имеющегося на руках, и задают "центрирование изображения" на дисплее. "Расцентровка" проявляется в виде смещения изображения на дисплее по вертикали или/и по горизонтали. Характерна для дисплеев, разрешение матриц которых меньше максимального разрешения, поддерживаемого контроллером дисплея. w_offs определяет смещение по горизонтали матрицы дисплея в поле контроллера дисплея, а h_offs - аналогичное смещение, но по вертикали.
Для проверки правильности центровки изображения, после инициализации дисплея (w_offs и h_offs задаем равными нулю) следует выполнить следующий код:
LCD_Fill(lcd, COLOR_WHITE); //заливка дисплея белым цветом
LCD_DrawRectangle(lcd, 0, 0, LCD_GetWidth(lcd) - 1, LCD_GetHeight(lcd) - 1, COLOR_RED); //прямоугольник со сторонами, проходящими по краям дисплея
LL_mDelay(5000); //пауза 5 секунд
Вся видимая область дисплея должна окраситься белым цветом. На этом белом фоне должен быть полностью виден прямоугольник со сторонами красного цвета. Если какая-то из сторон прямоугольника невидна, то центровка изображения неправильная, и необходимо при инициализации установить такие параметры h_offs и w_offs, чтобы все стороны прямоугольника были видимыми. Если настройка центровки не позволяет добиться видимости всех сторон прямоугольника, то причиной могут быть либо неправильные параметры resolution1 и resolution2 (неверно определенное разрешение матрицы дисплея), либо неправильные параметры width_controller и height_controller (неверно определенное максимальное разрешение матрицы дисплея, поддерживаемое контроллером). Однако, есть и еще один вариант - бракованный дисплей, не обеспечивающий заявленное разрешение из-за дефекта при изготовлении.
Параметр orientation определяет ориентацию картинки и может принимать одно из четырех значений, определенных драйвером:
PAGE_ORIENTATION_PORTRAIT портрет
PAGE_ORIENTATION_LANDSCAPE пейзаж
PAGE_ORIENTATION_PORTRAIT_MIRROR портрет зеркальное отображение (перевернуто)
PAGE_ORIENTATION_LANDSCAPE_MIRROR пейзаж зеркальное отображение (перевернуто)
Параметры init, set_win, sleep_in и sleep_out определяют функции для доступа к драйверу применямого контроллера дисплея. Причем, допускается "обнулять" параметры sleep_in и sleep_out. Все четыре функции (а, как минимум, две первых) должны входить в состав низкоуровневого драйвера дисплея. Напомню, что сама библиотека “из коробки” поддерживает контроллеры дисплеев ili9341 и st7789. Указанным выше параметрам соответствуют функции из состава дисплейных драйверов библиотеки: xxxx_Init, xxxx_SetWindow, xxxx_SleepIn, xxxx_SleepOut, где xxxx - префикс, определяющий используемый драйвер дисплея (ST7789 либо ILI9341).
Параметр connection_data определяет параметры подключения дисплея к МК для передачи данных, в том числе, DMA. connection_data представляет из себя структуру типа LCD_SPI_Connected_data:
typedef struct {
SPI_TypeDef *spi; //используемый spi
LCD_DMA_TypeDef dma_tx; //используемый DMA поток
GPIO_TypeDef *reset_port; //порт вывода RESET
uint16_t reset_pin; //пин вывода RESET
GPIO_TypeDef *dc_port; //порт вывода DC
uint16_t dc_pin; //пин вывода DC
GPIO_TypeDef *cs_port; //порт вывода CS
uint16_t cs_pin; //пин вывода CS
} LCD_SPI_Connected_data;
При этом, параметр dma_tx представляет из себя структуру типа LCD_DMA_TypeDef:
typedef struct {
DMA_TypeDef *dma; //контроллер DMA
uint32_t stream; //поток DMA
} LCD_DMA_TypeDef;
Параметр data_bus определяет ширину кадра spi (8 либо 16 бит). Для данного параметра библиотекой определены три значения (константы):
LCD_DATA_UNKNOW_BUS //неизвестная ширина кадра
LCD_DATA_8BIT_BUS //ширина кадра 8 бит
LCD_DATA_16BIT_BUS //ширина кадра 16 бит
Параметр bkl_data определяет механизм управления подсветкой дисплея и представляет из себя структуру типа LCD_BackLight_data:
typedef struct {
//-------------- Для подсветки с PWM --------------
TIM_TypeDef *htim_bk; //таймер
uint32_t channel_htim_bk; //канал таймера
//------- Просто для включения и выключения подсветки, если htim_bk = 0 ---------
GPIO_TypeDef *blk_port; //порт вывода
uint16_t blk_pin; //пин порта
uint8_t bk_percent; //яркость подсветки для подсветки с PWM, %
//либо 0 - подсветка отключена, > 0 подсветка включена
} LCD_BackLight_data;
Если есть проблемы, вызванные потерей связи с контроллером дисплея (зависания дисплея, "каша" и т.п.), то зачастую помогает подтяжка к питанию (pull_up) линий sck и mosi. Например, настройка gpio, используемых spi, в виде:
/* SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA7 ------> SPI1_MOSI */
GPIO_InitStruct.Pin = LCD_SCL_Pin|LCD_SDA_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP; /* Подтяжка к питанию есть */
GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
Если не помогает подтяжка к питанию, то причина потерь связи может быть вызвана большой скоростью spi, которая не поддерживается контроллером дисплея либо длинными проводниками, соединяющими дисплей с микроконтроллером, наводками от каких-либо помехосоздающих устройств, плохим контактом и т.п.
Обращаю внимание, что скорость выводов spi (maximum output speed) должна быть Very High. Скорость управляющих выводов LCD_CS, LCD_DC, LCD_RESET - High. Если используется вывод CS (LCD_CS) дисплея, то подтяните его к питанию (pull up) и проинициализируйте высоким уровнем (GPIO output level -> High). Вывод LCD_RESET также проинициализируйте высоким уровнем (GPIO output level -> High).
Дисплей может не запускаться из-за неправильной полярности тактового сигнала spi (см. в настройках spi параметр CPOL - Clock Polarity). Как правило, дисплей на контроллере st7789 работает при значении CPOL = high, а на контроллере ili9341 при значении CPOL = low (может работать и на high, но не все модели и не всегда стабильно, признак неправильной полярности - дисплей стартует через раз/два при сбросе микроконтроллера).
Проблемы связи также могут быть вызваны пульсирующим питанием модуля дисплея. Возможно, в параллель питающих проводников дисплея потребуется установить электролитический конденсатор, например, емкостью около 100 мкф.
Также проблемы могут быть вызваны неправильным питающим напряжением дисплея. Так, например, популярные дисплеи с контроллерами ili9341 имеют на плате встроенный преобразователь напряжения 3,3 В, и должны запитываться от напряжения около 5 В. При попытке запитать эти модули от напряжения 3,3 В, выработанные преобразователем платы микроконтроллера, можно получить нестабильную работу дисплея на скорости spi свыше 10 - 20 Мбит/с. В тоже время, следует помнить, что популярные IPS дисплеи 1,3' 240х240 px на базе контроллера st7789 питаются напряжением 3.3 В.
Всегда сверяйте с документацией питающее напряжение модуля дисплея!
Рекомендую проводить пробное подключение дисплея на скорости около 10 Мбит/с. Добившись стабильной работы дисплея на этой скорости (многократный сброс не вызывает ошибок в работе, дисплей не подвисает, артефактов нет и т.п.), можно поэтапно повышать скорость (уменьшать делитель частоты spi, параметр Prescaler).
Автор: VadRov
Контакты: Youtube Дзен VK Telegram
Поддержать автора: donate.yoomoney