Designing a custom STM32MP1 board for embedded Linux using Yocto and OpenSTLinux

Custom Board Design

First lets discuss the hardware. There are vendor boards available:

  1. Evaluation boards are expensive due to the large number of external connectors and integrated circuitry (including FPGAs and HDMI decoders!). These are really meant for sales demos. Any development work done on these would be wasted time since your own custom design would use a different hardware layout and therefore require a totally different device tree.

  2. Development boards are cheaper but often cater to the current craze for a raspberry pi form factor. The STM32MP1 chips cater to a different market than the broadcom MPUs. As a result of insisting on using a raspberry pi header, the dev board do not break out all the pins of the MPU.

So lets remedy this situation by designing a development board for the STM32MP1 Application Microprocessor with all MPU pins routed through to 2.54mm headers. No exotic proprietary connectors and only essential external circuitry included. To minimize issues, the ST DK2 dev board was used as a reference design.

The schematics can be found below:

Overview Power Supply
STM32MP1 Power STM32MP1 DDR
STM32MP1 GPIO STM32MP1 Debug

Power Supply Design

There are two options with power supply design: An all-in-one PMIC chip or a discrete power supply. I evaluate and discuss the pros and cons below.

Spoilers: I went with the discrete design.

PMIC: Power Management ICs

Two PMIC chips caught my eye:

  • STMicroElectronics STPMIC1
  • NXP PF3000 Power Management ICs.

I decided to design a development board for evaluating two power management integrated circuits (PMIC) chips. I managed to squeeze both into a single board, thereby cutting costs 😉

The two PMIC’s are organised into two independantly powered sub-systems.

Each sub-system has a number of external power/signal connectors, along with a list of the jumpers used to configure how those connectors are routed to/from the PMIC chip. The PMICs are configured directly by modifying registers using either a MCU/MPU via the I2C connectors.

Each output has an LED indicator and testpoint. Note that the LED will only light if the PMIC output voltage is set above 2V.

Circuit design notes

The schematic has a separate sheet for both sub-systems. There is one for each chip:

  • DEV – The development board sub-system including power/config connectors.
  • MAIN – The individual PMIC including the required passive components for bypassing and SMPS/regulator operation. Designed to be reuseable blocks that can easily be incorproated into an existing design.
STPMIC1 DEV STPMIC1 MAIN
PF3000 DEV PF3000 MAIN
PCB layout notes

This is a four layer PCB design. The layer stackup is:

  1. Signal
  2. Ground
  3. Small signal and power
  4. Small signal and ground

Note the ground planes in layer 2 and 4 are connected.

The docs directory contains:

  • RevA gerber build files.
  • Bill of materials
  • CPL pick and place files.
  • Related vendor application notes, datasheets and reference designs.

Custom symbols and footprints used in this project can be found in the KicadLibrary repository.

PMIC #1: NXP PF300 Board connector definitions
Connector Description Comment
J15 Wall adaptor power input 3.7V – 5.5V
DC Barrel 2.1mm
J12 Battery power input 3.1V – 4.5V
2.54mm Header
J20 I2C debug 1: Ground
2: SCL
3: SDA
J23 Digital Interface
(see datasheet)
1: Ground
2: STANDBY
3: RESETBMCU
4: SD_VSEL
5: INTB
6: PWRON
J21 PF3000 Power Output Ground:
2,4,6,8,10,12,14,16,18,20,22,24,26

1: SW1B Buck
3: SW1A Buck
5: SWBST Boost
7: VSNVS LDO
9: VCCSD LDO
11: V33 LDO
13: SW3 Buck
15: VREFDDR LDO
17: LDO4
19: LDO3
21: SW2 Buck
23: LDO2
25: LDO1

PMIC #1: NXP PF300 Board jumper and switch definitions

Warning: function of jumpers can be inverted using the registers. See datasheet for details.

