Bascom-AVR

Kleiner Bascom AVR Kurs -
Ausgaenge mit Computer steuern (z.B. LEDs, Relais,...)

Hallo!

Beim letzten mal ging es um die UART und die Verbindung des µC über die RS-232-Schnittstelle mit dem Computer. Dieses Wissen kann man jetzt nutzen, um mit dem Computer die Ausgänge (Pins) des µC anzusteuern (z.B. um damit LEDs oder Relais zu schalten).

Damit das Beispiel nicht zu unübersichtlich wird, möchte ich nur fünf Pins ansteuern. Das sollte aber genügen. Dieses Beispiel lässt sich mit dem ATmega8 ganz einfach auf 17 oder, wenn man aufs Ganze geht, sogar auf 20 Ausgänge erweitern.

Der ATmega8 soll auf diese Befehle reagieren:

"SET 2 ON"       -->   Rückgabe: "OK"
"SET 3 OFF"      -->   Rückgabe: "OK"
"SET 3 ON"       -->   Rückgabe: "OK"
"SET ALL ON"     -->   Rückgabe: "OK"
"SET ALL OFF"    -->   Rückgabe: "OK"
"GET 5"          -->   Rückgabe: "0" oder "1"
"GET 4"          -->   Rückgabe: "0" oder "1"
"GET ALL"        -->   Rückgabe: z.B. "01001"
"ALL 01001"      -->   Rückgabe: "OK"
                                 "OK"
                                 "OK"
                                 "OK"
                                 "OK"

Weiters wäre es ideal wenn die Groß-/Kleinschreibung bei diesen Befehlen ignoriert werden würde.

Und hier das (etwas umfangreichere) Beispiel:

'========================================================================
' Ausgänge des ATmega8 über die UART (RS-232) schalten.
' (by Gerold http://halvar.at)
'
' Kommandos:
' - SET <Ausgangsnummer> ON|OFF
'     Einzelne Ausgänge schalten.
'     Beispiele: "SET 1 ON" oder "SET 2 OFF"
'     Rückgabe: "OK". Bei Fehler "ERROR" oder nichts.
' - SET ALL ON|OFF
'     So können in einem Rutsch alle Ausgänge ein- oder ausgeschaltet werden.
'     Beispiele: "SET ALL ON" oder "SET ALL OFF"
'     Rückgabe: "OK". Bei Fehler "ERROR" oder nichts.
' - GET <Ausgangnummer>
'     So wird der Status des angegebenen Ausganges zurück gegeben.
'     Rückgabe: "0" oder "1"
' - GET ALL
'     Gibt den Status aller Ausgänge zurück.
'     Z.B. so: "10010". Diese Ausgabe bedeutet, dass der erste Ausgang und der
'     vierte Ausgang ein und die anderen Ausgänge aus sind. Die Zählung beginnt
'     links (nicht wie bei Binärzahlen, von rechts).
' - ALL <Status für alle Ausgänge>
'     Damit werden in einem Rutsch alle Ausgänge geschaltet.
'     Beispiel: "ALL 10010". In diesem Beispiel wird in einem Rutsch der erste
'     und der vierte Augang eingeschaltet. Die Ausgänge 2, 3 und 5 werden
'     ausgeschaltet.
'     Rückgabe: "OK" für jeden Ausgang. Bei Fehler "ERROR" oder nichts.
'========================================================================
$regfile = "m8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100
$baud = 38400

'Ausgänge
Output_1 Alias Portd.5
Output_2 Alias Portd.6
Output_3 Alias Portd.7
Output_4 Alias Portb.0
Output_5 Alias Portb.1

Config Output_1 = Output
Config Output_2 = Output
Config Output_3 = Output
Config Output_4 = Output
Config Output_5 = Output

'Globale Variablen
Dim Tmp As Byte
Dim New_command As String * 15
Dim Command_array(3) As String * 5
Dim New_status As Bit

'Prozeduren
Declare Sub Serial0charmatch()
Declare Sub Do_get_command(cmd_output As String)
Declare Sub Do_set_command(cmd_output As String , Cmd_status As String)
Declare Sub Do_all_command(cmd_status As String)

'UART-Buffer
Config Serialin = Buffered , Size = 15 , Bytematch = 13
Enable Interrupts


Do
   If New_command <> "" Then
      'Alles in Großbuchstaben umwandeln
      New_command = Ucase(new_command)

      'Anweisung aufteilen
      Tmp = Split(new_command , Command_array(1) , " ")

      Select Case Command_array(1)
      Case "GET"
         'Status des Ausganges/der Ausgänge zurück geben
         Call Do_get_command(command_array(2))
      Case "SET"
         'Stellt einen oder alle Ausgänge ein
         Call Do_set_command(command_array(2) , Command_array(3))
      Case "ALL"
         'Stellt alle Ausgänge ein
         Call Do_all_command(command_array(2))
      Case Else
         Print "ERROR: unknown command"
      End Select

      New_command = ""
   End If
