Aus RN-Wissen.de
Version vom 17. Februar 2006, 12:00 Uhr von PicNick (Diskussion | Beiträge) (Ein-queuen einer Message)

Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

Betriebsystem für Bascom

Die im Text angesprochene Bascom-Library befindet sich im Community-Download-Bereich, zusammen mit einem einfachen Beispielprogramm.

Im Wesentlichen ist es die Aufgabe eine Betriebssystems, das Zusammenspiel der verschiedenen Programmteile, aus denen ja so eine µC-Applikation besteht, zu regeln. Die Nachteile des gewissen Overheads und der natürlich erforderlichen Restriktionen´sollten durch die Vorteile der Standardisierung und Modularisierung deutlich aufgewogen werden.

Bascom bietet für sehr viele AVR-intere Geräte und Peripherie Funktionen an, die oft ganz einfach durch "Config" Anweisungen initiiert werden können und dem Programmierer viel Detailentwicklungsarbeit abnehmen. Ein unschätzbarer Vorzug. Was aber immer wieder bleibt, ist das Problem des Zusammenwirkens dieser Komponenten und die gewünschte Mehrgleisigkeit, "Multithreading", des gesamten Projekts.

Es soll hier ein Konzept vorgestellt werden, das hauptsächlich in Hinblick auf eine Standardisierung der Rechner-Kommunikation entwickelt wurde. Also µC <-> PC und µC <-> Peripherie.

Das verwendete "Message-Queueing" ist natürlich keine neue Erfindung, jeder Windows-Programmierer kennt das. Das Problem war es, eine Form zu schaffen, die sich in Bascom so eingliedern läßt, daß auch die "normale" Verwendung des Compilers möglich ist. Nur an den Schnittstellen zu den Sys-Funktionen müssen die Konventionen eingehalten werden.


Begriffe

  • Units: Ein Programmteil, der sozusagen mitspielen will, wird als Unit od. "Einheit" bezeichnet. In den meisten Fällen wird so eine Unit die verschiedenen Funktionen umfassen, die für die Bedienung eines bestimmten Device od. "Gerät" erforderlich sind.
  • Class/Ident: Es gibt Geräteklassen, die sich durch ein Set von "Methoden" definieren, die auf eines oder mehrere tatsächliche Geräte angewendet werden können. Anschauliches Beispiel: die Klasse "ADC", die für 8 Channels angewendet werden kann. Grundsätzlich werden die ADC-Channels ja völlig gleich behandelt, aber für jeden Kanal gelten unter Umständen verschiedene Parameter: Natürlich die Kanal-Nummer 0-7, aber auch Umrechnungsfaktoren und Grenzwerte
  • Tags: Jeder Class/Ident-Ausprägung wird ein Tag ("Etikett") zugeordnet, das einen vom System verwalteten Bereich bezeichnet, der für die Kommunikation Unit <-> System erforderlich ist. Tags werden bei jedem Aufruf an das System verwednet und bei Aufrufen durch das System immer mitgeliefert.
  • Message-Queueing: An sich können alle Funktionen eines Units "normal" verwendet, also aufgerufen werden. Doch jede Unit bietet wenigstens eine "Sub"-function an, die auch indirekt über Messages angesprochen werden kann und daher auch ein standardisiertes Format haben muß.
  DECLARE SUB unit-entry ( Tag AS WORD, Byval cmd as BYTE, Byval Param as WORD ) 

Das ist auch die SUB, die beim Einlangen einer Nachricht über das Netzwerk aufgerufen wird.

  • Tag ist das erwähnte Etikett, mit dem die SUB alle ihre Referenzen zu privaten Daten und individuellen Parametern (im Flash Speicher) verwaltet.
  • Cmd ist eine Aufrufspezifischer Code, der über die Art des Aufrufes Auskunft gibt. Einige Codes sind fest durch das System vergeben, andere können beliebig von der Geräteklasse für spezielle Befehle festgelgt werden
  • Param ist ein Beiwert zu CMD, der daher völlig unterschiedliche Bedeutung haben kann. (Beim Netzwerk-Nachrichten zeigt er auf die eingelangten Daten, die von der Unit zu bearbeiten sind).

