Smart Remote 3 - Firmware Architecture Replication SDK 16.0.0

I recently came across the Smart Remote 3 product by Nordic.  I currently have a product in development that is a remote that has very similar functionality.  Even down to the key scan chip SX1509.  I just developed a shield to go with the nRF52840 DK that has all the key components laid out to develop against.  I have been using the Adafruit Bluefruit52 Library but find it's limitations when I must be able to debug and also implement OTA DFU for this more complex project.

I find the Smart Remote 3 architecture design to be what I need to follow.  I have the source code for the Smart Remote 3 but it uses the SDK 14.2.  It's day one of development and I'm starting the journey with the scheduler task and already ran into an issue with SDK version differences.  To continue on with the design of a background and foreground scheduler, what does Nordic recommend since this "app_isched.h" source code isn't compatible with SDK 16.0.0? With SDK 16.0.0, do I even need this level of scheduling now and is the priority of handling the BLE Stack events already managed for me and I can just use app_timers to do my work? Does the SDK scheduler have priority event options?  Are there any updated scheduler examples using SDK 16.0.0?  Should I consider FreeRTOS as a replacement solution?  I think that will be a over complicated solution for what I need and think I will have better support using components from the SDK.  If so are there any examples that implement similar solution as the Smart Remote?  I plan to use this thread for my adventures through this firmware and support.  It's my first production project using Segger Studio and none Arduino based project.  I'm learning curve will be step in the begining.

Thanks

