Developing Simulink Device Drivers for ARM Cortex

February 12, 2015

Simulink Embedded Coder offers an ARM Cortex-M support toolbox, which includes code optimisation for the MCU and QEMU emulation but lacks any S-Block drivers for the device. The lack of drivers limits the Simulink development to merely number crunching. You can create cevel blocks that execute external C functions but this requires separate source files with a shared header and pre-defined initialisation, leaving the model without full control of the hardware. In this post, I go over the process of creating hardware driver S-Blocks.

CMSIS

ARM have a universal driver set named CMSIS that standardises register names, system start-up function, exceptions and defines. Developing the basic device drivers to use this means that we can transition a model from MCU and even manufacturer by simply changing the board include.

Digital Output S-Block

GPIO Functions

As explained above, a minimum requirement of a board.h is a definition of register locations. We need to create functions that the Simulink S-Block can call to modify these.

The S-Block needs:

1 An init function

/*-------------------------------------------------------------------------------
enable the peripheral clock
*------------------------------------------------------------------------------*/
inline static void pmc_enable_periph_clk_matlab(int ul_id)
{

if (ul_id &lt; 32) {
if ((PMC-&gt;PMC_PCSR0 & (1u &lt;&lt; ul_id)) != (1u &lt;&lt; ul_id)) {
PMC-&gt;PMC_PCER0 = 1 &lt;&lt; ul_id;
}
} else {
ul_id -= 32;
if ((PMC-&gt;PMC_PCSR1 & (1u &lt;&lt; ul_id)) != (1u &lt;&lt; ul_id)) {
PMC-&gt;PMC_PCER1 = 1 &lt;&lt; ul_id;
}
}
}

/*-------------------------------------------------------------------------------
configure pins
*------------------------------------------------------------------------------*/
void pio_config_pin(int pin)  {

int mask = (1 &lt;&lt; pin);

pmc_enable_periph_clk_matlab(ID_PIOA);

/* Set up LED pins. */
PIO-&gt;PIO_OER = mask; // direction output
PIO-&gt;PIO_PUDR = mask; // pull-up disable
PIO-&gt;PIO_OWER = mask; // enable output write
pio_block( mask, 0x0 ); // turn everything off
}


2 An output function (to run each step)

/*-------------------------------------------------------------------------------
pio set pins
*------------------------------------------------------------------------------*/
void pio_pin(int pin, int level)  {

int mask = (1 &lt;&lt; pin);

PIO-&gt;PIO_SODR = mask & level; // set output
PIO-&gt;PIO_CODR = mask & ~level; // clear output

}


3 A terminate function

void pio_terminate_pin(int pin) {

int mask = (1 &lt;&lt; pin);

pio_block( mask, 0x0 ); // turn everything off

}


Note the types aren’t explicit due to type definition discrepancy when it comes to building the S-Block. The function calls will be so it shouldn’t be a problem. I looked at using pin masks rather than using single pins but the S-Block init cannot accept block inputs – only parameters – so it was a lost cause. The blocks can be built into a masked sub-system within Simulink for the same effect.

Put these in a gpio_control.c file with declarations in gpio_control.h (except the inline pmc_enable.., MATLAB doesn’t need to see this).

Make the S-Block

Now to MATLAB. Put the driver source (gpio_control.c,.h) in a working directory, along with the CMSIS source including the MCU header (we’re using ATMEL, which can be found in Atmel/Atmel Toolchain/ARM GCC/4.8.1429/CMSIS_Atmel of an Atmel Studio install).

Configure

1. Create a S-Block template definition: def = legacy_code('initialize')
2. Source files:
def.SourceFiles = {'gpio_control.c'}
def.HeaderFiles = {'gpio_control.h'}
3. Functions:
def.OutputFcnSpec = 'pio_pin(uint8 p1, uint32 u1)'
def.StartFcnSpec = 'pio_config_pin(uint8 p1)'
def.TerminateFcnSpec =<br /> 'pio_terminate_pin(uint8 p1)'
4. Includes: def.IncPaths = {'CMSIS_Atmel/Device/ATMEL/sam3s/include/','CMSIS_Atmel/','CMSIS_Atmel/CMSIS/Include/','CMSIS_Atmel/Device/ATMEL/'}
5. A name: def.SFunctionName = 'Cortex_Pin_Digital_Output'

You may notice the include path is specific to the SAM3S directory. MATLAB MEX compiler needs to be able to build the source and for this reason we must define a MCU at this point. It can be changed once the block is complete but the source won’t compile now without a board header. Add to the top of the gpio_control.c source:

