Warum funktioniert meine VGA-Implementierung mit einem AVR-Mikrocontroller nicht?

Problem

Ich versuche, über die VGA-Spezifikation den geeigneten Signalausgang für die Verbindung eines AVR ATmega328P-Mikrocontrollers mit einem LCD-Monitor zu erzeugen. Die VGA-Spezifikation, die ich zu erfüllen versuche, ist der Industriestandard 640*480 mit einer Bildwiederholfrequenz von 60 Hz (1).

Hintergrundinformation

Der AVR arbeitet mit einer Frequenz von 20 MHz , was kurz vor der VGA-Pixeltaktfrequenz von 25,175 MHz liegt . Nachdem ich ähnliche Projekte online betrachtet hatte (2), glaubte ich jedoch, dass ich durch eine gewisse Manipulation der Anzahl der Uhren pro Region die in der VGA-Spezifikation festgelegten Timings einhalten könnte. Meine angepassten Timings für die vertikale und horizontale Spezifikation finden Sie unten:

Vertikales Timing (Frame)

Sichtbarer Bereich: 15,24 ms, 480 Zeilen (304800 Taktzyklen bei 20 MHz)
Vordere Schwarzschulter: 0,3175 ms, 10 Zeilen (6350 Taktzyklen bei 20 MHz)
Synchronimpuls: 0,0635 ms, 2 Zeilen (1270 Taktzyklen bei 20 MHz)
Hintere Schwarzschulter : 1,04775 ms, 33 Zeilen (20955 Taktzyklen bei 20 MHz)
Vollbild: 16,66875 ms, 525 Zeilen (333375 Zyklen bei 20 MHz)

Horizontales Timing (Linie)

Sichtbarer Bereich: 25,45 us, 508 Pixel (508 Taktzyklen bei 20 MHz)
Vordere Schwarzschulter: 0,65 us, 13 Pixel (13 Taktzyklen bei 20 MHz)
Synchronimpuls: 3,8 us, 76 Pixel (76 Taktzyklen bei 20 MHz)
Hintere Schwarzschulter : 1,9 us, 38 Pixel (38 Taktzyklen bei 20 MHz)
Ganze Zeile: 31,75 us, 635 Pixel (635 Taktzyklen bei 20 MHz)

Basierend auf diesen Timings beträgt die Frame-Aktualisierungsrate 59,9925 Hz . Der sichtbare Bereich auf dem Bildschirm hat eine Auflösung von 508*480 im Gegensatz zu den 640*480 der Spezifikation.

Mir ist bewusst, dass bei einer Taktfrequenz von 20 MHz die Timings nicht exakt eingehalten werden können, aber wenn man meine Timings mit der tatsächlichen Spezifikation (3) vergleicht, liegen die Timings sehr nahe beieinander.
Die Software, die ich geschrieben habe, um diese Ausgaben zu erzeugen, wurde in AVR-Assembler geschrieben, dies ermöglicht mir, die Anzahl der Taktzyklen für jede Region zu zählen, und kann direkt unter diesem Absatz gefunden werden. Die Software gibt auf unbestimmte Zeit einen roten Rahmen auf dem Bildschirm aus.
Ein Bild des Schaltplans für die Hardwareimplementierung finden Sie unter dem Code.

;
; VGA_INTERFACE.asm
;
; Outputs the required signals with the correct timings for VGA output to a monitor.
;
;
;
; Created: 13/02/2018 
; Author : Tom
;









; COMPILER SETTINGS
.INCLUDE "M328pDEF.INC"


; INTERRUPT VECTORS
.org 0      ; defines absolute address for interrupt vector




; ****************************************************************************************
; **** IO PORT D SETUP
; ****************************************************************************************

    ; ddrd pin I/O direction configured

    sbi ddrd,0  ; RED BIT 0             
    sbi ddrd,1  ; RED BIT 1             
    sbi ddrd,2  ; GRN BIT 0
    sbi ddrd,3  ; GRN BIT 1
    sbi ddrd,4  ; BLU BIT 0
    sbi ddrd,5  ; BLU BIT 1
    sbi ddrd,6  ; HORIZONTAL SYNC       
    sbi ddrd,7  ; VERTICAL SYNC          



    ldi r20, 0xC0
    out portd, r20      ; clears the RGB bits and sets the sync pulses high





