Starten Sie die Anzeige auf STM32 über LTDC ... in den Registern

Grüße! Vor kurzem musste ein Projekt ein Display mit einer LVDS-Schnittstelle starten. Um die Aufgabe zu implementieren, wurde der STM32F746-Controller ausgewählt, weil Ich habe schon ziemlich viel mit ihm gearbeitet und er hat das LTDC-Modul, mit dem Sie ohne Controller direkt mit dem Display arbeiten können. In diesem Fall ist der Controller bereits im Mikrocontroller implementiert. Das letzte Argument war auch, dass es auf diesem Stein ein STM32F746-Disco-Debugging gab, das ich zur Hand hatte, was bedeutet, dass ich mit der Arbeit an dem Projekt beginnen konnte, ohne darauf zu warten, dass das Board, die Komponenten usw. zu mir kamen.

Heute werde ich Ihnen erklären, wie Sie das LTDC-Modul ausführen und mit Registern (CMSIS) arbeiten. HAL und andere Bibliotheken mögen und nutzen sie aus religiösen Gründen nicht, aber dies ist auch von Interesse. Sie werden sehen, dass das Anheben komplexer Peripheriegeräte in Registern so einfach ist wie normales SPI. Interessant? Dann lass uns gehen!



1. Ein wenig über LTDC


Dieses Peripheriemodul ist im Wesentlichen eine Steuerung, die normalerweise an der Seite des Displays steht, beispielsweise SSD1963 und dergleichen. Wenn wir uns die Struktur von LTDC ansehen, werden wir sehen, dass es sich physikalisch um einen 24-Bit-Parallelbus + Hardware-Grafikbeschleuniger + Datenarray im RAM handelt, bei dem es sich tatsächlich um einen Anzeigepuffer (Bildpuffer) handelt.



Am Ausgang haben wir einen gewöhnlichen parallelen Bus, der 24 Farbbits (8 Bit pro Farbe des RGB-Modells), Synchronisationsleitungen, eine Anzeige-Ein / Aus-Leitung und einen Pixeltakt enthält. Letzteres ist in der Tat ein Taktsignal, mit dem Pixel in die Anzeige geladen werden, dh wenn wir eine Frequenz von 9,5 MHz haben, können wir in 1 Sekunde 9,5 Millionen Pixel laden. In der Theorie sind die Zahlen in der Praxis natürlich aufgrund von Timings und anderen Dingen etwas bescheidener.

Für eine detailliertere Einführung in LTDC empfehle ich Ihnen, einige Dokumente zu lesen:

  1. Ein Überblick über die Funktionen von LTDC in F4, in unserem F7 ist alles gleich
  2. Anwendungshinweis 4861. "LCD-TFT-Display-Controller (LTDC) auf STM32-MCUs"

2. Was müssen wir tun?


ST-Mikrocontroller haben aus gutem Grund an Popularität gewonnen. Die wichtigste Voraussetzung für elektronische Komponenten ist die Dokumentation, und alles ist in Ordnung. Die Seite ist sicherlich schrecklich, aber ich werde Links zu allen Dokumentationen hinterlassen. Der Hersteller bewahrt uns vor der Qual und Erfindung des Fahrrads, daher werden auf Seite 520 im Referenzhandbuch RM0385 Schwarz-Weiß-Schritte angegeben, was wir tun müssen:



Tatsächlich müssen Sie nicht die Hälfte der beschriebenen Schritte ausführen: Sie müssen entweder nicht gestartet werden oder sind bereits standardmäßig konfiguriert. Für den minimalen Start, mit dem wir Pixel zeichnen, Bilder, Grafiken, Text usw. anzeigen können, reicht Folgendes aus:

  • Aktivieren Sie die LTDC-Taktung
  • Stellen Sie das Taktsystem und die Frequenz der Datenausgabe ein (Pixeltakt).
  • Konfigurieren Sie die E / A-Ports (GPIO) für die Arbeit mit LTDC
  • Richten Sie Timings für unser Display-Modell ein
  • Stellen Sie die Polarität der Signale ein. Standardmäßig bereits erledigt
  • Geben Sie die Hintergrundfarbe der Anzeige an. Wir werden ihn noch nicht sehen, Sie können es "bei Nullen" lassen.
  • Legen Sie die tatsächliche Größe des sichtbaren Bereichs der Anzeige für eine bestimmte Ebene fest
  • Wählen Sie das Farbformat: ARGB8888, RGB 888, RGB565 usw.
  • Geben Sie die Adresse des Arrays an, das als Bildspeicher fungieren soll
  • Geben Sie die Datenmenge in einer Zeile an (Länge in Breite).
  • Geben Sie die Anzahl der Zeilen an (Anzeigehöhe).
  • Fügen Sie die Ebene hinzu, mit der wir arbeiten
  • Aktivieren Sie das LTDC-Modul

