Forum: Mikrocontroller und Digitale Elektronik UART lib für ATmega328P


von Andreas R. (noobsen)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

habe davor nur STM32 Prozessoren programmiert und wollte jetzt für einen 
Controllino MINI über Microchip Studio einen ATmega programmieren. 
Jedoch bekomme ich die USB Schnittstelle einfach nicht zum Laufen. Alle 
anderen Ein- und Ausgänge + ADC funktionieren.

Selbst wenn ich alle anderen Libs auskommentiere und nur die UART lib 
drin habe, schmiert er ab, sobald ich ein Zeichen sende. Vielleicht 
sieht ja jemand einen groben Schnitzer oder kann mir weiterhelfen ...
Da ich nur den Controllino da habe, habe ich leider keine debug 
Schnittstelle und kann das fertige Programm nur über USB aufspielen und 
testen.

Vielen Dank.

Schöne Grüße noobsen

von Stefan F. (Gast)


Lesenswert?

Warum dein Programm "abschmiert" kann ich nicht erkennen. Mir ist aber 
dies aufgefallen:

Die Methode rund um UART_ZEICHEN_BUFFER_LEER erscheint mir äußerst 
fragwürdig. Schau dir mal diesen Artikel an, dort ist ein erprobtes 
Konzept beschrieben, das zuverlässig funktioniert: 
https://www.mikrocontroller.net/articles/FIFO

Alle Variablen, die sowohl innerhalb als auch außerhalb der ISR benutzt 
werden, würde ich als volatile kennzeichnen. In deinem Fall betrifft 
das die Variable UART_Flag_senden_aktiv. Im konkreten Code vernute ich, 
dass es keinen Unterschied macht, aber ich würde das volatile trotzdem 
einsetzen um Probleme bei künftigen Änderungen zu vermeiden.

https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Puffern_von_volatile-Variablen

Du könntest eine Reihe LED's anschließen, die anzeigen wie weit dein 
Code ausgeführt wird.

von Andreas R. (noobsen)


Lesenswert?

Hallo Stefan,

danke für dein Feedback. Volatile hatte ich auch schon probiert, leider 
ohne Erfolg. Den Ringbuffer verwende ich eigentlich sehr erfolgreich 
beim ST.

Mit dem Lampen-Spiel hatte ich es auch versucht, jedoch komme ich nur zu 
dem Ergebnis, dass er einmal in die Funktion USART_UDRE_vect() kommt und 
ein Zeichen reinschiebt zum Senden. Mit einem Terminalprogramm sieht man 
auch, dass das eine Zeichen auch übertragen wird. Danach jedoch im 
Nirvana hängt ...
Nach meinem Verständnis müsste die Funktion USART_UDRE_vect() ein 
zweites Mal aufgerufen werden, sobald das eine Zeichen übertragen worden 
ist, jedoch passiert das nicht. Da nichts weiter im Code bzw. main() 
vorhanden ist, bin ich überfragt, wo ich noch eine Lampe ein oder 
ausschalten soll, um etwas zu sehen ...
1
void USART_UDRE_vect(void)
2
{
3
  if( !(UART_TX_Buffer[UART_TX_Buffer_Register_Senden]==UART_ZEICHEN_BUFFER_LEER) )
4
  {
5
    UDR0 = UART_TX_Buffer[UART_TX_Buffer_Register_Senden];
6
    UART_TX_Buffer[UART_TX_Buffer_Register_Senden] = UART_ZEICHEN_BUFFER_LEER;
7
8
    if( (UART_TX_Buffer_Register_Senden+1) < UART_TX_BUFFER_SIZE )
9
      UART_TX_Buffer_Register_Senden++;
10
    else
11
      UART_TX_Buffer_Register_Senden = 0;
12
  }
13
  // Alle Zeichen gesendet, ISR abschalten
14
  else
15
  {
16
    UART_Flag_senden_aktiv = AUS;
17
    UCSR0B &= ~(1<<UDRIE0);
18
  }
19
}

von Stefan F. (Gast)


Lesenswert?

