Forum: Mikrocontroller und Digitale Elektronik ATOI nur bis 4 Stellen


von Stefan D. (mackie05)


Lesenswert?

Hallo,

ich versuche jetzt schon seit 3 Stunden ein Problem mit ATOI zu lösen:

Ich schicke per Uart Zahlen vom PC im ASCII Format an einen ATMega328P.
Die Zahlen sind im Bereich von 0-65535 und NULL-terminiert.
Empfangen wird über Interrupt, gewandelt mit ATOI und über UART wieder 
zum PC zurückgeschickt.

Mein Problem: Sende ich Zahlen von 0-9999 läuft alles problemlos. Sobald 
ich allerdings die erste fünfstellige Zahl sende, wird diese noch wieder 
zurückgeschickt. Die nächste Zahl, ganz gleich welche wird dann 
allerdings immer zu 0. Wenn ich daraufhin weitere Zahlen sende verbleibt 
der Wert bei 0.

Beispiel:

PC TX -> PC RX
100 -> 100
1000 -> 1000
10000 -> 10000
12000 -> 0
1000 -> 0
150 -> 0
etc.

Es scheint, als ob ATOI nicht mit mehr als 5 Stellen umgehen kann und 
dann den Dienst verweigert...

<c>
char uart_buffer[8];
uint8_t NewValue=0, z=0;

ISR (USART_RX_vect)
{
  uart_buffer[z]=UDR0;
  z++;
  if (uart_buffer[z-1]==0x00) {z=0; NewValue=1;}
}

int main(void)
{
  Init_SPI_Master();
  Init_Uart();

  while(1)
  {
    if (NewValue==1)
    {
      NewValue=0;
      uint16_t PCValue = atoi(uart_buffer);
      //uint16_t PCValue = strtoul(uart_buffer,NULL,10);
      if (PCValue<65536)
      {
        SPI_Write_Reg(PCValue);
        Uart_puts("DAC MAX541 set to: ");
        Uart_Send_16Bit(PCValue);
        Uart_CR_LF();
      }
      else
      {
        Uart_puts("Value out of range!");
        Uart_CR_LF();
      }
    }
  }
}
</c>

Ich habe das auch schon mit "strtoul" versucht - gleiches Ergebnis.
Hat jemand eine Idee?

PS: Der Übersichtlichkeit halber habe ich die UART-Routinen und die 
Inits weggelassen.

Danke und Gruß Stefan

von Walter S. (avatar)


Lesenswert?

1) volatile verwenden
2) das ganze noch solange verriegeln bis die Daten verarbeitet sind 
damit nicht der Interrupt in den Daten rumpfuscht

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Stefan D. schrieb:
> if (PCValue<65536)
PCValue kann niemals grösser als 65535 werden...

Sendest du tatsächlich eine richtige 0 als Ende einer Zahl?

von Stefan D. (mackie05)


Lesenswert?

@ Walter, prinzipiell gute Einwände, jedoch funktioniert es ja für die 
Werte von 0-9999 zuverlässig und reproduzierbar.

@Lothar, das ist lediglich ein Plausibilitätscheck, damit Werte >65536 
vom PC kommend ignoriert werden. Es sollen ja nur die Werte kleiner 
65536 verarbeitet werden.

Ich sende das "NULL" vom HTerm und wie gesagt bei Werten von 0-9999 tut 
es zuverlässig.

von Diek (Gast)


Lesenswert?

Die uint16_t kann aber nur max 16Bit speichern und das ist nur 0-65535. 
Mehr oder weniger geht nicht.
Dein array oben ist nicht initialisiert. Kann es daran liegen?

von Karl H. (kbuchegg)


Lesenswert?

Dann würde ich vorschlagen, du fügst hier mal ein
1
  if (NewValue==1)
