Forum: Mikrocontroller und Digitale Elektronik Arduino Uno / Atmega328 Serielle Schnittstelle mit Interrupt (USART_UDRE_vect) läuft nicht richtig..


von Matthias (Gast)


Lesenswert?

Hallo zusammen,

Wir haben ein kleines Programm, dabei soll der ADC dauerhaft wandeln, 
und der aktuelle Wandelwert über die serielle Schnittstelle an den PC 
übertragen werden.

Es treten folgende Probleme auf:
Werden Daten ohne, oder mit geringer Wartezeit in der ISR des UART an 
den PC gesendet, so kommen beim PC immer verschiedene Werte-Folgen an 
diese folgen bestehen aus 2-5Bytes und wiederholen sich dann wieder.
z.b.
29-144-229 oder 00-02-165-223
wobei der Wert des Potis auf etwa 144 sein sollte.

Zudem hört die Übertragung bei einem Setzen der AD-Spannung auf 255 
komplett auf, es werden also noch 2 mal 255 übertragen und dann kommt 
nichts mehr. Wie kann sowas überhupt sein?

Wäre wirklich toll wenn jemand dieses Verhalten erklären könnte.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#define F_CPU 16000000UL
4
#include <util/delay.h>
5
#define FOSC 16000000 // Clock Speed
6
#define BAUD 9600
7
#define MYUBRR FOSC/16/BAUD-1
8
9
void USART_Init( unsigned int ubrr)
10
{
11
  /*Set baud rate */
12
  UBRR0H = (unsigned char)(ubrr>>8);
13
  UBRR0L = (unsigned char)ubrr;
14
  //Enable receiver and transmitter */
15
  UCSR0B = 0x38;
16
  /* Set frame format: */
17
  UCSR0C = 0x06;
18
}
19
20
void ADC_Conf(void)
21
{
22
  ADMUX = 0x61;
23
  ADCSRA = 0xe5;
24
  DIDR0 =0x3F;
25
}
26
27
ISR(USART_UDRE_vect)
28
{
29
  _delay_ms(30); --Wartezeit
30
  UDR0 = ADCH;
31
  reti();
32
}
33
34
int main(void)
35
{
36
  USART_Init(MYUBRR);
37
  ADC_Conf();
38
  sei();
39
  USART_Transmit(ADCH);
40
  while(1)
41
  {
42
43
    }
44
}

Ein schönes Wochenende und vielen Dank,
Matthias

von STK500-Besitzer (Gast)


Lesenswert?

Matthias schrieb:
> _delay_ms(30);

Dafür sollte man dich einfach verhauen.
In ISRs  haben delays nichts zu suchen.

Matthias schrieb:
> USART_Transmit(ADCH);

Wie sieht die aus?

Und wie kommen diese Werte-Folgen zustande?

von Stefan F. (Gast)


Lesenswert?

> _delay_ms(30);

Delays haben in einer ISR nichts zu suchen. Damit öffnest du die Büchse 
der Pandorra. Warte lieber, bis das Senderegister frei ist:

loop_until_bit_is_set(UCSRA, UDRE);

> reti();

Das solltest du ganz weg lassen, darum kümmert sich der C Compiler 
bereits. Ich fürchte dass du durch diesen falschen Befehl den 
Stapelspeicher durcheinander bringst.

von Hermann G. (df2ds)


Lesenswert?

Und in einer UARS-ISR sollte man ein UART-Register auslesen und nicht 
ADCH. Das ist das High-Byte des AD-Wandlers!
Sorry, falsch gelesen. Hier wird ja in das UART geschrieben.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Hermann G. schrieb:
> Und in einer UARS-ISR sollte man ein UART-Register auslesen und
> nicht ADCH. Das ist das High-Byte des AD-Wandlers!

Ich glaube er hatte folgendes vor: Man sendet ein byte an den 
Mikrocontroller, um eine Messung auszulösen. Er soll dann mit dem 
aktuellen Messwert des ADC antworten.

Aber du hast Recht, es ist wichtig das UDR Register auszulesen, sonst 
kann er danach kein weiteres Zeichen mehr empfangen.

von Matthias (Gast)


Lesenswert?

STK500-Besitzer schrieb:
> Matthias schrieb:
>> _delay_ms(30);
>
> Dafür sollte man dich einfach verhauen.
> In ISRs  haben delays nichts zu suchen.

Ja genau!
Niemand will hier ein Delay haben!
Eine Vermutungt ist, dass es sich vielleicht um ein 
Synchronisationsproblem der seriellen Schnittstelle handelt?
Deshalb wurde es eingebaut!

STK500-Besitzer schrieb:
> Matthias schrieb:
>> USART_Transmit(ADCH);
>
> Wie sieht die aus?

Oh, wie konnte ich sie nur vergessen...
1
void USART_Transmit( unsigned char data )
2
{
3
  /* Wait for empty transmit buffer */
4
  while ( !( UCSR0A & (1<<UDRE0)) )
5
  ;
6
  /* Put data into buffer, sends the data */
7
  UDR0 = data;
8
}



Stefanus F. schrieb:
> Delays haben in einer ISR nichts zu suchen. Damit öffnest du die Büchse
> der Pandorra. Warte lieber, bis das Senderegister frei ist:
>
> loop_until_bit_is_set(UCSRA, UDRE);

Ok, aber warten wollen wir gerade nicht, wir sollen die Zeit während des 
Sendens des aktuellen Werts für andere Berechnungen nutzen können.
Deshalb soll auch bei jedem abgeschlossenen Sendevorgang ein Interrupt 
ausgeführt werden welcher den dann aktuellen Wert sendet.
Während dies passiert wollen wir andere Dinge erledigen lassen...


