Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Balkonkraftwerk Speicher und Wechselrichter Tests und Tutorials

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.

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

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


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 Byten Bytes müssen den Unit-Code (Klasse, Ident) beinhalten, der Rest ist wahlfrei
    • Klassencode
    • Die Ident-Nummer 1-3
  • Wahlfrei
  • 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 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

 Declare Sub Unit_call(tag As Word , Byval Cmd As Byte , Byval Param As Word)
 Declare Function Unit_new() As Word
 Declare Function Unit_find_clsid(byval Class As Byte , Byval Ident As Byte) As Word
 Declare Sub Unit_vec_set(tag As Word , Byval Vec As Word)
 Declare Sub Unit_par_set(tag As Word , Byval Par As Word)
 Declare Sub Unit_ref_set(tag As Word , Byval Flg As Byte)
 Declare Sub Unit_flg_set(tag As Word , Byval Flg As Byte)
 Declare Function Unit_flg(tag As Word) As Byte
 Declare Function Unit_vec(tag As Word) As Word
 Declare Function Unit_par(tag As Word) As Word
 Declare Function Unit_ref(tag As Word) As Byte
 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

MESSAGES & TIMER

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

Kommunikation

 Declare Sub Com_msgdone(msg As Word)
 Declare Function Com_msg() As Word
 Declare Sub Com_txstart()
 Declare Sub Com_txclose()
 Declare Sub Com_txbyte(byval Val As Byte)
 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

Autor

Siehe auch


LiFePO4 Speicher Test