Bytes im Gänsemarsch mit BASCOM Overlay

Immer dann, wenn eine byteweise Verarbeitung von Daten ansteht, ist das BASCOM-Overlay eine praktische Sache. Beipiele: Byteweises Abspeichern und Lesen in/aus einem EEPROM oder Ein-/Ausgabe über eine serielle Schnittstelle. Im Folgenden soll das an einem einfachen Beispiel mit Schreiben/Lesen eines EEPROM demonstriert werden.

Mit einer Folge von Dim-Anweisungen wird ein zusammenhängender Speicherplatz im RAM des Controllers reserviert.

'EEPROM Variables band segments -----------------------------------------------
'Band segments:   Byte1: LoByte(Segment center freq., kHz)
'                 Byte2: HiByte(Segment center freq., kHz)         
'                 Byte3: Width of band segment (kHz)
'                 Byte4: Encoder value C
'                 Byte5: Encoder value L
'                 Byte6: High pass(1), low pass(0)

Dim wrdSeg_freq As Word                           'Center freq. band segment from EEPROM
Dim bytSeg_width As Byte                          'Segment width from EEPROM
Dim bytSeg_C As Byte                              'Encoder value for C from EEPROM
Dim bytSeg_L As Byte                              'Encoder value for L from EEPROM
Dim bytSeg_HL As Byte                             'High pass(1), Low pass(0)
Dim bytSeg_Arr(6) As Byte At wrdSeg_freq Overlay

In der letzten Zeile legen wir ein Byte-Array quasi darüber, beginnend mit dem ersten (low) Byte der 2 Byte langen Word-Variablen wrdSeg_freq. Die Speicherzuordnung sieht dann so aus (mit der BASCOM-Standardzählung für Arrays beginnend mit 1):

Overlay-Struktur

Speicheranordnung der Overlay-Struktur
Die Ablage im SRAM beginnt mit dem Low-Byte von wrdSeg_freq.

Das geht ebenso mit 4-Byte- (Long und Single) oder noch längeren Variablen sowie Strings, z.B.

Dim lngVar As Long
Dim bytVar0 As Byte At lngVar Overlay
Dim bytVar1 As Byte At lngVar + 1 Overlay
Dim bytVar2 As Byte At lngVar + 2 Overlay
Dim bytVar3 As Byte At lngVar + 3 Overlay

entsprechend für eine 2 Bytes-Word-Variable

Dim wrdVar As Word
Dim bytVar0 As Byte At wrdVar Overlay
Dim bytVar1 As Byte At wrdVar + 1 Overlay

Die Zählung der Bytes bytVar0, bytVar1... beginnt jeweils mit dem Low-Byte bytVar0. Der Wert der Variablen (wrdVar oder lngVar), z.B. bei einer Ausgabe, ergibt sich natürlich aus der umgekehrten Reihenfolge, bei wrdVar z.B. (HiByte_LoByte).

Eine andere Konstruktion wäre z.B. (Integers sind 2 Bytes lang):

Dim byt_Arr(6) As Byte
Dim intVar1 As Integer At byt_Arr(1) Overlay
Dim intVar2 As Integer At byt_Arr(3) Overlay
Dim intVar3 As Integer At byt_Arr(5) Overlay

Das Overlay-Byte-Array belegt keinen zusätzlichen Speicherplatz. Im Programm kann man die Variablen wahlweise ansprechen: Wie in den ursprünglichen Dim-Anweisungen festgelegt oder über die Elemente des überlagerten Byte-Arrays. Das erweist sich bei der EEPROM Ein- und Ausgabe als überaus praktisch.

Zunächst Setzen der Werte über die dimensionierten Variablen und Speichern im EEPROM:

'Define & write Band segment
wrdSeg_freq = 1800
bytSeg_width = 10
bytSeg_C = 25
bytSeg_L = 107
bytSeg_HL = 1
Call WriteBandSegment(20)       'Write to EEPROM, start address = 20

So holen wir die Daten aus dem EEPROM zurück:

Call ReadBandSegment(20)        'Read from EEPROM, start address = 20

Damit stehen die Daten der dimensionierten Variablen zur weiteren Verarbeitung wieder zur Verfügung.

Da wir es mit einem Byte-Array zu tun haben, gestalten sich Schreiben und Lesen sehr einfach:

