Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
LiFePO4 Speicher Test

(Timed-Message-Queue)
(SYS.BAS Demo)
 
(29 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
 
==Betriebsystem für Bascom==
 
==Betriebsystem für Bascom==
  
Die im Text angesprochene Bascom-Library befindet sich im Community-Download-Bereich, zusammen mit einem einfachen Beispielprogramm.  
+
Die im Text angesprochenen Bascom-Libraries befindet sich im Community-Download-Bereich, zusammen mit einem einfachen Beispielprogramm.  
 +
===MYSYSLIB.ZIP===
 +
*Bascom-Libraries --> In das Bascom-Lib Verzeichnis kopieren
 +
**Mybuffer.LBX  Interne Hilfsfunktionen
 +
**Myunit.LBX    UNIT Kontrolle
 +
**Mymsgque.LBX  MESSAGE-QUEUE Kontrolle
 +
**Myrncom.LBX  Kommunikations Module
 +
*Bascom-include-Files--> In ein Bascom Projekt- Verzeichnis kopieren
 +
**Myunit.BAS
 +
**Mymsgque.BAS
 +
**Myrncom.BAS
 +
*Bascom-Demo-TestProgramm
 +
**SYS.BAS, ausgelegt f. RNBFRA 1.2  ebenfalls in ein Projekt-Verzeichnis. Letztlich ist das Programm etwa 3700 Byte groß, also auch mit einer BasCom DemoVersion zu behandeln
  
 +
*Kommunikations-Test-Parter f. PC
 +
**RN_SERVER.EXE f. XP, W98, W2k
 +
**MFC42(d).DLL, was das jemandem fehlen sollte
 +
 +
Wenn alles verbunden und gestartet ist, steuert der PC im Sekundentakt die 4 LEDs auf der RNBFRA Karte.
 +
==Vorwort, Sinn und Zweck==
 
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.  
 
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.  
  
Zeile 11: Zeile 29:
 
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.  
 
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.  
  
 +
'''Anmerkung''': Dieses Multi-tasking beruht natürlich darauf, daß so ein einzelnes "Task-Slice" kurz gehalten wird und keine wesentlichen Wait-States beinhaltet. Schließlich muß für die anderen ja auch was übrigbleiben. Auch ausführlich "FOR" Schleifen sollten zu Einzel-Slices umgewandelt werden, d.h. bei jedem Aufruf EIN Step.
 +
Was daraus folgt, ist, daß die einzelnen Tasks im Grunde als "State-Machine" arbeiten, z.B.
 +
*FOR-Start
 +
*FOR-Step
 +
*FOR-Limit
 +
 +
'''Noch eins''': Das System betrachtet den Anwender nicht als seinen natürlichen Feind, d.h. etliche Formal-Prüfungen der Call-Parameter mit Fehlercodes etc. sind nicht implementiert. Bitte also das Ganze pfleglich und mit etwas Liebe zu behandeln.
  
 
===Begriffe===
 
===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.  
 
*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
 
*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
 +
'''Anmerkung''': Aus Sicht des Systems sind das einfach zwei Bytes, die zusammen auf diesem Rechner eindeutig sein müssen. 
 
*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.  
 
*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ß.  
+
*Message-Queueing: An sich können alle Funktionen eines Units "normal" im Rahmen des Bascom 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 )  
 
   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.  
 
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.
 
*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  
 
*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).
+
*Param ist ein Beiwert zu CMD, der daher völlig unterschiedliche Bedeutung haben kann.
  
 
===TAG-Referenzen===
 
===TAG-Referenzen===
Zeile 40: Zeile 66:
 
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.  
 
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).
 
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===
+
==SYS.BAS Demo==
*Generell: 
+
Statt umfangreicher theoretischer Abhandlungen einfach das Sys.BAS Demoprogramm mit einigen Kommentaren.
Die Library "mysyslib.lbx" muß in das Bascom-Library-Directory gestellt werden.
+
<pre>
 +
$regfile = "m32def.dat"
 +
$crystal = 8000000
  
$INCLUDE "mysyslib.bas"
+
Const Tmr_c_prescale = 64
Hier erfolgen die Definitionen für die Library
+
Const Tmr_c_preload = 131
  
====Units====
+
$baud = 9600
<pre>
+
$hwstack = 64
Const Max_c_unit = 4
+
$swstack = 256
Const Unit_arrsz = Max_c_unit * Unit_len
+
$framesize = 64
</pre>
+
*Max_c_unit gibt an, wieviele Units maximal verwendet werden können.
+
*Unit_arrsz  daraus wird errechnet, wie groß das Array sein muß
+
<pre>
+
Dim Unitbase As Word
+
Dim Unitfill As Word
+
Dim Unittop As Word
+
</pre>
+
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
+
Const Max_c_unit = 8
 +
Const Max_c_msg = 16
  
  Call Unit_init(unitbase , Unitarr(1) , Unit_arrsz)
+
Const Rx_bufsize = 48
  
====Message-Queue====
+
$include "myrncom.bas"
recht analog dazu die Einrichtung der Message-Queue
+
$include "myunit.bas"
<pre>
+
$include "mymsgque.bas"
Const Max_c_msg = 16
+
Const Msg_arrsz = Max_c_msg * Msg_len
+
 
</pre>
 
</pre>
 +
Hier erfolgen die Definitionen für die Library
 +
*Die Timer-Werte sind für 1 mS Interrupt bei 8MHZ ausgelegt.
 +
*Max_c_unit gibt an, wieviele Units maximal verwendet werden können.
 +
*Max_c_msg gibt an, wieviele Messages gleichzeitig verwendet werden können.
 +
*rx_buffersize gibt die Größe des (Ring)Buffers für den RNCOM-Empfang an.
 +
*Include: in den definitions-includes werden alle notwendigen Werte festgelegt.
  
 
<pre>
 
<pre>
Dim Msgbase As Word                                        ' address array
+
'-------------------------------------------------
Dim Msgrdpnt As Word                                        ' read pointer
+
'           Common Commands
Dim Msgtop As Word                                          ' array top
+
'-------------------------------------------------
Dim Msgwrpnt As Word                                        ' write pointer
+
Const Cmd_nop = 0
 +
Const Cmd_setup = 1
 +
Const Cmd_init = 2
  
Dim Msgarr(msg_arrsz) As Byte
+
Const Cmd_time = 4
 +
Const Cmd_set = 5
 +
Const Cmd_msg = 16
 
</pre>
 
</pre>
Auch diese Felder müssen aufeinander folgen
+
Das sind Muster für festgelegte Codes für Kommandos. Dem System ist es gleich, was man definiert. Es muß halt in ein Byte reinpassen.
 +
<pre>
 +
'-------------------------------------------------
 +
'          RNBFRA I2C
 +
'-------------------------------------------------
 +
Config I2cdelay = 5
 +
Config Scl = Portc.0                                        'Ports fuer IIC-Bus
 +
Config Sda = Portc.1
 +
'-------------------------------------------------
 +
'          RNBFRA I2C Addresses
 +
'-------------------------------------------------
 +
Const Pwr_adr = &H74                                        'I2C Adress Powerport
 +
Const Out_adr = &H72                                        'I2C Adress Extension-Out Port
 +
Const Exp_adr = &H7E                                        'I2C Read Adress Expansion-In Port
  
  
  Call Msg_init(msgbase , Msgarr(1) , Msg_arrsz)
+
Const Pwr_default = &HFF                                    'I2C Powerport
 +
