Recommended approach for reading LM393 tachometers

Hello,

I’m having a problem reading tachometers and maybe someone in this forums know how to approach this.
I suspect that interrupts are being lost when reading LM393 tachometers for a 4-wheeled rover with software based counters using four instances of the lm393_tachometer class listed below, specially when the system is being overloaded by other interrupts, such as UARTs. I understand that counters could be implemented by hardware, avoiding the need to use interrupts, but is there a way to implement this in MbedOs? Board is Nucleo-144 STM32F767ZI.

Any help will be appreciated.
Kind regards,

Nicolás


Code:

#ifndef LM393_H
#define LM393_H

#include "DigitalOut.h"
#include <mbed.h>

#define DEFAULT_TACHO1_PIN PE_7
#define DEFAULT_TACHO2_PIN PE_8
#define DEFAULT_TACHO3_PIN PG_9
#define DEFAULT_TACHO4_PIN PG_14

class lm393_tachometer {
public:
    static constexpr uint32_t max_interval = 1000000;
    lm393_tachometer(PinName pin,int n_ticks_per_rev);
    ~lm393_tachometer() = default;
    float get_rpm();
    inline uint32_t get_total_tick_count() const { return this->counter; }
private:        
    Timer timer;
    InterruptIn irq;
    volatile uint32_t counter;
    volatile float dt;
    volatile float rpm;
    const float encoder_k;    
    void tick();    
    DigitalOut debug_led;
};
#endif
#include "lm393_tachometer.h"

lm393_tachometer::lm393_tachometer(PinName pin,int n_ticks_per_rev):
          irq(pin) //PullUp, PullDown, PullNone, PullDefault
        , counter(0)
        , dt(0)
        , rpm(0)
        , encoder_k(1.0f/n_ticks_per_rev)
        , debug_mode(false)
        , debug_led(LED2)
{

}

void lm393_tachometer::setup()
{
    this->irq.fall(callback(this, &lm393_tachometer::tick));
    this->irq.enable_irq();    
    this->timer.start();
}

void lm393_tachometer::tick() 
{
    this->dt = timer.elapsed_time().count();
    this->timer.reset(); 
    /* Avoid out of range readings */    
    this->rpm = (this->dt <= lm393_tachometer::max_interval) ? 
            ( this->encoder_k  / (this->dt/float(1000000.0f))) *60.0f : 0.0f;  
    this->counter++;
    this->debug_led.write(!this->debug_led.read());
}

float lm393_tachometer::get_rpm() 
{
    /* Avoid out of range readings */    
    if(this->timer.elapsed_time().count()>= lm393_tachometer::max_interval)
    {   
        this->rpm = 0.0f;
    }    

    /* A frecuencias muy bajas se producen errores ocasionales. */   
    if( this->rpm > 1000 )
    {        
        this->rpm = 0.0f;
    }    

    return this->rpm;
}

Hello Nicolás,

You can try the code below. A Timeout is used rather than a Timer to set up a counting period with us precision. The example program is counting pulses for 500ms and assumes 32 pulses per revolution. Adapt to your needs:

#include "mbed.h"

#define DEFAULT_TACHO1_PIN  PE_7
#define DEFAULT_TACHO2_PIN  PE_8
#define DEFAULT_TACHO3_PIN  PG_9
#define DEFAULT_TACHO4_PIN  PG_14

#define TO_COUNT_PER_MIN    (60 * 1E6)

class   Tachometer
{
public:
    Tachometer(PinName inputPinName, PinMode inputPinMode, uint16_t pulsesPerRev);
    ~Tachometer() { }
    void        begin();
    uint32_t    totalCount();
    void        rpmCountFor(uint32_t timeFrame_us);
    bool        isRpmCounting();
    bool        isRpmAvailable();
    float       rpmRead();
private:
    InterruptIn _input;
    uint16_t    _pulsesPerRev;
    uint32_t    _totalCount;
    uint32_t    _timeout_us;
    bool        _rpmCounting;
    uint32_t    _rpmCount;
    Timeout     _timer;
    bool        _rpmAvailable;

    void        _onPulse();
    void        _rpmCountStop();
};

//=====================================

Tachometer::Tachometer(PinName inputPinName, PinMode inputPinMode, uint16_t pulsesPerRev) :
    _input(inputPinName, inputPinMode),
    _pulsesPerRev(pulsesPerRev),
    _totalCount(0),
    _rpmCounting(false),
    _rpmCount(0),
    _rpmAvailable(false)
{ }

void Tachometer::begin()
{
    _rpmCounting = false;
    _rpmAvailable = false;
    _totalCount = 0;
    _input.fall(callback(this, &Tachometer::_onPulse));
}

void Tachometer::rpmCountFor(uint32_t timeFrame_us)
{
    _timeout_us = timeFrame_us;
    _rpmAvailable = false;
    _rpmCount = 0;
    _rpmCounting = true;
    _timer.attach_us(callback(this, &Tachometer::_rpmCountStop), _timeout_us);
}

bool Tachometer::isRpmCounting()
{
    return _rpmCounting;
}

void Tachometer::_onPulse()
{
    _totalCount++;

    if (_rpmCounting) {
        _rpmCount++;
    }
}

void Tachometer::_rpmCountStop()
{
    _rpmCounting = false;
    _rpmAvailable = true;
}

bool Tachometer::isRpmAvailable()
{
    return _rpmAvailable;
}

float Tachometer::rpmRead()
{
    return (_rpmCount * TO_COUNT_PER_MIN) / _pulsesPerRev / _timeout_us;
}

//=====================================
// Let's assume 32 pulses per revolution

Tachometer* tacho[] =
{
    new Tachometer(DEFAULT_TACHO1_PIN, PullUp, 32),
    new Tachometer(DEFAULT_TACHO2_PIN, PullUp, 32),
    new Tachometer(DEFAULT_TACHO3_PIN, PullUp, 32),
    new Tachometer(DEFAULT_TACHO4_PIN, PullUp, 32)
};

int main()
{
    for (uint8_t i = 0; i < 4; i++) {
        tacho[i]->begin();
    }

    while (true) {
        for (uint8_t i = 0; i < 4; i++) {
            if (!tacho[i]->isRpmCounting()) {
                // let's count pulses for 500000 us (= 500 ms);
                tacho[i]->rpmCountFor(500000);
            }

            ThisThread::sleep_for(1000);

            if (tacho[i]->isRpmAvailable()) {
                printf("Tacho[%i] = %7.2f RPM\r\n", i, tacho[i]->rpmRead());
            }
        }

        printf("------------------------\r\n");
    }
}

Thank you very much for the advise @hudakz, I will try your suggestion : )

Please notice that I have modified the Tachometer::begin() function in the code above (added _input.fall(...)).

1 Like