Sub WriteBandSegment(byVal wrdStartAddr As Word)
'Write band segment data to EEPROM @ start address wrdStartAddr
'Band segments:   Byte 1: LoByte(Segment center, kHz) -> wrdSeg_freq
'                 Byte 2: HiByte(Segment center, kHz) -> wrdSeg_freq
'                 Byte 3: Width band segment (kHz)    -> bytSeg_width
'                 Byte 4: Encoder value C             -> bytSeg_C
'                 Byte 5: Encoder value L             -> bytSeg_L
'                 Byte 6: High pass(1), low pass(0)   -> bytSeg_HL
'Variables and bytes bytSeg_Arr(1...6) are overlayed

   For bytTmp0 = 1 To 6
      WriteEEPROM bytSeg_Arr(bytTmp0) , wrdStartAddr
      Incr wrdStartAddr
   Next bytTmp0

End Sub

Sub ReadBandSegment(byVal wrdStartAddr As Word)
'Read band segment data from EEPROM @ start address wrdStartAddr
'Band segments:   Byte 1: LoByte(Segment center, kHz) -> wrdSeg_freq
'                 Byte 2: HiByte(Segment center, kHz) -> wrdSeg_freq
'                 Byte 3: Width band segment (kHz)    -> bytSeg_width
'                 Byte 4: Encoder value C             -> bytSeg_C
'                 Byte 5: Encoder value L             -> bytSeg_L
'                 Byte 6: High pass(1), low pass(0)   -> bytSeg_HL
'Variables and bytes bytSeg_Arr(1...6) are overlayed

   For bytTmp0 = 1 To 6
      ReadEEPROM bytSeg_Arr(bytTmp0) , wrdStartAddr
      Incr wrdStartAddr
   Next bytTmp0

End Sub

Bei der EEPROM Ein-/Ausgabe müssen wir uns also nicht darum kümmern, welchen Typ die hantierten Daten haben, z.B. 2-Byte-Word oder 4-Byte-Single. Die Overlay-Bytes werden im Gänsemarsch hantiert. Um die Zuordnung zu den dimensionierten Variablen haben wir uns einmal - oben bei den Dim- und Overlay-Anweisungen - gekümmert. Das funktioniert genauso bei einer byteweisen seriellen Datenübertragung.

Ein weiteres Beispiel zum Speichern und Lesen von Single-Variablen

Da eine Single-Variable 4 Bytes belegt, sehen wir ein Byte-Array der Länge 4 vor.

Dim sngEEPROM As Single                           'Single to/from EEPROM
Dim bytSngEEP(4) As Byte At sngEEPROM Overlay
Dim wrdAddr As Word                               'EEPROM address
Dim bytTmp0 As Byte                               'Temporary byte variable
Dim sngTmp0 As Single                             'Temporary single variable

'Declare subs
Declare Sub EEPROMWriteSng(byRef sngSingle As Single)
Declare Sub EEPROMReadSng(byRef sngSingle As Single)

wrdAddr = 11                                      'EEPROM start address
sngTmp0 = 123.456                                 'Single to be stored in EEPROM
Call EEPROMWriteSng(sngTmp0)                      'Store sngTmp0 to EEPROM

wrdAddr = 11
Call EEPROMReadSng(sngTmp0)                       'Retrieve sngTmp0 from EEPROM

'==============================================================================
Sub EEPROMWriteSng(byRef sngSingle As Single)
'Decode sngSingle to 4 bytes of bytSngEEP for writing to EEPROM
'Input sngSingle is set to sngEEPROM, overlayed with bytSngEEP(i), i=1...4
'The 4 bytes bytSngEEP(i)are written to EEPROM
'Input:  sngSingle to be decoded & written to EEPROM
'        wrdAddr first EEPROM address

   sngEEPROM = sngSingle                          'Set input
   For bytTmp0 = 1 To 4
      WriteEEPROM bytSngEEP(bytTmp0) , wrdAddr
      Incr wrdAddr
   Next bytTmp0
End Sub

'==============================================================================
Sub EEPROMReadSng(byRef sngSingle As Single)
'Encode 4 bytes read from EEPROM to sngSingle
'The 4 bytes are read to bytSngEEP(i), i=1...4
'bytSngEEP is overlayed @ sngEEPROM
'sngEEPROM is set to output sngSingle
'Input:  wrdAddr first EEPROM address
'Output: sngSingle encoded to single variable read from EEPROM

   For bytTmp0 = 1 To 4
      ReadEEPROM bytSngEEP(bytTmp0) , wrdAddr
      Incr wrdAddr
   Next bytTmp0
   sngSingle = sngEEPROM                          'Set output
