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:

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:

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.

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:

  1. #define S_FUNCTION_NAME name
  1. This defines the name of the entry point for the S-function code. 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:

  1. #define S_FUNCTION_LEVEL 2
  1. This statement defines the file as a level 2 S-function. This allows you to take advantage of the full feature set included with S-functions. Level-1 S-functions are currently used only to maintain backwards compatibility.

  1. #include "simstruc.h"
  1. The file 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:

A noninlined S-function should conditionally include both these files, as in the following code from sfuntmpl_basic.c:

Required Functions

The S-function API requires you to implement several functions in your driver:

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.

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.

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.

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:

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:

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.

mdlOutputs - Output Devices.   In a driver for an output device (such as a DAC), mdlOutputs must:

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.

mdlTerminate

This final required function is typically needed only in DAC drivers. The following routine sets the output of each DAC channel to zero:

ADC drivers usually implement mdlTerminate as an empty stub.


  Parameterizing Your Driver Writing an Inlined S-Function Device Driver