2
  {
3
    NewValue=0;
4
5
    Uart_puts( uart_buffer );   // <-----
6
7
    uint16_t PCValue = atoi(uart_buffer);
8
9
    ....
ein und siehst dir an, welchen String der AVR tatsächlich empfangen hat.

von Karl H. (kbuchegg)


Lesenswert?

Stefan D. schrieb:

> @Lothar, das ist lediglich ein Plausibilitätscheck, damit Werte >65536
> vom PC kommend ignoriert werden. Es sollen ja nur die Werte kleiner
> 65536 verarbeitet werden.

Da bist du aber schon viel zu spät drann.
Aus einem atoi kann per Definition nichts größeres als 65535 rauskommen. 
Eigentlich kann nichts größeres als 32737 rauskommen, denn das i in atoi 
steht für int. Und der hat auf deinem AVR nun mal einen Wertebereich von 
-32768 bis +32767.
Wenn du auf einen unsigned aus bist, dann wäre die richtige Funktion 
atou (u wie unsigned)

von Stefan D. (mackie05)


Lesenswert?

@ Lothar, Diek, Stimmt das ist noch ein Fragment als ich noch maximal 
einen 12 Bit Wert übertragen hab. Für 16 Bit Werte macht das natürlich 
keinen Sinn - ändert aber erstmal nichts am Problem.

Hab das Array jetzt initialisiert - leider keine Veränderung.

<c>
char uart_buffer[8]={0};
</c>

von Stefan D. (mackie05)


Lesenswert?

Karl H. schrieb:
> Dann würde ich vorschlagen, du fügst hier mal ein
>
1
>   if (NewValue==1)
2
>   {
3
>     NewValue=0;
4
> 
5
>     Uart_puts( uart_buffer );   // <-----
6
> 
7
>     uint16_t PCValue = atoi(uart_buffer);
8
> 
9
>     ....
10
>
> ein und siehst dir an, welchen String der AVR tatsächlich empfangen hat.

Hab ich getan:
Empfangene Daten aus HTerm:

100DAC MAX541 set to: 100;<\r><\n>
100DAC MAX541 set to: 100;<\r><\n>
10000DAC MAX541 set to: 10000;<\r><\n>
DAC MAX541 set to: 0;<\r><\n>

Gesendet hatte ich 100, 100, 10000, 12000

Wie man sieht fehlt die 12000 in der Rückmeldung :-(

Den Punkt mit dem Wertebereich sehe ich schon, allerdings sollte es ja 
bis 32767 funktionieren.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Stefan D. schrieb:

> Wie man sieht fehlt die 12000 in der Rückmeldung :-(

Also lautet die eigentliche Frage nicht, was mit dem atoi los ist, 
sondern warum da kein gültiger String vorhanden ist.

von Karl H. (kbuchegg)


Lesenswert?

Karl H. schrieb:
> Stefan D. schrieb:
>
>> Wie man sieht fehlt die 12000 in der Rückmeldung :-(
>
> Also lautet die eigentliche Frage nicht, was mit dem atoi los ist,
> sondern warum da kein gültiger String vorhanden ist.

Womit wir bei der nächsten Modifikation wären.

Die ISR, die Zeichen empfängt, soll doch bitte schön mal ein bischen 
auskunftsfreudiger sein, was denn da so alles eintrudelt.
1
ISR (USART_RX_vect)
2
{
3
  uart_buffer[z] = UDR0;
4
5
  {
6
    char tmp[20];
7
    sprintf( tmp, "'%c' - %02x\r\n", uart_buffer[z], uart_buffer[z] );
8
    Uart_puts( tmp );
9
  }
10
  
11
  z++;
12
  if (uart_buffer[z-1]==0x00)
13
  {
14
    z=0;
15
    NewValue=1;
16
  }
17
}

nicht nervös werden. Normalerweise ist ein Versenden per UART keine gute 
Idee aus einer ISR heraus. Aber da es hier um einen Test geht und du 
händisch am Terminal tippst, geht das schnell genug (ausser dein 
Versenden ist Interrupt getrieben, dann funktioniert das nicht)

Alte Volksweisheit
In der klassischen Verarbeitungskette
1
  Eingabe - Verarbeitung - Ausgabe
kann ein Fehler überall sitzen - auch in der Eingabe. Es ist daher eine 
gute Idee, wenn man im Fehlerfall erst einmal sicherstellt, dass das 
Programm auch wirklich mit den Daten arbeitet von denen man annimmt, das 
sie dort auftauchen. Ansonsten kann man sich nämlich im Schritt 
'Verarbeitung' einen Wolf suchen und doch nichts finden.

von Karl H. (kbuchegg)


Lesenswert?

Karl H. schrieb:

> Die ISR, die Zeichen empfängt, soll doch bitte schön mal ein bischen
> auskunftsfreudiger sein, was denn da so alles eintrudelt.

Und ich wette 2 zu 1, du wirst dort eine weitere nicht erwartete 
'Eingabe' auftauchen sehen, die dir den String noch ehe er verarbeitet 
wurde wieder zu einem 0 String zusammenkürzt. Eventuell wird dort auch 
ein Carriage Line Feed auftauchen.

von Stefan D. (mackie05)


Lesenswert?

Stimmt, irgendwie empfange ich Mist:

Bin im ersten Schritt von 115000 auf 19200 Bau runter gegangen um den 
Fehler zu minimieren.

Bei deinem Code kommt jetzt folgendes bei raus:

'1' - 31<\r><\n>
'0' - 30<\r><\n>
'0' - 30<\r><\n>
'100DAC MAX541 set to: 100;<\r><\n>

Beim Senden von 1000

'5' - 35<\r><\n>
'0' - 30<\r><\n>
'0' - 30<\r><\n>
'500DAC MAX541 set to: 500;<\r><\n>

Beim Senden von 5000

Werde mir das mal mit dem Oszi anschauen, was da wirklich ankommt...
Vielen Dank erstmal für die gute und schnelle Hilfe!

von Karl H. (kbuchegg)


Lesenswert?

Stefan D. schrieb:
> Stimmt, irgendwie empfange ich Mist:
>
> Bin im ersten Schritt von 115000 auf 19200 Bau runter gegangen um den
> Fehler zu minimieren.

Das muss nichts heissen. Eine kleinere Baudrate bedeutet nicht 
automatisch, dass die Fehlerwahrscheinlichkeit sinkt. Entscheidend ist, 
mit wievielen % Fehler sich die Baudrate einstellen lässt. Das ist ein 
Zusammenspiel aus Prozessortaktfrequenz und Baudrate. Es kann durchaus 
sein, dass die höhere Baudrate einen kleineren prozentualen Fehler hat 
und damit zuverlässiger funktioniert.

von Karl H. (kbuchegg)


Lesenswert?

Dein hTerm ist so eingestellt, dass es Tastendrücke sofort weiterleitet?

Oder wartet es darauf, dass du Return drückst?
Denn dann ist alles klar. Auch das gedrückte Return wird gesendet und 
kommt dir in deiner Eingabe in die Quere.

Auch darf die Übertragung einzelner Zeichen jetzt nicht zuuuu schnell 
passieren. Denn der AVR braucht ja auch etwas Zeit um die Rückmeldung zu 
schicken. Beim händischen Tippen normalerweise kein Problem, sofern 
hTerm jeden Tastendruck sofort auf die Reise bringt.

: Bearbeitet durch User
von Stefan D. (mackie05)


Angehängte Dateien:

Lesenswert?

Das Oszi sagt, kein Fehler im Sender! Gemessen am RX-Pin des ATMega 
328P.

So langsam wird es doch wirklich merkwürdig....

von Stefan D. (mackie05)


Lesenswert?

Karl H. schrieb:
> Dein hTerm ist so eingestellt, dass es Tastendrücke sofort weiterleitet?
>
> Oder wartet es darauf, dass du Return drückst?
> Denn dann ist alles klar. Auch das gedrückte Return wird gesendet und
> kommt dir in deiner Eingabe in die Quere.
>
> Auch darf die Übertragung einzelner Zeichen jetzt nicht zuuuu schnell
> passieren. Denn der AVR braucht ja auch etwas Zeit um die Rückmeldung zu
> schicken. Beim händischen Tippen normalerweise kein Problem, sofern
> hTerm jeden Tastendruck sofort auf die Reise bringt.

Ja erst bei Return wird alles gesendet. Wüsste auch nicht, wo ich es bei 
HTerm umstellen kann...

Ja das kann natürlich sein, dass es dann zu schnell geht...

von Karl H. (kbuchegg)


Lesenswert?

Stefan D. schrieb:

> Ja erst bei Return wird alles gesendet. Wüsste auch nicht, wo ich es bei
> HTerm umstellen kann...

Ich denke du hast recht, das kann man bei hTerm nicht wirklich 
einstellen

> Ja das kann natürlich sein, dass es dann zu schnell geht...

Plan B. Die Ausgabe in der ISR drastisch verkürzen. Dann muss es ein * 
als Rückmeldung auch tun, auch wenn es besser wäre, das Zeichen selbst 
zu sehen. Aber zumindest kann man mal sehen, ob die Anzahl der Zeichen 
stimmt.

Dann bleibt noch Plan C:
Innerhalb der ISR einen vollständigen String zur Seite kopieren, so dass 
der Buffer schon wieder zur Verfügung steht, damit weitere Zeichen 
empfangen werden können, während der erste abgearbeitet wird.

Dann gibts noch Plan D:
Einen Ringbuffer installieren, in dem eintrudelnde zeichen geparkt 
werden, die sich dann die Hauptschleife abholt und zu einem String 
zusammensetzt. Die Fleury UART Library macht das beispielsweise.

von Stefan D. (mackie05)


Lesenswert?

Fehler gefunden!

Mit der Funktion Uart_Send_16Bit hatte ich 5 Zeichen fürs Array. Aus 
irgendeinem Grund müssen es für eine 5-stellige Zahl mindestens 6 sein.


<c>
void Uart_Send_16Bit(uint16_t data)
{
  char snuma[5];  // Mit 6 tut es!
  itoa (data,snuma,10);
  Uart_puts(snuma);
}
</c>

Jetzt tut es! Nix desto trotz, Karl Heinz, vielen Dank für Deine Mühe!

von Karl H. (kbuchegg)


Lesenswert?

Stefan D. schrieb:

> void Uart_Send_16Bit(uint16_t data)
> {
>   char snuma[5];  // Mit 6 tut es!
>   itoa (data,snuma,10);

und auch hier wieder: falsche Funktion.
Für unsigned gibt es die Funktion utoa.

Und nein, das ist kein Kavaliersdelikt, denn für -32750 wäre dein Array 
immer noch um 1 Stelle zu klein.

von Karl H. (kbuchegg)


Lesenswert?

Allerdings denke ich, dass im Input Buffer immer noch ein Problem auf 
dich wartet. Das ist noch nicht erledigt.

von Justus S. (jussa)


Lesenswert?

Stefan D. schrieb:
> Aus
> irgendeinem Grund müssen es für eine 5-stellige Zahl mindestens 6 sein.

wenn du das immer noch nicht verstehst, solltest du dir dringend ein 
Grundlagenbuch über C zulegen...

von Mark B. (markbrandis)


Lesenswert?

Stefan D. schrieb:
> Aus irgendeinem Grund müssen es für eine 5-stellige Zahl mindestens 6 sein.

Das da:

> char snuma[5];

enthält ja keine Zahl, sondern Zeichen. Genau genommen soll es eine 
Zeichenkette sein. Und wie markiert man in der Programmiersprache C das 
Ende einer solchen? Dies liest Du bitte nach.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Stefan D. schrieb:
> <c>
> ....
> </c>
Kleiner Tipp: probiers mal wie in der Anleitung direkt über dem 
Eingabefeld beschrieben nicht mit spitzen sondern mit eckigen Klammern: 
[ ]

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.