End Sub

Da in BASCOM, anders als z.B. in MS Visual Basic, im Hauptprogramm erklärte Variable "Public", also auch in Subs gültig sind, wären die Übergabeparameter in beiden Subs eingentlich unnötig.  Der Zugriff wäre direkt auf die Variable sngEEPROM sowohl im Hauptprogramm als auch in den Subs möglich. Die Übergabe als Parameter macht die Sache aber etwas übersichtlicher. Vor der Sub EEPROMWriteSng hätte man eine Zuordnung "sngEEPROM = sngTmp0" machen müssen, nach der Sub EEPROMReadSng entsprechend "sngTmp0 = sngEEPROM". In die Sub EEPROMWriteSng könnte man den Übergabeparameter auch mit "byVal" übergeben, aus der Sub EEPROMReadSng heraus geht "byVal" nicht, hier ist "byRef" erforderlich. Aus Symmetriegründen sind daher beide Übergabeparameter mit "byRef" erklärt.

Beim Rücklesen von Single-Variablen wird man nicht genau den Wert erhalten, den man eingespeichert hat, im obigen Beispiel also für den festgelegten Wert "123.456"  etwa "123.455923" oder so ähnlich. Für eine identische Zahl, z.B. auf einem LCD, muss dann die Rundungseigenschaft von "Fusing" mit der gleichen Nachkomma-Stellenzahl bemüht werden. Genaue Reproduzierbarkeit ist nur mit Festkommazahlen (Word, Integer, Long) möglich.

Long-Variablen, auch 4 Bytes lang, lassen sich fast identisch handhaben, "Single" durch "Long" ersetzen - fertig. Für Word-/Integer-Variablen entsprechend mit nur 2 Bytes.

Auch Stringmanipulationen lassen sich elegant mit Overlays durchführen.

Dim strDis As String * 20
Dim bytDis(21) As Byte At strDis Overlay

strDis = Space(20)                                     'Fill with blanks
bytDis(1) = "A"
bytDis(6) = "B"
bytDis(12) = "C"

Ergibt einen String strDis mit den an den Positionen 1, 6 und 12 gesetzten Zeichen, Rest leer (Blanks).

Da ein String am Ende immer mit einem zusätzlichen Null-Byte abgeschlossen wird, also strDis tatsächlich 21 Bytes enthält, ist das Byte-Array bytDis mit 21 dimensioniert. Das ist eigentlich unnötig, da nur auf die hier 20 druckbaren Zeichen zugegriffen wird. Die meisten Autoren spendieren noch das zusätzliche Byte.

Elegant an der Overlay-Konstruktion ist, dass man die jeweils einfachste Art der Zeichenmanipulation wählen kann, hier also mit einem Statement strDis mit 20 Blanks zu besetzen bzw. einzelne Positionen durch Ansprache der entsprechenden Bytes.

Will man einzelne Zeichen in einem String ansprechen, verwendet man die Funktion "Mid" - was sonst?
Mit Overlay geht es speichersparender.

Dim bytTmp0 As Byte
Dim bytTmp1 As Byte
Dim bytTmp3 As Byte
Dim wrdFreq As Word
Dim strDis As String * 20
Dim bytDis(21) As Byte At strDis Overlay
Dim strTmp6 As String * 6
Dim bytTmp6(7) As Byte At strTmp6 Overlay

wrdFreq = ...                                          'Frequency in kHz calculated elsewhere
strTmp6 = Str(wrdFreq)                                 'Convert freq. to a string
strTmp6 = Format(strTmp6, "  0.000")                   'Frequency formatted in MHz
bytTmp1 = Len(strTmp6)                                 'Length of freq. string
bytTmp3 = 5                                            'Position of freq. in strDis minus 1
For bytTmp0 = 1 To bytTmp1                             'Put freq.-string into strDis-string
   Incr bytTmp3                                        'Position in strDis
   bytDis(bytTmp3) = bytTmp6(bytTmp0)                  'one character by another
Next bytTmp0

Damit wird die Frequenz ab Byte-Position 6 in den String strDis übertragen. Eine feste Maske, z.B. hinter der Frequenz noch "MHz", lässt sich mit der oben beschriebenen Methode erzeugen. Der komplette String strDis kann nachfolgend mit einem LCD-Statement an ein Display übertragen werden.

Referenzen

[1]   http://www.mcselec.com/index.php?option=com_content&task=view&id=307&Itemid=57

Einordung: