MCU Coding Tips

Please factor in the following information when writing your MCU code:

If you need code examples that use the deprecated C++ afLib, download the archive file (.zip).

If you need code examples that use afLib2, download the archive file (.zip).


Don’t Forget to Call af_lib_loop()

This is very basic, but critical: when you write MCU code using afLib3, you must call af_lib_loop() to give afLib3’s state machine time to execute. Without this, afLib3 won’t run, and your code will not interact properly with the Afero Platform components.

You should definitely make a call to af_lib_loop() in your code’s loop() method; and you can call it elsewhere, as well, if you need to ensure that afLib3 is executing frequently enough to support your application.

#include <SPI.h>

#include "af_lib.h"
#include "arduino_spi.h"
#include "arduino_uart.h"
#include "af_module_commands.h"
#include "af_module_states.h"
#include "arduino_transport.h"

// Pin Defines assume Arduino Uno
#define CS_PIN                 10
#define INT_PIN                 2

af_lib_t* af_lib = NULL;

// Callback executed any time ASR has information for the MCU
void attrEventCallback(const af_lib_event_type_t eventType,
                       const af_lib_error_t error,
                       const uint16_t attributeId,
                       const uint16_t valueLen,
                       const uint8_t* value) {
    // Any application-specific actions
}

void setup() {
    Serial.begin(115200); 

    // Initialize afLib
    af_transport_t* arduinoSPI = arduino_transport_create_spi(CS_PIN);
    af_lib = af_lib_create_with_unified_callback(attrEventCallback, arduinoSPI);
    arduino_spi_setup_interrupts(af_lib, digitalPinToInterrupt(INT_PIN));
}

void loop() {
    // Any application-specific actions

    // CRITICAL: Give afLib3 processing time by calling af_lib_loop() in sketch’s loop()
    af_lib_loop(af_lib);
}

Notes:

  • afLib3 requires time slices to process attribute writes.
  • af_lib_loop() performs small amounts of work every time it’s called. A multi-threaded process is emulated on a single-threaded MCU.
  • Call af_lib_loop() as often as possible in your code. If afLib3 has little to do, it returns quickly. Some complex calls, like setAttribute, require multiple loop() calls to finish.
  • IMPORTANT! Calling af_lib_loop() from within the attrEventCallback callback is NOT supported and will likely result in undesired behavior.
  • When afLib3 is finished with an important task, you’ll get a notify event via attrEventCallback(). Within that callback, you'll want to write code for any/all potential eventTypes that you'll receive. Here's an example switch statement:
    switch (eventType) {
        case AF_LIB_EVENT_UNKNOWN:
            // Useful for debugging
            break;
    
        case AF_LIB_EVENT_ASR_SET_RESPONSE:
            // Response to af_lib_set_attribute() for an ASR attribute
            break;
    
        case AF_LIB_EVENT_MCU_SET_REQ_SENT:
            // Request from af_lib_set_attribute() for an MCU attribute has been sent to ASR
            break;
    
        case AF_LIB_EVENT_MCU_SET_REQ_REJECTION:
            // Request from af_lib_set_attribute() for an MCU attribute was rejected by ASR break;
    
        case AF_LIB_EVENT_ASR_GET_RESPONSE:
            // Response to af_lib_get_attribute() 
            break;
    
        case AF_LIB_EVENT_MCU_DEFAULT_NOTIFICATION:
            // Unsolicited default notification for an MCU attribute
            break;
    
        case AF_LIB_EVENT_ASR_NOTIFICATION:
            // Unsolicited notification of non-MCU attribute change
            switch (attributeId) {
                case AF_SYSTEM_ASR_STATE:
                    // This is where you'll watch for System State changes!
                    switch (value[0]) {
                        case AF_MODULE_STATE_REBOOTED:
                            break;
    
                        case AF_MODULE_STATE_LINKED:
                            break;
    
                        case AF_MODULE_STATE_UPDATING:
                            break;
    
                        case AF_MODULE_STATE_UPDATE_READY:
                            break;
    
                        case AF_MODULE_STATE_INITIALIZED:
                            break;
    
                        case AF_MODULE_STATE_RELINKED:
                            break;
                    }
                    break;
            }
            break;
    
        case AF_LIB_EVENT_MCU_SET_REQUEST:
            // Request from ASR to MCU to set an MCU attribute, requires a call to af_lib_send_set_response()
            break;
    
    }
  • Make sure afLib3 has time to complete its work. When doing multiple af_lib_set_attribute*() calls, ideally wait for attrEventCallback before doing additional writes.
  • If you write faster than afLib3 can process, the queue may fill up, and the return code will be:
    AF_ERROR_QUEUE_OVERFLOW   -4  // Queue is full
  • Always check the return code from afLib3 calls. Check for return of AF_SUCCESS.

Adjust the Request Queue Size If Necessary

You may notice that the return code from some afLib3 calls in your MCU code indicates a full request queue (AF_ERROR_QUEUE_OVERFLOW = -4). If you observe this, you should consider increasing the size of the request queue. This value is defined as the REQUEST_QUEUE_SIZE in af_Lib.h. The default value is 10, but you can safely change that value within the bounds of available memory on your platform.

Watch Your Memory Usage

If you’ve worked much with Arduino, this tip is probably not news to you: Memory is limited, and if your MCU code exceeds available memory, your output can be quite puzzling. We have often encountered bizarre – but seemingly error-free – behavior from a sketch running on Uno, only to find that the same code runs entirely as expected on Teensy. This is particularly useful to remember in the context of the Useful Debugging Methods provided elsewhere; use of those methods requires significant memory, and will be difficult using Uno.

See Also

Tutorial 3: Afero + Arduino