Const Out_default = &H00                                    'I2C Extension-Out Port
  
=====Ein-queuen einer Message=====
+
</pre>
 +
Das ist dem RNBFRA-User alles vertraut, braucht man wohl nicht zu erklären.
 
<pre>
 
<pre>
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
+
'          Class IDs
 +
'-------------------------------------------------
 +
Const Cls_pcf = &H42                                        ' ASCII "B"
 
</pre>
 
</pre>
 
+
Das sollte man für den Demo-Test nicht ändern, da es auch im PC-Programm so verwendet wird.  
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.
+
 
+
====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
+
 
<pre>
 
<pre>
Config Timer0 = Timer , Prescale = 64                      'Timer 1mS
+
'-------------------------------------------------
Const Tmr_c_preload = 131
+
'          CLASS entries    R8:R9=Flash Param  (restore indiv.params)
 +
'-------------------------------------------------
 +
Declare Sub Pcfclass(byval Tag As Word , Byval Cmd As Byte , Byval Param As Word)
 +
</pre>
 +
Die Deklaration der UNIT-Haupt-Funktion. Der Name ist beliebig, das Format muß aber genau so sein.
 +
<pre>
 +
'=========================================================================================
 +
' Timer0 Prescale for 1mS Interrupt
 +
'=========================================================================================
 +
Config Timer0 = Timer , Prescale = Tmr_c_prescale          'Timer 1mS
 
On Timer0 Interrupt_ticker                                  ' Timer for Timer Queue
 
On Timer0 Interrupt_ticker                                  ' Timer for Timer Queue
 
</pre>
 
</pre>
 +
Hier wird Timer0 initialisiert.Das System selbst verwendet den Timer nicht. Nur um "getimete" Messages abzusetzen, braucht man natürlich schon irgendeinen Timer
 +
<pre>
 +
'-------------------------------------------------
 +
' frequently used units
 +
'-------------------------------------------------
 +
Dim Pwrtag As Word
 +
Dim Outtag As Word
 +
Dim Exptag As Word
  
 +
'-------------------------------------------------
 +
'          work data
 +
'-------------------------------------------------
 +
Dim Par_byte As Byte
 +
Dim Par_word As Word
 +
Dim Par_addr As Word
 +
Dim Mytag As Word
 +
Dim Tempb As Byte
  
<pre>
+
'-------------------------------------------------
Interrupt_ticker:
+
'  enable
   Timer0 = Tmr_c_preload
+
'-------------------------------------------------
   Loadadr Msgbase , Z
+
   Enable Timer0
  Gosub Time_scan
+
   Enable Interrupts
Return
+
 
</pre>
 
</pre>
Bei jedem Aufruf von "Time_scan" wird bei allen Timer-Einträgen eins heruntergezählt. Bei null wird dann der Queue-Eintrag aktiv gesetzt.
+
Es werden 3 Units mit der selben Funktion erzeugt. Jede Unit steuert einen PCF-Chip auf dem RNBFRA-Board.
 +
<pre>
 +
'-------------------------------------------------
 +
'          setup Units
 +
'-------------------------------------------------
 +
' RNBFRA PCF Ports
  