- Matt

  • Hi,

    I find the Smart Remote 3 architecture design to be what I need to follow.  I have the source code for the Smart Remote 3 but it uses the SDK 14.2.  It's day one of development and I'm starting the journey with the scheduler task and already ran into an issue with SDK version differences.

    The Smart Remote 3 reference design is quite complex, so migrating to SDK 16 will be a bit of work. If you want to use the Smart Remote design I suggest you stick with SDK 14.2 unless you have a compelling reason for migrating.

    To continue on with the design of a background and foreground scheduler, what does Nordic recommend since this "app_isched.h" source code isn't compatible with SDK 16.0.0? With SDK 16.0.0, do I even need this level of scheduling now and is the priority of handling the BLE Stack events already managed for me and I can just use app_timers to do my work?

    I cannot say without knowing more about your application. Are you trying to migrate the SmartRemote 3, or not? If your own design, what are the requirements?

    Does the SDK scheduler have priority event options?

    No. The SDK scheduler is a very simple scheduler without priorities. It's only task is to move execution from an interrupt context to the main thread. 

    Are there any updated scheduler examples using SDK 16.0.0?

    No. But the scheduler has not changed significantly for a long time. You can refer to the scheduler tutorial for an example (though not specifically for SDK 16).

    Should I consider FreeRTOS as a replacement solution?

    Only if you need an RTOS (complex scheduler, event queues, etc). Using FreeRTOS can make complex designs simpler, but also makes simpler designs more complex. So you need to see if it is right for your design.

  • Einar,

    I have decided to only replicate some of the high level design concepts of the Smart Remote.  I had a 3 month pause on the project but now I'm back on it. I am having an issue with the Event Bus where when I submit an event it is allocating memory for it through a BALLOC pool.  When the application scheduler wakes up and process the event the point that is associated with the event has corrupted or partial data.  I do you have any suggestions of what could be causing this?  At first I thought it could have been the union structure in the event struct but can't locate the issue.  Thanks for your guidence.

    #ifndef __M_EVENT_BUS_H__
    #define __M_EVENT_BUS_H__
    
    // System Includes
    #include <stdint.h>
    #include <stdbool.h>
    
    // nRF SDK Includes
    #include "sdk_errors.h"
    
    /**@brief Translate event type to event group. */
    #define EVENT_GROUP(_event_type) (event_group_t)((unsigned long)(_event_type) >> 8)
    
    /**@brief Event groups. */
    typedef enum
    {
        EVT_GROUP_SYSTEM,       /**< System state events. */
        EVT_GROUP_KEYPAD,       /**< Keypad input events. */
        EVT_GROUP_HID,          /**< HID report events. */
        EVT_GROUP_BT,           /**< Bluetooth events. */
        EVT_GROUP_DISPLAY,      /**< Display events. */
        EVT_GROUP_HAPTIC,       /**< Haptic events */
    } event_group_t;
    
    /**@brief Event types. */
    typedef enum
    {
        /* System Events */
        EVT_SYSTEM_BATTERY_LEVEL    = (EVT_GROUP_SYSTEM << 8),
    
        /* Keypad input events */
        EVT_KEY_PRESS               = (EVT_GROUP_KEYPAD << 8),
    
        /* HID report events */
        EVT_HID_REPORT_INPUT        = (EVT_GROUP_HID << 8),
        EVT_HID_REPORT_OUTPUT,
        EVT_HID_REPORT_FEATURE,
    
        /* Bluetooth events */
        EVT_BT_CONN_STATE           = (EVT_GROUP_BT << 8),
        EVT_BT_ADV_STATE,
        EVT_BT_ALERT_LEVEL,
        EVT_BT_DFU_STATE,
        EVT_BT_SIG_STRENGTH,
    
        /* Display events */
        EVT_DISPLAY_REFRESH         = (EVT_GROUP_DISPLAY << 8),
    
        /* Haptic Events */
        EVT_HAPTIC_STATE            = (EVT_GROUP_HAPTIC << 8),
    
    } event_type_t;
    
    typedef struct
    {
        event_type_t type;
    
        union {
            // Valid for EVT_GROUP_SYSTEM.
            struct {
                uint8_t    data;
            } system;
    
            // Valid for EVT_GROUP_KEYPAD.
            struct {
                uint8_t    code;
                uint8_t    action_type;
            } keypad;
    
            // Valid for EVT_GROUP_HID,
            struct {
                uint32_t   usage;
                int16_t    report;
            } hid;
    
            // Valid for EVT_GROUP_BT,
            struct {
                uint8_t    data;
                uint16_t   conn_id;
                uint16_t   peer_id;
            } bt;
        };
    } event_t;
    
    // EVT_BT_CONN_STATE data
    #define BT_CONN_STATE_DISCONNECTED      0
    #define BT_CONN_STATE_CONNECTED         1
    #define BT_CONN_STATE_SECURED           2
    
    // EVT_BT_ADV_STATE data
    #define BT_ADV_STATE_IDLE               0
    #define BT_ADV_STATE_ACTIVE             1
    #define BT_ADV_STATE_ACTIVE_BONDABLE    2
    
    // EVT_BT_SIG_STRENGTH data
    #define BT_SIG_STRENGTH_1               1
    #define BT_SIG_STRENGTH_2               2
    #define BT_SIG_STRENGTH_3               3
    #define BT_SIG_STRENGTH_4               4
    #define BT_SIG_STRENGTH_5               5
    
    /**@brief Event handler.
     *
     * @param[in]   p_event Pointer to the event structure.
     *
     * @return      True if further processing of the given event should be abandoned.
     */
    typedef bool (*event_handler_t)(const event_t *p_event);
    
    
    /**@brief Function for initializing the event bus.
     *
     * @param[in]   event_handlers_table    Table with event handlers.
     *
     * @return      NRF_SUCCESS on success, otherwise an error code.
     */
    ret_code_t event_bus_init(const event_handler_t *event_handlers_table);
    
    
    /**@brief Function for sending an event.
     *
     * @param[in]   event_type  Event type.
     * @param[in]   ...         Event data.
     *
     * @note                    Event data elements are mapped one to one to data fields in the event structure.
     *
     * @return      NRF_SUCCESS on success, otherwise an error code.
     */
    ret_code_t event_send(event_type_t event_type, ...);
    
    #endif /* __M_EVENT_BUS_H__ */

    #include "m_event_bus.h"
    
    // System Includes
    #include <stdarg.h>
    
    // nRF SDK Includes
    #include "nrf_balloc.h"
    #include "app_scheduler.h"
    
    #include "app_config.h"
    #include "resources.h"
    
    #define NRF_LOG_MODULE_NAME m_event_bus
    #define NRF_LOG_LEVEL       CONFIG_EVENT_BUS_LOG_LEVEL
    #include "nrf_log.h"
    NRF_LOG_MODULE_REGISTER();
    
    static const event_handler_t   *sp_event_handlers;  /**< Pointer to the event handlers table. */
    
    // Define event allocator.
    NRF_BALLOC_DEF(s_event_pool, sizeof(event_t), CONFIG_EVENT_POOL_SIZE);
    
    #if CONFIG_EVENT_MONITOR_ENABLED
    /**@brief Translate event type to string. */
    static const char *event_type_str(const event_t *p_event) {
        ASSERT(p_event != NULL);
    
        switch (p_event->type) {
            #define EVT2STR(_event_type); case _event_type: return #_event_type
                EVT2STR(EVT_SYSTEM_BATTERY_LEVEL)
                EVT2STR(EVT_KEY_PRESS);
                EVT2STR(EVT_BT_CONN_STATE);
                EVT2STR(EVT_BT_ADV_STATE);
                EVT2STR(EVT_BT_ALERT_LEVEL);
                EVT2STR(EVT_BT_DFU_STATE);
                EVT2STR(EVT_BT_SIG_STRENGTH);
                EVT2STR(EVT_DISPLAY_REFRESH);
                EVT2STR(EVT_HAPTIC_STATE);
            #undef EVT2STR
    
            default:
                return "UNKNOWN";
        }
    }
    
    /**@brief Dump event in human-readable format. */
    static void event_dump(const event_t *p_event) {
        uint32_t hold_time;
    
        switch (EVENT_GROUP(p_event->type)) {
            case EVT_GROUP_SYSTEM:
                NRF_LOG_INFO("< %s: %u >", (uint32_t)event_type_str(p_event), p_event->system.data);
                break;
    
            case EVT_GROUP_KEYPAD:
                NRF_LOG_INFO("< %s: Code[0x%X] Action[0x%X] >",
                          (uint32_t)event_type_str(p_event),
                          p_event->keypad.code,
                          p_event->keypad.action_type);
                break;
    
            case EVT_GROUP_HID:
                NRF_LOG_INFO("< %s: 0x%04X:0x%04X %d >", (uint32_t)event_type_str(p_event),
                             HID_USAGE_PAGE(p_event->hid.usage), HID_USAGE_ID(p_event->hid.usage),
                             p_event->hid.report);
                break;
    
            case EVT_GROUP_BT:
                NRF_LOG_INFO("< %s: %u 0x%04X 0x%04X >",
                             (uint32_t)event_type_str(p_event),
                             p_event->bt.data,
                             p_event->bt.conn_id,
                             p_event->bt.peer_id);
                break;
    
            case EVT_GROUP_DISPLAY:
                NRF_LOG_INFO("< %s >", (uint32_t)event_type_str(p_event));
                break;
    
            case EVT_GROUP_HAPTIC:
                NRF_LOG_INFO("< %s >", (uint32_t)event_type_str(p_event));
                break;
    
            default:
                NRF_LOG_INFO("< UNKNOWN EVENT: 0x%02X >", p_event->type);
                break;
        }
    }
    #endif
    
    /**@brief Process the event. */
    static void event_process(void *p_event_data, uint16_t event_size) {
        const event_handler_t *p_handler = sp_event_handlers;
        event_t *p_event = p_event_data;
    
        ASSERT((p_event != NULL) && (p_handler != NULL));
    
        #if CONFIG_EVENT_MONITOR_ENABLED
        if (CONFIG_EVENT_MONITOR_TYPES & (1ul << EVENT_GROUP(p_event->type))) {
            event_dump(p_event);
        }
        #endif
    
        // Pass the event to handlers.
        while (*p_handler != NULL) {
            if ((*p_handler++)(p_event)) {
                // Stop event processing if handler returned true.
                #if CONFIG_EVENT_MONITOR_ENABLED
                if (CONFIG_EVENT_MONITOR_TYPES & (1ul << EVENT_GROUP(p_event->type))) {
                    NRF_LOG_INFO("< ^^^^ EVENT CONSUMED ^^^^ >");
                }
                #endif
                break;
            }
        }
    
        nrf_balloc_free(&s_event_pool, p_event);
    }
    
    /**@brief Function for sending an event. */
    ret_code_t event_send(event_type_t event_type, ...) {
        ret_code_t status;
        event_t *p_event;
        va_list args;
    
        p_event = nrf_balloc_alloc(&s_event_pool);
        if (p_event == NULL) {
            NRF_LOG_WARNING("%s(): WARNING: Allocation error: Event lost!", (uint32_t)__func__);
            APP_ERROR_CHECK_BOOL(CONFIG_EVENT_FORCE_ERROR_CHECKING);
            return NRF_ERROR_NO_MEM;
        }
    
        va_start(args, event_type); /**< Initialize a variable argument list */
        p_event->type = event_type;
    
        switch (EVENT_GROUP(p_event->type))
        {
            case EVT_GROUP_SYSTEM:
                p_event->system.data = va_arg(args, uint32_t);
                break;
    
            case EVT_GROUP_KEYPAD:
                p_event->keypad.code = va_arg(args, uint32_t);
                p_event->keypad.action_type = va_arg(args, uint32_t);
                break;
    
            case EVT_GROUP_HID:
                p_event->hid.usage = va_arg(args, uint32_t);
                p_event->hid.report = (int16_t)va_arg(args, int);
                break;
    
            case EVT_GROUP_BT:
                p_event->system.data = 2;
                p_event->bt.data = va_arg(args, uint32_t);
                p_event->bt.conn_id = va_arg(args, uint32_t);
                p_event->bt.peer_id = va_arg(args, uint32_t);
                break;
    
            case EVT_GROUP_DISPLAY:
                break;
    
            case EVT_GROUP_HAPTIC:
                break;
    
            default:
                APP_ERROR_CHECK_BOOL(false);
                break;
        }
    
        va_end(args); /**< End using variable argument list */
    
        //event_dump(p_event);
        status = app_sched_event_put(p_event, sizeof(p_event), event_process);
        if (status != NRF_SUCCESS) {
            nrf_balloc_free(&s_event_pool, p_event);
    
            NRF_LOG_WARNING("%s(): WARNING: Scheduling error: Event lost!", (uint32_t)__func__);
            APP_ERROR_CHECK_BOOL(CONFIG_EVENT_FORCE_ERROR_CHECKING);
        }
    
        return status;
    }
    
    /**@brief Function for initializing the event bus. */
    ret_code_t event_bus_init(const event_handler_t *event_handlers_table) {
        if (event_handlers_table == NULL) {
            return NRF_ERROR_INVALID_PARAM;
        }
    
        sp_event_handlers = event_handlers_table;
    
        return nrf_balloc_init(&s_event_pool);
    }

Related