Forum: Mikrocontroller und Digitale Elektronik Probleme mit UART-Empfang


von Christian S. (aldebaran22322)


Lesenswert?

Hallo Liebe Gemeinde,

Ich benötige mal eure Hilfe in Sachen Kommunikation zwischen AVR's.
Zur Hardware:

Der Sender ist ein ATmega162PU. Dieser sendet über ein Bluetooth-Modul 
(BTM222)  folgende Informationen:
1
A,0
2
B,0
3
C,0
4
D,0
5
E,48
6
F,56
7
G,0
8
H,0
9
I,0
10
J,0
11
K,0
12
L,0
13
M,0
14
N,0
15
O,0
16
P,0
17
Q,0
18
R,0
19
S,0
20
T,0
21
U,0
22
V,0
23
W,0
24
X,0
25
Y,0

Das funktioniert auch ganz Gut. Die Zahlen hinter den Buchstaben sind 
Werte eines DMX-Pultes.

Auf Empfängerseite ist ebenfalls ein BTM222 mit einem ATMega644.
Das Bluetooth-Modul empfängt auch alles wunderbar. Nur dem AVR konnte 
ich das zuhören noch nicht richtig beibringen.

Um Die Einstellung Baudrate mache ich mir keine Sorgen. Aber der 
Vollständigkeit halber: Der AVR läuft mit einem Externen 16MHz Quarz. 
Die Baudrate beträgt 19200 und der Code um die ganze geschichte zu 
Initialisieren sieht so aus:
1
UBRR1H   = (unsigned char)(F_CPU / (BAUD * 16L) - 1)>>8;
2
UBRR1L   = (unsigned char)(F_CPU / (BAUD * 16L) - 1);
3
4
UCSR1A = (1<<RXC1);
5
UCSR1B = (1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1);  
6
UCSR1C = (1<<UCSZ11)|(1<<UCSZ10); 
7
sei();

Die Einzige Debugging-möglichkeit die ich habe ist über dem UART0 die 
Daten Auszugeben und mir am PC im Terminal anzusehen.

Hier ist mein Code um den Empfang zu realisieren:


Auszug aus der UART.h:
1
char uart_getchar()
2
{
3
  // Ist schon ein Zeichen im Buffer?
4
  while (!(UCSR1A & (1<< RXC1)));
5
  return UDR1;
6
}
7
8
void uart_readline(char* str)
9
{
10
  char c;
11
  unsigned char index;
12
  
13
  index = 0;
14
15
   while (c != 13)
16
   {
17
    c = uart_getchar();
18
    if (c != -1)
19
    {
20
      if (c == 13)      /*ASCII: NewLine */
21
      {
22
        str[index] = '\0';  /* Ende der Zeile erreicht,also String abschließen */
23
        /* Funktion beenden */
24
        return;
25
      }
26
      else/*Ansonsten normales Zeichen, anhängen an die Zeichenkette */
27
      {
28
        str[index] = c;
29
        index++;
30
      }
31
    }
32
  }
33
}
34
[c]
35
36
37
38
Und die Main.c
39
40
[c]
41
ISR(USART1_RX_vect)
42
{
43
  
44
  char eingabe[255];
45
  char chan[1];
46
  char val;
47
  uart_readline(eingabe);
48
  uart_puts0("\r\n");
49
  uart_puts0("Eingabe ist: ");
50
  uart_puts0(eingabe);
51
  char *ptr;
52
  ptr = strtok(eingabe, ",");
53
  strcpy(chan, ptr);
54
  uart_puts0("\nChan:");
55
  uart_puts0(chan);
56
  ptr = strtok(NULL, ",");
57
  val = atoi(ptr);
58
  uart_puts0("\nVal:");
59
  uart_putc0(val);
60
  uart_puts0("\n");
61
}
62
63
64
65
int main(void)
66
{
67
   uart_init();
68
  DDRB=0b00000001; // Feedback LED 
69
  PORTB |= (1<<0); // Feedback LED AN
70
  _delay_ms(1000);
71
  PORTB &= ~(1<<0); // Feedback LED AUS
72
 
73
  while(1)
74
    {
75
    
76
    PORTB |= (1<<0);
77
    _delay_ms(50);
78
    PORTB &= ~(1<<0);
79
    _delay_ms(100);
80
    }
81
  
82
  
83
}

