Zeilensynchronisation: Jede 64 us muss ein Signal mit der Spannung 0,0 V gesendet werden, welches dafür sorgt, dass der Kathodenstrahl der Fernehröhre vom rechten Rand zurück an den linken springt. Damit dieser Rücksprung für den Betrachter nicht sichtbar wird, muss der Kathodenstrahl in dieser Zeit ausgetastet werden; dieses Austasten erfolgt automatisch, wenn die Signalspannung 0,3 V oder weniger beträgt.
Bildsignal: An das Zeilensynchronisationssignal schließt sich das Bildsignal an; mit Spannungen von 0,3 bis 1,0 V werden die Helligkeitsinformationen für den Kathodenstrahl übertragen.
Bildsynchronisation: Ein Bild besteht aus 625 Zeilen; die Zeilen werden vom Kathodenstrahl nicht der Reihe nach abgefahren, sondern im so genannten Zeilensprung-Verfahren. Für das erste Halbbild werden die Zeilen 1, 3, 5, ... auf die Mattscheibe gezeichnet, für das zweite Halbbild die Zeilen 2, 4, 6, ... Insgesamt werden für ein vollständiges Bild 625 Zeilen in 1/25 s übertragen. Um den Kathodenstrahl wieder nach oben zu schicken, wird eine komplexe Folge von Signalen (den Hauptimpulsen mit ihren Vor- und Nachtrabanten) gesendet. Auch bei diesen Signalen ist die Spannung stets kleiner als 0,3 V, so dass der Sprung des Strahls von unten nach oben vom Betrachter nicht gesehen werden kann.
Nun wollen wir mit dem Mikrocontroller ein BAS-Signal erzeugen, welches eine Minmal-Grautreppe auf dem Bildschirm erzeugt. "Minimal-Grautreppe" soll hier heißen: Es werden 3 vertikale Balken erzeugt, ein schwarzer (mit 0,3 V), ein grauer (mit 0,7 V) und ein weißer (mit 0,8 V). Der Mikrocontroller muss dafür die vier verschiedenen Spannungen 0 V, 0,3 V, 0,7 V und 1,0 V erzeugen. Dazu benutzen wir die folgende Schaltung, welche an die Ports B.0 und B.1 sowie an Masse angeschlossen wird. Der Widerstand R_AV steht für den Eingangswiderstand der AV-Buchse; er wird nur für den Fall benötigt, dass man die erzeugten Signale mit einem Oszilloskop studieren möchte. Wenn Die Schaltung an den Fernseher angeschlossen wird, muss dieser Widerstand entfernt werden.
Kommen wir jetzt zur Software. Ich habe das Programm mit BASCOM geschrieben. Entscheidende Idee ist hier: Damit das Programm das Timing für die einzelnen Zeilen genau einhält, benutze ich hier die TIMER1-Komponente des Atmega328. Damit kann man in Zeitabständen von 64 us Interrupts auslösen, die über einen zugehörigen Interrupt-Handler die einzelnen Zeilen (inkl. Zeilensynchronisation) und auch die Bildsynchronisationspulse erzeugen.
Der Aufbau dieses Interrupt-Handlers ist in der folgende Übersicht schematisch dargestellt:
Details des Programms kann man dem folgenden ausführlich kommentierten Code-Bock entnehmen; insbesondere wird hier auch dargestellt, wie der TIMER1 passen konfiguriert wird.
Code: Alles auswählen
' grautreppe1.BAS-Datei für Nano-Board : Version 1.1 vom 28.01.2020
'
' Zeigt auf einem Fernseher eine einfache Grautreppe an.
'
' Infos zur Beschaltung:
' ___
' PB.0 ---|___|---o-----> zum AV-Eingang des Fernsehers
' 1k |
' ___ |
' PB.1 ---|___|---'
' 420
'
' PB.1 PB.0 BAS-Signal in V
' 0 0 0,0
' 0 1 0,3
' 1 0 0,7
' 1 1 1,0
'
' Zur Vereinfachung unterscheiden wir hier nicht zwischen dem 1. und 2. Halbbild!
'
'----------------------------------------------------------------------------
$regfile = "m328pdef.dat" 'Dadurch wird auch das Pin Layout bestimmt
$crystal = 16000000
$framesize = 32
$swstack = 32
$hwstack = 64
'**********************************************************
'******************* Deklarationen ************************
Dim Hsync_64_us As Word
Dim Vsync_32_us As Word
Dim Hsync_duration_1 As Word
Dim Hsync_duration_2 As Word
Dim Vsync_duration_1 As Word
Dim Vsync_duration_2 As Word
Dim Hoffset0 As Word 'Position des linken Randes vom grauen Streifen
Dim Hoffset1 As Word 'Position des linken Randes vom schwarzen Streifen
Dim Width As Word
Dim Synccounter As Word 'zählt die Zeilen eines Halbbildes
Dim Vsync_line As Word
Dim Vsync_pre_trabant As Word
Dim Vsync_main As Word
Dim Vsync_post_trabant As Word
'****************** Initialisierung ***********************
Ddrb = &B00000011 'Ports B.0 u. B.1 als Ausgang
Portb = 1 'schwarz (0,3 V)
Hsync_64_us = 1024 - 16 'Zeilenlänge 1024 Takte = 64 us; Aufruf der ISR dauert ca. 1 us (kein leichtes Flackern mehr!)
Vsync_32_us = 512 - 16 'Vertikalsignallaenge 512 Takte; Aufruf der ISR dauert ca. 1 us (kein leichtes Flackern mehr!)
Hsync_duration_1 = 1 'Warten vor Horizontalsynchronpuls
Hsync_duration_2 = 4 'Horizontalsynchronpulsdauer 5 us
Vsync_duration_1 = 2 'Vertikalsynchronpulsdauer 2 us
Vsync_duration_2 = 27 'Vertikalsynchronpulsdauer 27 us
Vsync_line = 305 'nach VSync_line - 1 Zeilen erfolgt VSync ---- ggf. nur 305 <<<<<<<<<<<<<<<<<<<<<<<<<<
Vsync_pre_trabant = Vsync_line + 5 'Signalnummern...
Vsync_main = Vsync_pre_trabant + 5
Vsync_post_trabant = Vsync_main + 4
Tccr1b = &B00000001 'Start von Timer1 mit Prescale = 1; d. h. 1 Timer-Takt = 1/16 us
Timsk1.ocie1a = 1 'Interrupt bei CompA (nicht einfach TIMSK wie beim Attiny)
On Compare1a Isr_compare Nosave
'**********************************************************
'******************** Hauptprogramm ***********************
Hoffset0 = 500 'Beginn des grauen Streifens; min. 192 (Ende von HSync-Phase) + 1
Hoffset1 = 750 'Beginn des weißen Streifens; max. ca 1000
Synccounter = 0 'zählt Synchronpulse : Start bei 0
Compare1a = Hsync_64_us 'entspricht Zeilenlänge, 1024 Timer-Takte = 64 us
Enable Interrupts
Do
'Nur auf Interrupts warten...
Loop
'**********************************************************
'******************* Unterprogramme ***********************
'**********************************************************
'******************Interruptroutinen***********************
Isr_compare:
Timer1 = 0
Portb = 1
Incr Synccounter
'//////// Zeilensynchronisation: Normale Zeile ///////////////
If Synccounter < Vsync_line Then 'Signalnummer 1 bis 305
' Waitus Hsync_duration_1 'Schwarzschulter; Pause von ca. 3 us durch vorherige Befehle
Portb.0 = 0 'Zeilensynchronisationssignal einschalten
Waitus Hsync_duration_2
Portb.0 = 1 'Zeilensynchronisationssignal ausschalten; dazu kommt noch Schwarzteil bis Timer1 Hoffset0 erreicht
Do
Loop Until Timer1 >= Hoffset0 'Auf linken Streifenrand (Grau) warten
Portb = 2 'jetzt U = 0,7 V (grau)
Do
Loop Until Timer1 >= Hoffset1 'rechter Rand des grauen Streifens
Portb = 3 'linker Rand des weißen Streifens; jetzt U = 1,0 V
' Spannung 1 V bleibt bis zum nächsten Interrupt, d. h. bis zum Zeilenende
Goto Ende
End If
'////////// Bildsynchronisationssignale /////////////
Portb = 1 'schwarz; 0,3 V
Compare1a = Vsync_32_us 'halbe Zeile
'///////////VORTRABANT////////////
If Synccounter < Vsync_pre_trabant Then 'Signalnummer 306 bis 310
Portb = 0
Waitus Vsync_duration_1
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
Goto Ende
End If
'///////////HAUPT///////////
If Synccounter < Vsync_main Then 'Signalnummer 311 bis 315
Portb = 0
Waitus Vsync_duration_2
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
Goto Ende
End If
'///////////NACH_TRABANT///////////
Portb = 0 'Signalnummer 316 bis 320
Waitus Vsync_duration_1
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
'//////////Ende eines Halb-Bildes///////
If Synccounter >= Vsync_post_trabant Then 'im Anschluss an Signalnummer 320
Synccounter = 0
Compare1a = Hsync_64_us
End If
Ende:
Return
Und das Bild auf dem Fernseher so:
Genauere und weiterführende Informationen zum Thema BAS, FBAS und PAL finden Sie hier
######################################################################################################
Erzeugen von BAS-Signalen zu Texten
Nun wollen wir als Beispiel für eine komplexere Bild-Struktur eine Textzeile auf den Bildschirm unseres Fernsehers bringen:
In diesem Bild bestehen die einzelnen Zeichen aus Mustern mit einer Breite von 8 Pixeln. Wir wissen bereits, dass die Übertragung der Bildinformation einer einzigen Zeile in der Zeitspanne von 52 μs erfolgt. Um 8 Pixelwerte zu übertragen, steht also nur sehr wenig Zeit zur Verfügung. Dies ist das entscheidende Problem, das es zu lösen gilt.
Im Internet findet man eine Reihe von fertigen Programmen, die solches leisten. Leider sind sie nur knapp kommentiert. Außerdem sind sie wegen der Zeitproblematik oft vollständig in Assembler geschrieben. Dies sorgt dafür, dass es nicht ganz einfach ist, sie nachzuvollziehen.
Hier soll nun zunächst ein Programm vorgestellt werden, welches vollständig in BASCOM geschrieben ist. Dabei ist es uns weniger wichtig, ein perfektes Programm mit möglichst großem Funktionsumfang vorzustellen; vielmehr wollen wir hier ausführlich die zugrunde liegenden Ideen deutlich machen.
In der obigen Abbidldung ist das Muster für das Zeichen A zu sehen. In der ersten und letzten Zeile sowie in der rechten Spalte sind keine Pixel gesetzt (d. h. schwarz). Dies gilt für alle Muster unserer Zeichen; dadurch werden die Zeichen auf jeden Fall getrennt dargestellt, auch wenn der Pixel-Strom ohne Unterbrechung erfolgen sollte.
Die einzelnen Pixelzeilen können wir als binäre Kodierung einer Zahl ansehen. Die Zeile 3 können wir z. B. durch die Zahl &B01101100 kodieren. Die Ziffer 1 steht führt ein schwarzes Pixel, die Ziffer 0 für ein weißes. Dieses Byte können wir in einer Byte-Variablen "Textzeilenmuster" abspeichern.
Nun gilt es, mit den einzelnen Bits dieser Variablen möglichst rasch das zugehörige Bildsignal zu erzeugen. Wenn wir dieses Signal z. B. an PortB.3 ausgeben wollen, dann könnte man dazu im Prinzip folgendes Programmteil benutzen:
Code: Alles auswählen
For J = 7 To 0 Step -1
PortB.3 = Textzeilenmuster.J
Next J
Leider dauert die Ausführung dieser Schleife viel zu lange.
Glücklicherweise besitzt der Atmega328 einen SPI-Peripheriebaustein (SPI = Serial Peripheral Interface); mit dessen Hilfe lassen sich die Bits eines Registers sehr rasch und auch mit konstanter Geschwindigkeit ausgeben. Für unsere Zwecke stark vereinfacht ist dieser Baustein so aufgebaut wie in der folgenden Abbildung:
Für unsere Zwecke konfigurieren wir diesen Baustein folgendermaßen:
Spcr = &B01010000
Spsr.spi2x = 1
Dadurch wird das SPI als Master definiert und eingeschaltet; außerdem wird festgelegt, dass das MSB als erstes aus dem Register geschoben wird. Die Taktung des Schieberegister wird auf maximale Geschwindigkeit eingestellt, in unserem Fall (Nano-Board mit 16 MHz) erfolgen die 8 Takte in genau 1 μs.
Sobald nun eine Zahl mit dem Befehl
SPDR = Textzeilenmuster
in das Schieberegister SPDR geladen wird, werden die Bits automatisch (d. h. ohne weiteres Dazutun der ALU) über den so genannten MOSI-Ausgang (PortB.3) ausgegeben. Dazu muss dieser Anschluss als Ausgang konfiguriert sein. Ferner muss auch der Slave-Select-Pin \SS (PortB.2) als Ausgang festgelegt werden (vgl. Abschnitt 18.3.2 des Manuals). Über die Clock-Leitung können wir bei Bedarf (Aufnahme von Signaldiagrammen) das Taktsignal ausgeben; in diesem Fall muss dann natürlich zusätzlich auch PortB.5 zum Ausgang gemacht werden.
Den DA-Wandler aus unserem Grautreppen-Projekt schließen wir nun nicht mehr an die Ports B.0 und B.1, sondern an die Ports B.0 und B.3 an.
Hat B.0 den Wert 1, dann kann man nun mit B.3 die Helligkeit steuern: B.3 = 0 erzeugt mit 0,3 V den Helligkeitswert Schwarz, und B.3 = 1 erzeugt Weiß. Wenn das Schieberegister nicht aktiv ist, wird also Weiß angezeigt. Nun führt die bislang gewählte Art der Kodierung des Zeichenmuster aber dazu, dass die Schrift in Weiß auf schwarzen Grund erscheint. Dies korrigieren wir ganz einfach, indem wir nicht Textzeilenmuster, sondern das Inverse davon, also not Textzeilenmuster, an das Schieberegister SPDR übergeben.
Nachdem nun geklärt ist, wie ein einzelnes Byte des Bildmusters übertragen werden kann, gilt es, diese Muster in der richtigen Reihenfolge auszusenden; außerdem darf nicht vergessen werden, die passenden Synchronisationssignale für den Zeilen- und den Bildwechsel zu senden.
Im Wesentlichen benutzen wir dasselbe Programm wie beim Erzeugen der Grautreppe. Lediglich der Programmteil, in welchem die Übertagung der Bildinformationen erfolgt, muss ausgetauscht werden. Dazu ist es sinnvoll, im Vorfeld (d. h. noch bevor die Timer1-Interrupts aktiviert werden) die einzelnen Textzeilenmuster in einem Array abzulegen. Weitere Einzelheiten entnimmt man dem folgenden Source-Code und den ausführlicheren Erläuterungen an dieser Stelle:
Code: Alles auswählen
' bas-zeichenkette1.BAS-Datei für Nano-Board : Version 1.1 vom 28.01.2020
'
' Zeigt auf einem Fernseher eine einfache Grautreppe an.
'
' Infos zur Beschaltung:
' ___
' PB.0 ---|___|---o-----> zum AV-Eingang des Fernsehers
' 1k |
' ___ |
' PB.3 ---|___|---'
' 420
'
' PB.3 PB.0 BAS-Signal in V
' 0 0 0,0
' 0 1 0,3
' 1 0 0,7
' 1 1 1,0
'
' Zur Vereinfachung unterscheiden wir hier nicht zwischen dem 1. und 2. Halbbild!
' Programm wie bei bas_zeichenkette3.bas, aber: Bei SPI keine Schleife, sondern 20? Einzel-Befehle; Text-String immer mit Leerzeichen auffüllen!
' ggf. Schleife in Assembler?
' Funktioniert mit max. 14 Zeichen; ab 15 Zeichen Zeit für Bild-Zeile zu knapp!
' 03.02.2020
'
'----------------------------------------------------------------------------
$regfile = "m328pdef.dat" 'Dadurch wird auch das Pin Layout bestimmt
$crystal = 16000000
$framesize = 50 '32
$swstack = 50 '32
$hwstack = 100 '64
$baud = 9600 'zu Testzwecken
'**********************************************************
'******************* Deklarationen ************************
Dim Hsync_64_us As Word
Dim Vsync_32_us As Word
Dim Hsync_duration_1 As Word
Dim Hsync_duration_2 As Word
Dim Vsync_duration_1 As Word
Dim Vsync_duration_2 As Word
Dim Hoffset0 As Word 'Position des linken Randes vom grauen Streifen
Dim Hoffset1 As Word 'Position des linken Randes vom schwarzen Streifen
Dim Width As Word
Dim Synccounter As Word 'zählt die Zeilen eines Halbbildes
Dim Vsync_line As Word
Dim Vsync_pre_trabant As Word
Dim Vsync_main As Word
Dim Vsync_post_trabant As Word
Dim Lc As Byte 'Zeilenzähler eines Buchstabens
Dim J As Word
Dim J0 As Word
Dim J1 As Word
Dim Tmp As Word
Dim Text As String * 23 '23 Zeichen
Dim Buchstabenoffset(23) As Word '23 Zeichen
Dim Eintrag As Byte
Dim Buchstabe As String * 1
Dim Code As Byte
Dim Adresse As Word
Dim Laenge As Byte
Dim Textzeilenmuster(460) As Byte 'max. Anzahl der Zeichen * 20
Dim Znr As Byte
Dim Musternr As Word
Dim Restlaenge As Byte
Dim Restzk As String * 23 '23 Zeichen
'****************** Initialisierung ***********************
Ddrb = 255 'Ports B.0 u. B.3 als Ausgang; B.3=MOSI
Portb = 1 'schwarz (0,3 V)
Hsync_64_us = 1024 - 16 'Zeilenlänge 1024 Takte = 64 us; Aufruf der ISR dauert ca. 1 us (kein leichtes Flackern mehr!)
Vsync_32_us = 512 - 16 'Vertikalsignallaenge 512 Takte; Aufruf der ISR dauert ca. 1 us (kein leichtes Flackern mehr!)
Hsync_duration_1 = 1 'Warten vor Horizontalsynchronpuls
Hsync_duration_2 = 4 'Horizontalsynchronpulsdauer 5 us
Vsync_duration_1 = 2 'Vertikalsynchronpulsdauer 2 us
Vsync_duration_2 = 27 'Vertikalsynchronpulsdauer 27 us
Vsync_line = 305 'nach VSync_line - 1 Zeilen erfolgt VSync ---- ggf. nur 305 <<<<<<<<<<<<<<<<<<<<<<<<<<
Vsync_pre_trabant = Vsync_line + 5 'Signalnummern...
Vsync_main = Vsync_pre_trabant + 5
Vsync_post_trabant = Vsync_main + 4
Hoffset0 = 192 'Beginn des Bildes (Textes); min. 192 (Ende von HSync-Phase) + 1
Synccounter = 0 'zählt Synchronpulse : Start bei 0
Tccr1b = &B00000001 'Start von Timer1 mit Prescale = 1; d. h. 1 Timer-Takt = 1/16 us
Timsk1.ocie1a = 1 'Interrupt bei CompA (nicht einfach TIMSK wie beim Attiny)
On Compare1a Isr_compare Nosave
'**********************************************************
'******************** Hauptprogramm ***********************
'Eingabe des Textes
Text = "ABCDEFGHIKLMNOPQRSTUVWX" 'max. 23 Zeichen
Text = Ucase(text)
Laenge = Len(text)
If Laenge < 23 Then
Restlaenge = 23 - Laenge
Restzk = Space(restlaenge)
Text = Text + Restzk
Laenge = Len(text)
End If
'Textzeilenmuster-Array erzeugen
For J = 1 To Laenge
Buchstabe = Mid(text , J , 1)
If Buchstabe = " " Then Code = 91 - 65 Else Code = Asc(buchstabe) - 65 'Leerzeichen bei DATA hinter Z
Buchstabenoffset(j) = Code * 10
Next J
For Lc = 0 To 19
Znr = Lc
Shift Znr , Right 'Halbierung
For J = 1 To Laenge
Musternr = Lc * Laenge
Musternr = Musternr + J
Tmp = Buchstabenoffset(j) + Znr
Eintrag = Lookup(tmp , Muster)
Textzeilenmuster(musternr) = Not Eintrag
Next J
Next Lc
'Jetzt geht es los...
Lc = 0 'Buchstaben-Line-Counter resetten
Compare1a = Hsync_64_us 'entspricht Zeilenlänge, 1024 Timer-Takte = 64 us
Enable Interrupts
Do
'Nur auf Interrupts warten...
Loop
'**********************************************************
'******************* Unterprogramme ***********************
'**********************************************************
'******************Interruptroutinen***********************
Isr_compare:
Timer1 = 0
Portb = 1
Incr Synccounter
'//////// Zeilensynchronisation: Normale Zeile ///////////////
If Synccounter < Vsync_line Then 'Signalnummer 1 bis 305
' Waitus Hsync_duration_1 'Schwarzschulter; Pause von ca. 3 us durch vorherige Befehle
Portb.0 = 0 'Zeilensynchronisationssignal einschalten
Waitus Hsync_duration_2
Portb.0 = 1 'Zeilensynchronisationssignal ausschalten; dazu kommt noch Schwarzteil bis Timer1 Hoffset0 erreicht
'Vorbereitung der Bilderzeugung
J0 = Lc * Laenge
Do
Loop Until Timer1 >= Hoffset0 'Auf linken Rand (schwarz) warten
'Bilderzeugung
Spcr = &B01010000 'Fosc/2, SPI enabled, SPI Master
Spsr.spi2x = 1
' 23 Zeichen; keine Schleife, um Zeit zu sparen
J = J0 + 1
Spdr = Textzeilenmuster(j) 'Schieben des Bytes nach B.3 erfolgt in 16 Zyklen, d. h. in 1 us
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spdr = Textzeilenmuster(j)
Incr J
Spcr = 0 'SPI aus
Portb.3 = 0 'Rest schwarz
Incr Lc
If Lc > 19 Then Lc = 0
Goto Ende
End If
'////////// Bildsynchronisationssignale /////////////
Portb = 1 'schwarz; 0,3 V
Compare1a = Vsync_32_us 'halbe Zeile
'///////////VORTRABANT////////////
If Synccounter < Vsync_pre_trabant Then 'Signalnummer 306 bis 310
Portb = 0
Waitus Vsync_duration_1
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
Goto Ende
End If
'///////////HAUPT///////////
If Synccounter < Vsync_main Then 'Signalnummer 311 bis 315
Portb = 0
Waitus Vsync_duration_2
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
Goto Ende
End If
'///////////NACH_TRABANT///////////
Portb = 0 'Signalnummer 316 bis 320
Waitus Vsync_duration_1
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
'//////////Ende eines Halb-Bildes///////
If Synccounter >= Vsync_post_trabant Then 'im Anschluss an Signalnummer 320
Synccounter = 0
Compare1a = Hsync_64_us
Lc = 0
End If
Ende:
Return
'********************** DATA ************************************
'Zeichenmuster aus muster6.mst
Muster:
Data 0 'Dummy, damit Zählung der Musterbytes bei 1 beginnt
Data 0 , 56 , 108 , 198 , 198 , 254 , 254 , 198 , 198 , 0 'A
Data 0 , 248 , 252 , 204 , 248 , 252 , 198 , 254 , 252 , 0 'B
Data 0 , 124 , 254 , 192 , 192 , 192 , 192 , 254 , 124 , 0 'C
Data 0 , 240 , 252 , 206 , 198 , 198 , 206 , 252 , 240 , 0 'D
Data 0 , 254 , 254 , 192 , 240 , 240 , 192 , 254 , 254 , 0 'E
Data 0 , 254 , 254 , 192 , 240 , 240 , 192 , 192 , 192 , 0 'F
Data 0 , 124 , 254 , 192 , 222 , 222 , 204 , 252 , 120 , 0 'G
Data 0 , 198 , 198 , 198 , 254 , 254 , 198 , 198 , 198 , 0 'H
Data 0 , 126 , 24 , 24 , 24 , 24 , 24 , 24 , 126 , 0 'I
Data 0 , 126 , 126 , 12 , 12 , 12 , 12 , 124 , 56 , 0 'J
Data 0 , 204 , 216 , 240 , 224 , 240 , 216 , 204 , 198 , 0 'K
Data 0 , 192 , 192 , 192 , 192 , 192 , 192 , 254 , 254 , 0 'L
Data 0 , 198 , 238 , 254 , 214 , 198 , 198 , 198 , 198 , 0 'M
Data 0 , 198 , 198 , 230 , 246 , 222 , 206 , 198 , 198 , 0 'N
Data 0 , 56 , 124 , 198 , 198 , 198 , 198 , 124 , 56 , 0 'O
Data 0 , 248 , 252 , 198 , 252 , 248 , 192 , 192 , 192 , 0 'P
Data 0 , 56 , 124 , 198 , 198 , 198 , 206 , 124 , 58 , 0 'Q
Data 0 , 248 , 252 , 198 , 252 , 248 , 216 , 204 , 198 , 0 'R
Data 0 , 124 , 206 , 192 , 124 , 62 , 134 , 204 , 120 , 0 'S
Data 0 , 126 , 126 , 24 , 24 , 24 , 24 , 24 , 24 , 0 'T
Data 0 , 198 , 198 , 198 , 198 , 198 , 198 , 254 , 122 , 0 'U
Data 0 , 198 , 198 , 198 , 108 , 108 , 40 , 56 , 56 , 0 'V
Data 0 , 198 , 198 , 198 , 198 , 214 , 214 , 124 , 40 , 0 'W
Data 0 , 198 , 198 , 108 , 56 , 56 , 108 , 198 , 198 , 0 'X
Data 0 , 102 , 102 , 60 , 60 , 24 , 24 , 24 , 24 , 0 'Y
Data 0 , 254 , 254 , 12 , 24 , 48 , 96 , 254 , 254 , 0 'Z
Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 'Leerzeichen
ld r16, x+
wird z. B. der Inhalt des vom Index-Register X indizierten Speichers in das Register r16 geladen; anschließend wird der Index automatisch um 1 erhöht. Dies alles geschieht innerhalb von nur 2 Mikrocontroller-Takten, beim Nano-Board also innerhalb von 1/8 μs.
Bei BASCOM lässt sich Assembler-Code problemlos in den BASIC-Code einfügen. Das gesamte Programm zur Darstellung einer Bildzeile sieht nun so aus:
Code: Alles auswählen
' bas-zeichenkette_1_asm.BAS-Datei für Nano-Board : Version 6.1 vom 10.02.2020
'
' Zeigt auf einem Fernseher eine Zeichenfolge mit maximal 40 Zeichen (A, ..., Z und Leerzeichen).
' Die Muster für die einzelnen Buchstaben stehen in den DATA-Zeilen am Ende des Programms.
' Diese wurden mit dem Programm "zeichengenerator.exe" erzeugt.
'
' Infos zur Beschaltung:
' ___
' PB.0 ---|___|---o-----> zum AV-Eingang des Fernsehers
' 1k |
' ___ |
' PB.3 ---|___|---'
' 420
'
' PB.3 PB.0 BAS-Signal in V
' 0 0 0,0
' 0 1 0,3
' 1 0 0,7
' 1 1 1,0
'
' Zur Vereinfachung unterscheiden wir hier nicht zwischen dem 1. und 2. Halbbild!
' Programm wie bei bas_zeichenkette3.bas, aber zur Beschleunigung wird die SPI-Ausgabe-Schleife mit Assembler-Befehlen durchgeführt
' Funktioniert mit max. 40 Zeichen
' 10.02.2020
'
'----------------------------------------------------------------------------
$regfile = "m328pdef.dat" 'Dadurch wird auch das Pin Layout bestimmt
$crystal = 16000000
$framesize = 50
$swstack = 50
$hwstack = 100
'**********************************************************
'******************* Deklarationen ************************
Dim Hsync_64_us As Word
Dim Vsync_32_us As Word
Dim Hsync_duration_1 As Word
Dim Hsync_duration_2 As Word
Dim Vsync_duration_1 As Word
Dim Vsync_duration_2 As Word
Dim Hoffset0 As Word 'Position des linken Randes vom grauen Streifen
Dim Hoffset1 As Word 'Position des linken Randes vom schwarzen Streifen
Dim Width As Word
Dim Synccounter As Word 'zählt die Zeilen eines Halbbildes
Dim Vsync_line As Word
Dim Vsync_pre_trabant As Word
Dim Vsync_main As Word
Dim Vsync_post_trabant As Word
Dim Lc As Byte 'Zeilenzähler eines Buchstabens
Dim J As Word
Dim J0 As Word
Dim Tmp As Word
Dim Text As String * 40 '40 Zeichen
Dim Buchstabenoffset(40) As Word '40 Zeichen
Dim Ausgabe As Byte
Dim Buchstabe As String * 1
Dim Code As Byte
Dim Adresse As Word
Dim Laenge As Byte
Dim Textzeilenmuster(800) As Byte 'max. Anzahl der Zeichen * 20
Dim Znr As Byte
Dim Musternr As Word
Dim Restlaenge As Byte
Dim Restzk As String * 40 'zum Auffüllen der Zeichenkette "Text"
Dim Maxlaenge As Byte '40
'****************** Initialisierung ***********************
Ddrb = &B00101101 'Ports B.0 u. B.3 für BAS-Signal (B.3=MOSI); B.2 (für \SS); B.5 für Kontroll-LED;
Portb = 1 'schwarz (0,3 V)
Hsync_64_us = 1024 - 16 'Zeilenlänge 1024 Takte = 64 us; Aufruf der ISR dauert ca. 1 us (kein leichtes Flackern mehr!)
Vsync_32_us = 512 - 16 'Vertikalsignallaenge 512 Takte; Aufruf der ISR dauert ca. 1 us (kein leichtes Flackern mehr!)
Hsync_duration_1 = 1 'Warten vor Horizontalsynchronpuls
Hsync_duration_2 = 4 'Horizontalsynchronpulsdauer 5 us
Vsync_duration_1 = 2 'Vertikalsynchronpulsdauer 2 us
Vsync_duration_2 = 27 'Vertikalsynchronpulsdauer 27 us
Vsync_line = 305 'nach VSync_line - 1 Zeilen erfolgt VSync ----
Vsync_pre_trabant = Vsync_line + 5 'Signalnummern...
Vsync_main = Vsync_pre_trabant + 5
Vsync_post_trabant = Vsync_main + 4
Hoffset0 = 192 'Beginn des Bildes (Textes); min. 192
Synccounter = 0 'zählt Synchronpulse : Start bei 0
Maxlaenge = 40 'max. Anzahl der Zeichen
Tccr1b = &B00000001 'Start von Timer1 mit Prescale = 1; d. h. 1 Timer-Takt = 1/16 us
Timsk1.ocie1a = 1 'Interrupt bei CompA
On Compare1a Isr_compare Nosave
'**********************************************************
'******************** Hauptprogramm ***********************
'Eingabe des Textes:
' "ABCDEFGHIJLMNOPQRSTUVWXYZ ABCDEFGHI ABCD" 'genau 40 Zeichen
Text = "Fernsehsignal mit BASCOM UND Assembler" 'max. 40 Zeichen
Text = Ucase(text)
Laenge = Len(text)
If Laenge < Maxlaenge Then
Restlaenge = Maxlaenge - Laenge
Restzk = Space(restlaenge)
Text = Text + Restzk
Laenge = Len(text)
End If
'Textzeilenmuster-Array erzeugen:
For J = 1 To Laenge
Buchstabe = Mid(text , J , 1)
If Buchstabe = " " Then Code = 91 - 65 Else Code = Asc(buchstabe) - 65 'Leerzeichen bei DATA hinter dem Zeichen "Z"
Buchstabenoffset(j) = Code * 10
Next J
For Lc = 0 To 19 'Weil die Zeilen auf der Mattscheibe sehr dünn sind, wird jede Zeile doppelt angezeigt
Znr = Lc
Shift Znr , Right 'Halbierung
For J = 1 To Laenge
Musternr = Lc * Laenge
Musternr = Musternr + J
Tmp = Buchstabenoffset(j) + Znr
Ausgabe = Lookup(tmp , Muster)
Textzeilenmuster(musternr) = Not Ausgabe 'Eine 0/1 beim Muster wird weiß/schwarz dargestellt. (Wenn SPI eingeschaltet wird, ist der Ausgang des Schieberegisters automatisch High -> weiß.)
Next J
Next Lc
'Jetzt geht es los...
Lc = 0 'Buchstaben-Line-Counter resetten
Compare1a = Hsync_64_us 'entspricht Zeilenlänge, 1024 Timer-Takte = 64 us
Enable Interrupts
Do
'Nur auf Interrupts warten...
Loop
'**********************************************************
'******************* Unterprogramme ***********************
'**********************************************************
'******************Interruptroutinen***********************
Isr_compare:
Timer1 = 0
Portb = 1
Incr Synccounter
'//////// Zeilensynchronisation: Normale Zeile ///////////////
If Synccounter < Vsync_line Then 'Signalnummer 1 bis 305
' Waitus Hsync_duration_1 'Schwarzschulter; Pause von ca. 3 us durch vorherige Befehle
Portb.0 = 0 'Zeilensynchronisationssignal einschalten
Waitus Hsync_duration_2
Portb.0 = 1 'Zeilensynchronisationssignal ausschalten; dazu kommt noch Schwarzteil bis Timer1 Hoffset0 erreicht
'Vorbereitung der Bilderzeugung
J0 = Lc * Laenge
Incr J0
Do
Loop Until Timer1 >= Hoffset0 'Auf Ende des zeilensynchronisationssignals warten
'Bilderzeugung
Spcr = &B01010000 'SPI enabled, SPI Master,
Spsr.spi2x = 1 'Fosc/2
'Erst Pointer X retten? Augenscheinlich nicht nötig!
Loadadr Textzeilenmuster(j0) , X 'Addresse des Array-Elements mit dem Index j0 in den Pointer X laden
$asm
push r16 'sicherheitshalber die benutzten Register r16, r18 und r24 auf Return-Stack retten
push r18
in r18, SREG 'SREG wird durch SBIW verändert
push r24
ldi r24, 40 'maxlaenge (40)
Zaehlschleife: 'Schleife für die Ausgabe von 40 Zeichen
ld r16, x+ 'Inhalt des indizierten Array-Elements in r16 laden und dann Index um 1 erhöhen
Out Spdr , R16 'r16 in das Schieberegister der SPI laden und Übertragung starten
nop 'min. 11 NOPs
nop
nop
nop
nop
nop
nop
nop
NOP
nop
NOP
sbiw r24, 1 'Schleifenindex r24 um 1 verringern (arbeitet mit SREG)
brne zaehlschleife 'Rücksprung, wenn r24 > 0
pop r24 'r24 wieder herstellen
Out Sreg , R18 'Die Register SREG und r18 wieder herstellen
pop r18
pop r16 'r16 wieder herstellen; gleichzeitig auch kleine Pause
$end Asm
Spcr = 0 'SPI aus
Portb.3 = 0 'Rest schwarz
Incr Lc
If Lc > 19 Then Lc = 0
Goto Ende
End If
'////////// Bildsynchronisationssignale /////////////
Portb = 1 'schwarz; 0,3 V
Compare1a = Vsync_32_us 'halbe Zeile
'///////////VORTRABANT////////////
If Synccounter < Vsync_pre_trabant Then 'Signalnummer 306 bis 310
Portb = 0
Waitus Vsync_duration_1
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
Goto Ende
End If
'///////////HAUPT///////////
If Synccounter < Vsync_main Then 'Signalnummer 311 bis 315
Portb = 0
Waitus Vsync_duration_2
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
Goto Ende
End If
'///////////NACH_TRABANT///////////
Portb = 0 'Signalnummer 316 bis 320
Waitus Vsync_duration_1
Portb = 1
' Spannung 0,3 V bleibt bis zum nächsten Interrupt, d. h. bis zum (halben) Zeilenende
'//////////Ende eines Halb-Bildes///////
If Synccounter >= Vsync_post_trabant Then 'im Anschluss an Signalnummer 320
Synccounter = 0
Compare1a = Hsync_64_us
Lc = 0
End If
Ende:
Return
'********************** DATA ************************************
'Zeichenmuster aus muster6.mst: 8 Punkte breit und 10 Punkte hoch
Muster:
Data 0 'Dummy, damit Zählung der Musterbytes bei 1 beginnt
Data 0 , 56 , 108 , 198 , 198 , 254 , 254 , 198 , 198 , 0 'A
Data 0 , 248 , 252 , 204 , 248 , 252 , 198 , 254 , 252 , 0 'B
Data 0 , 124 , 254 , 192 , 192 , 192 , 192 , 254 , 124 , 0 'C
Data 0 , 240 , 252 , 206 , 198 , 198 , 206 , 252 , 240 , 0 'D
Data 0 , 254 , 254 , 192 , 240 , 240 , 192 , 254 , 254 , 0 'E
Data 0 , 254 , 254 , 192 , 240 , 240 , 192 , 192 , 192 , 0 'F
Data 0 , 124 , 254 , 192 , 222 , 222 , 204 , 252 , 120 , 0 'G
Data 0 , 198 , 198 , 198 , 254 , 254 , 198 , 198 , 198 , 0 'H
Data 0 , 126 , 24 , 24 , 24 , 24 , 24 , 24 , 126 , 0 'I
Data 0 , 126 , 126 , 12 , 12 , 12 , 12 , 124 , 56 , 0 'J
Data 0 , 204 , 216 , 240 , 224 , 240 , 216 , 204 , 198 , 0 'K
Data 0 , 192 , 192 , 192 , 192 , 192 , 192 , 254 , 254 , 0 'L
Data 0 , 198 , 238 , 254 , 214 , 198 , 198 , 198 , 198 , 0 'M
Data 0 , 198 , 198 , 230 , 246 , 222 , 206 , 198 , 198 , 0 'N
Data 0 , 56 , 124 , 198 , 198 , 198 , 198 , 124 , 56 , 0 'O
Data 0 , 248 , 252 , 198 , 252 , 248 , 192 , 192 , 192 , 0 'P
Data 0 , 56 , 124 , 198 , 198 , 198 , 206 , 124 , 58 , 0 'Q
Data 0 , 248 , 252 , 198 , 252 , 248 , 216 , 204 , 198 , 0 'R
Data 0 , 124 , 206 , 192 , 124 , 62 , 134 , 204 , 120 , 0 'S
Data 0 , 126 , 126 , 24 , 24 , 24 , 24 , 24 , 24 , 0 'T
Data 0 , 198 , 198 , 198 , 198 , 198 , 198 , 254 , 122 , 0 'U
Data 0 , 198 , 198 , 198 , 108 , 108 , 40 , 56 , 56 , 0 'V
Data 0 , 198 , 198 , 198 , 198 , 214 , 214 , 124 , 40 , 0 'W
Data 0 , 198 , 198 , 108 , 56 , 56 , 108 , 198 , 198 , 0 'X
Data 0 , 102 , 102 , 60 , 60 , 24 , 24 , 24 , 24 , 0 'Y
Data 0 , 254 , 254 , 12 , 24 , 48 , 96 , 254 , 254 , 0 'Z
Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 'Leerzeichen