Вывод изображения на цветной дисплей ST7735


• О проекте
• Обратная связь
• Полезные ссылки
• Полезные программы
• Друзья сайта


Последние комментарии

Аби: Подключение микроконтроллеров к шине RS-485
написано просто о...

Анатолий: Джойстик для денди на stm32
Автору 100500 рес...




           

Библиотека для AVR





AXLIB Генератор





Помощь сайту


				

Вывод изображения на цветной дисплей ST7735

	
	
	

Дата: 17 Августа 2015. Автор: Алексей

	
	
Здравствуйте. Сегодня я хочу с Вами поделиться своими наработками с TFT дисплеем на базе контроллера ST7735. Данный дисплей можно купить практически где угодно.

TFT Дисплей ST7735

Для его управления необходима шина SPI и три вывода CS, A0 и RES. Управляющий МК я взял ST32F407VGT. Данный МК установлен на плату Discovery4. Программа написана под IAR 7.40, а для генерации проекта был использован ST32CubeMX.
Железо.
МК TFT
PA1 CS
PA2 A0
PA3 RES
PA5 SCK
PA7 SDA

Программная часть.
Вот тут начинается самое веселье. Библиотеку для работы с этим дисплеем я не стал сочинять, а решил взять из сети. Самая, на мой взгляд, интересная оказалась здесь. Написана она была правда под CooCox. По началу я ее запустил из под CooCox и наигравшись решил перенести на IAR. Больше всего меня порадовала данная библиотека тем, что она может выводить буковки как латинские так и русские без каких-либо конверторов. Кто работал со знакосинтезирующими ЖК типа 16х2 меня поймут. Ну сказано, сделано. На деле оказалось все совсем не комильфо.

Засада первая.
ST32CubeMX – эта зараза при генерации проекта создает функции инициализации периферии не в отдельных файлах, а прям в основном main.(Изучая IAR я нашел как создавать проект с раздельными файлами для периферии. В окне генератора кода заходим во вкладку Code Generator и ставим галку слева надписи Generate peripheral initalization as a pair of .c/.h files per IP. Теперь генератор кода создаст проект с разделенными файлами инициализации периферии. И для того чтобы работать с периферией в своих файлах нужно фсего навсего проинклюдеть нужный файл с необходимой периферией.). Вроде бы оно ничего, но при подключении внешних заголовочных файлов с функциями для которых нужна периферия просто не работают. Это связано с тем, что заголовочные файлы подключаются к проекту до вызова инициализационных функций периферии. Что делать? А делать остается только одно. Выдираем необходимые данные из этих функций и переносим в библиотеку для TFT дисплея. Вот что должно получиться.

SPI_HandleTypeDef hspi1;

// Инициализация
void lcd_st7735_init(void) 
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_1LINE;
    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_2;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
    hspi1.Init.CRCPolynomial = 10;
    HAL_SPI_Init(&hspi1);
    
    // CS=0  - начали сеанс работы с дисплеем
    LCD_ST7735_CS_0;    
    
    // аппаратный сброс дисплея
    LCD_ST7735_RES_0;           // RST=0
    HAL_Delay(LCD_DELAY);       // пауза
    LCD_ST7735_RES_1;           // RST=1
    HAL_Delay(LCD_DELAY);       // пауза

    // инициализация дисплея
    lcd_st7735_send_cmd(0x11);  // после сброса дисплей спит - даем команду проснуться
    HAL_Delay(LCD_DELAY);       // пауза
    lcd_st7735_send_cmd(0x3A);  // режим цвета:
    lcd_st7735_send_data(0x05); //16 бит
    lcd_st7735_send_cmd(0x36);  // направление вывода изображения:   
    lcd_st7735_send_data(0x14); // снизу вверх, справа на лево, порядок цветов RGB
    lcd_st7735_send_cmd(0x29);  // включаем изображение
    LCD_ST7735_CS_1;
}