TAG-Referenzen

Sysunits.png

Das System findet durch den Tag die zugeordnete SUB-Routine (Vector). Die Unit hingegen findet durch die System-Function "Unit_REF()" ihre privaten Daten, und kann auch durch "Unit_parbyte(tag), Unit_parword(tag),Unit_parlong(tag),Unit_parsing(tag)" ihre individuellen Parameter abfragen.

Die ersten beiden Bytes der Flash-Parameter sind genormt und beinhalten den Class-Code und Identifier der Unit. Die übrigen sind vom System nicht festgelegt und können beliebige Werte beinhalten.

Message-Queue

Sysmsg.png

  • Durch msg_enqueue() wird ein indirekter Aufruf in die Queue gestellt. Es gibt keine Festlegung darüber, wann und wo dieser Call erfolgen kann.
  • Die Abarbeitung erfolgt FIFO durch msg_dequeue(). Bei jedem Aufruf wird EINE Message (wenn eine vorhanden ist) abgearbeitet. Auch für diesen Aufruf gibt es keinen zwingenden Platz, doch ist es zweckmäßig und empfehlenswert, ihn nur einmal an zentraler Stelle (DO..LOOP) zu tätigen.

Anmerkung: Enqueue() und Dequeue() führen sowohl Sub-Vector als auch Tag. "Vector" hat Vorrang, wenn er aber nicht angegeben wird (NULL), wird eben der Standard-Unit-Vector verwendet. Sonderfall: Bei Angabe von "Vector" ist die Befüllung von TAG nicht zwingend. Dadurch kann auch jede x-beliebige sub-function aufgerufen werden, nur das Declare-Format MUSS stimmen. ("TAG" ist beim Aufruf dann eben NULL).


Initialisierung

  • Generell:

Die Library "mysyslib.lbx" muß in das Bascom-Library-Directory gestellt werden.

$INCLUDE "mysyslib.bas"

Hier erfolgen die Definitionen für die Library

Units

 Const Max_c_unit = 4
 Const Unit_arrsz = Max_c_unit * Unit_len
  • Max_c_unit gibt an, wieviele Units maximal verwendet werden können.
  • Unit_arrsz daraus wird errechnet, wie groß das Array sein muß
Dim Unitbase As Word
Dim Unitfill As Word
Dim Unittop As Word

Diese drei Felder müssen aufeinander folgen, die Namensvergabe ist aber wahlfrei

Und irgendwo wird Platz in der benötigten Länge bereitgestellt.

Dim Unitarr(unit_arrsz) As Byte

Jetzt kann die Kontrollstruktur initialisiert werden

  Call Unit_init(unitbase , Unitarr(1) , Unit_arrsz)

Message-Queue

recht analog dazu die Einrichtung der Message-Queue

Const Max_c_msg = 16
Const Msg_arrsz = Max_c_msg * Msg_len
Dim Msgbase As Word                                         ' address array
Dim Msgrdpnt As Word                                        ' read pointer
Dim Msgtop As Word                                          ' array top
Dim Msgwrpnt As Word                                        ' write pointer

Dim Msgarr(msg_arrsz) As Byte

Auch diese Felder müssen aufeinander folgen


  Call Msg_init(msgbase , Msgarr(1) , Msg_arrsz)
Ein-queuen einer Message
Declare Function Msg_enqueue(control As Word , Byval Vector As Word , 
                          Byval Tag As Word , Byval Cmd As Byte , Byval Param As Word) As Byte
Dim Enqresult As Byte
    Enqresult = Msg_enqueue(Msgbase, 0, Tag, Cmd, Param) 

Hier wurde der Call-Vector NULL angegeben, was der Normalfall ist. Daher wird dann auch der normale Unit-Entry verwendet.

Anmerkung: Das Enqueueing ist so abgesichtert, daß es auch in einer ISR verwendet werden kann, was ja auch eine typische Anwendung darstellt:

Abarbeiten der Message-Queue
Declare Sub Msg_dequeue(control As Word , Byval Cmd As Byte , Byval Param As Word)

