This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

multiple instances of ITC objects in CMSIS-RTOS

Hello,

I'd like to write some reusabel code module to handle e.g. UART ports. Therefore I'd like to create a variable number (based on some kind of define...) of threads dealing with UART port 1,2,3 ... since it is possible to declare CMSIS-RTOS threads with a variable number of instances it could look like this:

#define UART_PORT_NUM 3

osThreadDef(uart_thread, osPriorityNormal, UART_PORT_NUM, 0)

my problem: If I need any kind of inter-thread communication (ITC) object, like mutexes or mail queues, for each of this threads I don't know how to declare them, since osMailQDef, osMutexDef and so on, are not designed with a "variable" number of instances...

Any suggestions?

Parents
  • Here is 1 exammple - very specific to Keil's CMSIS RTOS implementation.

    namespace OsRTX
    {
        // uint32_t os_mutex_cb_##name[4] = { 0 }; becomes..
        struct Mutex { uint32_t _private_[4]; };
    }
    
    class OsMutex
    {
    private:
        OsRTX::Mutex m_rtxMutex;
        osMutexId m_MutexId;
    
        void Create()
        {
            osMutexDef_t temp; // Note: not all osDef_t items can be temporary
            temp.mutex = &m_rtxMutex;
            m_MutexId = osMutecCreate( (const osMutexDef_t *) &temp);
        }
    public:
        OsMutex()       { Create(); }  // Initialized at construction!
                                       // Note: not all os items can be initialized
                                       // before the OS is running.
        ~OsMutex() {}
        osStatus Wait(uint32_t millis) { return osMutexWait(m_MutexId, millis); }
        osStatus Release()             { return osMutexRelease(m_MutexId); }
    };
    
    // 2 Instances of Mutex Objects
    
    OsMutex Uart1Mutex;
    osMutex Uart2Mutex;
    
    // Usage
    
    status = Uart1Mutex.Wait(100);
    status = Uart1Mutex.Release();
    

    You can do something like this

    class Uart : public UartBase
    {
        UART* m_uart;
        OsMutex m_mutex;
    public:
        Uart(UART* uart) : m_uart(uart) {}
        ~Uart() {}
        osStatus Acquire(uint32_t millis) { return m_mutex.Wait(millis); }
        osStatus Release()
    };
    
    Uart GPS_Uart(UART1);
    Uart GSM_Uart(UART2);
    Uart Debug_Uart(UART3);
    

    Combining MANY items you can

    
    Class GenericObjectUsingTaskAndUart
    {
        static const uint32_t mbxitems = 10;
        UartBase *m_uart;
        OsThread A1;
        OsThread A2;
                        // Usually the object IS-A thread, but if you want multiple threads
                        // tied together, you would have the class contain them...
        OsMbx<mbxitems,DataType_t> InQueue// Mailbox with 10 entries of type DataType_t
    public:
        GenericObjectUsingTaskAndUart(UartBase *uart) : m_uart(uart) {}
        ~GenericObjectUsingTaskAndUart() {}
        osStatus InitTask() { // Init and start threads, other stuff, that must happen
                              // After OS is running.  Could break into InitTask and Start
                            }
    
        OsMbxBase<DataType_t> *GetInQueue() { return &InQueue; }
    };
    
    // We now have an object that contains what it needs and knows about then, but is not
    // specifically tied to something
    
    GenericObjectUsingTaskAndUart Gen1(UART1);
    GenericObjectUsingTaskAndUart Gen2(UART2);
    GenericObjectUsingTaskAndUart Gen3(UART3);
    
    

    You may create objects for every os item type (mailbox,message,timer,thread)
    They can be generically combined in any way imagined (for good or bad)

    This is just an example for thought. I am not suggesting that it is a way anyone should use it. Just posting it as an actual answer the original question. I am certainly not requesting any kind of critique.

