Draaisnelheid meten met IR-sensoren

Bij de mechanisch geconstrueerde versnellingsbak volgen de verhoudingen tussen draaisnelheden eenvoudigweg uit de tandwielverhoudingen van de versnelling waarin de bak geschakeld is. Maar het is nuttig te weten wat de grondsnelheid (in rotaties per minuut; rpm) op een bepaald moment bedraagt.

Ik sloeg aan het experimenteren met IR-obstacle sensoren om kleine reflecterende streepjes op de wielen te meten. Mijn metertje meet direct meerdere sensoren en kan ook de (benaderde) verhouding direct tonen op een LCD schermpje.

Video : toestemming voor cookies nodig
Instellingen

Met een mechanische versnellingsbak kunnen uit één inkomende motoraandrijving diverse afgeleide draaisnelheden worden gemaakt. Omdat maar één motor wordt gebruikt, volgen de verhoudingen tussen draaisnelheden eenvoudigweg uit de tandwielverhoudingen.

Bij een elektronische regeling zijn echter al snel twee aparte motoren nodig. Het inzicht in de precieze uiteindelijke draaisnelheden en -verhoudingen wordt dan lastiger. Verschillen in de pulsbreedtes van de sturing of de wrijving of speling in de mechanische constructie spelen dan allemaal mee. Omdat beide bewegingen niet aan één bron ontspruiten worden de uiteindelijke individuele draaisnelheden zowel elektronisch als mechanisch beïnvloedt.

RPM Meter 1
IR_obstacle_sensors

IR-sensoren

Voor het meten van de draaisnelheid kwam ik online enkele principes tegen. Enkelen gebruikten hiervoor de IR-sensoren die al eerder in een project gebruikt werden.

Online vond ik enkele voorbeelden, maar die voldeden geen van allen voor relatief lage snelheden. Bovendien konden ze zonder uitzondering slechts één draaisnelheid meten en misten daardoor sowieso een modus waarin de verhouding tussen twee snelheden wordt weergegeven.

Het liefst gebruikte ik een universele Arduino Uno met LCD Keypad Shield. Omdat deze microcontroller twee hardware interrupts heeft, zou het mogelijk zijn meteen twee snelheden te meten. De IR-obstacle sensoren kunnen zó worden afgesteld dat ze getriggerd worden door een passerende reflecterende strook op het wiel waarvan de draaisnelheid moet worden gemeten. Zoals op de afbeelding van de versnellingsbak hiernaast te zien is, plakte ik alvast een strookje op het grootste tandwiel waarop de basis-draaisnelheid van de versnellingsbak zou kunnen worden gemeten.

Het idee zou zijn deze pulsflank een hardware interrupt van de Arduino te laten triggeren. Dit heeft het voordeel dat de meting onafhankelijk is van vertraging in de hoofdlus van het programma dat o.a. het display aanstuurt.

Spinning reflector on Z60
IR-sensor edge

Het werd echter vrij snel duidelijk dat de detectie-flank van dergelijke optische metingen absoluut niet gebruikt kan worden om een interrupt op de Arduino te triggeren. Zoals in de afbeelding hiernaast te zien is (gele curve boven), kan deze bij een lagere draaisnelheid snel richting de 1 ms gaan. Zo'n flank triggert simpelweg meteen tientallen interrupts!

Daarom bufferde (en inverteerde) ik het sensorsignaal met een z.g. Schmitt-inverter. Deze 'klapt' op één punt om en maakt de puls weer mooi scherp. Zie de onderste groene puls. Nu leverde het passeren van een reflecterend streepje op het wiel telkens slechts één interrupt op.

Op de foto's de experimentele opbouw met twee test-sensoren. Omdat het gebruikte Schmitt-IC (CD40106) zes inverters bevat, lijkt het zinvol om een aparte module te maken zodat de pulsen van deze sensoren in de toekomst tevens flexibeler als inputs gebruikt kunnen worden bij flip-flops, counters of andere discrete digitale logica waarvoor steile (klok)pulsen onontbeerlijk zijn.

