Давно в планах висит статья про подключение дисплея на базе популярного контроллера ST7735 к STM32. В общем-то, план был довольно долгосрочный... Но благодаря читателю блога, который поднял эту тему, принято решение кардинально ускорить данный процесс. Так что сегодня об этом и пойдет речь.
Так, задачей нашей будет сделать базовый проект, который позволит с минимальным порогом вхождения начать работу с любым дисплеем на базе ST7735. В дебри настроек погружаться не будем, пересказыванием даташита заниматься тоже, во главе угла - реальная польза от практической реализации. Вот кстати ссылка на подробнейшее pdf описание контроллера - ссылка.
Подключение дисплея на базе ST7735.
Начинаем традиционно - с аппаратной части и подключения дисплея. У меня есть в наличии несколько десятков дисплеев, но преимущественно разрешением 128*160 пикселей. Такой и возьму:
Дисплей имеет 14-ти контактный шлейф для коммутации, его распиновка выглядит следующим образом:
- NC - Not Connected
- GND
- LED K - катод светодиодов подсветки
- LED A - анод светодиодов подсветки
- GND
- RST - сигнал сброса
- D/C - сигнал выбора передачи данных или команд
- SDA - SPI MOSI
- SCK - SPI SCK
- VCC - напряжение питания
- IOVCC - источник питания внутренних интерфейсов
- CS - Chip Select
- GND
- NC - Not Connected
К слову, в разных схемах/статьях/алиэкспрессах обозначение может незначительно, но отличаться. Например, сигнал D/C часто обозначают как RS.
Собрав по-быстрому, "на коленке", плату для подключения дисплея, получаем следующий вариант коммутации:
У меня выбранные ножки фиксированы разводкой платы, а так можно использовать, естественно, любые другие. На подсветку (LED K и LED A) я подключил 3.3 В напрямую (благо источник питания позволяет, потребление светодиодов подсветки на моем дисплее - в районе 40 мА на 3.3 В), то есть подсветка будет работать перманентно. А в обычных проектах я подключаю примерно так:
Соответственно, так мы имеем возможность, во-первых, отключить подсветку, когда требуется, а, во-вторых, управлять яркостью, подавая, например, ШИМ-сигнал разной скважности с микроконтроллера.
В качестве управляющего контроллера у меня STM32F103RE. Пока не забыл, немаловажный нюанс - у меня стоит на этой плате кварц на 12 МГц, можно сказать, что величина нестандартная, так как в подавляющем большинстве случаев используется 8 МГц, ну или реже - 16 МГц. Поэтому обратите на это внимание в настройках тактирования при портировании проекта на свое железо.
Для выдачи данных на дисплей используется интерфейс SPI, причем дисплей только принимает информацию, не передавая ничего в обратном направлении. Это мы учтем при инициализации периферии чуть ниже. Кроме того, потребуются три порта ввода-вывода для подключения линий Chip Select, Reset, D/C. Последняя нужна для информирования дисплея о том, являются ли передаваемые байты данными или командой. Но и с этим мы разберемся чуть позже - при написании библиотеки для работы с ST7735.
Формирование цветов.
На чем хочется немного подробнее остановиться, так это на схеме передачи цветов. Дисплей предлагает 3 варианта кодирования цвета:
- 12 битов на пиксель = 4 бита красный (R) + 4 бита зеленый (G) + 4 бита синий (B)
- 16 битов на пиксель = 5 битов красный (R) + 6 битов зеленый (G) + 5 битов синий (B)
- 18 битов на пиксель = 6 битов красный (R) + 6 битов зеленый (G) + 6 битов синий (B)
В этом базовом проекте я использую второй из этих вариантов. Настраивается данная опция в регистре ST7735 COLMOD, для 16-ти битного формата значение 0x05.
При таком режиме основные цвета будут закодированы так:
То есть получаем 16-ти битные значения:
- Красный - 0xF800
- Зеленый - 0x07E0
- Синий - 0x001F
- Черный - 0x0000
- Белый - 0xFFFF
Библиотека для работы с ST7735.
Давайте перейдем к созданию проекта. В STM32CubeMx настраиваем необходимую периферию, которую мы уже, в принципе, всю обсудили:
Тактовые частоты с учетом моего резонатора на 12 МГц такие:
А настройки SPI:
DMA в базовом проекте использовать не будем, но я обычно рекомендую не пренебрегать возможностями контроллера. И, соответственно, для перебрасывания больших объемов данных задействовать DMA.
Итак, меньше слов, больше дела, генерируем проект и сразу добавляем в него файлы для будущей библиотеки ST7735:
Начинаем с необходимых определений в файле st7735.h. Первым делом несколько цветов, чтобы наши тесты были более наглядными:
#define ST7735_COLOR_RED 0xF800 #define ST7735_COLOR_GREEN 0x07E0 #define ST7735_COLOR_BLUE 0x001F #define ST7735_COLOR_YELLOW 0xFFE0 #define ST7735_COLOR_WHITE 0xFFFF #define ST7735_COLOR_BLACK 0x0000 #define ST7735_COLOR_ORANGE 0xFA20
Второе в списке необходимых дел - выбор портов для взаимодействия с дисплеем:
#define ST7735_RESET_PORT (GPIOB) #define ST7735_RESET_PIN (GPIO_PIN_7) #define ST7735_CS_PORT (GPIOC) #define ST7735_CS_PIN (GPIO_PIN_11) #define ST7735_DC_PORT (GPIOB) #define ST7735_DC_PIN (GPIO_PIN_6)
Зафиксируем также размеры используемого дисплея. Для моего 128*160:
#define ST7735_X_SIZE 128 #define ST7735_Y_SIZE 160
Далее в файле идут всяческие определения команд/регистров, полный код файлов и ссылку на готовый проект можно будет найти в конце статьи. А пока продолжаем с библиотекой... И далее наши конструктивные действия будут сосредоточены в файле st7735.c. Чтобы использовать hspi1
добавляем:
extern SPI_HandleTypeDef hspi1;
Начинаем последовательно с самого низкого уровня реализовывать необходимые функции. И первая из них - это отправка байта по интерфейсу SPI:
/*----------------------------------------------------------------------------*/ void ST7735_SendByte(uint8_t data) { HAL_SPI_Transmit(&hspi1, &data, 1, ST7735_SPI_TIMEOUT); } /*----------------------------------------------------------------------------*/
Контроллер ST7735 может ожидать от нас либо байт команды, либо байт данных. С точки зрения SPI в этих случаях все одинаково, отличается лишь уровень сигнала на D/C:
/*----------------------------------------------------------------------------*/ void ST7735_SendCommand(uint8_t data) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_RESET); ST7735_SendByte(data); } /*----------------------------------------------------------------------------*/ void ST7735_SendData(uint8_t data) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); ST7735_SendByte(data); } /*----------------------------------------------------------------------------*/ void ST7735_SendDataMultiple(uint8_t *data, uint32_t num) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); for (uint32_t i = 0; i < num; i++) { ST7735_SendByte(*data); data++; } } /*----------------------------------------------------------------------------*/
Инициализация дисплея.
Переходим к инициализации дисплея. Я приведу полный код из одного из своих проектов, его можно использовать в качестве отправной точки, изменив, либо дополнив в зависимости от собственных нужд. Кстати, контроллеры ST7735 бывают разных модификаций (ST7735R, ST7735S...), и там могут быть отличия в процедуре конфигурирования. Если возникнут проблемы, пишите на форум, будем решать:
/*----------------------------------------------------------------------------*/ void ST7735_Init() { HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(ST7735_RESET_PORT, ST7735_RESET_PIN, GPIO_PIN_SET); HAL_Delay(5); HAL_GPIO_WritePin(ST7735_RESET_PORT, ST7735_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(5); HAL_GPIO_WritePin(ST7735_RESET_PORT, ST7735_RESET_PIN, GPIO_PIN_SET); HAL_Delay(5); ST7735_SendCommand(ST7735_SWRESET); HAL_Delay(150); ST7735_SendCommand(ST7735_SLPOUT); HAL_Delay(500); ST7735_SendCommand(ST7735_FRMCTR1); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendCommand(ST7735_FRMCTR2); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendCommand(ST7735_FRMCTR3); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendCommand(ST7735_INVCTR); ST7735_SendData(0x07); ST7735_SendCommand(ST7735_PWCTR1); ST7735_SendData(0xA2); ST7735_SendData(0x02); ST7735_SendData(0x84); ST7735_SendCommand(ST7735_PWCTR2); ST7735_SendData(0xC5); ST7735_SendCommand(ST7735_PWCTR3); ST7735_SendData(0x0A); ST7735_SendData(0x00); ST7735_SendCommand(ST7735_PWCTR4); ST7735_SendData(0x8A); ST7735_SendData(0x2A); ST7735_SendCommand(ST7735_PWCTR5); ST7735_SendData(0x8A); ST7735_SendData(0xEE); ST7735_SendCommand(ST7735_VMCTR1); ST7735_SendData(0x0E); ST7735_SendCommand(ST7735_INVOFF); ST7735_SendCommand(ST7735_MADCTL); ST7735_SendData(0xC0); ST7735_SendCommand(ST7735_COLMOD); ST7735_SendData(0x05); ST7735_SendCommand(ST7735_GMCTRP1); ST7735_SendData(0x02); ST7735_SendData(0x1c); ST7735_SendData(0x07); ST7735_SendData(0x12); ST7735_SendData(0x37); ST7735_SendData(0x32); ST7735_SendData(0x29); ST7735_SendData(0x2d); ST7735_SendData(0x29); ST7735_SendData(0x25); ST7735_SendData(0x2B); ST7735_SendData(0x39); ST7735_SendData(0x00); ST7735_SendData(0x01); ST7735_SendData(0x03); ST7735_SendData(0x10); ST7735_SendCommand(ST7735_GMCTRN1); ST7735_SendData(0x03); ST7735_SendData(0x1d); ST7735_SendData(0x07); ST7735_SendData(0x06); ST7735_SendData(0x2E); ST7735_SendData(0x2C); ST7735_SendData(0x29); ST7735_SendData(0x2D); ST7735_SendData(0x2E); ST7735_SendData(0x2E); ST7735_SendData(0x37); ST7735_SendData(0x3F); ST7735_SendData(0x00); ST7735_SendData(0x00); ST7735_SendData(0x02); ST7735_SendData(0x10); ST7735_SendCommand(ST7735_NORON); HAL_Delay(10); ST7735_SendCommand(ST7735_DISPON); HAL_Delay(100); HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_SET); } /*----------------------------------------------------------------------------*/
Инициализацию для статьи переделал на более наглядный вариант в стиле последовательной отправки значений. А так, конечно, рекомендую раскидать конфигурацию в массив или структуру и в цикле пробегать.
Установка рабочей области.
Для того, чтобы выводить на дисплей графическую информацию, нужно задать область, с которой мы хотим работать. Для этого используются команды CASET и RASET, то есть установка начальных и конечных значений колонок (x) и рядов (y) пикселей дисплея. Разбираем подробно и наглядно:
Допустим мы хотим работать с областью размером 25 * 50 пикселей, расположенной в левом верхнем углу. Суммарно дисплей представляет из себя полотно 128*160 пикселей. Так что для достижения наших целей мы должны установить необходимые пределы командами CASET и RASET (псевдокод):
- CASET(0, 25)
- RASET(0, 50)
- Изменения после этих команд вступают в силу после выполнения команды RAMWR
- Далее можно отправлять данные для задания цвета пикселей.
Формат команд такой, рассмотрим на примере CASET:
Что означает, что начальные и конечные значения областей для каждой из команд задаются в виде 16-ти битных значений. В реальном коде по итогу получаем следующее:
/*----------------------------------------------------------------------------*/ void ST7735_SetColAddr(uint16_t cStart, uint16_t cStop) { uint8_t data[4]; data[0] = (cStart & 0xFF00) >> 8; data[1] = cStart & 0x00FF; data[2] = (cStop & 0xFF00) >> 8; data[3] = cStop & 0x00FF; ST7735_SendCommand(ST7735_CASET); ST7735_SendDataMultiple(data, 4); } /*----------------------------------------------------------------------------*/ void ST7735_SetRowAddr(uint16_t rStart, uint16_t rStop) { uint8_t data[4]; data[0] = (rStart & 0xFF00) >> 8; data[1] = rStart & 0x00FF; data[2] = (rStop & 0xFF00) >> 8; data[3] = rStop & 0x00FF; ST7735_SendCommand(ST7735_RASET); ST7735_SendDataMultiple(data, 4); } /*----------------------------------------------------------------------------*/
Отрисовка графических примитивов.
В качестве наглядного эксперимента будем выводить на дисплей основополагающую фигуру - прямоугольник. Задавать аргументами будем:
cStart
,rStart
- координата левого верхнего углаcStop
,rStop
- координата правого нижнего углаcolor
- цвет заливки
Реализация:
/*----------------------------------------------------------------------------*/ void ST7735_DrawRect(uint16_t cStart, uint16_t rStart, uint16_t cStop, uint16_t rStop, uint16_t color) { HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_RESET); ST7735_SetColAddr(cStart, cStop - 1); ST7735_SetRowAddr(rStart, rStop - 1); ST7735_SendCommand(ST7735_RAMWR); uint32_t size = (cStop - cStart) * (rStop - rStart); uint8_t colorBytes[2]; colorBytes[0] = (color & 0xFF00) >> 8; colorBytes[1] = color & 0x00FF; HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); for (uint32_t i = 0; i < size; i++) { ST7735_SendByte(colorBytes[0]); ST7735_SendByte(colorBytes[1]); } HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_SET); } /*----------------------------------------------------------------------------*/
В целом, тут ничего нового:
- Устанавливаем рабочую область при помощи
ST7735_SetColAddr()
иST7735_SetRowAddr()
- Далее та команда, о которой я упоминал -
ST7735_SendCommand(ST7735_RAMWR)
- Рассчитываем размер области в пикселях
- Разбиваем 16 бит цветовой информации на байты и выдаем на SPI для каждого пикселя. Соответственно, цвета всех пикселей у нас будут одинаковыми, заливка сплошная и однородная.
Ну и все, запускаем тестирование. Для этого будем рисовать прямоугольники размером на весь дисплей разных цветов. В main.c в цикле while(1)
добавляем (напоминаю, что полный код под спойлерами в конце статьи):
/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ // Demo 1 ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_RED); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_GREEN); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_BLUE); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_YELLOW); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_WHITE); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_BLACK); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_ORANGE); // End of demo 1 } /* USER CODE END 3 */
Само собой не забываем про инициализацию ST7735:
/* USER CODE BEGIN 2 */ ST7735_Init(); /* USER CODE END 2 */
Компилируем и прошиваем. Результат выглядит следующим образом:
Все вроде бы работает. Но ключевое слово тут "вроде бы". Хотя при первом использовании дисплея проблема может быть неочевидна. А проблема заключается в скорости отрисовки, которая может и должна быть значительно выше. Это как раз один из случаев, когда быстродействия библиотеки HAL не хватает. Поэтому отправку данных мы осуществим "вручную". На практике это означает ряд программных изменений.
Ускорение библиотеки для ST7735.
Изменяем функцию отправки данных:
/* ---------------------------------------------------------------------------*/ void ST7735_SendByte(uint8_t data) { while((SPI1->SR & SPI_SR_TXE) == RESET); SPI1->DR = data; } /* ---------------------------------------------------------------------------*/
А также добавляем функцию ожидания завершения передачи:
/* ---------------------------------------------------------------------------*/ void ST7735_WaitLastData() { while((SPI1->SR & SPI_SR_TXE) == RESET); while((SPI1->SR & SPI_SR_BSY) != RESET); } /* ---------------------------------------------------------------------------*/
В чем разница ожидания флага окончания передачи в первой функции и зачем нужна вторая? Разница в том, что флаг SPI_SR_TXE сигнализирует о том, что данные из регистра SPI1->DR ушли в сдвиговый регистр STM32, откуда они будут выданы уже наружу. А флаг SPI_SR_BSY - это уже сигнал о полном окончании процесса передачи. Иногда разницы существенной нет, но не в этом случае.
Рассматриваем вполне реальную ситуацию - происходит последовательная отправка байта команды, а затем байта данных. Процесс будет такой:
- Выставляем низкий уровень на выводе D/C
- Помещаем в регистр SPI1->DR байт команды
- Проверяем флаг SPI_SR_TXE перед передачей байта данных
- Выставляем высокий уровень на D/C
- Отправляем байт данных
Все, здесь возникает проблема - между 3-м и 4-м шагами. Поскольку мы проверили только флаг SPI_SR_TXE перед следующей передачей, значит данные ушли в сдвиговый регистр, НО(!) не факт, что были переданы дисплею. А мы уже меняем уровень на D/C.
Резюме здесь такое - в процессе отправки, например, большого кол-ва байт данных друг за другом достаточно проверять лишь флаг SPI_SR_TXE. Что оптимально с точки зрения производительности. Но по завершению отправки всех данных или после отправки команды нужно учесть и флаг SPI_SR_BSY. Что получаем в коде:
/* ---------------------------------------------------------------------------*/ void ST7735_WaitLastData() { while((SPI1->SR & SPI_SR_TXE) == RESET); while((SPI1->SR & SPI_SR_BSY) != RESET); } /* ---------------------------------------------------------------------------*/ void ST7735_SendCommand(uint8_t data) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_RESET); ST7735_SendByte(data); ST7735_WaitLastData(); } /* ---------------------------------------------------------------------------*/ void ST7735_SendData(uint8_t data) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); ST7735_SendByte(data); ST7735_WaitLastData(); } /* ---------------------------------------------------------------------------*/ void ST7735_SendDataMultiple(uint8_t *data, uint32_t num) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); for (uint32_t i = 0; i < num; i++) { ST7735_SendByte(*data); data++; } ST7735_WaitLastData(); } /* ---------------------------------------------------------------------------*/
Поскольку эти функции подразумевают некий законченный процесс транзакции, то добавляем везде вызов ST7735_WaitLastData()
. Продолжаем бороться за быструю отрисовку, добавляем в начале функции инициализации включение SPI:
SPI1->CR1 |= SPI_CR1_SPE;
Это легко упустить из виду и сложно отловить, когда ничего не работает. Остается модифицировать функцию рисования прямоугольника:
/* ---------------------------------------------------------------------------*/ void ST7735_DrawRect(uint16_t cStart, uint16_t rStart, uint16_t cStop, uint16_t rStop, uint16_t color) { HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_RESET); ST7735_SetColAddr(cStart, cStop - 1); ST7735_SetRowAddr(rStart, rStop - 1); ST7735_SendCommand(ST7735_RAMWR); uint32_t size = (cStop - cStart) * (rStop - rStart); uint8_t colorBytes[2]; colorBytes[0] = (color & 0xFF00) >> 8; colorBytes[1] = color & 0x00FF; HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); for (uint32_t i = 0; i < size; i++) { ST7735_SendByte(colorBytes[0]); ST7735_SendByte(colorBytes[1]); } ST7735_WaitLastData(); HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_SET); } /* ---------------------------------------------------------------------------*/
Запускаем тот же самый тест:
Результат налицо - не прибавить, не убавить.
Проведем еще один демо-тест для проверки работоспособности нашей координатной системы. Расположим фигуры таким образом:
Добавляем соответствующий код. Причем его уже нет смысла вызывать из цикла, поэтому сразу после инициализации дисплея вызываем:
/* USER CODE BEGIN 2 */ ST7735_Init(); // Demo 2 ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_BLACK); ST7735_DrawRect(0, 0, 35, 35, ST7735_COLOR_RED); ST7735_DrawRect(35, 35, 85, 100, ST7735_COLOR_GREEN); ST7735_DrawRect(85, 100, 128, 160, ST7735_COLOR_BLUE); // End of demo 2 /* USER CODE END 2 */
И полученный результат соответствует нашим планам:
Я изначально собирался описать еще процесс вывода изображения на этот дисплей, но и эта статья получилась гораздо длиннее, чем я планировал, и с выводом изображения есть свои нюансы, поэтому будет в ближайшие дни вторая часть, а на сегодня на этом заканчиваем 🤝
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * <h2><center>© Copyright (c) 2021 STMicroelectronics. * All rights reserved.</center></h2> * * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "st7735.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ SPI_HandleTypeDef hspi1; /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_SPI1_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ extern unsigned short testImage[]; /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); /* USER CODE BEGIN 2 */ ST7735_Init(); // Demo 2 /* ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_BLACK); ST7735_DrawRect(0, 0, 35, 35, ST7735_COLOR_RED); ST7735_DrawRect(35, 35, 85, 100, ST7735_COLOR_GREEN); ST7735_DrawRect(85, 100, 128, 160, ST7735_COLOR_BLUE); */ // End of demo 2 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ // Demo 1 ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_RED); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_GREEN); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_BLUE); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_YELLOW); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_WHITE); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_BLACK); ST7735_DrawRect(0, 0, ST7735_X_SIZE, ST7735_Y_SIZE, ST7735_COLOR_ORANGE); // End of demo 1 } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /** * @brief SPI1 Initialization Function * @param None * @retval None */ static void MX_SPI1_Init(void) { /* USER CODE BEGIN SPI1_Init 0 */ /* USER CODE END SPI1_Init 0 */ /* USER CODE BEGIN SPI1_Init 1 */ /* USER CODE END SPI1_Init 1 */ /* SPI1 parameter configuration*/ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SPI1_Init 2 */ /* USER CODE END SPI1_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET); /*Configure GPIO pin : PC11 */ GPIO_InitStruct.Pin = GPIO_PIN_11; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /*Configure GPIO pins : PB6 PB7 */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/** ****************************************************************************** * @file : st7735.c * @brief : ST7735 driver * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "st7735.h" /* Declarations and definitions ----------------------------------------------*/ extern SPI_HandleTypeDef hspi1; /* Functions -----------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ void ST7735_SendByte(uint8_t data) { // HAL_SPI_Transmit(&hspi1, &data, 1, ST7735_SPI_TIMEOUT); while((SPI1->SR & SPI_SR_TXE) == RESET); SPI1->DR = data; } /*----------------------------------------------------------------------------*/ void ST7735_WaitLastData() { while((SPI1->SR & SPI_SR_TXE) == RESET); while((SPI1->SR & SPI_SR_BSY) != RESET); } /*----------------------------------------------------------------------------*/ void ST7735_SendCommand(uint8_t data) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_RESET); ST7735_SendByte(data); ST7735_WaitLastData(); } /*----------------------------------------------------------------------------*/ void ST7735_SendData(uint8_t data) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); ST7735_SendByte(data); ST7735_WaitLastData(); } /*----------------------------------------------------------------------------*/ void ST7735_SendDataMultiple(uint8_t *data, uint32_t num) { HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); for (uint32_t i = 0; i < num; i++) { ST7735_SendByte(*data); data++; } ST7735_WaitLastData(); } /*----------------------------------------------------------------------------*/ void ST7735_Init() { SPI1->CR1 |= SPI_CR1_SPE; HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(ST7735_RESET_PORT, ST7735_RESET_PIN, GPIO_PIN_SET); HAL_Delay(5); HAL_GPIO_WritePin(ST7735_RESET_PORT, ST7735_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(5); HAL_GPIO_WritePin(ST7735_RESET_PORT, ST7735_RESET_PIN, GPIO_PIN_SET); HAL_Delay(5); ST7735_SendCommand(ST7735_SWRESET); HAL_Delay(150); ST7735_SendCommand(ST7735_SLPOUT); HAL_Delay(500); ST7735_SendCommand(ST7735_FRMCTR1); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendCommand(ST7735_FRMCTR2); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendCommand(ST7735_FRMCTR3); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendData(0x01); ST7735_SendData(0x2C); ST7735_SendData(0x2D); ST7735_SendCommand(ST7735_INVCTR); ST7735_SendData(0x07); ST7735_SendCommand(ST7735_PWCTR1); ST7735_SendData(0xA2); ST7735_SendData(0x02); ST7735_SendData(0x84); ST7735_SendCommand(ST7735_PWCTR2); ST7735_SendData(0xC5); ST7735_SendCommand(ST7735_PWCTR3); ST7735_SendData(0x0A); ST7735_SendData(0x00); ST7735_SendCommand(ST7735_PWCTR4); ST7735_SendData(0x8A); ST7735_SendData(0x2A); ST7735_SendCommand(ST7735_PWCTR5); ST7735_SendData(0x8A); ST7735_SendData(0xEE); ST7735_SendCommand(ST7735_VMCTR1); ST7735_SendData(0x0E); ST7735_SendCommand(ST7735_INVOFF); ST7735_SendCommand(ST7735_MADCTL); ST7735_SendData(0xC0); ST7735_SendCommand(ST7735_COLMOD); ST7735_SendData(0x05); ST7735_SendCommand(ST7735_GMCTRP1); ST7735_SendData(0x02); ST7735_SendData(0x1c); ST7735_SendData(0x07); ST7735_SendData(0x12); ST7735_SendData(0x37); ST7735_SendData(0x32); ST7735_SendData(0x29); ST7735_SendData(0x2d); ST7735_SendData(0x29); ST7735_SendData(0x25); ST7735_SendData(0x2B); ST7735_SendData(0x39); ST7735_SendData(0x00); ST7735_SendData(0x01); ST7735_SendData(0x03); ST7735_SendData(0x10); ST7735_SendCommand(ST7735_GMCTRN1); ST7735_SendData(0x03); ST7735_SendData(0x1d); ST7735_SendData(0x07); ST7735_SendData(0x06); ST7735_SendData(0x2E); ST7735_SendData(0x2C); ST7735_SendData(0x29); ST7735_SendData(0x2D); ST7735_SendData(0x2E); ST7735_SendData(0x2E); ST7735_SendData(0x37); ST7735_SendData(0x3F); ST7735_SendData(0x00); ST7735_SendData(0x00); ST7735_SendData(0x02); ST7735_SendData(0x10); ST7735_SendCommand(ST7735_NORON); HAL_Delay(10); ST7735_SendCommand(ST7735_DISPON); HAL_Delay(100); HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_SET); } /*----------------------------------------------------------------------------*/ void ST7735_SetColAddr(uint16_t cStart, uint16_t cStop) { uint8_t data[4]; data[0] = (cStart & 0xFF00) >> 8; data[1] = cStart & 0x00FF; data[2] = (cStop & 0xFF00) >> 8; data[3] = cStop & 0x00FF; ST7735_SendCommand(ST7735_CASET); ST7735_SendDataMultiple(data, 4); } /*----------------------------------------------------------------------------*/ void ST7735_SetRowAddr(uint16_t rStart, uint16_t rStop) { uint8_t data[4]; data[0] = (rStart & 0xFF00) >> 8; data[1] = rStart & 0x00FF; data[2] = (rStop & 0xFF00) >> 8; data[3] = rStop & 0x00FF; ST7735_SendCommand(ST7735_RASET); ST7735_SendDataMultiple(data, 4); } /*----------------------------------------------------------------------------*/ void ST7735_DrawRect(uint16_t cStart, uint16_t rStart, uint16_t cStop, uint16_t rStop, uint16_t color) { HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_RESET); ST7735_SetColAddr(cStart, cStop - 1); ST7735_SetRowAddr(rStart, rStop - 1); ST7735_SendCommand(ST7735_RAMWR); uint32_t size = (cStop - cStart) * (rStop - rStart); uint8_t colorBytes[2]; colorBytes[0] = (color & 0xFF00) >> 8; colorBytes[1] = color & 0x00FF; HAL_GPIO_WritePin(ST7735_DC_PORT, ST7735_DC_PIN, GPIO_PIN_SET); for (uint32_t i = 0; i < size; i++) { ST7735_SendByte(colorBytes[0]); ST7735_SendByte(colorBytes[1]); } ST7735_WaitLastData(); HAL_GPIO_WritePin(ST7735_CS_PORT, ST7735_CS_PIN, GPIO_PIN_SET); } /*----------------------------------------------------------------------------*/
/** ****************************************************************************** * @file : st7735.h * @brief : ST7735 driver header * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ #ifndef ST7735_H #define ST7735_H /* Includes ------------------------------------------------------------------*/ #include "stm32f1xx_hal.h" #include "stdbool.h" /* Declarations and definitions ----------------------------------------------*/ #define ST7735_COLOR_RED 0xF800 #define ST7735_COLOR_GREEN 0x07E0 #define ST7735_COLOR_BLUE 0x001F #define ST7735_COLOR_YELLOW 0xFFE0 #define ST7735_COLOR_WHITE 0xFFFF #define ST7735_COLOR_BLACK 0x0000 #define ST7735_COLOR_ORANGE 0xFA20 #define ST7735_RESET_PORT (GPIOB) #define ST7735_RESET_PIN (GPIO_PIN_7) #define ST7735_CS_PORT (GPIOC) #define ST7735_CS_PIN (GPIO_PIN_11) #define ST7735_DC_PORT (GPIOB) #define ST7735_DC_PIN (GPIO_PIN_6) #define ST7735_X_SIZE 128 #define ST7735_Y_SIZE 160 #define ST7735_NOP 0x00 #define ST7735_SWRESET 0x01 #define ST7735_RDDID 0x04 #define ST7735_RDDST 0x09 #define ST7735_SLPIN 0x10 #define ST7735_SLPOUT 0x11 #define ST7735_PTLON 0x12 #define ST7735_NORON 0x13 #define ST7735_INVOFF 0x20 #define ST7735_INVON 0x21 #define ST7735_DISPOFF 0x28 #define ST7735_DISPON 0x29 #define ST7735_CASET 0x2A #define ST7735_RASET 0x2B #define ST7735_RAMWR 0x2C #define ST7735_RAMRD 0x2E #define ST7735_PTLAR 0x30 #define ST7735_COLMOD 0x3A #define ST7735_MADCTL 0x36 #define ST7735_FRMCTR1 0xB1 #define ST7735_FRMCTR2 0xB2 #define ST7735_FRMCTR3 0xB3 #define ST7735_INVCTR 0xB4 #define ST7735_DISSET5 0xB6 #define ST7735_PWCTR1 0xC0 #define ST7735_PWCTR2 0xC1 #define ST7735_PWCTR3 0xC2 #define ST7735_PWCTR4 0xC3 #define ST7735_PWCTR5 0xC4 #define ST7735_VMCTR1 0xC5 #define ST7735_RDID1 0xDA #define ST7735_RDID2 0xDB #define ST7735_RDID3 0xDC #define ST7735_RDID4 0xDD #define ST7735_PWCTR6 0xFC #define ST7735_GMCTRP1 0xE0 #define ST7735_GMCTRN1 0xE1 #define ST7735_SPI_TIMEOUT 100 /* Functions -----------------------------------------------------------------*/ extern void ST7735_Init(); extern void ST7735_DrawRect(uint16_t cStart, uint16_t rStart, uint16_t cStop, uint16_t rStop, uint16_t color); #endif // #ifndef ST7735_H
Ссылка на проект - MT_ST7735_Example_1
Хотел использовать данный дисплей с STM32F0DISCOVERY(STM32F051R8T6), но на экране только рябь. Я понимаю что проблема в частотах тактирования, но как правильно их настроить не знаю. Максимальная частота моего контроллера 48 мГц. Если не сложно подскажите как настроить тактирование?
Для всех, кто читает эти строки 🙂 Обсуждение этого вопроса мы продолжили на форуме - https://microtechnics.ru/community/stm32/displej-st7735-podklyuchenie-k-stm32f100-stm32f072/
Кто нибудь пробовал читать RAM дисплея командой (2Eh) ?
Я как-то пробовал, ради интереса. Прочитался какой-то мусор и я не стал разбираться...
Вы читали RAM у дисплея 7735 или 7789, которые продают китайцы на алике, я правильно понял? Поскольку в китайском исполнении эти дисплеи идут с одним выводом для передачи данных, то есть там только MOSI. Или я ошибаюсь? И что нужно делать с SPI в stm32 для работы по одному проводу?
У меня на базе ST7735 был, но с широким шлейфом (на пинов 40, не помню точно), там был параллельный интерфейс выведен в том числе.
Приветствую) Ковыряю дисплей на st7735s. Содрал программу с вашего сайта, вроде даже все заработало(я использую AVR). Дисплей проинициализировался, но вот отображение цветов немного неправильное. Чтобы привести в чувство цвета, пришлось при ините выставлять инверсию и цветовой фильтр BRG. Самостоятельно не не могу понять, почему так вышло, решил спросить у вас, быть может подскажете.
Есть еще некоторые проблемы с разрешением. У меня дисплей 80*160, при установке такого разрешения изображение уплывает на 26 пикселей(они не работают) по вертикали(80). В итоге для верного отображения выставил разрешение 106*160. Не судите строго, я нуб)
Доброго дня ) Вполне может быть такое, что сам контроллер (st7735) на плате с дисплеем на разных модулях по-разному подключен физически, поэтому и отличия в работе при одинаковой инициализации.
Смещение тоже явление обычное, но тут оно слишком большое, что будет если настроить с верными значениями (80 и 160) и вывести квадрат 30х30 в (0, 0)?
Ответил на форуме)
Прикрепляю ссылку на всякий случай - форум.
И почему 26 пикселей, а не 48?
Для дисплея mini 160x80 display во всех библиотеках
#define ST7735_XSTART 26
#define ST7735_YSTART 1
или
#define ST7735_XSTART 1
#define ST7735_YSTART 26
Все в принципе работает... Может быть кто-нибудь в курсе, что это за магическое число 26? - Ну в сам деле интересно...
В оригинальном мануале от Sitronix ST7735 262K Color Single-Chip TFT Controller/Driver
я не нашел ничего в описаниях команд MADCTL, CASET, RASET, RAMWR.
Доброго времени суток!
На дисплеях 160х80 почти всегда бывает смещение по пикселям, поэтому этот оффсет программно компенсируется, скорее всего в этих дефайнах величина и задается. То есть грубо говоря для того, чтобы пиксель с координатами (0, 0) соответствовал физически углу дисплея.
Здравствуйте, Aveal.
А можно подробней про физический угол дисплея. Особенно интересно где это можно почитать.
На дисплеях 160х80 бывает все, что угодно... Однако почему такое магическое число 26?
Я может выразился не очень удачно )
Можно попробовать убрать эти оффсеты, то есть задать нули. Если после этого через библиотечные функции нарисовать квадрат, допустим, 30х30 с началом в (0, 0), то он будет отрисован не там, где ожидается, а со смещением. Сама величина смещения зависит от того, как дисплей физически на плате подключен к контроллеру (st7735 в данном случае), поэтому по большому счету, оффсет может быть разным, в зависимости от изготовителя модуля. По моим наблюдениям на большинстве модулей 160х80 действительно смещение составляет 26 пикселей. У меня раньше было очень много дисплеев 160х128, и в зависимости от партии могли быть оффсеты (1, 2), (1, 3) итп по x и y соответственно. Бывали партии, где смещения изначально вообще не было.
То есть это просто технологическая фантазия производителя и ни к чему этим грузиться. Спасибо.
В целом да, именно так.
Во многих библах для ардуино сии смещения есть. У меня попался совсем кривой. Ох и заставил же он меня выстрадать все, что на этой картинке нарисовано и уже давно известно..
Да, дисплеи всякие попадаются...