Hier ist ein Ausschnitt von dem, was mir mein kleiner Krabbler so 
ausspuckt:
1
Eingabe ist: A,0
2
Chan:A
3
Val:[00]
4
5
Eingabe ist: B,–¸?,4hxÄÇ€·ŽZ>rd~ÿ
6
Chan:B
7
Val:[00]
8
9
Eingabe ist: E,50
10
Chan:E
11
Val:2
12
13
Eingabe ist: F,,104
14
Chan:F
15
Val:h
16
17
Eingabe ist: M,8
18
Chan:M
19
Val:[08]
20
21
Eingabe ist: Q,100
22
Chan:Q
23
Val:d
24
25
Eingabe ist: W,
26
Chan:W
27
Val:[00]


Manchmal funktioniert alles wie geplant, manchmal setzt er ein Komma da 
wo keins sein soll. Manchmal Datensalat. Hin und wieder versagt strtok. 
Alles in allem zu ineffizient. Ich tippe mal Stark darauf, dass die Art, 
wie ich den Interrupt abarbeite zu lange dauert und der AVR deswegen 
info's verliert.
Mir ist auch klar, dass ich durch diese ganzen uart_puts0("whatever"); 
die ISR unfassbar verlängere.

Leider habe ich wie gesagt keine andere Möglichkeit in den AVR zu 
gucken. Des weiteren bin ich auch am Ende meiner Programmiererischen 
Fähigkeiten und hoffe, dass ihr mir den Entscheidenden Tipp gebt.

Bin für jeden konstruktiven Rat dankbar.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Christian S. schrieb:
> Bin für jeden konstruktiven Rat dankbar.

 Lass deiner ISR Routine ein bisschen Luft, mach die USART Ausgabe
 in main.

 In der ISR Stringende abwarten, Flag setzen, Rest in main()

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Da sieht man mal wieder, wie mal sich selbst in's Abseits stellt.

Programmiere einen RX- und TX- Fifo für jeweils einen Uart, dann kann 
man ganz bequem in der Main-Loop den Datenstrom per DEA analysieren und 
entsprechend deiner Vorgaben reagieren.

von Wolfgang (Gast)


Lesenswert?

Christian S. schrieb:
> Die Einzige Debugging-möglichkeit die ich habe ist über dem UART0 die
> Daten Auszugeben und mir am PC im Terminal anzusehen.

Dann solltest du vielleicht mal 8€ in einen kleinen Logikanalysator 
investieren, damit du dir solche Dinge zeitlich genau ansehen kannst, 
z.B. indem du einen IO-Pin setzt, solange der µC in der ISR am werkeln 
ist und sehen kannst, wie das in Bezug zur Datenübertragung steht.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Die ISR wird normalerweise für jedes empfangene Zeichen ausgelöst, du 
hast also, wenn du ganze Strings in der ISR abfragst (abgesehen von dem 
zeitraubenden extra Krams) auf jeden Fall nach dem ersten empfangenen 
Zeichen beim zweiten Zeichen einen 'pending' Interrupt, der dazu führt, 
das die ISR beim Verlassen auf jeden Fall sofort wieder getriggert wird, 
egal, ob da noch ein Zeichen ist oder nicht.

Die ISR sollte also, wie oben schon angedeutet, nur ein Zeichen 
empfangen und kann meinetwegen Flags oder was auch immer setzen, um CR 
oder Komma zu signalisieren. Bei DMX bietet es sich an, die Daten ohne 
Rücksicht in einen Buffer zu schreiben und während des langen Mark/Space 
zu bearbeiten.

