微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何使用 Keil 的 CMSIS RTOS2 线程和计时器 C 函数创建 C++ 任务类工作线程?

如何解决如何使用 Keil 的 CMSIS RTOS2 线程和计时器 C 函数创建 C++ 任务类工作线程?

如何将 C++ 类概念映射到 C 函数 osTimerNew() 和 osThreadNew() ?

如何使用 C++ 成员函数作为 Keil RTOS2 osTimerNew() 和 osThreadNew() 回调实现。

谢谢

解决方法

从面向对象的角度来看,我建议您解决这个问题的方法是错误的。线程和计时器之间没有直接关系表明它们应该在单个“类”中,并且这不是机械地将函数“映射”到类的问题。相反,您需要确定 - 即您想要实例化对象的东西,然后定义接口 - 定义方法这些对象的功能和能力。

为此,我建议线程(或任务)和计时器是单独的类。您可以创建一个周期性任务的更高级别的类,然后可以组合和/或从这些其他类派生。例如:

enter image description here

enter image description here

让我们从 cTask 类开始。简单地将 osThreadNew() 函数包装在类包装器中是错误的(或至少毫无意义);相反,您需要将任务视为 class 并考虑该类可能执行的所有操作。为此,CMSIS RTOS 参考为其文档的组织提供了一些灵感。它有一个关于线程管理线程标志的部分,可用于设计cTask接口。

一个简单的任务类可能有以下接口示例:

    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() ;
            }
} ;

然后你可能会有一个任务:

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
                } 
            }
        }
} ;

这样你就可以实例化实例 od cMyThread 并与之通信:

    cMyThread thread_a ;
    cMyThread thread_b ;

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

显然,该接口可以更广泛,您可能希望为信号量、互斥体、消息队列和计时器添加类。

以上仅作说明;如您所见,可能还有很多工作要做,但要回答您的问题 osThreadNew()would be used here to implementcTask::spawn()and would start thecTask::threadMain()via the static entry_point()function by passing it thethis` 指针。

您将采用与 cTimer 类类似的方法,并根据计时器可以执行的操作来定义接口。例如启动、取消、等待、设置事件处理程序等

没有必要盲目地为每个 CMSIS RTOS 功能提供接口; C++ 层提供了一个机会,可以将某些细节抽象为更易于使用且更易于移植到其他 RTOS API 的内容。

,

您将“this”提供给 osTimerNew()/osThreadNew() 代替“void * argument”参数。

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 ),// 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 {};
};

现在使用任务类:

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
    }
};

并创建另一个任务:

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
    }
};

最后创建工人:

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 文档:

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

和实施:

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

我的工具链:Keil MDK-ARM Plus 5.33.0.0; ArmClang/Link v6.15

铸造解决方案来自这里:Casting between void * and a pointer to member function

另一种投射方式是:

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),// let RTOS know about the object
    nullptr
);

来源: Get memory address of member function?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。