Main Content

Create Target Registration Packages for C/C++ Test Execution on Targets

This example describes how to create a target registration package for running C/C++ tests authored with Polyspace® Test™ on a target. Here, the word target is used as an umbrella term for an embedded system with specific processor architecture, cross-compilation tools, and channels for communication with a host computer.

You can run both graphical and xUnit tests added to a Polyspace Platform project on a target. The steps to execute tests on targets are similar to those for executing tests on a host computer; however, you must perform a one-time setup to register your target in the Polyspace Platform user interface. This example describes how to create a target registration package for the one-time setup. The example uses a specific development board (STM32 Nucleo-144 board with STM32F767ZI MCU) as the target to illustrate the steps but you can easily generalize these steps to your development board.

For more information on using the target registration package for registering the target and running tests, see:

Overview

A target registration package primarily consists of a MATLAB® file for target registration and another for target unregistration. Other files in the package are referenced from the target registration and unregistration files.

To create a target package, follow these steps:

  1. Generate C/C++ driver code to handle low-level I/O interactions with your target. Your target registration package will leverage the driver code for communication with the target. If you are already deploying your source code to a target, you may have generated these driver files.

  2. Create a preferences file in JSON format to store target-related paths and other information. Others using your target package can simply modify this file to refer to paths on their machines without touching the rest of the package.

  3. Create the outline of a target registration package. In particular, create a template target registration MATLAB file to read from the preferences file, and define and register a board and toolchain.

    To complement the target registration, create a second MATLAB file for unregistering the previously registered board and toolchain.

  4. Fill in the details of the MATLAB file for target registration. You might have to create additional supporting files along the way as needed.

This example walks through these steps for the STM32 Nucleo-144 development board.

Generate Driver Files

Before creating a target registration package, you must generate the driver files that handle all low-level I/O interactions with your target board. You can write the driver files for your board or generate them using software tools specific to your board. Once you have the driver files, save the root path to these files in a preferences file (see Create Preferences File).

The steps to generate the driver files vary depending on the target. If you are already deploying your source code to a target, you may have generated these driver files.

For the STM32 Nucleo-144 board, to generate these files, follow these steps1 :

  1. Download the STM32CubeMX software.

    In addition, you will also need these tools:

    • STM32CubeIDE, which comes with a GNU cross-compiler for Arm targets. You will provide the path to this compiler later.

    • STM32CubeProgrammer, which comes with a command-line tool to load your test executable on the board. You will provide the path to this tool later.

  2. Open the STM32CubeMX tool. On the starting screen, select Start My project from ST Board > ACCESS TO BOARD SELECTOR.

  3. Search for the STM32F7 series of boards, select the board NUCLEO-F767ZI in the search results and click Start Project.

    Search for stm32f7 and locate the board STM32 NUCLEO-F767ZI.

    This creates a project and opens a layout of the board on a Pinout & Configuration tab.

  4. On this tab, configure the type of communication your board will be using.

    In this example, the USART communication interface will be used. To specify this communication interface:

    • In the Categories tree on the left, under the Connectivity section, select USART3. In the USART3 Mode and Configuration page on the right, select the Asynchronous mode in the Mode dropdown. Then, in the Configuration section, configure this port as follows:

      • Select the GPIO Settings tab. Note that the communications uses two pins, one for sending and another for receiving data.

        Select USART3 on the left and on the GPIO Settings tab, note the pins PD8 and PD9.

      • Select the NVIC Settings tab. Make sure that the option USART3 global interrupt is selected. This is required because the communication between your host computer and the board will use global interrupts.

      • Select the DMA Settings tab. Click Add to add the USART3_RX and USART3_TX settings.

    • In the Categories tree on the left, under the Timers section, select TIM1. Change Slave Mode to a setting other than Disable and select a Trigger Source.

  5. From the Pinout & Configuration tab, navigate to the Project Manager tab. In this example, you will not create a project but generate the driver files that a project typically uses for low-level I/O interaction.

    Before generating driver code, specify these options to make sure that the generated driver files do not contain a main() function:

    • Project node – Enter a Project Name and Project Location. Select the option Do not generate the main() so that you can later specify your own main() function via the target registration file. Also, note the location specified in Toolchain Folder Location. This is the location where your driver files will be stored (referred to as DriverPath later). In addition, make sure that Toolchain / IDE is set to STM32CubeIDE and Generate Under Root is not selected.

    • Code Generator node – Select Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral. This option allows you to generate separate files with functions for your USART3 peripherals and call those functions from the main() function that you will define using the target registration file.

  6. Select Generate Code on the top right.

    The driver files will be generated in the folder specified earlier for the setting Toolchain Folder Location.

