4 years, 4 months ago.

C++/OOP novice - implementing mbed timeout API inside a class object

Hi, first a disclaimer: relatively inexperienced with C++ in general, esp. WRT to embedded systems... Normal C, OTOH I can do :-). Sorry about the long preamble to follow...

I have a pre-made piece of equipment in which 3 linear actuators are used to control the X,Y & Z axes of a platform. I can't go into too much detail because it's proprietary. At the moment the LA's are controlled manually by a person (switches etc). My task is to tap into the relevant control circuitry with my embedded controller (LPC4088QSB + interface hardware) to automate aspects of the platform's movement.

There are no movement encoders anywhere, but the LA's have built-in end-stop cutouts. Drive them (supply +ve or -ve voltage) to the limit and they just stop even thought the drive voltage is still present. I intend to time the travel between end stops and attempt a kind of 'dead-reckoning' method of (somewhat!) predicable positioning.

My method of detecting the motion is to sense the motor current. I have interface circuitry that gives me a logic low while the motor is running and I have the following code cobbled together from the MBED example docs that successfully times this duration. I'd also like some kind of safety timeout in case the end of the motor-run signal is missed due to.. whatever and here's where the difficulty arises.

First the code- my 'Motor' class:

class Motor {
    public:
        // create the InterruptIn on the specified Motor pin (pulled up externally) and initialise indicator LED
        Motor(PinName pin, DigitalOut ml) : _interrupt(pin, PullUp), _mled(ml), _mip(pin) {
//        Motor(PinName pin, DigitalOut ml) : _interrupt(pin, PullUp), _mled(ml) {

            _interrupt.fall(callback(this, &Motor::timerStart));    // attach timerStart function to falling edge
            _interrupt.rise(callback(this, &Motor::timerStop));     // attach timerStop function to rising edge
        }

        float read() {                                                // public method to read timer value
            return _runtime;
        }

    private:                                                        // private vars & methods
        InterruptIn _interrupt;                                     // interrupt on pin
        Timer _t;                                                   // timer
        Timeout _overrun;
        volatile enum except_val { _uninit = -1, _running = -2, _timeout = -3 };   // exception return values
        volatile float _runtime = _uninit;                          // initialise timer value
        DigitalOut _mled;                                           // Motor activity LED placeholder
        DigitalIn _mip;

        void timerStart() {
            _overrun.attach_us(callback(&this, this->timerStop), 60000000));      // setup timeout (sec)
            _t.start();                                             // start timer
            _runtime = _running;                                    // set read return value to 'running' (incomplete)
            _mled = !_mled;                                         // show activity
            evt_q.call(callback(start_msg), (void *)this);                            // post event to print motor started
        }

        void timerStop() {
            _t.stop();                                              // stop timer
            _overrun.detach();                                      // detach timeout
            _runtime = _t.read();                                   // save interval time (sec float)
            if((_mip == 0) || (_runtime >= MOTOR_TIMEOUT)) {        // check reason for stopping:
                evt_q.call(clear_PLC_inputs);                       // post event on the queue to reset PLC inputs
                _runtime = _timeout;                                // input still low (motor running!) or timed out
            }                                          
            _t.reset();                                             // reset for next time
            _mled = !_mled;                                         // clear activity led
            evt_q.call(output_to_serial);                           // post event on the queue to print motor runtimes
        }
};

Note I also have an event queue (evt_q) running to handle messages to a debug serial port etc and LEDs are toggled when the motor is running.

The problem occurs when attempting to attach the member function timerStop to the timeout callback.

Things I've tried & their result:

_overrun.attach_us(callback(&this, this->timerStop), 60000000));
Error: Cannot take the address of an rvalue of type 'Motor *' in "main.cpp"

_overrun.attach(&timerStop, 60.0));
Error: Must explicitly qualify name of member function when taking its address in "main.cpp"

_overrun.attach(&Motor::timerStop, 60.0);
Error: No matching member function for call to 'attach_us' in "extras/mbed-os.lib/drivers/Ticker.h"

_overrun.attach_us(callback(&Motor::timerStop), 60000000);
Error: No matching function for call to 'callback' in "main.cpp"

