Forum: Mikrocontroller und Digitale Elektronik Steuerbefehle von UART mit µC auswerten und darauf reagiere


von Mister mit Kanister (Gast)


Lesenswert?

Schönen Tag,

ich hab bei meinem Problem über die Forumssuche nichts passendes 
gefunden, deswegen ein neuer Thread.

Es dreht sich um folgendes:

Ich möchte vom PC aus (vorerst mit nem Terminalprogramm) 
"selbsterdachte" Befehle/Kommandos an ein µC schicken (ATmega16@16MHz). 
Der µC soll darauf reagieren und eine entsprechende Aktion (in meinem 
Falle Messung) ausführen und das Ergebnis zurückliefern.

Meine Frage bezieht sich auf das kreieren eines funktionierenden 
Protokolles. Ich stelle mir das so vor:

Ich schicke an den µC ein String mit z.B. "FREQ?" der Controller führt 
die Messung aus und schickt das ERgebnis zurück z.B. "4711"kHz oder auch 
nur "4711"

Dann z.B. eine Spannungsmessung, schicke "ADVAL?" er mißt die Spannung 
am ADC, gibt zurück "0815mV" oder "0815" usw.

Es soll auch möglich sein, dem µC ein Befehl zum setzen bestimmter 
Registerwerte zu senden, z.B. Vorteiler setzen: "SET_PRESC_128" und der 
µC gibt zurück "OK" / "DONE" usw.

Jetzt meine Frage: Wie kann man das am besten realisieren?
Wie erkennt der µC dass ein Befehl zu "Ende" ist, also soll ich 
eindeutige ASCI Char senden wie das "?" oder beim Setzen von Parametern 
das "SET..." oder soll ich auf CR und oder LF achten.

Ich habe mir mal überlegt wie man das sinvollerweise machen kann, eine 
schlecht Funktionierende Version habe ich mit einem Empfangspuffer 
realisiert, der mir den ankommenden Befehl speichert. Das Problem ist, 
wenn ich ein unbekannten Befehl schicke, füllt sich der Sendepuffer und 
der nächste Befehl wird nicht ausgeführt. Ich poste mal hier meine 
(schlechte) Lösung:

von Mister mit Kanister (Gast)


Lesenswert?

inits...

volatile unsigned char buffer[8];// Pufferspeicher für Kommandoempfang
volatile unsigned char buffer_pos = 0;// aktuelle Schreibposition für 
den Buffer
volatile unsigned char buffer_received = 0;// Kommando empfangen Flag


ISR(USART_RXC_vect)
{
  if (!(buffer_received))
  {
    buffer[buffer_pos++] = uart_getc();
    if (buffer[buffer_pos - 1] == '?')
    {
      buffer_received = 1;
      buffer_pos = 0;
    }
  }
}