Loop

End


'Wird automatisch ausgeführt, wenn CARRIAGE RETURN empfangen wird.
Sub Serial0charmatch()
   Input New_command Noecho
End Sub


'Gibt den Status des Ausganges/der Ausgänge zurück
Sub Do_get_command(cmd_output As String * 5)
   Select Case Cmd_output
   Case "ALL"
      Print Output_1 ; Output_2 ; Output_3 ; Output_4 ; Output_5
   Case "1"
      Print Output_1
   Case "2"
      Print Output_2
   Case "3"
      Print Output_3
   Case "4"
      Print Output_4
   Case "5"
      Print Output_5
   Case Else
      Print "ERROR: unknown output pin"
   End Select
End Sub


'Stellt einen oder alle Ausgänge ein
Sub Do_set_command(cmd_output As String * 5 , Cmd_status As String * 5)
   'Neuen Status herausfinden
   Select Case Cmd_status
   Case "ON"
      New_status = 1
   Case "OFF"
      New_status = 0
   Case Else
      Print "ERROR: unknown status"
      Exit Sub
   End Select

   'Ausgang/Ausgänge einstellen
   Select Case Cmd_output
   Case "ALL"
      Output_1 = New_status
      Output_2 = New_status
      Output_3 = New_status
      Output_4 = New_status
      Output_5 = New_status
   Case "1"
      Output_1 = New_status
   Case "2"
      Output_2 = New_status
   Case "3"
      Output_3 = New_status
   Case "4"
      Output_4 = New_status
   Case "5"
      Output_5 = New_status
   Case Else
      Print "ERROR: unknown output pin"
      Exit Sub
   End Select
   Print "OK"
End Sub


'Stellt alle Ausgänge ein
Sub Do_all_command(cmd_status As String * 5)
   Local Status_string As String * 1

   'Output_1
   Status_string = Mid(cmd_status , 1 , 1)
   Select Case Status_string
   Case "1"
      Output_1 = 1
      Print "OK"
   Case "0"
      Output_1 = 0
      Print "OK"
   Case Else
      Print "ERROR: unknown status"
   End Select

   'Output_2
   Status_string = Mid(cmd_status , 2 , 1)
   Select Case Status_string
   Case "1"
      Output_2 = 1
      Print "OK"
   Case "0"
      Output_2 = 0
      Print "OK"
   Case Else
      Print "ERROR: unknown status"
   End Select

   'Output_3
   Status_string = Mid(cmd_status , 3 , 1)
   Select Case Status_string
   Case "1"
      Output_3 = 1
      Print "OK"
   Case "0"
      Output_3 = 0
      Print "OK"
   Case Else
      Print "ERROR: unknown status"
   End Select

   'Output_4
   Status_string = Mid(cmd_status , 4 , 1)
   Select Case Status_string
   Case "1"
      Output_4 = 1
      Print "OK"
   Case "0"
      Output_4 = 0
      Print "OK"
   Case Else
      Print "ERROR: unknown status"
   End Select

   'Output_5
   Status_string = Mid(cmd_status , 5 , 1)
   Select Case Status_string
   Case "1"
      Output_5 = 1
      Print "OK"
   Case "0"
      Output_5 = 0
      Print "OK"
   Case Else
      Print "ERROR: unknown status"
   End Select
End Sub

Dann fange ich mal oben an. Ein wiederverwendbares Programm hat einen Kopf in dem erklärt wird was das Programm macht und was dazu notwendig ist beziehungsweise wie man es bedient. Viele schreiben in den Kopf auch, welche Pins wie angeschlossen werden. Das habe ich mir gespart, da ich die Pins im Programm mit aussagekräftigen Namen versehe.

Die Unterprozedur "Serial0charmatch" wird von Bascom automatisch ausgeführt, sobald über die UART das Zeichen mit der ASCII-Nummer 13 rein kommt. ASCII-13 ist CARRIAGE RETURN. Diese Einstellung wird mit dem Befehl CONFIG SERIALIN getroffen.

Die MainLoop habe ich klein gehalten, damit sie übersichtlich bleibt. Alle wichtigen Dinge werden in ausgelagerten Unterprozeduren erledigt.

Der Programmablauf beginnt, sobald über die UART ein CARRIAGE RETURN rein kommt. Bascom führt dann die Unterprozedur "Serial0charmatch" aus. In dieser Prozedur wird der Inhalt des UART-Buffers in die globale Variable "New_command" geschrieben.

In der MainLoop wird ständig geprüft ob etwas in dieser Variable steht. Sobald etwas drinnen steht, wird zuerst der Inhalt der Variable "New_command" mit dem Befehl UCASE in Großbuchstaben umgewandelt. Zahlen sind davon nicht betroffen.

