Skip to content

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

License

Notifications You must be signed in to change notification settings

vadrov/stm32-display-spi-dma

Repository files navigation

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

About

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

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages