Real-Time Workshop | ![]() ![]() |
Writing a Noninlined S-Function Device Driver
Device driver S-functions are relatively simple to implement because they perform only a few operations. These operations include:
SimStruct
.
y
.
u
to an I/O device.
Your driver performs these operations by implementing certain specific functions required by the S-function API.
Since these functions are private to the source file, you can incorporate multiple instances of the same S-function into a model. Note that each such noninlined S-function also instantiates a SimStruct
.
Conditional Compilation for Simulink and Real-Time
Noninlined S-functions must function in both Simulink and in real-time environments. Real-Time Workshop defines the preprocessor symbols MATLAB_MEX_FILE
, RT
, and NRT
to distinguish simulation code from real-time code. Use these symbols as follows:
mex
command, MATLAB_MEX_FILE
is automatically defined.
RT
is automatically defined.
Real-Time Workshop provides these conditionals to help ensure that your driver S-functions access hardware only when it is appropriate to do so. Since your target I/O hardware is not present during simulation, writing to addresses in the target environment can result in illegal memory references, overwriting system memory, and other severe errors. Similarly, read operations from nonexistent hardware registers can cause model execution errors.
In the following code fragment, a hardware initialization call is compiled in generated real-time code. During simulation, a message is printed to the MATLAB command window.
#if defined(RT)
/* generated code calls function to initialize an A/D device */
INIT_AD();
#elif defined(MATLAB_MEX_FILE)
/* during simulation, just print a message */if (ssGetSimMode(S) == SS_SIMMODE_NORMAL) {
mexPrintf("\n adc.c: Simulating initialization\n");
}
#endif
The MATLAB_MEX_FILE
and RT
conditionals also control the use of certain required include files. See Required Defines and Include Files below.
You may prefer to control execution of real-time and simulation code by some other means. For an example, see the use of the variable ACCESS_HW in matlabroot
/rtw/c/dos/devices/das16ad.c
Required Defines and Include Files
Your driver S-function must begin with the following three statements, in the following order:
name
must be the name of the S-function source file, without the .c
extension. For example, if the S-function source file is das16ad.c
:
simstruc.h
defines the SimStruct
(the Simulink data structure) and associated accessor macros. It also defines access methods for the mx*
functions from the MATLAB MEX API.
Depending upon whether you intend to build your S-function as a MEX file or as real-time code, you must include one of the following files at the end of your S-function:
simulink.c
provides required functions interfacing to Simulink.
A noninlined S-function should conditionally include both these files, as in the following code from sfuntmpl_basic.c
:
#ifdef MATLAB_MEX_FILE /* File being compiled as a MEX-file? */ #include "simulink.c" /* MEX-file interface mechanism */ #else #include "cg_sfun.h" /* Code generation registration function */ #endif
Required Functions
The S-function API requires you to implement several functions in your driver:
mdlInitializeSizes
specifies the sizes of various parameters in the SimStruct
, such as the number of output ports for the block.
mdlInitializeSampleTimes
specifies the sample time(s) of the block.
mdlOutputs
: for an input device, reads values from the hardware and sets these values in the output vector y
. For an output device, reads the input u
from the upstream block and outputs the value(s) to the hardware.
mdlTerminate
resets hardware devices to a desired state, if any. This function may be implemented as a stub.
In addition to the above, you may want to implement the mdlStart
function. mdlStart
, which is called once at the start of model execution, is useful for operations such as setting I/O hardware to some desired initial state.
This following sections provide guidelines for implementing these functions.
mdlInitializeSizes
In this function you specify the sizes of various parameters in the SimStruct
. This information may depend upon the parameters passed to the S-function. Parameterizing Your Driver describes how to access parameter values specified in S-function dialog boxes.
Initializing Sizes - Input Devices. The mdlInitializeSizes
function sets size information in the SimStruct
. The following implementation of mdlInitializeSizes
initializes a typical ADC driver block.
static void mdlInitializeSizes(SimStruct *S) { uint_T num_channels; ssSetNumSFcnParams(S, 3); /* Number of expected parameters */ if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)){ /*Return if number of expected != number of actual params */ return; } num_channels = mxGetPr(NUM_CHANNELS_PARAM)[0]; ssSetNumInputPorts(S, 0); ssSetNumOutputPorts(S, num_channels); ssSetNumSampleTimes(S,1); }
This routine first validates that the number of input parameters is equal to the number of parameters in the block's dialog box. Next, it obtains the Number of Channels parameter from the dialog.
ssSetNumInputPorts sets the number of input ports to 0 because an ADC is a source block, having only outputs.
ssSetNumOutputPorts sets the number of output ports equal to the number of I/O channels obtained from the dialog box.
ssSetNumSampleTimes sets the number of sample times to 1. This would be the case where all ADC channels run at the same rate. Note that the actual sample period is set in mdlInitializeSampleTimes
.
Note that by default, the ADC block has no direct feedthrough. The ADC output is calculated based on values read from hardware, not from data obtained from another block.
Initializing Sizes - Output Devices. Initializing size information for an output device, such as a DAC, differs in several important ways from initializing sizes for an ADC:
The following example is an implementation of mdlInitializeSizes
for a DAC driver block.
static void mdlInitializeSizes(SimStruct *S) { uint_T num_channels; ssSetNumSFcnParams(S, 3); /* Number of expected parameters */ if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)){ /* Return if number of expected != number of actual params */ return; } num_channels = mxGetPr(NUM_CHANNELS_PARAM)[0]; ssSetNumInputPorts(S, num_channels); /* Number of inputs is now the number of channels. */ ssSetNumOutputPorts(S, 0); /* Set direct feedthrough for all ports */ { uint_T i; for(i=0, i < num_channels, i++) { ssSetInputPortDirectFeedThrough(S,i,1); } } ssSetNumSampleTimes(S, 1); }
mdlInitializeSampleTimes
Device driver blocks are discrete blocks, requiring you to set a sample time. The procedure for setting sample times is the same for both input and output device drivers. Assuming that all channels of the device run at the same rate, the S-function has only one sample time.
The following implementation of mdlInitializeSampleTimes
reads the sample time from a block's dialog box. In this case, sample time is the fifth parameter in the dialog box. The sample time offset is set to 0.
static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, mxGetPr(ssGetSFcnParams(S,4))[0]); ssSetOffsetTime(S, 0, 0.0); }
mdlStart
mdlStart is an optional function. It is called once at the start of model execution, and is often used to initialize hardware. Since it accesses hardware, you should compile it conditionally for use in real-time code or simulation, as in this example:
static void mdlStart(SimStruct *S) { #if defined(RT) /* Generated code calls function to initialize an A/D device */ INIT_AD(); /* This call accesses hardware */ #elif defined(MATLAB_MEX_FILE) /* During simulation, just print a message */ if (ssGetSimMode(S) == SS_SIMMODE_NORMAL) { mexPrintf("\n adc.c: Simulating initialization\n"); } #endif }
mdlOutputs
The basic purpose of a device driver block is to allow your program to communicate with I/O hardware. Typically, you accomplish this by using low level hardware calls that are part of your compiler's C library, or by using C-callable functions provided with your I/O hardware.
All S-functions implement a mdlOutputs
function to calculate block outputs. For a device driver block, mdlOutputs
contains the code that reads from or writes to the hardware.
mdlOutputs - Input Devices. In a driver for an input device (such as an ADC), mdlOutputs
must:
y
for use by the model.
The following code is the mdlOutputs
function from the ADC driver matlabroot
/rtw/c/dos/devices/das16ad.c
. The function uses macros defined in matlabroot
/rtw/c/dos/devices/das16ad
.h to perform low-level hardware access. Note that the Boolean variable ACCESS_HW
(rather than conditional compilation) controls execution of simulation and real-time code. The real-time code reads values from the hardware and stores them to the output vector. The simulation code simply outputs 0
on all channels.
static void mdlOutputs(SimStruct *S, int_T tid) { real_T *y = ssGetOutputPortRealSignal(S,0); uint_T i; if (ACCESS_HW) { /* Real-time code reads hardware*/ ADCInfo *adcInfo = ssGetUserData(S); uint_T baseAddr = adcInfo->baseAddr; real_T offset = adcInfo->offset; real_T resolution = adcInfo->resolution; /* For each ADC channel initiate conversion,*/ /* then read channel value, scale and offset it and store */ /* it to output y */ for (i = 0; i < NUM_CHANNELS; i++) { uint_T adcValue; adcStartConversion(baseAddr); for ( ; ; ){ if (!adcIsBusy(baseAddr)) break; } adcValue = adcGetValue(baseAddr); y[i] = offset + resolution*adcValue; } } else { /* simulation code just zeroes the output for all channels*/ for (i = 0; i < NUM_CHANNELS; i++){ y[i] = 0.0; } } }
mdlOutputs - Output Devices. In a driver for an output device (such as a DAC), mdlOutputs
must:
u
from the upstream block.
The following code is the mdlOutputs
function from the DAC driver matlabroot
/rtw/c/dos/devices/das16da.c
. The function uses macros defined in matlabroot
/rtw/c/dos/devices/das16ad
.h to perform low-level hardware access. This function iterates over all channels, obtaining and scaling a block input value. It then range-checks and (if necessary) trims each value. Finally it writes the value to the hardware.
In simulation, this function is a stub.
static void mdlOutputs(SimStruct *S, int_T tid) { if (ACCESS_HW) { int_T i; DACInfo *dacInfo = ssGetUserData(S); uint_T baseAddr = dacInfo->baseAddr; real_T resolution = dacInfo->resolution; InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); for (i = 0; i < NUM_CHANNELS; i++) { uint_T codeValue; /* Get and scale input for channel i. */ real_T value = (*uPtrs[i] - MIN_OUTPUT)*resolution; /* Range check value */ value = (value < DAC_MIN_OUTPUT) ? DAC_MIN_OUTPUT : value; value = (value > DAC_MAX_OUTPUT) ? DAC_MAX_OUTPUT : value; codeValue = (uint_T) value; /* Output to hardware */ switch (i) { case 0: dac0SetValue(baseAddr, codeValue); break; case 1: dac1SetValue(baseAddr, codeValue); break; } } } }
mdlTerminate
This final required function is typically needed only in DAC drivers. The following routine sets the output of each DAC channel to zero:
static void mdlTerminate(SimStruct *S) { uint_T num_channels; uint_T i; num_channels = (uint_t)mxGetPr(ssGetSFcnParams(S,0)[0]); for (i = 0; i < num_channels; i++){ ds1102_da(i + 1, 0.0); /* Hardware-specific DAC output */ } }
ADC drivers usually implement mdlTerminate as an empty stub.
![]() | Parameterizing Your Driver | Writing an Inlined S-Function Device Driver | ![]() |