Forum: Mikrocontroller und Digitale Elektronik UART Verständnisproblem auf ATmega328p


von Chandler B. (chandler)


Lesenswert?

nabend,

ich habe auf einem ATmega328 UART am laufen, habe aber ein bisschen 
Verständisprobleme warum sich mein Code so verhält wie es sich verhält

main.c
1
#include "../defines.h"
2
#include <avr/io.h>
3
#include <stdlib.h>
4
#include <stdint.h>
5
#include "uart.h
6
7
#define UART_MAXSTRLEN 5
8
9
static volatile bool uart_dataReceived_b = false;
10
static volatile char uart_data_c[UART_MAXSTRLEN + 1] = {0};
11
12
int main(void)
13
{
14
    uart_init(MYUBRR);
15
16
    sei();
17
    uart_sendString("start\n");
18
19
    while (1) 
20
    {
21
        if(uart_dataReceived_b)
22
        {
23
            uart_sendString(&uart_data_c[0]);
24
            uart_dataReceived_b = false;
25
        }
26
    }
27
}
28
29
30
ISR(USART_RX_vect)
31
{
32
  unsigned char nextChar_c;
33
    static uint8_t uart_dataReceivedLength_u8 = 0;
34
35
  // Daten aus dem Puffer lesen
36
  nextChar_c = UDR0;
37
  if( false == uart_dataReceived_b )   // wenn uart_string gerade in Verwendung, neues Zeichen verwerfen
38
  {
39
      if( (nextChar_c != '\n')
40
      && (nextChar_c != '\r'))
41
//      && (UART_MAXSTRLEN > uart_dataReceivedLength_u8))
42
      {
43
          uart_data_c[uart_dataReceivedLength_u8] = nextChar_c;
44
          uart_dataReceivedLength_u8++;
45
      }
46
      else
47
      {
48
          uart_data_c[uart_dataReceivedLength_u8] = '\0';
49
            uart_dataReceivedLength_u8 = 0;
50
//            uart_dataReceived_b = 1;
51
      }
52
  }
53
}

uart.c
1
static void uart_transmit( unsigned char data )
2
static void uart_transmit( unsigned char data )
3
{
4
  while(!(UCSR0A & (1<<UDRE0)));
5
  UDR0 = data;
6
}
7
8
void uart_init(unsigned int ubrr)
9
{  
10
  /* Set baud rate */
11
  UBRR0H = ubrr >> 8;
12
  UBRR0L = ubrr & 0xFF;
13
14
  /* Enable receiver and transmitter */
15
  UCSR0B |= (1<<RXCIE0)|(1<<RXEN0)|(1<<TXEN0); // RXCIE0: RX Interrupt
16
17
  /* Set frame format: 8data, 1stop bit */
18
  UCSR0C = (1<<UCSZ00)|(1<<UCSZ01);
19
}
20
21
void uart_flush(void)
22
{
23
  uint8_t dummy;
24
  while (UCSR0A & (1<<RXC0))
25
  dummy = UDR0;
26
}
27
28
void uart_sendString(char *StringPtr)
29
{
30
  while(*StringPtr != 0x00)
31
  {
32
    uart_transmit(*StringPtr);
33
    StringPtr++;
34
  }
35
}

An für sich funktioniert es (wenn die Auskommentierten sachen nicht 
auskommentiert sind).

Wenn ich jetzt eine uart nachricht mit weniger als 5 zeichen schicke, 
passiert nichts (da die variable zum signalisieren dass was neues da ist 
auskommentiert ist). -> korrekt

Nun zu meinem Problem. Wenn ich mehr als 7 Zeichen sende( qwertzuiop), 
erhalte ich die ersten 7 zeichen wieder zurück( qwertzu ).
Von wo werden diese gesendet? bzw. Warum?

Beitrag #6691837 wurde vom Autor gelöscht.
von Hmmm (Gast)


Lesenswert?

Chandler B. schrieb:
> Von wo werden diese gesendet? bzw. Warum?

Buffer Overflow:

Chandler B. schrieb:
> #define UART_MAXSTRLEN 5
> [...]
> uart_data_c[UART_MAXSTRLEN + 1] = {0};

von Matthias L. (Gast)


Lesenswert?

Zu einem ordentlichen (FIFO) Buffer gehören auch Zähler für 
In/Out-Position und Füllstand.

von Chandler B. (chandler)


Lesenswert?

Hmmm schrieb:
> Chandler B. schrieb:
>> #define UART_MAXSTRLEN 5
>> [...]
>> uart_data_c[UART_MAXSTRLEN + 1] = {0};

oha gesehen. Habe ich abgeändert
1
uart_data_c[UART_MAXSTRLEN - 1] = {0};

danke schön

Hmmm schrieb:
> Buffer Overflow:

Was ich aber nicht verstehe, wenn ich den Aufruf 
uart_sendString(&uart_data_c[0]); auch noch auskommentiere, kommt nichts 
mehr.
Es muss dann ja die variable uart_dataReceived_b  gesetzt werden und das 
habe ich auskommentiert.

von Chandler B. (chandler)


Lesenswert?