Beängstigend Ich hatte Angst, aber es stellte sich heraus, dass es bei allen Vorgängen 20 Minuten lang funktionierte. Es gibt eine Aufgabe, der Plan ist geplant und es bleibt nur zu erfüllen.

3. Einrichten des Uhrensystems


Der erste Punkt, den wir benötigen, um ein Taktsignal an das LTDC-Modul zu senden, erfolgt durch Schreiben in das RCC-Register:

RCC->APB2ENR |= RCC_APB2ENR_LTDCEN; 

Als nächstes müssen Sie die Taktfrequenz vom externen Quarz (HSE) auf eine Frequenz von 216 MHz, dh auf das Maximum, konfigurieren. Der erste Schritt besteht darin, die Taktquelle vom Quarzresonator einzuschalten und auf das Bereitschaftsflag zu warten:

 RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); 

Stellen Sie nun die Verzögerung für den Flash-Speicher des Controllers auf ein Sie weiß nicht, wie sie mit der Kernfrequenz arbeiten soll. Sein Wert ist wie der Rest der Daten dem Referenzhandbuch entnommen:

 FLASH->ACR |= FLASH_ACR_LATENCY_5WS; 

Um nun die gewünschte Frequenz zu erhalten, werde ich 25 MHz vom Eingang auf 25 teilen und 1 MHz erhalten. Als nächstes multipliziere ich gerade in PLL mit 432, weil In Zukunft gibt es einen Frequenzteiler mit einem Mindestwert von / 2, auf den Sie die doppelte Frequenz anwenden müssen. Danach verbinden wir den PLL-Eingang mit unserem Quarzresonator (HSE):

 RCC->PLLCFGR |= RCC_PLLCFGR_PLLM_0 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLM_4; RCC->PLLCFGR |= RCC_PLLCFGR_PLLN_4 | RCC_PLLCFGR_PLLN_5 | RCC_PLLCFGR_PLLN_7 | RCC_PLLCFGR_PLLN_8; RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC; 

Aktivieren Sie nun PLL und warten Sie auf das Ready-Flag:

 RCC->CR |= RCC_CR_PLLON; while((RCC->CR & RCC_CR_PLLRDY) == 0){} 

Wir weisen den Ausgang unserer PLL als Quelle der Systemfrequenz zu und warten auf das Bereitschaftsflag:

 RCC->CFGR |= RCC_CFGR_SW_PLL; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_1) {} 

Dies beendet die allgemeine Takteinstellung und wir fahren mit der Einstellung der Taktfrequenz (PLLSAI) für unsere Anzeige (Pixeltakt) fort. Das Signal für PLLSAI gemäß Datenblatt wird nach dem Teiler / 25 genommen, dh am Eingang haben wir 1 MHz. Wir brauchen eine Frequenz von ca. 9,5 MHz, dafür multiplizieren wir die Frequenz von 1 MHz mit 192 und erhalten dann mit zwei Teilern mit 5 und 4 den gewünschten Wert PLLSAI = 1 MHz * 192/5/4 = 9,6 MHz:

 RCC->PLLSAICFGR |= RCC_PLLSAICFGR_PLLSAIN_6 | RCC_PLLSAICFGR_PLLSAIN_7; RCC->PLLSAICFGR |= RCC_PLLSAICFGR_PLLSAIR_0 | RCC_PLLSAICFGR_PLLSAIR_2; RCC->DCKCFGR1 |= RCC_DCKCFGR1_PLLSAIDIVR_0; RCC->DCKCFGR1 &= ~RCC_DCKCFGR1_PLLSAIDIVR_1; 

Als letzten Schritt aktivieren wir PLLSAI für die Anzeige und warten auf das arbeitsbereite Flag:

 RCC->CR |= RCC_CR_PLLSAION; while ((RCC->CR & RCC_CR_PLLSAIRDY) == 0) {} 