Теперь после вызова данной функции, будет проинициализирован SPI MASTER полудуплекс и проинициализирован сам TFT дисплей. Функции передачи команды и данных я не стал переделывать, а перенес их как есть.

Засада вторая.


// Функция заполнения прямоугольной области экрана заданным цветом
void lcd_st7735_fillrect(uint8_t startX, uint8_t startY, uint8_t stopX, uint8_t
stopY, uint16_t color);


Изначально я предполагаю что люди писавшие эту библу были адекватными и нулевую точку оси координат выбрали левый нижний угол. Я этому следовал, так как функция отрисовки пикселя по координатам и текст выводились как часы. Но что-то видимо пошло не так и в данной функции, которая должна отрисовывать прямоугольник, почему-то координата диагонали указана шиворот на выворот. То есть X перепутан с Y. На данный момент я на это забил, но чуть позже переделаю. Та же участь постигла функцию.


// Рисование прямоугольника (не заполненного)
void lcd_st7735_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint16_t color);


Это происходит из-за того что последняя функция является производной от предыдущей.

Засада третья.

Функция отрисовки прямой. Вот тут я намучался по самый не балуй. Мало того что данная функция имела ту же проблему с координатной сеткой, так она еще себя вела немного не адекватно. Линии нарисованные до 150 пикселя по горизонту отражались нормально, но стоило перейти за черту 150-го пикселя, начинался полный хаос. Линия рисовалось произвольной длинны, произвольного направления. Так же на поле появлялись различные артефакты в виде хаотичных пикселей. По началу я думал, что это слишком большая скорость SPI (о чем предупреждал автор), но менял скорость было все тщетно. В итоге я не стал разбираться с функцией, а просто ее удалил. В замен я написал свою с использованием классического алгоритма Брезенхема и все пошло как по маслу.
Функции вывода символа и текста работали хорошо, от чего я их менять не стал, а лишь перенес как есть. После чего немного подумав, я добавил еще пару функций для текста.
Первая функция


// Вывод строки в цвете по строке
void lcd_st7735_putstr_xy(uint8_t x, uint8_t y, const uint8_t str[], uint16_t
charColor, uint16_t bkgColor);

Данная функция выводит переданную ей строку по координатам X номер адреса символа от 0 до 19, а Y номер адреса по сроке от 0 до 9 (снизу вверх). То есть данная функция чем-то напоминает функции вывода строки по координатам в знакосинтезирующих ЖК типа 16х2.
Вторая функция


// Форматирование строки
// uint8_t *str массив для строки
// int dig число для вывода
// uint8_t rd количество разрядов в числе. 
// (массив должен иметь на 2 элемента больше чем количество разрядов в числе)
void lcd_format_int(uint8_t *str, int dig, uint8_t rd);


Данная функция должна переводить число из int в char массив. То есть из числа сделать строку. Заморочился я этим из-за того что библиотека stdio.h отжирает очень много памяти, но как оказалось моя функция сократила места не совсем много. Хотя если форматировать float , то я думаю что выйграл больше памяти чем stdio.h. Короче это философский вопрос и каждый должен сам решить что ему выгоднее.
А теперь самое сладкое. К данной библиотеке я добавил функцию вывода картинок.


// Вывод картинки из массива
void lcd_st7735_img(uint8_t x, uint8_t y, uint16_t w, uint16_t h, const uint16_t *pix);