Klassisch und zweckmäßig ist das De-Queueing an zentraler Stelle, im Main- DO..LOOP

   Do
      Call Msg_dequeue(msgbase , 0 , 0)
   Loop

Timed-Message-Queue

Hier handelt es sich um Queue-Entries, die verzögert durchgeführt werden. Param wird dann für die Angabe des Zählers verwendet.

    Enqresult = Time_entry(Msgbase, 0, Tag, Cmd, Counter) 

Damit das funktioniert, muß natürlich ein Timer laufen. Vom Verfasser wird gerne der Timer0 für ein Millisekunden-Intervall eingestellt. Das heißt dann, daß der genannte Zähler in mS Einheiten heruntergezählt wird

Config Timer0 = Timer , Prescale = 64                       'Timer 1mS
Const Tmr_c_preload = 131
On Timer0 Interrupt_ticker                                  ' Timer for Timer Queue


Interrupt_ticker:
   Timer0 = Tmr_c_preload
   Loadadr Msgbase , Z
   Gosub Time_scan
Return

Bei jedem Aufruf von "Time_scan" wird bei allen Timer-Einträgen eins heruntergezählt. Bei null wird dann der Queue-Eintrag aktiv gesetzt.

Anmerkung: Wenn eine Unit-Sub regelmäßig immer wieder aufgerufen werden will, muß sie jedesmal einen neuen Timer-entry aufsetzen. Das ist derzeit so gelöst, weil sonst zwei Arten Timer-Entries geführt werden müßten: Mit auto-repeat und single-shot. Da wird man in folgenden Versionen noch weitersehen.

Anlegen einer Unit

Als Beispiel eine eigentlich völlig sinnlose Klasse, die nur demonstrieren soll, wie das Ganze funktioniert

  • Definitionen und Parameter
Const Cls_any = &H20

Irgendein Symbol, das allerdings in er gesamten µC Applikation eindeutig sein muß, wird als Klassen-Code festgelegt.

Declare Sub Anyclass(byval Tag As Word , Byval Cmd As Byte , Byval Param As Word)

Das ist der default-Unit-Entry

An einer geeigneten Stelle (nach "END") werden die Unit-Paramter deklariert.

 Any_par:
   Data Cls_any , 1 , &H78 , 16255% , 300000& , 3.55!

Obligat als erstes der Klassencode, dann der Identifier. Der Rest ist eben irgendwas, was diese eine konkrete Unit zum arbeiten benötigt.

  • Eintragen in der Tabelle

Natürlich NACH Unit-Init()

 Dim Par_addr As Word      'Hilfsfeld
 Dim Anytag As Word        

      Anytag = Unit_new(unitbase)       	

Die Funktion gibt den eindeutigen TAG der Unit zurück. Jetzt muß die Unit ihre Werte eintragen. Für den Aufruf übergeben wir die Adresse der Parameter

      Par_addr = Loadlabel(any_par)                         ' individial parameter
      Call Anyclass(mytag , Cmd_setup , Par_addr)	'CMD_SETUP ist festgelegt


  • Setup der Unit

Das macht die Unit am besten selbst, da sie durch die mitgelieferten Paramter modifizierbar ist.

'-----------------------------------------------
'        Class ANY
'-------------------------------------------------
'        private class data
'-------------------------------------------------
Const Max_c_any = 2
Dim Anyidx As Byte

Dim Any_bck(max_c_any) As Word
Dim Any_dta(max_c_any) As Byte

WENN die Klasse /Unit private Daten im SRAM braucht, muß sie sie natürlich dimensionieren. Und zwar am besten in Tabellenform, das kommt Bascom entgegen. (Max_c_any ist die maximale Anzahl der möglichen Units dieser Klasse)

Mögliches Grundmuster der Funktion, die anhand CMD entscheidet, was zu tun ist

'---------------------------------------------------------------------------
Sub Anyclass(byval Tag As Byte , Byval Cmd As Byte , Byval Param As Word)

   Select Case Cmd
   Case Cmd_setup:

   Case Cmd_show:

   Case Else:
      Print Str(tag) ; " CMD1:" ; Str(cmd) ; " PAR1:" ; Str(param)
   End Select
