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
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:
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:
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:
/* generate 'enum APPL_CM_VARS_ID_T' that contains all variable names */
We now can generate the variable list we would like to use using the GOAL_CM_VARLIST macro:
As a next step, we need to propagate our new variables to the GOAL CM. This is two step process:
Registering our module at the GOAL CM. This will reserve the according memory in the GOAL CM for handling our new module.
Adding our module to the GOAL CM. This will actually our variables to the GOAL CM.
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:
We can read the current value using goal_cmGetVarValue:
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:
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:
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:
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:
To get the data back from storage, use goal_cmLoad which uses the target-specific goal_targetNvsReadData function:
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:
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:
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.
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:
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.
In this tutorial, you’re learned how to use the GOAL CM to