Create Preferences File

To make sure that your target registration package is usable on multiple computers with minimal modifications, isolate information on paths specific to your computer into a separate preferences JSON file. Others using your target registration file can simply replace the paths in the JSON file without touching your target registration MATLAB code.

To create a preferences file for the STM32 Nucleo-144 board:

  1. Copy the following JSON data (or equivalents for your STM32 Nucleo-144 board) into a file.

    {
        "ProgrammerPath": "C:/Program Files/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI.exe",
        "GnuARMPath": "C:/ST/STM32CubeIDE_1.16.1/STM32CubeIDE/plugins/com.st.stm32cube.ide.mcu.externaltools.gnu-tools-for-stm32.12.3.rel1.win32_1.0.200.202406191623/tools",
        "DriverPath": "C:/ProgramData/supportpackages/stm32/stm32f7xx_based/stm32f7xx_dpfp/boards/nucleo_f767zi",
        "ToolchainName": "GCC ARM Cortex M | STM32F767ZI",
        "BoardName": "STM32F767ZI Nucleo",
        "TargetConnectionName": "STM32 F767ZI - Host Connection",
        "COMPORT" : "COM3"
    }
    Here:

    • ProgrammerPath stores the file path to the STM32CubeProgrammer command-line interface (STM32_Programmer_CLI.exe). This interface is used to program (flash) the STM32 microcontroller.

    • GnuARMPath stores the path to the GNU Arm toolchain binaries (compiler, assembler, linker, etc.) provided with STM32CubeIDE. This is used to build your embedded application for the STM32 board.

    • DriverPath stores the path to the driver files you generated in the previous section.

    • ToolchainName and BoardName stores the names to be used for the board and toolchain you will register. After target registration, these names appear for the options Compilation toolchain (Testing) and Target board name (Testing).

      For the sake of brevity, the example preferences file stores only the board and toolchain name. You can store any constant (processor name, execution tool name, and so on) in the preferences file. Alternatively, you can store a single root name in the preferences file and create names by appending to the root name in your target registration MATLAB code.

    • COMPORT stores the serial communication port used to communicate with the board.

  2. Create a folder STM32Package and save the preferences file as targetpreferences.json. In the next section, you will see how to read the preferences file.

    This is the beginning of your target package creation. You will save all subsequent files in this folder. As of now, your target package folder contains the following:

    STM32Package/
    ├── targetpreferences.json

Create Target Registration Package

Create the outline of the target registration package:

  • A MATLAB function in a file readJSONToStruct.m to read the JSON preferences.

  • A subfolder targetUtilities to store helper code, with a further subfolder src to store C code.

  • A template MATLAB file packageRegister.m for target registration that calls the above function to read the preferences, adds the helper code subfolders to the MATLAB path, and outlines the steps required for target registration.

  • A MATLAB file packageUnregister.m that unregisters the target.

  • A README.md file for notes.

At the end of this step, your target package folder will contain the following:

STM32Package/
├── targetpreferences.json
├── readJSONToStruct.m
├── packageRegister.m
├── packageUnregister.m
├── targetUtilities/
│   ├── src/
└── README.md
The following subsections go into details of reading the preferences file, and creating the template registration and unregistration files.

Reading Preferences File

To read the preferences JSON file, you can adopt one of the following approaches.

  • Write your own MATLAB function in the file readJSONToStruct.m to read from a JSON file and return the JSON keys in a structure.

    function jsonStruct = readJSONToStruct(jsonFullPath)
          arguments
              jsonFullPath (1,1) string
          end
          jsonStr = fileread(jsonFullPath);
          try
              jsonStruct = jsondecode(jsonStr);
          catch
              error('TargetJsonError');
          end
    end
    Using this function allows you to access the JSON key values in MATLAB code using the returned structure. For instance, you can read the value of the JSON key ProgrammerPath using the syntax:
    prefs = readJSONToStruct("targetpreferences.json")
    prefs.ProgrammerPath

  • Define a MATLAB class that inherits from the class pstest.target.TargetPreferences (available only with Polyspace Test). The class constructor takes a JSON file path as argument and reads the JSON into an object with the JSON keys as members. In this case, following MATLAB conventions, you will write the class definition in a file STM32F767ZI_Preferences.m.

    classdef STM32F767ZI_Preferences < pstest.target.TargetPreferences
        methods(Access=public)
            function this = STM32F767ZI_Preferences(jsonFullPath)
                arguments
                    jsonFullPath (1,1) string
                end
                this@pstest.target.TargetPreferences(jsonFullPath);
            end
        end
    end
    If you create an object of this class in MATLAB code:
    prefs = STM32F767ZI_Preferences("targetpreferences.json");
    You can access the JSON key-values using the members of this object. For instance, in the above example, you can access the JSON key ProgrammerPath using prefs.ProgrammerPath.

Creating Template File for Target Registration

Add the following template content for target registration to the file packageRegister.m. The template invokes the previously created function readJSONToStruct() to read the preferences file, adds all utility subfolders to the MATLAB path, and creates separate sections for the various characteristics of a target. In the next section, you will fill the various sections.

%% Read preferences file and create a preferences structure
currentFilePath = fileparts(mfilename('fullpath'));
addpath(currentFilePath);
jsonFullPath = fullfile(currentFilePath, 'targetpreferences.json');
prefs = readJSONToStruct(jsonFullPath);

%% Add targetUtilities folder to path
addpath("targetUtilities/");

%% Save path across all Polyspace sessions
if savepath
    warning('Error while saving path!');
end
rehash toolboxcache;

%% Create board

%% Create processor and set language implementation

%% Create toolchain and set processor as supported hardware

%% Specify how to start target hardware and associate with board

%% Create communication interface and associate with board

%% Create main function for execution on target and associate with board

%% Create data transfer protocol information and associate with board

%% (Optional) Create timer for profiling and associate with processor

%% Associate processor with board

%% Create a connection to board

%% Register connection to board and toolchain
The next section walks you through the details of target creation. The completed target registration file is provided at the end.

Creating Target Unregistration File

Add the following content to the file packageUnregister.m to unregister the board and toolchain that would be created using the target registration file. When unregistering the board and toolchain, set the argument 'IncludeAssociations' to true so that any other object created as part of the board or toolchain also gets removed.

% Read preferences file and create a preferences structure
currentFilePath = fileparts(mfilename('fullpath'));
addpath(currentFilePath);
jsonFullPath = fullfile(currentFilePath, 'targetpreferences.json');
prefs = readJSONToStruct(jsonFullPath);

% Remove toolchain definition
target.remove('Toolchain', prefs.ToolchainName, 'IncludeAssociations', true);

% Remove board definition
target.remove('TargetConnection', prefs.TargetConnectionName, 'IncludeAssociations', true);

Fill Details of Target Registration

You will now fill in the details of target registration. Each subsection below discusses how to fill a section of the target registration file (including creating necessary utility files), and shows the filled section for the STM32 Nucleo-144 board. The next section shows the complete target registration package, including the filled target registration file and all utility files.

Create Board

Create a target.Board object to represent the board.

%% Create board
board = target.create("Board", "Name", prefs.BoardName);
For more information, see target.Board (Embedded Coder).

Note that the variable prefs comes from the preferences file you created earlier.

Create Processor and Set Language Implementation

Create a target.Processor object to represent the processor. The processor encapsulates information such as name, manufacturer, timer, number of cores, and so on. For more information, see target.Processor (Embedded Coder).

The processor has a property LanguageImplementations, which is a target.LanguageImplementation object encapsulating details such as byte ordering and sizes of data types. Instead of setting the language implementation details explicitly, you can use target.get (Embedded Coder) to automatically populate the language implementation details. For more information, see target.LanguageImplementation (Embedded Coder).

%% Create processor and set language implementation
processor = target.create("Processor", ...
    "Name", "STM32F767ZI", ...
    "Manufacturer", 'ST');
processor.LanguageImplementations = target.get("LanguageImplementation","GNU GCC ARM 32-bit");
You can find all prepopulated language implementation details by entering the following in the MATLAB command window:
availableLanguageImplementations = target.get("LanguageImplementation")

Create Toolchain and Set Processor as Supported Hardware

Create a target.Toolchain object to represent the toolchain for building C/C++ code. You can specify a toolchain in one of the following ways:

  • Using a CMake-style toolchain specification, which describes a compiler, linker, and so on, in CMake language.

  • Explicitly specifying paths to your compiler, linker, and so on. In this case, a makefile will be created for building code.

This example explicitly specifies paths to compilers and other toolchain binaries. However, instead of explicitly specifying every detail of the compiler binaries, it uses a shorthand notation available for GNU®-family toolchains.

