ver-ODERn läuft schief

Hallo,

ich lese mit SPI einen 24Bit Wert ein. Die 3 Einzel-Bytes stimmen.
Schiebe ich diese mittels ODER in ein unsigned long an passende Stelle, werden mir die höchsten freien Bits mit 1 aufgefüllt. Warum das denn? Muß doch 0 bleiben.

Lese ich die 3 Einzel Bytes nicht als Byte sondern als unsigned long ein, stimmt alles.

Code zur Verdeutlichung was ich meine.

Die Einzelbyte sind zum Bsp. so
1101 11010100 10100011

/*
Arduino Mega2560
*/
#include <SPI.h>

#define CS   38  // bring CS high for ADC sleep mode, low to start new conversion
#define MOSI 51  // MOSI is not used for this device
#define MISO 50  // status and data bits from ADC
#define SCK  52  // SPI clock from Arduino to ADC

unsigned long Messwert;

void setup()
{
  Serial.begin(57600);
  
  SPI.begin();
   
  // Chip-Select auf HIGH
  pinMode(27, OUTPUT);     // EA DOGM
  pinMode(28, OUTPUT);     // SD-Karte
  pinMode(29, OUTPUT);     // MAX6675
  digitalWrite(27, HIGH);  // EA DOGM
  digitalWrite(28, HIGH);  // SD-Karte
  digitalWrite(29, HIGH);  // MAX6675
      
  pinMode(53, OUTPUT);     // change this to 53 on a Mega, normal 10
    
  pinMode(CS, OUTPUT);     // MCP3550-50
  digitalWrite(CS, HIGH);  // MCP3550-50
  pinMode(MOSI, OUTPUT);   // MOSI (data out from Arduino)
  pinMode(MISO, INPUT);    // MISO (data in to Arduino)
  pinMode(SCK, OUTPUT);    // SCK  (serial clock)
  
  //SPI.setClockDivider(SPI_CLOCK_DIV32);  // SPI clock rate < 5 MHz per MCP3550 spec
  //SPI.setBitOrder(MSBFIRST);		   // MSB first
  SPI.setDataMode(SPI_MODE3);		   // rising/falling edge of clock
  
  delay(1);  // Zwangspause für MCP3550-50 Startup/Reset (normal 10µs)
     
}

void loop()
{
 
  Serial.println("NEU: ");
  Messwert = MCP3550();
  Serial.println(Messwert, BIN);
  Serial.println(Messwert);
  Serial.println();
  
  delay(2000);
}

unsigned long MCP3550 ()
{
  unsigned long value = 0;
  digitalWrite(CS, LOW);
  delay(90);                             
  digitalWrite(CS, HIGH);
  delay(90);                             
  digitalWrite(CS, LOW);
  delay(90);                             
  unsigned long Data1 = SPI.transfer(0);           // Byte einlesen, D24 ... D16
  unsigned long Data2 = SPI.transfer(0);           // Byte einlesen, D15 ... D 8
  unsigned long Data3 = SPI.transfer(0);           // Byte einlesen, D 7 ... D 0
  digitalWrite(CS, HIGH);
  Serial.print(Data1, BIN); Serial.print(" "); Serial.print(Data2, BIN); Serial.print(" "); Serial.println(Data3, BIN);
  value = (Data1 << 16) | (Data2 << 8) | Data3;
  return value;
}

Wenn die 3 SPI Bytes als Byte einlese, werden die höchsten freien Bits von value mit Einsen aufgefüllt. Die müßte doch aber 0 bleiben, weil ja value immer neu genullt wird.

In einer anderen Funktion von mir mach ich das nach dem gleichen Schema und es funktioniert.

float MAX_6675(int CS)
{
  digitalWrite(CS, LOW);
  NOP;                                  // delay 62.5ns on a 16MHz AtMega, 100ns erforderlich
  NOP;
  byte MSB = SPI.transfer(0);           // höherwertiges Byte einlesen, D15 ... D8
  byte LSB = SPI.transfer(0);           // niederwertiges Byte einlesen, D7 ... D0
  digitalWrite(CS, HIGH);
  unsigned int KTemp = MSB << 8;   
  KTemp = KTemp | LSB;
  KTemp = KTemp >> 3;
  //float Celius = KTemp * 0.25;        // umgerechnet wird in loop mit Mittelwertbildung
  //return Celius;
  return KTemp;
}

