Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Working with configuration data is a common task when creating Industrial Communication devices. Therefore, GOAL comes with a module allowing to generically create variables of different types for storing configuration data. Once created, the GOAL CM will handle the data for us including storing and loading those data to and from a nonvolatile memory.

The available data types are:

  • GOAL_CM_UINT8: an unsigned 8 bit value

  • GOAL_CM_UINT16: an unsigned 16 bit value

  • GOAL_CM_UINT32: an unsigned 32 bit value

  • GOAL_CM_INT8: a signed 8 bit value

  • GOAL_CM_INT16: a signed 16 bit value

  • GOAL_CM_INT32: a signed 32 bit value

  • GOAL_CM_IPV4: an IPv4 address (32bit)

  • GOAL_CM_STRING: a null terminated string

  • GOAL_CM_GENERIC: a generic blob

Declaring variables

Variables are grouped in modules, so you can build logical groups of variables in your application.

To add your own variables, we simply start to declare them using some macros provided by goal_cm.h:

Code Block
languagec
/*              Name,        Data type,     Max. size,     Validation Cb,  Change Cb */   \
    GOAL_CM_VAR(<VAR_NAME>,  <DATA_TYPE>,   <DATA_SIZE>,   <VAL_CB>,       <CHG_CB>), 

As shown above, a declaration consists of a variable name, one of the above mentioned datatypes, an optional validation callback (can be null) and an optional change callback (can be null).

So let’s assume we would like to build a device with a serial interface for communication. Therefore we would like to store the baud rate, a username and a password for login via a terminal and we would like to store the current firmware revision. Additionally, we would like to enable or disable the login via the serial interface.

So the following code snippet declares the according variables for us:

Code Block
languagec
#define APPL_CM_VARS \
/*              Name,                  Data type,       Max. size,            Validation Cb, Change Cb */   \
    GOAL_CM_VAR(APPL_CM_VAR_ENABLE,    GOAL_CM_UINT8,   sizeof(uint8_t),      NULL,          appl_cmChg),   \
    GOAL_CM_VAR(APPL_CM_VAR_BAUD,      GOAL_CM_UINT32,  sizeof(uint32_t),     NULL,          appl_cmChg),   \
    GOAL_CM_VAR(APPL_CM_VAR_PASSWORD,  GOAL_CM_GENERIC, APPL_CM_PASSWORD_LEN, NULL,          appl_cmChg),   \
    GOAL_CM_VAR(APPL_CM_VAR_USER,      GOAL_CM_STRING,  APPL_CM_STRING_LEN,   NULL,          appl_cmChg),   \
    GOAL_CM_VAR(APPL_CM_VAR_FWREV, GOAL_CM_STRING,  APPL_CM_STRING_LEN,   NULL,          NULL)

So we have

  • APPL_CM_VAR_ENABLE as uint8 for enabling/disabling the serial interface,

  • APPL_CM_VAR_BAUD as uint32 for storing the baud rate to use,

  • APPL_CM_VAR_PASSWORD as blob for storing the hashed password,

  • APPL_CM_VAR_USER as string for storing the username for login and

  • APPL_CM_VAR_FWREV as string for storing the current firmware revision.

Now the GOAL CM has to generate the enumeration of the variable IDs of each defined variable. The macro GOAL_CM_VAR_IDS will do this for us:

Code Block
languagec
/* generate 'enum APPL_CM_VARS_ID_T' that contains all variable names */
#include <goal_cm_id.h>
GOAL_CM_VAR_IDS(APPL_CM_VARS_ID_T, APPL_CM_VARS);

We now can generate the variable list we would like to use using the GOAL_CM_VARLIST macro:

Code Block
languagec
/* generate 'GOAL_CM_VARENTRY_T cmVars[]' array that maps the above table */
#include <goal_cm_t.h>
GOAL_CM_VARLIST(cmVars, APPL_CM_VARS);