Reply
  • Here is 1 exammple - very specific to Keil's CMSIS RTOS implementation.

    namespace OsRTX
    {
        // uint32_t os_mutex_cb_##name[4] = { 0 }; becomes..
        struct Mutex { uint32_t _private_[4]; };
    }
    
    class OsMutex
    {
    private:
        OsRTX::Mutex m_rtxMutex;
        osMutexId m_MutexId;
    
        void Create()
        {
            osMutexDef_t temp; // Note: not all osDef_t items can be temporary
            temp.mutex = &m_rtxMutex;
            m_MutexId = osMutecCreate( (const osMutexDef_t *) &temp);
        }
    public:
        OsMutex()       { Create(); }  // Initialized at construction!
                                       // Note: not all os items can be initialized
                                       // before the OS is running.
        ~OsMutex() {}
        osStatus Wait(uint32_t millis) { return osMutexWait(m_MutexId, millis); }
        osStatus Release()             { return osMutexRelease(m_MutexId); }
    };
    
    // 2 Instances of Mutex Objects
    
    OsMutex Uart1Mutex;
    osMutex Uart2Mutex;
    
    // Usage
    
    status = Uart1Mutex.Wait(100);
    status = Uart1Mutex.Release();
    

    You can do something like this

    class Uart : public UartBase
    {
        UART* m_uart;
        OsMutex m_mutex;
    public:
        Uart(UART* uart) : m_uart(uart) {}
        ~Uart() {}
        osStatus Acquire(uint32_t millis) { return m_mutex.Wait(millis); }
        osStatus Release()
    };
    
    Uart GPS_Uart(UART1);
    Uart GSM_Uart(UART2);
    Uart Debug_Uart(UART3);
    

    Combining MANY items you can

    
    Class GenericObjectUsingTaskAndUart
    {
        static const uint32_t mbxitems = 10;
        UartBase *m_uart;
        OsThread A1;
        OsThread A2;
                        // Usually the object IS-A thread, but if you want multiple threads
                        // tied together, you would have the class contain them...
        OsMbx<mbxitems,DataType_t> InQueue// Mailbox with 10 entries of type DataType_t
    public:
        GenericObjectUsingTaskAndUart(UartBase *uart) : m_uart(uart) {}
        ~GenericObjectUsingTaskAndUart() {}
        osStatus InitTask() { // Init and start threads, other stuff, that must happen
                              // After OS is running.  Could break into InitTask and Start
                            }
    
        OsMbxBase<DataType_t> *GetInQueue() { return &InQueue; }
    };
    
    // We now have an object that contains what it needs and knows about then, but is not
    // specifically tied to something
    
    GenericObjectUsingTaskAndUart Gen1(UART1);
    GenericObjectUsingTaskAndUart Gen2(UART2);
    GenericObjectUsingTaskAndUart Gen3(UART3);
    
    

    You may create objects for every os item type (mailbox,message,timer,thread)
    They can be generically combined in any way imagined (for good or bad)

    This is just an example for thought. I am not suggesting that it is a way anyone should use it. Just posting it as an actual answer the original question. I am certainly not requesting any kind of critique.

Children
  • Ok, that's what I need, thank you! I have to adapt it (I forgot to mention, that I am using C, the mentioned C++ thread was just to demonstrate, that there is a need for such an implementation)
    I was also thinking about avoiding the typical osXyzDef() macros and to do all the stuff manually.
    The only question, which you also have mentioned in your comment: Is it really save/recommendable to create the osXyzDef_t structures (e.g. osMutexDef_t) temporary? The macros are creating them as const, so I thought, the kernel needs it that way.

  • See my example for just supplying a struct to the tread, informing it what queues etc to associate with.

  • It is not at all safe to just make them temporary.

  • I had a similar need for reusable code involving mutexes. The way the macros are defined in
    cmsis_os.h make that difficult, but after some study it appears all that's needed is a
    control block of uint32_t[4] and a temporary pointer.

    The osMutexDef(x) goes away, as does osMutex(x). I've replaced those with:

    typedef struct _osMutexCb {
       uint32_t    cb[4];     // this is the actual control block
       void        *pCb;      // pointer to the control block. Only needed during osMutexCreate
    } osMutexCb_t;
    

    You should be able to allocate space for that statically or dynamically, as many of
    them as you need.

    I've created a wrapper for the create. This could also be done as a macro or inline, but I
    have some portability issues with the latter.

    osMutexId osMutexCreate2(osMutexCb_t *MutexCb) {
       MutexCb->pCb = (void *) MutexCb->cb;
       return osMutexCreate((const osMutexDef_t *) &MutexCb->pCb);
    }
    

    The other mutex functions only use the osMutexId (which is just a pointer to the control
    block, so why couldn't osMutexCreate just use it in the first place?). Duh.

    That's an awfully lot of monkey motion for a relatively simple thing. It's also a redundant
    level of indirection where we're sending osMutexCreate a pointer to a pointer. In the
    standard cmsis syntax using osMutexDef macro, that pointer space is only used once for the
    create. I tested this using an auto variable, demonstrating it does not need to be retained.

    The old Keil programmers would have never permanently wasted those 4 bytes, or the redundant
    level of indirection. They were careful of CPU clocks and space efficiency.

    I've seen some claims posted here that the standard osMutexDef() and osMutex() macros make
    code more readable. Nonsense. Wrapping a simple block of storage in a macro that references
    a typedef is less readable, not more. It took several hours to conclude that all you needed
    was 16 bytes of storage and send osMutexCreate a pointer to a pointer to that storage
    and you're finished.

    I'm at a loss to understand why Keil created these macros that make it difficult to write
    reusable code. The only reasonable answer might be that programmers will be less likely
    to "lose" control blocks by deallocating them from dynamic memory, or some other accident.

    Ok. But they could have easily protected dumb programmers by retaining the control block
    within OS static memory and allocating it during mutex create. Yes, that would mean a fixed
    number of mutexes would be allowed, but that's no different than the static memory allocated
    for the stack, or the fixed number of threads, etc.

    The bottom line is cmsis should have been similar to other OSes, and allow programmers
    to control their own mutexes and other OS objects. This would make portability and
    re-learning issues easier. I'm sure whomever designed this thing believes they were doing
    someone a favor. Sorry, no. It made things much more difficult, and in the end I've just
    rewritten a chunk of cmsis_os.h to let me do what I need to do. No doubt I'll rewrite
    more of it. That's time I've wasted merely because the convoluted multi-layer syntax in
    cmsis_os_h wouldn't allow me to do what is easily done with other OSes.