How to create C++ task class (a worker) with Keil's CMSIS RTOS2 thread and timer C funtions?

946 Views Asked by At

How do I map C++ class concept to C functions osTimerNew() and osThreadNew() ?

How to use a C++ member function as a Keil RTOS2 osTimerNew() and osThreadNew() callback implementation.

Thanks

2

There are 2 best solutions below

6
On

You give "this" to osTimerNew()/osThreadNew() in place of a "void * argument" parameter.

struct task
{
    task ( uint32_t timer_period_ms )
    {
        // ===  casting (needs must)
        using fp    = void ( task::* ) ( void * );
        using os_fp = void ( * )       ( void * );
        auto cast =
        [] ( fp in )
        {
            union {
                fp      in;
                os_fp   out;
            } u { in };

            return u.out;
        };


        auto timer_id = osTimerNew
        (
            cast ( &task::rtos_timer_callback  ),
            osTimerPeriodic,
            this,    // let RTOS know about the object
            nullptr
        );
 
        m_id_thread = osThreadNew
        (
            cast ( &task::rtos_thread_callBack ),
            this,    // let RTOS know about the object
            nullptr
        );

        osTimerStart ( timer_id, timer_period_ms );
    }
    
    virtual ~task() = default;

    virtual void do_work () = 0;

private:
    void rtos_timer_callback ( void * pvArg )
    {
        osThreadFlagsSet ( m_id_thread, 0x01 );
    }
    
    __NO_RETURN void rtos_thread_callBack ( void * pvArg )
    {
        while (1)
        {
            osThreadFlagsWait ( 0x01, osFlagsWaitAny, osWaitForever );
            do_work ();
        }
    }

private:
    osThreadId_t m_id_thread {};
};

Now use the task class:

struct d_task_0 : public task
{
    d_task_0 ( uint32_t timer_period_ms ) : task { timer_period_ms } {}
    void do_work () final
    {
        // called every d_task_0::timer_period_ms
    }
};

and create another task:

struct d_task_1 : public task
{
    d_task_1 ( uint32_t timer_period_ms ) : task { timer_period_ms } {}
    void do_work () final
    {
        // called every d_task_1::timer_period_ms
    }
};

And finally create workers:

d_task_0  worker0 { 500 }; // d_task_0::do_work () called every 500ms
d_task_1  worker1 { 800 }; // d_task_1::do_work () called every 800ms

RTOS2 documentation:

https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS__ThreadMgmt.html

https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS__TimerMgmt.html

and implementation:

https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/RTOS2

My toolchain: Keil MDK-ARM Plus 5.33.0.0; ArmClang/Link v6.15

Casting solution came from here: Casting between void * and a pointer to member function

Another way of casting is:

using os_fp                       = void ( * ) ( void * );
void ( task::*pTimer ) ( void * ) = &task::rtos_timer_callback;
void * task_timer                 = ( void*& ) pTimer;

auto timer_id = osTimerNew
(
    reinterpret_cast<os_fp>(task_timer),
    osTimerPeriodic,
    this,    // let RTOS know about the object
    nullptr
);

Source: Get memory address of member function?

4
On

From an object oriented point of, I would suggest that you are addressing this is the wrong way. There as no direct relationship between a thread and a timer that indicates that they should be a in a single "class" and it is not a matter of mechanistically "mapping" functions to classes. Rather you need to identify the classes - i.e. the things you want to instantiate objects of, and then define the interfaces - the methods that define the functions and capabilities of those objects.

To that end, I would suggest that a thread (or task) and a timer are separate classes. You might create a higher level class of a periodic task that might then by composed and/or derived from these other classes. For example:

enter image description here

or

enter image description here

Let us consider the cTask class to start with. It would be wrong (or at least pointless) to simply wrap the osThreadNew() function in a class wrapper; rather you need to think a a task as a class and consider all the things that class may do. To that end, the CMSIS RTOS reference provides some inspiration in the organisation of its documentation. It has a section on Thread Management and Thread Flags that can be use to design the cTask interface.

A simple task class might have the following interface foir example:

    class cTask
    {
        public:
            typedef uint32_t tEventFlags ;  
            
            cTask();
            virtual ~cTask();

            eOSStatus spawn( const char* taskname, 
                             int taskpriority = DEFAULT_PRIORITY, 
                             int stack_size = DEFAULT_STACK, void* stack_ptr = 0 );

            void setEvent( tEventFlags flags ) const ;
            static void delay(int period);
            static void lock();
            static void unlock();

            int getPriority() const ;
            int setPriority(int new_priority);

    private :
            virtual void threadMain() = 0 ;
            tEventFlags eventWait( tEventFlags flags, int timeout ) ;

            static void entry_point( void* arg )
            { 
                cTask* instance = reinterpret_cast<cTask*>(argv) ;
                instance->threadMain() ;
            }
} ;

And you might then have a task:

class cMyThread : cTask()
{
    public :
        cMyThread()
        {
            spawn( "mythread" ) ;
        }

        void someEvent()
        {
            setEvent( 0x0001 ) ;
        }

        void someOtherEvent()
        {
            setEvent( 0x0002 ) ;
        }

    private: 
    
        void threadMain()
        {
            for(;;)
            {
                tEventFlags event eventWait( 0x0003, WAIT_FOREVER ) ;
                if( (event & 0x0001) != 0  )
                {
                    // process some event
                } 

                if( (event & 0x0002) != 0  )
                {
                    // process some other event
                } 
            }
        }
} ;

Such that you might instantiate and communicate with instance od cMyThread thus:

    cMyThread thread_a ;
    cMyThread thread_b ;

    thread_a.someEvent() ;
    thread_b.someOtherEvent() ;

Obviously the interface could be much more extensive, and you would want to add classes for semaphores, mutexes, message queues as well as timers.

The above is illustrative only; as you can see there is a lot of work perhaps to be done, but to answer your question osThreadNew()would be used here to implementcTask::spawn()and would start thecTask::threadMain()via the staticentry_point()function by passing it thethis` pointer.

You would take a similar approach to the cTimer class with respec to defining teh interface in terms of things a timer can do. such as start, cancel, wait, set event handler etc.

It is not necessary to slavishly provide an interface to every CMSIS RTOS function; the C++ layer provides an opportunity to abstract some of that detail into something easier to use and easier to port to some other RTOS API.