int main(void)
{
uart_init();
sei();    // Global Interrupts Enable


while(1)
{

if ((buffer_received) && (buffer[buffer_pos] == 'F') && 
(buffer[buffer_pos + 1] == 'R')) ... && (buffer[buffer_pos + 4] == '?')
{
  buffer_received = 0;
  //führe Frequenzmessung aus und liefere Ergebnis zurück
}

if ( // Abfragen nächster Befehl ...


}
return 0;
}

von Karl H. (kbuchegg)


Lesenswert?

> Wie kann man das am besten realisieren?
> Wie erkennt der µC dass ein Befehl zu "Ende" ist,

Am allereinfachsten:
  Pro Befehl eine Zeile

d.h. jeder Befehl ist mit einem '\n' abgeschlossen.
Das ist auch für den Benutzer am allereinfachsten, dann
der ist normalerweise daran gewöhnt (*), dass er seinen Befehl
eintippt und dann auf Return drückt.

(*) ok. Heutzutage nicht mehr. Heute wird nur noch
    rumgeklickt.

von Jörg B. (manos)


Lesenswert?

1. Anhang fehlt.

2. Ich würde ein Befehlsende über CR/LF auswerten. Wenn das Kommando 
dann unbekannt ist einfach eine Fehlermeldung zurückschicken.

Prinzipiell solltest Du über Kommando-Eingaben alles was verstellbar ist 
auch verstellen können (also alles ausser Fuses) - ist nur eine Frage 
der Auswertung und Aufarbeitung.

von Karl H. (kbuchegg)


Lesenswert?

> if ((buffer_received) && (buffer[buffer_pos] == 'F') &&
> (buffer[buffer_pos + 1] == 'R')) ... && (buffer[buffer_pos + 4] == '?')

Das ist doch Unsinn das so zu machen.
Dafür gibt es schöne Funktionen, die allesamt in der
str...() Familie zu finden sind.

http://www.mikrocontroller.net/articles/FAQ#Wie_funktioniert_String-Verarbeitung_in_C.3F

von maddax (Gast)


Lesenswert?


von Mister mit Kanister (Gast)


Lesenswert?

Ich überarbeite mal das "Protokoll", indem ich die Stringfunktionen 
einbinde.

Ist es sinvoll, dass alle Kommandos die man verwendet, alle die selbe 
Länge haben, also meinetwegen immer 8 Zeichen?

Also wenn ich die Kommandos mit einem '\n' abschließe hab ich dann auch 
davor das '\0' Zeichen drin? Wenn ich das Terminierungszeichen immer am 
Ende vom String hab, dann kann ich ja auch das auswerten oder?

von Karl H. (kbuchegg)


Lesenswert?

> Ich überarbeite mal das "Protokoll", indem ich die Stringfunktionen
> einbinde.

Such dir im Web auch die strn... Funktionen.
Besonders strncmp wird hier hilfreich sein.


> Ist es sinvoll, dass alle Kommandos die man verwendet, alle die selbe
> Länge haben, also meinetwegen immer 8 Zeichen?

Für dich als Programmierer schon. Für den Benutzer nicht.
Im Zweifel gewinnt der Benutzer.

> Also wenn ich die Kommandos mit einem '\n' abschließe hab ich
> dann auch davor das '\0' Zeichen drin?

Nein. Warum sollte es? Dein Terminal weis nichts von einem '\0'.
Aber das sollte ja kein Problem sein in der Empfangsschleife
beim Auftreten eines '\n':
* den '\n' zu ignorieren
* statt dessen den String mit einem '\0' anschliessen
* den String damit als vollständig empfangen zu deklarieren
  und zur weiteren Verarbeitung (mittels buffer_received)
  freizugeben

von Mister mit Kanister (Gast)


Lesenswert?

Also ich hab folgendes nun realisiert:

Zeichen '\r' in der ISR abgefragt (mit \n hat es nicht funktioniert) und 
statdessen das \0 angefügt.

Es funktioniert in der Main- SChleife die Abfrage

if ((buffer_received) && !(strncmp("FR?", buffer, 3)) )
{
  buffer_received = 0;
  //führe Frequenzmessung aus und liefere Ergebnis zurück
}
Jedoch gibt der Compiler die Warnung
../main.c:229: warning: passing arg 2 of `strncmp' discards qualifiers 
from pointer target type
Funktioniert aber trotzdem.

Das eigentliche Problem ist: Wenn ich eine falsche Eingabe mache, wird 
buffer_received true gesetzt, aber dadurch dass er nie in die If 
Schleife springt wird das buffer_received Flag nicht mehr zurückgesetzt. 
Also springt er in der ISR für den Empfang gar nicht mehr in den 
Abschnitt wo die Zeichen abgeholt werden.

Ich könnte allerdings die Befehlsabfrage in der ISR machen, und nur wenn 
ein gültiger Befehl kommt, den buffer_received dann auf 1 setzen. Dann 
wird aber die ISR ziemlich lang und sehr Prozessorlastig.

Ich bräuchte also eine Art Indikator, der sich merkt ob ein GÜLTIGER 
Befehl empfangen wurde.

Damit er auf Befehle reagiert und Einstellungen verändert muss ich dann 
noch realisieren.

von Rahul, der Trollige (Gast)


Lesenswert?

Das Ende könnte man auch ziemlich einfach herausfinden, indem man alle 
Zeichen <=32 herausfiltert und bei Erkennen eines solchen (und einer 
Mindestpufferbelegung) ein "Stringende-Flag" setzt.
Dann kann man die einzelnen Befehle auseinanderklabüstern.
Müssen die Befehle unbedingt so lang sein?
Ich habe sowas mal mit einzelnen Buchstaben realisiert...

Den Empfang sollte man am besten per Ringpuffer und Interrupt 
organisieren.

von Rahul, der Trollige (Gast)


Lesenswert?

(Da habe ich eine Menge Zeug einfach mal überlesen...)

>Ich bräuchte also eine Art Indikator, der sich merkt ob ein GÜLTIGER
>Befehl empfangen wurde.

Deine Befehle besetehen ja aus lesbaren Zeichen. Die befinden sich alle 
im ASCII oberhalb der Position 31 (32 ist das Leerzeichen).
Wenn ein Zeichen <=31 auftritt, sollte entweder ein Fehler aufgetreten 
sein oder ein Befehl komplett im Puffer liegen.

>if ((buffer_received) && (buffer[buffer_pos] == 'F') &&
>(buffer[buffer_pos + 1] == 'R')) ... && (buffer[buffer_pos + 4] == '?')
>{
>  buffer_received = 0;
>  //führe Frequenzmessung aus und liefere Ergebnis zurück
>}
>
>if ( // Abfragen nächster Befehl ...

Sowas macht man besser so:

while (!(buffer_received)); // wartet auf "String"

Dann sollte man sich ein Array mit den Befehlen einrichten.
Dieses kann man dann mit zwei Schleifen mit dem empfangenen String 
vergleichen (eine Schleife für das "Wort" und eins für den 
"Buchstaben").
Übrigens bietet es sich auch an, noch eine upcase-Funktion zu haben 
(selberbauen oder aus irgendeiner Library besorgen) - die ermöglicht 
dann auch den Empfang und die Auswertung "komischer" Strings ("fR?").




von Karl H. (kbuchegg)


Lesenswert?

> Ich bräuchte also eine Art Indikator, der sich merkt ob ein GÜLTIGER
> Befehl empfangen wurde.

Wozu.

Alles was du brauchst ist:


int main()
{
   ....


   while( 1 ) {

     // Wenn irgendwas über die serielle gekommen ist

     if( buffer_received ) {

       // es ist was da.

       // welcher Befehl war es denn?

       if( strncmp( buffer, "ADC", 3 ) == 0 ) {
         // Aha, es war der ADC Befehl
         // Befehl abarbeiten

       }

       else if( strncmp( buffer, "PORT", 4 ) == 0 ) {
         // Aha ein PORT Befehl
         // Befehl abarbeiten
       }

       ....

       else {
         // kein gültiger Befehl. Fehlermeldung ausgeben etc.
       }

       // Wenn in der Zeile ein Befehl war ist er abgearbeitet
       // Aber auch wenn da keiner war:
       // An dieser Stelle ist alles soweit fertig, dass der
       // nächste Befehl wieder anrollen kann

       buffer_received = 0;
     }
   }
}


Macht euch doch nicht selbst das Leben mit immer noch
komplizierteren Konstrukten schwer.
buffer_received auf 1 heist doch nicht, dass da ein
'Befehl# angekommen ist. buffer_received auf 1 heist doch
lediglich dass eine komplette Eingabezeile vorhanden ist,
die jetzt bearbeitet werden kann. Die wird dann bearbeitet,
entweder indem da ein Befehl herausgeholt wird oder ein
Fehler erzeugt wird oder was auch immer. Aber eines steht
auf jeden Fall fest: Nachdem die Zeile bearbeitet wurde
ist der Eingangspuffer bereit eine neue Eingabe aufzunehmen.
Also wird buffer_received wieder auf 0 gesetzt und gut ists.

von Mister mit Kanister (Gast)


Lesenswert?

Danke, die Hilfe ist echt super!

um den Nachteil des aktiven Wartens (Pollings) uz vermeiden, wärs nicht 
besser in der ISR ein Aufruf zur Befehlauswertung zu geben, also z.B.:

ISR(USART_RXC_vect)
{
  if (!(buffer_received))
  {
    buffer[buffer_pos++] = uart_getc();
    if (buffer[buffer_pos - 1] <= 31)
    {
      buffer[buffer_pos - 1] = '\0';
      buffer_received = 1;
      buffer_pos = 0;
                        aufrufbefehlauswertungsfunktion();
    }
  }
}

Oder würde das die ISR nur unnötig lang machen?

@Karl Heinz: Ich hab mal die Abfrage von Dir realisiert. Das 
funktioniert super. Manchmal sieht man selbst die einfachsten 
Möglichkeiten nicht. Bin wohl zu tief in das Programm vertieft 
gewesen...

@Rahul: Du meinst also ähnlich wie mit dem strncomp(blablabla) über 2 
Arrays abzufragen, welcher Befehl nun gesendet wurde?

von Rahul, der Trollige (Gast)


Lesenswert?

Ja

von Mister mit Kanister (Gast)


Lesenswert?

Um nochmal auf den angesprochenen Ringpuffer zurückzukommen, was wäre 
der Vorteil wenn man den Ringpuffer verwendet gegenüber der Methode in 
der ISR wie ich es realisiert habe?

Immerhin habe ich durch Verwendung der Lese- und Schreibmarke einen 
erhöhten Aufwand.

von Rahul, der Trollige (Gast)


Lesenswert?

>was wäre der Vorteil wenn man den Ringpuffer verwendet gegenüber der >Methode in 
der ISR wie ich es realisiert habe?

Man könnte während der Ausführung eines Befehls schon den nächsten 
schicken.
Ich habe mir das mit dem Ringpuffer beim Empfang einfach angewöhnt.

von Christian B. (casandro)


Lesenswert?

Mister mit Kanister wrote:
> inits...
>
> volatile unsigned char buffer[8];// Pufferspeicher für Kommandoempfang

> ISR(USART_RXC_vect)
> {
>   if (!(buffer_received))
>   {
>     buffer[buffer_pos++] = uart_getc();
>     if (buffer[buffer_pos - 1] == '?')
>     {
>       buffer_received = 1;
>       buffer_pos = 0;
>     }
>   }
> }

Ohh das ist schlecht, wenn die Eingabe länger als 8 Zeichen ist 
überschreibst Du den Stack und somit deine Rücksprungadresse. Außerdem 
brauchst Du dafür ganze 8 Byte an kostbaren RAM.

Ich würde das über einen kleinen Zustandsautomaten machen. Der ist 
schnell implementiert, braucht wenig RAM und ist auch schnell im Ablauf.

von Rainer I. (raih)


Lesenswert?

Schau Dir mal die avrlib von Pascal Stang an.
http://hubbard.engr.scu.edu/avr/avrlib/
Da gibt's ein CmdLine-Interface.
Da kannst Du auch noch Parameter an deine Befehle dranhängen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.