'''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.
+
      Exptag = Unit_new()
 +
      Par_addr = Loadlabel(exp_par)                        ' PCF Inp-Port
 +
      Call Pcfclass(exptag , Cmd_setup , Par_addr)
  
====Anlegen einer Unit====
+
      Outtag = Unit_new()
Als Beispiel eine eigentlich völlig sinnlose Klasse, die nur demonstrieren soll, wie das Ganze funktioniert
+
      Par_addr = Loadlabel(out_par)                        ' PCF OUT-Port
*Definitionen und Parameter
+
      Call Pcfclass(outtag , Cmd_setup , Par_addr)
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.  
+
      Pwrtag = Unit_new()
 +
      Par_addr = Loadlabel(pwr_par)                        ' PCF PWR-Port
 +
      Call Pcfclass(pwrtag , Cmd_setup , Par_addr)
 +
</pre>
 +
Nur zur Erläuterung habe ich die Unit-Paramter gleich hier eingefügt, in SYS.BAS sind die natürlich weiter hinten.  
 
<pre>
 
<pre>
  Any_par:
+
'-----------------------------------------------
   Data Cls_any , 1 , &H78 , 16255% , 300000& , 3.55!
+
'        Class parameter
 +
'-----------------------------------------------
 +
'        class  idn devadr    default      timeout mS
 +
Out_par:
 +
   Data Cls_pcf , 1 , Out_adr , Out_default , 0%           ' passive device
 +
Pwr_par:
 +
  Data Cls_pcf , 2 , Pwr_adr , Pwr_default , 0%            ' passive device
 +
Exp_par:
 +
  Data Cls_pcf , 3 , Exp_adr , 0 , 1500%                  ' auto sensor device
 
</pre>
 
</pre>
Obligat als erstes der Klassencode, dann der Identifier. Der Rest ist eben irgendwas, was diese eine konkrete Unit zum arbeiten benötigt.
+
Für jede Unit gibt es vorgegebene Werte:
 
+
*Die ersten beiden Bytes '''müssen''' das Unit eindeutig identifizierbar machen, der Rest ist wahlfrei
*Eintragen in der Tabelle
+
**Die Ident-Nummer 1  (bei SYS.BAS die Geräteklasse)
Natürlich NACH Unit-Init()
+
**Die Ident-Nummer 2  (bei SYS.BAS das spezielle Gerät dieser Klasse)
 +
*Wahlfrei, was eben die Unit zum Arbeiten braucht
 +
**die I2C Addresse des PCF, der zu bedienen ist
 +
**Ein Anfangswert für diese Port. Beim Input (3) ist das sinnlos,
 +
**dafür steht nur bei diesem als nächstes der Wert 1500 als automatischer Wiederholungswert. Beim 1mS Timer ergibt das 1.5 Sekunden.
 
<pre>
 
<pre>
Dim Par_addr As Word      'Hilfsfeld
+
'-------------------------------------------------
Dim Anytag As Word       
+
'          main loop
 
+
'-------------------------------------------------
       Anytag = Unit_new(unitbase)      
+
  Do
 +
       Gosub Rxinput
 +
      Call Msg_dequeue(0 , 0 , 0)
 +
  Loop
 +
End
 +
</pre>
 +
Die Hauptschleife ist ziemlich einfach gehalten. Es wird jedesmal abgefragt, ob der PC was gesendet hat, danach wird geguckt, ob eine Message zum Abarbeiten ist. Und so geht's die ganze Zeit dahin.
 +
<pre>
 +
'==============================================================================
 +
' Timer 0  interrupt
 +
'==============================================================================
 +
Interrupt_ticker:
 +
  Timer0 = Tmr_c_preload
 +
  Gosub Time_scan
 +
Return
 
</pre>
 
</pre>
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
+
Die Timer ISR. Jedesmal wird mit  "Time-Scan" geprüft, ob eine "Getimete" Message fällig ist. Wenn ja, wird sie aktiviert.
Parameter
+
  
 
<pre>
 
<pre>
       Par_addr = Loadlabel(any_par)                         ' individial parameter
+
'==============================================================================
       Call Anyclass(mytag , Cmd_setup , Par_addr) 'CMD_SETUP ist festgelegt
+
' Workout Frame
 +
'==============================================================================
 +
Dim Class As Byte                                          'destination Class
 +
Dim Ident As Byte                                          'destination Ident
 +
Dim Msg As Word                                            'Message-Base
 +
Dim Leng As Byte
 +
 
 +
Rxinput:
 +
  Msg = Com_msg()
 +
  If Msg <> 0 Then
 +
       Leng = Com_rdbyte()
 +
       If Leng >= 3 Then
 +
        Class = Com_rdbyte()
 +
        Ident = Com_rdbyte()
 +
        Mytag = Unit_find_clsid(class , Ident)
 +
        If Mytag <> 0 Then
 +
            Call Unit_call(mytag , Cmd_msg , 0)
 +
        End If
 +
      End If
 +
      Call Com_msgdone(msg)
 +
  End If
 +
  Return
 
</pre>
 
</pre>
 +
Durch "Com-MSG()" wird gefragt, ob eine (komplette) Message eingetroffen ist. Entspricht etwa dem "Ischarwaiting()" vom Bascom selbst.
 +
Ist was da, wird das erste Byte der Message gelesen, das die Länge enthält. Nur, wenn die plausibel ist, wird die Zieladresse gelesen.
 +
Mit
 +
        Mytag = Unit_find_clsid(class , Ident)
 +
wird geschaut, ob wir so ein Unit auch haben. Wenn ja, wird mit
 +
            Call Unit_call(mytag , Cmd_msg , 0)
 +
die Funktion aufgerufen, mit dem Kommando cmd_msg, der Beiwert (argument) ist in diesem Falle NULL.
  
 +
Verarbeitet oder nicht, auf jeden Fall wird der noch reservierte Message-Bereich im UART-Buffer wieder freigegeben
 +
      Call Com_msgdone(msg)
 +
Und damit hat sich's.
  
*Setup der Unit
+
 
Das macht die Unit am besten selbst, da sie durch die mitgelieferten Paramter modifizierbar ist.  
+
===Die Unit "PCF"===
 +
Das ist ein Muster, das man auch ganz anders machen kann. Festgelegt ist nur der Einstieg und das Format. Natürlich erwartet das System, daß das Kommando durchgeführt wird, aber alle Details sind Sache der Anwenders.
 
<pre>
 
<pre>
'-----------------------------------------------
+
'------------------------------------------------------------------------------
'        Class ANY
+
'        UNITS
'-------------------------------------------------
+
'------------------------------------------------------------------------------
 +
'------------------------------------------------------------------------------
 +
'        Class PCF
 
'        private class data
 
'        private class data
'-------------------------------------------------
+
'------------------------------------------------------------------------------
Const Max_c_any = 2
+
Const Max_c_pcf = 3
Dim Anyidx As Byte
+
Dim Pcfidx As Byte
  
Dim Any_bck(max_c_any) As Word
+
Dim Pcf_bck(max_c_pcf) As Word                             ' backlink to tag
Dim Any_dta(max_c_any) As Byte
+
Dim Pcf_value(max_c_pcf) As Byte                           ' private data
 
</pre>
 
</pre>
WENN die Klasse /Unit private Daten im SRAM braucht, muß sie sie natürlich dimensionieren. Und zwar am besten
+
Wir wollen drei Units verwenden, also werden die verwendeten Daten als array dreifach angelegt. für den PCF haben wir eigentlich nur ein Byte, das den Soll oder Istwert des PCF aufnehmen kann.  
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
 
 
<pre>
 
<pre>
 
'---------------------------------------------------------------------------
 
'---------------------------------------------------------------------------
Sub Anyclass(byval Tag As Byte , Byval Cmd As Byte , Byval Param As Word)
+
Sub Pcfclass(byval Tag As Word , Byval Cmd As Byte , Byval Param As Word)
 +
Local Pcf_index As Byte
 +
 
 +
  Pcf_index = Unit_ref(tag)                                ' get private ref
  
 
   Select Case Cmd
 
   Select Case Cmd
 +
</pre>
 +
 +
===Command SETUP===
 +
<pre>
 +
'----------------------------------------------------------------------------
 +
'        SETUP Command
 +
'----------------------------------------------------------------------------
 
   Case Cmd_setup:
 
   Case Cmd_setup:
 +
      Call Unit_flg_set(tag , 1)                            ' set active
 +
      Par_word = Loadlabel(pcfclass)
 +
      Call Unit_vec_set(tag , Par_word)                    'set vector
 +
      Call Unit_par_set(tag , Param)                        'set FLASH parameter
 +
      Incr Pcfidx                                          'make private Index
 +
      Call Unit_ref_set(tag , Pcfidx)                      'store it
 +
      Pcf_index = Pcfidx
 +
      Pcf_bck(pcf_index) = Tag                              ' Backlink to unit
 +
      Pcf_value(pcf_index) = Unit_parbyte(tag , 3)          ' default value
 +
      Pcf_index = Unit_ref(tag)                            ' set received value
 +
      I2cinit
 +
</pre>
 +
In den folgenden Zeilen unterscheidet der Code, ob der jeweilige PCF als INPUT oder als OUTPUT zu verwenden ist.
 +
*Ist ein TImeout angegeben, soll es wohl INPUT sein, d.h. es wird ein Timer aufgesetzt
 +
*Sonst ist es wohl OUTPUT, daher wird der Anfangswert lt. Parameter an der PCF gesendet.
 +
<pre>
 +
      Par_word = Unit_parword(tag , 4)                      ' refresh time
 +
      If Par_word <> 0 Then
 +
        Gosub Pcf_refresh_time                            ' sensor device
 +
      Else
 +
        Gosub Pcf_send_val                                ' act.device
 +
      End If
 +
</pre>
 +
===Command MESSAGE (from PC)===
 +
<pre>
 +
'----------------------------------------------------------------------------
 +
'        MESSAGE RECEIVED  Command
 +
'----------------------------------------------------------------------------
 +
  Case Cmd_msg:
 +
      Tempb = Com_rdbyte()                                  ' ignore source class
 +
      Tempb = Com_rdbyte()                                  ' ignore source ident
 +
      Tempb = Com_rdbyte()                                  ' data byte
 +
      Pcf_value(pcf_index) = Tempb                            ' store
 +
      Gosub Pcf_send_val
 +
</pre>
 +
Der empfangene Wert wird zum PCF gesendet.
  
   Case Cmd_show:
+
===Command SET===
 +
Wird im Beispiel nicht verwendet.
 +
<pre>
 +
'----------------------------------------------------------------------------
 +
'        SET DEVICE Command
 +
'----------------------------------------------------------------------------
 +
   Case Cmd_set:
 +
      Pcf_value(pcf_index) = Param                            ' store
 +
      Gosub Pcf_send_val
 +
</pre>
 +
 
 +
===Command TIME===
 +
Wenn der TImer (1500, s.o.) abgelaufen ist, wird die Unit mit diesem Kommando aufgerufen. Es wird der aktuelle Wert vom PCF geholt, und als Message an den PC geschickt.
 +
Danach wird ein neuer Timer aufgesetzt.
 +
<pre>
 +
'----------------------------------------------------------------------------
 +
'        TIME ELAPSE  Command
 +
'----------------------------------------------------------------------------
 +
  Case Cmd_time:
 +
      Gosub Pcf_sens_val                                    ' read PCF Device
 +
'----------------------------------------------------------------------------
 +
'send Message to host
 +
      Call Com_txstart()
 +
' write empfänger--------
 +
      Tempb = 0
 +
      Call Com_txbyte(tempb)                                ' dest class
 +
      Call Com_txbyte(tempb)                                ' ident
 +
' write absender---------
 +
      Tempb = Unit_parbyte(tag , 0)                        '
 +
      Call Com_txbyte(tempb)                                ' source class
 +
      Tempb = Unit_parbyte(tag , 1)                        ' source ident
 +
      Call Com_txbyte(tempb)
 +
' write data-------------
 +
      Tempb = Pcf_value(pcf_index)
 +
      Call Com_txbyte(tempb)                                ' Data
 +
' finish message---------
 +
      Call Com_txclose()
 +
'----------------------------------------------------------------------------
 +
 
 +
      Gosub Pcf_refresh_time                                ' refresh timer
  
 
   Case Else:
 
   Case Else:
      Print Str(tag) ; " CMD1:" ; Str(cmd) ; " PAR1:" ; Str(param)
+
 
 
   End Select
 
   End Select
 +
  Exit Sub
 +
</pre>
 +
 +
Noch einige Sub's für die Unit.
 +
<pre>
 +
Pcf_refresh_time:
 +
  Par_word = Unit_parword(tag , 4)                        ' refresh time
 +
  Par_byte = Time_entry(0 , Tag , Cmd_time , Par_word)    'new timer
 +
  Return
 +
Pcf_send_val:
 +
  Par_byte = Unit_parbyte(tag , 2)                        ' device address
 +
  Tempb = Pcf_value(pcf_index)
 +
  I2cstart
 +
  I2cwbyte Par_byte
 +
  I2cwbyte Tempb
 +
  I2cstop
 +
  Tempb = 0
 +
  Return
 +
Pcf_sens_val:
 +
  Par_byte = Unit_parbyte(tag , 2)                        ' device address
 +
  Incr Par_byte                                            ' I2C Read addr
 +
  I2cstart
 +
  I2cwbyte Par_byte
 +
  I2crbyte Tempb , Nack
 +
  I2cstop
 +
  Pcf_value(pcf_index) = Tempb
 +
  Return
 
End Sub
 
End Sub
 
</pre>
 
</pre>
  
Wenn die Klasse irgendein Command nicht kennt, zeigt sie es hier im Beispiel einfach an und macht sonst nichts.
+
==Befehls-Referenz==
 
+
===UNIT===
Wichtig ist '''CMD_SETUP''', das Setzen des Sprungzieles und der Parameter
+
====Anlegen einer Unit====
 +
Durch die Konstante
 +
Const Max_c_unit = 8
 +
wurde der Platz für 8 Units bereitgestellt.
 
<pre>
 
<pre>
Local Par_word As Word
+
DIM tag AS WORD
      Par_word = Loadlabel(anyclass)
+
tag = Unit_new()
      Call Unit_vec_set(tag , Par_word)
+
      Call Unit_par_set(tag , Param)
+
 
</pre>
 
</pre>
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.  
+
Wenn noch Platz war, enthält ''tag'' als Ergebnis die Speicheradresse für die neue Unit. Mit diesem Wert sind die folgenden Befehle möglich:
 +
      Call Unit_flg_set(tag , 1)                           ' set active
 +
dadurch ist der Unit-Bereich endgültig reserviert.  
  
 +
      Par_word = Loadlabel(pcfclass)
 +
      Call Unit_vec_set(tag , Par_word)                    'set vector
 +
''pcfclass'' ist der Name der Funktion, die normalerweise vom System angesprungen wird, wenn irgendetwas von der Unit verlangt wird.
  
Nun wird ein privates Datenbereich reserviert. Der Klassenspezifische Index wird (bei jedem setup) erhöht und als Datenreferenz in der Unit-Tabelle eingetragen
+
      Par_word = Loadlabel(Unitparam)
<pre>
+
       Call Unit_par_set(tag , Byval Par-Word)
      Incr Anyidx
+
''Unitparam'' ist ein Label im Flash-Speicher, wo mit "DATA" die für das Unit spezifischen Konstanten angelegt wurden. Die ersten beiden Konstanten-Bytes müssen zusammen eine eindeutige ID für die Unit bilden. 
       Call Unit_ref_set(tag , Anyidx)
+
  
       Any_bck(anyidx) = Tag
+
       Call Unit_ref_set(tag , Pcfidx)                       'store private data referenz
      Any_dta(anyidx) = 0
+
''Pcfidx'' ist ein Byte, überlicherweise ein Index, mit dessen Hilfe die Unit immer zu ihren ganz privaten SRAM daten finden kann.  
</pre>
+
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'''
+
====Finden des TAG einer Unit====
<pre>
+
Ist der tag der Unit nicht bekannt, aber der Unit-Code (s.o.), dann
Local Par_class As Byte
+
DIM tag AS WORD
Local Par_ident As Byte
+
tag = Unit_find_clsid(ident-byte-1, ident-byte-2)
Local Par_devad As Byte
+
Local Par_word As Word
+
Local Par_long As Long
+
Local Par_sing As Single
+
  
  Case Cmd_show:
+
====Aufruf einer Unit====
      Par_class = Unit_parbyte(tag , 0)     ' Der Klassencode steht immer an der ersten Stelle der Parameter
+
Der genormte (direkte) Aufruf
      Par_ident = Unit_parbyte(tag , 1)     ' ebenso Ident
+
  CALL Unit_call(tag , Cmd-code-byte , Cmd-Param-word )
 +
''Cmd-code-byte , Cmd-Param-word'' sind, vom System her gesehen, beliebige Werte, mit denen die Unit halt was anfangen können soll.
  
      Par_devad = Unit_parbyte(tag , 2)     ' die Parameter werden einfach geholt und angezeigt.
+
Das entspricht dem folgenden Aufruf:
      Par_word = Unit_parword(tag , 3)      ' das Daten-Offset muß man wissen, auch was dann dort steht
+
call Pcfclass(Tag , Cmd-code-byte , Cmd-Param-word )
      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)
+
</pre>
+
  
====Aufrufbeispiele====
+
Die folgenden Aufrufe kann die Sub nun tätigen:
  
<pre>
+
UnitFlag = Unit_flg(tag)
'------------ AUFBAU-----------------------------
+
UnitEntry = Unit_vec(tag)
      Anytag = Unit_new(unitbase)
+
UnitParam = Unit_par(tag)
      Par_addr = Loadlabel(any_par)                         ' individial parameter1
+
UnitRef = Unit_ref(tag)  
      Call Anyclass(mytag , Cmd_setup , Par_addr)
+
  
      Anytag = Unit_new(unitbase)
+
Die erwähnten Unit-Parameter aus dem Flash-Speicher können mit Angabe des Byte-Offset gelesen werden
      Par_addr = Loadlabel(any_par2)                        ' individial parameter2
+
      Call Anyclass(mytag , Cmd_setup , Par_addr)
+
  
'------------ CALLS-----------------------------
+
Declare Function Unit_parbyte(tag As Word , Byval Offset As Byte) As Byte
' suchen des Tags nach Class und Ident
+
Declare Function Unit_parword(tag As Word , Byval Offset As Byte) As Word
      Anytag = Unit_find_clsid(unitbase , Cls_any , 1)
+
Declare Function Unit_parintg(tag As Word , Byval Offset As Byte) As Integer
 +
Declare Function Unit_parlong(tag As Word , Byval Offset As Byte) As Long
 +
Declare Function Unit_parsing(tag As Word , Byval Offset As Byte) As Single
  
' Direkter Aufruf
+
Wenn erforderlich, können die Werte aber auch sequentiell mittels "READ" gelesen werden
      Call Anyclass(anytag , Cmd_show , 0)
