Programmiermuster zur Generierung des VGA-Signals mit Mikrocontroller?

Ich möchte ein VGA-Signal mit einem Mikrocontroller erzeugen (wie TI Tiva ARM, der mit einer Geschwindigkeit von 90/120 MHz läuft).

Ich bin mir nicht sicher, wie man genaue Timings mit Mikrocontrollern macht. Welches Programmiermuster muss ich verwenden?

Benötige ich einen Inline-Assembler-Code? Wie setzt man Interrupts sinnvoll ein?

Wäre toll, wenn jemand einen Pseudo-Code zeigen würde, wie man ein VGA-Signal erzeugt.

Ich habe erfolgreich ein VGA-Signal mit FPGA generiert. Aber ich kann einfach nicht herausfinden, wie ich das mit MCU machen soll.

Es gibt VGA-Bibliotheken für AVR. Anderer Mikrocontroller, aber Sie könnten sich davon inspirieren lassen. Möglicherweise können Sie den DMA-Controller verwenden, um die Haupt-CPU zu entlasten, da das Generieren eines Videosignals ohne diese Art von Tricks ziemlich CPU-intensiv ist.

Antworten (2)

Diese Antwort basiert alle auf Standard-VGA-Bildschirmen mit einer Auflösung von 640 x 480, da Sie in Ihrer Antwort darauf verwiesen haben. Das Timing für SVGA (Super VGA, 800 x 600), XGA (1024 x 768) und so weiter wird unterschiedlich sein. Diese Seite enthält eine gute Liste praktisch aller verwendeten Auflösungen. Aber das Pixel-Timing für diese größeren Bildschirme wird so schnell sein, dass ich bezweifle, dass Sie es mit einem Mikro erzeugen könnten.

VGA-Timing

Der Standard-VGA-Bildschirm hat also horizontal 640 Pixel und vertikal 480 Zeilen. Zur Generierung des Timings wird ein Pixeltakt von 25,175 MHz verwendet. (Theoretisch bedeutet dies, dass das Mikro in der Lage sein müsste, Pulse zu erzeugen, die genau 39,72 ns lang sind, was ziemlich schwierig wäre, aber es scheint, dass Sie mit 25-MHz- und 40-ns-Takten auskommen können.

Das Format des VGA-Timings für jede Zeile stammt aus den Tagen von CRT-Computerbildschirmen und vor Fernsehgeräten. Für ein 640x480-Bild sieht es so aus (hier nur die Horizontale):

Geben Sie hier die Bildbeschreibung ein

Es gibt zwei Synchronisationsimpulse – einen pro Zeile (horizontale Synchronisation) und einen pro Rahmen (vertikale Synchronisation). Dem horizontalen Synchronisationsimpuls geht ein "hinteres Schwarzschulter"-Intervall voraus und ein "vorderes Schwarzschulter"-Intervall folgt. Zwischen diesen beiden befindet sich das aktive Video (640 Pixel). Die Gesamtzeile ist jedoch 800 Pixel breit.

Ebenso haben die vertikalen Zeilen unten einen Synchronisationsimpuls, der von einer vertikalen hinteren Schwarzschulter und einer vorderen Schwarzschulter umgeben ist und 524 Pixel hoch ist.

Geben Sie hier die Bildbeschreibung ein

Damals, als Video mit CRTs anstelle von LCD-Bildschirmen gemacht wurde, gab es einen Elektronenstrahl, der für jede Zeile über den Bildschirm scannte, der sich dann ein wenig nach unten bewegte, die nächste Zeile scannte usw. Am unteren Rand des Bildschirms musste es reißen zurück an die Spitze.

Am Ende jeder Zeile muss sich der Strahl wieder zum Anfang der nächsten Zeile bewegen – das dauert ein wenig. Während dieser Zeit wurde das Video ausgeblendet. Dies ist als die horizontale Auffrischzeit bekannt und ist die Summe aus dem Timing der hinteren Schwarzschulter + Sync + vorderen Schwarzschulter. Ebenso für die vertikale Auffrischperiode. Deshalb gibt es insgesamt 800 Pixel auf dem Bildschirm und 524 Zeilen vertikal, obwohl Sie nur 640 x 480 sehen.

Das Timing kann dann in Taktimpulsen angegeben werden:

Geben Sie hier die Bildbeschreibung ein

wobei HPX die 640 Pixel sind, HFP das horizontale hintere Schwarzschulterintervall ist, HSP der horizontale Synchronisationsimpuls ist, HBP das horizontale hintere Schwarzschulterintervall ist. Ebenso für die Vertikale. Dies müssen Sie für Ihr Timing im Mikrocontroller verwenden (vorausgesetzt, Sie haben einen 40-ns-Takt).

Jede volle Zeile hat also 800 Takte oder 800 x 40 ns = 32 µs.

Beachten Sie, dass das kritische Timing nur auftritt, wenn Pixel auf den Bildschirm geschrieben werden (12 Bit alle 40 ns). Während Sie die Pixel ausschreiben, werden Sie nicht viel Zeit haben, etwas anderes zu tun. Aber während der vorderen Schwarzschulter, des Synchronisationsimpulses und der hinteren Schwarzschulter haben Sie jeweils 635 ns, 3,81 µs und 1,9 µs für andere Dinge.

Beachten Sie, dass Sie, wenn Sie keinen Prozessor haben, der schnell genug ist, um 40-ns-Pixel zu erzeugen, alles halbieren können (im Wesentlichen mit 12,5 MHz oder einem Takt von 80 ns) und eine Auflösung von 320 x 240 haben. Für den Monitor wird also jedes Pixel zweimal wiederholt. Es denkt immer noch, dass Sie VGA senden.

VGA-Ausgang

Um die Videosignale für jedes Pixel zu erzeugen, können Sie Ihren eigenen DAC (Digital-Analog-Wandler) mit Widerständen herstellen. Wenn Sie 4 Bit pro Farbe (RGB) zuweisen, benötigen Sie insgesamt 12 Widerstände, die so angeordnet sind:

Geben Sie hier die Bildbeschreibung ein

Sie könnten mit weniger Bits auskommen, zum Beispiel 3 Bits pro Farbe oder sogar 2, aber die Bildqualität wird nicht so gut sein.

Firmware

Angenommen, wir haben einen 100-MHz-32-Bit-Prozessor (10 ns pro Befehl). Nehmen wir auch an, wir verwenden 12-Bit-Pixel, die zwei Pixel in einem 32-Bit-Wort speichern (also verschwenden wir leider 8 Bit).

Nehmen wir vor dem Start an, dass r0 mit der Adresse des 32-Bit-Startworts geladen ist, das Pixel für diese Zeile enthält, und r1 mit der Adresse eines speicherabgebildeten E/A-Ports geladen ist, von dem die unteren 12 Bits D0–D11 herausgebracht werden , und r2 ist ein temporäres, das zwei Pixel hält.

Ich verwende einen erfundenen RISC-ähnlichen Befehlssatz, aber es sollte ziemlich offensichtlich sein, was vor sich geht. Angenommen, jeder Befehl dauert einen Zyklus oder 10 ns.

ld r2,[r0]      ; load indirect through register r0, 32 bits (2 pixels)
andi r2,0xFFF   ; and immediate, get lower 12 bits
st r2,[r1]      ; store pixel to I/O port
ld r2,[r0]      ; get pixel again
rsh r2,16       ; right shift 16 bits to get upper pixel
andi r2,0xFFF   ; and immediate, get lower 12 bits
st r2,[r1]      ; store pixel to I/O port
addi r0,4       ; increment memory address (assume byte addressing)   

Wenn der Prozessor schneller als 100 MHz ist, müssen Sie Nops hinzufügen, damit die gesamte Sequenz immer noch 80 ns für zwei Pixel benötigt.

Sie wiederholen diese Sequenz von acht Anweisungen 320 Mal inline. Richten Sie am Ende einen Interrupt für 635 ns in der Zukunft ein (Ende der hinteren Veranda) und kehren Sie von der Interrupt-Ebene zurück (die eingegeben wurde, als Sie mit dem Senden von Pixeln begonnen haben). In der Zwischenzeit haben Sie 63 Anweisungen für die Basisebene frei.

Erzeuge am Interrupt (Ende der hinteren Schwarzschulter) den Beginn des horizontalen Sync-Impulses (geht auf Low) und setze einen weiteren Interrupt diesmal 3,81 &mgr;s in der Zukunft (Ende des horizontalen Sync) und verlasse den Interrupt. Sie können dieses Mal etwa 380 Anweisungen ausführen.

Am Interrupt (Ende der horizontalen Synchronisation) vervollständige die horizontale Synchronisation (geht zurück hoch), setze eine Unterbrechung auf 1,9 &mgr;s in der Zukunft (Ende der vorderen Veranda) und kehre von der Unterbrechung zurück. Etwa 190 weitere Anleitungen verfügbar.

Beginnen Sie am letzten Interrupt (Ende der Veranda) erneut mit der Ausgabe von Pixeldaten (alles in der Interrupt-Routine).

Ähnlicher Code für das Ende des Frames (vertikale Synchronisation usw.).

Mit Ausnahme der horizontalen Rücklaufzeit dient dies alles nur dazu, die Pixel aus dem RAM auf den Bildschirm zu kopieren. Es wären zusätzliche 32 µs * 44 Zeilen oder 1,408 ms während des vertikalen Rücklaufs verfügbar oder 140.800 Anweisungen für andere Dinge. Aber dies, hinzugefügt mit den zusätzlichen Anweisungen, die während des horizontalen Rücklaufs (304.000) verfügbar sind, wäre immer noch nicht schnell genug, um das nächste Mal einen vollen Videoframe mit der vollen Auflösung von 640 x 480 zu erzeugen. Sie würden wirklich mindestens einen 200-MHz-Prozessor benötigen, um sowohl Video zu erzeugen als auch auf den VGA-Ausgang zu kopieren.

Es ist kein Wunder, dass PCs von Anfang an mit spezialisierter Grafikhardware ausgestattet sind, die den Inhalt eines Video-RAMs ohne Unterstützung des Hauptprozessors auf einen Videoport (VGA oder was auch immer) schreibt.

Eigentlich sehr hilfreiche Informationen. Kann aber mehr Details zur Softwareseite geben. Ich brauche einen Timer mit 25 MHz Frequenz? Danke
@kesrut Ja. Sie werden einen sehr schnellen Prozessor brauchen. Ein 200-MHz-Prozessor würde 5 ns pro Befehl (unter der Annahme eines Zyklus/Befehls) oder 8 Befehle pro Pixeltakt benötigen. Nicht viel Zeit. Beachten Sie, dass der Multi-Core- Parallax-Propeller über spezielle Timer verfügt, sodass er eine VGA-Ausgabe in einem Kern erzeugen kann, während Sie 7 weitere Kerne für andere Dinge haben. Vielleicht möchten Sie das als Co-Prozessor in Betracht ziehen. Hier ist eine Demo und eine andere .
@kesrut Eigentlich reicht ein 100-MHz- (oder sogar 76-MHz-) Prozessor aus. Siehe das Update zu meiner Antwort.
Leute haben es mit übertakteten Atmel-Mikrocontrollern gemacht: tinyvga.com/avr-vga
Sie werden dies auf keinen Fall mit einem Timer tun, Sie befinden sich im Bereich des Schreibens von Cycle-Counted-Assembler, und Ihr Prozessor wird die meiste Zeit damit verbringen, den Bildschirm zu aktualisieren. Sie können Ihren Prozessor während der Teile der Anzeigewellenform, in denen nichts auf dem Bildschirm zu sehen ist, und/oder in Teilen der Anzeige, in denen ein großer Bereich mit durchgehender Farbe vorhanden ist, andere Dinge tun lassen.
@ pjc50 Keines der AVR-Projekte, die ich mir angesehen habe, macht echtes VGA. Wie ich in meiner Antwort erkläre, können Sie während des aktiven Videos jede Pixelzeit mit der halben Auflösung (320x240) verdoppeln. Das Projekt, mit dem Sie verknüpft sind, zeigt nur zwanzig 8x12-Zeichen in jeder Zeile an. Bei gleichem Sync-Timing denkt der Monitor immer noch, er bekommt ein VGA-Bild.
@PeterGreen Stimme zu, ich erkläre das ziemlich gut in meiner Antwort im Abschnitt Firmware. Das Aktualisieren des aktiven Videos erfordert einen Code von 3 oder 4 Anweisungen pro Pixel, also insgesamt 1920–2560 Inline-Zeilen des Assemblercodes, abhängig vom Prozessortakt (76 oder 100 MHz). Andere Aktivitäten können während der vorderen Schwarzschulter-, Synchronisations- und hinteren Schwarzschulterperioden stattfinden – insgesamt 5,71 &mgr;s pro Leitung. Wenn Sie einen 200-MHz-Prozessor (8 Anweisungen pro Pixel) hätten, könnten Sie eine Schleife verwenden und bräuchten keine zweitausend Zeilen Inline-Code. Aber keine Interrupts erlaubt (eigentlich findet dies in einem ISR statt).

Einige Projekte verwenden einen i2s- oder Spi-Port, um jede Farbe zu steuern. Der tiva C hat einen parallelen Port mit dma/fifo um die Pixeldaten zu streamen.

Willkommen bei EE.SE. Die richtige Groß- und Kleinschreibung würde helfen, Ihren Beitrag lesbarer zu machen. Es ist ziemlich kurz im Detail.
Die Verwendung von On-Chip-Peripheriegeräten für das genaue Timing ist in der Tat weitaus besser als die Verwendung des CPU-Kerns! Fragen auf einer SE-Site dürfen sowieso nicht nach dem eigentlichen Code fragen, um etwas zu implementieren - es geht vielmehr darum, die Methode bereitzustellen , auf deren Grundlage jemand gehen und Code schreiben kann, und dies weist in der Tat in eine vielversprechende Richtung.