Продолжаем программирование линейки контроллеров STM32F4 с использованием библиотеки LL.
Я решил немного перескочить некоторые части привычного алгоритма изучения библиотек и перейти сразу к шине SPI.
Данное решение принято было потому, что в плате STM32F429I-DISCOVERY, с которой мы работаем, установлен дисплей TFT разрешением 240×320 пикселей на контроллере ILI9341, который подключен к контроллеру как по интерфейсу LTDC, так и по шине SPI. Насчёт LTDC пока вопрос остаётся открытым, так как для него пока, видимо, библиотека LL ещё не написана, по крайней мере в последней на данный момент версии Cube MX мы при всём желании не можем задействовать на интерфейс LTDC данную библиотеку. Поэтому с дисплеем поработаем пока с использованием шины SPI. Дисплей нам постоянно будет требоваться в работе для мониторинга тех или иных состояний и значений, поэтому и было принято данное решение. Заодно и шину SPI рассмотрим.
Модуль SPI у контроллеров серии F4 устроен аналогично серии F1, практически без отличий, а вернее он вообще абсолютно такой же
Это даёт нам также возможность использовать материалы данного урока для подключения аналогичного дисплея к контроллерам серии F1 по шине SPI.
Что касается регистров, то там из изменений лишь наличие бита FRF (Frame format) в регистре CR2.
С дисплеем TFT разрешением 240×320 на контроллере ILI9341 мы неоднократно работали, в том числе и с использованием шины SPI, правда с использованием библиотеки HAL и с контроллером F1. Но то, что шина устроена точно так же, а также то, что с шиной SPI мы также с использованием библиотеки LL работали, применяя контроллер F1, то нам это значительно облегчит задачу.
Исходя из схемы к плате, подключен наш дисплей к шине SPI5, а ножки, в том числе и управляющие распределились следующим образом
PD12 — RESET
PC2 — CS
PD13 — DC
PF7 — SCK
PF9 — MOSI
Значит с таким учётом и будем настраивать наш проект в Cube MX, который мы сделаем из проекта прошлого занятия с именем LL_TIMER и присвоим ему новое имя LL_SPI_ILI9341.
Откроем наш проект в Cube MX и первым делом отключим таймер, он нам не потребуется
Включим SPI и настроим битрейт
Мы могли бы ещё уменьшить делитель, тем самым увеличив скорость обмена ещё вдвое, и всё бы работало, так как дисплей у нас впаянный, а не подключенный по проводам с разъёмами, но эффекта это никакого не даст, так как мы передаём в дисплей пакеты по одному или по два байта, больше времени уходит на обработку данных, мы не можем их заранее подготовить, положив в буфер и аппаратно оттуда без регистров АЛУ передать в контроллер дисплея. Это будет возможно, когда мы будем применять DMA. А пока предлагаю не мучить модуль SPI лишней неоправданной нагрузкой.
Включим ножку PC2 на выход
То же самое проделаем и с ножками PD12 и PD13
Скорость им настроим максимальную, такую же как и у ножек SPI
Также включим генератор случайных чисел
Настроим следующий делитель в Clock Configuration
Также задействуем библиотеку LL для SPI и RNG
Соберём проект, откроем его в Cube IDE и для начала в файле main.c удалим нашу функцию обработки событий от таймера
/* USER CODE BEGIN 4 */
void TIM6_Callback(void)
{
if(LL_TIM_IsActiveFlag_UPDATE(TIM6))
{
LL_TIM_ClearFlag_UPDATE(TIM6);
if(tim6_flag & 0x01) {LED2_OFF(); LED1_ON(); tim6_flag = 0;}
else {LED1_OFF(); LED2_ON(); tim6_flag = 1;}
}
}
Из проекта урока 179 с именем ILI9341_SPI добавим в соответствующие папки нашего проекта файлы spi_ili9341.h и spi_ili9341.c и затем переименуем их соответственно в ll_spi_ili9341.h и ll_spi_ili9341.c, а также файлы для работы со шрифтами fonts.h, font8.c, font12.c, font16.c, font20.c и font24.c.
Обновим дерево проекта и в файле ll_spi_ili9341.h вместо данной библиотеки
#include <cstdlib>
подключим две стандартные
#include <stdio.h>
#include <stdlib.h>
Также вместо вот этой библиотеки
#include "stm32f4xx_hal.h"
подключим несколько библиотек из комплекта LL
1 2 3 4 |
#include "stm32f4xx_ll_gpio.h" #include "stm32f4xx_ll_utils.h" #include "stm32f4xx_ll_spi.h" #include "stm32f4xx_ll_rng.h" |
Также изменим немного макросы работы с ножками GPIO, так как, во-первых, библиотека другая и ножки тоже другие
1 2 3 4 5 6 |
#define RESET_ACTIVE() LL_GPIO_ResetOutputPin(GPIOD,LL_GPIO_PIN_12) #define RESET_IDLE() LL_GPIO_SetOutputPin(GPIOD,LL_GPIO_PIN_12) #define CS_ACTIVE() LL_GPIO_ResetOutputPin(GPIOC,LL_GPIO_PIN_2) #define CS_IDLE() LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_2) #define DC_COMMAND() LL_GPIO_ResetOutputPin(GPIOD,LL_GPIO_PIN_13) #define DC_DATA() LL_GPIO_SetOutputPin(GPIOD,LL_GPIO_PIN_13) |
А также переименуем защиту от повторного использования здесь
#ifndef __LL_SPI_ILI9341_H
#define __LL_SPI_ILI9341_H
и здесь
#endif /* __LL_SPI_ILI9341_H */
Перейдём теперь в файл ll_spi_ili9341.c и изменим также имя подключаемого файла
#include <ll_spi_ili9341.h>
Удалим подключение переменных для RNG
extern SPI_HandleTypeDef hspi1;
extern RNG_HandleTypeDef hrng;
В функции TFT9341_SendCommand удалим вот эту строку
HAL_SPI_Transmit (&hspi1, &cmd, 1, 5000);
Вместо неё добавим такие строки
1 2 3 4 5 |
DC_COMMAND(); while(!LL_SPI_IsActiveFlag_TXE(SPI5)) {} LL_SPI_TransmitData8 (SPI5, cmd); while(!LL_SPI_IsActiveFlag_RXNE(SPI5)) {} LL_SPI_ReceiveData8(SPI5); |
Надеюсь, что как передаются байты в шину SPI при использовании библиотеки LL мы помним.
Аналогично, в функции TFT9341_SendData удалим вот эту строку
HAL_SPI_Transmit (&hspi1, &dt, 1, 5000);
А вместо неё добавим такие
1 2 3 4 5 |
DC_DATA(); while(!LL_SPI_IsActiveFlag_TXE(SPI5)) {} LL_SPI_TransmitData8 (SPI5, dt); while(!LL_SPI_IsActiveFlag_RXNE(SPI5)) {} LL_SPI_ReceiveData8(SPI5); |
В функции TFT9341_WriteData удалим вот эту строку
HAL_SPI_Transmit(&hspi1, buff, chunk_size, HAL_MAX_DELAY);
А добавим вот такие строки
1 2 3 4 5 6 7 8 |
uint16_t chunk_size = buff_size > 32768 ? 32768 : buff_size; for(int i=0; i<chunk_size; i++) { while(!LL_SPI_IsActiveFlag_TXE(SPI5)) {} LL_SPI_TransmitData8 (SPI5, buff[i]); while(!LL_SPI_IsActiveFlag_RXNE(SPI5)) {} LL_SPI_ReceiveData8(SPI5); } |
Затем в тех местах, где встретится вызов функции задержки HAL_Delay, заменим её на LL_mDelay.
В функции TFT9341_FillRect удалим строку
HAL_SPI_Transmit(&hspi1, data, 2, HAL_MAX_DELAY);
и заменим её на такую
1 2 3 |
for(uint32_t i = 0; i < (x2-x1+1)*(y2-y1+1); i++) { TFT9341_WriteData(data, 2); |
В функции TFT9341_RandColor удалим строку
return HAL_RNG_GetRandomNumber(&hrng)&0x0000FFFF;
и вместо неё вставим вот такие
1 2 3 4 |
uint16_t TFT9341_RandColor(void) { while (!LL_RNG_IsActiveFlag_DRDY(RNG)) {} return LL_RNG_ReadRandData32(RNG)&0x0000FFFF; |
Перейдём в файл main.c и подключим нашу библиотеку
1 2 |
/* USER CODE BEGIN Includes */ #include "ll_spi_ili9341.h" |
Удалим объявление переменной для переключения светодиодов
uint8_t tim6_flag=0;
Подключим высоту и ширину экрана
1 2 3 |
/* USER CODE BEGIN PV */ extern uint16_t TFT9341_WIDTH; extern uint16_t TFT9341_HEIGHT; |
Добавим также функцию задержки в микросекундах
1 2 3 4 5 6 7 8 |
/* USER CODE BEGIN 0 */ //------------------------------------------------------- __STATIC_INLINE void DelayMicro(uint32_t __IO micros) { micros *=(SystemCoreClock/1000000)/5; while(micros--); } //------------------------------------------------------- |
В функции main() удалим настройку таймера
LL_TIM_EnableIT_UPDATE(TIM6);
LL_TIM_EnableCounter(TIM6);
Объявим две локальные переменные
1 2 |
/* USER CODE BEGIN 1 */ uint16_t i,j; |
Включим SPI5, вызовем функцию инициализации дисплея и окрасим его в чёрный цвет
1 2 3 4 |
/* USER CODE BEGIN 2 */ LL_SPI_Enable(SPI5); TFT9341_ini(240, 320); TFT9341_FillScreen(TFT9341_BLACK); |
Соберём код, прошьём контроллер и увидим, что дисплей наш окрасился в чёрный цвет
В бесконечном цикле добавим все наши тесты, немного подредактировав код в свете требований библиотеки LL
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
/* USER CODE BEGIN 3 */ for(i=0;i<20;i++) { TFT9341_FillScreen(TFT9341_RandColor()); LL_mDelay(150); } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); for(i=0;i<20;i++) { TFT9341_FillRect(0, 0, TFT9341_WIDTH/2-1, TFT9341_HEIGHT/2-1, TFT9341_RandColor()); LL_mDelay(100); TFT9341_FillRect(TFT9341_WIDTH/2, 0, TFT9341_WIDTH-1, TFT9341_HEIGHT/2-1, TFT9341_RandColor()); LL_mDelay(100); TFT9341_FillRect(0, TFT9341_HEIGHT/2, TFT9341_WIDTH/2-1, TFT9341_HEIGHT-1, TFT9341_RandColor()); LL_mDelay(100); TFT9341_FillRect(TFT9341_WIDTH/2, TFT9341_HEIGHT/2, TFT9341_WIDTH-1, TFT9341_HEIGHT-1, TFT9341_RandColor()); LL_mDelay(100); } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); for(i=0;i<1000;i++) { TFT9341_FillRect(LL_RNG_ReadRandData32(RNG)%TFT9341_WIDTH, LL_RNG_ReadRandData32(RNG)%TFT9341_HEIGHT, LL_RNG_ReadRandData32(RNG)%TFT9341_WIDTH, LL_RNG_ReadRandData32(RNG)%TFT9341_HEIGHT, TFT9341_RandColor()); LL_mDelay(10); } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); for(i=0;i<15000;i++) { for(j=0;j<100;j++) { TFT9341_DrawPixel(LL_RNG_ReadRandData32(RNG)%TFT9341_WIDTH, LL_RNG_ReadRandData32(RNG)%TFT9341_HEIGHT, TFT9341_BLACK); } TFT9341_DrawPixel(LL_RNG_ReadRandData32(RNG)%TFT9341_WIDTH, LL_RNG_ReadRandData32(RNG)%TFT9341_HEIGHT, TFT9341_RandColor()); DelayMicro(100); } LL_mDelay(500); for(j=0;j<20;j++) { for(i=0;i<TFT9341_WIDTH;i++) { TFT9341_DrawLine(TFT9341_RandColor(),i,0,i,TFT9341_HEIGHT-1); } } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); for(i=0;i<1000;i++) { TFT9341_DrawLine(TFT9341_RandColor(), LL_RNG_ReadRandData32(RNG)%TFT9341_WIDTH, LL_RNG_ReadRandData32(RNG)%TFT9341_HEIGHT, LL_RNG_ReadRandData32(RNG)%TFT9341_WIDTH, LL_RNG_ReadRandData32(RNG)%TFT9341_HEIGHT); LL_mDelay(10); } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); for(j=0;j<20;j++) { for(i=0;i<TFT9341_WIDTH/2;i++) { TFT9341_DrawRect(TFT9341_RandColor(), i, i, TFT9341_WIDTH-i-1, TFT9341_HEIGHT-i-1); } } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); for(i=0;i<2000;i++) { TFT9341_DrawCircle(LL_RNG_ReadRandData32(RNG)%(TFT9341_WIDTH-40)+20, LL_RNG_ReadRandData32(RNG)%(TFT9341_HEIGHT-40)+20, 20,TFT9341_RandColor()); LL_mDelay(3); } LL_mDelay(500); TFT9341_FillScreen(TFT9341_BLACK); TFT9341_SetTextColor(TFT9341_YELLOW); TFT9341_SetBackColor(TFT9341_BLUE); TFT9341_SetFont(&Font24); TFT9341_DrawChar(10,10,'S'); TFT9341_DrawChar(27,10,('t')); TFT9341_DrawChar(44,10,('m')); TFT9341_DrawChar(61,10,('3')); TFT9341_DrawChar(78,10,('2')); TFT9341_SetTextColor(TFT9341_GREEN); TFT9341_SetBackColor(TFT9341_RED); TFT9341_SetFont(&Font20); TFT9341_DrawChar(10,34,('S')); TFT9341_DrawChar(24,34,('t')); TFT9341_DrawChar(38,34,('m')); TFT9341_DrawChar(52,34,('3')); TFT9341_DrawChar(66,34,('2')); TFT9341_SetTextColor(TFT9341_BLUE); TFT9341_SetBackColor(TFT9341_YELLOW); TFT9341_SetFont(&Font16); TFT9341_DrawChar(10,54,('S')); TFT9341_DrawChar(21,54,('t')); TFT9341_DrawChar(32,54,('m')); TFT9341_DrawChar(43,54,('3')); TFT9341_DrawChar(54,54,('2')); TFT9341_SetTextColor(TFT9341_CYAN); TFT9341_SetBackColor(TFT9341_BLACK); TFT9341_SetFont(&Font12); TFT9341_DrawChar(10,70,('S')); TFT9341_DrawChar(17,70,('t')); TFT9341_DrawChar(24,70,('m')); TFT9341_DrawChar(31,70,('3')); TFT9341_DrawChar(38,70,('2')); TFT9341_SetTextColor(TFT9341_RED); TFT9341_SetBackColor(TFT9341_GREEN); TFT9341_SetFont(&Font8); TFT9341_DrawChar(10,82,('S')); TFT9341_DrawChar(15,82,('t')); TFT9341_DrawChar(20,82,('m')); TFT9341_DrawChar(25,82,('3')); TFT9341_DrawChar(30,82,('2')); TFT9341_SetTextColor(TFT9341_YELLOW); TFT9341_SetBackColor(TFT9341_BLUE); LL_mDelay(2000); TFT9341_FillScreen(TFT9341_BLACK); for(i=0;i<16;i++) { TFT9341_SetRotation(i%4); TFT9341_SetFont(&Font24); TFT9341_FillScreen(TFT9341_BLACK); TFT9341_String(1,100,"ABCDEF12345678"); TFT9341_SetFont(&Font20); TFT9341_String(1,124,"ABCDEFGHI12345678"); TFT9341_SetFont(&Font16); TFT9341_String(1,144,"ABCDEFGHIKL123456789"); TFT9341_SetFont(&Font12); TFT9341_String(1,160,"ABCDEFGHIKLMNOPQRSTUVWXY 123456789"); TFT9341_SetFont(&Font8); TFT9341_String(1,172,"ABCDEFGHIKLMNOPQRSTUVWXYZ 123456789ABCDEFGHIKL"); LL_mDelay(2000); } TFT9341_SetRotation(0); |
Соберём код, прошьём контроллер, и посмотрим, как работают тесты
Все наши тесты исправно работают.
Таким образом, на данном уроке нам удалось поработать с дисплеем TFT, распаянном на плате STM32F429I-DISCOVERY и подключенному по шине SPI.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F429I-DISCO
Смотреть ВИДЕОУРОК (нажмите на картинку)
Здравствуйте!
Спасибо за Ваши уроки — очень познавательно!
Дисплеем как раз разжился и решил поэкспериментировать.
Сделал как написано в readme — в CubeMX(6.5.0) создал проект для CubeIDE(1.7.0), добавил туда файлы, запустил CubeIDE, вылезли ошибки:
font12.c:89: multiple definition of
Font12_Table'
Font16_Table'font16.c:89: multiple definition of
font20.c:87: multiple definition of
Font20_Table'
Font24_Table'font24.c:85: multiple definition of
font8.c:89: multiple definition of `Font8_Table'
Помогите, пожалуйста разобраться с этими ошибками.
Владимир.