Technical Articles

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®.

PIL_fig1_w.jpg
Figure 1. PIL process overview.

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:

  1. Implement a connectivity configuration class.
  2. Register the connectivity configuration.

The folder structure for our example PIL implementation for Raspberry Pi looks like this:

PIL_fig2_w_wl.jpg

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:

  1. 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 a main.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.
  1. Instantiates the builder.
    The builder is instantiated by calling the super class
    rtw.connectivity.MakefileBuilder.
  1. Instantiates the launcher.
    The launcher class, Launcher, supports starting and stopping the application associated with a builder object. It must include two methods: startApplication and stopApplication.

    The launcher instantiates the super class rtw.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 in startApplication.
  1. 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 own rtiostream. Because the API for rtIOStream 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.
  1. 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.
  1. 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).

PIL_fig3_w.jpg
Figure 2. Model configuration settings with Toolchain selected.

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.

About the Author

Lars Rosqvist is a senior consultant who helps customers in the automotive, railway, mining, and industrial automation industries adopt Model-Based Design methodologies from initial steps to full deployment. Before joining MathWorks, Lars developed climate control systems in the automotive industry. Lars received his M.S. in applied physics and electrical engineering from Linköping University of Technology.

Roger Aarenstrup is a senior consultant who works with customers in the automotive, medical, energy production, and industrial automation industries. He helps customers adopt and optimize Model-Based Design methodologies and workflows. With over 15 years’ experience, Roger has worked with modeling and simulation in the aerospace industry, robot control for industrial automation, and real-time operating system development for telecommunications. He received an M.S. in computer science and engineering from Luleå University of Technology and an M.B.A. in e-business from the University of Gävle.

Kristian Lindqvist is a senior pilot engineer who works with pilot projects for adopting Model-Based Design for customers in all major industries. Kristian has a long history using MathWorks products in the automotive industry from all stages of the development process. Kristian received both an M.S. in electrical engineering and a Lic.Eng. in automatic control from the Royal Institute of Technology in Stockholm.

Published 2016 - 92982v00