Andreas R. schrieb:
> UCSR0B &= ~(1<<UDRIE0);

Ich vermute, dass der Interrupt erst dann ausgelöst wird, wenn das UDR 
Register danach wieder leer wird. Wenn es zu dem Zeitpunkt aber schon 
leer ist, gibt es keinen Grund für einen erneuten Interrupt. Einen 
hattest du ja schon vorher aber da war dein Sendepuffer leer.

Das ist ein genereller Unterschied zwischen STM32 und AVR an ganz vielen 
(nicht allen) Stellen. Bei STM32 musst du jeden Interrupt selbst 
Quittieren oder Deaktivieren, sonst wird die selbe ISR bei ihrem Ende 
direkt wieder aufgerufen. Bei AVR werden die Interrupt-Flags beim 
Eintritt in die ISR automatisch gelöscht, so dass ein Ereignis immer nur 
zu maximal einem ISR Aufruf führt.

Oder anders formuliert: Ein STM32 würde würde die ISR immer wieder 
aufrufen, solange du das "UDR ist leer geworden" Flag nicht löschst. Ein 
AVR ruft die ISR nur einmal auf, wenn das Register leer wird.

In meinen AVR Programmen funktioniert die UART_send() Funktion so:

Wenn der UART sender aktiv ist, dann schreibe ich das Zeichen in den 
Puffer. Wenn er nicht aktiv ist, schreibe ich es direkt ins UDR 
Register.

Dadurch entfällt auch die Notwendigkeit, das UDRIE0 immer wieder 
umzuschalten. Ich schalte es einmal bei der Initialisierung ein, und 
dann bleibt es dabei.

Probiere das mal.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

> void USART_UDRE_vect(void)
Das ist doch keine ISR!
Zumindest nicht in der AVR GCC Welt.

von Andreas R. (noobsen)


Lesenswert?

Ohne Interrupt funktioniert es, wenn man einfach ein Zeichen in UDR 
schreibt. Jedoch möchte ich bei vielen Zeichen den Mikrocontroller nicht 
in einer while() warten lassen, bis das nächste Zeichen gesendet werden 
kann.
Später sollen auch größere Informationspakete an die Schnittstelle 
gesendet werden, während die Hauptstatemaschine läuft.
Von dem her würde ich es gerne mit Interrupt gesteuert realisieren.

von Andreas R. (noobsen)


Lesenswert?

Arduino F. schrieb:
>> void USART_UDRE_vect(void)
> Das ist doch keine ISR!
> Zumindest nicht in der AVR GCC Welt.

Wie sollte er korrekt heißen?

von Stefan F. (Gast)


Lesenswert?

Arduino F. schrieb:
> Das ist doch keine ISR!

Facepalm, das hätte ich erkennen müssen. Habe ich aber nicht.

Andreas R. schrieb:
> Wie sollte er korrekt heißen?

Ich empfehle dazu diese Doku 
https://www.nongnu.org/avr-libc/user-manual/index.html

In diesem Fall konkret die Unterseite 
https://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Dort steht als Beispiel:
1
ISR(USART_UDRE_vect)
2
{
3
    // user code here
4
}

Andreas R. schrieb:
> Ohne Interrupt funktioniert es, wenn man einfach ein Zeichen in UDR
> schreibt. Jedoch möchte ich bei vielen Zeichen den Mikrocontroller nicht
> in einer while() warten lassen, bis das nächste Zeichen gesendet werden
> kann.
> Später sollen auch größere Informationspakete an die Schnittstelle
> gesendet werden, während die Hauptstatemaschine läuft.
> Von dem her würde ich es gerne mit Interrupt gesteuert realisieren.

Ich glaube, du hast meinen Vorschlag (direkt ins UDR zu schreiben) nicht 
verstanden. Du sollst doch nur direkt ins Register schreiben, wenn der 
Sender nicht aktiv ist. Wenn er hingegen aktiv ist, sollst du nicht 
warten, sondern wie gehabt den Puffer und die ISR nutzen.

von Andreas R. (noobsen)


Lesenswert?