Функция получает координату левого нижнего угла выводимой картинки, высоту, ширину и массив с данными. А теперь вся история создания данной функции. Изначально я думал пихать JPG картинки на SD карту, а потом выводить на экран. В процессе изучения кодирования изображения я понял что даже самое низкое качество JPG изображения больше по памяти чем растровый BMP. JPG хорош для дисплеев мелкозернистых и с большим разрешением, а для таких клопиков как ST7735 с разрешением 160х128 достаточно BMP. Далее было принято решение написать функцию которая бы декодировала BMP изображения из 24 битного формата в 16 битный и выводила на дисплей. Изучив внутренности формата BMP я столкнулся с такой идиллией. Изображение в формате BMP с разрешением 160х128 весит 60 Кб, а если 160 умножить на 128 да на 2 байта на пиксель, то получается уже 40 Кб. 20 Кб отнимать у МК это кощунство. От сюда было принято решение, быть 16 битному массиву. Ага… Ну картинка 5х5 это 25 элементов массива. Набить ручками можно. 10х10 это 100 элементов. Если с пивом, то и это можно преодолеть. А если 160х128, то это уже 20480 элементов. Тут даже коньяк не поможет. Поразмыслив и решив что когда-то я доберусь до дисплеев с разрешением в 320х240 пикселей. Я перемножил и упал в осадок. Не… Уж пусть этим занимается ПК. Где такую взять? Ну конечно же в ентернете. Ага, ща. Перекопал всю сеть. Ничего подобного нет. Все наоборот из массива хотят BMP собрать. Ну да и фик с ним. Что я не джедай что ли. Берем VisualStudio 2010 и…
Программа называется ConvertBMP. Конвертирует 24 битное BMP изображения с максимальным разрешением 320х240 пикселей в одномерный 16 битный массив. Данные цвета в массиве уже переконвертированны из 24 битного в 16 битный.

ConvertBMP

На самом верху, как можно догадаться, выводится текущее изображение, которое нужно перебрать в массив. Ниже выводятся параметры изображения. Слева разрешение, а справа размер в байтах самого фала BMP. По нему можно определить на сколько отличается размер картинки от размера массива. Например, картинка 320х240 весит 230 Кб, а массив с той же картинкой 150 Кб. Я думаю, 80 Кб на дороге не валяются. Ниже выводится адрес размещения файла изображения на диске. Это нужно чисто для эстетики, дабы знать где лежит картинка. Ниже этажом выбор направления массива. Здесь давайте по подробнее. При написании программы я использовал библиотеки Microsoft это не секрет, а вот то что выборку пикселей по координатам идет от верхнего левого угла меня немного удивило. Я-то привык, как в меня в школе учили, что оси координат начинаются с левого нижнего угла, а тут. Короче я плюнул на это и начал отлаживать программу по микрософтовской координатной сетке. Когда программа была закончена, мне нужно было изменит циклы сборки массива. Но посмотрев уже на проделанный труд я решил не ломать старое, а добавить второй вариант. Теперь если выбрать прямой вариант, то массив будет собираться от верхнего левого угла и направо до конца строки. Затем переходит на следующую строку и снова слева направо. Ну как мы пишем на листе. А вот обратное направление, это когда массив собирается начиная от нижнего левого угла и направо до конца строки, затем переходит на строку выше и снова слева на право. Такой массив идеально подходит для вывода картинки на дисплей ST7735 160х128. Ну и наконец внизу расположены две кнопки. Одна для выбора картинки с диска, а вторая запускает конвертацию. После конвертации в папке img, расположенной рядом с программой, создастся заголовочный файл img.h. Что в нем внутри


// Файл img.h создан с помощью программы конвертатор ConvertBMP
// Массив для вывода изображения на дисплей. Передача цвета RGB 16 бит 5-6-5
// Разрешение изображения 10x10, размер массива 200 байт
// Адрес изображения C:\Users\Alex\Desktop\Конвертер картинокp\icture\10x10.bmp
// Конвертация обратная

#ifndef IMG_H
#define IMG_H


uint16_t width  = 10; 	// Ширина изображения
uint16_t height = 10; 	// Высота изображения