For GNU-family toolchains, where the compile command is a variant of gcc such as arm-none-eabi-gcc and accepts gcc options, you can use the following shorthand. Instead of a creating an empty toolchain and specifying all toolchain properties:

toolchain = target.create('Toolchain', ...
     'Name', 'GCC ARM Cortex M | STM32F767ZI')
% Specify full set of properties %
Create a toolchain preconfigured for the GNU-family:
toolchain = target.create('Toolchain', ...
     'Name', 'GCC ARM Cortex M | STM32F767ZI', ... 
     'Family', 'GNU', ...
     'ToolPrefix', 'arm-none-eabi-', ...
     'MakeTool', 'gmake')
% Specify minimal set of properties %
For more information, see target.Toolchain (Embedded Coder).

The software development tools for the STM32 Nucleo-144 board uses a GNU-family toolchain with the arm-none-eabi- toolchain prefix. Use the above shorthand to create a preconfigured toolchain and specify only the following additional information:

  • Path to folder containing compiler and other binaries (using the SytemPaths argument).

  • Additional compiler and linker flags (using the CompilerFlags and LinkerFlags arguments).

Finally, add the previously created processor as a supported hardware for the toolchain.

%% Create toolchain and set processor as supported hardware
compilerLinkerFlags = [
    "-mcpu=cortex-m7", ...
    "-mthumb", ...
    "-mfpu=fpv5-sp-d16", ...
    "-mfloat-abi=hard"
];

linkerFlag = "-T " + prefs.DriverPath + "/STM32CubeIDE/STM32F767ZITX_FLASH.ld";
toolchain = target.create('Toolchain', ...
    'Family', 'GNU', ...
    'Name', prefs.ToolchainName, ...
    'ToolPrefix', 'arm-none-eabi-', ...
    'MakeTool', 'gmake', ...
    'SystemPaths', [prefs.GnuARMPath '/bin'], ...
    'CompilerFlags', [ ...
        compilerLinkerFlags, ...
        "-ffunction-sections", ...
        "-fdata-sections", ...
        "-Wall" ...
    ], ...
    'LinkerFlags', [ ...
        compilerLinkerFlags, ...
        linkerFlag ...
    ] ...
);

toolchain.SupportedHardware = target.create( ...
    'HardwareComponentSupport', ...
    'Component', processor ...
);

Note that the variable prefs comes from the preferences file you created earlier.

Instead of writing the toolchain registration code by hand, you can also generate the code using a Toolchain app. For more information, see Toolchain.

Specify How to Start Target Hardware and Associate With Board

Create a target.Command object to specify a command that executes applications on the STM32 Nucleo-144 board. Then, specify this command as an execution tool associated with the board.

%% Specify how to start target hardware and associate with board

startCommand = target.create('Command');
startCommand.String = prefs.ProgrammerPath;
startCommand.Arguments = {'--connect' ...
    'port=swd'...
    '--erase' ...
    'all' ...
    '--download'...
    '$(EXE)' ...
    '--go'};
 

executionTool = target.create( ...
    "SystemCommandExecutionTool", ...
    "Name", "STLink V2 VCP  - Target Process Execution", ...
    "StartCommand", startCommand ...
);

board.Tools.ExecutionTools = executionTool;
Note that the command uses the environment variable $(EXE) to denote the application executable. If the executable to download and run is named timesTwo.elf, the above target.Command object effectively implements this system command:
STM32_Programmer_CLI.exe  --connect port=swd --erase all --download timesTwo.elf --go
The system command connects to the board using the SWD (Serial Wire Debug) interface, erases all memory, loads the application, and starts execution from the entry point of the application.

Alternate Approach for Defining Execution Tool.  The above code snippet is the simplest way to add start (or stop) commands that load applications on your board. In the more general case, for instance, if you use an emulator or debugger, you might need to call separate commands at the start – one command that starts the emulator and a separate one that starts the actual application. In this case, create a class that inherits from the target.ExecutionTool or target.DebugIOTool class, and implement methods such as startApplication, stopApplication, and so on. For more information, see target.ExecutionTool (Embedded Coder) or target.DebugIOTool (Embedded Coder).

