Forum: Mikrocontroller und Digitale Elektronik Probleme bei Ansteuerung über UART


von chris (Gast)


Lesenswert?

Hallo,

ich sitze hier vor einem kleinen Problem, das ich nicht in den Griff 
bekomme.
Ich will mit einem Atmega644P-20PU einen String auswerten, den ich vom 
Rechner über den UART zum µC geschickt habe. Hier bei stellen die ersten 
6 Zeichen die Adresse des Boards dar.

Der String sieht z.B. wie folgt aus:

ABCDEF,GHIJKL,010\r

ABCDEF: Adresse des Boards/Empfängers (es sollen einmal mehrere Boards 
verwendet werden
GHIJKL: Adresse des Senders
010:    3Bit zur Übertragung von Informationen
\r:     Abschlusszeichen

Nachdem ich den String eingelesen habe (Code aus UART-Tutorial, 
Interruptbetrieb), extrahiere ich die ersten 6 Zeichen und vergleiche 
Sie mit der Boardadresse. Stimmt diese über ein, werte ich die 
3-Informationsbits aus. Allerdings kommt es bei gefühlten 50% der 
Übertragungen dazu, dass die Informationsbits nicht ausgelesen werden, 
obwohl er das eigentlich machen sollte.

Hier der Code aus meiner main.c:
1
#include "header.h"
2
3
#define UART_MAXSTRLEN 30
4
 
5
volatile uint8_t uart_str_complete = 0;     // 1 .. String komplett empfangen
6
volatile uint8_t uart_str_count = 0;
7
volatile char uart_string[UART_MAXSTRLEN + 1] = "";
8
9
ISR(USART0_RX_vect)
10
{
11
  unsigned char nextChar;
12
 
13
  // Daten aus dem Puffer lesen
14
  nextChar = UDR0;
15
  if( uart_str_complete == 0 ) {  // wenn uart_string gerade in Verwendung, neues Zeichen verwerfen
16
 
17
    // Daten werden erst in string geschrieben, wenn nicht String-Ende/max Zeichenlänge erreicht ist/string gerade verarbeitet wird
18
    if(nextChar != '\r'){
19
      uart_string[uart_str_count] = nextChar;
20
      uart_str_count++;
21
    }
22
    else {
23
      uart_string[uart_str_count] = '\0';
24
      uart_str_count = 0;
25
      uart_str_complete = 1;
26
    }
27
  }
28
}
29
30
31
int main(void)
32
{
33
  init();
34
  sei();
35
36
  led1_aus;
37
  led2_aus;
38
  led3_aus;
39
  while(1)
40
  {
41
    input_lesen();
42
43
44
    if(uart_str_complete==1)
45
    {
46
      for(int i=0;i<6;i++)
47
        rx_adresse[i] = uart_string[i];
48
      rx_adresse[6]='\0';
49
50
      if(strcmp(rx_adresse,"ABCDEF")==0)
51
      {
52
        if(uart_string[14]=='1')
53
          led1_ein;
54
        else
55
          led1_aus;
56
57
        if(uart_string[15]=='1')
58
          led2_ein;
59
        else
60
          led2_aus;
61
62
        if(uart_string[16]=='1')
63
          led3_ein;
64
        else
65
          led3_aus;
66
      }
67
      uart_str_complete=0;
68
    }
69
  }
70
  return 0;
71
};

Vielleicht hat jemand eine Idee!?

Danke
chris

von Juergen G. (jup)


Lesenswert?

Hi chris (Gast),

Das Problem wird sein das Deine Informationsbits vom UART als ASCII 
empfangen werden, und somit Steuerzeichen darstellen koennen die Deinen 
String oder Deine Stringverarbeitungsroutine durcheinender bringen 
koennen.
Desweiteren koennen auch dieses Informationsbits ein \r oder ein : sein.

Somit verschluckt sich Deine Stringverarbeitungsroutine, weil sie diese 
Zeichen als Delimiter interpretiert.

Ju

von chris (Gast)


Lesenswert?

Dass sich die Routine verschlucken könnte ist mir klar, aber warum soll 
sie das tun, wenn ich die Daten korrekt übertrage? Dann sollte die 
Routine doch in der Lage sein diese auch korrekt auszuwerten.

Welche Alternativen gibt es noch die Daten zu übertragen?
Mit der ASCII-Version könnte man einfach mit einem einfachen 
Terminalprogramm "zuhören".

von Serieller (Gast)


Lesenswert?

1) Dein Hauptprogramm ist nicht optimal.