Gut beobachtet!

byte MSB = 0x88;
byte LSB = 0x88;
unsigned int  _16bits = MSB << 8 | LSB;
unsigned long _32bits = MSB<< 8 | LSB;
Serial.println (_16bits, HEX);
Serial.println(_32bits, HEX);

Der Ausdruck (MSB<<8 | LSB) wird im Standard-Datenformat int gerechnet und das ergibt 0x8888 = -30584
-30584 ist aber FFFF8888, als (unsigned oder nicht) long interpretiert.

Bei unsigned int ( dein 2. Fall) merkst du es nur nicht, dass das das oberste Bit schon das Vorzeichen ist.

Hallo,

oh, vielen Dank, ist ja ganz schön Fehlerträchtig wenn man das nicht weis.

byte MSB = 0x88;
byte LSB = 0x88;
int  _16bits = MSB << 8 | LSB;
long _32bits = MSB << 8 | LSB;
Serial.println (_16bits, BIN); Serial.println (_16bits, HEX); Serial.println (_16bits);
Serial.println (_32bits, BIN); Serial.println (_32bits, HEX); Serial.println (_32bits);

ergibt:
11111111111111111000100010001000
FFFF8888
-30584
11111111111111111000100010001000
FFFF8888
-30584

und

byte MSB = 0x88;
byte LSB = 0x88;
unsigned int  _16bits = MSB << 8 | LSB;
unsigned long _32bits = MSB << 8 | LSB;
Serial.println (_16bits, BIN); Serial.println (_16bits, HEX); Serial.println (_16bits);
Serial.println (_32bits, BIN); Serial.println (_32bits, HEX); Serial.println (_32bits);

ergibt:
1000100010001000
8888
34952
11111111111111111000100010001000
FFFF8888
4294936712

Zwischen long und unsigned long lauert ja eine Böse Falle. Im HEX Format scheint es gleich zu sein. In BIN und DEZ jedoch nicht. Das ist das versteckte Vorzeichen was Du meinst?

unsigned int MSB = 0x88;
unsigned int LSB = 0x88;
unsigned int  _16bits = MSB << 8 | LSB;
unsigned long _32bits = MSB << 8 | LSB;
Serial.println (_16bits, BIN); Serial.println (_16bits, HEX); Serial.println (_16bits);
Serial.println (_32bits, BIN); Serial.println (_32bits, HEX); Serial.println (_32bits);

ergibt:
1000100010001000
8888
34952
1000100010001000
8888
34952

Demnach würde es ausreichen wenn ich MSB/LSB oder was auch immer beim auslesen mit unsigned int deklariere?

Demnach würde es ausreichen wenn ich MSB/LSB oder was auch immer beim auslesen mit unsigned int deklariere?

Ja, eine Möglichkeit. Ich mag union, mit dem man einen Speicherbereich wahlweise als Bytes oder als andere Datentypen sehen/benutzen kann.

Aber erstmal egal wie : Eine böse Falle, insbesondere da man ziemlich leicht an die Grenzen des int Bereichs kommt.

Gefahr erkannt, Gefahr gebannt.

Hallo,

union, guck ich mir mal an. Spart man durch SRAM ein?
Übrigens muß ich die “SPI Bytes” als unsigned long deklarieren. Nicht als unsigned int. Sorry. Sonst hab ich immer noch falsche Werte.

Meine Funktion funktioniert nun auch soweit mit ordentlichen Timings. Wer sich dafür interessiert, bitte schön. Das mit der Erkennung vom Vorzeichen muß noch rein und vielleicht noch die feste Wartezeit von 80ms durch millis Abfrage ersetzen. Die Basis steht jedenfalls. :wink:

22Bit A/D Wandler IC, MCP3550-50

/*
Arduino Mega2560
*/

// NOP;  // delay 62.5ns on a 16MHz AtMega
#define NOP __asm__ __volatile__ ("nop\n\t")

#include <SPI.h>

#define CS   38  // bring CS high for ADC sleep mode, low to start new conversion
#define MOSI 51  // MOSI is not used for this device
#define MISO 50  // status and data bits from ADC
#define SCK  52  // SPI clock from Arduino to ADC

unsigned long Messwert;