Damit ist die Grundeinstellung des Taktsystems abgeschlossen. Um nicht zu vergessen und dann nicht zu leiden, aktivieren wir die Taktung an allen Eingangs- / Ausgangsanschlüssen (GPIO). Wir haben keine Batterieleistung, zumindest nicht zum Debuggen, daher sparen wir nicht:

 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOHEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOJEN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOKEN; 

4. Konfigurieren der E / A-Ports (GPIO)


Das Einrichten von gpio ist sehr einfach - wir haben alle Abschnitte des LTDC-Busses als alternativen Ausgang und mit hoher Frequenz zu konfigurieren. Zu diesem Zweck haben wir im Referenzhandbuch auf Seite 201 diesen Tipp:



Die Tabelle gibt an, welche Bits in den Registern Sie setzen müssen, um die erforderliche Einstellung zu erhalten. Es ist erwähnenswert, dass alle Zahnspangen deaktiviert sind. Wo kann man nach einer alternativen Funktion suchen? Gehen Sie dazu auf Seite 76 im Datenblatt unseres Controllers und sehen Sie sich die folgende Tabelle an:



Wie Sie sehen können, ist die Logik der Tabelle einfach: Wir finden die Funktion, die wir benötigen, in unserem Fall LTDC B0, dann schauen wir uns an, auf welchem ​​GPIO es sich befindet (z. B. PE4), und oben sehen wir die Nummer der alternativen Funktion, die wir zum Konfigurieren verwenden werden (AF14 mit uns). Um unseren Ausgang als Push-Pull-Ausgang mit einer alternativen Funktion, LTDC B0, zu konfigurieren, müssen wir den folgenden Code schreiben:

 GPIOE->MODER &= ~GPIO_MODER_MODER4; GPIOE->MODER |= GPIO_MODER_MODER4_1; GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4_1; GPIOE->AFR[0] &= ~GPIO_AFRL_AFRL4_0; GPIOE->AFR[0] |= GPIO_AFRL_AFRL4_1 | GPIO_AFRL_AFRL4_2 | GPIO_AFRL_AFRL4_3; 

Ich habe ein Beispiel für den PE4-Pin gegeben, der Pin B0 auf dem LTDC-Bus entspricht, dh, es ist ein Null-Bit von blauer Farbe. Für alle anderen Schlussfolgerungen ist die Einstellung identisch, nur 2 Schlussfolgerungen verdienen besondere Aufmerksamkeit, eine der fertigen enthält eine Anzeige und die andere die Hintergrundbeleuchtung. Sie sind als normaler Push-Pull-Ausgang konfiguriert, mit dem jeder eine LED blinkt. Das Setup sieht folgendermaßen aus:

 GPIOK->MODER &= ~GPIO_MODER_MODER3; GPIOK->MODER |= GPIO_MODER_MODER3_0; 

Diese Einstellung gilt für die PK3-Ausgabe, bei der die Hintergrundbeleuchtung ein- und ausgeschaltet wird. Übrigens können Sie es auch DRÜCKEN, um die Helligkeit reibungslos anzupassen. Bei PI12, das ein Display (DISP) enthält, ist alles gleich. Die Geschwindigkeit an diesen 2 Pins ist standardmäßig niedrig, weil Einige Hochfrequenzaktionen sind von ihnen nicht erforderlich.

Sie können alle anderen E / A-Ports auf der Platine der Debug-Platine oder im Schaltplan Ihres eigenen Geräts anzeigen.

5. Timings und ihre Einstellungen


Timings aus physikalischer Sicht sind gewöhnliche Verzögerungen. Ich denke, Sie haben wiederholt verschiedene Perversionen vom Typ Verzögerung (1) beobachtet, als Sie sich Codebeispiele auf Displays mit SPI / I2C-Controllern ähnlich ILI9341 angesehen haben. Dort ist eine Verzögerung erforderlich, damit der Controller beispielsweise Zeit hat, den Befehl anzunehmen, auszuführen und dann etwas mit den Daten zu tun. Im Fall von LTDC ist alles ungefähr gleich, nur werden wir keine Krücken herstellen und warum nicht - unser Mikrocontroller selbst kann die erforderlichen Timings in der Hardware konfigurieren. Warum werden sie auf einem Display benötigt, auf dem kein Controller vorhanden ist? Ja, es ist elementar, dass Sie nach dem Füllen der ersten Pixelzeile zur nächsten Zeile wechseln und zum Anfang zurückkehren. Dies ist auf die Technologie der Herstellung von Displays zurückzuführen, und daher hat jedes spezifische Displaymodell seine eigenen Timings.

