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
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:
or
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 implement
cTask::spawn()and would start the
cTask::threadMain()via the static
entry_point()function by passing it the
this` 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.
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
);