Forum: Mikrocontroller und Digitale Elektronik AVR serielle Kommunikation


von Da H. (darko91)


Lesenswert?

Liebe Leute,
Ich hoffe, jemand von Euch kann mir bei meinem Problem helfen
Ich programmiere das ArduinoUno Board (ATmega328p-µC) in Atmel. Bei der 
seriellen Kommunikation hänge ich jedoch.

Code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
5
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
6
7
#define F_CPU  16000000
8
#define BAUD  9600
9
#define BRC    ((F_CPU/16/BAUD) - 1)
10
11
12
#define RX_BUFFER_SIZE 128
13
14
char rxBuffer[RX_BUFFER_SIZE];
15
uint8_t rxReadPos = 0;
16
uint8_t rxWritePos = 0;
17
18
char getChar(void);
19
char peekChar(void);
20
21
int main( void )
22
{
23
  UBRR0H = (BRC >> 8);
24
  UBRR0L = BRC;
25
  
26
  UCSR0B = (1 << RXEN0) | (1 << RXCIE0);
27
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
28
  
29
  DDRB = (1 << PORTB3);
30
  
31
  sei();
32
  
33
  
34
  while(1)
35
  {
36
    char c = getChar();
37
    
38
    if(c == 'a')
39
    {
40
      sbi(PORTB, PORTB3);
41
    }
42
    else if(c == 'b')
43
    {
44
      cbi(PORTB, PORTB3);
45
    }
46
  }
47
}  
48
char peekChar(void)
49
{
50
  char ret = '\0';
51
  
52
  if(rxReadPos != rxWritePos)
53
  {
54
    ret = rxBuffer[rxReadPos];
55
  }
56
  return ret;
57
}
58
char getChar(void)
59
{
60
  char ret = '\0';
61
  
62
  if(rxReadPos != rxWritePos)
63
  {
64
    ret = rxBuffer[rxReadPos];
65
    
66
    rxReadPos++;
67
    
68
    if(rxReadPos >= RX_BUFFER_SIZE)
69
    {
70
      rxReadPos = 0;
71
    }
72
  }
73
  return ret;
74
}
75
ISR(USART_RX_vect)
76
{
77
  rxBuffer[rxWritePos] = UDR0;
78
  
79
  rxWritePos++;
80
  
81
  if(rxWritePos >= RX_BUFFER_SIZE)
82
  {
83
    rxWritePos = 0;
84
  }
85
}

Nach dem Builden und danach Draufspielen aufs Arduino öffne ich das Tera 
Term, welches mir ein Signal ans Board schicken soll.
Die RX-LED am Board blinkt kurz, was bedeutet, dass das Signal 
angekommen ist. Die an PORTB3 angeschlossene LED leuchtet aber nicht.
Woran kann das Problem liegen? Tipps?

MfG

von Peter II (Gast)


Lesenswert?

rxReadPos und rxWritePos  müssen volatile sein.

von Uwe (de0508)


Lesenswert?

Hallo,

bei deinen globalen Variable, mit ISR Zugriff, muss noch das Wort 
"volatile" ergänzt werden.

von Da H. (darko91)


Lesenswert?

Peter II schrieb:
> rxReadPos und rxWritePos  müssen volatile sein.

Stimmt, danke!
1
volatile uint8_t rxReadPos = 0;
2
volatile uint8_t rxWritePos = 0;

