www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik schnelle Kommunikation über UART


Important announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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

Autor: Peter II (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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?

Autor: Peter II (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
vegiss das mit dem nop - es fehlt nur die notwendige einrückung dann ist 
es klar.

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
Ok, korrigier ich schnell. Sollte aber am Problem nix ändern.

Autor: Pako (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Pako (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
@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 ...

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.
)

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: Pako (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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?

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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

Autor: spess53 (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
Hi

Dumme Frage: Warum nimmst du nicht das Hardware-SPI?

MfG Spess

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
Ein Blick ins Datenblatt zeigt: der ADC verwendet kein SPI, auch wenn TI 
das vielleicht behauptet.

Autor: Pako (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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.

Autor: spess53 (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
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

Autor: Nikolas B. (physikant)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
@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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel




Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder GIF-Format hochladen.
Siehe Bildformate
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken erkennst du die Nutzungsbedingungen an.

webmaster@mikrocontroller.netImpressumNutzungsbedingungenWerbung auf Mikrocontroller.net