1

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

francek
  • 492
  • 4
  • 11

2 Answers2

2

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.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Even though my Q is not about OOP ('is a' vs 'has a' etc.) your example and explanation is an interesting read. My Q is about mechanics : How to let Keil CMSIS RTOS2 written/compiled in C know about class instances when it calls callback functions given to osThreadNew()/osTimerNew(). And that can be achieved by giving 'this' to RTOS2 (as you confirmed). RTOS2 while following calling convention puts 'this' into CPU register R0 when calling callback function (which just happens to be class method and therefore interpreting value in register R0 as 'this' and access eg task_instance.m_id_thread ). – francek Jun 07 '21 at 18:12
  • 1
    @francek Essentially you can call regular functions and static member functions as entry points. Some compilers or static analysis tools may issue a warning about calling a C++ linkage function through a C linkage function pointer, but this is generally not a problem. You can of course make the entry point function external to the class and give it C linkage, and still pass the this pointer to call a virtual task function to a cTask derived object. – Clifford Jun 07 '21 at 18:35
  • @francek If this answer is not what you were looking for, then your question needs to be more specific. _"map C++ class concept..."_ could mean many things. It seems that you may only be asking how to use a C++ member function as a task or timer callback implementation. That seems not to be what you have asked however, and has little to do with classes as a "concept". – Clifford Jun 07 '21 at 18:41
  • You answered the Q, especially with the sentence "to answer your question osThreadNew() would be used ...". And I should have said "I am slightly more interested in mechanics than in OOD". "how to use a C++ member function as a task or timer callback implementation." - excellent wording. – francek Jun 08 '21 at 18:56
0

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?

francek
  • 492
  • 4
  • 11
  • That casting from a static function into a member function is an abomination. Don't do that. It's Undefined Behavior, and not even the "stable" kind. –  Jun 04 '21 at 17:31
  • To be clear, There's *layers* to the Undefined Behavior here. The execution of the cast via union aliasing is UB, and the raw idea of passing a member function as a C callback as if it was a free-floating function is as well. –  Jun 04 '21 at 17:34
  • I know Frank. That rather bizarre casting is last bit I struggle to find more elegant solution to. But we've all seen much worse and this code is working. I am sure there is many people who are looking for 'whatever works' to get job done and they will find this solution 'good enough'. – francek Jun 04 '21 at 18:36
  • 1
    there's a super-simple solution: Pass a pointer to an actual static member function that takes a pointer as a parameter, and from that static function, `reinterpret_cast<>` the pointer to the class's type and invoke the member function. –  Jun 04 '21 at 18:38
  • "this code is working" It's working out of sheer luck. –  Jun 04 '21 at 18:41
  • "sheer luck" i know (!!!), right ? C++ is amazing. Many tried and failed. Having said that, could we possibly settle on "sheer educated luck" ? – francek Jun 04 '21 at 19:54