void setup()
{
  Serial.begin(57600);
  
  SPI.begin();
  pinMode(53, OUTPUT);     // change this to 53 on a Mega, normal 10 
   
  pinMode(CS, OUTPUT);     // MCP3550-50
  digitalWrite(CS, HIGH);  // MCP3550-50
  pinMode(MOSI, OUTPUT);   // MOSI (data out from Arduino)
  pinMode(MISO, INPUT);    // MISO (data in to Arduino)
  pinMode(SCK, OUTPUT);    // SCK  (serial clock)
  
  //SPI.setClockDivider(SPI_CLOCK_DIV32);  // SPI clock rate < 5 MHz per MCP3550 spec
  //SPI.setBitOrder(MSBFIRST);		   // MSB first
  SPI.setDataMode(SPI_MODE3);		   // rising/falling edge of clock
  
  delay(1);  // Zwangspause für MCP3550-50 PowerUP (normal 10µs)
     
}

void loop()
{
 
  Serial.println("NEU: ");
  Messwert = MCP3550();
  Serial.println(Messwert, BIN);
  Serial.println(Messwert);
  Serial.println();
  
  delay(2000);
}


unsigned long MCP3550 ()  
{
  unsigned long value = 0;      // zugleich 50ns Zwangspause CS High to Low
  digitalWrite(CS, LOW);        // starte AD Convertion
  delay(80);                    // 80ms typische Conversion Time (MCP3550-50)
  while(digitalRead(MISO) == HIGH)       // warten bis SDO/RDY mit LOW Ready signalisiert
    {
     digitalWrite(CS, HIGH);             // wenn nicht Ready, toggle CS erneut High zu Low
     delay(1);  
     digitalWrite(CS, LOW); 
    }  
  NOP;                                      // 20ns Zwangspause
  unsigned long Data1 = SPI.transfer(0);    // Byte einlesen, D24 ... D16
  unsigned long Data2 = SPI.transfer(0);    // Byte einlesen, D15 ... D 8
  unsigned long Data3 = SPI.transfer(0);    // Byte einlesen, D 7 ... D 0
  digitalWrite(CS, HIGH);
  Serial.print(Data1, BIN); Serial.print(" "); Serial.print(Data2, BIN); Serial.print(" "); Serial.println(Data3, BIN);
  value = (Data1 << 16) | (Data2 << 8) | Data3;
  return value;
}

union, guck ich mir mal an. Spart man durch SRAM ein?

Nein, sparen tust du nichts. Dein Programm wird nur klarer verständlich.

union {unsigned long l; byte b[4]} data;
data.b[0] = 0x88 ; // 0 .. 7
data.b[1] = 0xAA;  // 8.. 15
data.b[2] = 0x01; // 16 .. 23
data.b[3] = 0;   
Serial.println (data.l, HEX); // liefert  1AA88

Du brauchst keinen temporären Zeiger zum casten wie bei

byte b[4] = {0x88, 0xAA, 0x01, 0};
unsigned long *lp = (unsigned long*) b;  // Eine zusätzliche Variable, wird evtl aber auch wegoptimiert.
Serial.println (*lp,  HEX); // liefert  1AA88

casts sind immer ein klein bisschen gemogelt, weil du dem Compiler das Überprüfen auf Korrektheit vermasselst. Aber in beiden Fällen vermeidest du die fehleranfällige Rechnerei mit int. Die passende Datenstruktur zu verwenden ist immer besser als Bitschiebereien, wenn man's vermeiden kann.

Hallo,

so, leider ist das nicht so logisch wie ich dachte bzw. wie es aussieht. Zumindestens für mich. Die Reihenfolge beim zusammensetzen wird umgedreht. Bei einem Array ist Index 0 ganz links das erste und die höchste Indexnummer ganz rechts, die letzte. Nun dachte ich genau wie beim Array werden die 4 Einzelbytes in ein long zusammengesetzt. Jetzt ist aber bei union die Indexreihenfolge oder wie man das nennt vertauscht. Also wieder in Binärlogik. MSR links, LSB rechts. Wieder ganz schön tricky. ;)

Ist das so richtig? Es muß sichergestellt sein dass vom AD-IC das MSR wirklich links eingesetzt wird und das LSB rechts in der long Variable. Sollte eigentlich passen, bin mir aber nicht mehr sicher nachdem durcheinander. ;)

