Skip to content

minimal Makefile-based build guide for bare-metal ARM micrcontrollers

Notifications You must be signed in to change notification settings

xtianbetz/arm-mcu-makefile-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ARM MCU Makefile Guide

STATUS: WORK IN PROGRESS. NOTHING TO SEE HERE.

A minimalist guide and skeleton for building, running, and debugging firmware for ARM-based microcontrollers.

You will use only the following software tools:

  • Make, for orchestrating build tasks

  • GNU GCC and LD, for compiling ARM binaries

  • OpenOCD, for flashing firmware and interactive debugging.

  • GDB, for interactive debugging via OpenOCD.

  • Your favorite text editor, for editing source files.

  • Your favorite terminal emulator, for doing CLI tasks.

Goals for this Guide

After following this guide, you will be able to:

  • Demonstrate a minimal Makefile-based solution for a very simple LED-blinking firmware.

  • Understand the basic concepts of MCU development.

  • Perform build/test/debug cylces entirely from the CLI on Linux (and eventually Mac)

  • Adapt the Makefile to a continuous integration system such as Jenkins. In other words, it should be straightforward to automate building and publishing firmware binary release-candidates automatically following code review

  • Avoid copying vendor code with non-free licenses. Some vendors provide code examples or supporting source files with non-free licenses. Instead of copying example code from these vendors, we will reference datasheets and manuals.

  • The classic vendor-supported methods of developing firmware for MCUs is using a full-blown GUI-based IDE like Keil, IAR, or MCU Eclipse.

  • These tools are GUI-based and can be proprietary and/or expensive. They also have their own learning curves as they are advanced tools for "serious professionals".

  • GUI-based tools can obscure the underlying concepts which aren’t really that complicated!

  • Using vendor-provided SDKs and their accompanying non-free licenses could inadvertently lock you into a particular MCU vendor. There are many different ARM MCU vendors you can choose from, and you should be able to switch when you want.

Hardware

Development Boards

This guide targets the following MCU vendor development kits:

  • ST

    • STM32F3DISCOVERY STM32F3 Discovery Board (STM32F303VCT6) (Cortex-M4)

    • STM32F407G-DISC1 STM32F4 Discovery Board (STM32F407VGT6) (Cortex-M4)

  • Nuvoton

    • NuTiny-NUC029SEE (NUC029SEE) (Cortex-M0)

  • NXP

    • OM13080UL: LPCXpresso1125 Board (LPC1125JBD48) (Cortex-M0)

    • OM13093UL: LPCXpresso board for LPC11C24 with CMSIS DAP probe (LPC11C24FBD48) (Cortex-M0)

Reading Vendor Datasheets

The datasheet for your microcontroller is often the best source of information about how to program your MCU. Find your vendor’s datasheet on their website and put it someplace handy. You will be using the datasheet a lot!

Things we need from the Vendor Datasheets:

  • ROM (flash) size

  • ROM base address

  • RAM size

  • RAM base address

  • GPIO Bank and Pin for blinking LED

  • GPIO configuration and/or control registers

