ebps.de.vu > Schaltungen > AVR > Mini-Composite-Generator

Mini-Composite-Generator

Beschreibung

Bei dieser Schaltung geht es vor allem um die Software. Ein ATTiny11 (der kleinste und billigste verfügbare AVR-Controller) gibt ein Videosignal, das von jedem Fernseher oder Monitor angezeigt werden kann (PAL-B-Standard). Aufgrund des kleinen Controllers sind zwar nur 5 Zeilen Text mit je 10 Zeichen möglich, aber für kurze Nachrichten oder Hinweise reicht es auch so. Trotz der Riesenpixel muss der ATTiny11 sogar noch auf 8 MHz übertaktet werden, um die Daten schnell genug ausgeben zu können.

Mit einer anderen Version der Software können Pixelgrafiken von bis zu 80 Pixeln Breite angezeigt werden (die Textversion verwendet 60 Pixel pro Zeile). Da kein Zeichensatz gespeichert werden muss, steht mehr Programmspeicher für die Grafik zur Verfügung. Für eine Textanzeige kann am PC eine Grafik erzeugt werden, die den gewünschten Text in beliebiger Schriftart und -größe enthält, welche dann in den AVR geschrieben wird.

Aufbau

Die Schaltung ist sehr einfach gehalten und lässt sich in 10 Minuten aufbauen. Da der AVR übertaktet ist, sollten Sie genau darauf achten, zum Quarz passende Kondensatoren zu nehmen (im Zweifelsfall lieber etwas mehr als etwas zu wenig Kapazität), damit der Oszillator trotzdem zuverlässig anschwingt und sauber läuft. Das Videosignal wird einfach durch 2 Widerstände zusammengesetzt, die vom angeschlossenen Monitor mit 75 Ω belastet werden. In einigen Monitoren fehlt dieser Widerstand, in diesem Fall muss er in der Schaltung noch zwischen dem Ausgang und Masse eingebaut werden, damit am Ausgang die richtigen Signalpegel anliegen. Theoretisch könnte man mit diesem Aufbau 4 verschiedene Spannungen ausgeben, tatsächlich werden jedoch nur drei verwendet: Sync (0 V, beide Ports auf Masse), schwarz (0,3 V, nur PB0 (1 kΩ) auf +5V) und weiß (1 V, beide Ports auf +5V). Die vierte Möglichkeit (nur PB1 (390 Ω) auf +5V) ergibt ca. 0,75 V, was einem hellen Grau entspricht. Achtung: für die Grafik-Version werden die Widerstände vertauscht angeschloßen!

Software

Die Software gliedert sich in mehrere Teile, die hier für die Textversion genauer erklärt werden sollen. Aufgrund des kleinen Programmspeichers des ATTiny11, der schon mit dem minimalen Zeichensatz (ASCII-Zeichen 0x20 (Leerzeichen) bis 0x7F (DEL)) zu fast zwei Dritteln belegt ist, waren einige Tricks und "Unschönheiten" notwendig, um alles in den Controller zu bekommen. Aber auch bei der Grafikversion habe ich versucht, so wenig Speicher wie möglich zu verwenden, damit auch große Grafiken angezeigt werden können. Die maximalen Größen sind am Anfang der Assemblerdatei aufgelistet.

Textausgabe

Die Textausgabe erfolgt Zeichenweise in einer Schleife. Jeder Pixel ist 6 Zyklen (0,75 µs) lang, so dass in eine Zeile von 52 µs Länge etwa 11,5 Zeichen passen würden. Mit etwas Abstand zu den Bildrändern, welche oft vom Gehäuse des Monitors verdeckt werden, ergibt sich also eine aktive Zeilenlänge von 45 µs oder 10 Zeichen zu je 6 Pixeln.

Von den 6 Pixeln werden nur 5 für das Zeichen verwendet, der sechste Pixel ist immer dunkel und stellt den Abstand zum nächsten Zeichen dar. Die 6 Zyklen werden wie folgt aufgeteilt:

Da der letzte Pixel immer schwarz ist, ist hier nur ein Zyklus zum Ausgeben nötig, so dass hier die 5 restlichen Zyklen dafür verwendet werden, zu überprüfen, ob die Schleife nochmals durchlaufen werden muss und ggf. wieder an den Anfang zu springen. Außerdem wird hier das nächste Byte aus dem Zeichensatz im Programmspeicher geladen, da dieser Befehl 3 Zyklen benötigt und deshalb nicht zwischen 2 normale Pixel passt. Während eine Zeile des ersten Zeichens ausgegeben wird, wird also gleichzeitig das nächste bereits Schritt für Schritt vorbereitet:

