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


von Sergej D. (Firma: Keine) (2sergej)


Angehängte Dateien:

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!

von Freizeitbastler (Gast)


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.

von Jörg X. (Gast)


Lesenswert?

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

Schöner sieht's dann wohl so aus:
1
#include <stdint.h>
2
#include <stdlib.h>
3
//Mehr  #includes ...
4
5
#define AD7746_DATA_REG 1
6
7
int32_t get_adc_res(uint8_t adr)
8
{
9
    int32_t result;
10
    uint8_t buffer;
11
 
12
    i2c_start_wait(adr | I2C_WRITE);  // Set device address and write mode
13
    i2c_write(AD7746_DATA_REG);                  // CapData Register
14
    i2c_rep_start(adr | I2C_READ);    // Set device address and read mode  
15
16
    /* Reading Data from CapData Register using the register 
17
    address pointer auto-increment-feature of serial interface*/
18
    buffer = i2c_readAck();           // Read Data High-Byte
19
    result = buffer << 16L;
20
    buffer= i2c_readAck();            // Read Data Middle-Byte
21
    result |= buffer << 8L;
22
    buffer  = i2c_readNak();          // Read Data Low-Byte
23
    result |= buffer;
24
    i2c_stop();
25
    return result;
26
}
27
28
int main(void)
29
{
30
    int32_t adc_val;
31
    char str[16];
32
    while (1)
33
    {
34
        adc_val = get_adc_res(DEVICE_AD7746);
35
        ltoa(cdc_RESULT, str, 10);
36
        uart_putstring(s);
37
        uart_putstring("\n\r");
38
    }
39
    return 0;
40
}

hth. Jörg

von Jörg X. (Gast)


Lesenswert?

ups, Copy-&-paste fehler:
1
//...
2
int main(void)
3
{
4
    int32_t adc_val;
5
    char str[16];
6
    while (1)
7
    {
8
        adc_val = get_adc_res(DEVICE_AD7746);
9
        ltoa(adc_val, str, 10);
10
        uart_putstring(str);
11
        uart_putstring("\n\r");
12
    }
13
    return 0;
14
}
15
//...

von Sergej D. (Firma: Keine) (2sergej)


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!

von Jörg X. (Gast)


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

von S. G. (goeck)


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?
1
bool ADS1234_getData( int32_t *Data )
2
{
3
    uint8_t in1, in2, in3, dummy;
4
    int32_t adcData;
5
6
    //first off shift in 24 Bits (= 3x8 Bits)
7
    in1 = 0xab;//spi_putc(0xff);
8
    in2 = 0xcd;//spi_putc(0xff);
9
    in3 = 0xef;//spi_putc(0xff);
10
11
    /* now begin shifting everything to the left, 
12
     * then fill up lowest available byte with read bytes
13
     */
14
    adcData  = in1 << 16L;
15
    adcData |= in2 << 8L;
16
    adcData |= in3;
17
    
18
    *Data = adcData;
19
    return ( SPCR & (1<<SPE) );
20
}

Danke euch.
Grüße

von Karl H. (kbuchegg)


Lesenswert?

Probiers mal so:
1
bool ADS1234_getData( int32_t *Data )
2
{
3
    uint8_t in1, in2, in3, dummy;
4
    uint32_t adcData;
5
6
    //first off shift in 24 Bits (= 3x8 Bits)
7
    in1 = 0xab;//spi_putc(0xff);
8
    in2 = 0xcd;//spi_putc(0xff);
9
    in3 = 0xef;//spi_putc(0xff);
10
11
    /* now begin shifting everything to the left, 
12
     * then fill up lowest available byte with read bytes
13
     */
14
    adcData  = ((uint32_t)in1) << 16L;
15
    adcData |= ((uint32_t)in2) << 8L;
16
    adcData |= in3;
17
    
18
    *Data = adcData;
19
    return ( SPCR & (1<<SPE) );
20
}

von S. G. (Gast)


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

von Karl H. (kbuchegg)


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 :-)

von S. G. (Gast)


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... :-)

von Karl H. (kbuchegg)


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.

von Hc Z. (mizch)


Lesenswert?

> Du musst den Compiler daran hindern, int als Zwischenstufe zu benutzen.

Statt
1
   adcData  = ((uint32_t)in1) << 16L;
sollte auch
1
   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.

von S. G. (goeck)


Lesenswert?

Morgen,

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

Dieser Weg funktionierte bei mir, Danke nochmal Karl Heinz.
1
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
1
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

von Karl H. (kbuchegg)


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.

von Stefan E. (sternst)


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.

von S. G. (goeck)


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 .?

von Stefan E. (sternst)


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?
1
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.

von Karl H. (kbuchegg)


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 :-)

von S. G. (goeck)


Lesenswert?

Ah, alles klar.

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

Grüße

von S. G. (goeck)


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!

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.