Da ein Befehl aus zwei oder drei Teilen besteht, die mit einem Leerzeichen (" ") voneinander getrennt sind, splitte ich diese Teile auf, damit man besser damit arbeiten kann. Der Befehl SPLIT erledigt das für uns. SPLIT bekommt als erstes Argument den aufzusplittenden String übergeben. Als zweites Argument muss man ein ARRAY übergeben, welches mit den einzelnen Textabschnitten befüllt wird. Und als drittes Argument gibt man an, welche Zeichenfolge die einzelnen Textabschnitte voneinander trennt. Die Variable "Tmp" wird dabei mit der Anzahl der aufgesplitteten Textabschnitte befüllt (auch wenn wir diese Information nicht brauchen).

Mit SELECT CASE kann man den µC, je nach Wert einer Variable, etwas bestimmtes tun lassen. In diesem Fall wird geprüft ob im ersten Array-Element der Text "GET", "SET" oder "ALL" steht.

Wenn "GET" drinnen steht, dann wird die Unterprozedur "Do_get_command" ausgeführt. An diese Prozedur wird als Argument die abzufragende Pinnummer (als Text) oder "ALL" übergeben. Diese Information steht im zweiten Array-Element. In der Prozedur "Do_get_command" wird mit SELECT CASE geprüft, ob ALL oder eine Pinnummer übergeben wurde. Wurde "ALL" übergeben, dann wird der Status aller Ausgänge mit PRINT über die UART an den Computer zurück geschickt. Wurde eine Pinnummer übergeben, dann wird der Status dieses einen Pins zurück geschickt. Wurde irgendetwas anderes übergeben, dann wird eine Fehlermeldung zurück geschickt.

Wenn "SET" im ersten Array-Element steht, dann wird die Unterprozedur "Do_set_command" ausgeführt. An diese Prozedur wird als erstes Argument die Pinnummer des zu setzenden Pins oder "ALL" übergeben. Als zweites Argument wird der gewünschte Status ("ON" oder "OFF") übergeben. In der Prozedur "Do_set_command" wird zuerst geprüft ob "ON" oder "OFF" übergeben wurde. Bei "ON" wird die globale BIT-Variable "New_status" auf 1 gesetzt. Bei "OFF" wird diese Variable auf 0 gesetzt. Ich hätte ja gerne eine lokale Variable dafür verwendet, aber das ist eine Einschränkung von Bascom. Man kann keine BIT-Variablen lokal definieren. In diesem Fall ist das kein Problem, da das Programm noch recht übersichtlich ist und keine Gefahr besteht, dass der Wert der Variable "New_status" von Außen geändert werden kann. Diese Gefahr könnte bestehen, wenn man diese Variable auch in einem Interrupt-Handler verwende würde, aber das tun wir ja nicht. Nachdem der neue Status ermittelt wurde, wird wieder mit SELECT CASE entschieden, welcher Ausgang neu gesetzt wird.

Wenn "ALL" im ersten Array-Element steht, dann wird die Unterprozedur "Do_all_command" ausgeführt. An diese Prozedur wird nur der neue, gewünschte Status übergeben. Das genügt, da wir ja wissen, dass alle Ausgänge neu gesetzt werden sollen.

In der Prozedur wiederholt sich folgendes Szenario für jeden Pin:

  • Mit dem Befehl MID wird aus dem übergebenen Text (z.B. "10010") das erste, zweite, dritte, vierte oder fünfte Zeichen herausgeholt und in die lokale Variable "Status_string" geschrieben.
  • Je nach Wert des "Status_string" ("0" oder "1") wird der Ausgang auf LOW oder HIGH gesetzt.
  • Mit PRINT "OK" wird der Text "OK" über die UART an den Computer zurück gegeben.
  • Trat ein Fehler auf, dann wird eine Fehlermeldung zurück gegeben.

Nach dem Ausführen der entsprechenden Unterprozedur wird die Variable "New_command" wieder zurück gesetzt, damit nicht bei jedem Schleifendurchlauf die Ausgänge neu gesetzt werden.

Wurde in der MainLoop weder "GET", "SET" oder "ALL" erkannt, dann wird eine Fehlermeldung über die UART an den Computer zurück geschickt.

Dieses Programm kann man testen, indem man die Befehle direkt über ein Terminalprogramm an den µC schickt.

Viel Spaß beim Ausprobieren!


mfg Gerold :-)

PS: Man kann dieses Programm verkürzen. Aber ich habe absichtlich darauf verzichtet, da es sich ja um ein Beispiel für Anfänger handelt. Dieses Beispiel wird im nächsten Beitrag verändert und um ein paar zusätzliche Ausgänge erweitert. Dann hat man einen direkten Vergleich zwischen den einzelnen Techniken.



Den zugehörigen Original-Beitrag findest du im Loetstelle-Forum.