unsigned long MCP3550 ()  
{
  unsigned long value = 0;      // zugleich 50ns Zwangspause CS High to Low
  digitalWrite(CS, LOW);        // starte AD Convertion
  delay(80);                    // 80ms typische Conversion Time (MCP3550-50)
  while(digitalRead(MISO) == HIGH)       // warten bis SDO/RDY mit LOW Ready signalisiert
    {
     digitalWrite(CS, HIGH);             // wenn nicht Ready, toggle CS erneut High zu Low
     delay(1);  
     digitalWrite(CS, LOW); 
    }  
  NOP;                            // 20ns Zwangspause
  union {
         unsigned long ADraw;
         byte b[4];
        } data;  
  data.b[3] = 0;                  // Byte mit Nullen füllen, D32 ...D25 nicht vorhanden
  data.b[2] = SPI.transfer(0);    // Byte einlesen, D24 ... D16
  data.b[1] = SPI.transfer(0);    // Byte einlesen, D15 ... D 8
  data.b[0] = SPI.transfer(0);    // Byte einlesen, D 7 ... D 0
  
  digitalWrite(CS, HIGH);
  Serial.print(data.b[3], BIN); Serial.print(" "); Serial.print(data.b[2], BIN); Serial.print(" "); Serial.print(data.b[1], BIN);Serial.print(" "); Serial.println(data.b[0], BIN);
  return data.ADraw;
}

Kann man auch die Variable ADraw immer bei der Definition Nullen und muß dann nicht die höchste Stelle mit data.b[3] = 0; extra Nullen? Sollte doch vom Ergebnis gleich sein? Sollte auch sicherer sein in Unterfunktionen wie ich vor einer Weile erst hier gelernt habe.

union {
         unsigned long ADraw = 0;
         byte b[4];
        } data;                   // data ist der union Name
  data.b[2] = SPI.transfer(0);    // Byte einlesen, D24 ... D16
  data.b[1] = SPI.transfer(0);    // Byte einlesen, D15 ... D 8
  data.b[0] = SPI.transfer(0);    // Byte einlesen, D 7 ... D 0

Wenn die union {...} data; eine globale Variable ist, wird sie schon nach jedem Reset auf 0 gesetzt.

Kann man auch die Variable ADraw immer bei der Definition Nullen und muß dann nicht die höchste Stelle mit data.b[3] = 0; extra Nullen?

Bei lokalen Variablen wie in deinem Fall, musst du das machen, richtig, Ob du das während der Definition machst oder später, ist kein Unterschied. Ausser dass es etwas weniger Arbeit ist, ein Byte auf 0 zu setzen als ein long.

Hallo,

okay. Danke für die vielen hilfreichen Erklärungen. Wieder was gelernt. :smiley:

Hey,

danke für das ausführliche Programm,habe in den letzten Tagen dadurch sehr viel gelernt. Als Arduino und C++-Neuling habe ich manche Teile aber nicht ganz verstanden. Konkret heißt das:

-Wiso labelst du Pin38 als CS?,ist der Mega nicht an Pin 53 (laut Datenblatt) gebunden?

Der Part, in welchem der MCP3550 als long deklariert wird:

-Was passiert mit der variable "value", die den Wert Null erhält? Sehe ich das nur nicht, oder wird darauf tatsächlich nicht mehr zurückgegriffen?

-Wenn "MISO == HIGH" gelten soll, muss der Pin dann nicht entsprechend angesteuert werden?,oder werden Input Pins generell durch angeklemmte Sensoren auf High gesetzt?

Arbeite erst seit Anfang des Monats mit Arduino und muss gerade festellen wie schnell das Ganze an Komplexität zulegt, je mehr man damit versucht.

Hallo,

Wiso labelst du Pin38 als CS?,ist der Mega nicht an Pin 53 (laut Datenblatt) gebunden?

Wenn man nur ein einziges SPI Gerät am SPI Bus hat, kann man den Pin53 verwenden. Aber jedes SPI Gerät benötigt seinen eigenen Chip-Select Pin. Sonst wird das nichts. Sonst reden alle durcheinander auf der Leitung. Aber egal ob man Pin53 nutzt oder nicht, der muß unbedingt als Ausgang definiert sein. Wenn man das Hardware SPI nutzt. Wenn man kein Hardware SPI nutzt, kann man ihn für sonstwas verwenden.