For instance, to implement the same start command as above using the target.ExecutionTool class:

  1. In a new file STM32F767ZI_ExecutionTool.m, create a class STM32F767ZI_ExecutionTool that inherits from target.ExecutionTool and implement the startApplication method of the class. In the implementation below, the method calls the same STM32_Programmer_CLI.exe system command as earlier and in addition, displays some messages.

    classdef STM32F767ZI_ExecutionTool < target.ExecutionTool
        methods
            % Start executing the application on the target.
            function errFlag = startApplication(this)
                
                binFullPath = char(this.Application);
    
                currentFilePath = fileparts(mfilename('fullpath'));
                parentPath = currentFilePath(1:end-length('targetUtilities')-1);
                jsonFullPath = fullfile(parentPath, 'targetpreferences.json');
                prefs = readJSONToStruct(jsonFullPath);
                programmerPath = prefs.ProgrammerPath;
                disp(programmerPath);
    
                [~, name, ext] = fileparts(binFullPath);
                disp(['Programming ' name ext]);
    
                cwd = [ ...
                    '"', char(programmerPath), ...
                    '" --connect port=swd --erase all --download "', ...
                    binFullPath, ...
                    '" --go' ...
                ];
                [status, cmdout] = system(cwd);
    
                if status ~= 0
                    error(cmdout);
                    errFlag = true;
                else
                    disp('Programming successfull !');
                    errFlag = false;
                end
            end
        end
    end
  2. Save the MATLAB file STM32F767ZI_ExecutionTool.m in the targetUtilities folder.

    Your folder structure looks like this:

    STM32Package/
    ├── targetpreferences.json
    ├── readJSONToStruct.m
    ├── packageRegister.m
    ├── packageUnregister.m
    ├── targetUtilities/
    │   ├── STM32F767ZI_ExecutionTool.m
    │   ├── src/
    └── README.md

  3. In your target registration file packageRegister.m, create a target.ExecutionService object, which allows you to use the MATLAB class you implemented to start the application on the board.

    %% Specify how to start target hardware and associate with board
    % Create a MATLAB build dependency on the class derived from target.ExecutionTool class
    buildDeps = target.create("MATLABDependencies", "Classes", "STM32F767ZI_ExecutionTool");
    
    % Create an execution service that uses an API 
    % That is obtained from the MATLAB build dependency
    api = target.get("API", "ExecutionTool");
    executionServiceAPI = target.create("APIImplementation", ...
                "Name", "STM32F767ZI - Execution Service Implementation", ...
                "API", api, ...
                "BuildDependencies", buildDeps);
    executionService = target.create("ExecutionService", ...
                "Name", "STM32F767ZI - Target Process Execution", ...
                "APIImplementation", executionServiceAPI);
    
    % Associate the execution service with the board
    board.Tools.ExecutionTools = executionService;

Create Communication Interface and Associate with Board

Create a target.CommunicationsInterface object to send data to and receive data from the board. The object has two properties:

  • Channel: This property states the name of the standard to be used for communication. In this example, you specify that the communication happens using an RS-232 channel.

  • APIImplementations: This property implements the actual details of communication using the driver files you generated earlier.

    In this example, you specified that the communication with the board happens using USART peripherals and then generated driver code for this communication. If you communicated with your board outside the Polyspace context, for instance, using an STM32CubeIDE project, you would be using the functions from the driver code for communication, for instance, HAL_UART_Transmit() for sending data and HAL_UART_Receive() for receiving data. For targets registered using the target framework API, the same communication is handled using the rtiostream C API. For instance, this API uses the function rtIOStreamSend() for sending data and rtIOStreamRecv() for receiving data. To leverage the generated driver code, you have to create custom implementations of the functions from the rtiostream API that call functions from the generated driver code.

