Erzeugen von BAS-Signalen mit dem NANO-Board

Atmega 328 mit BASCOM und FORTH programmieren
Antworten
Heinrichs
Beiträge: 173
Registriert: Do 21. Okt 2010, 18:31

Erzeugen von BAS-Signalen mit dem NANO-Board

Beitrag von Heinrichs » Fr 24. Jan 2020, 12:12

Zur Übertragung von analogen Signalen für das Schwarz-Weiß-Fernsehen werden so genannte BAS-Signale benutzt. Die Abkürzung BAS steht für Bild-Austast-Synchron-Signal. Aufgabe dieses Signal ist es, nicht nur Bildinformationen, sondern auch Signale zur Synchronisierung zu übertragen. Wir unterscheiden drei Arten von Signalen:

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.

bas_1.jpg
Zeilensgnal
bas_1.jpg (20.08 KiB) 11230 mal betrachtet

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.

vsync1.jpg
Bildsychronisationssignal
vsync1.jpg (18.25 KiB) 11229 mal betrachtet

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.

DA_Wandler.jpg
Einfacher D/A-Wandler
DA_Wandler.jpg (8.63 KiB) 11229 mal betrachtet

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:

isr.jpg
Interrupt-Handler
isr.jpg (114.89 KiB) 11228 mal betrachtet

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


Das hiermit erzeugte Zeilen-Signal sieht z. B. so aus:

Oszi_zeile.jpg
BAS-Signal für eine Zeile
Oszi_zeile.jpg (30.14 KiB) 11210 mal betrachtet

Und das Bild auf dem Fernseher so:

foto_grautreppe_klein.jpg
Fernsehbild mit Grautreppe
foto_grautreppe_klein.jpg (12.72 KiB) 11225 mal betrachtet

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:

Fernseher_mit_Textzeilen_klein.jpg
Fernsehbild mit Textzueilen
Fernseher_mit_Textzeilen_klein.jpg (33.61 KiB) 10875 mal betrachtet

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.

zeichen_A.jpg
Muster des Zeichens A
zeichen_A.jpg (18.06 KiB) 10874 mal betrachtet

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
Hierdurch wird als erstes das höchstwertige Bit (Most Significant Bit, kurz: MSB) an PortB.3 ausgegeben, dann die weiteren bis zum Bit mit dem niedrigsten Wert. Die einzelnen Bits von &B01101100 werden somit in der korrekten zeitlichen Reihenfolge ausgegeben, nämlich so wie man sie von links nach rechts nach liest.

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:

spi_modell.jpg
Stark vereinfachtes SPI-Modell
spi_modell.jpg (23.85 KiB) 10873 mal betrachtet

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
Zwischen dem Ende eines Schiebevorgangs und dem Anfang des folgenden klafft eine zeitliche Lücke, weil die Inkrementierung und das Holen des Array-Elements zusammen merklich länger dauern als der Schiebevorgang. Diese Lücke lässt sich schließen, wenn man die Inkrementierung und das Holen des Array-Elements in Assembler programmiert und ein so genanntes Index-Register einsetzt: Mit dem Assembler-Befehl

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

Weitere Erläuterungen zu diesem Programm findet man wieder hier.

Antworten