32Bit-Zahl als Bit-Array / --> Einlesen eines Absolutwertgebers

Hi,
bräuchte mal wieder Hilfe:

Ich bekomme eine 32 Bit Zahl über einen Eingang auf den Arduino Bit für Bit übertragen.
Jetzt stell ich mir die Frage, wie ich das am elegantesten umrechne.
Mir ist bis jetzt nur die Möglichkeit eingefallen, die 32 Bit in ein "bool myarray[32]" abzuspeichern und dann händisch durchzurechnen: myarray[1]*2^0+myarray[2]*2^1+myarray[3]*2^2+......

Gibts da nicht eine andere Möglichkeit? Irgendwie die Bits "durchzuschieben"? Bitshift hab ich mir mal angeschaut, aber wie bring ich das neue Bit dann in die Zahl rein?

Habt ihr da eine Idee?

Gruß/hk007

hk007:
Habt ihr da eine Idee?

http://arduino.cc/en/Reference/BitWrite

Hey,
das hört sich gut an. Werds mal testen.
Sind die Befehle neu? Die hab ich noch nie gesehen/gebraucht. :blush:

Nein, nicht so neu, kenne sie seit mindestens 1/2 Jahr.
grüße Uwe

hk007:
Sind die Befehle neu? Die hab ich noch nie gesehen/gebraucht. :blush:

Wenn Du auf der verlinkte Referenzseite "Page History" anklickst, kannst Du sehen, dass die Dokumentation der Bit-Manipulationsbefehle im Jahr 2008 angelegt wurde.

Wenn Du die einzelnen Bits nacheinander (seriell) bekommst, kannst Du auch tatsächlich Bits schieben.
Kleines Beispiel:

int bits[20] = {1,0,1,1,0,0,1,0,0,0,1,1,1,0,1,0,0,1,1,1};

void setup() {
  Serial.begin(9600);
  long data = 0;
  for (int i = 0; i< 20; i++) 
    //alle Bits eins weiter schieben und aktuelles Bit dazu addieren
    data = ( data << 1 ) + bits[i];
  Serial.println(data,BIN);
}

void loop() {}

Hi,

also das mit den bitWrite klappt ganz prima :slight_smile:
zu meiner Geschichte hätte ich jetzt noch zwei Zusatzfragen:
1.)
Gibts irgendwo eine Aufstellung, wie viel Laufzeit ein Befehl hat?
Hintergrund:
Ich hab den Befehl in einer Interruptroutine:

long distance = 0;
int ctr;
const int data = 3;
.
.
.
void Takt() {
  newdata = HIGH;
  bitWrite(distance, ctr, !digitalRead(data)); 
  ctr=ctr+1;
}

Da würd mich jetzt interessieren, ob ich berechnen kann, wie lange die dauert. OK, ich könnte am Anfang und am Ende die micros() abfragen. Aber ob das so genau ist?
Wenn die Routine länger als 0,4 ms dauert, dann hab ich nämlich ein Timing-Problem.

2.)
Ich hab Anfangs von einer 32 Bit Zahl geschrieben.
Kann man eigentlich auch Zahlenformate selber definieren? Mir würde nämlich für "distance" ein signed Format mit 20Bit + Vorzeichen für den Anwendungsfall genau passen.

Kann man eigentlich auch Zahlenformate selber definieren? Mir würde nämlich für "distance" ein signed Format mit 20Bit + Vorzeichen für den Anwendungsfall genau passen.

Im Prinzip schon:

struct Distance 
{
 signed value:20;
 unsigned sign:1;
};

Distance d;
...
  d.value = 0xFFFFF;
  d.sign = 1;

aber ob die Variable d tatsächlich nur 3 byte belegt statt 4, bliebe zu prüfen. Auch Wertebereichsüberschreitungen werden weder beim Compilieren noch zur Laufzeit geprüft, fürchte ich.

Gibts irgendwo eine Aufstellung, wie viel Laufzeit ein Befehl hat?

Ja, für die Maschinenbefehle ( Assembler )
" Die meisten Befehle brauchen nur 1 Zyklus ", meine ich irgendwo gelesen zu haben, aber genauer weiss ich nicht. Tip: Datenblatt .
Für unsereins ist eher interessant, wie gut der Compiler arbeitet, bzw. wie man ihn unterstützen kann.

Dein Takt() braucht sicher keine 400 µs, könnte aber durch ein optimiertes bitWrite evtl. noch verbessert werden:

#define data 3

long bit = 0x00001;
long distance = 0;
...
void Takt() {
 newdata = HIGH; 
 if ( !digitalRead(data)) distance |= bit;
 bit <<= 1; 
}