In order to enable the GPIOs as outputs and blink an LED, we have to find the addresses of the control registers from the vendor datasheets. We also need to know which GPIO bank (and/or pin) is mapped to an existing LED on the dev board (hopefully there is one).

  • Nuvoton

    • MCU: NUC029SEE

    • MCU Datasheet: Nuvoton NUC029xEE technical user manual (TRM_NUC029xEE_EN_Rev1.01.pdf)

      • Note: For this model, the 'S' in the model designates the physical package type. This datasheet is shared across a few different package types.

    • SDK Datasheet: NuMicro Family NuTiny-SDK-NUC029SEE User Manual (UM_NuTiny-SDK-NUC029SEE_EN_Rev1.00.pdf)

    • SVD File: NUC029EE_v1.svd (from Keil pack downloads, see link below)

    • ROM size: 128K

    • ROM base address: 0x0000_0000

    • RAM size: 16K

    • RAM base address: 0x2000_0000

    • LED GPIO: GPIO1 (from "SDK Circuit Schematic" in SDK User Manual). Acccording to "Target Chip Schematic", LED is hooked up to GPIO Bank B (PB.4), Pin 10

    • LED PIN Bit Offset: 4 (i.e. PB.4 is enabled using the fifth bit in the register, GPIOB_DOUT)

    • See "Section 6.2.4 System Memory Map", "6.2.7 Register Map", "6.6.5 Register Map", "SDK Circuit Schematic"

    • System Control Register: GPB_MFP, Offset: GCR_BA(0x5000_0000)+0x34 "GPIOA Multiple Function and Input Type Control Register"

    • 0x5000_4000-0x5000_7FFF: "GPIO Control registers"

    • GPIO_BA (base address): 0x5000_4000

    • GPIOB_PMD (I/O mode control register): GPIO_BA+0x040

    • GPIOB_DOUT (output register): GPIO_BA+0x048

  • ST

    • ST STM32F407xx: look in "Memory mapping" (Section 4), "Table 10. register boundary addresses"

    • 0x4002_0C00-0x4002_0FFF: GPIOD

    • TODO: exact register addresses of the GPIO bank we want to enable for use.

Software

Getting Started

x@x1carbon:~$ cd ~/Code
x@x1carbon:~/Code$ mkdir arm-blink

Project Source File Overview

The following source files are used to build the binary you will load:

  • The device startup ARM assembly source (startup_ARMCMX.s)

  • The device memory parameters (heap and stack size) (mem_ARMCM0.h)

  • The device application C source (main.c)

Walking through the ARM Startup Assembly Language File

We’ll start by copying the the ARM assembly startup file we need from the official ARM software github.

x@x1carbon:~/Code$ git clone git@github.com:ARM-software/CMSIS_5.git
x@x1carbon:~/Code$ cd CMSIS_5/CMSIS
x@x1carbon:~/Code/CMSIS_5/CMSIS$ find | grep "startup" | grep CM0 | grep GCC
./DSP/Platforms/FVP/ARMCM0/Startup/GCC/startup_ARMCM0.S
./DSP/Platforms/IPSS/ARMCM0/Startup/GCC/startup_ARMCM0.S
x@x1carbon:~/Code/CMSIS_5/CMSIS$ cp ./DSP/Platforms/FVP/ARMCM0/Startup/GCC/startup_ARMCM0.S ~/Code/arm-blink/

What is going on in this file? First go read the first five paragraphs from the fine manual for binutils as (the GNU binutils assembler). The manual explains what a binary section is and will help you understand how the linker works, so don’t skip it!

The first two lines of startup_ARMCM0.S set the ASM syntax and architecture (the Cortex-M0 is an armv6-m).

The Interrupt Vector Table

  • The next lines of the setup assembly define a binary section called ".vectors" with a two byte alignment. This section is commonly called the interrupt vector table.

  • The interrupt vector table is essentially an array of function pointers.

  • In the assembly code, three global symbols are declared. These symbols help the linker find the vector table when it assembles the final binary that you will flash to your device.

  • Next, the vector table itself is defined. The ARM Cortex-M0 vector table layout is specified by ARM.

  • When an interrupt occurs the CPU jumps to the 32-bit address for the specific interrupt that occurred and continues executing code there.

  • The interrupt we care about right now is the Reset interrupt that occurs when the CPU powers on.

  • The first 32-bits in the vector table are special: they hold the initial stack pointer, the address of the top of the stack. (note: stacks grow down on ARM and nearly all other modern processors).

  • The next 32-bits point at the address of the reset handler code (which will be defined later in the assembly file).

  • A bunch more default handlers are created for different interrupts that we reload-firefox

  • Next, the "BL" instruction tells the CPU to branch to the SystemInit function, when the SystemInit function returns it will continue on the next instruction. This function will be written in C code later and linked using the link script.

  • After we return from SystemInit, we pick up by loading two addresses using two ARM-specific "synthetic" opcodes. These lines reference two symbols, _copy_table_start_ and _copy_table_end_. These symbols are used by ResetHandler to copy the data sections of the binary from ROM to RAM. We’ll see them being defined later in the linker script.

  • A similar process happens with _zero_table_start_ and _zero_table_end_ in order to zero out the BSS section.