Um herauszufinden, welche Werte wir benötigen, besuchen Sie die ST-Website und sehen Sie sich das Diagramm der STM32F746-Disco-Debug-Karte an . Dort können wir sehen, dass das Display RK043FN48H-CT672B ist und die Dokumentation dazu zum Beispiel hier verfügbar ist. Die Tabelle auf Seite 13 in Abschnitt 7.3.1 interessiert uns am meisten:



Dies sind unsere Werte, die wir beim Einrichten benötigen. In der Dokumentation finden Sie auch viel Interessanteres, z. B. Signaldiagramme auf dem Bus usw., die Sie möglicherweise benötigen, wenn Sie beispielsweise die Anzeige auf FPGA oder CPLD erhöhen möchten.

Gehen Sie zu den Einstellungen. Um diese Werte nicht im Kopf zu behalten, werde ich sie zunächst in Form von Definitionen anordnen:

 #define DISPLAY_HSYNC ((uint16_t)30) #define DISPLAY_HBP ((uint16_t)13) #define DISPLAY_HFP ((uint16_t)32) #define DISPLAY_VSYNC ((uint16_t)10) #define DISPLAY_VBP ((uint16_t)2) #define DISPLAY_VFP ((uint16_t)2) 

Es gibt eine interessante Funktion. Die Timing- Impulsbreite , die als DISPLAY_HSYNC bezeichnet wird , hat in der Tabelle nur einen Wert für die Pixeltaktfrequenz von 5 MHz, für 9 und 12 MHz jedoch nicht. Dieses Timing muss für Ihre Anzeige ausgewählt werden. Ich habe diesen Wert von 30 erhalten, als es in den Beispielen von ST anders war. Wenn Sie beim ersten Start einen Fehler bei der Einstellung haben, wird das Bild entweder nach links oder nach rechts verschoben. Wenn es rechts ist, verringern wir das Timing, wenn es links ist, erhöhen wir es. Tatsächlich beeinflusst es den Ursprung der sichtbaren Zone, die wir später sehen werden. Denken Sie daran, und das folgende Bild von Seite 24 unseres AN4861 hilft, diesen gesamten Absatz zu verstehen:



Eine kleine Abstraktion ist hier zweckmäßig. Wir haben 2 Anzeigezonen: sichtbar und allgemein. Die sichtbare Zone hat Abmessungen mit einer deklarierten Auflösung von 480 x 272 Pixel, und die Gesamtzone ist die sichtbare + unsere Zeitsteuerung, von der es auf jeder Seite 3 gibt. Es lohnt sich auch zu verstehen (dies ist keine Abstraktion mehr), dass ein System-Tick 1 Pixel beträgt, sodass die Gesamtfläche 480 Pixel + HSYNC + HBP + HFP beträgt.

Es lohnt sich auch zu erkennen, dass je weniger Timings, desto besser - die Anzeige wird schneller aktualisiert und die Bildrate wird leicht erhöht. Experimentieren Sie daher nach dem ersten Lauf mit den Timings und reduzieren Sie sie so weit wie möglich, während Sie die Stabilität beibehalten.

Um die Zeiten festzulegen, habe ich mir innerhalb des Projekts einen kleinen „Spickzettel“ für die Zukunft gemacht. Er hilft Ihnen auch zu verstehen, welche bestimmte Figur und wo Sie sie schreiben sollen:

 /* *************************** Timings for TFT display********************************** * * HSW = (DISPLAY_HSYNC - 1) * VSH = (DISPLAY_VSYNC - 1) * AHBP = (DISPLAY_HSYNC + DISPLAY_HBP - 1) * AVBP = (DISPLAY_VSYNC + DISPLAY_VBP - 1) * AAW = (DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP - 1) * AAH = (DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP - 1) * TOTALW = (DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP + DISPLAY_VFP - 1) * TOTALH = (DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP + DISPLAY_HFP - 1) * */ 

Woher kommt dieser „Spickzettel“? Zuerst haben Sie einige Absätze zuvor eine ähnliche „Formel“ gesehen. Zweitens gehen Sie zu Seite 56 unseres AN4861:



Ich hoffe, Sie haben die physikalische Bedeutung der Timings vor dem Erscheinen dieses Spickzettel verstanden und ich bin sicher, dass Sie es selbst hätten zusammenstellen können. Es ist nichts Kompliziertes daran, und Bilder von RM und AN helfen dabei, die Auswirkung von Timings auf den Bilderzeugungsprozess visuell zu verstehen.

Jetzt ist es Zeit, einen Code zu schreiben, der diese Timings festlegt. In dem "Spickzettel" sind die Bits des Registers angegeben, in die beispielsweise TOTALH geschrieben werden soll, und nachdem das Vorzeichen gleich der Formel ist, die der Ausgabe eine bestimmte Zahl gibt. Okay? Dann schreiben wir:

 LTDC->SSCR |= ((DISPLAY_HSYNC - 1) << 16 | (DISPLAY_VSYNC - 1)); LTDC->BPCR |= ((DISPLAY_HSYNC+DISPLAY_HBP-1) << 16 | (DISPLAY_VSYNC+DISPLAY_VBP-1)); LTDC->AWCR |= ((DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP - 1) << 16 | (DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP - 1)); LTDC->TWCR |= ((DISPLAY_WIDTH + DISPLAY_HSYNC + DISPLAY_HBP + DISPLAY_HFP -1)<< 16 |(DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP + DISPLAY_VFP - 1)); 

Und das ist alles mit Timings! In diesem Abschnitt können Sie nur die Hintergrundfarbe konfigurieren. Ich habe es standardmäßig schwarz, daher ist es in Null geschrieben. Wenn Sie die Farbe der Hintergrundebene (Hintergrund) ändern möchten, können Sie auch einen beliebigen Wert schreiben, z. B. 0xFFFFFFFF, und alles mit Weiß füllen:

 LTDC->BCCR = 0; 

Das Referenzhandbuch enthält eine wunderbare Illustration, die deutlich zeigt, dass wir tatsächlich drei Ebenen haben: Hintergrund, Ebene 1 und Ebene 2. Die Hintergrundebene ist „kastriert“ und kann nur mit einer bestimmten Farbe gefüllt werden, kann aber auch bei der Implementierung unglaublich nützlich sein zukünftiges GUI-Design. Diese Abbildung zeigt auch deutlich die Priorität von Ebenen. Dies bedeutet, dass die Füllfarbe nur dann im Hintergrund angezeigt wird, wenn die verbleibenden Ebenen entweder leer oder transparent sind.

Als Beispiel zeige ich eine der Seiten des Projekts, auf der während der Implementierung der Vorlage der Hintergrund mit einer Farbe gefüllt wurde und der Controller nicht die gesamte Seite neu zeichnete, sondern nur einzelne Sektoren, die für viele andere Aufgaben etwa 50-60 fps empfangen konnten:



6. Der letzte Teil des LTDC-Setups


Die LTDC-Einstellungen sind in zwei Abschnitte unterteilt: Der erste ist für das gesamte LTDC-Modul gleich und befindet sich in der LTDC- Registergruppe. Der zweite Abschnitt ist in einer von zwei Ebenen konfiguriert und befindet sich in der Gruppe LTDC_Layer1 und LTDC_Layer2 .

Wir haben die allgemeinen Einstellungen im vorherigen Absatz vorgenommen. Dazu gehören das Einstellen der Timings und der Hintergrundebene. Nun fahren wir mit dem Festlegen der Ebenen fort und unsere Liste erfordert die tatsächliche Größe der sichtbaren Zone der Ebene, die in Form von 4 Koordinaten (x0, y0, x1, y2) beschrieben wird, mit denen wir die Abmessungen des Rechtecks ​​erhalten können. Die Größe der sichtbaren Ebene kann geringer sein als die Auflösung der Anzeige. Niemand stört sich daran, die Größe der Ebene auf 100 pro 100 Pixel festzulegen. Schreiben Sie den folgenden Code, um die Größe der sichtbaren Zone anzupassen:

 LTDC_Layer2->WHPCR |= (((DISPLAY_WIDTH + DISPLAY_HBP + DISPLAY_HSYNC - 1) << 16) | (DISPLAY_HBP + DISPLAY_HSYNC)); LTDC_Layer2->WVPCR |= (((DISPLAY_HEIGHT + DISPLAY_VSYNC + DISPLAY_VBP - 1) << 16) |(DISPLAY_VSYNC + DISPLAY_VBP)); 