Das klingt vielversprechend!
Bin gerade unterwegs und werde es morgen früh gleich mal testen und 
berichten.
Danke Leute!!

: Bearbeitet durch User
von Purzel H. (hacky)


Lesenswert?

Es gibt 2 interrupts.

Der eine : UartDataRegisterEmpty bedeutet das UartDataRegister is leer, 
ist ins Shieberegister kopiert worden und wird raus geschoben. In diesem 
Interrupt wird das naechste Datenbyte in das TxRegister geladen.

der Andere : TxReady bedeutet das Effektive schieberegister ist leer. 
Die Uebertragung ist beendet.

Also erst mal : X mal UartDataRegisterEmpty interrupt laufen lassen und 
zum Schluss den TxReady. Bedeutet beim vorherigen, dem letzten 
UartDataRegisterEmpty  macht man nichts, weil man nichts zum Laden mehr 
hat.

Eine Library mit Ringbuffer benotigt man eigentlich nicht, laesst grad 
eine Statemachine das Protokol abspulen. Ich arbeite immer mit linearen 
buffern, die sind so lange wie die Meldung ist. Natuerlich nicht 
dynamisch alloziert, sondern fest.

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Purzel H. schrieb:
> Also erst mal : X mal UartDataRegisterEmpty interrupt laufen lassen und
> zum Schluss den TxReady.

Und wozu? Sofern man nicht einen RS485-Treiber bespaßen will, braucht 
man TXC nicht, und kann sich ausschließlich auf UDRE beschränken.

von Andreas R. (noobsen)


Lesenswert?

Hallo zusammen,

der Fehler lag wirklich an der Bennenung des Namen für den ISR.
Jetzt funktioniert Tx & Rx über Interrupt Steuerung wunderbar.

Vielen Dank für eure Hilfe!

Hier noch die fertige Version:
1
ISR(USART_UDRE_vect)
2
{
3
  char *TXpointer = UART_TX_Buffer;
4
5
  if( !(TXpointer[UART_TX_Buffer_Register_Senden]==UART_ZEICHEN_BUFFER_LEER) )
6
  {
7
    UDR0 = TXpointer[UART_TX_Buffer_Register_Senden];
8
    TXpointer[UART_TX_Buffer_Register_Senden] = UART_ZEICHEN_BUFFER_LEER;
9
10
    if( (UART_TX_Buffer_Register_Senden+1) < UART_TX_BUFFER_SIZE )
11
      UART_TX_Buffer_Register_Senden++;
12
    else
13
      UART_TX_Buffer_Register_Senden = 0;
14
  }
15
  else
16
  {
17
    UART_Flag_senden_aktiv = AUS;
18
    UCSR0B &= ~(1<<UDRIE0);
19
  }
20
}
21
22
void UART_send_string(char *data)
23
{
24
  char *TXpointer = UART_TX_Buffer;
25
26
  while(*data)
27
  {
28
    TXpointer[UART_TX_Buffer_Register_Schreiben] = *data++;
29
30
    if( (UART_TX_Buffer_Register_Schreiben+1) < UART_TX_BUFFER_SIZE )
31
      UART_TX_Buffer_Register_Schreiben++;
32
    else
33
      UART_TX_Buffer_Register_Schreiben = 0;
34
  }
35
  
36
  // starten, wenn Buffer noch nicht bereits abgearbeitet wird
37
  if(UART_Flag_senden_aktiv==AUS)
38
  {
39
    UART_Flag_senden_aktiv = AN;
40
    UCSR0B |= (1<<UDRIE0);
41
  }
42
}

von Wastl (hartundweichware)


Lesenswert?

Andreas R. schrieb:
> UART_TX_Buffer_Register_Schreiben

Andreas R. schrieb:
> UART_TX_Buffer_Register_Senden