The C Code

The Linker Script

The main purpose of the linker script is to describe how the sections in the input files should be mapped into the output file, and to control the memory layout of the output file.

The linker script instructs GNU ld to create a binary that includes the vector table section, compiled assembly instructions section, and compiled C program sections. All these sections need to go into the exact right locations in the binary. This way the CPU can find the vector table where it expects it to be, the vector table’s second entry points at the compiled ResetHandler code, and so on.

We will just the copy linker script from the ARM CMSIS v5 distribution since it has an open license:

x@x1carbon:~/Code/arm-blink$ cp /home/x/Code/CMSIS_5/CMSIS/DSP/Platforms/IPSS/ARMCM0/LinkScripts/GCC/lnk.ld .

The linker script references a file called mem_ARMCM0.h which defines the stack size as 12 KB and a heap size of 1024 KB (1 MB).

x@x1carbon:~/Code/arm-blink$ cp /home/x/Code/CMSIS_5/CMSIS/DSP/Platforms/IPSS/ARMCM0/LinkScripts/GCC/mem_ARMCM0.h .

Next, edit the linker script to make the size value parameters match your MCU from the information you collected above. Make sure to convert decimal sizes to hex!

For example, for the NUC029SEE has 128KB ROM and 16KB RAM, so you would set the values as follows.

  • Set __ROM__SIZE to "0x000020000" (128Kb*1024 is 131072 bytes in decimal, 0x20000 in hex)

  • Set __RAM__SIZE to "0x000004000" (16Kb*1024 is 16384 bytes in decimal, 0x4000 in hex)

  • The __ROM_BASE and __RAM_BASE do not need to be changed since they are standard values.

The linker script defines the memory layout using the MEMORY command. This command allows later parts of the linker script to reference specific regions of memory. Our memory consists of two regions: FLASH and RAM, which are defined according to the size/base parameters you setup.

Next, the linker script declares that that ResetHandler function (defined in the startup assembly file) should be the main entry point for our final binary.

The script then uses the SECTIONS keyword to define the binary sections:

  • The main .text section is defined in the FLASH memory region. It includes the following susubsections:

    • The .vectors section, which references the vector table defined in the startup assembly file.

    • All input program binary .text sections follow next. In our case the C code will be compiled into a binary object with a single text section.

    • The code for any initializers, finalizers, constructors, and destructors (i.e. for C libraries or C++ applications)

    • Read-only data (rodata)

    • A special section called the eh-frame is used by GCC to handle C++ exceptions and unwind the stack when debugging.

  • The .ARM.extab and .ARM.exidx sections are also used for exceptions and stack unwinding, but are specifically part of the ARM standard.

  • The .copy.table section is a special section that is used by the ARM startup assembly code. This section will contain the memory addresses of the program data section (i.e. the part that would contain global variables or structures).

  • The .zero.table section is similar to the .copy.table sections. The memory regions referenced by the symbols in this section will be zeroed out in the ARM startup assembly.

  • The .data section is the first binary section in the MEMORY region. It includes the following subsections:

    • the data_start symbol marks the start of this section; it is referenced earlier in the linker script and in startup assembly fiile.

    • the vtable (a virtual method table) used by C++ programs. **

    • Next, the linker includes the main data section and all other data sections from any input binary files.

    • Data values for preinit, init, finit come next. These are also used for C++ programs.

Building OpenOCD

  • TODO: Walkthrough install of ARM GCC toolchain

export PATH=$PATH:/home/x/Toolchains/gcc-arm-none-eabi-9-2019-q4-major/bin
  • TODO: Walkthrough build/install OpenOCD

Resources

Youtube Videos

The following series of videos can help you understand how the Cortex-M processors work.

About

minimal Makefile-based build guide for bare-metal ARM micrcontrollers

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published