To create and add a custom implementation of the rtiostream API, follow these steps.

  1. Implement these functions of the rtiostream API:

    For the STM32 Nucleo-144 board, create the following header and source file containing implementations of the rtiostream API:

    • customRtiostream.h:

      #ifndef STM32F767ZI
      #define STM32F767ZI
      
      #include <stddef.h>
      
      #define RTIOSTREAM_ERROR (-1)
      #define RTIOSTREAM_NO_ERROR (0)
      
      /* Note: if the functions declared in this file should be compiled into a shared
       * library (e.g. a .dll file on Windows), you must ensure that the functions are
       * externally visible. The procedure to achieve this depends on the compiler and
       * linker you are using. For example, on Windows, you may need to provide an
       * exports definition .def file that lists all of the functions to be
       * exported; see ./rtiostream/rtiostream_pc.def for a suitable .def file.
       */
      
      
      extern int rtIOStreamOpen(
          int    argc,
          void * argv[]
      );
      
      extern int rtIOStreamSend(
          int          streamID,
          const void * src, 
          size_t       size,
          size_t     * sizeSent
          );
      
      extern int rtIOStreamRecv(
          int      streamID,
          void   * dst, 
          size_t   size,
          size_t * sizeRecvd
          );
      
      extern int rtIOStreamClose(
          int streamID
          );
      
      #endif /* #ifndef STM32F767ZI_SERIAL */

    • customRtiostream.c:

      #include "customRtiostream.h"
      #include "STM32F767ZI_board.h"
      #include "stm32f7xx_hal.h"
      #include "stm32f7xx_hal_uart.h"
      
      extern UART_HandleTypeDef huart3;
      
      int rtIOStreamOpen(int argc, void * argv[])
      {
            (void)argc;
            (void)argv;
            return RTIOSTREAM_NO_ERROR;
      }
      
      int rtIOStreamSend(int streamID, const void * src, size_t size,size_t* sizeSent)
      {
            (void)streamID;
            if (HAL_OK == HAL_UART_Transmit(&huart3, src, size, HAL_MAX_DELAY)) {
               *sizeSent = size;
               return RTIOSTREAM_NO_ERROR;
            } else {
               *sizeSent = (size - huart3.TxXferCount);
               return RTIOSTREAM_ERROR;
            }
      }
      
      int rtIOStreamRecv(int streamID, void * dst, size_t size, size_t* sizeRecvd)
      {
            (void)streamID;
             if (HAL_OK == HAL_UART_Receive(&huart3, dst, size, HAL_MAX_DELAY)) {
                  *sizeRecvd = size;
                  return RTIOSTREAM_NO_ERROR;
             } else {
                  *sizeRecvd = (size - huart3.RxXferCount);
                  return RTIOSTREAM_ERROR;
             }
      }
      
      int rtIOStreamClose(int streamID)
      {
            (void)streamID;
            return RTIOSTREAM_NO_ERROR;
      }
      

    Save the header and source file in the targetUtilities/src subfolder. At the end of this step, the target package looks like this:

    STM32Package/
    ├── targetpreferences.json
    ├── readJSONToStruct.m
    ├── packageRegister.m
    ├── packageUnregister.m
    ├── targetUtilities/
    │   ├── STM32F767ZI_ExecutionTool.m  #Optional
    │   ├── src/
    │   │   ├── customRtiostream.h
    │   │   ├── customRtiostream.c
    └── README.md

  2. In your target registration file packageRegister.m, create a target.CommunicationsInterface object that refers to the above custom implementation of the rtiostream C API.

    %% Create communication interface and associate with board
    
    % Create communication interface
    comms = target.create( ...
        "CommunicationInterface", ...
        "Name", "STLink V2 VCP - Communication Interface" ...
    );
    
    comms.Channel = "RS232Channel";
    
    comms.APIImplementations = target.create( ...
        "APIImplementation", ...
        "Name", "STLink V2 VCP - APIImplementation" ...
    );
    
    % Specify that you will be using the rtiostream API for communication
    comms.APIImplementations.API = target.get( ...
        'API', 'rtiostream' ...
    );
    
    % Specify the location of your custom rtiostream implementation as a build dependency.
    % Also, specify the location of the driver includes
    baseSoftDir = [currentFilePath, '/targetUtilities/src'];
    
    comms.APIImplementations.BuildDependencies = target.create( ...
        "BuildDependencies" ...
    );
    
    comms.APIImplementations.BuildDependencies.SourceFiles = [ ...
        baseSoftDir, '/customRtiostream.c' ...
    ];
    
    comms.APIImplementations.BuildDependencies.IncludeFiles = [ ...
        baseSoftDir, '/customRtiostream.h' ...
    ];
    
    comms.APIImplementations.BuildDependencies.IncludePaths = [ ...
        prefs.DriverPath, '/Drivers/STM32F7xx_HAL_Driver/Inc' ...
    ];
    
    % Finally, associate the communication interface with the board
    board.CommunicationInterfaces = comms;

For more information, see target.CommunicationInterface (Embedded Coder).

Create Main Function for Execution on Target and Associate With Board

Create a target.MainFunction object that defines the entry point to your application. When generating driver files earlier, you specified that a main() function must not be generated. The reason was that building using the target framework API itself involves generation of an intermediate main function. In this section of target registration, you specify the ingredients of the generated main using the target.MainFunction. For more information, see target.MainFunction (Embedded Coder).