Wie Sie sehen können, ist alles genau wie bei den Timings. Die Startpunkte (x0, y0) der sichtbaren Zone bestehen aus der Summe zweier Timings: HSYNC + HBP und VSYNC + VBP. Um die Koordinaten des Endpunkts (x1, y1) zu berechnen, werden Breite und Höhe in Pixel einfach zu den Wertdaten hinzugefügt.

Jetzt müssen Sie das Format der empfangenen Daten konfigurieren. Die maximale Qualität wird bei Verwendung des ARGB8888-Formats erzielt, gleichzeitig erhalten wir jedoch die maximale Menge an belegtem Speicher. Ein Pixel belegt 32 Bit oder 4 Bytes, was bedeutet, dass der gesamte Bildschirm 4 * 480 * 272 = 522.240 Bytes benötigt, dh die Hälfte des Flash-Speichers unseres nicht schwächsten Controllers. Haben Sie keine Angst - das Anschließen von externem SDRAM und Flash-Speicher über QSPI löst Speicherprobleme und es gibt keine Einschränkungen für dieses Format. Wir freuen uns über gute Qualität. Wenn Sie Platz sparen möchten oder Ihr Display das 24-Bit-Format nicht unterstützt, werden hierfür geeignetere Modelle verwendet, z. B. RGB565. Ein sehr beliebtes Format für Displays und Kameras. Vor allem bei Verwendung benötigt 1 Pixel nur 5 + 6 + 5 = 16 Bit oder 2 Byte. Dementsprechend ist die von der Schicht belegte Speichermenge zweimal geringer. Standardmäßig ist auf dem Controller bereits das ARGB8888-Format konfiguriert und sieht folgendermaßen aus:

 LTDC_Layer2->PFCR = 0; 

Wenn Sie ein anderes Format als ARGB8888 benötigen, gehen Sie zu den Seiten 533 und 534 im Referenzhandbuch und wählen Sie das gewünschte Format aus der folgenden Liste aus:



Erstellen Sie nun ein Array und übergeben Sie seine Adresse an LTDC. Es wird zu einem Frame-Puffer und ist eine „Reflexion“ unserer Ebene. Beispielsweise müssen Sie das 1. Pixel in der 1. Zeile mit weißer Farbe füllen. Dazu müssen Sie nur den Farbwert (0xFFFFFFFF) in das erste Element dieses Arrays schreiben. Müssen Sie das 1. Pixel in der 2. Zeile füllen? Dann schreiben wir auch den Farbwert in das Element mit der Zahl (480 + 1). 480 - Machen Sie einen Zeilenumbruch und fügen Sie dann die Nummer in die Zeile ein, die wir benötigen.

Diese Einstellung sieht folgendermaßen aus:

 #define DISPLAY_WIDTH ((uint16_t)480) #define DISPLAY_HEIGHT ((uint16_t)272) const uint32_t imageLayer2[DISPLAY_WIDTH * DISPLAY_HEIGHT]; LTDC_Layer2->CFBAR = (uint32_t)imageLayer2; 

In guter Weise müssen Sie nach der Konfiguration von LTDC auch SDRAM konfigurieren, um den const- Modifikator zu entfernen und den Frame-Puffer im RAM abzurufen, weil Der eigene RAM von MK reicht nicht einmal für eine Schicht mit 4 Bytes. Dies tut zwar nicht weh, um die korrekte Konfiguration der Peripheriegeräte zu testen.

Als nächstes müssen Sie den Wert der Alpha-Ebene angeben, dh die Transparenz für unsere Layer2- Ebene. Dazu schreiben wir einen Wert von 0 bis 255, wobei 0 eine vollständig transparente Ebene ist, 255 vollständig undurchsichtig ist und zu 100% sichtbar ist:

 LTDC_Layer2->CACR = 255; 

Nach unserem Plan ist es nun notwendig, die Größe unseres sichtbaren Anzeigebereichs in Bytes aufzuzeichnen, dazu schreiben wir die entsprechenden Werte in die Register:

 LTDC_Layer2->CFBLR |= (((PIXEL_SIZE * DISPLAY_WIDTH) << 16) | (PIXEL_SIZE * DISPLAY_WIDTH + 3)); LTDC_Layer2->CFBLNR |= DISPLAY_HEIGHT; 

