Main Content

Deploy Component Algorithm as Component Model Library by Using CMake

Use the CMake approach to build code generated from a component algorithm model, and deploy the algorithm code as a component model library. CMake is a third-party, open-source tool for build process management. CMake uses configuration files (CMakeLists.txt) to generate standard build files for native build environments such as, makefiles, Ninja files, or Microsoft® Visual Studio® projects. This example guides you through these steps:

  1. Open the model.

  2. Generate component code from the model.

  3. Implement handwritten integration code.

  4. Configure CMake for your target environment.

  5. Generate build artifacts and build integrated code.

  6. Run the executable program.

Open Model

Open example model ComponentDeploymentFcn.slx.

open_system('ComponentDeploymentFcn');

Generate Component Code from Model

  1. Configure your target platform for the code generator.

  2. Generate source code.

  3. Create a CMakeLists.txt file for the component model library.

  4. Optionally, package generated code files and CMakeLists.txt files into a ZIP file that you can use to relocate the files.

Configure Model Target Environment Device Type

Inform the code generator about the C language constraints for your target platform by defining a workspace variable (prodHWDeviceType) that you can use for setting model configuration parameter ProdHWDeviceType.

switch computer('arch')
    case 'win64'
        prodHWDeviceType = 'Intel->x86-64 (Windows64)';
    case 'glnxa64'
        prodHWDeviceType = 'Intel->x86-64 (Linux 64)';
    case 'maci64'
        prodHWDeviceType = 'Intel->x86-64 (Mac OS X)';
    case 'maca64'
        prodHWDeviceType = 'Apple->ARM64';
    otherwise
        assert(false, 'Platform not supported');
end
set_param('ComponentDeploymentFcn', 'ProdHWDeviceType', prodHWDeviceType);
set_param('ComponentDeploymentFcn', 'ProdLongLongMode', 'on');

Generate Code and CMakeLists.txt for Component Model Library

To generate a platform-independent CMakeLists.txt file, specify the CMake toolchain.

set_param('ComponentDeploymentFcn', 'Toolchain', 'CMake');

Specify the creation of a ZIP file after code generation. The packNGo function packages the generated source code and CMake configuration file in the ZIP file.

set_param('ComponentDeploymentFcn', 'PackageGeneratedCodeAndArtifacts', 'on');

Generate code.

slbuild('ComponentDeploymentFcn')
### Starting build procedure for: ComponentDeploymentFcn
### Successful completion of code generation for: ComponentDeploymentFcn

Build Summary

Top model targets built:

Model                   Action           Rebuild Reason                                    
===========================================================================================
ComponentDeploymentFcn  Code generated.  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 14.884s

In the services subfolder beneath the build folder, the software generates a header file services.h. The file defines interfaces to services that you must implement for an application containing the generated component.

dir(fullfile('ComponentDeploymentFcn_ert_rtw', 'services', 'services.h'))
services.h  

In the services/lib subfolder beneath the build folder, the software generates a CMakeLists.txt file that you can use to build the generated code into a static library.

dir(fullfile('ComponentDeploymentFcn_ert_rtw', 'services', 'lib', 'CMakeLists.txt'))
CMakeLists.txt  

(Optional) Relocate Generated Files to Another Development Environment

You can relocate the ZIP file, which contains the generated code, build artifact, and CMakeLists.txt files to another development environment.

%% Optional
copyfile('ComponentDeploymentFcn.zip', 'path-for-destination');
cd path-for-destination
unzip('ComponentDeploymentFcn.zip');

Implement Handwritten Integration Code

main function

Implement handwritten code for a main function in your code generation folder (codeGenerationFolder/). For this example, you can use the function in file ComponentDeploymentFcnIntegration.c.

open('ComponentDeploymentFcnIntegration.c');
#include "ComponentDeploymentFcn.h"
#include <stddef.h>
#include <stdio.h>
extern uint64_T tick;   // Variable used by timer service implementation
const size_t nIter = 3;
int main(void) {
 
    // Run model initialize function
    printf("\nRunning model initialize function\n");
    CD_initialize();
 
    for (size_t k = 0; k < nIter; ++k) {
 
        // Run periodic Accumulator function at every iteration
        printf("\nRunning Accumulator function (iteration %llu)\n", k);
        CD_accumulator();
 
        if (k == 1) {
            // The asynchronous Integrator function only runs when k == 1
            printf("\nRunning Integrator function (iteration %llu)\n", k);
            CD_integrator();
        }
 
        // Increment timer tick count
        tick++;
    }
 
    // Run model terminate function
    printf("\nRunning model terminate function\n");
    CD_terminate();
}