#define __SAM3S2A__

#include "sam3.h"
#include "gpio_control.h"

#define PIO        PIOA


(or another board.h, ATMEL uses an MCU define system to load the specific board). The include directories will be auto added to the code builder when it comes to code generation, so it’s worth putting the folder in the MATLAB path.

Build

1. Generate mex info: legacy_code('sfcn_cmex_generate', def);
2. Compile: legacy_code('compile', def);
3. Make TLC: legacy_code('sfcn_tlc_generate',specs)
4. You’ll get some generated files with the name of the block (Cortex_Pin_Digital_Output). The source file (.c) is the one that is called during simulations and .tlc is used for generation. Unfortunately, the simulation code will also be trying to call the driver functions, which it can’t do and will cause catasphric crashing if it does (I learnt the hard way!). To solve this, open the file edit Cortex_Pin_Digital_Output.c, find mdlStart, mdlOutputs and mdlTerminate. Wrap each call to the driver functions in #ifndef MATLAB_MEX_FILE #endif and save. EG:
static void mdlStart(SimStruct *S)
{
/*
*/
uint8_T *p1 = (uint8_T *) ssGetRunTimeParamInfo(S, 0)-&gt;data;

/*
* Call the legacy code function
*/
#ifndef MATLAB_MEX_FILE
pio_config( *p1);
#endif
}

1. Recompile: legacy_code('compile', def);
2. Make RTW: legacy_code('rtwmakecfg_generate',def)
3. Make the block: legacy_code('slblock_generate',def)

All being well, a model window with the block will pop up. If you have problems at the compile stage, the error is fairly verbose; it’s normally due to a missing include so check folder structure. On Windows spaces in directory names will cause problems so you might be better off putting the CMSIS directory at root.

At this point you can remove the MCU specific define and include from the driver source file, making the S-Block processor independent. We’ll set the MCU in the ‘Custom Code > Header file’ section of a model instead.

Run Model

Connect a pulse generator to the block input. You can set the pin to change state by double clicking the new block and changing the parameter. Simulate the model, it should run as normal (connect a scope between the generator and driver block to check).

Generate Code

1. Open ‘Model Configuration Parameters’. In ‘Code Generation’ set the ‘System target file’ to ‘ert.tlc’. Set the ‘Target hardware’ to ‘ARM Cortex-M3 (QEMU)’ (you’ll need to download the toolbox linked at the top). Even though we’re not emulating, this will generate a usable example main file. At the bottom, check the checkbox ‘Generate code only’ (if you build it will build for QEMU and get conflicting defines with our MCU).
2. Within the ‘Interface’ section, set the ‘Code replacement library’ to ‘GCC ARM Cortex-M3’ (not required but makes things neater).
4. Close the settings window and click ‘Deploy to Hardware’! Code should be gerenated to modename_ert_rtw/.

Optional Steps

You’ve got code now that seamlessly integrates with the Cortex peripherals. Making additional drivers would follow the same process (ADC, PWM, DAC, etc.). The key is basic functions that set the CMSIS defined registers.

MATLAB new_system('Cortex_Drivers','Library')
Cortex_Drivers


Drag the driver block to the new library, then follow these steps to Add Library to Library Browser. NOTE You’ll need to put the source files for the driver functions with any model you make or in the MATLAB path.

Build

To build this, the most friendly open is probably to copy the source into an blank project in an IDE such as Atmel Studio. For fully streamlined Simulink build I’ve developed two options.

makefile

Using the Atmel Studio example Makefile and config.mk, one can quick throw together a Makefile that will build any Embedded Coder generated code – see my post on this.

The main changes to make are the includes and source:

• Source: I wildcard contents of ‘modelname_ert_rtw’ folder, followed by the block source and ‘system_mcu.c’,’startup_mcu.c’.
• Include: ‘modelname_ert_rtw’, block source folder and CMSIS includes used by block.

You can then invoke make using the external command option of MATLAB, generating a binary that can be flashed via JTAG.

rtwbuild

The Cortex-M toolbox includes support for the arm-gcc compiler. I did get the program compiling using this. You must change the ‘Target hardware’ to ‘None’, then in ‘Build configuration’ specify your own flags for the source, includes and linker.

The key step is changing the ‘Templates > Custom template > File customization template’ to ‘codertarget_file_process.tlc’ – it’s the file that generates the template Makefile. For successful building, you’ll need to tweak this.

In the long term I’d like to create a specific ‘Target hardware’ that would configure all this and provide options for MCU name etc.