Der Part, in welchem der MCP3550 als long deklariert wird: Was passiert mit der variable "value", die den Wert Null erhält? Sehe ich das nur nicht, oder wird darauf tatsächlich nicht mehr zurückgegriffen?

Das übersiehst Du. ;) Das ist in der eigentlichen Funktion. In der Variablen value landet am Ende der reine ausgelesene Rohwert vom SPI Gerät. Immer beim Aufruf der Funktion wird diese vorbeugend genullt. Es werden 3 einzelne Bytes vom SPI Bus gelesen. Danach wird jedes Byte an Ort und Stelle gerückt wo es hingehört und mit ODER zusammengesetzt und das ganze Gebilde wird in value gespeichert und dann mit return zurück an die aufrufende Funktion in loop geliefert.

Wenn "MISO == HIGH" gelten soll, muss der Pin dann nicht entsprechend angesteuert werden?,oder werden Input Pins generell durch angeklemmte Sensoren auf High gesetzt?

Du meinst while(digitalRead(MISO) == HIGH) .... MISO wird am Anfang als Input definiert. Mit digitalRead lese ich den Zustand ein. Einen Eingang kann man nicht mit High oder Low ansteuern. Es ist ein Eingang und kein Ausgang. Beim Eingang bin ich abhängig was mir irgendwer, ob ein Taster oder hier das IC, für ein Signal liefert. Das muß man abfragen und entsprechend darauf reagieren. Solange das Signal in dem Fall High ist, wird eine extra 1ms lang gewartet. Bei Low gehts weiter mit der Code Abarbeitung. Vielleicht übersiehst Du etwas anderes. Der Operator == ist nicht das gleiche wie =. Mit = übergibt man einen Wert zum Bsp. einer Variablen. x = 10; Die Variabel x hat nun den Wert 10 gespeichert. Mit x == 10; vergleiche ich ob die Variable den Wert 10 hat. Das ist ein großer Unterschied. Aber keine Sorge, damit hat wohl jeder Probleme gehabt. Später weis man das einfach wenn man sowas sieht. Beim Code schreiben kommt es jedoch immer wieder mal vor das man statt == nur = tippt und sich wundert das der Code nicht funktioniert. Dann kommen unzählige Serial.print() Befehle rein zum debuggen. :D

Arbeite erst seit Anfang des Monats mit Arduino und muss gerade festellen wie schnell das Ganze an Komplexität zulegt, je mehr man damit versucht.

Das ging mir genauso. Aber das wird mit der Zeit. Manchmal eine Pause einlegen tut auch gut um wieder das große Ganze zu sehen. ;) Nach ca 1 Jahr fange ich nun an eigene Funktionen zu schreiben. Hätte ich mir auch nie träumen lassen. Der nächste Plan ist die Funktion in eine Library zu verpacken. Mal sehen ob das klappt. Übrigens findest Du die aktuelle Funktion zum MCP3550 hier: http://forum.arduino.cc/index.php?topic=242576.new;topicseen#new Man muß nur die Spannung an AREF dem Funktionsaufruf mit übergeben in mV. Falls man einen schnelleren MCP3550... hat, kann man in der Funktion das delay(80) der Convertion Time entsprechend verkürzen. Diese Zwangspause möchte ich auch noch wegbekommen. Mit der Funktion werden nun postive und negative Spannungswerte ausgegeben. Kommt ja darauf an was zwischen Vin+ und Vin- für eine Differenz anliegt.

Alles klar?

Wow schnelle und detailierte Antwort. Dankeschön!

Nach ca 1 Jahr fange ich nun an eigene Funktionen zu schreiben.

ô_Ô Dann halt ich mich als Anfänger besser erst mal daran Funktionen für meine Sensoren umzuarbeiten :sweat_smile:

( zum BMA020 http://www.elv-downloads.de/Assets/Produkte/9/915/91521/Downloads/91521_bma020_data.pdf gibts hier bisher nur I2C-Ansätze)

Grüße, Christoph

Hallo,

jeder fängt mit dem an was einem einfacher erscheint. bzw. einfacher ist. :wink:
Eine fertige Funktion für sich anzupassen ist auch nicht einfach. Man muß das verwendete IC und seines kennen, die Unterschiede feststellen und dann ändern. Man kann dabei viel übersehen. Ich wünsche Dir auf jeden Fall maximale Erfolge.