- Zu lange Namen für Variablen
- ähnliche, fast gleichlautende Variablennamen, OMG!
- missverständliche Variablennamen, das sind Pointer, Indexe
in einem Array oder Ringbuffer

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Andreas R. schrieb:
> der Fehler lag wirklich an der Bennenung des Namen für den ISR.
Der Bezeichner war eigentlich schon OK so...
Es fehlten nur ein paar Attribute, die die Funktion zu einer ISR machen.
1
/* extern "C" */ void USART_UDRE_vect() __attribute__ ((__signal__,__used__, __externally_visible__));
2
void USART_UDRE_vect()
3
{
4
  // tuwas
5
}

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

Harald K. schrieb:
> Purzel H. schrieb:
>> Also erst mal : X mal UartDataRegisterEmpty interrupt laufen lassen und
>> zum Schluss den TxReady.
>
> Und wozu? Sofern man nicht einen RS485-Treiber bespaßen will, braucht
> man TXC nicht, und kann sich ausschließlich auf UDRE beschränken.

Man braucht es bei jeder halbduplex Kommunikation mit dem UART. Wenn man 
zwischen TX und RX umschalten will.

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Cyblord -. schrieb:
> Man braucht es bei jeder halbduplex Kommunikation mit dem UART. Wenn man
> zwischen TX und RX umschalten will.

Diesen Fall habe ich mit "rs485-Treiber bespaßen" gemeint. Sonst braucht 
man das aber nicht; wer macht schon freiwillig Halbduplex, wenn's nicht 
not tut?

von Cyblord -. (cyblord)


Lesenswert?

Harald K. schrieb:
> Cyblord -. schrieb:
>> Man braucht es bei jeder halbduplex Kommunikation mit dem UART. Wenn man
>> zwischen TX und RX umschalten will.
>
> Diesen Fall habe ich mit "rs485-Treiber bespaßen" gemeint. Sonst braucht
> man das aber nicht; wer macht schon freiwillig Halbduplex, wenn's nicht
> not tut?

Dann wenn man a.) nur 3 Leitungen haben kann oder b.) wenn man mit etwas 
Kommunizieren will was halbduplex macht.
z.B. Telemetrie-Sensoren im RC Modellbau.

Merke: Dein beschränkter Horizont ist nicht das Maß aller Dinge.

von Harald K. (kirnbichler)


Lesenswert?

Cyblord -. schrieb:
> Dann wenn man a.) nur 3 Leitungen haben kann

Rx, Tx und Gnd.

Klar, da muss man Halbduplex einsetzen. Was auch sonst?


Cyblord -. schrieb:
> Merke: Dein beschränkter Horizont ist nicht das Maß aller Dinge.

Welchen Teil von "wenn's nicht not tut" hast Du jetzt wieder überlesen?

Bist Du intellektuell von so einer Formulierung überfordert?

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

Harald K. schrieb:
> Cyblord -. schrieb:
>> Dann wenn man a.) nur 3 Leitungen haben kann
>
> Rx, Tx und Gnd.

Tja manchmal ist es GND,VCC,Signal.

> Welchen Teil von "wenn's nicht not tut" hast Du jetzt wieder überlesen?

Habe nichts überlesen aber dein Posts suggerierte sowas würde nur für 
RS485 gebraucht und das ist halt quatsch.

> Bist Du intellektuell von so einer Formulierung überfordert?

Ich kann nichts für deine falschen Aussagen.

von Harald K. (kirnbichler)


Lesenswert?

Cyblord -. schrieb:
> Ich kann nichts für deine falschen Aussagen.

Und ich nichts für Deine kategorische Weigerung, sinnerfassend zu lesen.

Schönes Wochenende.

von Andreas R. (noobsen)


Lesenswert?

Wastl schrieb:
> Andreas R. schrieb:
>> UART_TX_Buffer_Register_Schreiben
>
> Andreas R. schrieb:
>> UART_TX_Buffer_Register_Senden
>
> - Zu lange Namen für Variablen
> - ähnliche, fast gleichlautende Variablennamen, OMG!
> - missverständliche Variablennamen, das sind Pointer, Indexe
> in einem Array oder Ringbuffer

Hallo Wastl,

ich glaube, das ist Ansichtssache. Mir persönlich gefällt es besser 
etwas zu beschreiben, als einfach mit x, cnt oder gar cryptischen 
Zeichen so kurze Zeilen wie möglich zu produzieren.
Der Code muss auch nur von mir gelesen werden, also denke ich, kann man 
da ein Auge zudrücken.