As a next step, we need to propagate our new variables to the GOAL CM. This is two step process:

  1. Registering our module at the GOAL CM. This will reserve the according memory in the GOAL CM for handling our new module.

    Code Block
    languagec
    /* register application variables */
        res = goal_cmRegModule(cmVars);
  2. Adding our module to the GOAL CM. This will actually our variables to the GOAL CM.

    Code Block
    languagec
    /* add application variables */
        res = goal_cmAddModule(&cmMod, cmVars, NULL, NULL, NULL);

Thats it! GOAL CM now knows our variables and has reserved the according memory to store our values. It’s now time to set some initial values.

Reading and writing data

The GOAL CM module provides access functions to set and read values from our variables. First, lets set the initial baud rate for the serial interface to 115200 via goal_cmSetVarValue:

Code Block
languagec
    #define APPL_BAUD_INITIAL 115200
    ...
    val32 = APPL_BAUD_INITIAL;
    res = goal_cmSetVarValue(APPL_CM_MOD_ID, APPL_CM_VAR_BAUD, &val32, sizeof(val32), GOAL_TRUE, NULL);
    if (GOAL_RES_ERR(res)) {
        goal_logErr("error updating updating baud rate");
    }

We can read the current value using goal_cmGetVarValue:

Code Block
languagec
    val32 = 0;
    res = goal_cmGetVarById(APPL_CM_MOD_ID, APPL_CM_VAR_BAUD, &pCmVar);
    if (GOAL_RES_ERR(res)) {
        goal_logErr("error reading baud rate");
    } else {
        goal_logInfo("current baud rate: %d" FMT_x32, GOAL_CM_VAR_UINT32(pCmVar));
    }

Please note that we used GOAL_CM_VAR_UINT32 to convert the data to the correct uint32 type of the actual architecture.

Validating variable values

But wait, shouldn’t we only accept valid values for our baud rate? Let’s assume our hardware guys have messed it up (again… 🤪) and our serial interface only works with 9600 and 115200. We can control which values are accepted using a validation callback for our variable. A validation callback is defined as:

Code Block
languagec
typedef GOAL_STATUS_T (* goal_cm_validate)(
  uint32_t modId, 
  uint32_t varId, 
  GOAL_CM_VAR_T *var, v
  oid *newData, 
  uint32_t size
);

So we get the module id, the var id, the pointer to the var struct and the new data to write as a parameter.

The validation callback is called before GOAL CM writes new data to a variable. If the callback returns any value other than GOAL_OK, the new value will not be written and the goal_cmSetVarById returns an error to the calling function.

Our validation function can be implemented as in the following code snippet:

Code Block
languagec
#define APPL_BAUD_9600    9600
#define APPL_BAUD_115200  115200
...

/****************************************************************************/
/** Handle Baud Rate Changes
 *
 * This function is called by the configuration management when baud rate variable
 * shall be changed.
 */
GOAL_STATUS_T appl_baudVal(
    uint32_t modId,                             /**< module ID */
    uint32_t varId,                             /**< variable ID */
    GOAL_CM_VAR_T *var,                         /**< variable pointer */
    void *newData,                              /**< the new value */
    uint32_t size                               /**< size of new data */
)
{
    GOAL_STATUS_T res;                          /**< Result */
    uint32_t baud;                              /**< Value */
    res = GOAL_ERR_ACCESS;

    /* Check for correct module and variable id */
    if (APPL_CM_MOD_ID == modId && APPL_CM_VAR_BAUD == varId) {
        baud = *((uint32_t*) newData);

        /* Only allow 9600 and 115200 */
        if (APPL_BAUD_9600 == baud || APPL_BAUD_115200 == baud) {
            res = GOAL_OK;
        }
    }

    return res;
}

We need to change our variable declaration for GOAL_CM_VAR_BAUD to let GOAL CM know that we wish to validate any input to this variable:

Code Block
languagec
#define APPL_CM_VARS \
/*              Name,                  Data type,       Max. size,            Validation Cb,      Change Cb */   \
...
    GOAL_CM_VAR(APPL_CM_VAR_BAUD,      GOAL_CM_UINT32,  sizeof(uint32_t),     appl_baudVal,       appl_cmChg),   \
...    

