www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik atmega8, 24-bit messwert an UART schicken


Autor: Sergej Dragunov (Firma: Keine) (2sergej)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo!
Ich weiss mal wieder nicht weiter!
Ich verwende nen ATMEGA8 (&AVR Studio), um via I²C zyklisch Messwerte
aus dem Kapazitätssensor AD7746 auszulesen. Die Messwerte haben eine
Breite von 24Bit und teilen sich zu jeweils 8Bit auf in:
cdc_HIGH
cdc_MIDD
cdc_LOW
Ich möchte diese Werte zum eigenlichen Ergebnis "zusammenkleben",
um dieses dann auf den UART zu schicken.
Wenn ich statt utoa() ltoa() verwende, kommen da tatsächlich zyklisch
Messwerte auf dem Terminalprog an. Die sind recht hoch und ändern
sich bei kontinuierlicher Änderung der Kapazität leider nur sprunghaft!
Verwende ich utoa() (wie unten), dann bekomme ich nur ein und denselben 
Messwert:
65535dec, also 16mal die "1".

Was habe ich falsch gemacht???
Vielen Dank im Voraus!

Autor: Freizeitbastler (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Um eine Fehlerquelle auszuschließen kannst Du ja statt <X>toa mal die 
drei einzelnen Bytes direkt oder in HEX verschicken. Evtl. stimmt ja was 
mit dem Sensor oder dem I2C nicht.

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Solltest du die ersten beiden Bytes nicht mit "i2c_readAck" abholen?

Schöner sieht's dann wohl so aus:
#include <stdint.h>
#include <stdlib.h>
//Mehr  #includes ...

#define AD7746_DATA_REG 1

int32_t get_adc_res(uint8_t adr)
{
    int32_t result;
    uint8_t buffer;
 
    i2c_start_wait(adr | I2C_WRITE);  // Set device address and write mode
    i2c_write(AD7746_DATA_REG);                  // CapData Register
    i2c_rep_start(adr | I2C_READ);    // Set device address and read mode  

    /* Reading Data from CapData Register using the register 
    address pointer auto-increment-feature of serial interface*/
    buffer = i2c_readAck();           // Read Data High-Byte
    result = buffer << 16L;
    buffer= i2c_readAck();            // Read Data Middle-Byte
    result |= buffer << 8L;
    buffer  = i2c_readNak();          // Read Data Low-Byte
    result |= buffer;
    i2c_stop();
    return result;
}

int main(void)
{
    int32_t adc_val;
    char str[16];
    while (1)
    {
        adc_val = get_adc_res(DEVICE_AD7746);
        ltoa(cdc_RESULT, str, 10);
        uart_putstring(s);
        uart_putstring("\n\r");
    }
    return 0;
}

hth. Jörg

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ups, Copy-&-paste fehler:
//...
int main(void)
{
    int32_t adc_val;
    char str[16];
    while (1)
    {
        adc_val = get_adc_res(DEVICE_AD7746);
        ltoa(adc_val, str, 10);
        uart_putstring(str);
        uart_putstring("\n\r");
    }
    return 0;
}
//...

Autor: Sergej Dragunov (Firma: Keine) (2sergej)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Super Jörg!
Das mit dem ACK/NACK hätte ich nie entdeckt!
Jetzt scheint alles zu laufen. Auch das sprunghafte
Verhalten ist weg.
Dein Code ist natürlich eleganter!
Vielen Dank!
Hast Du das aus dem Ärmel geschüttelt, oder durftest
Du Dich auch schon mit dem AD7746 befassen?

Habe ich das mit dem:
char s[32];
richtig gemacht??

@Freizeitbastler
Habe ich so ähnlich gemacht. Habe den Sensor direkt,
ohne µC ausgelesen. Die Messwerte habe ich von Hand
umgerechnet....da hat alles geklappt. Danke trotzdem!

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Vielen Dank!
Bitte

> Hast Du das aus dem Ärmel geschüttelt, oder durftest
> Du Dich auch schon mit dem AD7746 befassen?
So funktioniert I2C (mit dem AD7746 hatte ich noch nie zutun)

>Habe ich das mit dem:
>char s[32];
>richtig gemacht?
Der String muss nur groß genug sein ("-2147483648\0"-> Max. 12 Stellen 
bei int32_t; "16777216\0" -> 9, wenn es nur um das (unsigned) 
ADC-Ergebnis geht).

hth. Jörg

Autor: S. G. (goeck)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

ich möchte diesen fred mal kurz aufwecken, weil ich eine Frage habe, die 
genau hierher passt. Ich habe das gleiche Problem, möchte eine 24Bit 
Variable vom SPI Bus nehmen in drei 1Byte Schritten. Das funktioniert 
auch. Dann lege ich mir eine 32Bit Variable an, packe das HighByte 
hinein und shifte 12mal nach links. Genau dabei bekomme ich aber den 
gesamten höherwertigen Anteil in meiner 32 Bit Variable auf high 
gesetzt. Hexadezimal sieht das dann so aus, nach dem shiften von 0xAB: 
0xFFFF0000; Shifte ich nur 8mal: 0xFFFFAB00. Hier seht ihr meinen Code, 
ich finde nichts daran...Kann mir bitte jemand erklären, warum meine 
32Bit Variable sich entscheidet negativ, also voll besetzt mit Einsen zu 
sein, dort wo nichts hinüber geschiftet wird?
bool ADS1234_getData( int32_t *Data )
{
    uint8_t in1, in2, in3, dummy;
    int32_t adcData;

    //first off shift in 24 Bits (= 3x8 Bits)
    in1 = 0xab;//spi_putc(0xff);
    in2 = 0xcd;//spi_putc(0xff);
    in3 = 0xef;//spi_putc(0xff);

    /* now begin shifting everything to the left, 
     * then fill up lowest available byte with read bytes
     */
    adcData  = in1 << 16L;
    adcData |= in2 << 8L;
    adcData |= in3;
    
    *Data = adcData;
    return ( SPCR & (1<<SPE) );
}

Danke euch.
Grüße

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

Bewertung
0 lesenswert
nicht lesenswert
Probiers mal so:
bool ADS1234_getData( int32_t *Data )
{
    uint8_t in1, in2, in3, dummy;
    uint32_t adcData;

    //first off shift in 24 Bits (= 3x8 Bits)
    in1 = 0xab;//spi_putc(0xff);
    in2 = 0xcd;//spi_putc(0xff);
    in3 = 0xef;//spi_putc(0xff);

    /* now begin shifting everything to the left, 
     * then fill up lowest available byte with read bytes
     */
    adcData  = ((uint32_t)in1) << 16L;
    adcData |= ((uint32_t)in2) << 8L;
    adcData |= in3;
    
    *Data = adcData;
    return ( SPCR & (1<<SPE) );
}

Autor: S. G. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Karl Heinz,

habe es vorhin schon einmal so probiert, änderte nichts, war aber auch 
meine erste Idee. Hab schon Feierabend gemacht und werd's daher einfach 
morgen nochmal versuchen.

Grüße
Göck

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

Bewertung
0 lesenswert
nicht lesenswert
S. G. schrieb:
> Hallo Karl Heinz,
>
> habe es vorhin schon einmal so probiert, änderte nichts,

Muss es aber.

  in1 << 8L;

in1 ist ein uint8_t. Damit die Operation gemacht werden kann, wird der 
uint8_t implizit auf einen int hochgecastet.
Bitmässig passiert dieses

   0x00AB << 8L

gibt als Ergebnis  0xAB00

So weit so gut. Nur hat dieses Zwischenergebnis den Datentyp int, also: 
Vorzeichenbehaftet.
Wird das einem uint32_t zugewiesen, muss das Zwischenergebnis erstmal 
auf 32 Bit aufgeblasen werden. Und da 0xAB00 eine negative Zahl ist, ist 
die entsprechende 32-Bit Zahl (ebenfalls vorzeichenbehaftet) 0xFFFFAB00. 
Und erst dann gehst in den uint32_t rein.

Du musst den Compiler daran hindern, int als Zwischenstufe zu benutzen. 
AM einfachsten geht das wohl, wenn du von vorne herein in1 auf einen 
uint32_t castest. Dann gibt es keine Notwendigkeit mehr für einen 
Zwischen-int und die Gefahr von Overflows bei  in1 << 16 ist auch 
gebannt :-)