; ****************************************************************************************
; **** STARTUP SEQUENCE
; ****************************************************************************************

; INITIALIZE STACK POINTER

    ldi r16,low(ramend)     ; loads the lower byte of top stack address into register 16 
    out spl,r16             ; stack pointer lower byte is set to lower byte of the top 
                            ; stack address stored in register 16

    ldi r16,high(ramend)    ; loads the upper byte of top stack address into register 16
    out sph,r16             ; stack pointer upper byte is set to upper byte of the top 
                            ; stack address stored in register 16






; main program loop
main:



V_LOOP:

; ****************************************************************************************
; **** VERTICAL LOOP - BEGIN
; ****************************************************************************************


; **** V-SYNC DRIVE LOW (2 lines, 1,270 cycles)




    cbi portd,7     ;2 drives v-sync active low 


; ========================================================================================
    ; Delay 1268 cycles
    ldi  r18, 2
    ldi  r19, 165
L1: dec  r19
    brne L1
    dec  r18
    brne L1
; ========================================================================================


    sbi portd,7     ;2 drives v-sync high 









; **** VERTICAL BACK PORCH (33 lines, 20955 cycles)

    ; **NOTE: Only 20951 cycles required to be wasted as 4 cycles are used by Horizontal 
    ; loop. 2 are used when setting max loop value in r16 and r17. A further 2 are used 
    ; setting horizontal sync active low.


; ========================================================================================
    ; Delay 20951 cycles
    ldi  r18, 28
    ldi  r19, 52
L2: dec  r19
    brne L2
    dec  r18
    brne L2
    rjmp PC+1
; ========================================================================================









; ****************************************************************************************
; **** HORIZONTAL LOOP - BEGIN (LOOPS 480 times)
; ****************************************************************************************


    ldi r16,low(480)        ;1 holds LSB of loop value
    ldi r17,high(480)       ;1 hold MSB of loop value




H_LOOP:


; **** H-SYNC DRIVE LOW (76 cycles)


    cbi portd,6     ;2 drives h-sync active low 

; ========================================================================================
    ; Delay 74 cycles
    ldi  r18, 24
L3: dec  r18
    brne L3
    rjmp PC+1
; ========================================================================================


    sbi portd,6     ;2 drives h-sync high









; **** HORIZONTAL BACK PORCH (38 cycles)


    ; **NOTE: Only 36 cycles required to be wasted as 2 cycles are used by RGB for setting
    ; the red bit 0 high.



; ========================================================================================
    ; Delay 36 cycles
    ldi  r18, 12
L4: dec  r18
    brne L4
; ========================================================================================









; **** RGB (508 cycles)

    ldi r20, 0xC1       ;1
    out portd, r20      ;1 sets red bit 0 high, all other RGB low, sync pulses high 


; ========================================================================================  
    ; Delay 506 cycles
    ldi  r18, 168
L5: dec  r18
    brne L5
    rjmp PC+1
; ========================================================================================


    ldi r20, 0xC0       ;1
    out portd, r20      ;1 sets the RGB outputs low, sync pulses high 









; **** HORIZONTAL FRONT PORCH (13 cycles)


    ; **NOTE: Only 5 cycles required to be wasted as 8 cycles are used up already. 4 are    
    ; are used for subtracting one from the loop counter. A further 4 are used for 
    ; jumping to start of horizontal loop and setting the Horizontal sync active low.


; ========================================================================================
    ; Delay 5 cycles
    lpm
    rjmp PC+1
; ========================================================================================


    ldi r18, low(1)     ;1
    ldi r19, high(1)    ;1


    sub r16,r18         ;1
    sbc r17,r19         ;1  


    brne H_LOOP     ; 2 cycles if true, 1 if false  









; ****************************************************************************************
; **** HORIZONTAL LOOP - END
; ****************************************************************************************