Du kopierst u.U. zu lange an dem String rum. Das Kopieren muss entweder 
fixer gehen, damit die ISR(USART0_RX_vect) den Eingangspuffer möglichst 
schnell wieder verwenden kann. Nicht Häppchenweise kopieren, 
Stringvergleiche machen und dann wieder ein Häppchen... und 
währenddessen schmeisst die ISR fröhlich Zeichen weg!

Als Alternative zum Kopieren (rx_adresse <= uart_string) kannst du mit 
zwei Puffern arbeiten und jeweils nur umschalten. Das erspart die 
Umkopieraktion.

2) Du könntest die Länge des Eingabepuffers haben und nur dann auf die 
Bitwerte prüfen, wenn die Länge das auch zu lässt. Im Moment könnte es 
passieren, dass du auf veraltete Bitwerte aus vorangehenden Telegrammen 
zugreifst. Die werden ja im Eingangspuffer nie ausgenullt.

3) Du bewegst dich in der ISR am Abgrund eines Bufferoverflows falls dir 
'\r' verloren gehen...

von chris (Gast)


Lesenswert?

Ich hab gerade gemerkt, dass die beiden 22pF-Kondensatoren am Quarz 
fehlen. Der der das Board aufgebaut hat hat die wohl vergessen.
Nun wollte ich mal schauen, ob die Taktfrequenz in der richtigen 
Größenordnung liegt. Mit dem folgenden Code erzeuge ich ein 
Rechtecksignal, das ich ausmessen kann.

1
int main(void)
2
{
3
  init();
4
  while(1)
5
  {
6
    PORTA ^=_BV(PA0);
7
  }
8
  return 0;
9
};

Mein Quarz hat 18.432MHz. Leider kann ich nicht abschätzen, wieviel 
Zyklen benötigt werden, bis dieser Code ausgeführt ist.
Gemessen habe ich aktuell eine Frequenz von rund 1,84MHz. Passt das oder 
ist das zu wenig?

Danke

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Juergen G. schrieb:
> Das Problem wird sein das Deine Informationsbits vom UART als ASCII
> empfangen werden, und somit Steuerzeichen darstellen koennen die Deinen
> String oder Deine Stringverarbeitungsroutine durcheinender bringen
> koennen.
Genau das ja nun gerade nicht!
Denn in ASCII ist sehr gut definiert, dass Ziffern und Zeichen sich 
nicht mit Steuercodes (und schon gar nicht mit der 0) überlappen...

chris schrieb:
> Gemessen habe ich aktuell eine Frequenz von rund 1,84MHz.
Warum misst du nicht die Quarzfrequenz direkt?

Ich würds so machen, und schauen, ob 1 sec rauskommt:
1
 include "delay.h"
2
3
 int main(void)
4
 {
5
   init();
6
   while(1)
7
   {
8
     PORTA ^=_BV(PA0);
9
     _sleep_ms(500);
10
   }
11
   return 0;
12
 };

von holger (Gast)


Lesenswert?

>Ich würds so machen, und schauen, ob 1 sec rauskommt:

Nur bei eingeschalteter Optimierung;)

von spess53 (Gast)


Lesenswert?

Hi

>Ich würds so machen, und schauen, ob 1 sec rauskommt:

Oder CKOUT-Fuse setzen und an CLKO (Pin2) messen.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