Jumper/Switch Description Setting Comment
JP1 Q2 Pullup ON Force Q2 gate to VDBG rail.
Use if PGOOD LED becomes erratic.
JP2 Q3 Pullup ON Force Q3 to VDBG rail.
Use if FAIL LED becomes erratic.
J13 OTP 3-2 Load OTP values from memory into OTP registers
J13 OTP 2-1 Disable OTP memory and load OTP values from fuses. “Try before buy” mode.
J16 I2C Bus Power (VDDIO) 3-2 Power from VDGB. 3.3V
J16 I2C Bus Power (VDDIO) 2-1 Power from Buck 2 output. 1.8V
J14 Digital Interface Power (V_PU) 3-2 Power from J16 output
J14 Digital Interface Power (V_PU) 2-1 Power from VSNVS output (fixed 3V)
J17 Standby 3-2 Enter standby mode
J17 Standby 2-1 Exit standby mode
J18 VCC_SD output range 3-2 1.8V to 1.85V
J18 VCC_SD output range 2-1 2.85V to 3.3V
J19 Power enable 2-1 IC power output is enabled
SW5 Power enable Momentary SPST.
IC power output is enabled.
J22 VPWR override 3-2 VPWR enable.
Set when J15 DC connector is powered
J22 VPWR override 2-1 VPWR disable.
Set when J12 battery power connector is powered.
Warning: Shorts J15 to ground!
PMIC #2: STPMIC1 Board connector definitions
Connector Description Comment
J2 External VIO 0.5V to 4.2V.
I/O voltage (for all digital signals except WAKEUP and PONKEY)
J3 External SWIN 0.5V – 7V.
External power source for VSWIN pin.
J4 Wall adaptor power input 0.5V – 7V.
DC Barrel 2.1mm
J1 Battery power input 0.5V – 7V.
2.54mm Header
J10 I2C debug 1: Ground
2: SCL
3: SDA
J5 Digital Interface
(see datasheet)
1: Ground
2: PONKEY (IN)
3: WAKEUP (IN)
4: RST (BOTH)
5: PWRCTL (IN)
6: INT (OUT)
J21 STPMIC1 Power Output Ground:
2,4,6,8,10,12,14,16,18,20,22,24,26

1: Buck2
3: Buck1
5: LDO4
7: VSWOUT LDO
9: VOTG LDO
11: Boost
13: Buck3
15: Buck4
17: LDO1
19: LDO6
21: LDO5
23: LDO2
25: VREFDDR LDO
27: LDO3

PMIC #2: STPMIC1 Board jumper and switch definitions

Warning: function of jumpers can be inverted using the registers. See datasheet for details.

Jumper/Switch Description Setting Comment
J6 VIO source select 3-2 Power source from Buck3 output
J6 VIO source select 2-1 Power source from VIO Ext (J2)
J7 VSWIN source select 3-2 Power source from BOOST output
J7 VSWIN source select 2-1 Power source from SWIN Ext (J3)
J8 LDO3 source select 1-2 Power source from VBAT (J4 or J1)
J8 LDO3 source select 3-4 Power source from BUCK2 output
J8 LDO3 source select 5-6 Power source from BUCK2 output
J9 PWRCTL Pullup/Pulldown 3-2 Pullup from VIO (J6)
J9 PWRCTL Pullup/Pulldown 2-1 Pulldown to ground
SW1 Wakeup enable Momentary SPST to VBAT
SW2 IC Reset Momentary SPST to ground
SW3 PONKEY User power on key

Writing a test driver for the STPMIC1

Driver can be found in this repository.

Exampe usage:

#include <stpmic1.h>

// Redirect stdout to ITM/SWO
int _write(int file, char *ptr, int len)
{
    for (int i=0; i<len; i++)
    {
        ITM_SendChar((*ptr++));
    }
    return len;
}