+
  
DIM Wrk_idx AS BYTE
+
===MESSAGES & TIMER===
  
' Indirekter Aufruf über die Queue
+
Declare Sub Msg_dequeue(byval Tag As Word , Byval Cmd As Byte , Byval Param As Word)
      Wrk_idx = Msg_enqueue(msgbase , 0 , Anytag , Cmd_show , 0)
+
Das ist der Befehl, wo in der Befehls-Warteschlange nach dem nächsten Auftrag gesucht wird. Ist einer da, wird er durchgeführt, ansonsten geschieht nichts. Typisch findet man diesen Befehl zentral in der Haupt DO..LOOP Schleife (s.o. im SYS.BAS - Beispiel)
  
' Auswerten der Queue
+
Declare Sub Time_scan()
      Call Msg_dequeue(msgbase , 0 , 0)
+
Das ist die Sub, die bei jedem Timerinterrupt aufgerufen wird, am besten direkt in der ISR. Sie zählt in allen anstehenden Timer-Aufträgen den Counter hinunter, bei NULL wird dieser Auftrag aktiv (Und von "msg-dequeue" durchgeführt)
  
 +
<pre>
 +
Declare Function Msg_enqueue(byval Vector As Word , Byval Tag As Word , Byval Cmd As Byte , Byval Param As Word) As Byte
 +
Declare Function Time_entry(byval Vector As Word , Byval Tag As Word , Byval Cmd As Byte , Byval Param As Word) As Byte
 +
</pre>
 +
Mit diesen Funktionen werden Aufträge an Units in die Warteschlange gestellt.
 +
*Bei msg_enqueue() ist ''Param'' einfach ein Wert, der dann auch an die Unit übergeben wird
 +
*Bei Time-entry() ist das die Anzahl Ticks, die "time-scan" herunterzählen soll.
 +
Bei einem 1 mS Ticker sind also 65535 mS möglich, also etwa eine Minute.
  
END
+
Vector kann als Null übergeben werden, wenn tag vorhanden ist. Dann wird der standard-entry der Unit angesprungen. Anderenfalls hat Vector Vorrang, d.h. im Grunde kann jede x-beliebige SUB aufgerufen werden, die das Format
 +
sub-name(Byval xxx As Word , Byval xxx As Byte , Byval xxxx As Word)
 +
hat. Wieweit die dann mit den Call-Argumenten zurechtkommt (wenn sie sie wirklich braucht) ist Sache des Annwenders.
  
Any_par:
+
===Kommunikation===
  Data Cls_any , 1 , &H78 , 16255% , 300000& , 3.55!
+
Declare Function Com_msg() As Word
Any_par2:
+
Mit dieser Fuktion wird gefragt, ob ein neues Paket über die UART reingekommen ist. Wenn nicht, wird NULL zurückgegeben. Sonst ist es die (Byte) Adresse der Länge dieser neuen Nachricht.
  Data Cls_any , 2 , &H44 , 255% , 250000& , 1.44!
+
Dieser Rückgabewert muß, egal, was mit der Nachricht gemacht wurde, dann der SUB
 +
Declare Sub Com_msgdone(msg As Word)
 +
übergeben werden, damit der Platz im Buffer wieder frei wird.  
 +
<pre>
 +
Declare Function Com_rdbyte() As Byte
 +
Declare Function Com_rdword() As Word
 +
Declare Function Com_rdlong() As Long
 +
Declare Function Com_rdintg() As Integer
 +
Declare Function Com_rdsing() As Single
 
</pre>
 
</pre>
 +
Mit diesen Funktionen kann das Paket gelesen werden (das erste Byte ist die Länge). Die Varianten gibt es, damit auch andere Datentypen direkt gelesen werden können, ohne daß sich Bascom beschwert.
 +
 +
'''Anmerkung''': Mit diesen Funktionen wird das Bascom "serialin=buffered" ersetzt. Das heißt, die Daten werden asynchron mittels Interrupt empfangen. Dabei wird der Ringbuffer, den Bascom angelegt hat, für den Level-0 Empfang verwendet.
 +
 +
<pre>
 +
Declare Sub Com_txstart()
 +
Declare Sub Com_txbyte(byval Val As Byte)
 +
Declare Sub Com_txclose()
 +
</pre>
 +
Wie man sich vorstellen kann, wird eine zu sendende Nachricht mit -start eröffnet, mit -close beendet, die Daten werden dazwischen nach der Reihe reingeschrieben. Dzt. gibt es dazu noch nicht die Varianten für verschieden Datentypen, die muß ich nachreichen.
 +
 +
Die Sende-Subs sind dzt. noch nicht gepuffert, d.h. jeder zu sendende Byte geht direkt in das UDR-Register. Das hält zwar auf, doch ein gepufferter Output würde ein Prozedere für "Buffer-Overflow" erfordern, und das wollte ich mir in erster Lesung noch nicht antun.
 +
 +
 +
'''Network Controller/PC''': Die Kommunikation setzt auf dem dzt. Stand des Projektes auf, soweit es den Level-0 betrifft. Das Beispielprogramm SYS.BAS nimmt aber vorweg, das als Sende- und Empfangsadresse 2 Byte, also 16-Bit verwendet werden. Hier kann es noch zu Änderungen kommen, die dann aber auch hier berücksichtigt werden.
 +
 +
==Anmerkungen==
 +
Die Kommunikations-Library Myrncom.BAS / LBX ist von den anderen Funktionen unabhängig. Man muß also die andern Libraries nicht zwangläufig verwenden. Umgekehrt geht das System auch ohne die Kommunikation-Module. Man sieht aus der Referenz, welche Calls dann halt möglich sind bzw. wegfallen.
 +
  
 
==Autor==
 
==Autor==
Zeile 274: Zeile 535:
  
 
==Siehe auch==
 
==Siehe auch==
* [[Network_MC/PC]]
+
*[[Network Controller/PC]]
 +
*[[Network Controller/PC Schichten]]
 +
*[[Network Controller/PC Vermittlung]]
 +
*[[Network Controller/PC Spezifikationen]]
 +
*[[Kommunikation/Protokolle]]
  
  
  
 
[[Kategorie:Projekte]]
 
[[Kategorie:Projekte]]

Aktuelle Version vom 9. März 2006, 19:30 Uhr

Betriebsystem für Bascom

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

MYSYSLIB.ZIP

  • Bascom-Libraries --> In das Bascom-Lib Verzeichnis kopieren
    • Mybuffer.LBX Interne Hilfsfunktionen
    • Myunit.LBX UNIT Kontrolle
    • Mymsgque.LBX MESSAGE-QUEUE Kontrolle
    • Myrncom.LBX Kommunikations Module
  • Bascom-include-Files--> In ein Bascom Projekt- Verzeichnis kopieren
    • Myunit.BAS
    • Mymsgque.BAS
    • Myrncom.BAS
  • Bascom-Demo-TestProgramm
    • SYS.BAS, ausgelegt f. RNBFRA 1.2 ebenfalls in ein Projekt-Verzeichnis. Letztlich ist das Programm etwa 3700 Byte groß, also auch mit einer BasCom DemoVersion zu behandeln
  • Kommunikations-Test-Parter f. PC
    • RN_SERVER.EXE f. XP, W98, W2k
    • MFC42(d).DLL, was das jemandem fehlen sollte

Wenn alles verbunden und gestartet ist, steuert der PC im Sekundentakt die 4 LEDs auf der RNBFRA Karte.