End Sub

Wenn die Klasse irgendein Command nicht kennt, zeigt sie es hier im Beispiel einfach an und macht sonst nichts.

Wichtig ist CMD_SETUP, das Setzen des Sprungzieles und der Parameter

Local Par_word As Word
      Par_word = Loadlabel(anyclass)
      Call Unit_vec_set(tag , Par_word)
      Call Unit_par_set(tag , Param)

Bascom läßt eine direkte Angabe eine Sprungzieles nicht zu. Daher der Umweg über eine word-variable. In dem Beispiel wird einfach die eigene Adresse, also der Name der SUB als Unit-Vector eingetragen. Die Unit-Parameter vom FLASH werden einfach direkt aus dem Aufruf übernommen. Gibt es keine Parameter(NULL), macht das zwar nichts, aber dann kann die Unit nicht von den Kommunikations-Funktionen gefunden werden.


Nun wird ein privates Datenbereich reserviert. Der Klassenspezifische Index wird (bei jedem setup) erhöht und als Datenreferenz in der Unit-Tabelle eingetragen

      Incr Anyidx
      Call Unit_ref_set(tag , Anyidx)

      Any_bck(anyidx) = Tag
      Any_dta(anyidx) = 0

Die Klasse schreibt in ihre privaten Daten den Unit-Tag als Backlink dazu, damit sie auch von dort aus zu ihrem Tag findet. Das ist bei ISR- Funktionen oft nötig. any_dta(anyidx) = 0 ist eben irgendwas.

Ab nun ist die Unit im System eingetragen und verwendbar.

Als Beispiel ein anderes Command: CMD_SHOW

Local Par_class As Byte
Local Par_ident As Byte
Local Par_devad As Byte
Local Par_word As Word
Local Par_long As Long
Local Par_sing As Single

   Case Cmd_show:
      Par_class = Unit_parbyte(tag , 0)	    ' Der Klassencode steht immer an der ersten Stelle der Parameter
      Par_ident = Unit_parbyte(tag , 1)     ' ebenso Ident

      Par_devad = Unit_parbyte(tag , 2)	    ' die Parameter werden einfach geholt und angezeigt.
      Par_word = Unit_parword(tag , 3)      ' das Daten-Offset muß man wissen, auch was dann dort steht
      Par_long = Unit_parlong(tag , 5)      ' es ist sicher sinnvoll, "CONST" zu definieren
      Par_sing = Unit_parsing(tag , 9)
      Print "Class/Id:" ; Str(par_class) ; "/" ; Str(par_ident) ; " Device:0x" ; Hex(par_devad)
      Print " word:" ; Str(par_word) ; " Long:" ; Str(par_long) ; " Single:" ; Str(par_sing)

Aufrufbeispiele

'------------ AUFBAU-----------------------------
      Anytag = Unit_new(unitbase)
      Par_addr = Loadlabel(any_par)                         ' individial parameter1
      Call Anyclass(mytag , Cmd_setup , Par_addr)

      Anytag = Unit_new(unitbase)
      Par_addr = Loadlabel(any_par2)                         ' individial parameter2
      Call Anyclass(mytag , Cmd_setup , Par_addr)

'------------ CALLS-----------------------------
' suchen des Tags nach Class und Ident
      Anytag = Unit_find_clsid(unitbase , Cls_any , 1)

' Direkter Aufruf
      Call Anyclass(anytag , Cmd_show , 0)

DIM Wrk_idx AS BYTE

' Indirekter Aufruf über die Queue
      Wrk_idx = Msg_enqueue(msgbase , 0 , Anytag , Cmd_show , 0)

' Auswerten der Queue
      Call Msg_dequeue(msgbase , 0 , 0)


END

Any_par:
   Data Cls_any , 1 , &H78 , 16255% , 300000& , 3.55!
Any_par2:
   Data Cls_any , 2 , &H44 , 255% , 250000& , 1.44!

Autor

Siehe auch


LiFePO4 Speicher Test