Schöne Grüße

: Bearbeitet durch User
von Wastl (hartundweichware)


Lesenswert?

Andreas R. schrieb:
> Der Code muss auch nur von mir gelesen werden, also denke ich, kann man
> da ein Auge zudrücken.

Du irrst. Du hast deinen unleserliche Code hier reingestellt weil
du um Hilfe gebeten hast. Daher "müssen" sich andere Leute mit
deinem Kauderwelsch auseinandersetzen. In einer Team von
Programmierern in dem du teilnimmst würde man dich auspeitschen.

Andreas R. schrieb:
> als einfach mit x, cnt oder gar cryptischen
> Zeichen so kurze Zeilen wie möglich zu produzieren.

Es gibt durchaus Möglichkeiten sich in einer Programmiersprache
prägnant auszudrücken ohne Variablen-Namen mit zwei oder drei
Buchstaben anzuwenden.

von Stefan F. (Gast)


Lesenswert?

Wastl schrieb:
> In einer Team von Programmierern in dem du
> teilnimmst würde man dich auspeitschen.

Das muss aber ein schlechtes Team sein, wenn es sich an solchen 
Kleinigkeiten schon aufreibt.

Diskutiert ihr auch über die Anzahl von Leerzeilen und ob geschweifte 
Klammern hinter "if (...)" in eine neue Zeile gehören?

von Wilhelm M. (wimalopaan)


Lesenswert?

Stefan F. schrieb:
> Wastl schrieb:
>> In einer Team von Programmierern in dem du
>> teilnimmst würde man dich auspeitschen.
>
> Das muss aber ein schlechtes Team sein, wenn es sich an solchen
> Kleinigkeiten schon aufreibt.

Ich würde das für ein gutes Team halten:

https://www.youtube.com/watch?v=MBRoCdtZOYg

von Harald K. (kirnbichler)


Lesenswert?

Andreas R. schrieb:
> Mir persönlich gefällt es besser
> etwas zu beschreiben, als einfach mit x, cnt oder gar cryptischen
> Zeichen so kurze Zeilen wie möglich zu produzieren.

Das ist legitim. Aber "UART_TX_Buffer_Register_Senden" ist irreführend, 
da das mit "Register" nichts zu tun hat. Das ist ein Index.

Die Kombination aus "TX" und "Senden" ist dazu noch redundant, denn "TX" 
und "Empfangen" kann es nicht geben, ebensowenig wie es "RX" und 
"Senden" geben kann.

"UART_TX_Buffer_Index" würde genügen.

von Wastl (hartundweichware)


Lesenswert?

Harald K. schrieb:
> "UART_TX_Buffer_Index" würde genügen.

Noch so eine Unsitte ist es Variablen-Namen GROSS zu schreiben.
Grosschreibung ist in einem anständigen Sourcen-Gefüge für
Konstanten (Defines) reserviert. Konstante Variablen dürfen
(sollen) dagegen auch klein geschrieben werden.

von Harald K. (kirnbichler)


Lesenswert?

Wastl schrieb:
> Noch so eine Unsitte ist es Variablen-Namen GROSS zu schreiben.

Ich sehe hier gemischte Groß- und Kleinschreibung.

Ja, als an gewisse Standards gewöhnter Mensch denkt man am Anfang 
automatisch an ein Macro (#define), aber wenn man das zehnte Zeichen (u) 
sieht, ändert sich was.

Schön finde ich das nicht; ich wollte in meinem Beitrag aber in erster 
Linie auf die irreführende und redundante Begriffswahl hinweisen, und 
nicht auf den fünfhundertdrölfzigten Coding-Richtlinien-Flamewar.

von Wastl (hartundweichware)


Lesenswert?

Harald K. schrieb:
> ich wollte in meinem Beitrag aber in erster
> Linie auf die irreführende und redundante Begriffswahl hinweisen

Du warst ja nicht als Urheber der Schreibweise angesprochen.

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.