Hey Leute,
da ich jetzt mit USART Daten vom PC zum MC (Atmel ATXmega xplained A1
Board) schicken kann und umgekehrt, möchte ich nun ein PWM Signal mit
bestimmten Befehlen steuern, die ich vom PC aus schicke.
Mit dem bisherigen Code kann ich zwar die Pulsweite verändern,
allerdings glaube ich nicht, dass meine Variante die beste ist.
Wie geht man an sowas ran? Wie könnte ich so eine Kommando-Abfrage mit
dem MC realisieren?
Zur Funktionsweise:
Mein Code guckt die ganze Zeit nach einem Startbit (0x01), bei dem es
die Zählvariable cmd inkrementiert. Danach schickt der MC in der
while-Schleife andauernd den string "Geben Sie einen Wert ein" an den
PC, weil cmd 1 ist.
Wenn man nun den ersten Wert (für den 16-bit Timer die oberen 8 bit)
schickt, wird cmd wieder inkrementiert, dass gleiche für den zweiten
Wert auch.
Bei cmd = 3 kommt man in der main in die if-Schleife, die den Compare
Wert des Timers auf den vom PC gesendeten Wert setzen soll.
Komischer weise funktioniert das ganze nur, wenn ich die if-Abfragen für
cmd=1 und cmd=2 mit einbaue, wenn ich die auskommentiere funktioniert
der Code nicht mehr :S
Soll man nicht normalerweise die ISR nur für Zuweisungen und ganz kurzen
Code benutzen?
Mein Code:
1
#include<stdint.h>
2
#include<avr/io.h>
3
#include<avr/interrupt.h>
4
5
#include"debouncing.h"
6
7
/*********************** Global Variables ***********************/
8
9
volatileuint8_trx_buffer;// USART receive buffer
10
uint8_tcmd;
11
uint8_tpwm_buffer_H;
12
13
/*********************** Function Prototype ***********************/
Das würde ich ganz anders angehen.
Die Interrupt Routine schreibt die empfangenen Zeichen in einen Puffer,
bis sie einen Zeilenumbruch empfängt. Dann setzt sie ein fertig-Flag auf
1 und ignoriert alle weiteren Zeichen, solange das Flag noch auf 1 ist.
Das Hauptprogramm Gibt die Eingabeaufforderung aus. Dann wartet es, bis
das fertig-Flag=1 ist. Danach kann es den empfangenen Befehl auseinander
nehmen und abarbeiten. Danach setzt das Hauptprogramm das fertig-flag
wieder auf 0 und sende die nächste Eingabeaufforderung.
Ich würde die Eingabeaufforderung allerdings weg lassen, wie es auch
Modems tun. Stattdessen würde ich nach jedem empfangenen befehl Feedback
geben, ob er erfolgreich oder nicht erkannt wurde.
Der Empfangspuffer muss natürlich groß genug sein, um den längsten
möglichen Befehl aufnehmen zu können.
Wenn Du statt eines einfachen Puffers zwei Puffer nimmst, die
abwechselnd für Empfang befüllt werden, kannst du den nächsten Befehl
schon empfangen während der erste abgearbeitet wird. Das würde ich aber
erst angehen, wenn der einfache Puffer schon funktioniert.
Hallo Stefan,
also lege ich sozusagen eine Art Paket fest das immer eingehalten wird?
z.B:
1. Byte: Befehl
2. Byte: MSB Wert
3. Byte: LSB Wert
4. Byte: Stopbit (du hattest \n vorgeschlagen oder?)
Ist es sinvoll das Receive Interrupt nach dem Empfangen des Stopbits
solange zu deaktivieren, bis man den Befehl abgearbeitet hat?
Willst Du nur Deine PWM steuern oder soll das nachher erweiterbar sein ?
Ein sinnvolles Protokoll wäre:
Startbyte|Datumbyte|...|Datumbyte|CRC|Endbyte
Packe das auf dem µC in einen Struct und werte das in einer eignen
Routine aus.
In der Routine dann ein ACK an den PC zurückgeben das alles richtig
angekommen ist.
Kommt das ACK nicht immer wieder die Sequenz senden.
Überlege Dir was Du realisieren willst und schreibe Dir erstmal im
Klartext auf was alles notwendig ist um Deine Idee umzusetzen.
Es ist überhaupt nicht sinnvoll, derart prozedural an sowas
heranzugehen. Denk dir lieber eine Art Kommandoprozessor aus, dem du
Kommandos geben kannst und der sie auswertet und das Gewünschte dann
tut. Wenn dir das zu schwer ist, nimm den Kommandoprozessor aus der
Lernbetty hier im Forum (..wieder tibetanische Gebetsmühle). Der ist in
plain C geschrieben und läuft auf allem, was man mit C programmieren
kann. Er ist auch klein, geradezu minimalistisch und hat doch mehr zu
bieten als das, was du oben angedacht hast.
W.S.
cppler schrieb:> Willst Du nur Deine PWM steuern oder soll das nachher erweiterbar sein ?
Ja, soll auf jedenfall erweiterbar sein.
cppler schrieb:> Startbyte|Datumbyte|...|Datumbyte|CRC|Endbyte
Was meinst du mit Datumbyte? Die Adresse?
W.S. schrieb:> Es ist überhaupt nicht sinnvoll, derart prozedural an sowas> heranzugehen. Denk dir lieber eine Art Kommandoprozessor aus, dem du> Kommandos geben kannst und der sie auswertet und das Gewünschte dann> tut.
Aber arbeitet der Kommandoprozessor nicht mit einem Protokoll? Irgendwie
muss man ja schon die Abfolge von bestimmten Daten festlegen oder?
Willy M. schrieb:> cppler schrieb:>> Willst Du nur Deine PWM steuern oder soll das nachher erweiterbar sein ?>> Ja, soll auf jedenfall erweiterbar sein.>
Dann schreibe Dir einen Ablaufplan dazu auf, z.B.:
1. auszuführender Befehl (PWM Tast, PWM Freq, LED usw. usf.)
2. Wert = Datum für den Befehl (PWM-OCRX, PWM-Hz, LED-hell usw. usf.)
3. Bestätigung alles korrekt empfangen zu haben
Das ganze packst Du dann in ein Protokoll mit Start/Stop und Prüfsumme.
> cppler schrieb:>> Startbyte|Datumbyte|...|Datumbyte|CRC|Endbyte>> Was meinst du mit Datumbyte? Die Adresse?>http://de.wikipedia.org/wiki/Daten> W.S. schrieb:>> Es ist überhaupt nicht sinnvoll, derart prozedural an sowas>> heranzugehen. Denk dir lieber eine Art Kommandoprozessor aus, dem du>> Kommandos geben kannst und der sie auswertet und das Gewünschte dann>> tut.>> Aber arbeitet der Kommandoprozessor nicht mit einem Protokoll? Irgendwie> muss man ja schon die Abfolge von bestimmten Daten festlegen oder?
Ja sicher, s.o. Du kannst auch eine Statemachine implementieren oder
einen einfachen Parser oder oder oder, das ist dann aber die Auswertung
der Befehle und Daten die Du verarbeitet haben willst und hat nichts mit
dem Übertragungsprotokoll zu tun.
Wie gesagt schreibe Dir erstmal auf was Du realisieren willst und
überlege Dir welche Befehle und Daten dazu notwendig sind.
Dann überlegst Du Dir wie Du die Befehel und Daten übermittelst und dann
implementierst Du erstmal die Übermittlung und shaust nach ob auch alles
wie gewünscht angekommen ist, z.B. indem Du in der Auswertungfunktion
erstmal alles wieder zurückschickst.
Dann gehst Du hin und implementierst die Auswertungsfunktion, ob das nun
ein Parser wird oder eine Statemachine hängt davon ab was Du willst und
kannst.
Am einfachsten ist ein simpler Parser der via defines die Befehle
zuweist und dann die entsprechenden Routinen mit den übergebenen Daten
aufruft.
Okay, also ich habe mir jetzt aufgeschrieben, was ich allgemein gerne
für Möglichkeiten mit dem Übertragungsprotokoll haben möchte.
--------------------------------------------
Funktionen:
* Übertragungsprotokoll dient nur zum Übertragen von Befehlen und
dazugehörigen Werten
- sichere Übertragung mit CRC-Prüfsumme (1 byte)
- senden einer Empfangsbestätigung (1 byte)
- einstellbare Anzahl an Befehlbytes (2 byte = 2^16 Befehle)
- einstellbare Anzahl an Datenbytes (1-4 byte, ist ja variabel)
Aufbau:
Startbyte | Befehlbyte(s) | Datum-/Datenbyte(s) | CRC | Endbyte
(so wie von cppler vorgeschlagen)
Startbyte: 0xAA = 0b10101010
Endbyte: 0x55 = 0b01010101
--------------------------------------------
Habt ihr vielleicht noch Hinweise, was man verbessern könnte? Muss ich
mir eine Option für eine 2byte Prüfsumme offen halten?
Wonach wähle ich den Grad meines Generatorpolynoms für meine CRC
Checksum?
Darf man die frei wählen?
Willy M. schrieb:> Aufbau:>> Startbyte | Befehlbyte(s) | Datum-/Datenbyte(s) | CRC | Endbyte> (so wie von cppler vorgeschlagen)>> Startbyte: 0xAA = 0b10101010> Endbyte: 0x55 = 0b01010101>> -------------------------------------------->> Habt ihr vielleicht noch Hinweise, was man verbessern könnte? Muss ich> mir eine Option für eine 2byte Prüfsumme offen halten?
Du hast jetzt grundsätzlich drei Möglichkeiten wegen der variablen
Anzahl von Daten/Befehlen:
1. Du machst für jeden Befehl und Datum ein anderes Startbyte bzw. einen
Code danach um unterscheiden zu können was übertragen wird.
Also jedesmal ein einzelnes Paket übertragen.
2. Du siehst ein zusätzliches Feld vor indem die Anzahl an
Befehlen/Daten mit übermittelt werden.
3. Du machst Dein Protokoll statisch und füllst die nicht verwendeten
Felder mit Sonderzeichen um sie als leer zu markieren.
Die Prüfsumme muß über das gesamte Paket gehen, lies Dir z.B das mal
durch:
http://www-stud.rbi.informatik.uni-frankfurt.de/~haase/crc.html
Ich glaube ich würde mich für Variante 2 entscheiden.
Ich würde das Übertragungsprotokoll dann so implementieren, dass die
verschiedenen Werte die aufeinander folgen gleich in entpsrechende
Buffer einsortiert werden, d.h. ein Befehlbuffer ein Wertebuffer ein
CRC-Buffer. Die CRC-Überprüfung würde dann ein Algorythmus machen (für
den ich vermutlich am längsten brauche) und bei einem Fehler ein Flag
zurückschicken, dass den PC den gleichen Befehl so lange schicken lässt,
bis korrekt Übertragen wurde (das hattest du [ccpler] auch schon
vorgeschlagen).
Wenn ich die Anzahl der Bytes erhöhe, muss ich dann auch das CRC Polynom
erhöhen?
Den Link hab ich mir schon durchgelesen, aber wie ich das in C schreibe
ist mir noch ein Rätsel...
Ich habe ein Projekt, bei dem zwei XY-montierte Servos einen
Laserpointer auf ein Regal richten, um anzuzeigen, wo etwas entnommen
werden soll ("pick by light"). Diese Einheit wird von einem Arduino
gesteuert, der per Ethernet seine Kommandos empfängt. In das IP-Socket
kann man genau so seriell schreiben und am anderen Ende Lesen, wie bei
der "normalen" seriellen bzw. USB-Verbindung. Ich schreibe immer
Kommandos, die aus 6 Bytes bestehen:
- Startkennung (>127)
- Befehlskennung (>127, zB. X-Pos, Y-Pos, Laser an/aus/blink)
- High-Byte (<127)
- Low-Byte (<127)
- Prüfbyte (xor aus hig und low)
- Endekennung (>127)
Jedes eintreffende Byte wird in eine Quewe mit 6 Positionen geschoben.
Also das älteste Byte gelöscht, die fünf weiteren um eine Position
verschoben und das neue Byte auf die letzte Position geschrieben. Nach
jedem empfangenen Byte werden die Bedingungen geprüft. Wird ein gültiges
Kommando erkannt, werden die Paramter übernommen, die gesamte Quewe
gelöscht, eine Bestätigung zum PC zurückgesendet und das Kommando
ausgeführt. Empfängt der PC nach 6 gesendeten Bytes und einer kurzen
Wartezeit keine Quittung, wird die letzte Sendung wiederholt ...
Frank schrieb:> Jedes eintreffende Byte wird in eine Quewe mit 6 Positionen geschoben.> Also das älteste Byte gelöscht, die fünf weiteren um eine Position> verschoben und das neue Byte auf die letzte Position geschrieben.
Wie sieht denn soetwas aus? Nehme ich ein Array dafür? Dann wäre mein MC
beim Empfangen von Daten doch nur damit beschäftigt die Daten von einem
ins andere Feld zu verschieben oder?
Und was passiert wenn das Array am Anfang noch leer ist, woher weiß den
mein MC wo er einen Wert lesen muss, oder wird solange gewartet, bis so
viele Daten gesendet wurden, dass z.B. mein Startbit bei arry[0]
angelangt ist, weil immer an dieser Stelle gelesen wird?
Willy M. schrieb:> Nehme ich ein Array dafür?
Du solltest Dich erstmal mit USART beschäftigen und erkennen was man für
eine kontinuierliche Datenübertragung braucht.
Üblicherweise macht man einen Ringbuffer und da Du ja Handshake hast
sollte das kein Problem sein.
Implementiere doch erstmal was ich schon erklärt habe, ohne CRC und nur
um das Handshake zu testen.
Dann nimmst Du Dir das CRC vor und dann die Steuerung des µCs ...
Hier ein Beispiel eines solchen Codes
static volatile unsigned char cmd[9]; // cmd+args
ISR( USARTC0_RXC_vect ) //
{ static unsigned char idx,crc=-1; char tmp; tmp=cmd[1]&7;
crc+=cmd[idx]=USARTC0.DATA; // anstelle von += kann auch ^= sein
if(idx)if(--idx);else if(crc)cmd[1]=0x10|tmp;
else*cmd^=(cmd[1]=0x8|tmp|*cmd&~31)&~31;
else idx=(*cmd>>5)+1,crc=~*cmd;
}
cmd[1].b0-b2 = saved user state machine or user flags
cmd[1].b3 = new_cmd
cmd[1].b4 = new_cmd_crc_error
cmd[1].b5-b7=argument count
cmd[0] = 5bit cmd
cmd[2-8] = args // kann auf word/.. erweitert werden.
Ich habe da doch nochmal eine-zwei Fragen.
Wieso benötige ich ein Endbit? Meine Framestrucktur sieht so aus:
Startbyte | FrameConfig Byte | Cmd Byte | (Cmd Byte) | Data Byte | ... |
CRC Byte | (Endbyte)
Wenn ich mit dem FrameConfig Byte sowieso dynamisch festlegen kann, wie
die folgenden Daten interpretiert werden sollen (z.B. 2 Cmd Bytes und
nur ein Data Byte), dann weiß die MCU doch sowieso, nach wieviel Byte
die Übertragung fertig ist oder?
Was mache ich wenn Daten ankommen, wo kein Startbit erkannt wird? Sollen
die dann einfach gelöscht oder erst garnicht in den FIFO-Buffer
geschireben werden?
Willy M. schrieb:> Ich habe da doch nochmal eine-zwei Fragen.>> Wieso benötige ich ein Endbit? Meine Framestrucktur sieht so aus:>> Startbyte | FrameConfig Byte | Cmd Byte | (Cmd Byte) | Data Byte | ... |> CRC Byte | (Endbyte)>> Wenn ich mit dem FrameConfig Byte sowieso dynamisch festlegen kann, wie> die folgenden Daten interpretiert werden sollen (z.B. 2 Cmd Bytes und> nur ein Data Byte), dann weiß die MCU doch sowieso, nach wieviel Byte> die Übertragung fertig ist oder?>> Was mache ich wenn Daten ankommen, wo kein Startbit erkannt wird? Sollen> die dann einfach gelöscht oder erst garnicht in den FIFO-Buffer> geschireben werden?
Du hast Dir also immer noch keinen Ablaufplan auf ein Blatt Papier
geschrieben.
Ob Du nun in Deiner Routine berechnest wie viele Datenpakete noch zu
empfangen sind oder einfacher auf's Endbyte wartest bleibt Dir
überlassen.
Wenn kein Startbyte kommt wird nix gemacht bis eines klar erkannt wurde.
Dann packst Du die Bytes in den Ringpuffer.
Wenn alles richtig im Puffer steht sendest Du das ACK and den PC und
arbeitest in der Hauptschleife den Puffer ab.
Und nun nimm Dir ein Blatt Papier und erstelle einen Ablaufplan NUR für
die Kommunikation ...
Ich versuche es ja, aber erstmal bin ich nicht gut darin, Ablaufpläne zu
erstellen, zweitens zerbreche ich mir immer den Kopf über die Bedingung.
Folgendes habe ich: (ohne Überprüfung, ob die daten richtig gesendet
wurden)
TF: Transmission Flag
1
Start
2
|
3
| PC sendet Daten
4
| false
5
Wenn (Startbyte == USARTC0.DATA) und (TF == 0) -------------> warte auf Startbyte
6
|
7
| true
8
|
9
setze TF
10
|
11
| false
12
Wenn TF gesetzt ----------------> warte auf Startbyte
13
|
14
| true
15
| false
16
FIFO Buffer schreiben <---------------
17
| |
18
| |
19
Wenn (USARTC0.DATA == Endbyte) -------
20
|
21
| true
22
|
23
lösche TF
24
|
25
setzte Transmission Complete Flag
26
|
27
sende ACK an PC
28
|
29
lösche Transmission Complete Flag
Sowas in der Art hatte ich mir vorgestellt, aber was passiert, wenn
einer meiner Datenbytes den gleichen wert hat wie das Endbyte?
Mit dieser Variante hätte ich in der ISR 3 if-Abfragen, geht das nicht
auch anders oder komme ich nicht darum diese 3 Abfragen in der ISR zu
machen?
Ist das überhaupt kritisch? Ich habe immer kein Gefühl dafür, ab wann in
der ISR "zu viel" geschieht und wann nicht...
Willy M. schrieb:> Sowas in der Art hatte ich mir vorgestellt, aber was passiert, wenn> einer meiner Datenbytes den gleichen wert hat wie das Endbyte?>
Was passiert wenn zufällig ein Startbyte entsteht ?
Du bist für das Protokoll verantwortlich.
> Mit dieser Variante hätte ich in der ISR 3 if-Abfragen, geht das nicht> auch anders oder komme ich nicht darum diese 3 Abfragen in der ISR zu> machen?>> Ist das überhaupt kritisch? Ich habe immer kein Gefühl dafür, ab wann in> der ISR "zu viel" geschieht und wann nicht...
In einer ISR sind mehrere ifs o.ä. kaum problematisch, nur keine anderen
Prozeduren aufrufen oder mit delays arbeiten ...
Wie schnell willst Du denn übertragen ?
Lies Dir mal das hier durch:
http://de.wikipedia.org/wiki/Kommunikationsprotokoll
Und welchen Grund hatte ich wohl Dir als erste Lösung was vorzuschlagen
?
Wenn Du es einfacher haben willst dann mache einen Hardwarehandshake das
ist bei RS232 vorgesehen:
http://de.wikipedia.org/wiki/RS232
Du kannst auch einfach eine Leitung der Schnittstelle dafür
"mißbrauchen", also sobald Dein µC einen High-Pegel am SelectPin hat
sendet der PC und der µC muß empfangen ist der Pegel Low ist die Sendung
zuende und es muß nichts gemacht werden.
Kommunikationsprotokolle sind nicht trivial, Du könntest z.B. eine ASCII
Tabelle anlegen und START/STOP mit Escapesequenzen realisieren und Deine
Befehle als Buchstaben und die Werte als Zahlen übertragen, d.h. für 255
als Zahlenwert brauchst Du dann drei Bytes usw. usf.
In Deinem Fall willst Du ja mitschicken wieviele Pakete kommen, also
kannst Du wie schon gesagt auf's Endbyte verzichten und nachzählen oder
Du einigst Dich auf eine Sequenz die ansonsten nicht vorkommen darf.
Du könntest z.B. auch alle relevanten Datenbytes via XOR mit dem Endbyte
verknüpfen oder oder oder ...
Schreibe Dir einfach auf ein Blatt Papier welche Werte zu übertragen
sind und welche Befehle und ob Du die unterschiedlich übertragen kannst.