BARE-METAL ARM CORTEX M3 CHIPS
05 OCTOBER 2024
This post is about programming bare metal SAM3X8E Arm Cortex M3 chips found on
Arduino Due boards. I had to learn how to do this because none of the
high-level tools for programming Arduino Dues are available for OpenBSD, which
I use for much of my personal computing.
Toolchain
Since we will not be using pre-packaged development tools, we need to assemble
our own toolchain. As usual, we need a compiler toolchain to build programs for
the target chip. As we will be bypassing the embedded bootloader, we will also
need a hardware programmer and an on-chip debugger to flash programs to the
chip. I used the following toolchain.
- Arm GNU compiler
toolchain.
- OpenOCD on-chip debugger.
- ST-LINK/V2
programmer.
Electrical connections
The following diagram outlines the electrical connections between the different
components necessary to move a compiled program from a PC to the MCU.
Wiring
Arduino Due
Arduino Due exposes the SAM3X8E’s Serial Wire Debug (SWD) interface via its
DEBUG port. The ST-LINK/v2 programmer uses the SWD protocol to communicate with
the chip.
Uploading the program
Follow the steps below to upload a program to the SAM3X8E chip. The
source.tar.gz tarball at the end of the page contains a sample program with a
OpenOCD config file and a linker script.
- Start OpenOCD:
$ openocd -f openocd-due.cfg
- Open a telnet session and set the GPNVM1 bit to 1:
$ telnet localhost 4444
> halt
> at91sam3 gpnvm show
> at91sam3 gpnvm set 1
> at91sam3 gpnvm show
- Build the program using the custom linker script.
$ arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -T script.ld \
-nostartfiles \
-nostdlib \
-o a.elf main.c
- Upload the program using OpenOCD:
$ openocd -f openocd-due.cfg -c "program a.elf verify reset exit"
Refer to the OpenOCD manual (AT91SAM3 flash driver section) for a complete list
of commands supported for the ATSAM3X8E.
GPNVM bits and the linker script
By design, ARM chips boot into address 0x00000. ATSAM3X8E’s memory consists of
a ROM and a dual-banked flash (flash0 and flash1), residing in different
locations of the chip’s address space.
The GPNVM bits control which of them maps to 0x00000. When GPNVM1 is cleared
(default), the chip boots from the ROM, which contains Atmel’s SAM-BA
bootloader. So, the chip runs the embedded bootloader instead of our program.
When the GPNVM1 bit is 1 (and the GPNVM2 bit is 0), flash0 at address 0x80000
maps to 0x00000. When both GPNVM bits are 0, flash1 maps to 0x00000. Since we
place our program in flash0 using the linker script, we set the GPNVM1 bit and
leave the GPNVM2 bit as it is.
The linker script places the vector table at the first address of the flash.
ARM chips expect this unless we relocate the vector table using the VTOR
register. The first entry of the vector table must be the stack pointer, and
the second must be the reset vector.
Finally, the ATSAM3X8E uses a descending stack. So, in the linker script, we
initialize the stack pointer to the highest memory location available. In the
reset vector, we zero out memory, initialize registers, and perform other tasks
before passing control to the main program.
Files: source.tar.gz
This post is about programming bare metal SAM3X8E Arm Cortex M3 chips found on Arduino Due boards. I had to learn how to do this because none of the high-level tools for programming Arduino Dues are available for OpenBSD, which I use for much of my personal computing.
Toolchain
Since we will not be using pre-packaged development tools, we need to assemble our own toolchain. As usual, we need a compiler toolchain to build programs for the target chip. As we will be bypassing the embedded bootloader, we will also need a hardware programmer and an on-chip debugger to flash programs to the chip. I used the following toolchain.
- Arm GNU compiler toolchain.
- OpenOCD on-chip debugger.
- ST-LINK/V2 programmer.
Electrical connections
The following diagram outlines the electrical connections between the different components necessary to move a compiled program from a PC to the MCU.
Wiring |
Arduino Due |
Arduino Due exposes the SAM3X8E’s Serial Wire Debug (SWD) interface via its DEBUG port. The ST-LINK/v2 programmer uses the SWD protocol to communicate with the chip.
Uploading the program
Follow the steps below to upload a program to the SAM3X8E chip. The source.tar.gz tarball at the end of the page contains a sample program with a OpenOCD config file and a linker script.
- Start OpenOCD:
$ openocd -f openocd-due.cfg - Open a telnet session and set the GPNVM1 bit to 1:
$ telnet localhost 4444 > halt > at91sam3 gpnvm show > at91sam3 gpnvm set 1 > at91sam3 gpnvm show - Build the program using the custom linker script.
$ arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -T script.ld \ -nostartfiles \ -nostdlib \ -o a.elf main.c - Upload the program using OpenOCD:
$ openocd -f openocd-due.cfg -c "program a.elf verify reset exit"
Refer to the OpenOCD manual (AT91SAM3 flash driver section) for a complete list of commands supported for the ATSAM3X8E.
GPNVM bits and the linker script
By design, ARM chips boot into address 0x00000. ATSAM3X8E’s memory consists of a ROM and a dual-banked flash (flash0 and flash1), residing in different locations of the chip’s address space.
The GPNVM bits control which of them maps to 0x00000. When GPNVM1 is cleared (default), the chip boots from the ROM, which contains Atmel’s SAM-BA bootloader. So, the chip runs the embedded bootloader instead of our program.
When the GPNVM1 bit is 1 (and the GPNVM2 bit is 0), flash0 at address 0x80000 maps to 0x00000. When both GPNVM bits are 0, flash1 maps to 0x00000. Since we place our program in flash0 using the linker script, we set the GPNVM1 bit and leave the GPNVM2 bit as it is.
The linker script places the vector table at the first address of the flash. ARM chips expect this unless we relocate the vector table using the VTOR register. The first entry of the vector table must be the stack pointer, and the second must be the reset vector.
Finally, the ATSAM3X8E uses a descending stack. So, in the linker script, we initialize the stack pointer to the highest memory location available. In the reset vector, we zero out memory, initialize registers, and perform other tasks before passing control to the main program.
Files: source.tar.gz