Real-Time Workshop | ![]() ![]() |
Signal Monitoring via Block Outputs
All block output data is written to the block outputs structure or specified global variables with each time step in the model code. To access the output of a given block in the generated code, your supervisory software must have the following information, per port:
rtB
structure, or the global variable where the data is stored
This information is contained in the BlockIOSignals
data structure. The TLC code generation variable, BlockIOSignals, determines whether BlockIOSignals
data is generated. If BlockIOSignals is set to 1
, a file containing an array of BlockIOSignals
structures is written during code generation. This file is named model
_bio.c
, and by default is not generated.
BlockIOSignals is disabled by default. To enable generation of model
_bio.c
, use the following statement in the Configure RTW code generation settings section of your system target file:
Alternatively, you can append the following command to the System target file field on the Target configuration section of the Real-Time Workshop pane.
Note that depending on the size of your model, the BlockIOSignals
array can consume a considerable amount of memory.
BlockIOSignals and the Local Block Outputs Option
When the Local block outputs code generation option is selected, block outputs are declared locally in functions instead of being declared globally in the rtB
structure when possible. The BlockIOSignals
array in model
_bio.c
will not contain information about such locally declared signals. (Note that even when all outputs in the system are declared locally, enabling BlockIOSignals will generate model
_bio.c
. In such a case the BlockIOSignals
array will contain only a null entry.)
Signals that are designated as test points via the Signal Properties dialog are declared globally in the rtB
structure, even when the Local block outputs option is selected. Information about test-pointed signals is therefore written to the BlockIOSignals
array in model
_bio.c
. Similarly, signals whose storage class is set are declared as global variables and represented in the BlockIOSignals
array.
Therefore, you can interface your code to selected signals by test-pointing them or using storage classes, without losing the benefits of the Local block outputs optimization for the other signals in your model.
model_bio.c and the BlockIO Data Structure
The BlockIOSignals
data structure is declared as follows.
typedef struct BlockIOSignals_tag { char_T *blockName; /* Block's full pathname (mangled by the Real-Time Workshop) */ char_T *signalName; /* Signal label (unmangled) */ uint_T portNumber; /* Block output port number (start at 0) */ uint_T signalWidth; /* Signal's width */ void *signalAddr; /* Signal's address in the rtB vector */ char_T *dtName; /* The C language data type name */ uint_T dtSize; /* The size (# of bytes) for the data type*/ } BlockIOSignals;
The structure definition is in matlabroot
/rtw/c/src/bio_sig.h
. The model
_bio.c
file includes bio_sig.h
. Any source file that references the array should also include bio_sig.h
.
model
_bio.c
defines an array of BlockIOSignals
structures. Each array element, except the last, describes one output port for a block. The final element is a sentinel, with all fields set to null values.
The code fragment below is an example of an array of BlockIOSignals
structures from a model
_bio.c
file.
#include "bio_sig.h" /* Block output signal information */ static const BlockIOSignals rtBIOSignals[] = { /* blockName, signalName, portNumber, signalWidth, signalAddr, dtName, dtSize */ { "simple/Constant", NULL, 0, 1, &rtB.Constant, "double", sizeof(real_T) }, { "simple/Constant1", NULL, 0, 1, &rtB.Constant1, "double", sizeof(real_T) }, { "simple/Gain", "accel", 0, 2, &rtB.accel[0], "double", sizeof(real_T) }, { NULL, NULL, 0, 0, 0, NULL, 0 } };
Thus, a given block will have as many entries as it has output ports. In the example above, the entry corresponding to the signal at output port 0 (indexing is 0-based) of the block with path simple/Gain
is named accel
and has width 2
.
Using BlockIOSignals to Obtain Block Outputs
The model
_bio.c
array is accessed via the name rtBIOSignals
. To avoid overstepping array bounds, you can do either of the following:
rtModel
access macro rtmGetNumBlockIO
to determine the number of elements in the array.
rtModel
access macro rtmGetModelMappingInfo
to return the mapping info corresponding to the model, and then access the array through the mapping info.
blockName
to identify the last element in the array.
You must then write code that iterates over the rtBIOSignals
array and chooses the signals to be monitored based on the blockName
and signalName
or portNumber
. How the signals are monitored is up to you. For example, you could collect the signals at every time step. Alternatively, you could sample signals asynchronously in a separate, lower priority task.
The following code example is drawn from the main program (rt_main.c
) of the Tornado target. The code illustrates how the StethoScope Graphical Monitoring/Data Analysis Tool uses BlockIOSignals
to collect signal information in Tornado targets. The following function, rtInstallRemoveSignals
, selectively installs signals from the BlockIOSignals
array into the StethoScope Tool by calling ScopeInstallSignal
. The main simulation task then collects signals by calling ScopeCollectSignals
.
static int_T rtInstallRemoveSignals(RT_MODEL *rtM, char_T *installStr, int_T fullNames, int_T install) { uint_T i, w; char_T *blockName; char_T name[1024]; ModelMappingInfo mapInfo = rtmGetModelMappingInfo(rtM); BlockIOSignals *rtBIOSignals = mapInfo.Signals.blockIOSignals; int_T ret = -1; if (installStr == NULL) { return -1; } i = 0; while(rtBIOSignals[i].blockName != NULL) { BlockIOSignals *blockInfo = &rtBIOSignals[i++]; if (fullNames) { blockName = blockInfo->blockName; } else { blockName = strrchr(blockInfo->blockName, '/'); if (blockName == NULL) { blockName = blockInfo->blockName; } else { blockName++; } } if ((*installStr) == '*') { } else if (strcmp("[A-Z]*", installStr) == 0) { if (!isupper(*blockName)) { continue; } } else { if (strncmp(blockName, installStr, strlen(installStr)) != 0) { continue; } } /*install/remove the signals*/ for (w = 0; w < blockInfo->signalWidth; w++) { sprintf(name, "%s_%d_%s_%d", blockName, blockInfo->portNumber, !strcmp(blockInfo->signalName,"NULL")?"":blockInfo->signalName, w); if (install) { /*install*/ if (!ScopeInstallSignal(name, "units", (void *)((int)blockInfo->signalAddr + w*blockInfo->dtSize), blockInfo->dtName, 0)) { fprintf(stderr,"rtInstallRemoveSignals: ScopeInstallSignal " "possible error: over 256 signals.\n"); return -1; } else { ret =0; } } else { /*remove*/ if (!ScopeRemoveSignal(name, 0)) { fprintf(stderr,"rtInstallRemoveSignals: ScopeRemoveSignal\n" "%s not found.\n",name); } else { ret =0; } } } } return ret; }
Below is an excerpt from an example routine that collects signals taken from the main simulation loop.
/*******************************************
* Step the model for the base sample time *
*******************************************/
OUTPUTS(rtM,FIRST_TID);
rtExtModeUploadCheckTrigger();
rtExtModeUpload(FIRST_TID,rtmGetTaskTime(rtM, FIRST_TID));
#ifdef MAT_FILE
if (rt_UpdateTXYLogVars(rtmGetRTWLogInfo(rtM),
rtmGetTPtr(rtM)) != NULL) {
fprintf(stderr,"rt_UpdateTXYLogVars() failed\n");
return(1);
}
#endif
#ifdef STETHOSCOPE
ScopeCollectSignals(0);
#endif
UPDATED(rtM,FIRST_TID);
if (rtmGetSampleTime(rtM,0) == CONTINUOUS_SAMPLE_TIME) {
rt_ODEUpdateContinuousStates(rtmGetRTWSolverInfo(rtM));
} else {
rt_SimUpdateDiscreteTaskTime(rtmGetTPtr(rtM),
rtmGetTimingData(rtM),0);
}
#if FIRST_TID == 1
rt_SimUpdateDiscreteTaskTime(rtmGetTPtr(rtM),
rtmGetTimingData(rtM),1);
#endif
rtExtModeCheckEndTrigger();
} /* end while(1) */
return(1);
} /* end tBaseRate */
<code continues ...>
See Targeting Tornado for Real-Time Applications for more information on using StethoScope.
![]() | Interfacing Parameters and Signals | C API for Parameter Tuning | ![]() |