Ein weiteres Problem bereitet der geringe SRAM-Speicher des Controllers - es gibt nur die 32 Standardregister! Um trotzdem nicht auch die ASCII-Codes der Zeichen noch aus dem Programmspeicher laden zu müssen (was zu lange dauern würde), wird die freie Zeit in der "Abstandszeile", die dazu über statt wie sonst üblich unter die Textzeile gelegt wurde dazu genutzt, die folgende Textzeile in die Register R1 bis R10 (allerdings rückwärts) zu laden, die anschließend wie SRAM in größeren Controllern mit dem 'ld'-Befehl wieder geladen werden kann.

HSYNC/Zeilenausgabe

Um das Programm etwas zu vereinfachen, wird der Beginn jeder Zeile durch einen Timer-Interrupt gesteuert. Da die Hauptschleife des Programms leer ist, also nur aus einem einzelnen Sprungbefehl besteht (Dauer: 2 Zyklen), ist ein maximaler Jitter von einem Zyklus möglich. Durch geschicktes Vertauschen von Befehlen ist es mir jedoch gelungen, den Jitter vollständig zu eliminieren, so dass das Bild völlig still steht.

Im Interrupt wird zunächst der Zeilenzähler erhöht, anschließend wird anhand der Zeilennummer überprüft, ob das Bild bereits vollständig aufgebaut wurde und nun der vertikale Synchronisierungsimpuls folgen muss oder es sich noch um eine normale Bildzeile handelt. In einer normalen Bildzeile wird zunächst der Timer für den nächsten Sync-Impuls neu geladen und der horizontale Synchronisierungsimpuls von 4,7 µs Dauer ausgegeben, anschließend wird eine Weile schwarz ausgegeben, bis die Stelle für das erste Zeichen erreicht wurde. Während der Wartezeit werden bereits mehrere Berechnungen und Überprüfungen vorgenommen:

Vor dem Verlassen der Interruptroutine wird dann noch der Zähler für die Zeile innerhalb des Zeichens erhöht. Da jedoch jede Zeichen-Zeile mehrere Bildschirmzeilen belegt (andernfalls würden die Zeichen extrem flach werden), gibt es dafür auch noch einen Zähler. Wird der Textzeilenzähler von "Zeile 0" (interner Wert 7) auf "Zeile 1" (interner Wert 6) erhöht, so wird außerdem die Textzeile aus dem Programmspeicher rückwärts in die Register R10 bis R1 geladen (es wurden ja in Zeile 0 keine Zeichen ausgegeben, es ist also noch viel Zeit bis zum Anfang der nächsten Zeile).

VSYNC/Halbbilder

Nach allen Zeilen muss ein vertikaler Synchronisierungsimpuls ausgegeben werden. Dieser ist wesentlich komplizierter als der horizontale, da er 2,5 Zeilen (160 µs) lang ist und deshalb durch horizontale Synchronisierungsimpulse unterbrochen werden muss (genaugenommen handelt es sich also um eine Impulsfolge). Hinzu kommt, dass es wegen der unterschiedlichen Halbbilder (ein Halbbild beginnt mit einer halben Zeile, das andere endet mit der anderen Hälfte dieser Zeile) auch noch zwei leicht unterschiedliche VSYNC-Impulse gibt. Der Einfachheit halber habe ich mich in diesem Projekt jedoch auf einen der beiden Impulse beschränkt, was auch problemlos zu funktionieren scheint, so dass ich auch keine halben Zeilen senden muss. Eine Unterscheidung zwischen den beiden Halbbildern gibt es nicht, sie sind beide völlig identisch (ein 7 Zeilen hoher Pixel ist also auf dem Bildschirm eigentlich sogar 14 Zeilen hoch!).

Nach dem VSYNC-Impuls werden alle Register (Zeilenzähler, Text-Zeichen-Zeiger, Textzeilen-Zähler und -Teiler) wieder auf die Anfangswerte zurückgesetzt. Da die Ausgabe der Impulsfolge länger als eine Zeile (64 µs) dauert, wird vor einem VSYNC-Impuls der Timer so neu geladen, dass der Timer-Interrupt erst wieder aufgerufen wird, wenn der erste HSYNC-Impuls des neuen Halbbilds "fällig" wird.

Einstellungen

Um das Programm Ihren Vorstellungen anzupassen, habe ich am Anfang einige Parameter einstellbar gemacht. Zunächst kann die Anzahl Textzeilen ("Lines") eingestellt werden, alle weiteren Werte (Bildzeilen pro Pixel "LineDivInit", erste aktive Bildschirmzeile "FirstLine" und letzte Bildschirmzeile "LastLine") werden automatisch daraus berechnet, können aber alternativ auch selbst angegeben werden. Mehr als 5 Textzeilen sind allerdings nicht möglich, da der Programmspeicher nicht ausreicht, um mehr Text festzulegen. Darüber hinaus sind noch die beiden Ports für das Sync-Signal (1 kΩ) und das Datensignal (390 Ω) einstellbar, es kommen aber ohnehin nur PB0 bis PB2 in Frage, da PB3 und PB4 vom Quarz verwendet werden und PB5 (Reset-Pin) nicht als Ausgang verwendet werden kann.