const uint16_t img[] = {
	0xFFFF, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1,
	0xFFFF, 0xFFFF, 0x8CA1, 0x8CA1, 0x1686, 0x8CA1, 0x8CA1, 0x8CA1, 0xFFFF, 0xFFFF,
	0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x1310, 0x1310, 0xA0B4, 0xFFFF, 0xFFFF,
	0xA0B4, 0x1686, 0x8CA1, 0x8CA1, 0x8CA1, 0x1686, 0x1310, 0xA0B4, 0x1310, 0xFFFF,
	0x1686, 0x1686, 0x1686, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0xA0B4, 0xA0B4, 0x1310,
	0xA0B4, 0x1686, 0x1686, 0xA0B4, 0x1686, 0x8CA1, 0x8CA1, 0x8CA1, 0xA0B4, 0x1310,
	0x1310, 0x1686, 0x1686, 0x1686, 0x1686, 0xFFFF, 0x1686, 0x8CA1, 0x8CA1, 0x1310,
	0x1686, 0x1686, 0x1686, 0x1686, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x1310, 0x8CA1,
	0xA0B4, 0x8CA1, 0x8CA1, 0x8CA1, 0x1686, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1, 0x8CA1,
	0x1310, 0x1310, 0x8CA1, 0x8CA1, 0xFFFF, 0x1310, 0x1310, 0x1310, 0x1310, 0x1310
};
#endif /* IMG_H */


Для теста я создал картинку 10х10 пикселей и сконвертировал в обратный массив. Что записалось в файл. Первая строка это вода для хохмы. Вторая строка повествует о типе созданного массива. В ней говорится что массив содержит данные для каждого пикселя в 16 битном формате RGB 5 бит для красного цвета, 6 бит для зеленого и 5 бит для синего. Следующая строка содержит техническую информацию. Разрешение изображения, в данный момент 10х10, и вес массива. Заметьте именно массива. То есть значение указывает на количество байт или килобайт в массиве. Как раз этот размер можно сравнить с размером файла BMP и убедиться в целесообразности хранения именно массива, а не файла. (Если конечно Вы не используете SD карту с файловой системой). Следующая строка, говорит где лежит BMP файл который конвертировали. Это нужно чтобы не вспоминать что за картинка в массиве, а достаточно пройти по адресу и увидеть в живую изображение. Я рекомендую сохранять картинки в папке рядом с программой и тогда их можно будет всегда найти. Следующая строка указывает на сборку массива. Прямая или обратная. Далее идут две переменные ширины и высоты. Вот именно эти переменные и нужно передавать функции при выводе картинки на дисплей. А за переменными уже идет сам массив. Теперь от слов к делу. Запускаем STM32CubeMX и создаем новый проект с МК STM32F407VGT. Далее выбираем SPI1 в режиме мастер полудуплекс и RCC работа от внешнего кварца, так как на дискавери4 стоит на 8 МГц. Также настраиваем ножки PA1, PA2, PA3 на выход.

STM32CubeMX

Далее переходим во вкладку настройки частоты. Здесь нам нужно записать 8 в частоту кварца, подключить его к HSE, подключить PLLCLK, а в HCLK записать 168. Будем гонять камень на максимуме. )))

STM32CubeMX

Далее нажимаем на шестеренку сверху и обозвав проект, запускаем генератор.

STM32CubeMX

Все, проект собран. Теперь нам нужно прикрутить нашу картинку. Для этого топаем в корень нашего проекта и создаем в нем две папки st7735 и img.

IAR

Судя из названия в первую кладем файлы библы для дисплея, а во вторую заголовочный файл с массивом картинки. Я для теста выбрал вот такую картинку.

Синичка

Теперь нужно подключить эти файлы к проекту. Для этого нажимаем правой кнопкой на заголовке проекта и выбираем Options…

IAR

В открывшемся окне переходим в пункт C/C++ Compiler, а там во вкладку Preprocessor и жмем на кнопочку добавления файлов к проекту. (Я в IAR не силен, так что я могу что-то не так называть)

IAR

И добавляем наши папочки.

IAR

Так как у нас файлы лежат в корне проекта, то правим адресацию как на картинке ниже.

IAR

Все, жмем Ок. Теперь прикручиваем наши файлы к проекту.

IAR

