pulseIn Messung über Fotodiode mit Transimpedanzverstärkung ungenau

Hallo liebe Member des Arduino-Forums,

ich habe folgendes Problem: Mein Versuchsaufbau besteht aus zwei Arduinos:

Arduino 1 (Mega): LED wird gepulst angesteuert, mittels Rotary Encoder kann ich die Frequenz zwischen 1 und 100 Hz regeln

Arduino 2 (Uno): Eine Fotodiode (liegt direkt neben der LED) mit Transimpedanzverstärkung ist an einen Digitalpin angeschlossen und erkennt das gepulste Signal der LED

Sobald ich nun an Arduino 1 die Frequenz höher als ~63 Hz drehe, werden an Arduino Uno nur noch Werte mit großen Sprüngen dazwischen angezeigt, z.B. LED Frequenz: 90 Hz, erkannte Frequenz des Arduino 2: 80Hz.

Die Frequenzerkennung läuft über pulseIn von HIGH und LOW des Digitalpins, die Pulse werden zusammengerechnet (1x an und aus) und dann in die Frequenz umgerechnet.

Hat jemand eine Idee wie man das beheben könnte? Ich bin noch blutiger Anfänger auf dem Gebiet also wäre ich froh wenn ihr eure Antworten idiotensicher formuliert :smiley:

LG Michael

Der Fehler steckt wie so oft in Zeile 42 und in der Verbindung zwischen Punkt A3 und K1.

(deleted)

Richtig, diese Antwort paßt immer wenn der Fragesteller alle wichtigen Informationen für sich behält. Siehe Topics am Anfang des Forums, was für Angaben für eine erfolgreiche Beratung notwendig sind.

Meine Analyse: Verbessere die Fehler in Deinem Code, dann funktioniert das wahrscheinlich. Mehr läßt sich bis jetzt nicht sagen.

Ganz ruhig, ist mein erster Post im Forum :smiley:

Hier ist der Code von Arduino 2 (Fotodiode):

const int pinLED = 7;
const int pinSensor = 5;

int highTime;    //integer for storing high time
int lowTime;     //integer for storing low time
float period;    // integer for storing period

float f = 0;

void setup() {
  Serial.begin(57600);

  while (! Serial) {
    delay(1);
  }
  
  pinMode(pinSensor, INPUT);
  pinMode(pinLED, OUTPUT);
  
  digitalWrite(pinLED, HIGH);
} 

 
void loop() {
  
  highTime=pulseIn(pinSensor,HIGH);  //read high time
  lowTime=pulseIn(pinSensor,LOW);    //read low time
  period = highTime+lowTime; // Period = Ton + Toff
  f=1000000/period;
  Serial.println(f);
  
}

Die LED zeigt einfach nur an, dass die Fotodiode gerade misst, sollte doch kein Problem sein oder?

ecxabyte:
Ganz ruhig, ist mein erster Post im Forum :smiley:

Und wer lesen kann ist klar im Vorteil.

Falls es interessiert: Hier ist der Code von Arduino 1 (LED und Rotary Encoder):

float f = 0;
const int pinLED = 7;
const int swPin = 18;

boolean on = true;

#define encoderPinA 2
#define encoderPinB 3
 
volatile byte encoderPos = 0;  // a counter for the dial
unsigned int lastReportedPos = 1;   // change management
static boolean rotating=false;      // debounce management
 
// interrupt service routine vars
boolean A_set = false;            
boolean B_set = false;


void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  while(!Serial){
    delay(1);
  }

  pinMode(pinLED, OUTPUT);

  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT); 
  pinMode(swPin, INPUT_PULLUP);
 
  digitalWrite(encoderPinA, HIGH);  // turn on pullup resistors
  digitalWrite(encoderPinB, HIGH);  // turn on pullup resistors

  Serial.println("Start: ");
  Serial.print("Interrupt Pin Indexes: ");
  Serial.print(digitalPinToInterrupt(encoderPinA));
  Serial.print(", ");
  Serial.println(digitalPinToInterrupt(encoderPinB));
  
  attachInterrupt(digitalPinToInterrupt(encoderPinA), doEncoderA, CHANGE); // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(digitalPinToInterrupt(encoderPinB), doEncoderB, CHANGE); // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(digitalPinToInterrupt(swPin), doSW, CHANGE);

  on = true;
}