Omdat de pulsen dan ook meteen dichtbij de 'verwerking' worden opgepoetst, worden de capaciteit, overspraak en impedantie van de sensor-leidingen ook meteen verdisconteerd. Dit komt de precisie van de metingen zeker ten goede.

RPM Meter 3

RPM Meter IR-sensorsRPM Meter Schmitt InvertersRPM Meter Display RPM/HZ

Arduino Sketch

Hiernaast (versie 1 van) de Sketch die ik schreef om twee sensoren uit te lezen. Het gaat uit van een Arduino Uno waarop genoemd LCD Keypad Shield is gemonteerd. De sensoruitgangen worden aangesloten op P2 en P3. De 5 volt voedingsspanning voor de sensoren betrek ik van de ICSP connector van het shield.

Met de 'Select'-knop kan de displayverlichting worden uitgeschakeld, en weer worden ingeschakeld. Met de 'Right'-knop (op mijn goedkope Chinese kloon 'Rigth' genoemd, haha) kan de weergave mode worden gewisseld tussen Hz/rpm en de overbrengingsverhoudig tussen de twee draaisnelheden.

// Simple dual freq/RPM meter with IR-obstacle detectors - WhizzBizz.com Arnoud van Delden 9th feb 2023
// Buttons reading based on sketch at https://www.elecrow.com/wiki/index.php?title=LCD_Keypad_Shield
// Interrupt stuff on https://projecthub.arduino.cc/BEASTIDREES62/d4a796a9-5bdf-4d5e-96ae-cd7fce0a4dc7
 
//Sample using LiquidCrystal library
#include 

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// define some values used by the panel and buttons
int lcd_key     = 0;
int adc_key_in  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
#define BACKLIT   10
#define SENS1SIG  2
#define SENS2SIG  3
#define DISPL_UPD 1000 // Every x ms...

bool backlitOn = true;
bool rpmFactorMode = false; // Display mode toggle...
volatile unsigned long difftime;
volatile bool tooslow = 1;
unsigned long lastdisplay;
float factor;

// Rpm1
volatile unsigned long lasttime1;
volatile unsigned long rpmtime1;
float period1;
float hrz1;
unsigned int rpm1;

// Rpm2
volatile unsigned long lasttime2;
volatile unsigned long rpmtime2;
float period2;
float hrz2;
unsigned int rpm2;

// Display output...
char lcd_line0[17]; // Line buffers for LCD
char lcd_line1[17];

// read the buttons
int read_LCD_buttons() {
  adc_key_in = analogRead(0);      // read the value from the sensor
  // Serial.print("Key value: ");
  // Serial.println(adc_key_in);

  // Button values of my perticulair board: 0, 135, 313, 486, 725
  // we add approx 50 to those values and check to see if we are close
  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
  if (adc_key_in < 50)   return btnRIGHT;  
  if (adc_key_in < 150)  return btnUP; 
  if (adc_key_in < 350)  return btnDOWN; 
  if (adc_key_in < 500)  return btnLEFT; 
  if (adc_key_in < 750)  return btnSELECT;   
  return btnNONE;  // when all others fail, return this...
}

void setup() {
  Serial.begin (115200);
  pinMode (BACKLIT,OUTPUT); // Display backlit...
  
  lcd.begin(16, 2);              // start the library
  lcd.setCursor(0,0);

  // Set up timers and interrupts for sensor inputs...
  TCCR1A = 0;
  TCCR1B = 0;
  TCCR1B |= (1 << CS12);  // Prescaler 256
  TIMSK1 |= (1 << TOIE1); // Enable timer overflow
  pinMode (SENS1SIG, INPUT_PULLUP);
  attachInterrupt(0, RPM1, FALLING);
  pinMode (SENS2SIG, INPUT_PULLUP);
  attachInterrupt(1, RPM2, FALLING);
  //oneSecond = millis();
}

