Message Communication Interfaces
Within a platform environment, code generated from a Simulink® model can communicate with platform software or with other code generated from Simulink models by using message-based communication. Generate C++ component code for message-based communication with platform software. Generate C++ or C component code for message-based communication with other models.
Generate C++ Messages to Communicate Data Between Simulink and an Operating System or Middleware
To generate C++ code that supports message communication between Simulink top models and external applications, use the Simulink Messages & Events Library Send and Receive blocks. Generating code from a top model to facilitate messages passed outside of the Simulink environment enables your modeled application to communicate in a distributed system that uses an external message protocol service, commonly referred to as an operating system or middleware (for example, DDS, ROS, SOMEIP, or POSIX messages).
Simulink top models pass messages by:
Top models contain message blocks to communicate outside the Simulink environment. If a top model contains a Send block directly connected to a root Outport block, the block converts its signals into messages and passes them outside the Simulink environment. If a top model contains a Receive block directly connected to a root Inport block, the block converts the received messages into signals.
The external message protocol manages the message communication according to its own standard, that is, policies that control capacity, order of delivery, and other quality of service (QoS) behavior.
To generate C++ messages to communicate between Simulink top models and an operating system or middleware, prepare your model, generate code, and integrate that code with your selected operating system or middleware.
Prepare Model
To set up your model so that it can pass messages with an operating system or middleware, configure the model as a top model that has at least one message block, either a Send block connected to a root Outport block or a Receive block connected to a root Inport block. The top model can then connect to your selected operating system or middleware through the message blocks:
Within a model, the message ports are connected as shown:
Generate Code
To generate C++ code from a model:
In the Apps gallery, click Embedded Coder.
In the Configuration Parameters dialog box, set these parameters:
In the Code Generation pane, set the Language to
C++
.In the Interface pane, set Code interface packaging to
C++ class
.In the Templates pane, select
Generate an example main program
.
Generate code. On the C++ Code tab, click Build.
View the generated code. On the C++ Code tab, click View Code.
Integrate Code
To integrate the generated C++ code from your model with your selected operating system or middleware use handwritten code to implement send and receive message classes and application code that uses those classes to pass messages. Specifically:
If you use your own main, create concrete subclasses from the generated abstract classes shown in
RecvData_<T>.h
andSendData<T>.h
. If you use the generated example main program, concrete subclasses are provided in the file.Implement the class functions
SendData
andRecvData
to call into your selected operating system or middleware to send and receive messages.Create an instance of your implemented send and receive message classes (send and receive objects).
Create an instance of the model class by using the instances of each message class (send and receive objects) as arguments in the model constructor.
Send and receive messages as required by your application. Manage the lifetime of the message queues.The queues must be in a state ready to accept messages prior to the first step of the model.
To integrate the generated code with a platform main program:
Open the generated example main program or create your own. If you use the generated example main program, the concrete subclasses
RecvData_real_T
andSendData_real_T
are in the file. If you create your own main, create concrete subclasses in your application code:To receive messages, manually write the implementation of the generated receive class. Implement the class function
RecvData
to call into your selected operating system or middleware to receive messages.An example implementation for POSIX is:
class mHMIHandlerRecvData_real_T: public RecvData_real_T { public: void RecvData(real_T* data, int32_T length, int32_T* status) { // Use POSIX API mq_receive to receive messages unsigned int priority = 1; *status = mq_receive(msgQueue, (char *)data, length, &priority); } };
Create a receive object.
static mHMIHandlerRecvData_real_T InMsgRecvData_arg;
To send messages, manually write the implementation of the generated send class. Implement the class function
SendData
to call into your selected operating system or middleware to send messages.An example implementation for POSIX is:
class mHMIHandlerSendData_real_T : public SendData_real_T { public: void SendData(const real_T* data, int32_T length, int32_T* status) { // Use the POSIX API mq_send to send messages unsigned int priority = 1; *status = mq_send(msgQueue, (char*)data, length, priority); } };
Create a send object.
static mHMIHandlerSendData_real_T OutMesgSendData_arg;
Create an instance of the model class by using your send and receive objects as arguments in the model constructor.
static mHMIHandler mHMI_Obj(InMsgRecvData_arg, OutMsgSendData_arg);
Send and receive messages as required by your application and maintain the lifetime of the message queue.
An example implementation for POSIX is:
int_T main(int_T argc, const char *argv[]) { // Unused arguments (void)(argc); (void)(argv); //Initialize model mHMI_obj.initialize(); // Open POSIX queue mqd_t msgQueue = mq_open("/PosixMQ_Example", O_RDONLY); if (msgQueue == -1) { printf("mq_open failed\n"); exit(1); } // Send and Receive messages while (rtmGetErrorStatus(mHMI_Obj.getRTM()) == (NULL)) { //perform application tasks here. rt_OneStep(); } // Close POSIX queue mq_close(msgQueue); // Terminate model mHMI_Obj.terminat(): return 0; }
For a more complex POSIX integration example, see Model Message-Based Communication Integrated with POSIX Message Queues
Considerations and Limitations
Handwritten code is the only supported integration technique.
You must select the model configuration parameter Generate an example main program. Applications that require a static main are not supported.
You cannot configure function prototype control (FPC) for a top model that has root message ports.
Generate C++ Messages to Communicate Data Between Simulink Components
To generate C++ code that supports message-based communication between model components in the Simulink environment, use the Simulink Messages & Events Library blocks Send and Receive. To customize the communication, use the Queue block (from the same library) to set parameters for capacity, sorting policy (LIFO, FIFO, and priority), and overwriting policy (behavior when the queue exceeds capacity). You can generate C++ code for GRT-based system target files by using Simulink Coder or for ERT-based system target files by using Embedded Coder.
Messages are an effective communication technique for distributed and complex systems that you can model within Simulink.
How to Prepare Models in Simulink for Message-Based Communication
In Simulink, model message-based communication between model components:
Create a model that contains a Send block (referenced model).
Create a model that contains a Receive block (referenced model).
Create a model that has two Model blocks (top model).
Set the first Model block to the model that contains the Send block (model from step 1).
Set the second Model block to the model that contains the Receive block (model from step 2).
When you run the model, a queue generates in the top model above the message line. A queue, explicit or implicit, controls the message communication. You can use the generated queue or you can add a Queue block in the top model to explicitly specify communication parameters.
How Model Components Pass Messages and How the Code Implements the Behavior
Referenced models pass messages:
In the model that contains the Send block, the Send block converts signals into messages.
The top model that contains the queue manages messages according to parameters that define capacity, order of delivery, and other quality of service (QoS) metrics.
In the model that contains the Receive block, the Receive block converts messages back to signals.
In the generated C++ code, the top model facilitates the connection between the send and receive referenced models by establishing a set interface that the referenced models can access independently of one another.
The generated C++ code implements message behavior:
A service is created at each model boundary. A service contains a reference to the top model and an entry point function, referred to as a service function, for referenced models to use to pass messages to the top model.
The top model initializes each service to create a connection to each referenced model.
The referenced models invoke service functions to pass messages to the top model.
C++ Code Generation Example
This example generates and examines C++ code from the model provided in Establish Message Send and Receive Interfaces Between Software Components.
Generate C++ Code:
Open the model.
In the Apps gallery, click Embedded Coder.
For each model (top and both referenced models), in the Configuration Parameters dialog box, set these parameters:
In the Code Generation pane, set Language to
C++
.In the Interface pane, set Code interface packaging to
C++ class
.
Save the model.
Generate code. On the C++ Code tab, click Build.
View the generated code. On the C++ Code tab, click View Code.
Examine C++ Code:
A service is created at each model boundary. In C++, services are represented as objects that hold an instance of the top model and a service function that referenced models invoke to pass messages.
To view the creation of the services, open the top model C++ file
MessageSendReceiveDefaultBufferModel.cpp
. View the constructor method.The constructor methods,
ReceiveComponentRecvData(*this)
andSendComponentSendData(*this)
, create the receive and send service objects respectively by taking as an argument a reference to the instance of the top model. Each service object saves a reference to the top model and defines the message interface (the service functionsRecvData
andSendData
).To view the receive and send service classes, open the top model header file
MessageSendReceiveDefaultBufferModel.h
, and view the following section.
The top model initializes each service to create a connection to each referenced model. To view the initialization, open the top model C++ file
MessageSendReceiveDefaultBufferModel.cpp
. View the constructor method.The constructor method
Receive_ComponentMDLOBJ0(get_ReceiveComponentRecvData())
, passes a reference to the receive service to the receive referenced model. The constructor method,Send_ComponentMDLOBJ1(get_SendComponentSendData())
, passes a reference to the send service to the send referenced model.The referenced models invoke service functions to pass messages to the top model. In C++, referenced models invoke the top model (common ancestor, if in a hierarchy) service functions to send or receive messages (specifically, a referenced model invokes the abstract service method (
RecvData
orSendData
) from the interface created in step 2).The abstract interface classes are emitted to a shared folder. The implementation of the service functions in each service is defined in the top model C++ file.To view the abstract interface class to send messages, open from the shared folder the header file
SendData_real_T.h
.To view the implementation of the service function to send messages, open the top model C++ file
MessageSendReceiveDefaultBufferModel.cpp
.To view how the send referenced model invokes the service function, open its C++ file
mSend.cpp
. In the step function, the model invokes the service function to send messages to the top model and receives back a return status.To view the abstract interface class to receive messages, open from the shared folder the header file
RecvData_real_T.h
.To view the implementation of the service function to receive messages, open top model C++ file
MessageSendReceiveDefaultBufferModel.cpp
.To view how the receive referenced model invokes the service function, open its C++ file
mRecieve.cpp
. In the step function, the model invokes the service function to receive messages and a status from the top model.
Considerations and Limitations
C++ code support is available for GRT-based system target files by using the Simulink Coder App.
C++ code support is available for ERT-based system target files by using the Embedded Coder App.
To generate code, for top and referenced models. select the same language (C++) and the same system target file.
You cannot configure function prototype control (FPC) for a top model that has root message ports.
External models and variant models are not supported.
Software-in-the-loop (SIL) and processor-in-the-loop (PIL) simulations are not supported.
Generate C Messages to Communicate Data Between Simulink Components
To generate C code that supports message-based communication between model components in the Simulink environment, use the Simulink Messages & Events Library blocks Send and Receive. To customize the communication, use the Queue block (from the same library) to set parameters for capacity, sorting policy (LIFO, FIFO, and priority), and overwriting policy (behavior when the queue exceeds capacity). You can generate C code for GRT-based system target files by using Simulink Coder or for ERT-based system target files by using Embedded Coder.
Messages are an effective communication technique for distributed and complex systems that you can model within Simulink.
How to Prepare Models in Simulink for Message-Based Communication
In Simulink, you can model message-based communication between model components:
Create a model that contains a Send block (referenced model).
Create a model that contains a Receive block (referenced model).
Create a model that has two Model blocks (top model).
Set the first Model block to the model that contains the Send block (model from step 1).
Set the second Model block to the model that contains the Receive block (model from step 2).
When you run the model, a queue generates in the top model above the message line. A queue, explicit or implicit, controls the message communication. You can use the generated queue or you can add a Queue block in the top model to explicitly specify communication parameters.
How Model Components Pass Messages and How the Code Implements the Behavior
Referenced models pass messages:
In the model that contains the Send block, the Send block converts signals into messages.
The top model that contains the queue manages messages according to parameters that define capacity, order of delivery, and other quality of service (QoS) metrics.
In the model that contains the Receive block, the Receive block converts messages back to signals.
In the generated C code, the top model facilitates the connection between the send and receive referenced models by establishing a set interface that the referenced models can access independently of one another.
The generated C code implements message behavior:
A service is created at each model boundary. A service contains a reference to the top model and an entry point function, referred to as a service function, for referenced models to use to pass messages to the top model.
The top model initializes each service to create a connection to each referenced model.
The referenced models invoke service functions to pass messages to the top model.
C Code Generation Example
This example generates and examines C code from the model provided in Establish Message Send and Receive Interfaces Between Software Components.
Generate C Code:
Open the model.
In the Apps gallery, click Embedded Coder.
For each model (top and both referenced models), in the Configuration Parameters dialog box, in the Code Generation pane, set Language to
C
and save the model.Generate code. On the C Code tab, click Build.
View the generated code. On the C Code tab, click View Code.
Examine C Code:
A service is created at each model boundary. In C, referenced models represent services as DWork. The service provides a pointer to an instance of the top model and a service function that referenced models invoke to pass messages.
To view the service to send messages, open the send referenced model header file
mSend.h
. View the DWork allocation.To view the send message data type, if the data type is shareable (for example, built-in data types, imported bus types, or exported bus types that have a specified data type) the information is located in a shared header file. If the data type is not shareable, the information is located the model header file
mSend.h
. For this example, view the shareable data type by opening the shared header fileSendData_real_T.h
.To view the service to receive messages, open the receive referenced model header file,
mReceive.h
. View the DWork allocation.To view the received messages data type, if the data type is shareable (for example,built-in data types, imported bus types, or exported bus types that have a specified data type) the information is located in a shared header file. If the data type is not shareable the information is located the model header file,
mReceive.h
. For this example, view the shareable data type by opening the shared header fileRecvData_real_T.h
.
The top model initializes each service to create a connection to each referenced model. In C, the top model initializes each referenced model DWork. To view the initialization, open the top model C file
MessageSendReceiveDefaultBufferModel.c
.The referenced models invoke service functions to pass messages to the top model. In C, a referenced model invokes a service function by dereferencing the service function pointer and passing a pointer to the instance of the top model. You can view the prototypes of the service functions in the top model header file. You can view the referenced model invocations of those service functions in the referenced model C files.
To view the prototype of the service function to send messages, open the top model header file,
MessageSendReceiveDefaultBufferModel.h
.To view the implementation of the service function to send messages, open the top model C file,
MessageSendReceiveDefaultBufferModel.c
.To view how the send referenced model invokes the service function, open the C file for the model
mSend.c
. In the step function, the model invokes the service to send instance data and a message to the top model and receives back a return status.To view the prototype of the service function to receive messages, open the top model header file
MessageSendReceiveDefaultBufferModel.h
.To view the implementation of the service function to receive messages, open the top model C file
MessageSendReceiveDefaultBufferModel.c
.To view how the receive referenced model invokes the service function, open the C file for the model
mReceive.c
. In the step function, the model invokes the service to receive a message payload and a return status.
Considerations and Limitations
C code support is available for GRT-based system target files by using the Simulink Coder App.
C code support is available for ERT-based system target files by using the Embedded Coder App.
To generate code, for top and referenced models, select the same language (C) and the same system target file.
You cannot configure function prototype control (FPC) for a top model that has root message ports.
External models and variant models are not supported.
Software-in-the-loop (SIL) and processor-in-the-loop (PIL) simulations are not supported.
Model Message-Based Communication Integrated with POSIX Message Queues
This example shows how to integrate generated C++ code with POSIX to facilitate message communication. The example application is a multisensor positioning system designed to estimate the position of a vehicle. The multisensor positioning system is composed of two components: sensors (multiple models) and an estimation model. The outlined workflow shows how to prepare each component, generate code, and integrate the generated code to pass messages between system components. This example assumes you are familiar with using messages in the Simulink environment. For more information, see Generate C++ Messages to Communicate Data Between Simulink and an Operating System or Middleware.
Sensor Models
The first component in the multisensor positioning system is the sensors. The positioning system uses an accelometer and a GPS sensor to estimate the vehicle position. Each sensor model is composed of a Function block, Send block, and Output port. Each sensor model sends messages to the estimation model by using POSIX messages.
Prepare Models
Open the sensor models AccelerometerSendMessages
and GPSSendMessages
.
For each model, perform these steps:
In the Apps Gallery, click Embedded Coder.
On the toolstrip, set the Stop time to
100
seconds. The stop time defines the duration of the data collection when estimating the vehicle position.In the model, open the Block Parameters for the Function block and set the Sample time to
0.01
.
Generate Code
1. For each sensor model, in the Configuration Parameters dialog box, set these parameters:
On the Code Generation pane, set Language to
C++
.On the Interface pane , set Code interface packaging to
C++ class
and Multi-instance code error diagnostic tonone
. Under Advanced parameters, selectMAT-file logging
.
2. Generate code. On the C++ Code tab, click Build.
Integrate Code
To integrate the generated C++ code from each sensor model with POSIX, hand write the implementation of the send class and generated main program (or create your own). This example provides a customized main program with an implemented send class. To integrate the code from each sensor model, replace the generated main file with the customized main file.
Review the customized main file for each sensor:
1. The customized main file for each sensor is implemented in the same way. As an example, open the customized main file AccelerometerSendMessages_ert_main_customized.cpp
.
2. Review the implemented send class:
The send class function,
SendData
, calls into the POSIX API (mq_send
) to send messages.To provide access to a message queue, a POSIX message queue is added as a class variable and a set method is implemented to set the queue.
class accelSendModelClassSendData_real_T : public SendData_real_T{ mqd_t msgQueue;
public: // Use POSIX to send messages void SendData(const real_T* data, int32_T length, int32_T* status) { unsigned int priority = 1; *status = mq_send(msgQueue, (char*)data, length, priority); }
// Set method for class POSIX queue void SetMQ(mqd_t mq) { msgQueue = mq; } };
After the send class implementation, an instance of the send class (a send object) is created. The send object is then attached to the sensor model to enable the sensor to send messages to the estimate model.
// Create send object static accelSendModelClassSendData_real_T Out1SendData_arg;
// Attach send object to sensor model to send messages static accelSendModelClass rtObj( Out1SendData_arg);
3. Review the added POSIX queue behavior functions. The main function uses these functions to open and close the POSIX queue.
mqd_t openMQ(const char *name, int flags, int maxmsg, int msgsize) { struct mq_attr attr; attr.mq_flags = 0; attr.mq_maxmsg = maxmsg; attr.mq_msgsize = msgsize;
mqd_t msgQueue = mq_open(name, flags, 0664, &attr);
if (msgQueue < 0) { printf("mq_open failed\n"); exit(1); }
return msgQueue; }
void closeMQ(mqd_t msgQueue) { mq_close(msgQueue); }
4. Review the main function. The implemented main function uses the previously defined class and queue functions to send messages and to manage the POSIX message queue. The main function performs the following:
Opens a POSIX message queue.
Sends messages.
Closes the POSIX queue.
int_T main(int_T argc, const char *argv[]) { // Unused arguments (void)(argc); (void)(argv);
// Initialize model rtObj.initialize();
// Open POSIX message queue mqd_t msgQueue = openMQ("/PosixMQ_accel", O_CREAT | O_WRONLY, 2, 16); Out1SendData_arg.SetMQ(msgQueue);
// Send messages while (rtmGetErrorStatus(rtObj.getRTM()) == (NULL)) { rt_OneStep(); }
// Matfile logging rt_StopDataLogging(MATFILE, rtObj.getRTM()->rtwLogInfo);
// Disable rt_OneStep() here
// Close POSIX message queue closeMQ(msgQueue);
return 0; }
Estimate Model
The second component is the estimate model. The estimate model receives messages that have input data from the sensor models and calculates the estimated position of the vehicle.
Prepare Model
Open the model PositionEstimateMessages
.
In the Apps Gallery, click Embedded Coder.
On the toolstrip, set the Stop time to
100
seconds.In the model, double-click to open each inport. In the Block Parameters, set Data type to
double
, Port dimensions to3
, and Sample time to0.01
.Open each Message Receive block. Clear the Use internal queue parameter to enable the model to use a POSIX message queue.
Open the Function block and view the implementation of the estimation algorithm.
Generate Code
1. In the Configuration Parameters dialog box, set these parameters:
On the Code Generation pane, set Language to
C++
.On the Interface pane, set Code interface packaging to
C++ class
and set Multi-instance code error diagnostic tonone
. Under Advanced parameters, selectMAT-file logging
.
2. Generate code. On the C++ Code tab, click Build.
Integrate Code
To integrate the generated C++ code from the estimate model with POSIX, hand write the implementation of the receive class and generated main program (or create your own). The estimate model receives messages from the sensor models and calculates an estimated position of the vehicle. This examples provides a customized main program with an implemented receive class. To integrate the code from the estimate model, replace the generated main file with the customized main file.
Review the customized main file:
1. Open the customized main file PositionEstimateMessages_ert_main_customized.cpp
.
2. Review the implemented receive class:
The receive class function,
RecvData
, calls into the POSIX API (mq_receive
) to receive messages.To provide access to a message queue, a POSIX message queue is added as a class variable and a set method is implemented to set the queue.
class positionEstimateModelClassRecvData_real_T : public RecvData_real_T{ mqd_t msgQueue;
public: // Uses POSIX API mq_receive to receive messages void RecvData(real_T* data, int32_T length, int32_T* status) { // Add receive data logic here unsigned int priority = 1; *status = mq_receive(msgQueue, (char *)data, length, &priority); }
// Set method for class POSIX queue void SetMQ(mqd_t mq) { msgQueue = mq; } };
After the receive class implementation, an instance of the receive class (a receive object) is created for each sensor model. The receive objects are then attached to the estimate model to enable the model to receive messages from each sensor.
// Create receive objects static positionEstimateModelClassRecvData_real_T In1RecvData_arg; static positionEstimateModelClassRecvData_real_T In2RecvData_arg;
// Attach receive objects to estimate model to receive messages static positionEstimateModelClass rtObj( In1RecvData_arg, In2RecvData_arg);
3. Review the added POSIX queue behavior functions. The main function uses these functions to open, close, and unlink the POSIX queue.
mqd_t openMQ(const char *name, int flags, int maxmsg, int msgsize) { struct mq_attr attr; attr.mq_flags = 0; attr.mq_maxmsg = maxmsg; attr.mq_msgsize = msgsize;
mqd_t msgQueue = mq_open(name, flags, 0664, &attr);
if (msgQueue < 0) { printf("mq_open failed\n"); exit(1); }
return msgQueue; }
void closeMQ(mqd_t msgQueue) { mq_close(msgQueue); }
void unlinkMQ(const char *name) { if (mq_unlink(name) < 0) { printf("mq_unlink failed\n"); exit(1); } }
4. Review the main function. The implemented main function uses the previously defined class and queue functions to receive messages and to manage the POSIX queue. The main function performs the following:
Opens a POSIX message queue.
Receives messages.
Closes the POSIX queue.
int_T main(int_T argc, const char *argv[]) { // Unused arguments (void)(argc); (void)(argv);
// Initialize model rtObj.initialize();
// Open POSIX message queues mqd_t msgQueueAccel = openMQ("/PosixMQ_accel", O_CREAT | O_RDONLY, 2, 16); mqd_t msgQueueGPS = openMQ("/PosixMQ_gpspos", O_CREAT | O_RDONLY, 2, 16);
In1RecvData_arg.SetMQ(msgQueueAccel); In2RecvData_arg.SetMQ(msgQueueGPS);
// Receive messages while (rtmGetErrorStatus(rtObj.getRTM()) == (NULL)) { rt_OneStep(); }
// Matfile logging rt_StopDataLogging(MATFILE, rtObj.getRTM()->rtwLogInfo);
// Disable rt_OneStep() here
// Close POSIX message queues closeMQ(msgQueueAccel); closeMQ(msgQueueGPS);
unlinkMQ("/PosixMQ_accel"); unlinkMQ("/PosixMQ_gpspos");
return 0; }
Execute Multisensor Positioning System
To execute the multisensor positioning system, rebuild and run the executables for all models. To view results, run the provided build script positioning_system_script
. An example of results is shown in this figure.
Generate Code for Wireless Sensor in Tire Pressure Monitoring Messaging System
This example shows how to generate code for individual embedded components in a distributed monitoring system. It also describes integrating target-specific external code for message protocol services, data acquisition from a physical environment, and nonvolatile memory (NVM) management on an embedded target.
The example focuses on how to generate C code for a sensor in a tire pressure monitoring system and follows this general workflow:
Review the distributed messaging system.
Access models and other project files.
Open the wrapper model for the sensor.
Locate Simulink® blocks that contain hooks for inserting external code.
Integrate custom target-specific driver and middleware code.
Build and generate C code from the sensor wrapper.
Inspect the generated makefile and build information.
Inspect the generated C code for the sensor.
For more information about the system-level modeling and core algorithms in the sensor and component models, see Wireless Tire Pressure Monitoring System with Fault Logging.
Review Distributed Messaging System
The messaging system highlighted in this example represents an automotive tire pressure monitoring system that uses identical pressure sensors at each wheel.
Sensors communicate wirelessly with the controller in the vehicle and publish periodic messages consisting of acquired pressure data and a unique device ID. The sensor device ID is stored in nonvolatile memory on the sensor and serves as the message topic ID. Device IDs are used to uniquely identify which sensors are associated with the tires installed in the vehicle, as the vehicle might be among many other cars in traffic and erroneous messages must be identified and rejected.
The controller receives the messages and inspects the sender device ID against a list of the relevant sensor IDs to determine which communications to examine and which ones to reject. The controller then evaluates payload pressure data from the relevant sensors for fault conditions. If the controller detects a fault condition, it indicates an error and aggregates the fault condition in a log that is stored in controller NVM.
The system-level model tirePressureMonitoringSystem
contains multiple identical tire pressure sensors modeled by multi-instanced Model blocks that are routed to the controller. Messages are sent by using the Message Send block, which reads the value of an input signal and converts it to a message that carries the signal value. Since all four sensors in the vehicle execute the same algorithm, you deploy the same code to each sensor. The only difference among the sensors is the unique sensor ID, which is burned into the NVM of each sensor before code execution.
The models and deployment steps in this example support a manufacturing process similar to the following:
Engineers update the design of the core algorithm of the sensor (
SensorCoreAlgorithm
) as needed and verify their design at the system level by simulating the system modeltirePressureMonitoringSystem.slx
.Engineers isolate the sensor functionality for code generation by referencing the sensor core algorithm model in a wrapper model (
SensorCodeGenerationWrapper.slx
).Engineers generate code from the
SensorCodeGenerationWrapper.slx
model and provide the compiled code to the device manufacturer.The manufacturer then produces hundreds of identical sensors, each running the exact same code.
As the car is being assembled, the manufacturer flashes unique ID numbers into the NVM of each sensor.
As each sensor powers up, the sensor runs the exact same code as all the others, but that code reads the unique ID number from NVM, seeding it to send uniquely addressed messages.
The following sections in this example focus on steps 2 and 3 of the overall manufacturing process and explain how to generate deployable code for the sensor component in this distributed messaging system. For more information about modeling the sensor core algorithm or system-level simulation, see Wireless Tire Pressure Monitoring System with Fault Logging.
Access Models and Other Project Files
The models and support files for this example are contained in a Simulink project.
To access the project files, enter slexTirePressureMonitoringSystem
at the MATLAB® command line.
Opening the project:
Creates a working folder under your MATLAB current folder
Unzips project files to that working folder
Runs the project initialization script
After the project opens, your working folder contains the top-level items shown in this image.
Open Wrapper Model for Sensor
In the current folder, navigate to models > sensor > wrappers
, and open sensorCodeGenerationWrapper.slx
.
The wrapper model sensorCodeGenerationWrapper
is a top-level model that isolates the sensor core model for code generation. The base algorithm for the sensor model converts pressure readings into messages that can be used with a messaging protocol and is implemented in sensorCoreAlgorithmModelReference
, a reference to the sensorCoreAlgorithm
model in the system-level model.
Locate Simulink Blocks that Contain Hooks for Inserting External Code
How you read NVM, acquire pressure data, and transmit messages depends on what operating system, tire pressure monitoring system (TPMS) sensor, and messaging protocol you are using. For this example, those system-dependent tasks require custom externally-written code.
MATLAB Function blocks in the wrapper model contain hooks for inserting the external code into the generated C code. This example uses the abstract class coder.ExternalDependency
for code insertion.
In the sensor wrapper, the following blocks call public methods defined in the class SensorTargetSpecificCodeExternalDependency
, a subclass of coder.ExternalDependency
:
sensorReadPressureMATLABFunction
— MATLAB Function block that supports reading pressure datasensorTransmitMATLABFunction
— MATLAB Function block that supports publishing transmit messagesreadNVMInitializeFunction
— Initialization block that runs at system startup and supports accessing NVM
For managing the sensor ID in NVM at startup, readNVMInitialization
contains these blocks:
Event Listener block
MATLAB Function block
readNVMMATLABFunction
Parameter Writer block
localDeviceIDParameterWriter
To explore hooks in readNVMInitialization
:
In the sensor wrapper, open the Initialization block
readNVMInitialization
. On start up, the Parameter Writer blocklocalDeviceIDParameterWriter
initializes the local sensor ID variablesensorCoreAlgorithmModelReference.LOCAL_DEVICE_ID
to the value ofSENSOR_PARAMETERS_NVM_ADDRESS_AS_UINT32
read from NVM.Open the MATLAB Function block
readNVMMATLABFunction
to view the code.
function extractedDeviceID = readNVMMATLABFunction(SENSOR_PARAMETERS_NVM_ADDRESS_AS_UINT32) coder.extrinsic( 'warning' ); defaultDeviceID = deviceIDBusTypeDefaultConstructor( );
if( coder.target( 'Rtw' ) ) extractedDeviceID = ... SensorTargetSpecificCodeExternalDependency.sensorReadNVMMatlabWrapper( SENSOR_PARAMETERS_NVM_ADDRESS_AS_UINT32, defaultDeviceID ); else warning( 'This block is intended only for code generation.' ); extractedDeviceID = defaultDeviceID; end return;
end
The function readNVMMATLABFunction
references the coder.ExternalDependency
subclass SensorTargetSpecificCodeExternalDependency
.
To view the class definition for SensorTargetSpecificCodeExternalDependency
and code for creating the function readNVMMATLABFunction
:
In your current folder, navigate to
models > sensor > target_specific_code > @SensorTargetSpecificCodeExternalDependency
.Open
SensorTargetSpecificCodeExternalDependency.m
.Scroll to the method definitions in
SensorTargetSpecificCodeExternalDependency.m
.
Note that the coder.ceval
statement invokes the external source code in sensor_read_nvm
to the code generator.
You can repeat these steps to explore the MATLAB Function blocks for inserting custom code that acquires the pressure readings and transmits messages:
sensorReadPressureMATLABFunction
, which uses the external source code sensor_read_pressuresensorTransmitMATLABFunction
, which uses the external source code sensor_transmit_data
For more information about using coder.ExternalDependency
for code integration, see Develop Interface for External C/C++ Code.
Integrate Custom Target-Specific Driver and Middleware Code
You customize this example by incorporating driver and middleware code specific to your system. You include your custom code in the generated code by updating the preexisting stub functions in the source and header files:
sensor_hand_code_source.c
sensor_hand_code_header.h
To include target-specific code:
In your current folder, navigate to
models > sensor > target_specific_code > source
.Open
sensor_hand_code_source.c
.Insert your custom code into the existing
sensor_read_nvm
,sensor_read_pressure
, andsensor_transmit_data
stub functions.
/* * The functions below are stubbed to allow compilation. However, the premise of the example is that these functions * are target-specific and would not be compiled for desktop simulation. */
#include "sensor_hand_code_header.h"
void sensor_read_nvm( void * nvm_source_address, void * ram_destination_addresss, size_t single_element_size, size_t element_quantity_to_copy ) { return; }
extern double sensor_read_pressure( void ){ double result = 0.0; return( result ); }
void sensor_transmit_data( void * message_source_address, size_t single_element_size, size_t element_quantity_to_send ){ return; }
The functions insert into the generated code by using the coder.ceval
command in the MATLAB Function block definitions.
Build and Generate C Code from Sensor Wrapper
Open the Embedded Coder app and build the sensor wrapper. Simulink builds the models and support files and generates code.
Generated code for the sensor wrapper and referenced core algorithm is located under the generated_code
folder in your current folder:
sensorCodeGenerationWrapper_ert_rtw
— Generated code for the sensor wrapper, including the generated makefilesensorCodeGenerationWrapper.mk
slprj
— Generated code for shared utilities, including the code for the sensor core algorithmsensorCoreAlgorithm
Inspect Generated Makefile and Build Information
Embedded Coder generates the customized makefile sensorCodeGenerationWrapper.mk
by modifying a template makefile with specifics of the Simulink models and further modifications introduced by using the coder.ExternalDependency
subclass.
Specifically, SensorTargetSpecificCodeExternalDependency.m
invokes coder.ExternalDependency.updateBuildInfo
to modify the build information object buildInfo
and provide additional information required to link to external code. This information includes the external files defined in the class.
properties( ... ... ) HEADER_FILE_SHORT_NAME = 'sensor_hand_code_header.h'; SOURCE_FILE_SHORT_NAME = 'sensor_hand_code_source.c'; end
methods( ... ... ) ... function [ ] = updateBuildInfo(buildInfo,buildContext)
externalCodeRootDirectoryAbsolutePath = ... SensorTargetSpecificCodeExternalDependency.getHandCodeRootDirectoryAbsolutePath( );
headerFileDirectoryAbsolutePath = ... fullfile(externalCodeRootDirectoryAbsolutePath,'include');
buildInfo.addIncludePaths(headerFileDirectoryAbsolutePath);
sourceFileDirectoryAbsolutePath = ... fullfile(externalCodeRootDirectoryAbsolutePath,'source');
buildInfo.addSourceFiles(SensorTargetSpecificCodeExternalDependency.SOURCE_FILE_SHORT_NAME);
buildInfo.addSourcePaths(sourceFileDirectoryAbsolutePath);
return;
end
When you build the wrapper model, the code generator adds external code to the makefile and build information.
Open sensorCodeGenerationWrapper.mk
located in generated_code
> sensorCodeGenerationWrapper_ert_rtw
to review the external source files and objects, which include sensor_hand_code_source.c
and sensor_hand_code_source.obj
, respectively. An example of this in the makefile is:
... ## SOURCE FILES SRCS = sensor_hand_code_source.c $(START_DIR)\sensorCodeGenerationWrapper_ert_rtw\sensorCodeGenerationWrapper.c $(START_DIR)\sensorCodeGenerationWrapper_ert_rtw\sensorCodeGenerationWrapper_data.c MAIN_SRC = $(START_DIR)\sensorCodeGenerationWrapper_ert_rtw\ert_main.c ALL_SRCS = $(SRCS) $(MAIN_SRC)
## OBJECTS
OBJS = sensor_hand_code_source.obj sensorCodeGenerationWrapper.obj sensorCodeGenerationWrapper_data.obj
MAIN_OBJ = ert_main.obj
ALL_OBJS = $(OBJS) $(MAIN_OBJ)
...
The absolute path for the external source file is defined in updateBuildInfo
by using
. You can review the makefile to confirm the absolute path for the target object for the external source code addSourcePaths
sensor_hand_code_source.c
.
## INTERMEDIATE TARGETS
# SOURCE-TO-OBJECT
.c.obj : $(CC) $(CFLAGS) -Fo"$@" "$<" ... {C:\<yourPath>\examples\slexTirePressureMonitoringSystem\models\sensor\target_specific_code\source}.c.obj : $(CC) $(CFLAGS) -Fo"$@" "$<"
The absolute path for the external include file is defined in updateBuildInfo
by using
. The path for the external header file addIncludePaths
sensor_hand_code_source.h
is specified in the response file sensorCodeGenerationWrapper_comp.rsp
, which is a text file that contains data to include as command-line arguments to the Windows compiler.
Open the file sensorCodeGenerationWrapper_comp.rsp
, which is also located in generated_code
> sensorCodeGenerationWrapper_ert_rtw
, to review the include folders and confirm the folder path for the external target-specific header file. An example of the include arguments is:
... -IC:/<yourPath>/examples/slexTirePressureMonitoringSystem66/models/sensor/target_specific_code/include -IC:/<yourPath>/examples/slexTirePressureMonitoringSystem66/generated_code/sensorCodeGenerationWrapper_ert_rtw ...
The generated makefile and supplemental compiler command-line arguments instruct the make system utility to compile and link code generated from the model and the externally provided source and header files.
Inspect Generated C Code for Sensor
In the Embedded Coder app, you can inspect the entry-point functions containing your custom code and the sensor core algorithm in the generated code.
Under the C Code tab, open the Code pane to view the generated code.
Inspect the initialization function sensorCodeGenerationWrapper_initialize
defined in sensorCodeGenerationWrapper.c
. The initialization function:
Initializes memory pools and initial states used in connection with the referenced sensor core algorithm model
Calls custom code in
sensor_read_nvm
, which initializes the parameter argumentLOCAL_DEVICE_ID
with the sensor IDSENSOR_PARAMETERS_NVM_ADDRESS_AS_UINT32
read from NVM
/* Model initialize function */ void sensorCodeGenerationWrapper_initialize(void) {
/* states (dwork) */ rtDW.sensorCoreAlgorithmModelReference_InstanceData.rtdw.sensorMeasurmentMessageOutputPort_SendData.SendData = Receive_SendData; rtDW.sensorCoreAlgorithmModelReference_InstanceData.rtdw.sensorMeasurmentMessageOutputPort_SendData.host = (NULL);
/* Assign pointer for instance parameters, Block: '<Root>/sensorCoreAlgorithmModelReference' */ rtDW.sensorCoreAlgorithmModelReference_InstanceData.rtm.sensorCoreAlgorithm_InstP_ref = &rtInstP.sensorCodeGenerationWrapperrtsensorCoreAlgorithmModelReference;
{ DeviceIDBusType s; int32_T i;
/* Start for slMsgMgr: '<Root>/slMsgMgr' */ sensorCodeGenerationWrapper_initMemPool (&rtDW.slMsgMgr_MemPool_MeasurementMsgBusType, &rtDW.slMsgMgr_memArray_MeasurementMsgBusType[0], &rtDW.slMsgMgr_freeList_MeasurementMsgBusType[0], 2);
/* Start for Receive: '<Root>/Receive' */ rtDW.Receive = rtP.Receive_InitialValue;
/* Outputs for Atomic SubSystem: '<Root>/readNVMInitializeFunction' */ /* MATLAB Function: '<S1>/readNVMMATLABFunction' */ for (i = 0; i < 10; i++) { s.device_id[i] = 0U; }
size_t elementSize; elementSize = sizeof(s);
/* ParameterWriter: '<S1>/localDeviceIDParameterWriter' incorporates: * MATLAB Function: '<S1>/readNVMMATLABFunction' */ sensor_read_nvm((void *)rtP.SENSOR_PARAMETERS_NVM_ADDRESS_AS_UINT32, &rtInstP.sensorCodeGenerationWrapperrtsensorCoreAlgorithmModelReference.LOCAL_DEVICE_ID, elementSize, (size_t)1.0);
/* End of Outputs for SubSystem: '<Root>/readNVMInitializeFunction' */ } }
Inspect the execution step function sensorCodeGenerationWrapper_step
defined in sensorCodeGenerationWrapper.c
. The step function:
Calls custom code in
sensor_read_pressure
, which returns the acquired pressure data and stores it inreal_T rtb_pressure
Runs the sensor model core algorithm
sensorCoreAlgorithm
during each execution cycleCalls custom code in
sensor_transmit_data()
, which transmits the sensor payload data by using calls specific to your message protocol service
Inspect the sensor core algorithm sensorCoreAlgorithm
defined in sensorCoreAlgorithm.c
. The sensor core algorithm constructs a message from the unique sensor ID, LOCAL_DEVICE_ID
, and the data acquisition payload, real_T rtb_pressure
, as MeasurementMsgBusType
structure elements for transmission.
Note that sensorCoreAlgorithm.c
is generated from the shared reference model. You can double-click the sensorCoreAlgorithmModelReference
block in the sensor wrapper model to open sensorCoreAlgorithm.c
in the Code pane.
See Also
coder.ceval
| coder.ExternalDependency
| coder.ExternalDependency.updateBuildInfo
| Parameter Writer | Send | Receive | Queue
Related Topics
- Simulink Messages Overview
- Establish Message Send and Receive Interfaces Between Software Components
- Model Message-Based Communication Integrated with POSIX Message Queues
- Generate Code for Wireless Sensor in Tire Pressure Monitoring Messaging System
- Wireless Tire Pressure Monitoring System with Fault Logging
- Prepare Sensor and Controller Models in a Distributed Monitoring System for Code Generation
- Develop Interface for External C/C++ Code