Vorwort, Sinn und Zweck

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.

Anmerkung: Dieses Multi-tasking beruht natürlich darauf, daß so ein einzelnes "Task-Slice" kurz gehalten wird und keine wesentlichen Wait-States beinhaltet. Schließlich muß für die anderen ja auch was übrigbleiben. Auch ausführlich "FOR" Schleifen sollten zu Einzel-Slices umgewandelt werden, d.h. bei jedem Aufruf EIN Step. Was daraus folgt, ist, daß die einzelnen Tasks im Grunde als "State-Machine" arbeiten, z.B.

  • FOR-Start
  • FOR-Step
  • FOR-Limit

Noch eins: Das System betrachtet den Anwender nicht als seinen natürlichen Feind, d.h. etliche Formal-Prüfungen der Call-Parameter mit Fehlercodes etc. sind nicht implementiert. Bitte also das Ganze pfleglich und mit etwas Liebe zu behandeln.

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

Anmerkung: Aus Sicht des Systems sind das einfach zwei Bytes, die zusammen auf diesem Rechner eindeutig sein müssen.

  • 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" im Rahmen des Bascom 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.

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).

SYS.BAS Demo

Statt umfangreicher theoretischer Abhandlungen einfach das Sys.BAS Demoprogramm mit einigen Kommentaren.

$regfile = "m32def.dat"
$crystal = 8000000

Const Tmr_c_prescale = 64
Const Tmr_c_preload = 131

$baud = 9600
$hwstack = 64
$swstack = 256
$framesize = 64


Const Max_c_unit = 8
Const Max_c_msg = 16

Const Rx_bufsize = 48

$include "myrncom.bas"
$include "myunit.bas"
$include "mymsgque.bas"

Hier erfolgen die Definitionen für die Library

  • Die Timer-Werte sind für 1 mS Interrupt bei 8MHZ ausgelegt.
  • Max_c_unit gibt an, wieviele Units maximal verwendet werden können.
  • Max_c_msg gibt an, wieviele Messages gleichzeitig verwendet werden können.
  • rx_buffersize gibt die Größe des (Ring)Buffers für den RNCOM-Empfang an.
  • Include: in den definitions-includes werden alle notwendigen Werte festgelegt.
'-------------------------------------------------
'           Common Commands
'-------------------------------------------------
Const Cmd_nop = 0
Const Cmd_setup = 1
Const Cmd_init = 2

Const Cmd_time = 4
Const Cmd_set = 5
Const Cmd_msg = 16

Das sind Muster für festgelegte Codes für Kommandos. Dem System ist es gleich, was man definiert. Es muß halt in ein Byte reinpassen.

'-------------------------------------------------
'           RNBFRA I2C
'-------------------------------------------------
Config I2cdelay = 5
Config Scl = Portc.0                                        'Ports fuer IIC-Bus
Config Sda = Portc.1
'-------------------------------------------------
'           RNBFRA I2C Addresses
'-------------------------------------------------
Const Pwr_adr = &H74                                        'I2C Adress Powerport
Const Out_adr = &H72                                        'I2C Adress Extension-Out Port
Const Exp_adr = &H7E                                        'I2C Read Adress Expansion-In Port


Const Pwr_default = &HFF                                    'I2C Powerport
Const Out_default = &H00                                    'I2C Extension-Out Port

Das ist dem RNBFRA-User alles vertraut, braucht man wohl nicht zu erklären.

'-------------------------------------------------
'           Class IDs
'-------------------------------------------------
Const Cls_pcf = &H42                                        ' ASCII "B"

Das sollte man für den Demo-Test nicht ändern, da es auch im PC-Programm so verwendet wird.

'-------------------------------------------------
'           CLASS entries     R8:R9=Flash Param  (restore indiv.params)
'-------------------------------------------------
Declare Sub Pcfclass(byval Tag As Word , Byval Cmd As Byte , Byval Param As Word)

Die Deklaration der UNIT-Haupt-Funktion. Der Name ist beliebig, das Format muß aber genau so sein.

'=========================================================================================
' Timer0 Prescale for 1mS Interrupt
'=========================================================================================
Config Timer0 = Timer , Prescale = Tmr_c_prescale           'Timer 1mS
On Timer0 Interrupt_ticker                                  ' Timer for Timer Queue

Hier wird Timer0 initialisiert.Das System selbst verwendet den Timer nicht. Nur um "getimete" Messages abzusetzen, braucht man natürlich schon irgendeinen Timer

'-------------------------------------------------
' frequently used units
'-------------------------------------------------
Dim Pwrtag As Word
Dim Outtag As Word
Dim Exptag As Word

'-------------------------------------------------
'           work data
'-------------------------------------------------
Dim Par_byte As Byte
Dim Par_word As Word
Dim Par_addr As Word
Dim Mytag As Word
Dim Tempb As Byte

'-------------------------------------------------
'   enable
'-------------------------------------------------
   Enable Timer0
   Enable Interrupts

Es werden 3 Units mit der selben Funktion erzeugt. Jede Unit steuert einen PCF-Chip auf dem RNBFRA-Board.

'-------------------------------------------------
'           setup Units
'-------------------------------------------------
' RNBFRA PCF Ports

      Exptag = Unit_new()
      Par_addr = Loadlabel(exp_par)                         ' PCF Inp-Port
      Call Pcfclass(exptag , Cmd_setup , Par_addr)

      Outtag = Unit_new()
      Par_addr = Loadlabel(out_par)                         ' PCF OUT-Port
      Call Pcfclass(outtag , Cmd_setup , Par_addr)

      Pwrtag = Unit_new()
      Par_addr = Loadlabel(pwr_par)                         ' PCF PWR-Port
      Call Pcfclass(pwrtag , Cmd_setup , Par_addr)

Nur zur Erläuterung habe ich die Unit-Paramter gleich hier eingefügt, in SYS.BAS sind die natürlich weiter hinten.

'-----------------------------------------------
'        Class parameter
'-----------------------------------------------
'         class   idn  devadr    default      timeout mS
Out_par:
   Data Cls_pcf , 1 , Out_adr , Out_default , 0%            ' passive device
Pwr_par:
   Data Cls_pcf , 2 , Pwr_adr , Pwr_default , 0%            ' passive device
Exp_par:
   Data Cls_pcf , 3 , Exp_adr , 0 , 1500%                   ' auto sensor device

Für jede Unit gibt es vorgegebene Werte:

  • Die ersten beiden Bytes müssen das Unit eindeutig identifizierbar machen, der Rest ist wahlfrei
    • Die Ident-Nummer 1 (bei SYS.BAS die Geräteklasse)
    • Die Ident-Nummer 2 (bei SYS.BAS das spezielle Gerät dieser Klasse)
  • Wahlfrei, was eben die Unit zum Arbeiten braucht
    • die I2C Addresse des PCF, der zu bedienen ist
    • Ein Anfangswert für diese Port. Beim Input (3) ist das sinnlos,
    • dafür steht nur bei diesem als nächstes der Wert 1500 als automatischer Wiederholungswert. Beim 1mS Timer ergibt das 1.5 Sekunden.
'-------------------------------------------------
'           main loop
'-------------------------------------------------
   Do
      Gosub Rxinput
      Call Msg_dequeue(0 , 0 , 0)
   Loop
End

Die Hauptschleife ist ziemlich einfach gehalten. Es wird jedesmal abgefragt, ob der PC was gesendet hat, danach wird geguckt, ob eine Message zum Abarbeiten ist. Und so geht's die ganze Zeit dahin.