Ob es da noch wirklich was bringt, statt mit zwei long mit einem von 3 Bytes und einer 1 byte Bitmaske zu arbeiten, weiss ich nicht.
digitalRead mit einer konstanten Pin-Nummer sollte schon ziemlich per Macros optimiert sein.

...ich könnte am Anfang und am Ende die micros() abfragen. Aber ob das so genau ist?

Sagt dir ziemlich genau, wie lang 2 mal micros() abfragen dauert, mit deinem bitWrite() wird es etwas länger :wink:

Deine ganze Frage klingt übrigens ziemlich nach shiftIn().
(Wodurch ändert sich denn der Wert des data - Pins ? Da ist doch sicher irgendwo ein Clock Puls...)
Probierst du gerade, das Rad neu zu erfinden? (Das soll keine Kritik sein, nur sollte man vor dem Neu-Erfinden wissen, was es schon gibt)

Hi michael_x

das ist viel Input. Das muss ich erst mal sacken lassen.

(Wodurch ändert sich denn der Wert des data - Pins ? Da ist doch sicher irgendwo ein Clock Puls...)
Probierst du gerade, das Rad neu zu erfinden? (Das soll keine Kritik sein, nur sollte man vor dem Neu-Erfinden wissen, was es schon gibt)

Gott beware. Ich will sicher nichts neu erfinden, was es bereits gibt. Bin aus angeborener Faulheit immer froh, wenn ich was vorhandenes verwenden kann.

Du hast richtig erkannt: Ich habe zwei Eingänge. eine Clk- und einen Data-Impuls. Um genauer zu sein: Ich lese will eine Absoluwertgeber einlesen. Bei jeder positiven Flanke des CLK wird der Data-Eingang gelesen.
Das "Telegramm" ist ca. 10ms lang. In dem "Telegramm" bekomme ich ein 24 Bit Datenpaket.

shiftin() kann (so viel ich aus der Reference gelesen habe) nur ein Byte??

Ja, für die Maschinenbefehle ( Assembler )
" Die meisten Befehle brauchen nur 1 Zyklus ", meine ich irgendwo gelesen zu haben, aber genauer weiss ich nicht. Tip: Datenblatt .
Für unsereins ist eher interessant, wie gut der Compiler arbeitet, bzw. wie man ihn unterstützen kann.