; **** VERTICAL FRONT PORCH (10 lines, 6350 cycles)

    ; **NOTE: Only 10 cycles have been used up for the Horizontal front porch, as a result a  
    ; further 3 must be added to the vertical front porch. 
    ; However 4 cycles are already being used, 2 to jump to start of vertical loop and a 
    ; further 2 to drive horizontal sync active low.
    ; As a result taking these two factors into account, the delay needs to be 6350+3-4 =
    ; 6349  cycles long.


; ========================================================================================
    ; Delay 6349 cycles
    ldi  r18, 9
    ldi  r19, 62
L6: dec  r19
    brne L6
    dec  r18
    brne L6
; ========================================================================================


    rjmp V_LOOP     ;2  relative jump to start of vertical loop









; ****************************************************************************************
; **** VERTICAL LOOP - END
; ****************************************************************************************

ATmega328P-Hardwareimplementierung

Ergebnisse

Nachdem ich die Hardware- und Softwareimplementierung getestet habe, habe ich festgestellt, dass sie auf der VGA-Schnittstelle meines Fernsehers im Wohnzimmer gut funktioniert, aber auf keinem anderen Monitor oder Fernseher, den ich getestet habe. Ich habe festgestellt, dass es auf einem Monitor eines Freundes irgendwie funktioniert, es gibt einen roten Bildschirm aus, aber mit zufälligen schwarzen Flecken und verliert regelmäßig das Signal. Die anderen Monitore, auf denen ich getestet habe, erkennen zwar die Eingabe, können aber einfach nichts auf dem Bildschirm ausgeben.

Ich glaube, der Grund, warum es auf einigen Displays funktioniert und auf anderen nicht, liegt einfach an den Toleranzen, die die Hersteller in ihren Geräten festgelegt haben.

Potentielle Lösungen

Ich habe zahlreiche Optimierungen im Code ausprobiert, dabei ging es hauptsächlich darum, die Anzahl der Taktzyklen in jeder Region zu ändern, aber dies führte zu keinem positiven Ergebnis. Dies führt mich zu einer von zwei möglichen Situationen:

1) (HÖCHSTWAHRSCHEINLICH) Ich habe die Software oder Hardware falsch implementiert, was dazu geführt hat, dass die Timings leicht verschoben sind.

2) Es ist sehr schwierig/unmöglich, die VGA-Spezifikation mit konsistenter Leistung auf allen VGA-fähigen Geräten zu implementieren, die einen ATmega328P verwenden, der mit 20 MHz arbeitet.

Aus diesem Grund plane ich, das Atmega-Gerät entweder mit einem 25,175-MHz-Quarz zu übertakten, um sicherzustellen, dass die Timings eingehalten werden können, oder ich werde einen leistungsfähigeren Mikrocontroller mit größerer Rechenleistung verwenden, etwa einen PIC24EP128MC202.

Wenn jemand eine Idee hat, warum meine aktuelle Implementierung nicht funktioniert und wie ich dies beheben könnte, wäre ich sehr dankbar!

Wenn Sie es geschafft haben, bis hierher zu lesen, danke trotzdem! :)

Verweise

(1)(3) VGA-Signal 640 x 480 bei 60 Hz Industriestandard-Timing – http://tinyvga.com/vga-timing/640x480@60Hz

(2) Lucid Science VGA Video Generator (leider wurde die Seite jetzt geschlossen) https://web.archive.org/web/20141102012544/http://www.lucidscience.com:80/pro-vga%20video%20generator -6.aspx

Hast du ein Oszilloskop?
Ich habe mit einem Logikanalysator getestet, beabsichtige jedoch, mit einem Oszilloskop zu testen.
Ich erinnere mich, dass der Empfängerteil der Überwachungslogik prüft, ob die Zeitsignale innerhalb der akzeptablen Abweichungen liegen. Können Sie überprüfen, ob Ihre Signale den Monitorspezifikationen entsprechen?
Nachdem ich mir die Datenblätter für die Monitore angesehen habe, kann ich keine spezifischen Details zu den vom Monitor akzeptierten Timings finden. Sie sagen einfach 640 * 480 @ 60 Hz (59,94 Hz).
Dazu fällt mir ein: linusakesson.net/scene/craft
Kannst du stattdessen auf 50Hz gehen? Das funktioniert für ein LCD. Dann kann Ihr Pixeltakt reduziert werden, ohne die Porch-Größen zu beeinträchtigen.
Die grundlegende Prämisse, einen niedrigeren Punkttakt zu verwenden, solange Sie die horizontalen und vertikalen Timings beibehalten, gilt für einen analogen Videostandard. Ein Logikanalysator sollte ausreichen, um das Timing zu überprüfen, obwohl Sie auch berücksichtigen müssen, ob die Signalspannungen innerhalb des Bereichs liegen.
Bedenken Sie, dass VGA-Eingänge oft eine Impedanz im Bereich von 75 Ohm haben. Wenn diese resistiv abgeschlossen sind, liefern Ihre 1K- und 2K-Serienwiderstände sehr niedrige Pegel.
Die Spannungen sind nach meinen Berechnungen in Ordnung, unter Berücksichtigung des 75-Ohm-Widerstands beträgt die niedrigste Spannung ungefähr 0,3 V, was gut im Spannungsbereich von 0 und 0,7 V liegt, und wie ich bereits sagte, ist die Ausgabe auf einem der Monitore in Ordnung I getestet an.
Der Punkt ist, dass die Monitore in Bezug auf das Niveau, das sie akzeptieren, unterschiedlich sind. Insbesondere Ihre Synchronisierungssignale werden wahrscheinlich nicht genug schwingen.

Antworten (2)

Erstens sollte es keine Probleme geben, VGA mit einem ATMega328 auszugeben. Sie können VGA ausgeben, das mit allem funktioniert, von alten CRTs, kleinen mysteriösen LCD-Modulen von aliexpress oder einem modernen LCD. Ich habe noch nie wirklich von Kompatibilitätsproblemen mit VGA gehört - insbesondere nicht von Timing-bezogenen.

Es gibt Dutzende von Projekten, die erfolgreich mindestens monochromatische 640x480-VGA (parallele RGB-Zeilen) mit viel weniger als einem ATMega ausgeben, und Sie benötigen nicht einmal einen 20-MHz-Takt - mehrere Projekte kommen mit einem 16-MHz-Takt aus. Und wenn Sie mit weniger als 640 x 480 einverstanden sind, gibt dieses Projekt gültiges VGA mit einem ATTiny15 aus, das mit 1,6 MHz läuft.

Nun, ich habe Ihre Baugruppe nicht doppelt überprüft, daher könnte ich falsch liegen, aber es scheint unwahrscheinlich, dass das Timing auf einem Display gut funktioniert, auf anderen jedoch nicht. Nein, ich denke, das ist einfach ein Problem der Signaldämpfung aufgrund von Impedanzfehlanpassungen.

Die 3 Farblinien erwarten 0 bis 0,7 V. Und wie ich sehe, haben Sie Ihre Widerstände so dimensioniert, dass sie bei einer Impedanz von 75 Ω entweder 0,696 V oder 0,348 V ergeben.

Ich weiß, das scheint wahrscheinlich eine sehr schöne, elegante Lösung zu sein, um Schwarz + zwei Farbtöne zu erhalten, aber ich fürchte, es wird nicht sehr gut funktionieren. Ihre Widerstände sind viel zu groß und dies verursacht einen schlimmen Fall von Impedanzfehlanpassung. Und Sie würden wahrscheinlich genau die Probleme haben, die Sie beschreiben - einige Displays (eine kleine Minderheit, die ich bei so großen Widerständen erwarten würde) könnten das Signal verwenden, die meisten jedoch nicht oder würden Pixel nur zeitweise pro Frame korrekt lesen.

Wenn Sie eine Impedanz von 75 Ω richtig treiben und gleichzeitig eine höhere Spannung auf den richtigen Spannungsbereich reduzieren möchten, müssen Sie ein Impedanzanpassungsnetzwerk wie einen L-Pad-Teiler verwenden.