'==============================================================================
' Timer 0  interrupt
'==============================================================================
Interrupt_ticker:
   Timer0 = Tmr_c_preload
   Gosub Time_scan
Return

Die Timer ISR. Jedesmal wird mit "Time-Scan" geprüft, ob eine "Getimete" Message fällig ist. Wenn ja, wird sie aktiviert.

'==============================================================================
' Workout Frame
'==============================================================================
Dim Class As Byte                                           'destination Class
Dim Ident As Byte                                           'destination Ident
Dim Msg As Word                                             'Message-Base
Dim Leng As Byte

Rxinput:
   Msg = Com_msg()
   If Msg <> 0 Then
      Leng = Com_rdbyte()
      If Leng >= 3 Then
         Class = Com_rdbyte()
         Ident = Com_rdbyte()
         Mytag = Unit_find_clsid(class , Ident)
         If Mytag <> 0 Then
            Call Unit_call(mytag , Cmd_msg , 0)
         End If
      End If
      Call Com_msgdone(msg)
   End If
   Return

Durch "Com-MSG()" wird gefragt, ob eine (komplette) Message eingetroffen ist. Entspricht etwa dem "Ischarwaiting()" vom Bascom selbst. Ist was da, wird das erste Byte der Message gelesen, das die Länge enthält. Nur, wenn die plausibel ist, wird die Zieladresse gelesen. Mit

        Mytag = Unit_find_clsid(class , Ident)

wird geschaut, ob wir so ein Unit auch haben. Wenn ja, wird mit

           Call Unit_call(mytag , Cmd_msg , 0)

die Funktion aufgerufen, mit dem Kommando cmd_msg, der Beiwert (argument) ist in diesem Falle NULL.

Verarbeitet oder nicht, auf jeden Fall wird der noch reservierte Message-Bereich im UART-Buffer wieder freigegeben

     Call Com_msgdone(msg)

Und damit hat sich's.


Die Unit "PCF"

Das ist ein Muster, das man auch ganz anders machen kann. Festgelegt ist nur der Einstieg und das Format. Natürlich erwartet das System, daß das Kommando durchgeführt wird, aber alle Details sind Sache der Anwenders.

'------------------------------------------------------------------------------
'        UNITS
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
'        Class PCF
'        private class data
'------------------------------------------------------------------------------
Const Max_c_pcf = 3
Dim Pcfidx As Byte

Dim Pcf_bck(max_c_pcf) As Word                              ' backlink to tag
Dim Pcf_value(max_c_pcf) As Byte                            ' private data

Wir wollen drei Units verwenden, also werden die verwendeten Daten als array dreifach angelegt. für den PCF haben wir eigentlich nur ein Byte, das den Soll oder Istwert des PCF aufnehmen kann.

'---------------------------------------------------------------------------
Sub Pcfclass(byval Tag As Word , Byval Cmd As Byte , Byval Param As Word)
Local Pcf_index As Byte

   Pcf_index = Unit_ref(tag)                                ' get private ref

   Select Case Cmd

Command SETUP

'----------------------------------------------------------------------------
'         SETUP Command
'----------------------------------------------------------------------------
   Case Cmd_setup:
      Call Unit_flg_set(tag , 1)                            ' set active
      Par_word = Loadlabel(pcfclass)
      Call Unit_vec_set(tag , Par_word)                     'set vector
      Call Unit_par_set(tag , Param)                        'set FLASH parameter
      Incr Pcfidx                                           'make private Index
      Call Unit_ref_set(tag , Pcfidx)                       'store it
      Pcf_index = Pcfidx
      Pcf_bck(pcf_index) = Tag                              ' Backlink to unit
      Pcf_value(pcf_index) = Unit_parbyte(tag , 3)          ' default value
      Pcf_index = Unit_ref(tag)                             ' set received value
      I2cinit

In den folgenden Zeilen unterscheidet der Code, ob der jeweilige PCF als INPUT oder als OUTPUT zu verwenden ist.

  • Ist ein TImeout angegeben, soll es wohl INPUT sein, d.h. es wird ein Timer aufgesetzt
  • Sonst ist es wohl OUTPUT, daher wird der Anfangswert lt. Parameter an der PCF gesendet.
      Par_word = Unit_parword(tag , 4)                      ' refresh time
      If Par_word <> 0 Then
         Gosub Pcf_refresh_time                             ' sensor device
      Else
         Gosub Pcf_send_val                                 ' act.device
      End If

Command MESSAGE (from PC)

'----------------------------------------------------------------------------
'         MESSAGE RECEIVED  Command
'----------------------------------------------------------------------------
   Case Cmd_msg:
      Tempb = Com_rdbyte()                                  ' ignore source class
      Tempb = Com_rdbyte()                                  ' ignore source ident
      Tempb = Com_rdbyte()                                  ' data byte
      Pcf_value(pcf_index) = Tempb                            ' store
      Gosub Pcf_send_val

Der empfangene Wert wird zum PCF gesendet.

Command SET

Wird im Beispiel nicht verwendet.

'----------------------------------------------------------------------------
'         SET DEVICE Command
'----------------------------------------------------------------------------
   Case Cmd_set:
      Pcf_value(pcf_index) = Param                            ' store
      Gosub Pcf_send_val

Command TIME

Wenn der TImer (1500, s.o.) abgelaufen ist, wird die Unit mit diesem Kommando aufgerufen. Es wird der aktuelle Wert vom PCF geholt, und als Message an den PC geschickt. Danach wird ein neuer Timer aufgesetzt.

'----------------------------------------------------------------------------
'         TIME ELAPSE   Command
'----------------------------------------------------------------------------
   Case Cmd_time:
      Gosub Pcf_sens_val                                    ' read PCF Device
'----------------------------------------------------------------------------
'send Message to host
      Call Com_txstart()
' write empfänger--------
      Tempb = 0
      Call Com_txbyte(tempb)                                ' dest class
      Call Com_txbyte(tempb)                                ' ident
' write absender---------
      Tempb = Unit_parbyte(tag , 0)                         '
      Call Com_txbyte(tempb)                                ' source class
      Tempb = Unit_parbyte(tag , 1)                         ' source ident
      Call Com_txbyte(tempb)
' write data-------------
      Tempb = Pcf_value(pcf_index)
      Call Com_txbyte(tempb)                                ' Data
' finish message---------
      Call Com_txclose()
'----------------------------------------------------------------------------

      Gosub Pcf_refresh_time                                ' refresh timer

   Case Else:

   End Select
   Exit Sub

Noch einige Sub's für die Unit.

Pcf_refresh_time:
   Par_word = Unit_parword(tag , 4)                         ' refresh time
   Par_byte = Time_entry(0 , Tag , Cmd_time , Par_word)     'new timer
   Return
Pcf_send_val:
   Par_byte = Unit_parbyte(tag , 2)                         ' device address
   Tempb = Pcf_value(pcf_index)
   I2cstart
   I2cwbyte Par_byte
   I2cwbyte Tempb
   I2cstop
   Tempb = 0
   Return
Pcf_sens_val:
   Par_byte = Unit_parbyte(tag , 2)                         ' device address
   Incr Par_byte                                            ' I2C Read addr
   I2cstart
   I2cwbyte Par_byte
   I2crbyte Tempb , Nack
   I2cstop
   Pcf_value(pcf_index) = Tempb
   Return
End Sub

Befehls-Referenz

UNIT

Anlegen einer Unit

