Datum:
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.
#define F_CPU 18432000UL #define BAUD 384000UL #define SPEED ((F_CPU+BAUD*8)/(BAUD*16)-1) #include <util/delay.h> #define nop() __asm volatile ("nop") volatile uint8_t active = 0; void uart_init(void) { UBRR0H = SPEED >> 8; UBRR0L = SPEED & 0xFF; UCSR0A |= (1<<TXC0); UCSR0B = (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0); sei(); } void uart_puti16(uint16_t val) { while ( !( UCSR0A & (1<<UDRE0)) ) nop(); UDR0 = val >> 8; while ( !( UCSR0A & (1<<UDRE0)) ) nop(); UDR0 = val & 0xFF; } int main (void) { DDRC |= (1 << PC5); uart_init(); ADC_init(); DDRD &= ~(1 << PD2); while(1) { if(active == 1) { uart_puti16(readADC()); } } return 0; } ISR(USART_RX_vect) { if(UDR0 == 'a') { uart_puti16(readADC()); } if(UDR0 == 's') { active = 1; } if(UDR0 == 'e') { active = 0; } } |
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
Datum:
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?
Datum:
vegiss das mit dem nop - es fehlt nur die notwendige einrückung dann ist es klar.
Datum:
Ok, korrigier ich schnell. Sollte aber am Problem nix ändern.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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
Datum:
Tut mir leid, dass ich das nicht erwähnt habe, dachte, es wäre nicht nötig. Der ADC hat 16bit, es ist ein ADS7825 von TI.
Datum:
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.
Datum:
@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 ...
Datum:
Die readADC ist tempomässig über jeden Zweifel erhaben? (Zur Probe würd ich mal einfach konstante Werte schicken
while(1) { if(active == 1) { uart_puti16( 4711 ); } } |
wenn da der Versatz immer noch da ist, wäre mein heißer Kandidat die PC-Seite. )
Datum:
Hier die PC-Seite:
static int werte[1000000]; static int counter = 0; static int pos = 0; void reader(void) { SerialPort^ port; Parity p = (Parity)Enum::Parse(Parity::typeid, "None"); StopBits s = (StopBits)Enum::Parse(StopBits::typeid, "1"); port = gcnew SerialPort("COM16",384000,p,8,s); port->Open(); unsigned int i = 0; unsigned int j = 0; port->Write("s"); while(true) { i = port->ReadByte(); j = port->ReadByte(); werte[pos] = j + (i*256); pos++; if(pos == 1000000) { pos = 0; counter++; } } } in main dann: Thread^ readThread = gcnew Thread(gcnew ThreadStart(reader)); readThread->Start(); und dann bei Bedarf Werte auslesen. |
Datum:
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:
uint16_t readADC(void) { DCLK_PORT &= ~(1<<DCLK_PIN); //Clk-Signal beginnen uint16_t data = 0; //hier kommen die Daten rein STARTSIG_PORT |= (1<<STARTSIG_PIN); //Konversion einleiten DCLK_PORT |= (1<<DCLK_PIN); //clk-Signal fortsetzen DCLK_PORT &= ~(1<<DCLK_PIN); DCLK_PORT |= (1<<DCLK_PIN); // nach 1 1/2 Takten kommen die Daten for(int i = 15; i >= 0; i--) //16 Datenbits auslesen { DCLK_PORT &= ~(1<<DCLK_PIN); //Flanke wechseln data |= ((DATASIG_PORT&(1<<DATASIG_PIN))<<i); // Daten einlesen DCLK_PORT |= (1<<DCLK_PIN); // Flanke wechseln } STARTSIG_PORT &= ~(1<<STARTSIG_PIN); // Konversion beenden return data; } |
Hier wird während der Konversion das Ergebnis der letzten Konversion ausgelesen.
Datum:
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:
uint16_t x = 0; while(1) { if(active == 1) { uart_puti16( x++); } } |
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?
Datum:
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:
uart_puti16(readADC());
_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
Datum:
Hi Dumme Frage: Warum nimmst du nicht das Hardware-SPI? MfG Spess
Datum:
Ein Blick ins Datenblatt zeigt: der ADC verwendet kein SPI, auch wenn TI das vielleicht behauptet.
Datum:
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.
Datum:
Hi >Ein Blick ins Datenblatt zeigt: der ADC verwendet kein SPI, auch wenn TI >das vielleicht behauptet. Für mich sieht das wie SPI Mode1 aus. MfG Spess
Datum:
@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