Skip to content

Embedded Devices

Michael Grossniklaus edited this page Apr 2, 2024 · 3 revisions

This page provides a tutorial how to use the Oberon compiler with the Clang toolchain to target embedded devices. The selected target here is the STM32F4-Discovery board and the code is a simple busy loop led blinker.

Reference this blog post for further relevant information.

Prerequisites

A custom build of the Clang toolchain is needed as the standard Clang supplied runtime libraries are currently not suitable for embedded use.

Arm provides a custom build which utilizes the alternative picolibc runtime. The toolchain need to be downloaded and installed.

In order to check that the code is running correctly we can utilize the QEMU system emulator. The qemu-arm-xpack version contains graphics support for the STM32F4-Discovery board which the upstream QEMU does not support. The qemu-arm-xpack can be be downloaded and installed for several platforms.

Link script

The linker script is needed to describe the memory layout to the linker and the information here is found in the MCU datasheet.

stm32f407zg.ld:

__boot_flash = 0x00000000;
__boot_flash_size = 0x1000;
__flash = 0x00001000;
__flash_size = 996k;
__ram = 0x20000000;
__ram_size = 128k;
__stack_size = 512;

INCLUDE picolibcpp.ld

Reference to the picolibc documentation for information on setting up this scripts.

Makefile

The command are rather involved, so it is advicable to wrapp this in a Makefile:

ARMPATH=/c/LLVMEmbeddedToolchainForArm/bin
O7 := oberon-lang
CC := clang
CFLAGS=-Os -m32 -mthumb --target=thumbv7m-none-eabi -march=armv7m -mcpu=cortex-m4 -mfloat-abi=soft
CRT=-lcrt0
SEMIHOST=-lcrt0-semihost -lsemihost
QEMU=qemu-system-gnuarmeclipse
QEMUFLAGS=--verbose --board STM32F4-Discovery --mcu STM32F407ZG --semihosting-config enable=on,target=native -d unimp,guest_errors

build: blinker.elf

blinker.ll: Blinker.ob07
    @echo Compiling $<
    @$(O7) -O0 --reloc=pic -fenable-extern -fenable-main -o $@ --filetype ll $<

blinker.elf: blinker.ll
    @echo Compiling $<
    @$(ARMPATH)/$(CC) $(CFLAGS) $(SEMIHOST) -g -T ./stm32f407zg.ld -o $@ $<

run: blinker.elf
    $(QEMU) $(QEMUFLAGS) --image $<

clean:
    @echo Clean
    @-rm -f *.elf *.ll

.PHONY: clean run

The semihost library variant is here linked and printf etc will work with the simulator and with real harware trough a debugger probe.

The ARMPATH should be changed to the location where the toolchain was installed.

Code

The Oberon code here is a simple as possible and uses a simple busy loop for delay.

Blinker.ob07:

MODULE Blinker;
IMPORT SYSTEM;

CONST
    (* RM0090 Reference manual *)
    PORTD           = 3;
    PIN             = 15;
    RCCAHB1ENR      = 40023830H;
    GPIODMODER      = 40020C00H;
    GPIODOTYPER     = 40020C04H;
    GPIODBSRR       = 40020C18H;
    sleepTime       = 80000000;

(* Import `puts` function from C <stdio.h> library. *)
PROCEDURE puts(s: ARRAY OF CHAR): INTEGER; EXTERN;

PROCEDURE Sleep (x: INTEGER);
BEGIN REPEAT DEC(x) UNTIL x = 0
END Sleep;

PROCEDURE Setup;
VAR 
    x : SET;
BEGIN
    (* Enable the GPIOD peripheral in 'RCCAHBENR'. *)
    SYSTEM.GET(RCCAHB1ENR, x);
    SYSTEM.PUT(RCCAHB1ENR, x + {PORTD});
    (* Set mode and set push-pull mode *)
    SYSTEM.GET(GPIODMODER, x);
    SYSTEM.PUT(GPIODMODER, x - {2*PIN + 1} + {2*PIN});
    SYSTEM.GET(GPIODOTYPER, x);
    SYSTEM.PUT(GPIODOTYPER, x - {PIN})
END Setup;

BEGIN
    Setup();
    REPEAT
        puts("ON");
        SYSTEM.PUT(GPIODBSRR, {15}); (* PD15 *)
        Sleep(sleepTime);
        puts("OFF");
        SYSTEM.PUT(GPIODBSRR, {15 + 16}); (* ~PD15 *)
        Sleep(sleepTime)
    UNTIL FALSE
END Blinker.

The register values used can be found in the MCU RM0090 reference manual.