X-Ryl669 / eMQTT5

An embedded MQTTv5 client in C++ with minimal footprint, maximal performance
MIT License
65 stars 14 forks source link

Porting to Renesas MCU without OS #11

Open krisnaresi opened 1 year ago

krisnaresi commented 1 year ago

Hi,

I tried to port it to RA6M5 Renesas MCU. In Types.hpp, there is several pre-processors macro such as _WIN32, _POSIX, and ESP_PLATFORM. As RA6M5 is not fall under one of them, I'm not defining anything. When I tried to compile it, there is requirement to include pthreadRTOS.h. Even after I make empty header file just to pass compilation, at the end of the day it still requires me to define pthread_t.

On README.md, it said that This code is specialized for embedded system with or **without** an operating system. AFAIK, to add thread into embedded system, we need to put Real Time OS (FreeRTOS, Azure RTOS, etc). So, my question is, how can I compile it without RTOS dependencies? Is there any pre-processor that I need to define? Or did I misinterpret something? Thank you for your assistance.

X-Ryl669 commented 1 year ago

Threading isn't required, and in fact no OS primitive is used (except for network). However, as you've found, you need to define few primitives for the library to work:

  1. You need to define types. eMQTT5 expect types of specific length, so it can't rely on int or long to expect 32 bits or 64 bits integer.
  2. Locking and atomic primitives. While it doesn't requires threads to work, the code is generic and if used in multithread environment, the shared access are protected by Lock and Mutex. You can define then to a plain wrapper over a boolean if you don't need them (see MQTTClient.cpp around line 347 for an implementation with atomic boolean).
  3. Basic lib C functions.
  4. Network primitives (if you use LWIP, you'll have to redirect the include to LWIP primitives)
  5. Break into Debugger (if building in Debug mode)

Defining the types:

If I were you, I would try to find a specific preprocessor definition for your platform (something your compiler defines that other compiler don't. For example, on linux, we have _LINUX or ESP_PLATFORM on ESP32. At worst, you can add a -DRA6M5 to your compiler flags.

Typically, in Types.hpp look at the line 219 for the definition of ESP32 platform and write something equivalent at line 268:

#elif defined(RA6M5)
    #include <stdlib.h>
    #include <string.h>
    // We need BSD socket here and TCP_NODELAY
    #include <whereis/socket.h> // We need close, connect, bind, setsockopt, getsockopt, fcntl 
    // We need gethostbyname or getaddrinfo
    #include <whereis/netdb.h>
    // We need va_args for printf like dump functions
    #include <stdarg.h>

    #ifndef DontWantUINT8
        typedef unsigned char uint8;
    #endif
    #ifndef DontWantUINT32
        typedef unsigned int uint32;
    #endif
    #ifndef DontWantUINT16
        typedef unsigned short uint16;
    #endif

    #ifndef DontWantUINT64
        typedef unsigned long long uint64;
    #endif

    #ifndef DontWantINT8
        typedef signed char int8;
    #endif
    #ifndef DontWantINT32
        typedef signed int int32;
    #endif
    #ifndef DontWantINT16
        typedef signed short int16;
    #endif

    #ifndef DontWantINT64
        typedef long long int64;
    #endif
    #ifndef DontWantNativeInt
        typedef intptr_t nativeint;
    #endif

    #define PF_LLD  "%lld"
    #define PF_LLU  "%llu"

    /** Unless you run on embedded OS, you'll not need those functions
        @return int value you need to pass to leaveAtomicSection */
    extern "C" int enterAtomicSection() { return 0; } // You might need `asm volatile("": : :"memory")` here, if you use GCC
    /** Unless you run on embedded OS, you'll not need those functions */
    extern "C" void leaveAtomicSection(int) { }

#else // Previously: line 268

Debugging

If you need to debug your code, the library will call breakUnderDebugger() on assert. It's defined in Platform.hpp. Without an OS, it can't capture the frame and callback. Yet, you can add a section like this:

#elif defined (RA6M5)
            *(char*)42 = 0; // This will crash the CPU instantly. If the debugger is attached, this should trigger an exception, you need to find the return address for this function on the stack and jump back to the return address to find where it failed, or place a breakpoint on this line, skip the instruction and return step by step to find where it failed.
    #else

Wrapping the threads

In Types.hpp around line 363, you will have to replace this:

#ifdef _WIN32
    typedef HANDLE              HTHREAD;
    typedef HANDLE              HMUTEX;
    typedef HANDLE              OEVENT;
#else
    typedef pthread_t           HTHREAD;
    typedef pthread_mutex_t     HMUTEX;
    typedef pthread_mutex_t     OEVENT;
#endif

to this:

#ifdef _WIN32
    typedef HANDLE              HTHREAD;
    typedef HANDLE              HMUTEX;
    typedef HANDLE              OEVENT;
#elif defined(RA6M5)
   // This isn't used anywhere anyway
   typedef void * HTHREAD;
   typedef int HMUTEX;
   typedef int OEVENT;
#else
    typedef pthread_t           HTHREAD;
    typedef pthread_mutex_t     HMUTEX;
    typedef pthread_mutex_t     OEVENT;
#endif

That should be it. Once you have a working port, feel free to create a PR, I'll happily merge it.