funktioniert leider trotzdem nicht =(

von Da H. (darko91)


Lesenswert?

Uwe S. schrieb:
> Hallo,
>
> bei deinen globalen Variable, mit ISR Zugriff, muss noch das Wort
> "volatile" ergänzt werden.

habs ergänzt, funktioniert leider noch immer nicht :(
1
volatile uint8_t rxReadPos = 0;
2
volatile uint8_t rxWritePos = 0;

von Karl H. (kbuchegg)


Lesenswert?

Darko Jen schrieb:
> Uwe S. schrieb:
>> Hallo,
>>
>> bei deinen globalen Variable, mit ISR Zugriff, muss noch das Wort
>> "volatile" ergänzt werden.
>
> habs ergänzt, funktioniert leider noch immer nicht :(


Eine recht gute Strategie ist es, zuerst mal die Kommunikation vom Board 
zum Terminal in Betrieb zu nehmen, damit du dein Programm in die Lage 
versetzt dir bei der Fehleranalyse zu helfen.

Also vergiss erst mal die Übertragung vom PC zum AVR, und kümmere dich 
um die umgekehrte Richtung.

Du willst, dass ein
1
....
2
int main( void )
3
{
4
  UBRR0H = (BRC >> 8);
5
  UBRR0L = BRC;
6
  
7
  UCSR0B = (1 << TXEN0);
8
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
9
  
10
  while(1)
11
  {
12
    putChar( 'x' );
13
  }
14
}

dir lauter x aufs Terminal zaubert.
Die nächste Funktion, die du haben willst, ist die putString.
1
void putString( const char* s )
2
{
3
  while( *s )
4
    putChar( *s++ );
5
}
denn damit kannst du dann
1
int main()
2
{
3
   ...
4
  while( 1 )
5
  {
6
    putString( "Hallo\n\r" );
7
  }
8
}
schon Texte aufs Terminal ausgeben.

Und mit diesen beiden Hilfsmitteln bist du dann schon in der Lage, 
deinen Empfangscode so zu pimpen, dass er dir mitteilt, was über die 
UART reinkommt
1
ISR(USART_RX_vect)
2
{
3
  rxBuffer[rxWritePos] = UDR0;
4
  putChar( rxBuffer[rxWritePos] );
5
  
6
  rxWritePos++;

und du kannst zum ersten Mal deinem Programm am Terminal dabei zusehen, 
welche Zeichen es empfängt.

Wenn die stimmen, dann kann man natürlich auch in der main mal eine 
Testausgabe machen lassen.
1
int main()
2
{
3
   ...
4
5
  while(1)
6
  {
7
    char c = getChar();
8
9
    putString( "Empfangen: " );
10
    putChar( c );
11
    
12
    if(c == 'a')
13
....

Also: Prioritäten ändern!
Zuerst musst du dein Programm in die Lage versetzen sich bemerkbar zu 
machen. Und dann überträgst du vom PC zum AVR. Denn in dieser 
Reihenfolge kann dir der AVR helfen. Anders rum eher nicht, weil du 
keine Möglichkeit hast, eindeutig festzustellen, was aus der UART 
rauskommt. Wenn überhaupt.

von holger (Gast)


Lesenswert?

>Die an PORTB3 angeschlossene LED leuchtet aber nicht.
>Woran kann das Problem liegen? Tipps?

Falsche Baudrate.

F_CPU im Code zu definieren heisst noch lange nicht
das die CPU auch mit diesem Takt läuft.
Falsch gesetze Fuses machen dir schnell einen
Strich durch die Rechnung.

von Leo C. (rapid)


Lesenswert?

Karl Heinz schrieb:
> Also vergiss erst mal die Übertragung vom PC zum AVR, und kümmere dich
> um die umgekehrte Richtung.

Und wenn das Senden vom AVR funktioniert, kannst Du Dich wieder ums 
Empfangen kümmern.

Darko Jen schrieb:
> uint8_t rxReadPos = 0;
> uint8_t rxWritePos = 0;

volatile wurde schon erwähnt. Außerderm kannst Du Dir die 
Initialisierung mit 0 sparen, da sie sowieso Default ist.

> char peekChar(void)
> {
>   char ret = '\0';
>
>   if(rxReadPos != rxWritePos)
>   {
>     ret = rxBuffer[rxReadPos];
>   }
>   return ret;
> }

Die Funktion sollte so funktionieren. Da rxReadPos volatile ist, muß der 
Compiler die Variable 2 mal aus dem RAM laden. Mit einer Hilfsvariablen 
kann ein RAM-Zugriff wegoptimiert werden. Bei dieser einfachen Funktion 
bringt das wenig, aber das Prinzip ist nützlich für komplexere 
Funktionen.
[/c]
char peekChar(void)
{
  char ret = '\0';
  uint8_t ri = rxReadPos;

  if(ri != rxWritePos)
  {
    ret = rxBuffer[ri];
  }
  return ret;
}
[/c]

> char getChar(void)
> {
>   char ret = '\0';
>
>   if(rxReadPos != rxWritePos)
>   {
>     ret = rxBuffer[rxReadPos];
>     rxReadPos++;
>     if(rxReadPos >= RX_BUFFER_SIZE)
>     {
>       rxReadPos = 0;
>     }
>   }
>   return ret;
> }

Hier bringt die Optimierung schon mehr, da rxReadPos sonst 4 mal gelesen 
und 2 mal geschrieben wird.
1
char getChar(void)
2
{
3
  char ret = '\0';
4
  uint8_t ri = rxReadPos;
5
6
  if(ri != rxWritePos)
7
  {
8
    ret = rxBuffer[ri];
9
    ri++;
10
    if(ri >= RX_BUFFER_SIZE)
11
    {
12
      ri = 0;
13
    }
14
    rxReadPos = ri;
15
  }
16
  return ret;
17
}

> ISR(USART_RX_vect)
> {
>   rxBuffer[rxWritePos] = UDR0;
>   rxWritePos++;
>   if(rxWritePos >= RX_BUFFER_SIZE)
>   {
>     rxWritePos = 0;
>   }
> }

Hier fehlt die Überprüfung ob der Puffer voll ist.
Da Du '\0' als "Puffer leer" wertest, würde ich dieses Zeichen auch 
nicht in den Puffer schreiben.
1
ISR(USART_RX_vect)
2
{
3
  uint8_t wi;
4
  char c = UDR0;
5
6
  if (c != '\0')
7
  {
8
    wi = rxWritePos;
9
    /* Eine Position im Puffer ist garantiert immer frei. */
10
    rxBuffer[wi] = c;
11
    wi++;
12
    if(wi >= RX_BUFFER_SIZE)
13
    {
14
      wi = 0;
15
    }
16
    /* Der Write-Index darf den Read-Index nicht überholen */
17
    /* Deshalb WritePos nur speichern, wenn ReadPos noch nicht eingeholt wurde */
18
    if (wi != rxReadPos)
19
    {
20
      rxWritePos = wi;
21
    }
22
  }
23
}

von Da H. (darko91)


Lesenswert?

...
...

> Hier fehlt die Überprüfung ob der Puffer voll ist.
> Da Du '\0' als "Puffer leer" wertest, würde ich dieses Zeichen auch
> nicht in den Puffer schreiben.
>
1
> ISR(USART_RX_vect)
2
> {
3
>   uint8_t wi;
4
>   char c = UDR0;
5
> 
6
>   if (c != '\0')
7
>   {
8
>     wi = rxWritePos;
9
>     /* Eine Position im Puffer ist garantiert immer frei. */
10
>     rxBuffer[wi] = c;
11
>     wi++;
12
>     if(wi >= RX_BUFFER_SIZE)
13
>     {
14
>       wi = 0;
15
>     }
16
>     /* Der Write-Index darf den Read-Index nicht überholen */
17
>     /* Deshalb WritePos nur speichern, wenn ReadPos noch nicht eingeholt 
18
> wurde */
19
>     if (wi != rxReadPos)
20
>     {
21
>       rxWritePos = wi;
22
>     }
23
>   }
24
> }
25
>

Danke Leo für deine Optimierung vom Code.
Leider ändert das nichts an der Funktion. Die LED leuchtet nicht. 
Lediglich die RX-LED am Board selber leuchtet jedes Mal beim Tippen 
eines Characters, sonst nichts. Hast noch einen Vorschlag?

von Walter S. (avatar)


Lesenswert?

Darko Jen schrieb:
> Hast noch einen Vorschlag?

die anderen Tipps beherzigen, z.B. von Karlheinz

von Da H. (darko91)


Lesenswert?

Karl Heinz schrieb:
> Darko Jen schrieb:
>> Uwe S. schrieb:

> ....
> int main( void )
> {
>   UBRR0H = (BRC >> 8);
>   UBRR0L = BRC;
>
>   UCSR0B = (1 << TXEN0);
>   UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
>
>   while(1)
>   {
>     putChar( 'x' );
>   }
> }
> [/c]
>
> dir lauter x aufs Terminal zaubert.
> Die nächste Funktion, die du haben willst, ist die putString.
>
>
1
> void putString( const char* s )
2
> {
3
>   while( *s )
4
>     putChar( *s++ );
5
> }
6
>
> denn damit kannst du dann
>
1
> int main()
2
> {
3
>    ...
4
>   while( 1 )
5
>   {
6
>     putString( "Hallo\n\r" );
7
>   }
8
> }
9
>
> schon Texte aufs Terminal ausgeben.
>
> Und mit diesen beiden Hilfsmitteln bist du dann schon in der Lage,
> deinen Empfangscode so zu pimpen, dass er dir mitteilt, was über die
> UART reinkommt
>
1
> ISR(USART_RX_vect)
2
> {
3
>   rxBuffer[rxWritePos] = UDR0;
4
>   putChar( rxBuffer[rxWritePos] );
5
> 
6
>   rxWritePos++;
7
>
>
> und du kannst zum ersten Mal deinem Programm am Terminal dabei zusehen,
> welche Zeichen es empfängt.
>
> Wenn die stimmen, dann kann man natürlich auch in der main mal eine
> Testausgabe machen lassen.
>
>
1
> int main()
2
> {
3
>    ...
4
> 
5
>   while(1)
6
>   {
7
>     char c = getChar();
8
> 
9
>     putString( "Empfangen: " );
10
>     putChar( c );
11
> 
12
>     if(c == 'a')
13
> ....
14
>
>
> Also: Prioritäten ändern!
> Zuerst musst du dein Programm in die Lage versetzen sich bemerkbar zu
> machen. Und dann überträgst du vom PC zum AVR. Denn in dieser
> Reihenfolge kann dir der AVR helfen. Anders rum eher nicht, weil du
> keine Möglichkeit hast, eindeutig festzustellen, was aus der UART
> rauskommt. Wenn überhaupt.

Danke Karl Heinz für deine Hilfe,
den 1. Schritt (Character-Ausgabe) habe ich somit geschafft.
Bei der Stringausgabe hänge ich leider - anstatt im Sekundentakt "Hallo" 
auszugeben, werden jeweils 2xDs ausgegeben ("DD" "DD"...)
Wie kommt es dazu?
LG
1
char putChar(char c)
2
{
3
  UDR0 = 'D';
4
  return UDR0;
5
}
6
void putString( const char* s )
7
{
8
  while( *s )
9
    putChar( *s++ );
10
}
11
int main( void )
12
{
13
  UBRR0H = (BRC >> 8);
14
  UBRR0L = BRC;
15
  
16
  UCSR0B = (1 << TXEN0);
17
  UCSR0A = (1 << UDRE0);
18
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
19
  
20
  while( 1 )
21
  {
22
    putString( "Hallo\n\r" );
23
    _delay_ms(1000);
24
  }
25
}

von Bernhard S. (dl9rdw)


Lesenswert?

Na dann schau mal was Deine putchar Funktion macht, dann weißt auch 
warum das so ist.

Grüße Bernhard...

von Leo C. (rapid)


Lesenswert?

> Bei der Stringausgabe hänge ich leider - anstatt im Sekundentakt "Hallo"
> auszugeben, werden jeweils 2xDs ausgegeben ("DD" "DD"...)
> Wie kommt es dazu?

Die Frage müßte lauten: "Wieso werden nur jeweils zwei 'D' 
ausgegeben?"
"Hallo\n\r" hat schließlich 7 Zeichen.

Hint: UDRE0

von Da H. (darko91)


Lesenswert?

Bernhard Schröcker schrieb:
> Na dann schau mal was Deine putchar Funktion macht, dann weißt auch
> warum das so ist.
>
> Grüße Bernhard...

Ich weiß schon, was die putchar Funktion macht, leider weiß ich aber 
nicht, wie ich das Problem behebe. Daher bitte ich auch um Hilfe, 
wodurch ich dann einiges dazulernen würde

von Da H. (darko91)


Lesenswert?

Leo C. schrieb:
>> Bei der Stringausgabe hänge ich leider - anstatt im Sekundentakt "Hallo"
>> auszugeben, werden jeweils 2xDs ausgegeben ("DD" "DD"...)
>> Wie kommt es dazu?
>
> Die Frage müßte lauten: "Wieso werden nur jeweils zwei 'D'
> ausgegeben?"
> "Hallo\n\r" hat schließlich 7 Zeichen.
>
> Hint: UDRE0

Richtig übersetzt
Ins UDR0 wird 'D' reingeschrieben. Durch die putString Funktion (s++) 
wird dann 2x 'D' ausgegeben.
> "Hallo\n\r" hat schließlich 7 Zeichen.
schon klar, wie soll ich die nun ausgeben?

von Leo C. (rapid)


Lesenswert?

1. Schau mal nach, wie putString() funktioniert. Welche Funktion ruft 
sie auf?

> Ins UDR0 wird 'D' reingeschrieben. Durch die putString Funktion (s++)
> wird dann 2x 'D' ausgegeben.

Nein, nicht 2x, es wird 7x 'D' ins UDR0 geschrieben. Daß Du nur 2 davon 
zu sehen bekommst, liegt daran, daß Du die Zeichen schneller in UDR0 
schreibst, als sie gesendet werden können.

2. UDRE0 != UDR0

von Da H. (darko91)


Lesenswert?

Liebe Leute,
Ich bedanke mich für eure Hilfe. Der Code funktioniert nun
Was ich eigenartig finde ist, dass die LED nur dann leuchtet, wenn die 
UART Initialisierung in einer eigenen Funktion steht.

Nun die Frage: Was muss hier am Code noch genau verändert werden, um 
eine Bluetooth Kommunikation bereitzustellen statt dem Terminal?
LED soll quasi per Smartphone ein und ausgeschaltet werden

http://www.seeedstudio.com/wiki/index.php?title=Bluetooth_Shield
Verwendeter Bluetooth-Chip: BC417
TX des Moduls wird mit PORTD7 des Boards verbunden
RX mit PORTD6
wie geht es nun weiter? Konfigurationen?
Jeder Vorschlag ist willkommen!!

Code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <stdint.h>
4
#define F_CPU  16000000UL
5
#include <util/delay.h>
6
7
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))    // clear bit
8
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))    // set bit
9
10
#define FOSC  16000000    // 16MHz
11
#define BAUD  9600      // Baudrate 9600
12
#define BRC    FOSC/16/BAUD-1  // Prescaler
13
14
#define RX_BUFFER_SIZE 128      // buffer 128 bits
15
char rxBuffer[RX_BUFFER_SIZE];
16
17
volatile uint8_t rxReadPos;
18
volatile uint8_t rxWritePos;
19
20
char getChar(void);
21
22
void USART_Init(unsigned int ubrr)    // wenn direkt in Main, funktioniert nicht
23
{
24
  //Set baud rate
25
  UBRR0H = (ubrr>>8);
26
  UBRR0L = ubrr;
27
  
28
  UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1<<RXCIE0);      // Enable receiver and transmitter
29
  UCSR0A = (1 << UDRE0);  //richtiger Empfang ansonsten nur mit BAUD 19200 bei Terminal möglich
30
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);    // Set frame: 8 bit data
31
}
32
33
int main( void )
34
{
35
  USART_Init(BRC);
36
  
37
  DDRB = (1 << PORTB0);  // PORTB3 is OUTPUT
38
    
39
  sei();  // set interrupt
40
  
41
  while(1)  
42
  {
43
    char c = getChar();
44
    
45
    if(c == '0')          // if sent character = "0"..
46
    {
47
      cbi(PORTB, PORTB0);      // clear bit - set LED to 0
48
      //PORTB &= ~(1<<PORTB0);
49
    }
50
    else if(c == '1')        // if sent character = "1"..
51
    {
52
      sbi(PORTB, PORTB0);      // set bit - set LED to 1
53
      //PORTB |= (1<<PORTB0);
54
    }
55
  }
56
  
57
  /*while(1)  // DDDD... übertragen
58
  {
59
    UDR0 = 'D';
60
    _delay_ms(500);
61
  }*/  
62
63
}
64
char getChar(void)
65
{
66
  char ret = '\0';
67
  
68
  if(rxReadPos != rxWritePos)
69
  {
70
    ret = rxBuffer[rxReadPos];
71
    
72
    rxReadPos++;
73
    
74
    if(rxReadPos >= RX_BUFFER_SIZE)
75
    {
76
      rxReadPos = 0;
77
    }
78
  }
79
  return ret;
80
}
81
ISR(USART_RX_vect)
82
{
83
  rxBuffer[rxWritePos] = UDR0;
84
  
85
  rxWritePos++;
86
  
87
  if(rxWritePos >= RX_BUFFER_SIZE)
88
  {
89
    rxWritePos = 0;
90
  }
91
}

von Leo C. (rapid)


Lesenswert?

Warum die Init in main() nicht gehen sollte, ist mir ein Rätsel.
Ansonsten:

>   //Set baud rate
>   UBRR0H = (ubrr>>8);
>   UBRR0L = ubrr;

Der Compiler kennt auch UBRR0 als 16 bit Register.
1
   //Set baud rate
2
   UBRR0 = ubrr;
Der generierte Code ist der Gleiche. Aber: weniger Sourcecode == weniger 
(Tipp-)Fehler-(möglichkeiten).

>   UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1<<RXCIE0);      // Enable

Besser den USART erst enablen, wenn er komplett initialisiert ist. Diese 
Zeile also ans Ende setzen.

>   UCSR0A = (1 << UDRE0);  //richtiger Empfang ansonsten nur mit BAUD

Was soll das?


>     UDR0 = 'D';
>     _delay_ms(500);

Also, das willt Du wirklich nicht!
sondern:
1
     while ((UCSR0A & (1<<UDRE0)) == 0)
2
         ; /* warte bis TX Data Register empty */
3
     UDR0 = 'D';

Hätte nicht gedacht, daß man den Hinweis auf UDRE0 so mißverstehen kann.

von Da H. (darko91)


Lesenswert?

Leo C. schrieb:
> Was soll das?
>
>     UDR0 = 'D';
>     _delay_ms(500);
>
> Also, das willt Du wirklich nicht!
> sondern:
>
1
>      while ((UCSR0A & (1<<UDRE0)) == 0)
2
>          ; /* warte bis TX Data Register empty */
3
>      UDR0 = 'D';
4
>
>
> Hätte nicht gedacht, daß man den Hinweis auf UDRE0 so mißverstehen kann.

der Abschnitt des Codes wurde Anfangs verwendet, um ein 'D' ans Terminal 
zu schicken. Jetzt wurde er nicht rausgelöscht, sondern auskommentiert. 
Danke trotzdem

von Leo C. (rapid)


Lesenswert?

Meine Frage "Was soll das?" bezog sich auf diese Zeile:

>   UCSR0A = (1 << UDRE0);  //richtiger Empfang ansonsten nur mit BAUD

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.