Und wenn dann feststeht, dass die Taktfrequenz korrket ist, würde ich 
eine der nächst wichtigen Regeln bei der Entwicklung von Programmen mit 
Dtaenübertragung beherzigen. Die da lautet:

Sieh zu, dass du dir irgendwo den empfangenen String ausgeben lassen 
kannst.

Im einfachsten Fall schick den String einfach an den Sender zurück (aber 
pass auf, dass der Sender dir den Datenkanal nicht zumüllt). Wenn man 
während der Entwicklungszeit eines Programms die Ansteuerung erst mal 
nicht von einem anderen Programm aus macht sondern von einem Terminal 
aus,dann ist das auch überhaupt kein Problem
1
  while(1)
2
  {
3
    input_lesen();
4
5
6
    if(uart_str_complete==1)
7
    {
8
      send_string( uart_string );
9
10
      for(int i=0;i<6;i++)

und schon schreibt einem der µC nach dem Drücken von Return direkt auf 
den Bildschirm, was er verstanden hat. Und dann hat dann auch das 
Rätselraten ein Ende, ob man ein Problem in der Übertragung oder in der 
Auswertung hat.

von cskulkw (Gast)


Lesenswert?

Hi chris,

1.
in der Interruptroutine prüfst Du nicht, ob der Datenpuffer nicht mit 
mehr als UART_MAXSTRLEN - Bytes beschrieben wird.

Wenn - warum auch immer - mehr Datenbytes empfangen werden und kein 
Return erkannt wird, dann kannst Du nicht ausschließen, ob andere 
Variablen, die für die übrige Programmkontrolle wichtig wären, 
überschrieben werden. Hinweis: In die map-datei schauen, ob das sein 
kann. Wenn nicht vorhanden, dann mit erzeugen lassen.

2.
Warum kopierst Du die ersten 6 Bytes in einen andere Stringvariable?

Du kannst doch auch die strncmp()-Funktions verwenden. Die überprüft 
eben nur n-Bytes.

Versuche doch mal

entfernen->      for(int i=0;i<6;i++)
entfernen->        rx_adresse[i] = uart_string[i];
entfernen->      rx_adresse[6]='\0';

      if(strncmp(uart_string[[0],"ABCDEF",6)==0)

Jetzt würde ich strncmp ab der Adresse uart_string[[0] für die Länge von 
6 Bytes mit dem String "ABCDEF" vergleichen. Stringterminierung wie \0 
spielt da keine Rolle.

Hinweis: Ich weiß nicht wie groß Dein Programm irgendwann einmal wird. 
Aber die str-Funktionen kosten doppelt RAM. Ich habe mir angewöhnt die 
Konstante Strings in den Flash zu legen und von dort auch zu 
vergleichen. Beim AVR ist das leider anders (Programmflash-Lib 
inkludieren). Man muß das mit der strncmp_P()-Funktion machen. Mit const 
bekommt man nicht das gewünschte Ergebnis. Mag vielleicht jetzt noch 
nicht stören...

3.
Mit cli() kannst Du den allgemeinen Interrupt sperren und mit sei() 
wieder zulassen. Nur für den Fall das die Kopiererei eine Art double 
buffering werden sollte, unterstelle ich hier mal, dass Deine 
UART-Geschwindigkeit wohl 115kBaud nicht überschreitet. Wenn als Dein 
Stringende erkannt wird, ist Dein mega644 mit seinen möglichen 20 MHz 
schneller am überprüfen, als der nächst Buchstabe eintrift.
Ansonsten solltest Du über Semaphoren nachdenken.
Es macht ja keinen Sinn pausenlos mit Nachrichten zugebommt zu werden, 
ohne sie gescheit ausgewertet zu haben oder eine Rückmeldung an den PC 
zu senden. Oder?

4. Ringspeicher
Vielleicht solltest Du über die Verwendung eines RIngspeichers 
nachdenken. Ich meine damit so eine Art Software FiFo. Meist will man ja 
mehr. Irgendwann...

Ich hoffe, dass hier etwas für Dich dabei war.

Viel Erfolg

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.