Durch die Konstante

Const Max_c_unit = 8

wurde der Platz für 8 Units bereitgestellt.

 DIM tag AS WORD
 tag = Unit_new()

Wenn noch Platz war, enthält tag als Ergebnis die Speicheradresse für die neue Unit. Mit diesem Wert sind die folgenden Befehle möglich:

     Call Unit_flg_set(tag , 1)                            ' set active

dadurch ist der Unit-Bereich endgültig reserviert.

     Par_word = Loadlabel(pcfclass)
     Call Unit_vec_set(tag , Par_word)                     'set vector

pcfclass ist der Name der Funktion, die normalerweise vom System angesprungen wird, wenn irgendetwas von der Unit verlangt wird.

     Par_word = Loadlabel(Unitparam)
     Call Unit_par_set(tag , Byval Par-Word)

Unitparam ist ein Label im Flash-Speicher, wo mit "DATA" die für das Unit spezifischen Konstanten angelegt wurden. Die ersten beiden Konstanten-Bytes müssen zusammen eine eindeutige ID für die Unit bilden.

     Call Unit_ref_set(tag , Pcfidx)                       'store private data referenz

Pcfidx ist ein Byte, überlicherweise ein Index, mit dessen Hilfe die Unit immer zu ihren ganz privaten SRAM daten finden kann.


Finden des TAG einer Unit

Ist der tag der Unit nicht bekannt, aber der Unit-Code (s.o.), dann

DIM tag AS WORD
tag = Unit_find_clsid(ident-byte-1, ident-byte-2)

Aufruf einer Unit

Der genormte (direkte) Aufruf

 CALL Unit_call(tag , Cmd-code-byte , Cmd-Param-word )

Cmd-code-byte , Cmd-Param-word sind, vom System her gesehen, beliebige Werte, mit denen die Unit halt was anfangen können soll.

Das entspricht dem folgenden Aufruf:

call Pcfclass(Tag , Cmd-code-byte , Cmd-Param-word )

Die folgenden Aufrufe kann die Sub nun tätigen:

UnitFlag = Unit_flg(tag) 
UnitEntry = Unit_vec(tag)
UnitParam = Unit_par(tag)
UnitRef = Unit_ref(tag) 

Die erwähnten Unit-Parameter aus dem Flash-Speicher können mit Angabe des Byte-Offset gelesen werden

Declare Function Unit_parbyte(tag As Word , Byval Offset As Byte) As Byte
Declare Function Unit_parword(tag As Word , Byval Offset As Byte) As Word
Declare Function Unit_parintg(tag As Word , Byval Offset As Byte) As Integer
Declare Function Unit_parlong(tag As Word , Byval Offset As Byte) As Long
Declare Function Unit_parsing(tag As Word , Byval Offset As Byte) As Single

Wenn erforderlich, können die Werte aber auch sequentiell mittels "READ" gelesen werden

MESSAGES & TIMER

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

Das ist der Befehl, wo in der Befehls-Warteschlange nach dem nächsten Auftrag gesucht wird. Ist einer da, wird er durchgeführt, ansonsten geschieht nichts. Typisch findet man diesen Befehl zentral in der Haupt DO..LOOP Schleife (s.o. im SYS.BAS - Beispiel)

Declare Sub Time_scan()

Das ist die Sub, die bei jedem Timerinterrupt aufgerufen wird, am besten direkt in der ISR. Sie zählt in allen anstehenden Timer-Aufträgen den Counter hinunter, bei NULL wird dieser Auftrag aktiv (Und von "msg-dequeue" durchgeführt)

 Declare Function Msg_enqueue(byval Vector As Word , Byval Tag As Word , Byval Cmd As Byte , Byval Param As Word) As Byte
 Declare Function Time_entry(byval Vector As Word , Byval Tag As Word , Byval Cmd As Byte , Byval Param As Word) As Byte

Mit diesen Funktionen werden Aufträge an Units in die Warteschlange gestellt.

  • Bei msg_enqueue() ist Param einfach ein Wert, der dann auch an die Unit übergeben wird
  • Bei Time-entry() ist das die Anzahl Ticks, die "time-scan" herunterzählen soll.

Bei einem 1 mS Ticker sind also 65535 mS möglich, also etwa eine Minute.

Vector kann als Null übergeben werden, wenn tag vorhanden ist. Dann wird der standard-entry der Unit angesprungen. Anderenfalls hat Vector Vorrang, d.h. im Grunde kann jede x-beliebige SUB aufgerufen werden, die das Format

sub-name(Byval xxx As Word , Byval xxx As Byte , Byval xxxx As Word)

hat. Wieweit die dann mit den Call-Argumenten zurechtkommt (wenn sie sie wirklich braucht) ist Sache des Annwenders.

Kommunikation

Declare Function Com_msg() As Word

Mit dieser Fuktion wird gefragt, ob ein neues Paket über die UART reingekommen ist. Wenn nicht, wird NULL zurückgegeben. Sonst ist es die (Byte) Adresse der Länge dieser neuen Nachricht. Dieser Rückgabewert muß, egal, was mit der Nachricht gemacht wurde, dann der SUB

Declare Sub Com_msgdone(msg As Word)

übergeben werden, damit der Platz im Buffer wieder frei wird.

 Declare Function Com_rdbyte() As Byte
 Declare Function Com_rdword() As Word
 Declare Function Com_rdlong() As Long
 Declare Function Com_rdintg() As Integer
 Declare Function Com_rdsing() As Single

Mit diesen Funktionen kann das Paket gelesen werden (das erste Byte ist die Länge). Die Varianten gibt es, damit auch andere Datentypen direkt gelesen werden können, ohne daß sich Bascom beschwert.

Anmerkung: Mit diesen Funktionen wird das Bascom "serialin=buffered" ersetzt. Das heißt, die Daten werden asynchron mittels Interrupt empfangen. Dabei wird der Ringbuffer, den Bascom angelegt hat, für den Level-0 Empfang verwendet.

 Declare Sub Com_txstart()
 Declare Sub Com_txbyte(byval Val As Byte)
 Declare Sub Com_txclose()

Wie man sich vorstellen kann, wird eine zu sendende Nachricht mit -start eröffnet, mit -close beendet, die Daten werden dazwischen nach der Reihe reingeschrieben. Dzt. gibt es dazu noch nicht die Varianten für verschieden Datentypen, die muß ich nachreichen.

Die Sende-Subs sind dzt. noch nicht gepuffert, d.h. jeder zu sendende Byte geht direkt in das UDR-Register. Das hält zwar auf, doch ein gepufferter Output würde ein Prozedere für "Buffer-Overflow" erfordern, und das wollte ich mir in erster Lesung noch nicht antun.


Network Controller/PC: Die Kommunikation setzt auf dem dzt. Stand des Projektes auf, soweit es den Level-0 betrifft. Das Beispielprogramm SYS.BAS nimmt aber vorweg, das als Sende- und Empfangsadresse 2 Byte, also 16-Bit verwendet werden. Hier kann es noch zu Änderungen kommen, die dann aber auch hier berücksichtigt werden.

Anmerkungen

Die Kommunikations-Library Myrncom.BAS / LBX ist von den anderen Funktionen unabhängig. Man muß also die andern Libraries nicht zwangläufig verwenden. Umgekehrt geht das System auch ohne die Kommunikation-Module. Man sieht aus der Referenz, welche Calls dann halt möglich sind bzw. wegfallen.


Autor

Siehe auch


LiFePO4 Speicher Test