STM32 and Arduino, working with a custom board

Arduino is supporting STM32 platform and after following the installation steps, you can easily work with the st-microelectronics development kit.

In the real life you need to create a specific setup once your prototype is transformed into a custom board. This setup redefines the pin mapping, the target MCU and needs to refine the firmware transfer method as you will use and external STLINK programmer.

In this post we are going to see the different step for doing this.

Let’s create a specific board & program to start

We are going to make a minimal board with a STM32L011D3P6 with a TSSOP14 packaging. This board have a LED connected on PIN8 (PA7). The circuit is the following:

The associated program is basically the blinky one:

void setup() {
   pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
   digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
   delay(100); // wait for a second
   digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
   delay(100); // wait for a second
}

Create a custom board definition

You can find a full example in this github repository.

Now we need to create a custom board definition for our circuit. The different board are defined in the following path:

~/Library/Arduino15/packages/STM32/hardware/stm32/1.3.0/variants/

You can create your own board description in the Arduino Workspace to avoid your work to be removed during a future platform update. So you can create the following directory:

mkdir -p ~/Documents/Arduino/hardware/stm32/1.3.0

In this directory you can copy the platform file from the source:

cp ~/Library/Arduino15/packages/STM32/hardware/stm32/1.3.0/platform.txt .

In this file you can change the name of the platform and leave the rest unchanged

# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification

name=STM32 Custom Boards
version=1.0.0

Now you can create a boards.txt file (or copy the original one and clean it). The part in red are the one you need to change as you want. This is an example for the L011. Memory size are obtained from datasheet. The variant takes its name from the variant we are going to create in the next step.

menu.pnum=Board part number

menu.xserial=Serial interface
menu.usb=USB interface

menu.opt=Optimize
menu.upload_method=Upload method
menu.flash=Flash Memory Size

################################################################################
# Custom boards

Demo_L011.name=Custom-Board

Demo_L011.build.vid=0x0483
Demo_L011.build.pid=0x5711
Demo_L011.vid.0=0x0483
Demo_L011.pid.0=0x5711

Demo_L011.build.core=arduino
Demo_L011.build.board=Demo_L011
Demo_L011.build.extra_flags=-D{build.product_line} {build.enable_usb} {build.xSerial}

# CUSTOM_DEMO board
# Support: Serial2 (USART2 on PA15, PA2)
Demo_L011.menu.pnum.CUSTOM_DEMO=Custom Board1
Demo_L011.menu.pnum.CUSTOM_DEMO.node=NODE_L031K6
Demo_L011.menu.pnum.CUSTOM_DEMO.upload.maximum_size=16384
Demo_L011.menu.pnum.CUSTOM_DEMO.upload.maximum_data_size=2048
Demo_L011.menu.pnum.CUSTOM_DEMO.build.mcu=cortex-m0plus
Demo_L011.menu.pnum.CUSTOM_DEMO.build.board=CUSTOM_DEMO
Demo_L011.menu.pnum.CUSTOM_DEMO.build.series=STM32L0xx
Demo_L011.menu.pnum.CUSTOM_DEMO.build.product_line=STM32L011xx
Demo_L011.menu.pnum.CUSTOM_DEMO.build.variant=CUSTOM_DEMO
Demo_L011.menu.pnum.CUSTOM_DEMO.build.cmsis_lib_gcc=arm_cortexM0l_math

# Upload menu
Demo_L011.menu.upload_method.STLink=STLink
Demo_L011.menu.upload_method.STLink.upload.protocol=STLink
Demo_L011.menu.upload_method.STLink.upload.tool=stlink_upload

Demo_L011.menu.xserial.generic=Enabled with generic Serial
Demo_L011.menu.xserial.none=Enabled without generic Serial
Demo_L011.menu.xserial.none.build.xSerial=-DHAL_UART_MODULE_ENABLED -DHWSERIAL_NONE
Demo_L011.menu.xserial.disabled=Disabled (No Serial)
Demo_L011.menu.xserial.disabled.build.xSerial=

Demo_L011.menu.usb.none=None

# Optimizations
Demo_L011.menu.opt.osstd=Smallest (-Os default)
Demo_L011.menu.opt.osstd.build.flags.optimize=-Os
Demo_L011.menu.opt.osstd.build.flags.ldspecs=
Demo_L011.menu.opt.oslto=Smallest (-Os) with LTO
Demo_L011.menu.opt.oslto.build.flags.optimize=-Os -flto
Demo_L011.menu.opt.oslto.build.flags.ldspecs=-flto
Demo_L011.menu.opt.o1std=Fast (-O1)
Demo_L011.menu.opt.o1std.build.flags.optimize=-O1
Demo_L011.menu.opt.o1std.build.flags.ldspecs=
Demo_L011.menu.opt.o1lto=Fast (-O1) with LTO
Demo_L011.menu.opt.o1lto.build.flags.optimize=-O1 -flto
Demo_L011.menu.opt.o1lto.build.flags.ldspecs=-flto
Demo_L011.menu.opt.o2std=Faster (-O2)
Demo_L011.menu.opt.o2std.build.flags.optimize=-O2
Demo_L011.menu.opt.o2std.build.flags.ldspecs=
Demo_L011.menu.opt.o2lto=Faster (-O2) with LTO
Demo_L011.menu.opt.o2lto.build.flags.optimize=-O2 -flto
Demo_L011.menu.opt.o2lto.build.flags.ldspecs=-flto
Demo_L011.menu.opt.o3std=Fastest (-O3)
Demo_L011.menu.opt.o3std.build.flags.optimize=-O3
Demo_L011.menu.opt.o3std.build.flags.ldspecs=
Demo_L011.menu.opt.o3lto=Fastest (-O3) with LTO
Demo_L011.menu.opt.o3lto.build.flags.optimize=-O3 -flto
Demo_L011.menu.opt.o3lto.build.flags.ldspecs=-flto
Demo_L011.menu.opt.ogstd=Debug (-g)
Demo_L011.menu.opt.ogstd.build.flags.optimize=-g -Og
Demo_L011.menu.opt.ogstd.build.flags.ldspecs=