Der Text kann ganz unten in der Assemblerdatei eingestellt werden. Nicht benötigte Zeichen und Zeilen müssen mit Leerzeichen aufgefüllt werden. Der Zeichensatz befindet sich in einer eigenen Datei und besteht aus 7 Blöcken (einen pro Pixelzeile), die sich aus je 96 Bytes zusammensetzen (Zeichen 0x20 bis 0x7F). Es werden von jedem Byte nur die 5 niedrigsten Bits verwendet, das LSB ist der Pixel ganz rechts (schreibt man die 7 Bytes für ein Zeichen als Binärwerte untereinander, kann man das Zeichen also direkt erkennen).

Unterschiede bei der Grafikversion

Die Grafikversion ist sehr ähnlich wie die Textversion aufgebaut, benötigt jedoch keinen Zeichensatz, wodurch einige Befehle eingespart werden konnten. Zusammen mit dem "Trick", PB0 als Ausgang für die Pixeldaten zu verwenden, war es deshalb möglich, die Pixelbreite auf 5 Zyklen (625 l;ns) zu reduzieren und gleichzeitig ohne schwarzen "Ladepixel" auszukommen. Auch der Zwischenspeicher im RAM ist nicht mehr nötig (oder möglich, da es keine leeren Abstandszeilen gibt), die Daten werden bei jedem Zeilendurchlauf immer neu aus dem Flash-Speicher geladen. Die Wartezeit vor dem Beginn des genutzten Zeilenbereichs ist variabel und wird automatisch aus der eingestellten Breite des Bildes und der daraus resultierenden Pixelbreite berechnet, damit Bilder, die nicht mit voller Breite dargestellt werden (z.B. verwenden 72 und 80 Pixel breite Bilder 5 Zyklen/Pixel, da mit 6 Zyklen/Pixel nur noch 64 (eigentlich 66) Pixel in die Zeile passen) zentriert angezeigt werden.

Natürlich gibt es auch ein paar andere Einstellungen: am Anfang des Programms wird die Breite und Höhe der anzuzeigenden Grafik eingestellt (die Breite muss durch 8 teilbar sein), die darauf folgenden Angaben werden automatisch berechnet. Die Grafik selbst wird ganz am Ende eingebunden. Um aus einer gewöhnlichen Pixelgrafik auf dem PC (BMP, PNG, JPG, ...) eine Assemblerdatei zu erzeugen, enthält das Archiv mit der Software ein Perl-Script, welches eine Pixelgrafik konvertieren kann. Die Assemblerdatei enthält einfach Byte-Werte, wobei jedes Byte 8 nebeneinanderliegende Pixel enthält (MSB = linkester Pixel). Das Perl-Script benötigt die ImageMagick-Bibliotheken.

Fotos

[Foto]
800 | 843
Mein Aufbau auf einem Reststück Lochrasterplatine.
[Foto]
800 | 1013
Oszillogramm einer Zeile. Die Stellen, an denen die 10 Zeichen ausgegeben werden, sind ganz oben deutlich zu erkennen, bei genauem Hinsehen kann man sogar die Unterteilung in 5 Pixel sehen.
[Foto]
800 | 1021
Zeitliche Dehnung des obigen Oszillogramms. Jetzt sind die Pixel deutlich zu sehen (es sieht hier so aus, als wären alle Pixel gleichzeitig weiß und schwarz, da auf dem Analogoszilloskop alle Zeilen übereinander angezeigt werden).
[Foto]
800 | 1015
Oszillogramm der VSYNC-Impulsfolge, bestehend aus 5 Vortrabanten (kurz 0 V), 5 Hauptimpulsen (lang) und 5 Nachtrabanten (wieder kurz). Der erste und der letzte Punkt auf der 0 V-Zeile sind gewöhnliche HSYNC-Impulse. Das Oszillogramm wurde mit einer alten Softwareversion erstellt, bei der die Zeit zwischen VSYNC und dem ersten HSYNC zu lang war.
[Foto]
640
Der Testtext, wie er mit einer TV-Karte als Monitorersatz angezeigt wird.
[Foto]
800 | 1280
Grafikversion mit einem 64x48-Pixel-Bild auf einem alten Monochrommonitor.
[Foto]
800 | 1280
Grafikversion mit einem 80x72-Pixel-Bild. In dieser Auflösung wird der Bildschirm voll ausgenutzt, die letzte Zeile ist bereits fast vollständig unter dem Rand verschwunden. Die hellen Punkte rechts sind übrigens keine Anzeigefehler, sondern Reflektionen ;-).

Links