2
\$\begingroup\$

I am using a driver that retrieves data from HW (Driver) and a display (Viewer) that will output data to the user.

The user calls a trigger function to initiate the getting of data from the Driver. The Driver's constructor accepts a "publish" callback function to be called when data is ready.

The Viewer receives a "get data" callback on construction, to be called when it wants data from my code.

My code must integrate the "pull" for data from the viewer with the data "pushed" back from the Driver once I call its trigger function. Another block of code (that I do not have access to) initiates requests from the Viewer and acts upon the data it received back.

This is represented by the main method in my example, below.

Thanks in advance!

#include <cstdint>
#include <iostream>
#include <functional>

class Viewer
{
public:
    Viewer(std::function<void(int&)> get_data_cb) : m_get_data_cb(get_data_cb)
    {
    }
    void populate_view(int &data)
    {
        m_get_data_cb(data);
    }

private:
    std::function<void(int&)> m_get_data_cb;
};

class Driver
{
public:
    Driver(std::function<void(int&)> publish_data_cb) : m_publish_data_cb(publish_data_cb)
    {
    }
    void trigger_event()
    {
        // tells RTL to start collecting data
        ++rtl_int;
        // trigger_event does not call the IRQ - this is just for demo purposes
        event_IRQ_handler();
    }
    void event_IRQ_handler()
    {
        // fires when RTL has finished collecting data
        m_publish_data_cb(rtl_int);
    }

private:
    std::function<void(int&)> m_publish_data_cb;
    int rtl_int{ 0 };
};

/**********************************************************************/
// Code I have control of starts here

volatile uint32_t publish_count{0};
int my_int{0};

// callback to send to Driver
void my_publish_data_cb(int &i)
{
    my_int = i;
    ++publish_count;
}

Driver my_driver = Driver{ my_publish_data_cb };

// callback to send to Viewer
void my_get_data_cb(int &i)
{
    uint32_t before = publish_count;
    my_driver.trigger_event();
    // I need to give the driver's event_IRQ_handler() time
    // to get the data but is this the best way???
    while (before == publish_count){};
    i = my_int;
    //cout << "Viewer: i =" << i << endl;
}

Viewer my_viewer = Viewer{ my_get_data_cb };

// Code I have control of ends here
/**********************************************************************/

int main()
{
    my_viewer.populate_view(my_int);
    std::cout << "my_int = " << my_int << std::endl;
    my_viewer.populate_view(my_int);
    std::cout << "my_int = " << my_int << std::endl;
    my_viewer.populate_view(my_int);
    std::cout << "my_int = " << my_int << std::endl;
    return 0;
}
\$\endgroup\$
8
  • \$\begingroup\$ I don't understand. Do you want this code reviewed or you have some other question? \$\endgroup\$
    – ALX23z
    Nov 19 at 9:46
  • \$\begingroup\$ Doesn't compile, because uint32_t is undefined. Did you intend std::uint32_t from <cstdint>? \$\endgroup\$ Nov 19 at 15:36
  • \$\begingroup\$ Good catch Toby. I missed that when I copy/pasted it in Sorry about that. It's fixed now \$\endgroup\$
    – Orange J
    Nov 19 at 15:53
  • \$\begingroup\$ ALX23z: I am looking for a review. Thanks \$\endgroup\$
    – Orange J
    Nov 19 at 15:54
  • 1
    \$\begingroup\$ You tagged the question "embedded". What kind of embedded device are you planning to run this on? Can you use C++'s thread-related functions? \$\endgroup\$
    – G. Sliepen
    Nov 19 at 20:04

1 Answer 1

1
\$\begingroup\$

You are using a busy-loop to wait for publish_count to be incremented by the interrupt handler. In general, this is a bad way to wait for something to happen, as it keeps the processor busy, which wastes power and might increase its temperature unnecessarily. However, in an embedded device that is not battery-powered, it might be fine. If the device has a single CPU core, then some compilers will even detect what you are doing and insert a CPU instruction in the loop that will pause the processor until an interrupt occurs, thus avoiding wasting power. However, that will not work on multi-core processors.

Since you mentioned the code is running on FreeRTOS, you can use its functions to suspend a task, and resume it from within the interrupt handler, for example using xTaskResumeFromISR().

Another option is to use a queue to send information from the ISR to a task. This decouples the task from the ISR, which might have some advantages. For example, in your code, my_get_data_cb() will always wait for the IRQ handler to generate a new value, but maybe there was already a new value available if the IRQ fired between two calls to my_get_data_cb(), in which case the latter could have returned it immediately. Furthermore, with queues you can send data larger than an int without having to worry about atomicity.

\$\endgroup\$
2
  • \$\begingroup\$ G. Sliepen: Thanks for the suggestions. Unfortunately, I cannot change the Driver class code so would not be able to use a queue to pass info btwn tasks. or xTaskResumeFromISR since the ISR would require knowledge of the task handle. The system is not battery powered and the ISR, by design, cannot fire unless an event has been triggered so the IRQ cannot be fired btwn two calls to the callback. I might just be stuck with what I have? \$\endgroup\$
    – Orange J
    Nov 21 at 3:42
  • \$\begingroup\$ The idea with a queue is to push to it from within my_publish_data_cb(), and pull from it in my_get_data_cb(). Similarly, you can pass the task handle in a global variable, or make my_publish_data_cb a lambda that captures it. \$\endgroup\$
    – G. Sliepen
    Nov 21 at 8:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.