void loop() {
  rotating = true;  // reset the debouncer
 
  if (lastReportedPos != encoderPos)
  {
    //Serial.print("Index: ");
    //Serial.println(encoderPos, DEC);
    lastReportedPos = encoderPos;
  }
  
  if(on){
    f = calcFreq(encoderPos);
    Serial.println(f);
    blinken(f);
  }
}

float calcFreq(int encoderValue) {
  float encoderFreq = 0;

  float prozentWert = (float)encoderValue/255;
  //Encoder: 0-255
  //Wunschfreuqenz: 1-100Hz

  encoderFreq = 1 + 99*prozentWert;
  
  return encoderFreq; 
}

void blinken(float freq) {
  float d = 1000/(freq*2);
  digitalWrite(pinLED, HIGH); // Schalte die LED an Pin7 an.
  delay(d); // Warte 1000 Millisekunden.
  digitalWrite(pinLED, LOW); // Schalte die LED an Pin7 an.
  delay(d); // Warte 1000 Millisekunden.
}
 
// Interrupt on A changing state
void doEncoderA()
{
  if ( rotating ) delay (1);  // wait a little until the bouncing is done
  if( digitalRead(encoderPinA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set ) 
      encoderPos += 1;
    rotating = false;  // no more debouncing until loop() hits again
  }
}
 
// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) delay (1);
  if( digitalRead(encoderPinB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) 
      encoderPos -= 1;
    rotating = false;
  }
}

void doSW(){
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();

  if (interrupt_time - last_interrupt_time > 200)
  {
    on = !on;
    Serial.println("LED switched");
  }

  last_interrupt_time = interrupt_time;
 
}

HotSystems:
Und wer lesen kann ist klar im Vorteil.

Mensch, hier wird man aber freundlich empfangen :smiley:

Ich dachte meine kurze Beschreibung:

ecxabyte:
Die Frequenzerkennung läuft über pulseIn von HIGH und LOW des Digitalpins, die Pulse werden zusammengerechnet (1x an und aus) und dann in die Frequenz umgerechnet.

Würde eventuell schon reichen für ein paar Denkanstösse, Schande über mich :slightly_frowning_face:

ecxabyte:
Mensch, hier wird man aber freundlich empfangen :smiley:

Wir tun immer unser Bestes, du auch ?

Dein Pulsgenerator ist schonmal fragwürdig bei 100 Hz. Delay arbeitet nicht mit float.

Oh wusste ich gar nicht. Was würdest du ansonsten vorschlagen?

Hi

millis() oder micros()
Serielle Ausgaben mit einer VIEL HÖHEREN Geschwindigkeit.
Bei 9600 Baud braucht EIN Zeichen 1ms - wenn Du 'Frequenz: .....' ausgibst, sind Das schon einige ms Wartezeit, Die zum delay() noch dazu kommen.
Das passiert Dir bei millis() nicht, Die zählen weiter, solange die Interrupts nicht gesperrt werden (und wenn nicht zu lang gesperrt wird, holen Die das Zählen nach).

Da Du für 100Hz 5ms Pause brauchst, dürfte millis() aber schon zu ungenau werden - spätestens nach 5ms ist eben nicht genau nach 5ms.
Dort könnte micros() besser sein, zählt in 4er Schritten die µs, also pro Sekunde bis 1.000.000 - dort wird mit >5000µs die 5ms wesentlich genauer getroffen, als mit millis();

Schaue Dir blink_without_delay in der IDE an.