Matthias L. schrieb:
> Zu einem ordentlichen (FIFO) Buffer gehören auch Zähler für
> In/Out-Position und Füllstand.

Was meinst du genau damit?
Also welche In/out-Position und welcher Füllstand?

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Du hast geschrieben

> An für sich funktioniert es (wenn die Auskommentierten
> Sachen nicht auskommentiert sind).

Dann sollten sich auch nicht auskommentiert sein, sonst bekomme ich als 
Leser einen Knoten im Kopf.

Drücke sich bitte klarer aus:
> Wenn ich jetzt eine uart nachricht mit weniger als 5 zeichen schicke

Wer schickt wohin? Ich nehme an dass du mit dem PC 5 Zeichen + <Enter> 
an den Mikrocontroller sendest. Also 6 Zeichen.

> kommt nichts mehr.

Meine Frau "kommt". In diesem technischen Zusammenhang hat das Wort für 
mich keine klar Bedeutung. Auch hier muss ich wieder raten, was du 
meinst.

Meinst das der Mikrocontroller nichts mehr empfängt, oder dass der PC 
nichts mehr empfängt?

> Von wo werden diese gesendet? bzw. Warum?

Ich dachte es kommt nicht mehr. Jetzt doch? Der PC sendet vermutlich 
nur, wenn du das Terminalprogramm entsprechend manuell bedienst. Und 
beim Mikrocontroller kann es nur mit uart_sendString() zusammenhängen, 
denn das ist die einzige Stelle wo uart_transmit() benutzt wird, was 
wiederum die einzige Stelle ist, die das UDR Register überschreibt.

Ich sehe in deinem Code eine kritische Race Condition:
1
        if(uart_dataReceived_b)
2
        {
3
            uart_sendString(&uart_data_c[0]);
4
            uart_dataReceived_b = false;
5
        }

Wenn etwas empfangen wurde, sendest du es und machst dich danach wieder 
empfangsbereit. Während dieser Zeit bis zu nicht empfangsbereit. 
Deswegen ist mit diesem Code nut Half-Duplex Kommunikation möglich. War 
das gewollt?

Das hier ist übrigend von hintern durch die Brust:
> uart_sendString(&uart_data_c[0]);

Schreibe einfach: uart_sendString(uart_data_c);

Da kommt technisch exakt das gleiche bei heraus, der Code wird aber 
besser lesbar. Denn so sieht man, dass der String gesendet werden soll, 
nicht nur das erste Zeichen, was man beim Anblick deiner Zeile mit dem 
[0] vermuten könnte, wenn man nicht aufpasst.

Due bekommst eine Warnung vom Compiler, die du nicht ignorieren 
solltest!:
1
warning: passing argument 1 of ‘uart_sendString’ discards ‘volatile’ qualifier from pointer target type [-Wdiscarded-qualifiers]
2
             uart_sendString(&uart_data_c[0]);

Du darfst nicht eine volatile Variable an eine Funktion übergeben, die 
eine nicht volatile Variable erwartet. Weil dadurch der Sinn von 
volatile verloren geht.

Die Zeile

> uart_data_c[UART_MAXSTRLEN + 1] = {0};

war richtig. Du willst einen Puffer anlegen, der ein Byte mehr speichern 
kann als die 5 Zeichen, weil dahinter noch Platz für den 
String-Terminierer \0 gebraucht wird.

Der Hauptfehler ist hier:

> // && (UART_MAXSTRLEN > uart_dataReceivedLength_u8))

Durch das Auskommentieren dieser Zeile bekommst du einen Pufferüberlauf. 
Du schreibst empfangene Zeichen hinter uart_data_c[] ins RAM, wodurch 
andere Daten überschrieben werden. Vermutlich trifft es in deinem Fall 
als erstes die Variable uart_dataReceived_b.

Ich habe deinen Code mal compiliert, um zu ermitteln, woe genau die 
Variablen im Speicher liegen:

1
avr-gcc -O1 -g -mmcu=atmega328 -Wa,-adhlns test.c
2
...
3
.bss:0000000000000000 uart_dataReceivedLength_u8.1789
4
.bss:0000000000000001 uart_data_c
5
.bss:0000000000000007 uart_dataReceived_b

Hier kann man schön sehen, dass die Variable uart_dataReceived_b im RAM 
an Adresse 7 hinter dem Puffer liegt. Wenn du 6 Zeichen (+ 
String-Terminierer) empfängst, überschreibst du uart_dataReceived_b mit 
dem String-Terminierer, der den Wert 0 hat. Wenn du Wenn du 7 Zeichen 
empfängst, überschreibst du uart_dataReceived_b mit dem letzten zeichen. 
Und weil das >0 ist, wird es als "true" ausgewertet.

Das erklärt, warum die Bedingung

> if(uart_dataReceived_b)

unerwartet erfüllt wurde.

von Stefan F. (Gast)


Lesenswert?

Chandler B. schrieb:
> Was meinst du genau damit?
> Also welche In/out-Position und welcher Füllstand?

