Processor-In-the-Loop Simulation on Embedded Linux Boards
By Lars Rosqvist, Roger Aarenstrup, and Kristian Lindqvist, MathWorks
In processor-in-the-loop (PIL) simulation, code generated from a Simulink® model runs directly on the target hardware, which means you can test models on the hardware using the same test cases as on the host. PIL tests are designed to expose problems with execution in the embedded environment. For instance, does your control loop fit within the execution time available on the embedded processor?
Using Raspberry Pi™ as an example, this article describes a PIL testing workflow based on Simulink support for custom targets, and shows how to implement a custom toolchain for easier compilation and linking.
The code used in this example is available for download.
PIL Process Overview
PIL simulation lets you automatically download and run tests on target hardware. Figure 1 summarizes the steps. When you start the simulation you initiate the code generation from the model. After automatically compiling and linking the code using toolchainInfo from the toolchain, the simulation downloads and starts the executable on the target hardware. You can then analyze the test results in MATLAB®.
Developing the PIL Configuration
Simulink has built-in support for building custom PIL implementations. To build up a custom PIL framework we do the following:
- Implement a connectivity configuration class.
- Register the connectivity configuration.
The folder structure for our example PIL implementation for Raspberry Pi looks like this:
Implement the Connectivity Configuration
The connectivity configuration is where you specify all actions necessary to perform the PIL. It collects instances of a builder, a launcher, and a communicator.
The connectivity configuration class does the following:
- Sets up target communication.
The target application framework class,TargetApplicationFramework
, configures the communication drivers for the target side. It creates a BUILDINFO object containing PIL-specific files, including amain.c
file that will be combined with the PIL component libraries to create the PIL application.
The first action in the constructor for this class is to implement a call the constructor of the super class,rtw.pil.RtIOStreamApplicationFramework
. After this, we add the main file and any additional files necessary for the communication.
In the Raspberry Pi example we add the files necessary to communicate using TCP/IP.
- Instantiates the builder.
The builder is instantiated by calling the super class
rtw.connectivity.MakefileBuilder.
- Instantiates the launcher.
The launcher class, Launcher, supports starting and stopping the application associated with a builder object. It must include two methods:startApplication
andstopApplication
.
The launcher instantiates the super classrtw.connectivity.Launcher
in the constructor.
In the Raspberry Pi example we have connected the Launcher to the hardware using a third-party tool. To make the example simple we also download the application instartApplication
.
- Sets up host communication.
If the communication protocol is TCP/IP or serial communication you can use the existing library files for the host communication, in other cases you can implement your ownrtiostream
. Because the API forrtIOStream
functions is designed to be independent of the physical layer across which the data is sent, it can be customized to handle other communications.
In our example we use TCP/IP as communication protocol, and can reuse the MATLAB TCP/IP library,libmwrtiostreamtcpip
.
- Calls the super class constructor to register components.
Now it is time to call the super class constructor and pass the builder, launcher, and host communication objects to the rtw.connectivity.Config.
- Registers a hardware-specific timer.
One advantage of running a PIL simulation is to get an execution profile on the target. To do this you must register a hardware-specific timer in the configuration class. For Raspberry Pi a timer is included in the rtlib library. All we do is replace the code_profile_read_timer function using a code replacement table.
The connectivity configuration in our Raspberry Pi example looks like this:
classdef ConnectivityConfig < rtw.connectivity.Config % Copyright 2015 The MathWorks, Inc methods function this = ConnectivityConfig(componentArgs) % 1. Set up target communication targetApplicationFramework = ... raspberryPiPIL.TargetApplicationFramework(componentArgs); % 2. Instantiate the builder builder = rtw.connectivity.MakefileBuilder(componentArgs, ... targetApplicationFramework, ''); % 3. Instantiate the launcher launcher = raspberryPiPIL.Launcher(componentArgs, builder); % 4. Set up host communication % File extension for shared libraries (e.g. .dll on Windows) sharedLibExt=system_dependent('GetSharedLibExt'); % Evaluate name of the rtIOStream shared library rtiostreamLib = ['libmwrtiostreamtcpip' sharedLibExt]; hostCommunicator = rtw.connectivity.RtIOStreamHostCommunicator(... componentArgs, ... launcher, ... rtiostreamLib); % Set a timeout value for initial setup of the communications % channel hostCommunicator.setInitCommsTimeout(20); % Configure a timeout period for reading of data by the host % from the target. timeoutReadDataSecs = 60; hostCommunicator.setTimeoutRecvSecs(timeoutReadDataSecs); % Custom arguments that will be passed to the % rtIOStreamOpen function in the rtIOStream shared % library (this configures the host-side of the % communications channel) tc = getTargetConfiguration(); rtIOStreamOpenArgs = {... '-hostname', tc.IP, ... '-client', '1', ... '-blocking', '1', ... '-port', tc.PortStr,... }; hostCommunicator.setOpenRtIOStreamArgList(... rtIOStreamOpenArgs); % 5. Call super class constructor to register components this@rtw.connectivity.Config(componentArgs,... builder,... launcher,... hostCommunicator); % 6. Register hardware specific timer this.setTimer(raspberryPiPIL.RaspberryPiPILCRL()); end end end
Register the Connectivity
To use the new PIL framework we have registered it in a sl_customization.m
file.
The connectivity configuration is registered by a call to registerTargetInfo
. A connectivity configuration must have a unique name and be associated with the developed connectivity implementation class, ConnectivityConfig
. The properties of the configuration define the system target file, toolchain, and hardware with which the connectivity implementation class is compatible.
In the Raspberry Pi example below the system target file must be set to Embedded Coder®, and the toolchain must be the Raspberry Pi toolchain.
function sl_customization(cm) % SL_CUSTOMIZATION for raspberryPiPIL % - Specifies the valid configuration parameter settings to enable the % raspberryPiPIL implementation. % Copyright 2015 The MathWorks, Inc cm.registerTargetInfo(@loc_createConfig); % Get default (factory) customizations hObj = cm.RTWBuildCustomizer; % Specify settings for valid PIL configuration function config = loc_createConfig config = rtw.connectivity.ConfigRegistry; config.ConfigName = 'Raspberry Pi PIL'; config.ConfigClass = 'raspberryPiPIL.ConnectivityConfig'; % The following model configuration settings must be matched to be valid config.SystemTargetFile = {'ert.tlc'}; config.Toolchain = {'raspberryPiw64 | gmake makefile (64-bit Windows)'} ;config.TargetHWDeviceType = {};
Develop a Custom Toolchain
A toolchain is a collection of tools required to compile, link, download, and run on a specified platform. A custom toolchain simplifies the compilation to an executable. With a toolchain you do not need to create a template makefile; the makefile is built up by the information specified in the toolchain. It is quicker and easier to integrate compiler, linker, and archiver for a custom toolchain.
We can easily select a custom toolchain in the model configuration settings (Figure 2).
Embedded Coder uses the toolchain information automatically when the code is generated and compiled according to the flow described earlier.
The toolchain information specifies how the makefile will be built up. It includes, for instance, the compiler flags for debugging, include files, and file extensions.
In the Raspberry Pi example we use a gcc arm-linux cross-compiler for the C compilation. As mentioned before we add rtlib
library because of the hardware timer that we shall use. When adding rtlib
you write it as –lrt. A code snippet from the toolchain file:
% ------------------------------ % C Compiler % ------------------------------ tool = tc.getBuildTool('C Compiler'); tool.setName( 'RaspberryPi C Compiler'); tool.setCommand( 'arm-linux-gnueabihf-gcc'); tool.setPath( ''); tool.setDirective( 'IncludeSearchPath', '-I'); tool.setDirective( 'PreprocessorDefine', '-D'); tool.setDirective( 'OutputFlag', '-o'); tool.setDirective( 'Debug', '-g'); tool.setFileExtension( 'Source', '.c'); tool.setFileExtension( 'Header', '.h'); tool.setFileExtension( 'Object', '.obj'); tool.Libraries = {'-lrt'}; tool.setCommandPattern('|>TOOL<| |>TOOL_OPTIONS<| |>OUTPUT_FLAG<||>OUTPUT<|');
The toolchain lets you specify the tools used for the compilation and can set different toolchain flags depending on the use case. For instance, you can have different settings for “fast runs” or “fast builds.”
Summary and Next Steps
Now we have a PIL framework and a toolchain that supports our target hardware and we can do the following:
- Test our algorithms on the target directly from Simulink.
- Get execution profiles for the functions.
- Compare the results of model-level simulation and simulations using the compiled object code and target drivers (an important verification step for many safety standards).
With a toolchain you can compile without customizing makefiles. The toolchain not only brings advantages when you run a PIL simulation but also when you deploy models on the embedded Linux® hardware. When “Generate code only” is not checked in the Code Generation tab in the configuration setting, you can directly build executables for the target.
Published 2016 - 92982v00