MfG

Interrupts sind auch falsch verwendet. Kein volatile. Kein atomares Auslesen (evtl. nur char verwenden statt int, dann braucht man das nicht). Serial und delay() in einer ISR!

Du kannst ja das Ausgangssignal des 1. Arduinos an den Eingang des 2.Arduino schalten und so die LED/Fotodiode als Fehlerquelle ausgeschlossen. Masse verbinden.
Grüße Uwe

Bei 9600 Baud braucht EIN Zeichen 1ms

Nur wenn man die Ausgabe vollballert. Wenn man "selten" und weniger als 64 Zeichen am Stück ausgibt, bremst HardwareSerial nicht.

postmaster-ino:
Hi

millis() oder micros()
Serielle Ausgaben mit einer VIEL HÖHEREN Geschwindigkeit.
Bei 9600 Baud braucht EIN Zeichen 1ms - wenn Du 'Frequenz: .....' ausgibst, sind Das schon einige ms Wartezeit, Die zum delay() noch dazu kommen.
Das passiert Dir bei millis() nicht, Die zählen weiter, solange die Interrupts nicht gesperrt werden (und wenn nicht zu lang gesperrt wird, holen Die das Zählen nach).

Da Du für 100Hz 5ms Pause brauchst, dürfte millis() aber schon zu ungenau werden - spätestens nach 5ms ist eben nicht genau nach 5ms.
Dort könnte micros() besser sein, zählt in 4er Schritten die µs, also pro Sekunde bis 1.000.000 - dort wird mit >5000µs die 5ms wesentlich genauer getroffen, als mit millis();

Schaue Dir blink_without_delay in der IDE an.

MfG

Danke, probiere ich mal aus :slight_smile: War mir nicht bewusst, dass die "Ausgabe" so viel Zeit in Anspruch nimmt

Serenifly:
Interrupts sind auch falsch verwendet. Kein volatile. Kein atomares Auslesen (evtl. nur char verwenden statt int, dann braucht man das nicht). Serial und delay() in einer ISR!

Serial in der Button ISR nehme ich in Kauf, den Rest lösche ich noch. Beim Rest der Antwort verstehe ich nur Bahnhof, kannst du die nochmal einfacher formulieren? :smiley:

uwefed:
Du kannst ja das Ausgangssignal des 1. Arduinos an den Eingang des 2.Arduino schalten und so die LED/Fotodiode als Fehlerquelle ausgeschlossen. Masse verbinden.
Grüße Uwe

Geht natürlich auch, ist aber nicht Sinn des Versuchsaufbaus. Hätte ich vllt konkret dazuschreiben sollen

Ich habe die Pulserzeugung jetzt über micros() gelöst, und alle Serial Ausgaben erfolgen jetzt nur noch 1x pro halbe Sekunde Code folgt:

loop() und blinken() vom Arduino 1 (Mega):

void loop() {
  rotating = true;  // reset the debouncer
 
  if (lastReportedPos != encoderPos)
  {
    //Serial.print("Index: ");
    //Serial.println(encoderPos, DEC);
    lastReportedPos = encoderPos;
  }
  
  if(on){
    f = calcFreq(encoderPos);

    blinken(f);
    
    if(currentMicros - serialMicros >= 500000)
    {
      Serial.println(f);
      serialMicros = currentMicros;
    } 
  }
}


void blinken(float freq) {
  float d = 1000000/(freq*2);
  
  currentMicros = micros();

  if (currentMicros - previousMicros >= d) {
    // save the last time you blinked the LED
    previousMicros = currentMicros;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(pinLED, ledState);
  
  }
}

kompletter Code vom Arduino 2 (Uno):

int test; 
const int pinLED = 7;
const int pinSensor = 5;
unsigned long duration = 0;

unsigned long previousMillis = 0; 
unsigned long currentMillis = 0; 

int highTime;    //integer for storing high time
int lowTime;     //integer for storing low time
float period;    // integer for storing period