Vergiss das für den Moment. Er meinte einen Ring-Puffer, den hast du 
aber nicht. Das ist ein andere Thema, mit dem du dich dann befassen 
kannst, wenn du eine Full-Duplex Kommunikation machen wirst (also 
gleichzeitig senden und empfangen).

von Chandler B. (chandler)


Lesenswert?

Stefan ⛄ F. schrieb:
>> An für sich funktioniert es (wenn die Auskommentierten
>> Sachen nicht auskommentiert sind).
>
> Dann sollten sich auch nicht auskommentiert sein, sonst bekomme ich als
> Leser einen Knoten im Kopf.

Ja, dass wäre übersichtlicher gewesen. Habe gedacht, dass ich zeige, 
dass ich daran gedacht habe. Ansonsten kämen evtl. Meldungen, dass diese 
sachen vergessen worden sind.

Stefan ⛄ F. schrieb:
> Wer schickt wohin? Ich nehme an dass du mit dem PC 5 Zeichen + <Enter>
> an den Mikrocontroller sendest. Also 6 Zeichen.

genau, ich schicke vom PC (hterm) die nachrichten zum Controller.

Stefan ⛄ F. schrieb:
> Meine Frau "kommt". In diesem technischen Zusammenhang hat das Wort für
> mich keine klar Bedeutung. Auch hier muss ich wieder raten, was du
> meinst.
>
> Meinst das der Mikrocontroller nichts mehr empfängt, oder dass der PC
> nichts mehr empfängt?

Hier meinte ich das der PC nichts mehr empfängt.

Stefan ⛄ F. schrieb:
> Wenn etwas empfangen wurde, sendest du es und machst dich danach wieder
> empfangsbereit. Während dieser Zeit bis zu nicht empfangsbereit.
> Deswegen ist mit diesem Code nut Half-Duplex Kommunikation möglich. War
> das gewollt?

Ja genau. Momentan habe ich nur meinen Controller und meinen PC. Daher 
passt das. Gedacht war, dass ich mir Befehle an den Controller schicke 
und dieser die dann Auswertet und irgendwelche Aktionen ausführt. Die 
Befehle kommen dann von mir (bzw. vom PC) da das eintippen ein bisschen 
dauert, sollte es kein Problem sein. Aber später wenn ein zweiter 
Controller da ist und sich diese gegenseitig Daten schicken müsste das 
noch abgeändert werden.

Stefan ⛄ F. schrieb:
> Der Hauptfehler ist hier:
>
>> // && (UART_MAXSTRLEN > uart_dataReceivedLength_u8))
>
> Durch das Auskommentieren dieser Zeile bekommst du einen Pufferüberlauf.
> Du schreibst empfangene Zeichen hinter uart_data_c[] ins RAM, wodurch
> andere Daten überschrieben werden. Vermutlich trifft es in deinem Fall
> als erstes die Variable uart_dataReceived_b.
>
> Ich habe deinen Code mal compiliert, um zu ermitteln, woe genau die
> Variablen im Speicher liegen:
>
> avr-gcc -O1 -g -mmcu=atmega328 -Wa,-adhlns test.c
> ...
> .bss:0000000000000000 uart_dataReceivedLength_u8.1789
> .bss:0000000000000001 uart_data_c
> .bss:0000000000000007 uart_dataReceived_b
>
> Hier kann man schön sehen, dass die Variable uart_dataReceived_b im RAM
> an Adresse 7 hinter dem Puffer liegt. Wenn du 6 Zeichen (+
> String-Terminierer) empfängst, überschreibst du uart_dataReceived_b mit
> dem String-Terminierer, der den Wert 0 hat. Wenn du Wenn du 7 Zeichen
> empfängst, überschreibst du uart_dataReceived_b mit dem letzten zeichen.
> Und weil das >0 ist, wird es als "true" ausgewertet.
>
> Das erklärt, warum die Bedingung
>
>> if(uart_dataReceived_b)
>
> unerwartet erfüllt wurde.

oha, daran habe ich nicht gedacht. Aber es ergibt sinn. Wenn ich immer 
weiter in den Buffer schreibe, werden die folgenden Daten (auch wenn 
diese nicht mehr zum buffer gehören) einfach überschrieben.
Ich war mir hier nicht ganz sicher, ob der Fehler an mir liegt (so wie 
es ja jetzt ist), oder ob es eine Eigenschaft des Controllers wäre. 
Welches den controller dazu veranlasst die Daten zu senden.

Vielen dank für deine Ausführliche Antwort.

: Bearbeitet durch User
von Alexander S. (alesi)


Lesenswert?

Chandler B. schrieb:
> uart.c
1
static void uart_transmit( unsigned char data )
2
static void uart_transmit( unsigned char data )
3
 {
4
    ...
5
  }

Warum kompiliert das ohne Fehlermeldung? Fehlt da nicht ein Semikolon am 
Ende der ersten Zeile, bzw ist die Zeile nicht überflüssig, da die 
Definition direkt folgt?

von Stefan F. (Gast)


Lesenswert?

Alexander S. schrieb:
> Warum kompiliert das ohne Fehlermeldung?

Tut es nicht. Es fehlt außerdem ein

> #include <stdbool.h>

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.