For the STM32 Nucleo-144 example, your main() function will initialize the board using functions in the generated driver files. You will write a custom initialization function in C that calls low-level initialization functions in the generated driver files, then refer to this custom initialization function in your target registration file.

  1. Create the following header and source file that declares and defines a function initSTM32F767ZI(), which calls low-level initialization functions. In the header, make sure to include all the appropriate driver files:

    • STM32F767ZI_board.h:

      #ifndef STM32F767ZI_board
      #define STM32F767ZI_board
      
      #include "main.h"
      #include "dma.h"
      #include "tim.h"
      #include "usart.h"
      #include "gpio.h"
      #include "stm32f7xx_hal.h"
      
      extern void initSTM32F767ZI(void);
      #endif /* STM32F746G_DISCOVERY_BOARD */
      

    • STM32F767ZI_board.c:

      #include "STM32F767ZI_board.h"
      
      extern UART_HandleTypeDef huart3; //defined in usart.c
      extern TIM_HandleTypeDef htim1; //defined in tim.c
      
      
      void initSTM32F767ZI(void) //Wraper function called in Initialization phase
      {
          MX_GPIO_Init(); //GPIO Initialization defined in gpio.c
          MX_DMA_Init(); //DMA Initialization defined in dma.c
      
          // Configure the Timer
          MX_TIM1_Init(); //TIM Initialization defined in tim.c
          HAL_TIM_Base_MspInit(&htim1); //Peripherical initialization defined in tim.c
          HAL_TIM_Base_Start(&htim1); //Start timer
      
          // Configure the required peripherals
          MX_USART3_UART_Init(); //USART3 Initialization defined in usart.c
          HAL_UART_MspInit(&huart3); //Peripherical initialization defined in usart.c
      }

    Save the header and source file in the targetUtilities/src subfolder. At the end of this step, the target package looks like this:

    STM32Package/
    ├── targetpreferences.json
    ├── readJSONToStruct.m
    ├── packageRegister.m
    ├── packageUnregister.m
    ├── targetUtilities/
    │   ├── STM32F767ZI_ExecutionTool.m   #Optional
    │   ├── src/
    │   │   ├── customRtiostream.h
    │   │   ├── customRtiostream.c
    │   │   ├── STM32F767ZI_board.h
    │   │   ├── STM32F767ZI_board.c
    └── README.md

  2. In your target registration package packageRegister.m, create a target.MainFunction object. In the InitializationCode property of this object, call the initialization function defined in the previous step.

    %% Create main function for execution on target and associate with board
    mainFunction = target.create("MainFunction", "Name", "Test Main");
    
    % Add the include file 'STM32F767ZI_board.h' so that it gets included in the generated main
    mainFunction.IncludeFiles = {[baseSoftDir,'/STM32F767ZI_board.h']};
    
    % Add code that directly goes into the body of the generated main
    % In this example, you can just add a call to the previously defined initialization function
    mainFunction.InitializationCode = sprintf('initSTM32F767ZI();\n');
    
    % Add paths to all generated driver files 
    % The driver files contain definitions of low-level functions to be used during build
    % Also add necessary include paths and defines
    driverPath = prefs.DriverPath;
    mainFunction.BuildDependencies.SourceFiles = { ...
        [driverPath, '/STM32CubeIDE/Application/User/Startup/startup_stm32f767zitx.s'], ...
        [baseSoftDir, '/STM32F767ZI_board.c'] ...
        [driverPath, '/Core/Src/gpio.c']...
        [driverPath, '/Core/Src/usart.c']...
        [driverPath, '/Core/Src/dma.c']...
        [driverPath, '/Core/Src/tim.c']...
        [driverPath, '/Core/Src/main.c']...
        [driverPath, '/STM32CubeIDE/Application/User/Core/syscalls.c'] ...
        [driverPath, '/STM32CubeIDE/Application/User/Core/sysmem.c'] ...
        [driverPath, '/Core/Src/system_stm32f7xx.c'] ...
        [driverPath, '/Core/Src/stm32f7xx_it.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_cortex.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_dma.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_exti.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_flash.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_flash_ex.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_gpio.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_pwr.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_rcc.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_rcc_ex.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_tim.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_tim_ex.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_uart.c'] ...
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_pwr_ex.c']
        };
    mainFunction.BuildDependencies.IncludePaths = ...
        {
        [driverPath, '/Drivers/STM32F7xx_HAL_Driver/Inc'] ...
        [driverPath, '/Drivers/CMSIS/Device/ST/STM32F7xx/Include'], ...
        [driverPath, '/Drivers/CMSIS/Include'],...
        [driverPath, '/Core/Inc'],...
        baseSoftDir
        };
    
    mainFunction.BuildDependencies.Defines = ...
        {
        'STM32F767xx', 'USE_HAL_DRIVER', 'USE_FULL_LL_DRIVER'
        }; 
    
    % Associate main function with board
    board.MainFunctions = mainFunction;