Stefanus F. schrieb:
>> reti();
>
> Das solltest du ganz weg lassen, darum kümmert sich der C Compiler
> bereits. Ich fürchte dass du durch diesen falschen Befehl den
> Stapelspeicher durcheinander bringst.

Da gebe ich dir im Grunde recht, er wurde ebenfalls nachträglich 
eingefügt um dass Fehlverhalten der Wertefolgen, sowie den völligen 
Sendestop beim Wert 255 eventuell zu beseitigen.

Hermann G. schrieb:
> Und in einer UARS-ISR sollte man ein UART-Register auslesen und nicht
> ADCH. Das ist das High-Byte des AD-Wandlers!
> Sorry, falsch gelesen. Hier wird ja in das UART geschrieben.

Dass Verstehe ich nun nicht, die Routine sollte nicht bei Empfang, 
sondern bei abgeschlossenen senden ausgeführt werden... und dass ist 
doch der richtige IRQ hierfür?

Stefanus F. schrieb:
> Hermann G. schrieb:
>> Und in einer UARS-ISR sollte man ein UART-Register auslesen und
>> nicht ADCH. Das ist das High-Byte des AD-Wandlers!
>
> Ich glaube er hatte folgendes vor: Man sendet ein byte an den
> Mikrocontroller, um eine Messung auszulösen. Er soll dann mit dem
> aktuellen Messwert des ADC antworten.
>
> Aber du hast Recht, es ist wichtig das UDR Register auszulesen, sonst
> kann er danach kein weiteres Zeichen mehr empfangen.

Im grunde möchten wir erstmal garnichts empfangen, sondern nur den 
jeweils aktuellen AD-Wert an den PC schicken und gleichzeitig noch 
Rechnezeit z.b. für vom PC empfangene Kommandos haben.


Hat nun leider etwas gedauert, aber SEHR vielen Dank für eure Antworten.
Ich hoffe die Sache ist nun etwas verständlicher :)

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


Lesenswert?

Matthias schrieb:
> Ok, aber warten wollen wir gerade nicht, wir sollen die Zeit während des
> Sendens des aktuellen Werts für andere Berechnungen nutzen können.
> Deshalb soll auch bei jedem abgeschlossenen Sendevorgang ein Interrupt
> ausgeführt werden welcher den dann aktuellen Wert sendet.
> Während dies passiert wollen wir andere Dinge erledigen lassen...

Genau das verhinderst du ja, wenn du delay_ms() in einer ISR verwendest. 
ISR sind nämlich blockend (zumindest beim AVR) und deswegen hältst du 
die ganze Chose für 30ms komplett an. Attribute wie ISR_NOBLOCK können 
dazu dienen, das zu verhindern, haben aber andere Seiteneffekte.
Wenn du delay_ms() in der Hauptschleife verwendest ist das o.k., denn es 
kann durch ISR unterbrochen werden.

In deinem Fall arbeitest du besser mit Flags zur Signalisierung zwischen 
den beiden ISR.

von c-hater (Gast)


Lesenswert?

Matthias schrieb:

> Im grunde möchten wir erstmal garnichts empfangen, sondern nur den
> jeweils aktuellen AD-Wert an den PC schicken und gleichzeitig noch
> Rechnezeit z.b. für vom PC empfangene Kommandos haben.

Dafür wäre der UDRE-Interrupt der richtige. Damit kannst du (teoretisch) 
einen ununterbrochenen ausgehenden Datenstrom von ADC-Daten erzeugen.

Es ist dann allerdings ziemlich sicher nicht so, das jedes ausgehende 
Byte tatsächlich ein Sample repräsentiert, d.h.: es ist sehr 
wahrscheinlich, dass du entweder Samples auslässt oder mehrfach 
versendest, also ein armseliges Resampling betreibst.

Das kann man prinzipiell auf drei Wegen lösen:

1) man sorgt durch geeignete Initialisierung der Hardware (UART und ADC) 
dafür, dass immer genau dann ein neues Sample vom ADC geliefert wird, 
wenn die UART gerade feststellt, dass sie in der Lage ist, einen neuen 
Wert zu versenden. Das ist allerdings relativ schwierig, da beim ADC 
zwingend ein Primfaktor 13 in der Takt-Einheit steckt, in den 
Standard-Bitraten der UART aber nicht, da stecken nur 2, 3 und 5 als 
Primfaktoren drinne. In der Regel wird man also mit Rücksicht auf den 
Empfänger keine geeignete Bitrate wählen können, um eine ganzzahlige 
Teilbarkeit erzielen zu können.

Bleibt also i.d.R.:

2) man sorgt dafür, dass die UART Bitrate (*Wortbreite) höher ist als 
die für die Übertragung der ADC-Daten nötige. Dann braucht man überhaupt 
keine UART-Interrupts, nur den ADC-Interrupt. Blocking Bullshit wie 
AnalogRead() musst du dir dann allerdings klemmen, das ist nur für 
Arduidioten gedacht...

3) Man "pimped" die ADC-Samplerate über ihre eigenen 
Konfigurationsfähigkeiten hinaus, indem man einen Timer als Trigger für 
die ADC benutzt. Dann ist man viel freier bei der Konfiguration der 
Samplerate und kann insbesondere diesen lästigen Primfaktor 13 
loswerden. Damit ist dann das unter 1) geschilderte Szenario auch bei 
Standard-Bitraten erreichbar. Aber auch hier braucht man keinen 
UART-Interrupt. Der Trigger für das Senden ist auch hier der 
ADC-Interrupt. Also nix AnalogRead().

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.