А вот здесь начинается жопа. Я подробно расскажу как эту жопу обойти. Если Вы не хотите заморачиваться и взяли готовый проект, то эту часть можно пропустить. Если же у Вас появится желание переделать данный пример под другой МК с использованием SPI2 например, то я дальше пошагово объясню что куда переносить для нормальной работы. И так поехали. Вот мы и дошли до первой засады. Инициализация периферии. Если сейчас попытаться собрать проект и запихнуть в МК, то вывалится куча ошибок по SPI. А вот почему.


// Отправка данных/команд на дисплей
void lcd_st7735_send(uint8_t data) ;
// Отправка команды на дисплей с ожиданием конца передачи
void lcd_st7735_send_cmd(uint8_t cmd);


Вот эти две функции отправляют данные и команды дисплею по шине SPI. А засада кроется в том что эти функции инициализируются до инициализации функции SPI. Вообще-то это дико, так как сначала в теле программы вызывается функция void MX_SPI1_Init(void); а за ней уже дисплей, но все равно пока я не перенес настройку SPI, функция инициализации не заработала. Поэтому переносим строку SPI_HandleTypeDef hspi1; в файл st7735.c в самый верх после инклюдов, а пачку команд инициализации SPI из функции void MX_SPI1_Init(void); переносим в функцию void lcd_st7735_init(void); Должно получится так.


// Инициализация
void lcd_st7735_init(void) 
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_1LINE;
    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_2;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
    hspi1.Init.CRCPolynomial = 10;
    HAL_SPI_Init(&hspi1);


Теперь все что относится к инициализации SPI в основном файле программы удаляем. Далее все что нам нужно написать, это две строки.

IAR

Все. Теперь заливаем проект в МК и видим выведенную картинку.

Вывод картинки на дисплей

Загрузки.

Программа ConvertBMP
Проект для IAR 7.40

Если программа не хочет запускаться, то необходимо установить Microsoft .NET Framework 4. Скачать его можно на официальном сайте Microsoft по этой ссылке.




Max    07.10.16 00:37

Спасибо за рабочий пример!

Voron_kor    24.11.17 22:25

Спасибо , а как боротся с полосой пикселей ? Проверил экран через arduino ide , там в 2-х строчках выбирается дисплей , если стоит black , то есть полоса , если green , то нет(цвет лычки плёнки на экране).

Алексей    25.11.17 00:17

Я понятия не имею как работать с этим дисплеем в среде arduino ide. Лучше спросить у автора этих функций. Да, не хотел пачкать дисплей, пока не установил в устройство. Вот пленка и висит.

Максим    07.04.18 20:58

Спасибо за инициализацию дисплея и за программу конвертации

say    26.04.18 21:35

Здравствуйте, подскажите как убрать так называемое звёздное небо на дисплее, при инициализации?

Алексей    27.04.18 00:27

Никак. Это связано с тем, что после инициализации, дисплей выводит мусор из видеопамяти. Побороть это можно только залитием всего экрана одним цветом после инициализации.

say    11.07.18 09:44

Здравствуйте, какое энергопотребление дисплея?

Алексей    11.07.18 14:53

Не измерял.

Александр    19.05.19 16:24

Ребята, может у кого есть рабочая библиотека для ILI9341 SPI. Уж очень охота пользовать дисплей 3.2 "

Алексей    19.05.19 22:53

Его лучше по параллельной шине подключать. SPI медленная для такого разрешения.

Александр    21.05.19 16:22

Нужна хоть какая библиотека, чтобы попробовать в протеус, хотя заказал spi

Алексей    22.05.19 11:17

Так у ардуины же есть. Для попробовать за глаза.

Александр    22.05.19 18:31

Тогда надо каким то образом из ардуиновской библиотеки UTFT вытянуть код

Алексей    22.05.19 18:46

Ну это не сложно. Открываешь файл и смотришь что там написано)) Так часто делаю когда влом самому маны читать.




Чтобы вставить ссылку используйте форму вида[url]http://www.адрес.ru[/url][text]текст ссылки[/text]
Чтобы вставить код используйте форму вида[code]код[/code]

Имя:   





  







Рейтинг@Mail.ru Яндекс.Метрика