Bare metal programming on the nRF9160

I have purchased the nRF9160 Development Kit and are just getting started with the device and the nRF Connect SDK.

I intend to use the supplied LTE-M modem firmware for my project along with my own application firmware. However, it seems like all the samples included in the SDK needs the Zephyr real-time OS in order to work.

Is it possible to do bare metal programming on the nRF9160 like I did with the nRF52840 and basically all microcontrollers I have programmed before?

I searched quite a bit in the files included with the SDK, and I found no samples with a linker script and traditional makefile for GCC, and I have no idea which headers to include in order to access registers in a convenient way.

Thanks in advance Slight smile

  • Thanks, Sigurd :-)

    I have now created a makefile that can build a "blinky" example without Zephyr. I have only tested this with the nrfx.h header, but I guess that it is possible to add C files from the nrfx library to the object list in the makefile, if one wishes to use the nrfx library for more than just easy register access.

    Example output when building and flashing:

    Creating directory build
    [CC] main.c --> build/main.o
    Creating directory build/cmsis
    [AS] /opt/nrfx-2.9.0/mdk/gcc_startup_nrf9160.S --> build/cmsis/startup.o
    [CC] /opt/nrfx-2.9.0/mdk/system_nrf9160.c --> build/cmsis/system.o
    [LD] build/blinky.axf
    [OBJCOPY] ./build/blinky.axf --> ./build/blinky.hex
    [OBJCOPY] ./build/blinky.axf --> ./build/blinky.bin
       text	   data	    bss	    dec	    hex	filename
       2144	    108	     36	   2288	    8f0	./build/blinky.axf
    Flashing ./build/blinky.hex
    Parsing image file.
    Verifying programming.
    Verified OK.
    Applying system reset.

    It actually works and makes the LEDs light up on the board :-)


    // Bare metal nRF9160 DK "Blinky" example                     PVH, October 2022
    #include "nrfx.h"
    int main()
        // LED1..4 on the nRF9160 DK are connected to P0.02..P0.05 and are active 
        // high, so we first configure these pins on P0 as outputs
        NRF_P0->DIRSET |= (1<<5) | (1<<4) | (1<<3) | (1<<2);
        while (1)
            // In the main loop, we make each of the LEDs light up one at a time
            for (uint32_t i = 0; i < 4; ++i)
                NRF_P0->OUTSET |= 1<<(i+2);
                // Some delay
                volatile uint32_t j = 1000000;
                while (j > 0)
                NRF_P0->OUTCLR |= 1<<(i+2);
        return 0;


    # Makefile for nRF9160 bare metal programming                 PVH, October 2022
    # Arm GNU toolchain can be found here (look for gcc-arm-none-eabi)
    # nrfx is available at
    # CMSIS can be found at
    # Paths to toolchain and SDK
    TOOLSPATH = /opt/gcc-arm-none-eabi-9-2020-q2-update
    NRFXPATH = /opt/nrfx-2.9.0
    # Specify project name, object files, headers (DEPS) and linker script
    PROJECT = blinky
    OBJ = main.o
    DEPS =
    LDSCRIPT = ${NRFXPATH}/mdk/nrf9160_xxaa.ld
    BUILDDIR = ./build
    # Startup and system code
    ASTART = ${NRFXPATH}/mdk/gcc_startup_nrf9160.S
    SYSSRC = ${NRFXPATH}/mdk/system_nrf9160.c
    # Common flags for CC, AS and LD
    FLAGS = -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mabi=aapcs  
    FLAGS += -mfpu=fpv5-sp-d16 -DNRF9160_XXAA
    # Shortcuts for various tools
    CC = ${TOOLSPATH}/bin/arm-none-eabi-gcc
    AS = ${TOOLSPATH}/bin/arm-none-eabi-gcc
    LD = ${TOOLSPATH}/bin/arm-none-eabi-gcc
    OBJCOPY = ${TOOLSPATH}/bin/arm-none-eabi-objcopy
    SIZETOOL = ${TOOLSPATH}/bin/arm-none-eabi-size
    # Compiler flags
    CFLAGS = ${FLAGS} -std=c99 -Wall -Werror
    CFLAGS += -I"${NRFXPATH}/templates" -I"${NRFXPATH}/mdk"
    CFLAGS += -I"${NRFXPATH}" -I"${CMSISPATH}/Core/Include"
    CFLAGS += -I"${NRFXPATH}/hal" -I"${NRFXPATH}/drivers/include"
    # Assembler flags
    AFLAGS = ${FLAGS} -x assembler-with-cpp
    # Linker flags
    LDFLAGS = ${FLAGS} -T "$(LDSCRIPT)" -Xlinker
    LDFLAGS += --gc-sections -Xlinker -Map="$(BUILDDIR)/$(PROJECT).map" 
    LDFLAGS += --specs=nano.specs
    LDFLAGS += -L"${NRFXPATH}/mdk/"
    LIBS = -Wl,--start-group -lgcc -lc -lnosys -Wl,--end-group
    # Check whether to optimize or build for debugging
    DEBUG ?= 0
    ifeq ($(DEBUG), 1)
    	CFLAGS += -O0 -g3 -gdwarf-2
    	AFLAGS += -g3 -gdwarf-2
    	LDFLAGS += -g3
    	CFLAGS += -O3
    # Substitute the correct path for the object filenames
    _OBJ = $(patsubst %,$(BUILDDIR)/%,$(OBJ))
    # Define operations
    define COMPILE
    	@echo "[CC] $< --> $@"
    	@$(CC) $(CFLAGS) -c -o $@ $<
    define ASSEMBLE
    	@echo "[AS] $< --> $@"
    	@$(AS) $(AFLAGS) -c -o $@ $<
    define OBJECTCOPY
    	@echo "[OBJCOPY] $(2)$(3) --> $(2)$(4)"
    	@$(OBJCOPY) $(1) $(2)$(3) $(2)$(4)
    define CREATEDIR
    	@echo "Creating directory $@"
    	@mkdir -p $@
    # Build the project
    $(BUILDDIR)/$(PROJECT).axf: $(_OBJ)  \
        $(BUILDDIR)/cmsis/startup.o $(BUILDDIR)/cmsis/system.o
    # Link
    	@echo "[LD] $@"
    	@$(LD) $(LDFLAGS) -o $(BUILDDIR)/$(PROJECT).axf \
    	$(BUILDDIR)/cmsis/startup.o \
    	$(BUILDDIR)/cmsis/system.o \
    	$(addprefix $(BUILDDIR)/, $(OBJ)) $(LIBS)
    # Generate hex and bin files
    	$(call OBJECTCOPY,-O ihex,$(BUILDDIR)/,"$(PROJECT).axf","$(PROJECT).hex")
    	$(call OBJECTCOPY,-O binary,$(BUILDDIR)/,"$(PROJECT).axf","$(PROJECT).bin")
    # Run the size tool
    	@echo ""
    	@$(SIZETOOL) $(BUILDDIR)/"$(PROJECT).axf"
    	@echo ""
    all: $(BUILDDIR)/$(PROJECT).axf
    	$(call CREATEDIR)
    	$(call CREATEDIR)
    $(BUILDDIR)/cmsis/startup.o: $(ASTART) | $(BUILDDIR)/cmsis
    	$(call ASSEMBLE)
    $(BUILDDIR)/cmsis/system.o: $(SYSSRC) | $(BUILDDIR)/cmsis
    	$(call COMPILE)
    $(BUILDDIR)/%.o: %.c $(DEPS) | $(BUILDDIR)
    	$(call COMPILE)
    # Remove builded stuff
    	rm -dfr $(BUILDDIR)
    # Flash the program
    flash: $(BUILDDIR)/$(PROJECT).axf
    	@echo Flashing $(BUILDDIR)/$(PROJECT).hex
    	@nrfjprog -f nrf91 --program $(BUILDDIR)/$(PROJECT).hex --sectorerase \
    	--verify --reset
    	@echo Erasing all flash
    	@nrfjprog -f nrf91 --eraseall

    The makefile is a bit messy and may contain errors or suboptimal settings or flags, so any suggestions are welcome, but so far, so good :-)

  • This post just saved me a few hours, thanks a lot ;)