von Christian S. (aldebaran22322)


Lesenswert?

Danke für eure Antworten

Wolfgang schrieb:
> Christian S. schrieb:
>> Die Einzige Debugging-möglichkeit die ich habe ist über dem UART0 die
>> Daten Auszugeben und mir am PC im Terminal anzusehen.
>
> Dann solltest du vielleicht mal 8€ in einen kleinen Logikanalysator
> investieren, damit du dir solche Dinge zeitlich genau ansehen kannst,
> z.B. indem du einen IO-Pin setzt, solange der µC in der ISR am werkeln
> ist und sehen kannst, wie das in Bezug zur Datenübertragung steht.

Logic-Analyser ist vorhanden, allerdings habe ich in diesem Zusammenhang 
keine Verwendung gesehen. Dass die Abarbeitung der ISR zu lange dauert 
habe ich auch ohne Analyzer herausgefunden indem ich einfach meine 
Blinkende LED aus der main beobachtet habe. Ist zwischendurch immer 
wieder stehen geblieben. Aber der Tipp ist gut. Ohne meine Feedback-LED 
oder einem Analyzer hätte ich mich wohl tot gesucht.



Die Idee mit dem FIFO bin ich mal angegangen, allerdings mit mäßigem 
Erfolg. Ich habe mehrere fertig gestrickte versionen eines Ringpuffers 
ausprobiert und bin dann letztendlich bei diesem hier stehen geblieben:
https://www.mikrocontroller.net/articles/FIFO#Code-Beispiel

Und hier lässt mich wieder mein Hirn im Stich. Diese Pointerarithmetik 
ist mir noch'ne nummer zu hoch. Ich habe den FIFO-Buffer aus dem 
Tutorial ohne änderungen in mein Projekt intigriert und meinen Code wie 
folgt angepast:


main.c
1
ISR (USART1_RX_vect)
2
{
3
  BufferIn(uart_getchar());
4
}
5
6
int main(void)
7
{
8
   uart_init();
9
      while(1)
10
    {
11
12
     uint8_t eingabe;
13
    BufferOut(&eingabe);
14
    
15
                uart_puts0("\r\n");
16
    uart_puts0("Eingabe ist: ");
17
    uart_putc0(eingabe);
18
19
    PORTB |= (1<<0);
20
    _delay_ms(50);
21
    PORTB &= ~(1<<0);
22
    _delay_ms(100);
23
    }
24
  
25
  
26
}

UART.h
1
char uart_getchar()
2
{
3
  while (!(UCSR1A & (1<< RXC1)));
4
  return UDR1;
5
}
6
7
void uart_readline(char* str)
8
{
9
  char c;
10
  unsigned char index;
11
  
12
  index = 0;
13
14
   while (c != 13)
15
   {
16
    c = BufferOut(&str);
17
    if (c != -1)
18
    {
19
      if (c == 13)
20
      {
21
        str[index] = '\0';
22
        return;
23
      }
24
      else
25
      {
26
        str[index] = c;
27
        index++;
28
      }
29
    }
30
  }
31
}


Auf dem ersten blick in meinen Terminal habe ich mich gefreut. Nach dem 
Controller-Start sieht es so aus:
1
Eingabe ist: A
2
Eingabe ist: ,
3
Eingabe ist: 0
4
Eingabe ist: 
5
6
Eingabe ist: B
7
Eingabe ist: ,
8
Eingabe ist: 0
9
Eingabe ist: 
10
11
Eingabe ist: C
12
Eingabe ist: ,
13
Eingabe ist: 0
14
Eingabe ist: 
15
16
Eingabe ist: D
17
Eingabe ist: ,
18
Eingabe ist: 0

Danach kommt allerdings nurnoch Block A und B in Dauerschleife. Selbes 
spiel, wenn ich den Puffer erhöhe. Dann schafft die erste runde es 
lediglich ein paar Buchstaben weiter im Alphabet.