_overrun.attach(timerStop, 60.0);      //and//
_overrun.attach_us(callback(Motor::timerStop), 60000000);
Error: Reference to non-static member function must be called; did you mean to call it with no arguments? in "main.cpp"

and other combinations I've probably forgotten LOL!

I need 3 instances of this class and need them all to operate independently and in 'parallel' which is why I was hoping I could encapsulate the timeout function into the class like the interrupt/duration timing (which work rather well as it happens).

Any advice appreciated.

1 Answer

4 years, 4 months ago.

Did you try:

_overrun.attach_us(callback(this, &Motor::timerStop), 60000000));

this is already a pointer so no need for the & in front of it. timerStop is a function so you need the & in order to pass the address (or technically you're passing the offset from the start of the motor object)

Also _mip is redundant, if you want to know the status of the pin you can read it from _interrupt, InterruptIn supports all the same calls as DigitalIn. By configuring the same pin as both an interrupt and a digital in you risk not getting the correct pin configuration settings.

Hi Andy, thanks for taking the time to reply.

After I posted the question I did indeed try the syntax you suggested (the interrupt handler syntax in the constructor gave me the hint) but it doesn't work: get a compiler error:

Error: No matching function for call to object of type 'mbed::Callback<void (char *)>' in "extras/mbed-os.lib/events/EventQueue.h"

Thanks also for the hint regarding _mip

posted by Chris Hoffmann 21 Nov 2019

Odd. I just tried your code and it builds fine for me after minor changes to let it build stand alone for the board I have set.

I used: _overrun.attach_us(callback(this, &Motor::timerStop),60000000); (Note: I removed the extra ")" that seems to be in your code on all those lines)

That error you give looks wrong for the command. Can you maybe post the exact code for that line and the declaration of timerStop, given the extra brackets the code you posted above can't be what's in the compiler, you'd be getting a different error if it was.

posted by Andy A 22 Nov 2019

You were right Andy, too many ')' but removing it didn't help. My code as of now:

class Motor {
    public:
        Motor(PinName pin, DigitalOut ml) : _interrupt(pin, PullUp), _mled(ml) {

            _interrupt.fall(callback(this, &Motor::timerStart));
            _interrupt.rise(callback(this, &Motor::timerStop));
        }

        float read() {
            return _runtime;
        }

    private:
        InterruptIn _interrupt;
        Timer _t;
        Timeout _overrun;
        volatile enum except_val { _uninit = -1, _running = -2, _timeout = -3 };
        volatile float _runtime = _uninit;
        DigitalOut _mled;

        void timerStart() {
            _overrun.attach_us(callback(this, &Motor::timerStop), 60000000);
            _t.start();
            _runtime = _running;
            _mled = !_mled;
            evt_q.call(callback(start_msg), (void *)this);
        }

        void timerStop() {
            _t.stop();
            _overrun.detach();
            _runtime = _t.read();
            if((_interrupt == 0) || (_runtime >= MOTOR_TIMEOUT)) {
                evt_q.call(clear_PLC_inputs);
                _runtime = _timeout;
            }                                          
            _t.reset();
            _mled = !_mled;
            evt_q.call(output_to_serial);
        }
};

Still produces the error:

Error: No matching function for call to object of type 'mbed::Callback<void (char *)>' in "extras/mbed-os.lib/events/EventQueue.h", Line: 1271, Col: 13

(using the online compiler).

posted by Chris Hoffmann 24 Nov 2019

Interesting development: using Mbed Studio instead of the online compiler, the code compiles fine(!).

I can even change the (previously) problem line to:

_overrun.attach(callback(this, &Motor::timerStop), 60.0);

and it still compiles.

The online compiler still throws the same error for the modified version.

posted by Chris Hoffmann 24 Nov 2019

Ok, it's solved now...

As you suspected (&I should've seen too) from the reported error, Andy, nothing to do with the _overrun.attach call.

It was the evt_q.call(callback(start_msg), (void *)this); call where I'm seeing if I can identify the runtime instance of the Motor object.

What threw me was the error message didn't refer to a particular line in my code, but to the line in EventQueue.h.

In my Mbed Studio version, this was changed to:

evt_q.call(callback(start_msg), (uint32_t)this);

Now that both are synced the online compile is successful also.

Thanks Andy for looking at this for me...

posted by Chris Hoffmann 24 Nov 2019