Create Data Transfer Protocol Information and Associate With Board

Create a target.PILProtocol object to specify additional information around data transfer such as buffer sizes and timeouts for sending or receiving data.

%% Create data transfer protocol information and associate with board
pilProtocol = target.create("PILProtocol");
pilProtocol.Name = "STM32F767ZI - PIL Protocol";
pilProtocol.ReceiveTimeout = 60;
pilProtocol.OpenTimeout = 60;
pilProtocol.SendBufferSize = 1024;
pilProtocol.ReceiveBufferSize = 1024;
board.CommunicationProtocolStacks = pilProtocol;

For more information, see target.PILProtocol (Embedded Coder).

(Optional) Create Timer for Profiling and Associate With Processor

If you want to perform execution time calculation for your target, create a target.Timer object to specify the timer function to be used for calculation.

For the STM32 Nucleo-144 example, follow these steps to specify a timer function:

  1. In the previously created header file STM32F767ZI_board.h, add the following function declaration:

    extern uint32_t getTimerCounter(void);

  2. Add the definition of the above function to the previously created source file STM32F767ZI_board.c:

    #include "STM32F767ZI_board.h"
    uint32_t getTimerCounter(void) {
         return __HAL_TIM_GET_COUNTER(&htim1);
    }
    The function uses the builtin macro __HAL_TIM_GET_COUNTER to get the current counter value of a timer.

  3. In your target registration file packageRegister.m, create a target.Timer object that refers to the timer function defined in the previous step.

    %% Create timer for profiling and associate with processor
    timer = target.create("Timer", "Name", "STM32 F767ZI Timer", ...
        "FunctionName","getTimerCounter", ...
        "FunctionReturnType","uint32", ...
        "IncludeFiles",{'STM32F767ZI_board.h'}, ...
        "FunctionLanguage","C");
    
    target.add(timer, "UserInstall", true);
    processor.Timers = timer;

For more information, see target.Timer (Embedded Coder).

Create Processor With Board

Connect the previously created target.Processor object to the previously created target.Board object:

%% Associate processor with board
board.Processors = processor;

Create Connection to Board

Create a target.TargetConnection object to create a connection to the board from your host computer.

%% Create a connection to board
connection = target.create("TargetConnection");
connection.Name = prefs.TargetConnectionName;

% Specify the communication channel used earlier when defining the communication interface
connection.CommunicationChannel = target.create("RS232Channel");
connection.CommunicationChannel.Name = "STLink V2 VCP - RS232Channel";

% Specify the required Baud Rate and COM Port
connection.CommunicationChannel.BaudRate = 115200;
connection.ConnectionProperties = target.create("Port","PortNumber",prefs.COMPORT);

% Attach the board to the connection object
connection.Target = board;

For more information, see target.TargetConnection (Embedded Coder).

Register Connection to Board and Toolchain

Add the previously created board connection and toolchain to the list of registered targets.

%% Register connection to board and toolchain
% Add the connection to the database of registered targets
target.add(connection, "UserInstall", true);

% Add the toolchain to the database of registered toolchains
target.add(toolchain, "UserInstall", true);

For more information, see target.add (Embedded Coder).

Summary

Your final target registration package consists of the following files.

STM32Package/
├── targetpreferences.json
├── readJSONToStruct.m
├── packageRegister.m
├── packageUnregister.m
├── targetUtilities/
│   ├── STM32F767ZI_ExecutionTool.m   #Optional
│   ├── src/
│   │   ├── customRtiostream.h
│   │   ├── customRtiostream.c
│   │   ├── STM32F767ZI_board.h
│   │   ├── STM32F767ZI_board.c
└── README.md

The completed package registration file packageRegister.m contains the following code. You can use this package registration file to register the board for use with Polyspace Test. Once registered, you can:

 Contents of Package Registration File

See Also

Topics


1 The files referred to in this example are the copyrighted property of STMicroelectronics®. Their names are used here for informational purposes only.