STM32 и LCD, бързо запълване на екрана
В момента са широко разпространени различни дисплеи с течни кристали, които са идеално свързани с контролерите от семейството STM32. Тази статия ще се съсредоточи върху един от често срещаните контролери STM32F103C8T6 и 7" дисплей на контролера SSD1963. И двата са лесно достъпни като пълни възли на Aliexpress и са сравнително евтини. Разбира се, всичко обсъдено по-долу е вярно за други дисплеи с паралелен интерфейс и повечето STM32 контролери. Ето как изглеждат свързаните устройства:
Накратко за връзката
Свързването на дисплея се състои в подаване на 3,3 и 5 волта към желаните изходи и свързване на информационните линии към контролера. Контролните сигнали D/C, WE, RST ще бъдат свързани към свободни I/O линии на процесора. В нашия случай това са D / C - PA1, WE - PA8, RST - PA2. Сигналите RD и CS могат да бъдат пропуснати, докато към RD трябва да се приложи логическа единица, т.е. свържете чрез резистор (в този случай 4,7 KΩ) към +3,3 V, а към CS - "0", т.е. свържете към земята.
Дисплеят е конфигуриран от производителя да работи с интерфейса в режим 8080 и според документацията сигналът CS "проба на чип" трябва да бъде активиран:
Първоначално той работеше така. Въпреки това, както показа тестът, ако не искате да използвате шината за данни за други цели, тя не е необходима.
След това трябва да свържете шината за данни. За този дисплей се предполага, че е 16-битов, но можете да изберете 8-битов и 9-битов режим на работа по време на инициализацията. Тоест, трябва да свържете поне линиите на дисплея DB0-DB7, като максимум също DB8-DB15. За удобство на програмирането и минимизиране на командите за преобразуване на данни е по-добре да ги пренесете в една I / O група. Имайки предвид 16-битовата версияшини за данни, тогава не е нужно да избирате на този микроконтролер - само PB0 - PB15.
Свързваме ги съответно с дисплей DB0-DB15:
Има много несвързани контакти на гребена на дисплея, нека това не ви притеснява. Има слот за SD карта памет, сензор за екрана, има дори окабеляване за чип памет EEPROM, но самият той липсва. Тези устройства заемат останалата част от конектора. Между другото, PATA кабелът на твърдите дискове на компютъра е идеален за 40-пинов конектор на дисплея.
Инициализация на дисплея
Оригиналният код е преместен в проекта почти непроменен, добавена е само условна компилация за избор на битовата ширина на шината за данни (инициализацията и командите преминават през 8-битова шина, независимо от този режим).
Кодът не инициализира входно-изходните портове и системния таймер, въз основа на които се реализират забавяния от милисекунди (delay_ms()).
След извършване на инициализацията:
Запълване на дисплея
Сега искам да изтрия този боклук и да запълня екрана с малко цвят. Изходният код от производителя съдържа необходимия материал за писане на код. Нека го използваме.
Както можете да видите, кодът зависи от избраната ширина на шината. Съответно зависи и времето, необходимо за завършване на прехвърлянето на пиксел към дисплея. За 16-битова шина един пиксел се прехвърля в един цикъл на пренос на шина за данни, за 9-битова шина са необходими два, за 8-битова шина са необходими 3. Откъде идват тези данни? От документацията за SSD1963.
В таблицата можете да намерите местоположението на всеки цветови компонент на пиксела в зависимост от режима. Проектът използва 8-битови, 9-битови и 16-битови режими (565 формат). Както можете да видите, може да се използва и "чист" 16-битов формат за по-точно цветно кодиране, но също такаизисква три автобусни трансфера. Не можем да използваме 18 и 24 битови формати поради наличието само на 16-битова шина на изхода на дисплея.
И така, колко бързо можем да запълним дисплея на 72 MHz процесор?
176ms - 16-битова шина 374ms - 9-битова шина 470ms - 8-битова шина
Не много бързо, разбира се, но може да е достатъчно за показване на бавно променяща се информация. Разбира се, 16-битовата шина изглежда по-привлекателна и може би ще подхожда на някого, но заема твърде много I / O портове, които след това може да не са достатъчни за свързване на други устройства към процесора.
Нека се опитаме да разгледаме компромисна опция - 9 бита, като печалба от почти 0,1 s от 8-битовата версия поради само един допълнителен I / O порт.
Оптимизация на скоростта
Нека се опитаме да ускорим процеса на изливане на дисплея. Ами ако намалим броя на логическите операции вътре в цикъла?
Променихме цветовото кодиране вместо 16-битова променлива във формат RGB565, използваме 32-битова, използвайки само 18 от тях във формат RGB666. В допълнение беше въведена временна променлива за съхраняване на стойността на регистъра LCD_DATA_PORT->ODR по време на два цикъла на извеждане на 9-битови данни към шината. Тук е необходимо да се направи резервация, че това не винаги е възможно, т.к. по време на изходното време състоянието на други портове от GPIO група B, конфигурирани за изход, може да се промени в този момент на прекъсването и програмата няма да работи правилно. В нашия случай обаче няма такива проблеми и проверяваме какво сме постигнали. И така, след първата оптимизация, екранът се запълва в 9-битов режим за 298 ms. Ако не използвате променливата и работите с текущото състояние на порта, тогава също има увеличение на скоростта, макар и не толковазначимо - 335 ms:
Можете също така да пожертвате възможността да използвате останалите портове от група B в изходен режим в името на скоростта и да премахнете логическите операции, свързани със запазването на тяхното състояние:
Ясно е, че режимът на въвеждане и алтернативните функции ще продължат да се използват, те са ODR без значение за малки и големи букви. Това ще даде малко повече скорост, до 246ms.
Следващата стъпка е да пренесете основния цикъл на итерация на пиксела във функция с едно ниво по-дълбоко и да се опитате да направите софтуерна версия на емулация на DMA канала, директен достъп до паметта. За да направим това, трябва да преместим контролната линия WE на дисплея в групата, където се намира шината за данни, т.е. GPIO B. Нека бъде PB9.
Както можете да видите от кода, ние последователно записваме 4 опции за данни в портова група B, където в допълнение към 9-битовата шина за данни се намира и WE сигналът. Операцията "0x0200" просто задава този сигнал. Този код дава голямо увеличение до 85 ms и ако замените дефиницията на масива "static uint32_t dp[4]" със "static uint16_t dp[4]", след това до 75 ms. За проверка беше измерен вариант с включване на режим DMA и същото прехвърляне на съдържанието на 4 клетки към I / O порта. Резултатът е само 230 ms. Защо DMA е по-бавен? Това е просто, в програмен режим компилаторът оптимизира кода и всичките 4 стойности се поставят в регистрите на процесора, а не в паметта, а извличането на паметта, което се извършва от DMA контролера, е много по-бавно от работата с регистри. Компилираният основен цикъл изглежда така:
08000265: ldr r3, [pc, #24] ; (0x8000280 ) 08000267: str r6, [r3, #12] 08000269: str r5, [r3, #12] 0800026b: str r4, [r3, #12] 0800026d: str r1, [r3, #12] 0800026 f: субтитри r2, #1 08000271: bne.n 0x8000266 Товавариант, както и във варианта с DMA канал, остава ограничението за използване на портове PB10-PB15. Можете обаче да им извеждате RST и D/C сигнали на дисплея и да ги вземете предвид в цикъла, тогава ще има по-малко ограничения.
Така постигнахме максимална скорост за запълване на целия екран или правоъгълна област с един цвят. Изглежда, че това е границата, но можете да въведете друго ограничение и да продължите малко напред.
Ето ги на екрана:
Ето селекция от тях, 100 броя, по-ясно:
Какво ни дава използването само на тези цветове? Да, за да запълним областта, не е необходимо да променяме състоянието на шината за данни в процеса на запълване. Достатъчно е да превключим състоянието на сигнала WE и да преброим колко пъти сме го направили. Освен това можете да обръщате WE толкова дълго, колкото желаете, основното е не по-малко от необходимото за запълване на зоната. Лесно е да се изчисли, че веднъж на пиксел трябва да прехвърлим два блока данни по шината, след което са необходими 2 потвърждения от WE сигнала. Съответно, целият екран се нуждае от (Screen_width*Screen_length*2) импулси или 800*480*2=768000.
Колко лесно е да генерираш импулси. Със сигурност! Можете да използвате таймер. TIM1 в този контролер е по-бърз от таймерите TIM2-TIM4, защото разположен на по-бързата тактова шина APB2. Проучванията показват, че като включите таймера в режим на PWM генератор с минимален делител, можете да получите време за запълване от 32 ms! Ясно е, че сигналът WE трябва да бъде премахнат от изхода на таймера, например PA8 (TIM1_CH1).
Възможно ли е да се увеличи още повече скоростта на пълнене? Оказа се, че да, само чрез прилагане на сигнала SYSCLK от изхода RCC_MCO към входа WE LCD. Това е максималната налична честота на процесора, 72 MHz. Времето за запълване на дисплея със симетричен цвят е 10,7 ms. Времето се отброява от таймера, след коетопри прекъсване сигналът се премахва и портът преминава в изходен режим.
Таймерът отчита времето с точност от 1/72 µs за броя на точките под 32000 и с точност от 1 µs за повече точки. Това се дължи на битовата дължина на брояча на таймера. Като се има предвид, че отнема известно време за обработка на прекъсването, когато таймерът е изключен, сигналът на изхода на MCO се премахва малко по-късно от необходимото, с малък запас. Експериментално е установено, че това са около 10-11 цикъла на честотата на процесора. По този начин можем да кажем, че има праг за използване на тази техника, при който тя остава по-бърза, въпреки режийните разходи за инициализиране на таймера и RCC_MCO и изключване. Квадрат 2x2 пиксел вероятно е по-изгодно за програмно попълване на цикъл.
Като заключение можем да кажем, че с добавянето на някои ограничения, времето за запълване на екрана беше намалено от 375 на 11 ms. Освен това пълненето става без участието на процесора, който в този момент може да изпълнява други задачи.
Ще се радвам на забележки и допълнения.
Hardcore conf в C++. Каним само професионалисти.