Как создать класс задачи C ++ (рабочий) с потоком Keil CMSIS RTOS2 и функциями таймера C?

Как сопоставить концепцию класса C ++ с функциями osTimerNew () и osThreadNew () в C?

Как использовать функцию-член C ++ в качестве реализации обратного вызова osTimerNew () и osThreadNew () в Keil RTOS2.

Спасибо


person francek    schedule 04.06.2021    source источник


Ответы (2)


С объектно-ориентированной точки зрения я бы предположил, что вы обращаетесь к этому неверно. Нет прямой связи между потоком и таймером, которая указывает, что они должны быть в одном классе, и это не вопрос механического сопоставления функций с классами. Скорее вам нужно идентифицировать классы, то есть вещи, для которых вы хотите создать экземпляры объектов, а затем определить интерфейсы - методы, которые определяют функции и возможности этих объектов.

С этой целью я бы предложил, чтобы поток (или задача) и таймер были отдельными классами. Вы можете создать класс более высокого уровня для периодической задачи, который затем может быть составлен и / или получен из этих других классов. Например:

введите описание изображения здесь

or

введите описание изображения здесь

Давайте для начала рассмотрим класс 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 () _ 9_cTask :: spawn () _ 10_cTask :: threadMain () _ 11_entry_point () _ 12_this` указатель.

Вы бы применили аналогичный подход к классу cTimer с учетом определения интерфейса с точки зрения того, что может делать таймер. такие как запуск, отмена, ожидание, установка обработчика событий и т. д.

Нет необходимости рабски предоставлять интерфейс для каждой функции ОСРВ CMSIS; Слой C ++ дает возможность абстрагироваться от некоторых деталей во что-то более простое в использовании и более легкое для переноса на какой-либо другой API ОСРВ.

person Clifford    schedule 07.06.2021
comment
Несмотря на то, что мой вопрос не касается ООП («есть» против «имеет» и т. Д.), Ваш пример и объяснение - интересное чтение. Мой вопрос касается механики: как сообщить Keil CMSIS RTOS2, написанному / скомпилированному на C, об экземплярах класса, когда он вызывает функции обратного вызова, данные osThreadNew () / osTimerNew (). И этого можно достичь, передав «это» RTOS2 (как вы подтвердили). RTOS2, следуя соглашению о вызовах, помещает «this» в регистр ЦП R0 при вызове функции обратного вызова (которая просто является методом класса и, следовательно, интерпретирует значение в регистре R0 как «это» и обращается, например, к task_instance.m_id_thread). - person francek; 07.06.2021
comment
@francek По сути, вы можете вызывать обычные функции и статические функции-члены в качестве точек входа. Некоторые компиляторы или инструменты статического анализа могут выдавать предупреждение о вызове функции связывания C ++ через указатель функции связывания C, но обычно это не проблема. Вы, конечно, можете сделать функцию точки входа внешней по отношению к классу и связать ее с C, и при этом передать указатель this для вызова функции виртуальной задачи в объект, производный от cTask. - person Clifford; 07.06.2021
comment
@francek Если это не то, что вы искали, ваш вопрос должен быть более конкретным. Концепция класса C ++ карты ... может означать многое. Кажется, вы можете только спросить, как использовать функцию-член C ++ в качестве реализации обратного вызова задачи или таймера. Однако похоже, что это не то, о чем вы спрашивали, и имеет мало общего с классами как концепцией. - person Clifford; 07.06.2021
comment
Вы ответили на Q, особенно предложив ответ на ваш вопрос: будет использоваться osThreadNew () .... И я должен был сказать, что меня немного больше интересует механика, чем OOD. как использовать функцию-член C ++ в качестве реализации обратного вызова задачи или таймера. - отличная формулировка. - person francek; 08.06.2021

Вы передаете это osTimerNew () / osThreadNew () вместо параметра аргумента void *.

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

Теперь используйте класс задачи:

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%20https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSTime__RTML/group__CMSTime__RT.html

https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS__ThreadMgmt.html%20https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSTime__RTML/group__CMSTime__RT.html

и реализация:

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

Моя цепочка инструментов: Keil MDK-ARM Plus 5.33.0.0; ArmClang / Ссылка v6.15

Решение для кастинга пришло отсюда: Приведение между void * и указатель на функцию-член

Другой способ кастинга:

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

Источник: Получить адрес в памяти функции-члена?

person francek    schedule 04.06.2021
comment
Преобразование статической функции в функцию-член является мерзостью. Не делай этого. Это неопределенное поведение, и даже не стабильное. - person Frank; 04.06.2021
comment
Чтобы было ясно, здесь есть слои неопределенного поведения. Выполнение приведения через объединение псевдонимов - это UB, и необработанная идея передачи функции-члена в качестве обратного вызова C, как если бы это была свободно плавающая функция, тоже. - person Frank; 04.06.2021
comment
Я знаю Фрэнка. Этот довольно причудливый кастинг - последнее, что я пытаюсь найти более элегантное решение. Но все мы видели намного хуже, и этот код работает. Я уверен, что есть много людей, которые ищут «все, что работает» для выполнения работы, и они найдут это решение «достаточно хорошим». - person francek; 04.06.2021
comment
есть супер-простое решение: передать указатель на фактическую статическую функцию-член, которая принимает указатель в качестве параметра, а из этой статической функции reinterpret_cast<> указатель на тип класса и вызвать функцию-член. - person Frank; 04.06.2021
comment
спасибо за предложение, я попробую - person francek; 04.06.2021
comment
этот код работает. Он работает по чистой случайности. - person Frank; 04.06.2021
comment
чистая удача, я знаю (!!!), не так ли? C ++ потрясающий. Многие пытались и потерпели неудачу. Сказав это, можем ли мы рассчитывать на чисто образованную удачу? - person francek; 04.06.2021