Die letzten beiden Schritte verbleiben, nämlich die Einbeziehung der Schicht 2 und des LTDC-Peripheriemoduls selbst. Schreiben Sie dazu die entsprechenden Bits:

 LTDC_Layer2->CR |= LTDC_LxCR_LEN; LTDC->GCR |= LTDC_GCR_LTDCEN; 

Damit ist die Konfiguration unseres Moduls abgeschlossen und Sie können mit unserem Display arbeiten!

7. Ein wenig über die Arbeit mit LTDC


Bei allen Arbeiten mit dem Display müssen nur noch Daten in das imageLayer2- Array geschrieben werden. Es hat eine Größe von 480 x 272 Elementen, was unserer Auflösung vollständig entspricht und auf eine einfache Wahrheit hinweist - 1 Array-Element = 1 Pixel auf dem Display .

Als Beispiel habe ich ein Bild in ein Array geschrieben, in das ich in LCD Image Converter konvertiert habe. In Wirklichkeit ist es jedoch unwahrscheinlich, dass sich Ihre Aufgaben darauf beschränken. Es gibt zwei Möglichkeiten: Verwenden einer vorgefertigten Benutzeroberfläche und Schreiben selbst. Für relativ einfache Aufgaben wie Textausgabe, grafische Darstellung und dergleichen empfehle ich Ihnen, eine eigene GUI zu schreiben. Dies dauert einige Zeit und vermittelt Ihnen ein umfassendes Verständnis der Funktionsweise. Wenn die Aufgabe groß und schwierig ist und keine Zeit für die Entwicklung einer eigenen Benutzeroberfläche bleibt, empfehle ich Ihnen, auf vorgefertigte Lösungen zu achten, z. B. uGFX und dergleichen.

Symbole für Text, Linien und andere Elemente sind von Natur aus Pixelarrays. Um sie zu implementieren, müssen Sie die Logik selbst implementieren. Sie sollten jedoch mit der grundlegendsten Funktion beginnen - der „Pixelausgabe“. Es sollten 3 Argumente erforderlich sein: die Koordinate entlang X, die Koordinate entlang Y und dementsprechend die Farbe, in der das angegebene Pixel gezeichnet wird. Es kann so aussehen:

 typedef enum ColorDisplay { RED = 0xFFFF0000, GREEN = 0xFF00FF00, BLUE = 0xFF0000FF, BLACK = 0xFF000000, WHITE = 0xFFFFFFFF } Color; void SetPixel (uint16_t setX, uint16_t setY, Color Color) { uint32_t numBuffer = ((setY - 1) * DISPLAY_WIDTH) + setX; imageLayer2[numBuffer] = Color; } 

Nachdem wir die Koordinaten in eine Funktion übernommen haben, berechnen wir sie neu in die Nummer des Arrays, das der angegebenen Koordinate entspricht, und schreiben dann die empfangene Farbe in das empfangene Element. Basierend auf dieser Funktion können Sie bereits Funktionen zum Anzeigen von Geometrie, Text und anderen GUI-Extras implementieren. Ich denke, die Idee ist verständlich, aber wie Sie sie zum Leben erwecken können, liegt in Ihrem Ermessen.

Zusammenfassung


Wie Sie sehen, ist die Implementierung selbst komplexer Peripheriegeräte in Registern (CMSIS) keine schwierige Aufgabe. Sie müssen lediglich verstehen, wie sie im Inneren funktioniert. Natürlich ist es jetzt in Mode, Firmware zu entwickeln, ohne zu verstehen, was passiert, aber dies ist eine Sackgasse, wenn Sie vorhaben, Ingenieur zu werden, und nicht ...

Wenn Sie den resultierenden Code mit einer Lösung in HAL oder SPL vergleichen, werden Sie feststellen, dass der in Registern geschriebene Code kompakter ist. Wenn Sie ein paar Kommentare hinzufügen, wo Sie sie benötigen, und sie in Funktionen einbinden, erhalten wir eine Lesbarkeit, die mindestens nicht schlechter ist als die von HAL / SPL. Wenn Sie sich daran erinnern, dass das Referenzhandbuch die Register dokumentiert, ist die Arbeit mit CMSIS bequemer.

1) Das Projekt mit Quellen in TrueSTUDIO kann hier heruntergeladen werden

2) Für diejenigen, die sich mit GitHub wohler fühlen

3) Laden Sie hier das Dienstprogramm zum Konvertieren von Bildern in LCD Image Converter-Code herunter

Source: https://habr.com/ru/post/de412753/


All Articles