Autor: S. G. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Na wiegesagt, werde es morgen mal ausprobieren. Es verwirrt mich 
allerdings etwas, denn ich hab ja alle beteiligten Variablen als uint 
definiert. Nur die "originale" Variable soll int sein. Ich werde morgen 
sehn... :-)

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

Bewertung
0 lesenswert
nicht lesenswert
S. G. schrieb:
> Na wiegesagt, werde es morgen mal ausprobieren. Es verwirrt mich
> allerdings etwas, denn ich hab ja alle beteiligten Variablen als uint
> definiert.

Deine Verwirrung rührt daher, dass du eine Zwischenstufe übersehen hast.
Die resultiert aus einer C-Regel, die da grob gesagt lautet:

Gerechnet wird immer mindestens mit int. Datentypen kleiner als int, 
werden zunächst auf int hochgehoben.

Und so wird aus deinem uint8_t zum Zwecke des Linksschiebens 
zwischendurch ein int. Wohlgemerkt: ein int! kein uint!
Und mit diesem int ist dann ein Vorzeichen ins Spiel gekommen, welches 
im Endergebnis seine Spuren hinterlässt.

Autor: Hc Zimmerer (mizch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Du musst den Compiler daran hindern, int als Zwischenstufe zu benutzen.

Statt
   adcData  = ((uint32_t)in1) << 16L;
sollte auch
   adcData  = in1 << 16UL;
dasselbe bewirken (long fasst nicht den Wertebereich von unsigned long, 
also Promotion zum unsigned Typ).  Ob das im erzeugten Code tatsächlich 
einen Unterschied ergibt, ist für mich zweifelhaft.  Deshalb nur als 
kleine Anmerkung.

Autor: S. G. (goeck)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Morgen,

habe jetzt das Problem gelöst, bekomme 0xAB0000 aus der Verschiebung 
heraus - wie ich es erwartet hatte.
Das hier hat nicht funktioniert:
adcData  = in1 << 16UL;

Dieser Weg funktionierte bei mir, Danke nochmal Karl Heinz.
adcData  = ((uint32_t)in1) << 16L; 

Eine frage stellt sich mir noch, was hat es mit den "L" und "UL" 
Suffixen auf sich. Ich verstehe bspw. diesen Befehl
a = a << 2
 so, dass es sich um zweimaliges Linksschieben mit Abspeichern in a 
handelt. Das L oder UL sagt jetzt etwas über das implizite casten aus?

Danke und Grüße vom Rhein
Göck

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

Bewertung
0 lesenswert
nicht lesenswert
S. G. schrieb:

> handelt. Das L oder UL sagt jetzt etwas über das implizite casten aus?

Ja und Nein

Diese Suffixe legen einen Datentyp für die Zahlenkonstante fest.

  5     ist ein int
  5L    ist ein long
  5UL   ist ein unsigned long

zusammen mit der Regelung, dass die der Datentyp einer Operation und der 
Zahlenraum, in welchem die Operation durchgeführt wird, ausschliesslich 
von den beteiligten Operanden abhängt, sollte jetzt klar sein, was der 
Unterschied zwischen

     in1 << 16               und
     in1 << 16UL

ist. Das erste wird als int Operation abgehandelt, das zweite als 
unsigned long Operation. Und mitlerweile weißt du auch, dass eine 
int-Operation an dieser Stelle ein "falsches" Ergebnis bringt.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> sollte jetzt klar sein, was der
> Unterschied zwischen
>
>      in1 << 16               und
>      in1 << 16UL
>
> ist. Das erste wird als int Operation abgehandelt, das zweite als
> unsigned long Operation. Und mitlerweile weißt du auch, dass eine
> int-Operation an dieser Stelle ein "falsches" Ergebnis bringt.

Sowohl 16L als auch 16UL bringen hier keine Änderung, denn << ist einer 
der wenigen Operatoren, wo die Größe der beteiligten Operanden nicht 
aneinander angeglichen wird.

Autor: S. G. (goeck)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Aha, wo finde ich sowas dokumentiert, in der C Referenz?
Anscheinend wird dieses Verhalten aber nicht - wie du schon vermutet 
hattest, Hc - von WinAVR so umgesetzt. Unter "normalen" C sollte das 
aber wohl so funktionieren .?

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
S. G. schrieb:
> Aha, wo finde ich sowas dokumentiert, in der C Referenz?

Im C Standard.

S. G. schrieb:
> Unter "normalen" C sollte das aber wohl so funktionieren .?

Meinst du mit "das" dieses?
adcData  = in1 << 16UL;
Nein. Das funktioniert generell nur dann, wenn auf der Architektur ein 
int größer als 16 Bit ist (z.B. auf dem PC). Denn wie gesagt, das UL hat 
hier keinerlei Einfluss darauf, wie die Operation ausgeführt wird.

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

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:

> Sowohl 16L als auch 16UL bringen hier keine Änderung, denn << ist einer
> der wenigen Operatoren, wo die Größe der beteiligten Operanden *nicht*
> aneinander angeglichen wird.

Die weiß ich auch nie auswendig.
Drum halte ich mich da einfach immer an den linken Operanden und dann 
stimmts wieder für alle Operatoren :-)

Autor: S. G. (goeck)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ah, alles klar.

Na dann habt vielen Dank soweit. Ich bastel dann mal weiter ;-)

Grüße

Autor: S. G. (goeck)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So...

Ich bin am nächsten Problem angelangt, udn würde euch daher bitten mal 
auf Beitrag "Speicher voll durch Multiplikation" einen Blick zu werfen.

Danke euch!

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
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
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 bestätigst du, die Nutzungsbedingungen anzuerkennen.