STM32 та LCD, швидке заливання екрану
В даний час набули поширення різні рідкокристалічні дисплеї, які відмінно підключаються до контролерів сімейства STM32. У даній статті мова піде про одного з поширених контролерів STM32F103C8T6 та дисплеї 7" на контролері SSD1963. Обидва у вигляді закінчених вузлів легко доступні на Aliexpress і відносно недорого стоять. Звичайно, все розглянуте нижче справедливо і для інших дисплеїв з паралельним інтерфейсом і більшості контролер Ось так виглядають пристрої, що з'єднуються:


Коротко про підключення
Підключення дисплея полягає в подачі живлення 3.3 та 5 вольт на потрібні висновки та з'єднання інформаційних ліній з контролером. Керуючі сигнали D/C, WE, RST підключаться до вільних ліній введення-виведення на процесорі. У нашому випадку це D/C – PA1, WE – PA8, RST – PA2. Сигнали RD і CS можна використовувати, у своїй на RD треба подати логічну одиницю, тобто. підключити через резистор (у разі 4,7 КОм) на +3.3 У, але в CS — «0», тобто. підключити на грішну землю.
Дисплей налаштований виробником для роботи інтерфейсу в режимі 8080, і, згідно з документацією, сигнал CS «вибірка кристала» повинен бути задіяний:

Спочатку він так і працював. Однак, як показала перевірка, якщо ви не хочете використовувати шину даних для інших цілей, вона не потрібна.
Далі необхідно підключити шину даних. У даного дисплея вона передбачається 16-розрядна, але можна при ініціалізації вибрати 8- та 9-бітні режими роботи. Тобто потрібно підключити щонайменше лінії дисплея DB0-DB7, як максимум ще й DB8-DB15. Для зручності програмування та мінімізації команд перетворення даних краще завести їх на одну групу вводу-виводу. Якщо розглядати варіант 16-розряднийшини даних, вибирати на даному мікроконтролері не доводиться - тільки PB0 - PB15.
З'єднуємо їх відповідно до DB0-DB15 дисплея:
На гребінці дисплея залишається багато непідключених контактів, нехай вас це не бентежить. На ньому є слот SD карт пам'яті, сенсор екрану, навіть є розведення під мікросхему EEPROM пам'яті, але вона відсутня. Ці пристрої займають решту роз'єму. До речі, під 40-контактний роз'єм дисплея ідеально підходить шлейф PATA жорстких дисків комп'ютера.

Ініціалізація дисплея
Оригінальний код майже без змін перенесено до проекту, додано лише умовну компіляцію для вибору розрядності шини даних (ініціалізація та команди йдуть по 8-бітній шині, незалежно від цього режиму).
У коді немає ініціалізації портів введення-виведення та системного таймера, на основі якого реалізуються мілісекундні затримки (delay_ms()).
Після виконання ініціалізації:

Заливка дисплея
Тепер хочеться стерти це сміття і залити екран будь-яким кольором. У вихіднику від виробника необхідний матеріал для написання коду є. Скористаємося ним.
Як бачимо, код залежить від обраної розрядності шини. Відповідно залежить і час, необхідне виконання передачі пікселя на дисплей. Для 16-розрядної шини піксель передається за один цикл передачі по шині даних, для 9-розрядної – за два, для 8-розрядної – за 3. Звідки ці дані? З документації на SSD1963.

У таблиці можна знайти розташування кожної складової кольору пікселя залежно від режиму. У проекті використовуються режими 8 біт, 9 біт та 16 біт (565 формат). Як бачите, можна було також використовувати «чистий» формат 16 біт для більш точного кодування кольору, але він такожвимагає трьох циклів передачі по шині. Формати 18 і 24 біт ми не можемо задіяти через наявність тільки 16-бітної шини на виході дисплея.
Отже, із якою швидкістю ми зможемо заповнити дисплей на процесорі з тактовою частотою 72 МГц?
176 мс - 16-розрядна шина 374 мс - 9-розрядна шина 470 мс - 8-розрядна шина
Не дуже швидко, звичайно, але для відображення інформації, що повільно змінюється, може бути достатньо. Більш привабливо виглядає звичайно 16-розрядна шина, і можливо комусь вона і підійде, але вона займає дуже багато портів введення-виводу, яких може потім не вистачити для підключення інших пристроїв до процесора.
Спробуємо розглянути компромісний варіант - 9 біт, як виграє майже 0.1 с у 8-бітного варіанта за рахунок всього одного додаткового порту введення-виводу.