Now we need to create a variant. The variants contains all the MCU configuration for pin, memory, clocks, available modules… All the files needs to be reviewed and modified. You need to have the device datasheet and the familly datasheet to correctly setting it.

ldscript.ld contains the memory mapping

/* Highest address of the user mode stack */
_estack = 0x20000800; /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x100; /* required amount of heap */
_Min_Stack_Size = 0x100; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 2K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 16K
}

PeripheralsPins.c defines all the pin mapping and alternate configurations. Following you have an example of how it looks like. See github repo for complete details.

#ifdef HAL_ADC_MODULE_ENABLED
const PinMap PinMap_ADC[] = {
  {PA_0, ADC1, STM_PIN_DATA_EXT(STM_MODE_ANALOG, GPIO_NOPULL, 0, 0, 0)}, // ADC_IN0
  {PA_1, ADC1, STM_PIN_DATA_EXT(STM_MODE_ANALOG, GPIO_NOPULL, 0, 1, 0)}, // ADC_IN1
  {PA_4, ADC1, STM_PIN_DATA_EXT(STM_MODE_ANALOG, GPIO_NOPULL, 0, 4, 0)}, // ADC_IN4
  {PA_7, ADC1, STM_PIN_DATA_EXT(STM_MODE_ANALOG, GPIO_NOPULL, 0, 7, 0)}, // ADC_IN7
  {NC, NP, 0}
};
#endif

stm32l0xx_hal_conf.h defines the different module activated with this chip. See github for details.

#define HAL_MODULE_ENABLED 
#define HAL_ADC_MODULE_ENABLED 
/*#define HAL_CRYP_MODULE_ENABLED */
/*#define HAL_COMP_MODULE_ENABLED */
/*#define HAL_CRC_MODULE_ENABLED */
/*#define HAL_CRYP_MODULE_ENABLED */
/*#define HAL_DAC_MODULE_ENABLED */
/*#define HAL_FIREWALL_MODULE_ENABLED */
/*#define HAL_I2S_MODULE_ENABLED */
/*#define HAL_IWDG_MODULE_ENABLED */
/*#define HAL_LCD_MODULE_ENABLED */
/*#define HAL_LPTIM_MODULE_ENABLED */
/*#define HAL_RNG_MODULE_ENABLED */
#define HAL_RTC_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
...

variant.cpp and variant.h list the pin mapping and default pin association for Serial, Wire… You also should take a look to the github repo for details.

...
extern const PinName digitalPin[];

enum {
PC10, //D0
PC14, //D1
PA4, //D2
PA9, //D3
PA10, //D4
PA7, //D5 - LED
PA0, //D6/A0
PA1, //D7/A1
PA13, //D8 - STLink Tx
PA14, //D9 - STLink Rx
PEND
};

// This must be a literal with the same value as PEND
#define NUM_DIGITAL_PINS 10
// This must be a literal with a value less than or equal to to MAX_ANALOG_INPUTS
#define NUM_ANALOG_INPUTS 2
#define NUM_ANALOG_FIRST 6

// On-board LED pin number
#define LED_BUILTIN 5
#define LED_GREEN LED_BUILTIN
...

Then we need to create some symbolic link from the original stm32 hardware files

cd ~/Documents/Arduino/hardware/stm32/1.3.0
ln -s ~/Library/Arduino15/packages/STM32/hardware/stm32/1.3.0/cores .
ln -s ~/Library/Arduino15/packages/STM32/hardware/stm32/1.3.0/libraries .
ln -s ~/Library/Arduino15/packages/STM32/hardware/stm32/1.3.0/system

Now we can compile our sketch.

Download the sketch

Normally you can download your sketch with st-link. The STM32L011 chips are supported in st-link version 1.0.4 but this version do not yet have MacOsX binaries released.

The first step is to verify if the binaries have been updated and published (so you have nothing to do) otherwise you can compile your own version based on a release > 1.0.4. The compilation procedure is describe in the St-link Github repo in doc/compiling.md

When compiling it the Makefile was not correctly recognized. So I had to do the different operation manually (create a directory, execute cmake then make).

Then you need to copy st-flash and st-info binaries to ~Library/Arduino15/packages/STM32/tools/STM32Tools/1.1.0/tools/macosx/stlink-1.5.0/ then you need to create a lib directory and copy the different libs and shared-libs (see initial directory content). Then you can create a symlink stlink pointing to stlink-1.5.0. Now the programm can be downloaded.

To program your chip with a ST-Linkv2 programmer you need to make the following wiring:

(Note : this ^^ picture have a bug: NRST is not the 2nd one on top-left but the 3rd one ..)

The NRST pin is needed for the first time you flash the device has from the factory it have a bootloader already installed and preventing SWD to be used as a SWD. Once you have flash it one time you do not need the connect the reset pin.

Sketch size conciderations

The main issue with Arduino is the size of the core itself. With STM32 devices with less than 32Kb of Flash and 8Bk of RAM it’s hard to succeed in compiling your sketch making something else than just flashing a LED.

Just to make a comparison:

Sketch Arduino Native code
Making a led blinking 11 700 bytes 3 592 bytes
Making a led blinking low power 24 568 bytes 9008 bytes

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.