void loop() {
  if (millis()-lastdisplay>DISPL_UPD) {
    updateDisplay();
    lastdisplay = millis();
  }

  // Read buttons...
  lcd_key = read_LCD_buttons();  // read the buttons
  //lcd.setCursor(0,1);          // move to the begining of the second line
  switch (lcd_key) {             // depending on which button was pushed, we perform an action
    case btnRIGHT:
      //lcd.print("RIGHT ");
      while (read_LCD_buttons()==btnRIGHT) delay(10); // Wait until released...
      rpmFactorMode = !rpmFactorMode; // Switch display mode...
      break;
    case btnLEFT:
      //lcd.print("LEFT   ");
      break;
    case btnUP:
      //lcd.print("UP    ");
      break;
    case btnDOWN:
      //lcd.print("DOWN  ");
      break;
    case btnSELECT: // Toggle backlit of LCD...
      //lcd.print("SELECT");
      while (read_LCD_buttons()==btnSELECT) delay(10); // Wait until released...
      backlitOn = !backlitOn;
      break;
    case btnNONE:
      //lcd.print("NONE  ");
      break;
  }

  if (backlitOn==true) {
    digitalWrite(BACKLIT, HIGH);
  } else {
    digitalWrite(BACKLIT, LOW);
  }
   
}

void updateDisplay() { // Update the display...
  if (tooslow) { // Flush values...
    hrz1 = 0;
    rpm1 = 0;
    hrz2 = 0;
    rpm2 = 0;
    factor = 0;
  } else { // Show calculated values...  
    period1 = (rpmtime1 / 1000.00);  
    hrz1 = (1 / period1);
    rpm1 = (60 * hrz1);
    period2 = (rpmtime2 / 1000.00);
    hrz2 = (1 / period2);
    rpm2 = (60 * hrz2);
    if (hrz1>0 && hrz2>0) {
      if (hrz1>hrz2)
        factor = hrz1/hrz2;
      else
        factor = hrz2/hrz1;
    } else 
      factor = 0;
  }

  if (!rpmFactorMode) {
  // Trick to correctly print the float value using sprintf...
    sprintf(lcd_line0, "%2d.%02d Hz %3d RPM", (int)hrz1, (int)(hrz1*100)%100, rpm1);
    sprintf(lcd_line1, "%2d.%02d Hz %3d RPM", (int)hrz2, (int)(hrz2*100)%100, rpm2);
  } else {
    sprintf(lcd_line0, "R1:%3d    R2:%3d", rpm1, rpm2);
    if (factor>0)
      sprintf(lcd_line1, "Gear Ratio 1:%d  ", round(factor));
    else
      sprintf(lcd_line1, "Gear Ratio 1:?  ", round(factor));
  }

  // Write to display...
  lcd.setCursor(0,0);
  lcd.print(lcd_line0);
  lcd.setCursor(0,1);
  lcd.print(lcd_line1);
} 

ISR(TIMER1_OVF_vect) {
  tooslow = 1; // No pulses received for ~1 sec... 
}

void RPM1 () {
  difftime = millis()-lasttime1;
  rpmtime1 = difftime;
  lasttime1 = millis();
  tooslow = 0;
}

void RPM2 () {
  difftime = millis()-lasttime2;
  rpmtime2 = difftime;
  lasttime2 = millis();
  tooslow = 0;
}
RPM Meter Display Ratio

Geslaagde tussenstap

De dubbele rpm- en draaiverhouding-meter wordt in het volgende project voornamelijk nog gebruikt om de basis draaisnelheid te meten. Bij de mechanische versnellingsbak is de verhouding tussen de draaisnelheden immers direct af te lezen als de gekozen 'versnelling'.

Zodra twee aparte motoren worden gebruikt, en deze individueel met elektronica zullen moeten worden aangestuurd, zal hij echter pas echt zijn diensten kunnen gaan bewijzen. De uitleesmodus waarin direct de verhouding tussen de twee draaisnelheden op het display wordt getoond, is in elk geval alvast ingebouwd... 😉 Wordt vervolgd...