Оптимізація за швидкістю
Спробуймо прискорити процес заливки дисплея. Що, якщо скоротити кількість логічних операцій усередині циклу?
Змінили кодування кольору замість 16-бітної змінної у форматі RGB565 використовуємо 32-бітове, задіявши лише 18 з них у форматі RGB666. Крім того, ввели тимчасову змінну для зберігання значення регістра LCD_DATA_PORT-ODR під час двох циклів виведення 9-бітних даних на шину. Тут треба зробити застереження, що це завжди можливо, т.к. за час виведення стан інших портів групи GPIO B, налаштованих на виведення, може бути змінено в цей час у перериванні і програма працюватиме неправильно. Однак у нашому випадку таких проблем немає і ми перевіряємо, чого ми досягли. Отже, після першої оптимізації екран заповнюється в 9-бітному режимі за 298 мс. Якщо змінну не використовувати, і працювати з поточним станом порту, то приріст швидкості теж є, хоч і не такийзначний - 335 мс:
Можна також заради швидкості пожертвувати можливістю використання портів групи B, що залишилися, в режимі виведення і прибрати логічні операції, що стосуються збереження їх стану:
Зрозуміло, що в режимі введення та в альтернативних функціях можливість використання збережеться, вони не залежать від регістра ODR. Це дасть ще деяке прискорення, до 246 мс.

Наступним етапом винесемо основний цикл перебору пікселів у функцію до рівня глибше і спробуємо зробити програмний варіант емуляції роботи каналу DMA, прямого доступу до пам'яті. Для цього нам треба перенести лінію керування WE дисплея до групи, де розташована шина даних, тобто. GPIO B. Нехай це буде PB9.
Як видно з коду, ми послідовно записуємо 4 варіанти даних у групу портів B, де крім 9-бітної шини даних розташований сигнал WE. Операція "0x0200" - це саме виставлення цього сигналу. Такий код дає чудовий приріст до 85 мс, а якщо замінити визначення масиву "static uint32_t dp [4]" на "static uint16_t dp [4]", то і до 75 мс. Для перевірки був виміряний варіант із включенням режиму DMA і такою самою передачею вмісту 4-х осередків у порт вводу-виводу. Результат лише 230 мс. Чому DMA повільніший? Все просто, в програмному режимі компілятор оптимізує код і всі чотири значення розміщуються в регістрах процесора, а не в пам'яті, а вибірка з пам'яті, яка виконується контролером DMA, йде значно повільніше, ніж робота з регістрами. Скомпільований основний цикл виглядає так:
08000265: ldr r3, [pc, # 24]; (0x8000280) 08000267: str r6, [r3, #12] 08000269: str r5, [r3, #12] 0800026b: str r4, [r3, #12] 0800 str r1, [r3, #12] 0800026f: subs r2, #1 08000271: bne.n 0x8000266 У цьомуваріанті, а також у варіанті з каналом DMA залишається обмеження використання портів PB10-PB15. Однак на них можна вивести сигнали дисплея RST та D/C та врахувати їх у циклі, тоді обмежень будемо менше.
Таким чином, ми досягли максимальної швидкості заповнення повністю екрана або прямокутної області одним кольором. Начебто це межа, але можна ввести ще одне обмеження і рушити трохи далі.
Ось вони виведені на екран:

Ось вибірка з них, 100 штук, наочно:

Що нам дає використання лише цих кольорів? Так що для заповнення області нам не треба змінювати стан шини даних у процесі заливання. Достатньо перемикати стан сигналу WE і рахувати скільки разів ми це зробили. Більш того, можна інвертувати WE скільки завгодно довго, головне не менше, ніж потрібно для заповнення області. Неважко порахувати, що раз на один піксел нам потрібно передати два блоки даних по шині, потрібно 2 підтвердження сигналом WE. Відповідно на весь екран треба (Ширіна_екрану*Довжина_екрану*2) імпульсів, або 800*480*2=768000.
Як простіше генерувати імпульси. Звичайно! Можна використовувати таймер. TIM1 у цьому контролері швидший, ніж таймери TIM2-TIM4, т.к. знаходиться на більш швидкісній шині тактування APB2. Дослідження показали, що увімкнувши таймер у режимі ШІМ генератора з мінімальним дільником можна отримати час заповнення 32 мс! Зрозуміло, що сигнал WE треба знімати з виходу таймера, наприклад PA8 (TIM1_CH1).
Чи можна ще збільшити швидкість заповнення? Виявилося так, просто подавши сигнал SYSCLK з виходу RCC_MCO на вхід WE LCD. Це максимальна доступна частота на процесорі 72 МГц. Час заповнення монітора симетричним кольором становить 10.7 мс. Відраховується час таймером, після чогопо перериванню сигнал знімається, а порт перемикається у режим виведення.
Таймер відраховує час з точністю 1/72 мкс для кількості точок, меншої за 32000 і з точністю 1 мкс для більшої кількості точок. Це з розрядністю лічильника таймера. Враховуючи, що потрібен якийсь час на обробку переривання при вимкненні таймера, сигнал на виході MCO знімається трохи пізніше, ніж потрібно з невеликим запасом. Експериментально встановлено, що це близько 10-11 тактів частоти процесора. Таким чином, можна сказати, що є поріг використання даної методики, при якому вона залишається швидше, незважаючи на накладні витрати на ініціалізацію таймера і RCC_MCO і відключення. Квадрат 2х2 пікселя можливо вигідніше програмно заповнювати по циклу.
Як висновок можна сказати, що, додавши деякі обмеження, було зменшено час заповнення екрану з 375 до 11 мс. Крім того, заповнення відбувається без участі процесора, який у цей час може виконувати інші завдання.
Буду радий зауваженням та доповненням.
Хардкорна конфа за С++. Ми запрошуємо лише профі.