ICh muss aber dazu sagen, dass mein Compiler einige Warnugen und 
Hinweise ausspuckt:
1
expected 'uint8_t *' but argument is of type 'char **'

dabei bezieht er sich auf die stelle stelle im FIFO-Code
1
uint8_t BufferOut(uint8_t *pByte)

und
1
Warning  1  passing argument 1 of 'BufferOut' from incompatible pointer type [enabled by default]

für diese stelle:
1
c = BufferOut(&str);

Aus meiner UART.h

von indEAS (Gast)


Lesenswert?

So wie ich das sehe, solltest Du einen Start (und einen Stop Identifier) 
spendieren:
1
// Datenstring leeren bzw. initialisieren:
2
for (buffid_rx=0; buffid_rx < RX_BUFFER_LENGTH; buffid_rx++) rx_buffer[buffid_rx]=0; // Empfangsdatenpuffer leeren
3
buffid_rx=0; // Zeiger auf den Anfang
4
rx_complete = false;
5
while (rx_complete == false)
6
 {
7
 while (!kbhit())   // auf neues Zeichen warten
8
  {
9
  restart_wdt();
10
  delay_us(20);
11
  // hier könnte man ncoh eine Timeout oder blinke LED spendieren...
12
  }
13
 character = getc();      // Character in den Puffer
14
 if (character == 0x2A) buffid_rx = 0;   // Bei Empfang von * Pointer zurücksetzen
15
 else  buffid_rx++; // Zeiger inkrementieren
16
 if (buffid_rx >= RX_BUFFER_LENGTH) buffid_rx = RX_BUFFER_LENGTH;  // Überlauf abfangen
17
 rx_buffer[buffid_rx]=character;
18
 if (character == 0x0D) rx_complete=true;   // Bei Empfang von CR aus der 
19
 }
Danach kannst Du den Krempel interpretieren
z.B.
1
LED_data_hb = ASCI_TO_HEX(rx_buffer[2],rx_buffer[3]);  // LEDs Byte 1
2
LED_data_lb = ASCI_TO_HEX(rx_buffer[4],rx_buffer[5]);  // LEDs Byte 0

von W.S. (Gast)


Lesenswert?

Christian S. schrieb:
> Das funktioniert auch ganz Gut.

Christian S. schrieb:
> while (!(UCSR1A & (1<< RXC1)));

Nein, das funktioniert eben nicht ganz gut, sondern ganz schlecht.
Du machst den immer wiederholten Anfängerfehler, mit dem Kopf durch die 
Wand programmieren zu wollen ohne auch nur einmal links oder rechts zu 
schauen.

Also:
1. schreib dir einen ordentlichen UART-Treiber! Das ist so einer, der 
beide Zeichenströme puffert (in und out), der die Zeichen-Ein- und 
Ausgabe per Interrupt erledigt und eine hardwareunabhängige 
Schnittstelle zu den höheren Programmschichten bietet. Ich hab hier 
schon so oft was Entsprechendes gepostet.

2. mache kein derartiges Gedöns wie in deiner geposteten 
Interruptroutine! In eine ISR kommt sowas nicht rein, merk dir das. 
Grund: du blockierst damit nämlich auf sinnlosesete Weise den ganzen µC, 
weil er in der ISR auf irgend ein langsames Flag warten muß, ohne 
derweil was Sinnvolles tun zu können. Selbst ein anderer Interrupt geht 
bei sowas regelmäßig flöten.

3. zur Auswertung:
Lade dir am besten die Lernbetty herunter, die ist zwar für einen 
ARM7TDMI, aber bis auf die Innereien des UART-Treibers ist z.B. die 
gesamte Kommandoauswertung hardwareunabhängig. Das könnte dann so 
aussehen:

if (match("A,",&Cpt))  { Awert = Long_In(&Cpt); return;}
if (match("B,",&Cpt))  { Bwert = Long_In(&Cpt); return;}
usw.

Also denke zuerst nach und hau erst dann in die Tasten.

W.S.

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.