float f = 0;

void setup() {
  Serial.begin(57600);

  while (! Serial) {
    delay(1);
  }
  
  pinMode(pinSensor, INPUT);
  pinMode(pinLED, OUTPUT);
  
  digitalWrite(pinLED, HIGH);
} 

 
void loop() {

  
  highTime=pulseIn(pinSensor,HIGH);  //read high time
  lowTime=pulseIn(pinSensor,LOW);    //read low time
  period = highTime+lowTime; // Period = Ton + Toff
  f=1000000/period;
  
  currentMillis = millis();

  if (currentMillis - previousMillis >= 500) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    Serial.println(f);
  }
  
  /*
  test = digitalRead(pinSensor);    
  Serial.println(test);    
  */
}

Das klappt soweit auch ganz gut, jedoch zeigt die Fotodiode immer eine etwas niedrigere Frequenz an, Bsp.: LED: 981 Hz; Fotodiode: 961 Hz.
Sobald ich jenseits der 1000 Hz gehe ist der Effekt noch deutlich stärker und irgendwann misst er nur noch jeden zweiten Puls (halbe Frequenz).

Hat jemand noch Verbesserungsvorschläge? Danke nochmal für den Vorschlag mit millis :slight_smile:

ecxabyte:
Beim Rest der Antwort verstehe ich nur Bahnhof, kannst du die nochmal einfacher formulieren? :smiley:

Variablen die innerhalb und außerhalb von ISRs verwendet werden sollten als volatile deklariert werden:
https://www.arduino.cc/reference/en/language/variables/variable-scope--qualifiers/volatile/

Multi-Byte Variablen die von ISRs verwendet werden müssen außerhalb bei abgeschalteten Interrupts angesprochen werden:

https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
Sonst kann ein Interrupt zwischen zwei Bytes einer Variablen dran kommen.

Generell braucht man für einen Encoder auch nicht zwei Interrupts. Du musst nur eine Flanke abfragen und bei Änderung den Zustand des anderen Pins

Serenifly:
Variablen die innerhalb und außerhalb von ISRs verwendet werden sollten als volatile deklariert werden:
https://www.arduino.cc/reference/en/language/variables/variable-scope--qualifiers/volatile/

Multi-Byte Variablen die von ISRs verwendet werden müssen außerhalb bei abgeschalteten Interrupts angesprochen werden:
atomar – Wikipedia
avr-libc: <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks
Sonst kann ein Interrupt zwischen zwei Bytes einer Variablen dran kommen.

Generell braucht man für einen Encoder auch nicht zwei Interrupts. Du musst nur eine Flanke abfragen und bei Änderung den Zustand des anderen Pins

Danke für die Erklärung! Habe die entsprechenden Variablen jetzt auf volatile geändert :slight_smile:

Ich hatte die Interrupt Lösung im Internet gefunden und fand sie recht elegant, zum Glück hat der Arduino Mega ja genug Pins also werde ich das denke ich so lassen.

ecxabyte:
Das klappt soweit auch ganz gut, jedoch zeigt die Fotodiode immer eine etwas niedrigere Frequenz an, Bsp.: LED: 981 Hz; Fotodiode: 961 Hz.
Sobald ich jenseits der 1000 Hz gehe ist der Effekt noch deutlich stärker und irgendwann misst er nur noch jeden zweiten Puls (halbe Frequenz).

Hat jemand noch Verbesserungsvorschläge? Danke nochmal für den Vorschlag mit millis :slight_smile:

Ich habe statt pulseIn() nun mal die Library FreqCount getestet, da gibt es jedoch dasselbe Problem: Die Messung zeigt konstant weniger an als der Ardunio mit der LED und ab 1kHz sind die Messungen leider gar nicht mehr zu gebrauchen. Ist das einfach das Limit der Kombination aus Fotodiode, Transimpedanzverstärkung und Arduino?