services.c

In your code generation folder (codeGenerationFolder/), create handwritten code that provides implementations of the target platform services declared in services.h. For this example, you can use the code in file services.c.

open('services.c');
#include "services.h"
#include <stdio.h>
 
uint64_T tick = 0;
 
static real_T DataTransfer_storage[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static real_T input_storage[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static real_T NVM_storage[10] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100};
static real_T output_storage[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

/* timer services */
uint64_T get_tick_outside_tick_CD_integrator(void) {
    printf("Service function called: get_tick_outside_tick_CD_integrator.\n");
    return tick;
}
 
/* data transfer services */
const real_T * get_CD_accumulator_DataTransfer(void) {
    printf("Service function called: get_CD_accumulator_DataTransfer.\n");
    return &DataTransfer_storage[0];
}
 
real_T * set_CD_integrator_DataTransfer(void) {
    printf("Service function called: set_CD_integrator_DataTransfer.\n");
    return &DataTransfer_storage[0];
}
 
real_T * set_CD_initialize_DataTransfer(void) {
    printf("Service function called: set_CD_initialize_DataTransfer.\n");
    return &DataTransfer_storage[0];
}
 
/* receiver services */
const real_T * get_CD_integrator_InBus_u(void) {
    printf("Service function called: get_CD_integrator_InBus_u.\n");
    return &input_storage[0];
}
 
const real_T * get_CD_initialize_InBus_NVM(void) {
    printf("Service function called: get_CD_initialize_InBus_NVM.\n");
    return &NVM_storage[0];
}
 
/* sender services */
real_T * getref_CD_accumulator_OutBus_y(void) {
    printf("Service function called: getref_CD_accumulator_OutBus_y.\n");
    return &output_storage[0];
}
 
real_T * getref_CD_terminate_OutBus_NVM(void) {
    printf("Service function called: getref_CD_terminate_OutBus_NVM.\n");
    return &NVM_storage[0];
}

CMakeLists.txt

To produce an executable application that incorporates the handwritten main function and service implementations, and links with the library generated from the Simulink model, in your code generation folder (codeGenerationFolder), create a handwritten CMakeLists.txt file. The file must perform these tasks:

  • Import the previously generated CMakeLists.txt file that contains the rules to build the static library from the component code.

  • Add a rule to build the handwritten source files ComponentDeploymentFcnIntegration.c and services.c into an executable program.

  • Link the executable program with the static library that contains the compiled component code.

For this example, you can use the provided CMakeLists.txt file.

cmake_minimum_required(VERSION 3.12)
 
# Set a name for the project
project(ComponentDeploymentFcnIntegration)
 
# Use the "add_subdirectory" command to reference the CMakeLists.txt file
# that was generated by codebuild for the code generated from
# the Simulink model.  This has defined a static library target
# named "ComponentDeploymentFcn" which we will link into
# the application further down.
add_subdirectory(ComponentDeploymentFcn_ert_rtw/services/lib)
 
# Use the "add_executable" command to define an executable target named
# "ComponentDeploymentFcnIntegration" that is built from the hand-written
# source files.
add_executable(ComponentDeploymentFcnIntegration
    ComponentDeploymentFcnIntegration.c
    services.c)
 
# Specify that the executable target "ComponentDeploymentFcnIntegration"
# incorporating the handwritten code should be linked against the
# "ComponentDeploymentFcn" library target containing the code
# generated from the Simulink model.
target_link_libraries(ComponentDeploymentFcnIntegration ComponentDeploymentFcn)

Configure CMake

Configure the CMake command and arguments for your target environment. In the MATLAB Command Window, run one of these examples.

Windows - Microsoft Visual Studio 2017 Solution

cmakeCommand = ['"', fullfile(matlabroot, 'bin', 'win64', 'cmake', 'bin', 'cmake.exe'), '"'];
cmakeArguments = '-G "Visual Studio 15 2017"';

Windows - Microsoft Visual Studio 2019 Solution

cmakeCommand = ['"', fullfile(matlabroot, 'bin', 'win64', 'cmake', 'bin', 'cmake.exe'), '"'];
cmakeArguments = '-G "Visual Studio 16 2019"';

Windows - Microsoft Visual Studio with NMake

[status, msvcInstall] = system('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -property installationPath');
msvcInstall = strtrim(msvcInstall);
assert(status == 0 && ~isempty(msvcInstall), 'Could not find location of Microsoft Visual Studio installation');
vcvarsAll = fullfile(msvcInstall, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat');
envSetupCommand = ['"' vcvarsAll '" amd64 && '];
cmakeCommand = [envSetupCommand, '"', fullfile(matlabroot, 'bin', 'win64', 'cmake', 'bin', 'cmake.exe'), '"'];
cmakeArguments = '-G "NMake Makefiles"';

Windows - MinGW

mingwLoc = getenv('MW_MINGW64_LOC');
assert(~isempty(mingwLoc), 'Could not find location of MinGW installation');
mingwLocBin = [mingwLoc, '/bin'];
envSetupCommand = ['set PATH=', mingwLocBin, ';%PATH% && '];
cmakeCommand = [envSetupCommand, '"', fullfile(matlabroot, 'bin', 'win64', 'cmake', 'bin', 'cmake.exe'), '"'];
cmakeArguments = ['-G "MinGW Makefiles" ', ...
    '-DCMAKE_C_COMPILER="gcc.exe" ', ...
    '-DCMAKE_CXX_COMPILER="g++.exe"'];

Linux - GCC and GMake

cmakeCommand = ['"', fullfile(matlabroot, 'bin', 'glnxa64', 'cmake', 'bin', 'cmake'), '"'];
cmakeArguments = '-G "Unix Makefiles"';

Mac - XCode with Clang

cmakeCommand = ['"', fullfile(matlabroot, 'bin', computer('arch'), 'cmake', 'bin', 'cmake'), '"'];
cmakeArguments = '-G "Xcode"';

Generate Build Artifacts and Build Integrated Code

To generate required build artifacts and build the integrated code, run CMake. The CMake tool processes the CMakeLists.txt file in the folder specified through the -S flag, and generates the configuration files for your build system in the folder that you specify through the -B flag. In codeGenerationFolder, run this commmand:

[status1, cmdout1] = system([cmakeCommand ' -S . -B build ', cmakeArguments], '-echo');

Using the --build flag, build the application.

[status2, cmdout2] = system([cmakeCommand ' --build build'], '-echo');

The location of the executable program file that the CMake generator produces depends on the CMake generator that you use.

  • Makefile based CMake generator - Relative to your current working folder, places the executable program file in subfolder build.

  • Project-based CMake generator - Relative to your current working folder, places the executable program file in folder build/Debug.

Windows - Visual Studio Solution

exeLocation = fullfile('build', 'Debug', 'ComponentDeploymentFcnIntegration.exe');

Windows - NMake Makefiles or GMake Makefiles

exeLocation = fullfile('build', 'ComponentDeploymentFcnIntegration.exe');

Linux - GMake Makefiles

exeLocation = fullfile('build', 'ComponentDeploymentFcnIntegration');

Mac - Xcode project

exeLocation = fullfile('build', 'Debug', 'ComponentDeploymentFcnIntegration');

Run Executable Program

Use this command to run the compiled executable program:

[status3, cmdout3] = system(exeLocation, '-echo');

Example output:

Running model initialize function 
Service function called: set_ModelInitialize_DataTransfer. 
Service function called: get_CD_initialize_InBus_NVM. 
 
Running Accumulator function (iteration 0) 
Service function called: get_CD_accumulator_DataTransfer. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
 
Running Accumulator function (iteration 1) 
Service function called: get_CD_accumulator_DataTransfer. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
 
Running Integrator function (iteration 1) 
Service function called: set_CD_integrator_DataTransfer. 
Service function called: get_tick_outside_CD_integrator. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
Service function called: get_CD_integrator_InBus_u. 
 
Running Accumulator function (iteration 2) 
Service function called: get_CD_accumulator_DataTransfer. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
Service function called: getref_CD_accumulator_OutBus_y. 
 
Running model terminate function 
Service function called: getref_CD_terminate_OutBus_NVM. 
close_system('ComponentDeploymentFcn',0)

See Also

Related Topics