Hab einige "Tests" gemacht:
Das Unterprogramm in der loop 50 mal aufgerufen. Am Ende der loop die Zeit mit millis() gemessen. Dann das Unterprogramm 100x aufgerufen. Den Zeitunterschied berechnet.... durch 50 geteilt....
--> Meine Unterprogramm "Takt ()" dauert ~21µs.(Beim Schreiben auf Bit"31" des "Long". Seltsam: Beim Schreiben auf Bit "0" ist es ~1,6µs schneller.

Das mit der Frage nach den 3Byte Datenformat war nur deshalb, weil dann das Vorzeichenbit automatisch an die richtigen Stelle gekommen wäre.

long bit = 0x00001;
long distance = 0;
...
void Takt() {
newdata = HIGH;
if ( !digitalRead(data)) distance |= bit;
bit <<= 1;
}

Sorry: das hab ich leider überhaupt nicht verstanden. Dazu fehlts mir wohl an C-Grundlagen.

das hab ich leider überhaupt nicht verstanden. Dazu fehlts mir wohl an C-Grundlagen.

Sorry, ja vermutlich:

distance |= bit; ist eine Kurzschreibweise für distance = distance | bit; // setze ein bit in distance
bit <<= 1; ist eine Kurzschreibweise für   bit  = bit << 1; // schiebe bit eins nach links

Das geht mit anderen Operatoren ( + , * , / usw.) auch, und wird oft direkt von der Hardware unterstützt.

(
Allerdings wird, wenn's die Hardware unterstützt, das wohl auch vom Compiler optimiert, so dass z.B.
bit = bit << 1; weniger Befehle sind als bit2 = bit1 << 1;
)

shiftin() kann (so viel ich aus der Reference gelesen habe) nur ein Byte??

Ja, das stimmt. Mit 3 shiftIn Befehlen kriegst du 24 bit.

Wenn du ein C - Experte werden willst, schau dir "union" an. Damit kannst du eine Speicherstelle ( Variable ) wahlweise als 3 bytes oder als 24 bit interpretieren. ( Oder 20 bit in deinem Fall )

distance |= bit; ist eine Kurzschreibweise für distance = distance | bit; // setze ein bit in distance
bit <<= 1; ist eine Kurzschreibweise für bit = bit << 1; // schiebe bit eins nach links

Das geht mit anderen Operatoren ( + , * , / usw.) auch, und wird oft direkt von der Hardware unterstützt.

Oh ja, mit "+" kenn ich das auch. aber "|=" war mir etwas zu kryptisch.

Ja, das stimmt. Mit 3 shiftIn Befehlen kriegst du 24 bit.

Hmm.... aber dann muss ich mich irgendwie drum kümmern, daß ich dann ins richtige der 3 Bytes schreibe, und daß ich die 3 Bytes am Ende dann zusammenfüge.
Oder denk ich da falsch?

Ich poste jetzt mal meinen Code. Ist vllt. etwas hölzern, aber geht.
Allerdings erkennt er nicht jedes Datenpaket :frowning: Manchmal verwirft er bis zu 4 Pakete, bis er eins dekodiert. Darum dachte ich zunächst, daß die Int-Routine "Takt()" zeitlich an der Grenze ist.
Das mit den millis() hab ich eingebaut , damit ich erkenne wann ein neues Paket startet. Wenn seit 20ms kein Takt mehr da ist, weiss ich daß ich in den 120ms Pause bin, und bei der nächsten Flanke ein neues Paket beginnt.

// Takteingang: PIN2
// Dateneingang: PIN3
// Länge Paket: 24Bit in ca. 10ms
// Anschliessend ca. 120ms Pause
// Das erste Bit des Datenpakets wird verworfen

const int data = 3;
boolean newdata = LOW;
int ctr;
unsigned long time;
long distance = 0;


void setup(){
  Serial.begin (57600);
  pinMode(data, INPUT);
  attachInterrupt(0, Takt, RISING);   //(UNO: Int0 = PIN 2)
}

void loop(){
  if (newdata) {
    time = millis();
  }
  newdata = LOW;
  
  if ((millis() - time) >= 20) {
    if (ctr >= 24) {
     distance = distance >>1;
     Serial.println (distance);
    } 
    ctr = 0;
  }
}

void Takt() {
  newdata = HIGH;
  bitWrite(distance, ctr, !digitalRead(data)); 
  ctr=ctr+1;
}

Manchmal verwirft er bis zu 4 Pakete

"verwerfen" bedeutet, dass länger als 20 ms nichts mehr gekommen ist, aber ctr nicht bis 24 gezählt hatte, richtig?

Was passiert, wenn du statt
if (ctr >= 24) { ... zum Test if (ctr > 0) { ... nimmst und mitschreibst, wieviele Pulse gekommen sind ?

Wenn der Clock - Puls nur zu lesen ist, ist shiftIn() übrigens nicht passend.

michael_x:

Manchmal verwirft er bis zu 4 Pakete

"verwerfen" bedeutet, dass länger als 20 ms nichts mehr gekommen ist, aber ctr nicht bis 24 gezählt hatte, richtig?

Ja

Was passiert, wenn du statt
if (ctr >= 24) { ... zum Test if (ctr > 0) { ... nimmst und mitschreibst, wieviele Pulse gekommen sind ?

    if (ctr > 0) {
     Serial.print (millis());
     Serial.print (": "); 
     Serial.println (ctr);
    }
--> Ergebnis:
19892: 24
20025: 23
20159: 24
20300: 24
20433: 24
20537: 1
20566: 22
20698: 24
20831: 23
20963: 24
21067: 1
21098: 22
21230: 24

Ich bin gerade am überlegen, ob ich vllt. ein Problem mit den Eingangssignal habe?
Zur Info: Die Signale kommen als 1,5V-Signal aus dem Geber. Damit ich es im Arduino einlesen kann, hab ich Sie durch eine Transistorbeschaltung verstärkt:
Gebersignal mit 5K an Basis des BC547C, 5V über 10K an Collector und Arduino-Eingang, Emitter an GND. (Schema wie im Bild, allerdings mit anderen Werten)
Da die Schaltung invertiert frag ich auch nach Rising und !digitalRead.
Im Anhang seht ihr mal ein Oszi-Bild des Signals.

Wenn der Clock - Puls nur zu lesen ist, ist shiftIn() übrigens nicht passend.

Soll heissen, dass dafür der CLK ein Ausgang sein soll?
Ne, bei mir sind beides Eingänge. Also der Geber sendet auf beiden Leitungen.

NewFile0.bmp (146 KB)

Transistor.JPG

Ich glaub jetzt eher, daß ich meinen Code noch optimieren muss.
Mit dieser Änderung:

  if ((millis() - time) >= 20 [color=red]&& !newdata[/color]) {
    if (ctr > 0) {
     Serial.print (millis());
     Serial.print (": "); 
     Serial.println (ctr);

sieht es schon wesentlich besser aus:

4483: 24
4615: 24
4748: 24
4881: 24
5014: 24
5147: 23
5251: 1
5280: 22
5412: 24
5545: 24
5679: 23
5812: 24

Aber es sind immer noch "Fehltelegramme".
Sieht aus, als ob es immer noch Timingprobleme beim Zusammenspiel zwischen Interruptroutine und Hauptprogramm gibt.
Den Int während der Auswertung im Hauptprogramm zu deaktivieren trau ich mich nicht, da ich dann fürchte einen Interrupt zu verpassen.

Wenn das

  if (newdata) {
    time = millis();
  }

noch drin ist, sollte deine Änderung nicht viel ausmachen, aber auch nicht stören :wink:

Das Oszi-Bild krieg ich nicht mit deiner Beschreibung zusammen: 24 Bit in 10 ms, dann 120 ms Pause
Was ist Clock, was Daten ? Wie sieht es bei einer Auflösung von z.B. 200 ms aus ?

4881: 24
5014: 24
5147: 23
5251: 1
5280: 22 --> diese Pulse müssen zwischen 5251 und 5260 gekommen sein, danach Ruhe bis 5280. ???

Nimm mal time = millis(); in die Interrupt-Routine
und probier sowas ähnliches aus:

void loop () 
{
    noInterrupts();
    unsigned long tmp = time;
    byte count = ctr;
    if ( millis() - tmp > 60 && count > 0)
    {
        ctr = 0;
        distance = 0;
      interrupts();
        Serial.print (tmp);
        Serial.print(": ");
        Serial.println(count);
    }
    else 
      interrupts();
}

( newdata brauchts dann gar nicht )
Ausserdem sollten distance und ctr als[b] volatile [/b] deklariert werden. ( Damit der compiler berücksichtigt, dass Takt() und loop() gleichzeitig laufen und er keine falschen Optimierungen vornimmt.)

Hi,

komisch, hatte gestern nacht noch mal ein Posting mit Erfolgsmeldung nachgeschoben. Irgendwie ist das weg????
Zum OsziBild: Takt ist gelb (6 Pakete a 4 Takte) , Data ist blau. Leider sieht man auf dem Bild nur ca. 75% des gesamten Telegramms. Sonst wäre nichts zu erkennen gewesen.

Das deaktivieren des Interrupts hat nichts gebracht. Das mit den Neusetzen der Zeit in der Int hab ich auch mal probiert. Aber ich wollte in der Int so wenig Code wie möglich halten. Drum nur das Setzen des newdata

Die zündende Idee war wie folgt:

Ich hab am Ende der Loop noch ein delay(1) eingefügt. Dann war auf jeden Fall sicher gestellt, daß im Falle einer Datenübertragung einmal die Int-Routine durchlaufen wird.
Anscheinend gab es Zustände, wo das Hauptprogramm "zu schnell" war.
Der Code sieht jetzt so aus:

const int data = 3;
boolean newdata = LOW;
int ctr;
unsigned long time;
long distance = 0;
float Position;


void setup(){
  Serial.begin (57600);
  pinMode(data, INPUT);
  attachInterrupt(0, INT01, RISING);   //(UNO: Int0 = PIN 2)
}

void loop(){
  X_Achse();
  delay(1);
}

void X_Achse() {
  if (newdata) {
    time = millis();
  }
  newdata = LOW;  

  if ((millis() - time) >= 20 ) {
     if (ctr == 24) {
       distance = distance >>1;      // erstes Bit verwerfen
       if bitRead(distance,20) {     // negativer Wert??
         bitWrite(distance,20,0);
         Position = float(distance/-100.0);  // Wert auf mm normieren
       }
       else {
         Position = float(distance/100.0);
       }
      Serial.println (Position);
    } 
    ctr = 0;
  }  
  
}

void INT01() {
  newdata = HIGH;
  bitWrite(distance, ctr, !digitalRead(data)); 
  ctr=ctr+1;
}

Jetzt bekomm ich alle 130 ms einen RICHTIGEN (24Bit langen) Wert :slight_smile:

Ausserdem sollten distance und ctr als volatile deklariert werden. ( Damit der compiler berücksichtigt, dass Takt() und loop() gleichzeitig laufen und er keine falschen Optimierungen vornimmt.)

OK.... sagt mir momentan nichts, werd mich aber mal zu volatile einlesen.

Danke für deine geduldigen Antworten.
Sorry nur nochmal dass das posting von gestern Nacht irgendwie weg war.