Hallo!
Wie versprochen, habe ich das letzte Programm ein wenig umgestaltet. Es kommen ein paar neue Befehle und Techniken darin vor, die ich im Anschluss erkläre. Außerdem habe ich das Programm aufgebohrt. Es können damit 20 Ausgänge des ATmega8 vom Computer aus ferngesteuert werden um damit LEDs, Relais oder andere Dinge zu schalten. :-)
Ich möchte nichts beschönigen. Das ist, im Gegensatz zu den anfänglichen kleinen Progrämmchen, schon richtig harter Stoff. Besonders für Nichtprogrammierer. Es ist nicht schlimm, wenn du mit diesem Programm noch nicht klar kommst. Schau' dir einfach die nächsten Kapitel an und lies dieses Programm später noch einmal durch.
'========================================================================
' 20 Ausgänge des ATmega8 über die UART (RS-232) schalten.
' (by Gerold http://halvar.at)
'
' Es wird der eingebaute RC-Oscillator mit 8 Mhz verwendet. So können PB6 und
' PB7 auch als Ausgänge verwendet werden.
'
' 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: "10010000000000000000".
' 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 10010000000000000000".
' In diesem Beispiel wird in einem Rutsch der erste
' und der vierte Augang eingeschaltet. Die restlichen Ausgänge werden
' ausgeschaltet.
' Rückgabe: "OK" für jeden Ausgang. Bei Fehler "ERROR" oder nichts.
' - ALL ON | OFF
' So können, wie bereits mit ``SET ALL ON | OFF``, in einem Rutsch alle
' Ausgänge ein- oder ausgeschaltet werden.
' Beispiele: "ALL ON" oder "ALL OFF"
' 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 nach Pin-Reihenfolge (1-28)
Output_1 Alias Portd.2
Output_2 Alias Portd.3
Output_3 Alias Portd.4
Output_4 Alias Portb.6
Output_5 Alias Portb.7
Output_6 Alias Portd.5
Output_7 Alias Portd.6
Output_8 Alias Portd.7
Output_9 Alias Portb.0
Output_10 Alias Portb.1
Output_11 Alias Portb.2
Output_12 Alias Portb.3 'MOSI
Output_13 Alias Portb.4 'MISO
Output_14 Alias Portb.5 'SCK
Output_15 Alias Portc.0
Output_16 Alias Portc.1
Output_17 Alias Portc.2
Output_18 Alias Portc.3
Output_19 Alias Portc.4
Output_20 Alias Portc.5
Config Output_1 = Output
Config Output_2 = Output
Config Output_3 = Output
Config Output_4 = Output
Config Output_5 = Output
Config Output_6 = Output
Config Output_7 = Output
Config Output_8 = Output
Config Output_9 = Output
Config Output_10 = Output
Config Output_11 = Output
Config Output_12 = Output
Config Output_13 = Output
Config Output_14 = Output
Config Output_15 = Output
Config Output_16 = Output
Config Output_17 = Output
Config Output_18 = Output
Config Output_19 = Output
Config Output_20 = Output
'Globale Variablen
Dim Tmp As Byte
Dim New_command As String * 30
Dim Command_array(3) As String * 20
'Prozeduren
Declare Sub Serial0charmatch()
Declare Sub Set_output(byval Outputnr As Byte , Byval Value As Byte)
Declare Sub Do_get_command(byval Cmd_output As String)
Declare Sub Do_set_command(byval Cmd_output As String , Byval Cmd_status As String)
Declare Sub Do_all_command(byval Cmd_status As String)
'UART-Buffer
Config Serialin = Buffered , Size = 30 , Bytematch = 13
'Interrupts einschalten
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
'Zurück setzen
New_command = ""
Command_array(1) = ""
Command_array(2) = ""
Command_array(3) = ""
End If
Loop
End
'Wird automatisch ausgeführt, wenn CARRIAGE RETURN empfangen wird.
Sub Serial0charmatch()
Input New_command Noecho
End Sub
Sub Set_output(byval Outputnr As Byte , Byval Value As Byte)
Select Case Outputnr
Case 1 : Output_1 = Value
Case 2 : Output_2 = Value
Case 3 : Output_3 = Value
Case 4 : Output_4 = Value
Case 5 : Output_5 = Value
Case 6 : Output_6 = Value
Case 7 : Output_7 = Value
Case 8 : Output_8 = Value
Case 9 : Output_9 = Value
Case 10 : Output_10 = Value
Case 11 : Output_11 = Value
Case 12 : Output_12 = Value
Case 13 : Output_13 = Value
Case 14 : Output_14 = Value
Case 15 : Output_15 = Value
Case 16 : Output_16 = Value
Case 17 : Output_17 = Value
Case 18 : Output_18 = Value
Case 19 : Output_19 = Value
Case 20 : Output_20 = Value
Case Else
Print "ERROR: unknown output pin"
End Select
End Sub
'Gibt den Status des Ausganges/der Ausgänge zurück
Sub Do_get_command(cmd_output As String * 3)
Select Case Cmd_output
Case "ALL"
Print Output_1 ; Output_2 ; Output_3 ; Output_4 ; Output_5 ; Output_6 ; _
Output_7 ; Output_8 ; Output_9 ; Output_10 ; Output_11 ; Output_12 ; _
Output_13 ; Output_14 ; Output_15 ; Output_16 ; Output_17 ; Output_18 ; _
Output_19 ; Output_20
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 "6" : Print Output_6
Case "7" : Print Output_7
Case "8" : Print Output_8
Case "9" : Print Output_9
Case "10" : Print Output_10
Case "11" : Print Output_11
Case "12" : Print Output_12
Case "13" : Print Output_13
Case "14" : Print Output_14
Case "15" : Print Output_15
Case "16" : Print Output_16
Case "17" : Print Output_17
Case "18" : Print Output_18
Case "19" : Print Output_19
Case "20" : Print Output_20
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 * 3 , Cmd_status As String * 3)
Local Outputnr As Byte
Local Value As Byte
'Neuen Status herausfinden
Select Case Cmd_status
Case "ON"
Value = 1
Case "OFF"
Value = 0
Case Else
Print "ERROR: unknown status"
Exit Sub
End Select
'Ausgang/Ausgänge einstellen
Select Case Cmd_output
Case "ALL"
For Outputnr = 1 To 20
Call Set_output(outputnr , Value)
Next Outputnr
Case Else
Outputnr = Val(cmd_output)
If Outputnr > 0 Then ' Wenn kein Fehler beim Umwandeln passiert ist
Call Set_output(outputnr , Value)
Else
Print "ERROR: unknown output pin"
Exit Sub
End If
End Select
Print "OK"
End Sub
'Stellt alle Ausgänge ein
Sub Do_all_command(cmd_status As String * 20)
Local Status_string As String * 1
Local Outputnr As Byte
Select Case Cmd_status
Case "ON"
For Outputnr = 1 To 20
Call Set_output(outputnr , 1)
Print "OK"
Next Outputnr
Case "OFF"
For Outputnr = 1 To 20
Call Set_output(outputnr , 0)
Print "OK"
Next Outputnr
Case Else
If Len(cmd_status) = 20 Then
For Outputnr = 1 To 20
Status_string = Mid(cmd_status , Outputnr , 1)
Select Case Status_string
Case "1"
Call Set_output(outputnr , 1)
Print "OK"
Case "0"
Call Set_output(outputnr , 0)
Print "OK"
Case Else
Print "ERROR: unknown status"
End Select
Next Outputnr
Else
Print "ERROR: unknown status"
End If
End Select
End Sub
Die Variable "New_command" ist jetzt größer geworden, da jetzt der Status beim Befehl ALL
20 Zeichen lang ist. "Command_array" wurde deshalb ebenfalls vergrößert. Auch CONFIG SERIALIN wurde entsprechend angepasst.
Wenn man sich die Deklarationen der Unterprozeduren ansieht, dann fallen zwei Dinge auf. Ich verwende "byval" und bei "As String" fehlt die Angabe der Textlänge.
Das ist jetzt ein bischen kompliziert. :-| Der Zusatz "byval" kennzeichnet einen Parameter als "Wertparameter". Das heißt, dass beim Aufruf der Unterprozedur der Wert einer Variable in eine neue Variable (in einen neuen Speicherbereich) kopiert wird. Gibt man "byval" nicht an, dann wird standardmäßig "byref" verwendet. Mit "byref" wird der Wert nicht in einen neuen Speicherbereich kopiert, sondern nur eine Referenz zum Speicherbereich übergeben in dem der Wert liegt. "byval" kapselt die Unterprozedur besser vom Rest des Programmes ab und hilft damit Fehler zu vermeiden. "byref" (der Standard) kapselt den Parameter nicht vom Rest des Programmes ab. Änderungen dieses Parameters (dieser Variable) wirken sich auch außerhalb der Unterprozedur aus. Umgekehrt ist es natürlich genau so. Ein mit "byref" deklarierter Parameter kann sich (z.B. durch einen Interrupt) ändern, wärend die Unterprozedur abgearbeitet wird. Das kann wiederum unerklärliche Fehler hervorrufen. Dafür ist "byref" schneller als "byval", da die Arbeit des Kopierens wegfällt.
So lange du nicht optimieren musst, nimm "byval"!
Und was den zweiten Puntk betrifft, kann ich dir keine Erklärung dazu bieten. Man kann die Textlänge beim Deklarieren nicht einstellen. Macht man es, dann bekommt man einen Fehler beim Kompilieren des Programms. Mehr weiß ich auch nicht darüber.
Hilfe findest du unter:
Es ist eine neue Unterprozedur dazu gekommen. "Set_output" erleichtert das Arbeiten mit den vielen Ausgängen. Besonders für die neu eingeführte FOR-NEXT-Schleife.
Die FOR-NEXT-Schleife (auch FOR-Schleife genannt) ist ein Befehlskonstrukt, mit dem man Programmcode mehrmals hintereinander ausführen kann, ohne diesen Programmcode mehrmals schreiben zu müssen. Der große Vorteil der FOR-NEXT-Schleife ist, dass man in einer Variable einen Zähler bei jedem Schleifendurchlauf erhöhen kann. Dieser Zähler (eine Zahl) kann in der Schleife verwendet werden.
So eine FOR-NEXT-Schleife sieht ungefähr so aus:
FOR <variablename> = <beginn> TO <ende>
<Programmcode, der mehrmals ausgeführt wird>
NEXT <variablename>
Beispiel:
DIM My_bytevar AS BYTE
FOR My_bytevar = 1 TO 10
PRINT "Wert der Variable 'My_bytevar': " ; My_bytevar
NEXT My_bytevar
Diese FOR-Schleife wird zehn mal ausgeführt. Beim Ersten Durchlauf hat die Variable "My_bytevar" den Wert 1. Beim zweiten Durchlauf hat sie den Wert 2. Usw.
Die Bascom-Hilfe zu diesem Befehl: http://avrhelp.mcselec.com/index.html?for_next.htm
'Zurück setzen
New_command = ""
Command_array(1) = ""
Command_array(2) = ""
Command_array(3) = ""
In der Hauptschleife hat sich kaum etwas geändert. Dort setze ich nur zusätzlich auch den Inhalt der Arrayelemente zurück. Damit versuche ich unvorhersehbare Nebeneffekte zu vermeiden.
Sub Set_output(byval Outputnr As Byte , Byval Value As Byte)
Select Case Outputnr
Case 1 : Output_1 = Value
Case 2 : Output_2 = Value
...
Case 19 : Output_19 = Value
Case 20 : Output_20 = Value
Case Else
Print "ERROR: unknown output pin"
End Select
End Sub
Da man für den Zugriff auf einen der Ausgänge den Namen des Ausganges (Output_1 bis Output_20) kennen muss hilft die neue Unterprozedur "Set_output" dabei, den Ausgang zu setzen wenn man nur die Ausgangsnummer aber nicht den Namen weiß. In einer FOR-Schleife hat man eine Zahl zur Verfügung die sich bei jedem Schleifendurchlauf hoch zählt. Um nicht in jeder FOR-Schleife mit SELECT CASE den gewünschten Ausgang bestimmen zu müssen, wurde dieser Code in diese neue Unterprozedur ausgelagert. An diese Unterprozedur wird die Nummer des Ausganges übergeben. Anhand dieser Nummer wird der gewünschte Ausgang mit SELECT CASE bestimmt und der neue Statuswert (0 oder 1) zugewiesen. Da man als Parameter einer Unterprozedur keine BIT-Variable verwenden kann, wurde dort eine BYTE-Variable verwendet. Das ist aber kein Problem, da beim Zuweisen eines Wertes an "Output_1" bis "Output_20" automatisch nur das erste Bit (von rechts gezählt) der Byte-Variable übergeben wird. Statt &B00000001
wird nur das erste Bit (rechts außen) &B1
zugewiesen. Das ist sehr praktisch und erstpart uns das Auslesen dieses Bits. Das ist natürlich Speicherverschwendung -- aber wer hat, der kann. :-)
Sub Do_get_command(cmd_output As String * 3)
Select Case Cmd_output
Case "ALL"
Print Output_1 ; ... Output_20
Case "1" : Print Output_1
...
Case "20" : Print Output_20
Case Else
Print "ERROR: unknown output pin"
End Select
End Sub
Die Unterprozedur "Do_get_command" wurde nur um die zusätzlichen Ausgänge erweitert.
Sub Do_set_command(cmd_output As String * 3 , Cmd_status As String * 3)
Local Outputnr As Byte
Local Value As Byte
'Neuen Status herausfinden
Select Case Cmd_status
Case "ON"
Value = 1
Case "OFF"
Value = 0
Case Else
Print "ERROR: unknown status"
Exit Sub
End Select
'Ausgang/Ausgänge einstellen
Select Case Cmd_output
Case "ALL"
For Outputnr = 1 To 20
Call Set_output(outputnr , Value)
Next Outputnr
Case Else
Outputnr = Val(cmd_output)
If Outputnr > 0 Then ' Wenn kein Fehler beim Umwandeln passiert ist
Call Set_output(outputnr , Value)
Else
Print "ERROR: unknown output pin"
Exit Sub
End If
End Select
Print "OK"
End Sub
Die Unterprozedur "Do_set_command" wurde ein wenig umgebaut. Die lokale Variable "Outputnr" wird für die FOR-Schleife zum Hochzählen benutzt. Die lokale BYTE-Variable "Value" ersetzt die im alten Programm verwendete BIT-Variable "New_status". Wird an den Parameter "Cmd_output" der Wert "ALL" übergeben, dann wird in einer FOR-Schleife jeder der zwanzig Ausgänge durchlaufen und ein- oder ausgeschaltet. Steht in der Variable "Cmd_output" nicht "ALL", dann wird erwartet, dass eine Zahl übergeben wurde. Also wird zuerst versucht, "cmd_output" mit dem Befehl VAL in eine Zahl umzuwandeln. Wurde keine Zahl erkannt, dann wird damit in die Variable "Outputnr" die Zahl 0 geschrieben. Wird eine Zahl erkannt, dann wird diese in "Outputnr" geschrieben. Der Rest der Unterprozedur sollte sich selbst erklären.
Sub Do_all_command(cmd_status As String * 20)
Local Status_string As String * 1
Local Outputnr As Byte
Select Case Cmd_status
Case "ON"
For Outputnr = 1 To 20
Call Set_output(outputnr , 1)
Print "OK"
Next Outputnr
Case "OFF"
For Outputnr = 1 To 20
Call Set_output(outputnr , 0)
Print "OK"
Next Outputnr
Case Else
If Len(cmd_status) = 20 Then
For Outputnr = 1 To 20
Status_string = Mid(cmd_status , Outputnr , 1)
Select Case Status_string
Case "1"
Call Set_output(outputnr , 1)
Print "OK"
Case "0"
Call Set_output(outputnr , 0)
Print "OK"
Case Else
Print "ERROR: unknown status"
End Select
Next Outputnr
Else
Print "ERROR: unknown status"
End If
End Select
End Sub
Die Unterprozedur "Do_all_command" hat sich mit der Einführung der FOR-Schleife am radikalsten verkürzt. Sie war im ursprünglichen Programm ja schon groß, aber bei 20 Ausgängen wäre sie ohne die FOR-Schleife riesig geworden. Wird "ON" oder "OFF" erkannt, dann wird in jeweils einer FOR-Schleife jeder Ausgang durchlaufen und ein- oder ausgeschaltet. Ansonsten wird geprüft ob der Statustext 20 Zeichen lang ist. Und wenn das der Fall ist, dann wird in einer FOR-Schleife jedes Zeichen des Statustextes (cmd_status) durchlaufen. Und wieder wird mit einem Aufruf der Unterprozedur "Set_output" jeder der Ausgänge ein- oder ausgeschaltet.
mfg Gerold :-)
Den zugehörigen Original-Beitrag findest du im Loetstelle-Forum.
Ich programmiere Progressive Web Applications, Mobile Apps, Desktop-Programme und noch vieles mehr. Falls es dich interessiert, findest du mehr Informationen darüber auf meiner Business-Website.