/stm32-display-spi-dma

Connecting the displays to the stm32 microcontroller via spi with DMA. Without HAL (only CMSIS and LL)

Primary LanguageCMIT LicenseMIT

Copyright (C)2019-2022 VadRov / www.youtube.com/@VadRov / www.dzen.ru/vadrov

Библиотека управления дисплеями по SPI с DMA. Release 1.4

Управление подключенным дисплеем (дисплеями) с интерфейсом spi к микроконтроллеру семейства stm32f4 с поддержкой DMA, плавным изменением яркости подсветки посредством pwm. Поддерживаются дисплеи как с выводом CS, так и без него. Доступен выбор механизма управления выделением памяти: статическое или динамическое (см. файл display.h).

В библиотеке (папка Display) представлены низкоуровневые драйвера для дисплеев с контроллерами st7789 и ili9341. Для подключения дисплея по spi c иным контроллером необходимо по примеру этих драйверов написать свой низкоуровневый драйвер со строками инициализации и т.п., обратившись к спецификации соответствующего контроллера дисплея.

Доступны графические примитивы (точка, линия, прямоугольник и т.д.), вывод текста, вывод блока данных (изображения) в заданную область экрана. Доступно подключение шрифтов пользователя (в составе библиотеки их 3).

В качестве примера, в коде проекта, созданного в среде STM32CudeIDE, представлено подключение дисплеев с контроллерами ST7789 и ILI9341 к микроконтроллеру STM32F401CCU6 по SPI с DMA. Демонстрируется преимущество использования DMA.

Как использовать библиотеку и настроить проект в среде STM32CudeIDE подробно рассказано в видео: Watch the video Upd.: Замечание к видео (там старый релиз библиотеки). Новый релиз библиотеки требует:

  • Настройки DMA не в режиме Circular, как в видео, а в режиме Normal.
  • Создание обработчика нового дисплея осуществляется функцией LCD_DisplayAdd, создающей и добавляющей дисплей в список дисплеев. Этот список объявлен в библиотеке глобальной переменной LCD. После первого вызова указанной функции необходимо переназначать эту (LCD) переменную.
  1. Для варианта с динамическим выделением памяти:
LCD = LCD_DisplayAdd (LCD, параметры дисплея...);
  1. Для варианта со статическим выделением памяти:
LCD_Handler lcd1;
LCD = LCD_DisplayAdd (LCD, &lcd1, параметры дисплея...);

Получить адрес обработчика созданного первого дисплея можно для обоих вариантов так (хотя, для 2 варианта адрес обработчика заведомо изместен: &lcd1):

LCD_Handler *lcd = LCD;

Таким образом, lcd будет указывать на адрес созданного обработчика дисплея, он же первый дисплей в списке дисплеев LCD. Если требуется подключить еще один дисплей и создать обработчик для него, то после выполнения вышеуказанных действий, следует выполнить следующее:

  1. Для варианта с динамическим выделением памяти:
LCD_Handler *lcd2 = LCD_DisplayAdd (LCD, параметры второго дисплея...);
  1. Для варианта со статическим выделением памяти:
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_DisplayAdd

  1. Для динамического выделения памяти:
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);     //Данные для управления подсветкой дисплея
  1. Для статического выделения памяти: Параметры аналогичны, за исключением одного дополнительного 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