Die Diskussion der Theorie hinter L-Pad-Teilern würde den Rahmen dieser Frage sprengen, aber Sie würden gut daran tun, Transistoren zu verwenden, um die Spannungspegel richtig einzustellen, ohne die hohe Impedanz eines Teilers zu haben.

Hier, nur um eine Plausibilitätsprüfung durchzuführen und zu sehen, ob meine Vermutung richtig ist, lassen Sie die zweite IO-Leitung an den 3 Farbeingängen fallen. Sie können einen VGA-Eingang mit den 75 Ohm sowieso nicht als Teilnehmer an einem Spannungsteiler korrekt ansteuern, daher müssen Sie diese Idee leider vollständig verwerfen. Sie können mit der Verwendung einer richtigen Leiter im DAC-Stil davonkommen, aber selbst dann sind alle Widerstände eine Größenordnung kleiner als 1K und 2K.

Verwenden Sie einfach einen IO-Pin, und ich habe gerade festgestellt, dass dies wahrscheinlich den Einzelpin-Strom übersteigt ... 50 mA, denke ich? Aber das ideale L-Pad für 75 Ω, das 5 V auf 0,7 V dämpft, wäre ein 64 Ω- und 12 Ω-Widerstand in Reihe mit Masse, wobei das Signal zum Display der Abgriff zwischen den beiden ist.

Die Magie hier ist, dass, wenn Sie meine Rundungsfehler entfernen, dies zu einem Widerstand von 75 Ω führt. Das bedeutet angepasste Impedanz. Dass jedes Ende das Äquivalent von 75 Ω vom Signalstift zur Masse hat.

Grundsätzlich sind die Pins Ihres Atmega nicht stark genug, um eine Impedanz von 75 Ω bei voller Leistung bei Betrieb mit 5 V zu treiben, da dies mehr als 50 mA verbrauchen würde. Verwenden Sie stattdessen einfach eine Leiter mit einem ähnlichen Verhältnis, aber nicht so groß. Versuchen Sie es mit 220 Ω und 56 Ω, und das sollte Ihre Signalprobleme lösen und die Signalspannung auf 0,7 V reduzieren, OHNE eine so große unangepasste Impedanz zu haben, dass die meisten Displays nicht einmal richtig funktionieren.

Nicht wirklich. Signalreflexionen durch Impedanzfehlanpassungen sind nicht das Problem. Unzureichende Pegel könnten sein, aber Reflexionen auf den Farblinien würden nur Geisterbilder verursachen, was nicht das ist, was beobachtet wird. Schauen Sie sich Schaltungen an, die tatsächlich dafür verwendet werden, und Sie werden sehen, dass das Problem hier nicht das Fehlen eines Pad-Dämpfungsglieds ist, sondern einfach ein zu hoher Reihenquellenwiderstand. Höchstwahrscheinlich liegt das eigentliche Problem bei den Sync-Signalen, nicht bei den Farben.
Hallo, ja du hast Recht Chris. Es lag an den Sync-Signalen. Die Impedanz war in Ordnung, keine Probleme mit den Widerstandsgrößen. Es lief darauf hinaus, dass jede neue Abtastzeile einen horizontalen Synchronisationsimpuls erfordert, selbst während der Schwarzschulterbereiche. Vielleicht liegt es nur an mir, aber aus der Dokumentation, die ich gelesen hatte, ging dies aus der VGA-Spezifikation nicht hervor. Nachdem ich diese Änderung vorgenommen habe, funktioniert es perfekt auf allen Monitoren, auf denen ich getestet habe. Es ist einfach passiert, dass ein Fernseher, auf dem ich getestet habe, nichts dagegen zu haben schien, wenn H-Sync nicht auf jeder Leitung war!

Werfen Sie einen Blick auf die LTDC-VGA-Timing-Spezifikationen. Bei LCD-TFT-Monitoren ist das Timing sehr ähnlich wie bei Standard-VGA-Monitoren, aber die Signalreihenfolgen sind unterschiedlich.