Saving and loading values to/from nonvolatile memory

GOAL CM has built-in functionalities for writing a set of variables to nonvolatile memory like flash memory and those data can also be load.

Calling goal_cmSave stores the data of all modules using the specific target-dependent function goal_targetNvsWrite:

Code Block
languagec
/****************************************************************************/
/** Saves all variables to permanent storage using the appropriate target
 *  function
 *
 * @retval GOAL_OK Values saved
 * @retval other failed
 */
GOAL_STATUS_T goal_cmSaveImpl(
    void
);

To get the data back from storage, use goal_cmLoad which uses the target-specific goal_targetNvsReadData function:

Code Block
languagec
/****************************************************************************/
/** Loads the variables from permanent storage using the appropriate target
 *  function
 *
 * @retval GOAL_OK Values loaded
 * @retval other failed
 */
GOAL_STATUS_T goal_cmLoad(
    void
);

The GOAL CM tries to load a variable storage automatically at startup. You can use goal_cmVarIsDefaultto check whether loading a config was successful. The function returns GOAL_TRUE if loading a config failed, otherwise it returns GOAL_FALSE. So we could use an initialization code for our variables like this:

Code Block
languagec
    /* if no config was loaded, create an initial value set and save it */
    if ((GOAL_TRUE == goal_cmVarIsDefault())) {
        /* set initial baud rate */
        val32 = APPL_BAUD_INITIAL;
        res = goal_cmSetVarValue(APPL_CM_MOD_ID, APPL_CM_VAR_BAUD, &val32, sizeof(val32), GOAL_FALSE, NULL);
        if (GOAL_RES_ERR(res)) {
            goal_logErr("error updating 32-bit variable");
        }
        ...
        /* write the config to storage */
        if (GOAL_RES_ERR(res)) {
            res = goal_cmSave();
        }
    }


Virtual variables

When we think about our firmware revision variable GOAL_CM_FWVERSION, this would be normally a value that is “baked in” in our firmware itself, and not a value you would set and store.

For such uses cases, GOAL CM offers so called virtual variables. Those variables can be created using an API and are excluded from the storage functionality. To declare such a variable, use the function goal_cmRegVarVirtual:

Code Block
languagec
/****************************************************************************/
/** Register a callback to handle access to virtual variables of a specific
 * module id.
 *
 * @retval other failed
 */
GOAL_STATUS_T goal_cmRegVarVirtual(
    uint32_t modId,                             /**< module ID */
    uint32_t varId,                             /**< variable ID */
    GOAL_CM_DATATYPE_T type,                    /**< variable type */
    uint32_t sizeMax,                           /**< maximum size */
    goal_cm_validate validate,                  /**< validate callback */
    goal_cm_changed changed                     /**< change callback */
);

It gets the module id, the variable id, the data type, the max. data size and pointers to validation and change callbacks (null if not used) as parameters.

Note

Virtual variables can only be added to existing modules, as the module id must be known to GOAL CM prior calling goal_cmRegVarVirtual.

So let’s declare our firmware variable as virtual! First, remove the declaration from the variable list. Now we redeclare our variable as virtual in our application:

Code Block
languagec
#define APPL_CM_FWVERSION_VIRTUAL_ID (1024)
...
    /* register virtual fw revision variable. Module must be known to the cm */
    res = goal_cmRegVarVirtual(APPL_CM_MOD_ID, APPL_CM_FWREV_VIRTUAL_ID, GOAL_CM_GENERIC, APPL_CM_STRING_LEN, NULL, NULL);
    if (GOAL_RES_ERR(res)) {
        goal_logErr("error registering virtual fw revision variable");
    }
Note

It is up to the application developer to select an appropriate variable id. Ensure that this id does not collide with variables declared in the variable list macro. Otherwise this leads to undefined behaviour.

That’s it! The firmware revision is now excluded from saving and loading. You just have to set the value at startup of your application.

Summary

In this tutorial, you’re learned how to use the GOAL CM to

  • declare modules and variables,

  • declare virtual variables and

  • and load and save variables to flash.

Table of Contents