int main(void)
{

  HAL_Init();
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();

  // connect using this I2C interface
  stpmic1_i2c_connect(&hi2c1);

  /*
   * Get status register info
   */
  stpmic1_main_ctrl_sr();
  stpmic1_get_turn_on_sr();
  stpmic1_get_turn_off_sr();
  stpmic1_get_ocp_ldos_sr();
  stpmic1_get_ocp_buck_bsw_sr();
  stpmic1_get_restart_sr();


  /*
   * Boost regulator and load switches
   */

  // Enable the VBOOST regulator (5.2V fixed output)
  stpmic1_set_register_value(STPMIC_BST_SW_CR, (STPMIC_BST_SW_BST_ON_Mask));

  // Enable the PWR_USB (OTG) power switch - internally connected to boost regulator.
  stpmic1_set_register_value(STPMIC_BST_SW_CR, (STPMIC_BST_SW_BST_ON_Mask) | (STPMIC_BST_SW_VBUSOTG_ON_Mask));

  // Enable the PWR_SW power switch - J7 connected to SWIN (requires ext power source on J3).
  stpmic1_set_register_value(STPMIC_BST_SW_CR, STPMIC_BST_SW_SWOUT_ON_Mask);

  // Enable the PWR_SW power switch - J7 connected to VBOOST.
  stpmic1_set_register_value(STPMIC_BST_SW_CR, (STPMIC_BST_SW_BST_ON_Mask) | (STPMIC_BST_SW_SWOUT_ON_Mask));

  // Enable the PWR_USB (OTG) power switch - internally connected to boost regulator.
  // AND
  // Enable the PWR_SW power switch - J7 connected to SWIN (requires ext power source on J3).
  stpmic1_set_register_value(STPMIC_BST_SW_CR, (STPMIC_BST_SW_VBUSOTG_ON_Mask) | (STPMIC_BST_SW_BST_ON_Mask) | (STPMIC_BST_SW_SWOUT_ON_Mask));

  /*
   * Buck regulator control
   */

  // Enable BUCK1 full 1.5V output
  stpmic1_set_register_value(STPMIC_BUCK1_MAIN_CR, (STPMIC_BUCK1_MAIN_ENA_Mask | STPMIC_BUCK1_PRESET_63_Mask));

  // Enable BUCK2 full 1.5V output
  stpmic1_set_register_value(STPMIC_BUCK2_MAIN_CR, (STPMIC_BUCK2_MAIN_ENA_Mask | STPMIC_BUCK2_PRESET_63_Mask));

  // Enable BUCK3 full 3.4V output
  stpmic1_set_register_value(STPMIC_BUCK3_MAIN_CR, (STPMIC_BUCK3_MAIN_ENA_Mask | STPMIC_BUCK3_PRESET_63_Mask));

  // Enable BUCK4 full 3.9V output
  stpmic1_set_register_value(STPMIC_BUCK4_MAIN_CR, (STPMIC_BUCK4_MAIN_ENA_Mask | STPMIC_BUCK4_PRESET_63_Mask));

  /*
   * LDO Control
   */

  // LDO1 enable and vout max
  stpmic1_set_register_value(STPMIC_LDO1_MAIN_CR, STPMIC_LDO1_MAIN_ENA_Mask | STPMIC_LDO1_PRESET_31_Mask);

  // LDO2 enable and vout max
  stpmic1_set_register_value(STPMIC_LDO2_MAIN_CR, STPMIC_LDO2_MAIN_ENA_Mask | STPMIC_LDO2_PRESET_31_Mask);

  // LDO3 enable and vout max
  stpmic1_set_register_value(STPMIC_LDO3_MAIN_CR, STPMIC_LDO3_MAIN_ENA_Mask | STPMIC_LDO3_PRESET_30_Mask);

  // LDO5 enable and vout max
  stpmic1_set_register_value(STPMIC_LDO5_MAIN_CR, STPMIC_LDO5_MAIN_ENA_Mask | STPMIC_LDO5_PRESET_31_Mask);

  // LDO6 enable and vout max
  stpmic1_set_register_value(STPMIC_LDO6_MAIN_CR, STPMIC_LDO6_MAIN_ENA_Mask | STPMIC_LDO6_PRESET_31_Mask);

  /*
   * VREFDDR
   */

  // Enable VREFDDR output
  stpmic1_set_register_value(STPMIC_REFDDR_MAIN_CR, STPMIC_REFDDR_MAIN_ENA_Mask);


  while(1)
  {
    // do something fun
  }
}

