Hallo,
ich habe vor, 16 Bit Datenpakete über den UART eines AtMega168 zu
transportieren. Die Daten stammen von einem ADC von TI. Dieser sampelt
alle 25µS, die ich auch so gut es geht ausnutzen will. Ich betreibe die
Schnittstelle mit 384000 baud.
Hier der Quelltext.
1
#define F_CPU 18432000UL
2
#define BAUD 384000UL
3
#define SPEED ((F_CPU+BAUD*8)/(BAUD*16)-1)
4
#include<util/delay.h>
5
#define nop() __asm volatile ("nop")
6
7
volatileuint8_tactive=0;
8
9
voiduart_init(void)
10
{
11
UBRR0H=SPEED>>8;
12
UBRR0L=SPEED&0xFF;
13
UCSR0A|=(1<<TXC0);
14
UCSR0B=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
15
sei();
16
}
17
18
voiduart_puti16(uint16_tval)
19
{
20
21
while(!(UCSR0A&(1<<UDRE0)))
22
nop();
23
UDR0=val>>8;
24
while(!(UCSR0A&(1<<UDRE0)))
25
nop();
26
UDR0=val&0xFF;
27
}
28
29
intmain(void){
30
31
DDRC|=(1<<PC5);
32
uart_init();
33
ADC_init();
34
35
DDRD&=~(1<<PD2);
36
37
while(1)
38
{
39
40
if(active==1)
41
{
42
uart_puti16(readADC());
43
}
44
}
45
return0;
46
}
47
48
ISR(USART_RX_vect)
49
{
50
if(UDR0=='a')
51
{
52
uart_puti16(readADC());
53
}
54
if(UDR0=='s')
55
{
56
active=1;
57
}
58
if(UDR0=='e')
59
{
60
active=0;
61
}
62
}
Wenn ich PC-seitig ein Programm verwende, dass in einer for-Schleife ein
"a" sendet und dann 2 Bytes ausliest, erhalte ich stets korrekte Daten,
allerdings mit nur etwa 500Hz, ich möchte allerdings mindestens 15kHz
erreichen.
Wenn ich nun ein "s" sende und dann konstant immer 2 Bytes einlese, geht
es schief. Die Datenpakete fangen in der Mitte des einen Datenpakets an
und enden in der Mitte des anderen.
Ich habe in die While-Schleife der main-Funktion diverse Werte von
_delay_us() und _delay_ms() eingebaut. Bei 1ms tritt der Fehler nicht
mehr auf, bei 5-10µs verschiebt sich die Position, bei der die Daten in
der Mitte eines Pakets anfangen.
Ich habe zunächst vermutet, dass es an der PC-Seite liegt, aber sowohl
mein selbstgeschriebenes Programm, als auch realterm ergeben diesen
Fehler.
Woran könnte das liegen?
Danke im Vorraus!
Grüße
Nikolas
du kannst doch nicht einfach das Register mehrfach auslesen!
if(UDR0 == 'a')
{
uart_puti16(readADC());
}
if(UDR0 == 's')
{
active = 1;
}
if(UDR0 == 'e')
{
active = 0;
}
das macht man nicht!
uint8_t tmp = UDR0;
if ( tmp == 'a' )
> while ( !( UCSR0A & (1<<UDRE0)) )> nop();
was soll das nop hier, ich dachte es soll schnell gehen?
1. Du rufst die Funktion "uart_puti16()" sowohl aus dem main() als auch
aus der ISR heraus auf, ohne daß diese reentrant oder
interruptverriegelt wäre.
2. Wenn Du "uart_puti16()" aus der ISR heraus aufrufst, ist Dein
Empfänger in dieser Zeit blockiert und kann keine Daten empfangen.
Nikolas B. schrieb:> Ich habe in die While-Schleife der main-Funktion diverse Werte von> _delay_us() und _delay_ms() eingebaut. Bei 1ms tritt der Fehler nicht> mehr auf, bei 5-10µs verschiebt sich die Position, bei der die Daten in> der Mitte eines Pakets anfangen.> Ich habe zunächst vermutet, dass es an der PC-Seite liegt, aber sowohl> mein selbstgeschriebenes Programm, als auch realterm ergeben diesen> Fehler.
Das schliesst immer noch nicht aus, dass die UART-Schicht am PC nicht
mitkommt. Auf dem PC ist die ganze Sache ja nicht so einfach wie auf dem
AVR. Da liegen einige Softwareschichten und da du kein Handshake benutzt
hat der PC keine Möglichkeit den µC mal kurz ein wenig einzubremsen.
Allerdings: Bytes einfach so rauszublasen ist meistens sowieso ein
Hazardspiel. Ohne eine Form von Primitiv-Protokoll wirst du nicht weit
kommen.
Vorschlag:
Da der ADC Wert nur 10 Nutzbits hat, verschieb die 'Trennlinie' an der
du den 16 BIt Wert in 2 Bytes auftrennst. Das LowByte bildet sich aus
den 7 unteren Bits des Wertes. Das High-Byte aus den 3 höherwertigen.
Was hast du dadurch gewonnen?
Du kriegst 1 Bit frei - das höchstwertige Bit - welches du als Kennung
benutzen kannst ob es sich bei dem Byte um das High-Byte oder das
Low-Byte handelt. Beim einen ist es auf 1 gesetzt, beim anderen ist es
0. Der Empfänger kann dann anhand des Bytes entscheiden was er damit tun
muss, bzw. fehlende Bytes erkennen und die Messung verwerfen.
Der Aufruf im ISR ist im Prinzip unnötig. Man könnte ihn komplett
streichen, da ich die Methode "a" senden - 2 Byte empfangen - "a senden
- usw. nur testweise implementiert hatte.
Allerdings
> erhalte ich stets korrekte Daten, allerdings mit nur etwa 500Hz
das erscheint mir dann doch ein bischen wenig.
Das Problem könnte folgendes sein:
Dein Empfänger kann im Prinzip wesentlich schneller empfangen. Gibst du
allerdings die Werte sofort irgendwo aus, dann bremst diese Ausgabe
alles andere aus. Dein Problem ist in diesem Fall nicht die Serielle,
sondern das Hinpinseln der Werte auf den Monitor.
Nikolas B. schrieb:> Der Aufruf im ISR ist im Prinzip unnötig.
Vorallem ist er gefährlich. Mit Interrupts zu arbeiten ist nicht ohne.
Gerade so Sachen wie Warteschleifen in ISRs (bzw. Aufruf von Funktionen
mit Warteschleifen) ist kein guter Stil, sofern es überhaupt
funktioniert.
Guckst Du
http://www.mikrocontroller.net/articles/Interrupt
Jungs, bitte!
Das war ein Test! Er hat sich halt eine quick&dirty Lösung eingebaut,
mit der er testen kann, ob ADC und UART prinzipiell funktionieren.
Sobald das Kommando für Dauersampeln vom PC kommt, spielt die ISR keine
große Rolle mehr.
Ja, das ist kein guter Stil in der ISR. Aber trotzdem bellst du hier den
falschen Baum an.
Wenn dein Auto statt der üblichen 120 nur noch 80 fahren kann, bringt es
nichts die halb durchgerostete Anhängerkupplung zu bemängeln. Ja, sie
ist durchgerostet und ja das sollte nicht sein. Aber mit dem Problem der
defekten Zylinderkopfdichtung hat das nichts zu tun.
@Pako
Dann denke man sich den Abschnitt mit "a" weg.
@kbuchegg
PC-Seitig läuft das Auslesen in einem eigenen Thread, der konstant Werte
in ein Array schreibt. Ein anderer Thread liest dieses Array aus und
stellt es dar.
Soll ich die PC-Seite posten? Muss da allerdings warnen, ist in c++/CLI
geschrieben und verwendet .net-Methoden ...
Wenn ich 4711 sende, geht es. Allerdings ist der ADCreader so ne Sache,
das war schon eine schwere Geburt, bis ich den am laufen hatte. Es ist
der ADS7825 von TI mit 16bit und 25µS Conversion-Time, hier der Code:
Nikolas B. schrieb:> der ADS7825 von TI mit 16bit und 25µS Conversion-Time
Sag mal, bist Du Dir sicher, daß es überhaupt ein Übertragungsfehler ist
und nicht ein Problem beim ADC?
Funktioniert dies hier:
1
uint16_tx=0;
2
while(1)
3
{
4
if(active==1)
5
{
6
uart_puti16(x++);
7
}
8
}
Beim ADC müßtest Du die Sample-Zeit und die Conversion-Zeit warten,
bevor Du das Ergebnis auslesen darfst, ansonsten ist es ungültig. In
Deiner readADC()-Funktion startest Du eine Conversion und liest direkt
das Ergebnis aus, ohne Wartezeit.
Noch was: braucht der ADC vielleicht ein ChipSelect-Wechsel zwischen
zwei Wandlungen?
Irrtum, ich beende die Konversion zu Beginn des ADC-readers. Gestartet
wird sie an dessen Ende. Wie ich im Beitrag zuvor schon geschrieben
habe, lassen sich statische Werte problemlos übertragen, es liegt also
schon am ADC. Ein Chipselect ist nicht nötig, sonst würde es ja sonst
nicht gehen.
Was ich festgestellt habe, ist, dass wenn ich folgendes Einfüge:
1
uart_puti16(readADC());
2
_delay_us(17);
die Übertragung funktioniert. Das macht das ganze etwas seltsam, denn
offensichtlich ist irgend etwas zu langsam. Mit dieser Zeile braucht ein
kompletter Zyklus etwa 72µS.
Nun kann man das mal durchrechnen:
Auslesen des ADC (grob geschätzt): 17µS
Übertragen der Daten (19/384000): 50µS
Nun hat der ADC zwischen zwei Aufrufen seiner Auslese-Funktion genug
Zeit zur Konversion, da er 25µS braucht, aber 50µS hat. Nun stellt sich
die Frage, warum diese 17 zusätzlichen µS notwendig sind?
Jedenfalls werd ich mich wohl (wenn sonst niemand noch eine Idee hat)
wohl mit den 72µS zufrieden geben und akzeptieren, dass ich nur 14kHz
statt den angestrebten 20kHz zur Verfügung habe.
Schade.
Danke auf jeden Fall an alle, die sich um eine Hilfe bemüht haben!!
Grüße
Nikolas
Nikolas B. schrieb:> uint16_t readADC(void)> {> (...)> STARTSIG_PORT |= (1<<STARTSIG_PIN); //Konversion einleiten> (...)> STARTSIG_PORT &= ~(1<<STARTSIG_PIN); // Konversion beenden> (...)> }Nikolas B. schrieb:> Irrtum, ich beende die Konversion zu Beginn des ADC-readers.
Tatsächlich? Ich habe aus den Kommentaren das Gegenteil geschlossen.
@Pako
Mit Irrtum meinte ich, dass meine Kommentare falsch sind. Der Pin auf
LOW startet die Konversion.
@spess
Leider kenne ich mich mit dem SPI-Protokoll nicht gut genug aus, um es
zu erkennen. Jedenfalls wusste ich nicht, wie das mit dem Einleiten
einer Konversion (und dem entsprechenden Timing zum Auslesen)
funktionieren soll, wenn ich Hardware-SPI verwende. Würde mich da aber
über etwas Hilfe freuen, wenn das doch funktionieren sollte :)
Grüße
Nikolas