Main Content

Access Structured Data Through a Pointer That External Code Defines

This example shows how to generate code that uses global data that some handwritten code defines. In the handwritten code, a pointer variable points to one of three structure variables that contain parameter data. A handwritten function switches the pointer between the structures. The generated code accesses the parameter data by dereferencing the pointer variable.

Explore External Code

Open the example source file rtwdemo_importstruct_user.c. The code defines a default data structure variable ReferenceStruct as constant (const) data and statically initializes each field. This structure represents the reference data set.

/* Constant default data structure (reference data set) */
const DataStruct_type ReferenceStruct = 
{
  11,   /* OFFSET */
  2     /* GAIN */
};

The code defines two other structure variables, WorkingStruct1 and WorkingStruct2, as volatile (volatile) data.

/* Volatile data structures (working data sets) */
volatile DataStruct_type WorkingStruct1;
volatile DataStruct_type WorkingStruct2;

The code defines a function that can initialize:

  • An inactive working structure from the active working structure.

  • Both working structures from the reference structure.

/* Function to initialize inactive working structures from active structure */
void InitInactiveWorkingStructs(void)
{
    if (StructPointer == &WorkingStruct1) {
        /* Copy values from WorkingStruct1 to WorkingStruct2 */
        memcpy((void*)&WorkingStruct2, (void*)&WorkingStruct1, sizeof(ReferenceStruct));
        
    } else if (StructPointer == &WorkingStruct2) {
        /* Copy values from WorkingStruct2 to WorkingStruct1 */
        memcpy((void*)&WorkingStruct1, (void*)&WorkingStruct2, sizeof(ReferenceStruct));
        
    } else {
        /* Initialize both working structures from ReferenceStruct */
        memcpy((void*)&WorkingStruct1, &ReferenceStruct, sizeof(ReferenceStruct));
        memcpy((void*)&WorkingStruct2, &ReferenceStruct, sizeof(ReferenceStruct));
    }
}

The code defines StructPointer, which is a const volatile pointer to a structure. The code initializes the pointer to the address of ReferenceStruct.

/* Define structure pointer.  Point to reference structure by default */
const volatile DataStruct_type *StructPointer = &ReferenceStruct;

Finally, the code defines a function that can dynamically set StructPointer to point to either ReferenceStruct, WorkingStruct1, or WorkingStruct2.

/* Function to switch between structures */
void SwitchStructPointer(Dataset_T Dataset)
{
    switch (Dataset) {
    case Working1:
        StructPointer = &WorkingStruct1;
        break;
    case Working2:
        StructPointer = &WorkingStruct2;
        break;
    default:
        StructPointer = &ReferenceStruct;
    }
}

The example header file rtwdemo_importstruct_user.h defines the enumeration Dataset_T and the structure type Datastruct_type. The file includes (#include) the built-in Simulink® Coder™ header file rtwtypes.h, which defines (typedef) Simulink Coder data types such as int16_T.

#include "rtwtypes.h"

typedef enum {
    Reference = 0,
    Working1,
    Working2
} Dataset_T;

typedef struct DataStruct_tag {
  int16_T   OFFSET; /* OFFSET */
  int16_T   GAIN;   /* GAIN */
} DataStruct_type;

The file also declares the global variables and the functions.

Purpose of External Code

The code is designed so that the source code of a control algorithm (whether generated or handwritten) can read data from ReferenceStruct, WorkingStruct1, or WorkingStruct2 by dereferencing (->) StructPointer. The two working structures (WorkingStruct1 and WorkingStruct2) are located in volatile memory and are intended to be changed during runtime by an external calibration tool. The calibration engineer does not change the active working structure. Instead, the engineer changes the parameter values in the inactive working structure and then activates it by switching working structures.

If necessary for safety or in preparation for shutting down the application, the calibration tool can point StructPointer to ReferenceStruct instead. ReferenceStruct stores default parameter values that do not change during execution.

Explore Example Model

Open the example model, ImportStruct.

The model creates variables and objects in the base workspace. The Constant block and the Gain block use the ECoderDemos.Parameter objects GAIN and OFFSET to set the Constant value and Gain block parameters. ECoderDemos is an example custom package that defines two classes, Parameter and Signal, and some custom storage classes.

In the Embedded Coder app, select Code Interface > Individual Element Code Mappings.

In the Code Mappings Editor, inspect the Parameters tab.

The Code Mappings Editor shows rows that correspond to OFFSET and GAIN, which set the parameter values. In the Storage Class column, OFFSET and GAIN use the custom storage class StructPointer, which the ECoderDemos package defines.

Open the Custom Storage Class Designer and inspect the custom storage classes in the ECoderDemos package. At the command prompt, use this command:

cscdesigner('ECoderDemos')

This example package defines multiple custom storage classes, including StructPointer. You cannot edit the definitions. However, you can create your own packages and custom storage classes later. For an example that shows how to create a package and a custom storage class, see Create and Apply Storage Class Defined in User-Defined Package.

Under Custom storage class definitions, click StructPointer. The settings for this custom storage class enable the generated code to interact with the pointer variable, StructPointer, from the external code. For example, the custom storage class uses these settings:

  • Data scope is set to Imported because the example external code defines (allocates memory for) StructPointer. With this setting, the code generator avoids generating unnecessary, duplicate definitions for the data items, such as the ECoderDemos.Parameter objects, that use the custom storage class.

  • Data access is set to Pointer because in the example external code, StructPointer is a pointer.

  • Memory section is set to ConstVolatile because the example external code defines StructPointer as constant, volatile data (const volatile).

  • Type is set to FlatStructure because in the example external code, StructPointer points to a structure. With this setting, the generated code treats each data item (ECoderDemos.Parameter object) as a field of a flat structure whose variable name and type name you can specify.

  • On the Structure Attributes tab, Struct name is set to StructPointer. For a FlatStructure custom storage class, Struct name specifies the name of the structure variable in the generated code. In this example, StructPointer is the name of the variable that the external code defines.

  • Type name is set to DataStruct_type, which is the name of the structure type that the example external code defines.

In the model, in the Configuration Parameters dialog box, inspect the Code Generation > Custom Code pane.

On the Additional source code tab, select Initialize code. This parameter value specifies the code to include in the generated model initialize function. In this model, the configuration parameter is set so that the generated code calls the InitInactiveWorkingStructs function. InitInactiveWorkingStructs initializes the working structures with the values from ReferenceStruct. The initialization code then sets the pointer to the first working structure.

On the Code information tab, select Source files. This configuration parameter identifies the example external code file rtwdemo_importstruct_user.c for inclusion in the build process after code generation.

Generate and Inspect Code

Generate code from the model.

In the generated file ImportStruct.c, the model initialization function calls InitInactiveWorkingStructs. The algorithm in the model execution (step) function dereferences the pointer variable StructPointer.

Related Topics