Discrete Power Supply

The power supply requirements for microprocessors are always complicated. I decided to use a discrete power supply scheme using a single LDO for the main 3.3V rail, a buck regulator for the DDR 1.35V rail and a load switch to detect the USB 1.8V rail. The latter comes from an internal regulator ands powers the VDD3V3_USBFS and VDD3V3_USBHS. This needs to be enabled only after the 1.8V rail comes up, so this needs to be enabled using a MOSFET. However, you need to pick a transistor that will fully switch on for 1.8V Vgs. After having some issues I played it safe with a load switch: the SIP3250, which turns on as low as 1.2V…according to the datasheet

The characteristics seem to back this up until you get to the Fig.10. Perhaps not quite as good as \small1.2V_{gs} but even at \small1.5V_{gs}, this is more than good enough for these requirements.

Whether this is too much hassle when you can use a single PMIC is debatable. The sheer number of passive components needed to support a PMIC is eye-watering.

The table below lists the power supply scheme on this board:

Power Supply Rail MPU Pin Name MPU Pin # Comment
3.3v VDD
VBAT
VDD_PLL
VDD_PLL2
VDD_ANA
VDD_DSI
PDR_ON_CORE
PDR_ON
K6
H3
J5
G13
L3
A12
N1
N2
Linear regulator ([SE8233X2](https://datasheet.lcsc.com/szlcsc/1912111437_Seaward-Elec-SE8233X2_C437587.pdf))
3.3v VDDA
VREF+
M4
N3
via inductor L3
If JP4 is connected (default)
3.3v VDD3V3_USBFS
VDD3V3_USBHS
W12
W15
via NMOS ([DMG2302U](https://datasheet.octopart.com/DMG2302U-7-Diodes-Inc.-datasheet-103571615.pdf)) when 1.85v rail is detected from MPU
1.35v VDD
VDDQ
VDDCORE
VDDQ_DDR
B2
A1
E7
E15
Buck regulator [TPS54202](http://www.ti.com/lit/ds/symlink/tps54202.pdf)
1.85v VDDA1V8_REG V11 Internal regulator, externally connect to VDDA1V8_DSI (B12)
1.2v VDD1V2_DSI_REG B16 Internal regulator, externally connected to VDD1V2_DSI_PHY (A16)
1.1v VDDA1V1_REG W11 Interal regulator, internally connected.

Note: 3.3v rail is also connected to:

  • SWD debug interface
  • SDIO connectors (microSD)
  • pull-ups for the BOOT pins
  • external power output on the pin headers (pin #26 on J1, J2, J3, J4)

Bill of materials

DataSheets

Testing the hardware

Before we boot the board with a Linux image. It is helpful to check the MPU is alive and responding. We can use the SEGGER JLink to connect to the cores via the SWD connector (J6).

Connect to the first A7 core (second core is not active by default)

BOOT pins must be set to B0 and B2.

JLinkExe -device STM32MP15XX_A7_0_DUALFLASH -if SWD -speed 4000 -autoconnect 1

SEGGER J-Link Commander V6.98a (Compiled Mar  5 2021 17:04:44)
DLL version V6.98a, compiled Mar  5 2021 17:04:29

Connecting to J-Link via USB...O.K.
Firmware: J-Link V11 compiled Feb  4 2021 12:59:17
Hardware version: V11.00
S/N: 261002652
License(s): FlashBP, GDB
OEM: SEGGER-EDU
VTref=3.341V
Device "STM32MP15XX_A7_0_DUALFLASH" selected.

Connecting to target via SWD
ConfigTargetSettings() start
ConfigTargetSettings() end
InitTarget() start
InitTarget() end
Found SW-DP with ID 0x6BA02477
DPIDR: 0x6BA02477
AP map detection skipped. Manually configured AP map found.
AP[0]: AXI-AP (IDR: Not set)
AP[1]: APB-AP (IDR: Not set)
AP[2]: AHB-AP (IDR: Not set)
Using preconfigured AP[1] as APB-AP
AP[1]: APB-AP found
Found Cortex-A7 r0p5
6 code breakpoints, 4 data breakpoints
Debug architecture ARMv7.1
Data endian: little
Main ID register: 0x410FC075
I-Cache L1: 32 KB, 512 Sets, 32 Bytes/Line, 2-Way
D-Cache L1: 32 KB, 128 Sets, 64 Bytes/Line, 4-Way
Unified-Cache L2: 256 KB, 512 Sets, 64 Bytes/Line, 8-Way
System control register:
  Instruction endian: little
  Level-1 instruction cache enabled
  Level-1 data cache enabled
  MMU enabled
  Branch prediction enabled
Memory zones:
  Zone: Default Description: Default access mode
  Zone: APB-AP (AP1) Description: DMA like acc. in AP1 addr. space
  Zone: AHB-AP (AP2) Description: DMA like acc. in AP2 addr. space
Cortex-A7 identified.

Connecting to the M4 core

BOOT pins must be set to B2 only.

JLinkExe -device STM32MP15XX_M4_DUALFLASH -if SWD -speed 4000 -autoconnect 1

SEGGER J-Link Commander V6.98a (Compiled Mar  5 2021 17:04:44)
DLL version V6.98a, compiled Mar  5 2021 17:04:29

Connecting to J-Link via USB...O.K.
Firmware: J-Link V11 compiled Feb  4 2021 12:59:17
Hardware version: V11.00
S/N: 261002652
License(s): FlashBP, GDB
OEM: SEGGER-EDU
VTref=3.342V
Device "STM32MP15XX_M4_DUALFLASH" selected.

Connecting to target via SWD
ConfigTargetSettings() start
ConfigTargetSettings() end
InitTarget() start
InitTarget() end
Found SW-DP with ID 0x6BA02477
DPIDR: 0x6BA02477
AP map detection skipped. Manually configured AP map found.
AP[0]: AXI-AP (IDR: Not set)
AP[1]: APB-AP (IDR: Not set)
AP[2]: AHB-AP (IDR: Not set)
AP[2]: Core found
AP[2]: AHB-AP ROM base: 0xE00FF000
CPUID register: 0x410FC241\. Implementer code: 0x41 (ARM)
Found Cortex-M4 r0p1, Little endian.
FPUnit: 6 code (BP) slots and 2 literal slots
Detection of Coresight components via ROM table search disabled by user
Cortex-M4 identified.

Here is a photo successfully connecting to the A7 core on the assembled board using JLink (via SWD). It lives!

Embedded Linux

Yocto

Setting up OpenSTLinux/Yocto distribution

Please see this article for instructions. Stop after completing the Install OpenSTLinux section.

Adding a custom Yocto layer and machine

An example layer for adding your device tree files can be found here

Clone this layer into the meta-st directory of the OpenSTLinux distribution package.

And run the environment setup script:

~/Projects/Yocto/mp15_2.1.0/distpkg/openstlinux-5.10-dunfell-mp1-21-03-31$ source ./layers/meta-st/scripts/envsetup.sh

Check the layers:

bitbake-layers check-layers

The meta-my-demo-layer is not included yet.
So we still need to add it to the Yocto build:

bitbake-layers add-layer ../layers/meta-st/meta-my-demo-layer/

Check the layers again:

bitbake-layers check-layers

The layer is now added to the bottom of the list. Note that position does not matter, but the priority does.

For instructions on how to create a new layer, see this ST Wiki page.

For instructions on how to create a new machine, see this ST Wiki page.

Now lets look at the layer in more detail.

Directory structure of the custom layer

../layers/meta-st/meta-my-demo-layer/
├── conf
│   ├── eula
│   │   ├── en.SLA0048.txt
│   │   ├── LICENCE.broadcom_bcm43xx
│   │   ├── LICENCE.cypress
│   │   ├── ST_EULA_SLA
│   │   ├── stm32mp1-demo -> ST_EULA_SLA
│   │   └── Vivante_GPU_drivers-End_User_Software_License_Terms.txt
│   ├── layer.conf
│   └── machine
│       └── stm32mp1-demo.conf
├── COPYING.MIT
├── LICENSE.md
├── mx
│   ├── ADD_SYMLINK_TO_STM32CUBEIDE_PROJECT_HERE.txt
│   ├── fdk2_161 -> /home/chris/STM32CubeIDE/workspace_1.6.1/fdk2_161/
│   ├── fdk2_161_nohse -> /home/chris/STM32CubeIDE/workspace_1.6.1/fdk2_161_nohse/
│   ├── mp157dab_custom -> /home/chris/STM32CubeIDE/workspace_1.6.1/mp157dab_custom
│   └── mp157fab_custom -> /home/chris/STM32CubeIDE/workspace_1.6.1/mp157fab_custom
├── README
└── README.md

Notes:
– The layer is defined in the layers/meta-st/meta-my-demo-layer/conf/layer.conf file.
– The EULA stm32mp1-demo symlink file was created according to the ST wiki instructions
– The symlink files under the mx directory point to the various STM32CubeIDE projects that generate device tree files (more on this in a bit).
– The machine stm32mp1-demo.conf file was copied from the meta-st-stm32mp-addons layer. We modify this to include the device tree files generated by our STM32CubeIDE project.

STM32CubeIDE

Modifying the OpenSTLinux device tree in STM32CubeIDE

The STM32CubeIDE can be used to generate device tree source file (.dts) for your embedded linux image. These describe the hardware for the board on which linux will run. This is compiled into a device tree binary file (.dtb) which is included on the image and loaded by u-boot along with the Linux kernel zImage.

For those familiar with STM32 CortexM development, STM32CubeIDE is the same pinmux code generation tool. It generates a device tree with “comment sections” that need to be filled out by the user. The simplest option is to create a STM32MP1-DK (development kit) project and copy the required comment sections over to your own dts file. See the forum page for workshop slides that go into more detail about these comment sections.

I have also written this article on the process.

For now we will just clone this project.

Open the project in STM32CubeIDE (v1.6.1) and run the code generator.

Update the symlink in the mx directory: mp157fab_custom to point to the project.

mp157fab_custom -> /home/chris/Projects/Embedded/mp157fab_custom/

Including the STM32CubeIDE project files into the layer.

As mentioned the mx directory contains a symbolic link to the directories of the STM32CubeIDE project. This is just done for flexibility, if you prefer you can save the entire project in the mx directory.

To tell Yocto that the layer should use these specific device tree files, you must update the variables in the stm32mp1-demo.conf file.

Variable Description
CUBEMX_DTB The prefix of your dts files, e.g. stm32mp157f-fdk2_161-mx
CUBEMX_PROJECT The internal STM32CubeIDE project path to the dts files, e.g. mx/fdk2_161/CA7/DeviceTree/fdk2_161

The second variable is comprised of the mx directory, the name of the symbolic link created in the previous step – fdk2_161 – and then the path of the device tree files in the STM32CubeIDE project. You can use your STM32CubeIDE project to determine these variables:

Bitbake

First build should only take ~2 hours. After that updates to the DTS are much quicker and don’t require a full rebuild.

  • Init the BitBake env setup: source ./layers/meta-st/scripts/envsetup.sh

  • Select the new build dir option:

  • Select the OpenSTLinux-EPLFS distro:

  • Select the demo layer:

The output summary should now have swtiched from

MACHINE           :  stm32mp1-mx

to

MACHINE           :  stm32mp1-demo
  • Compile the yocto project:
bitbake st-image-core

Booting Linux

The image can be booted using a microSD card. See these instructions for preparing the bitbake output.

Troubleshooting

Please see this article section on dealing with bitbake errors.
`

Leave a Reply

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