Try this: it's for nRF52833 so minor modifications of memory addresses etc. might be needed, but anyways:
main.c
#include <stdint.h>
#include "ext_include/nrf52833.h"
#include "nrf_helpers.h"
void delay_us(uint32_t i)
{
i *= 7;
i -= 7;
while(i--)
__asm__ __volatile__ ("nop");
}
void delay_ms(uint32_t i)
{
while(i--)
{
delay_us(1000);
}
}
#define LED1_ON() do{LO(P0, 10);}while(0)
#define LED1_OFF() do{HI(P0, 10);}while(0)
#define LED2_ON() do{LO(P0, 20);}while(0)
#define LED2_OFF() do{HI(P0, 20);}while(0)
void uart_print(const char *buf)
{
NRF_UART0->TASKS_STARTTX = 1;
while(buf[0] != 0)
{
NRF_UART0->TXD = buf[0];
while(!NRF_UART0->EVENTS_TXDRDY);
NRF_UART0->EVENTS_TXDRDY = 0;
buf++;
}
NRF_UART0->TASKS_STOPTX = 1;
}
void main()
{
IO_TO_GPO(P0, 10);
IO_TO_GPO(P0, 20);
LED2_ON();
//Uncomment after LED blinking with the default RC clock has been verified, for more accurate clock for UART
//NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
//NRF_CLOCK->TASKS_HFCLKSTART = 1;
//while(!NRF_CLOCK->EVENTS_HFCLKSTARTED) ;
LED2_OFF();
NRF_UART0->PSEL.TXD = (0<<5) | 4;
NRF_UART0->PSEL.RXD = (1<<5) | 9;
NRF_UART0->BAUDRATE = 0x01D7E000; // 115200 baud (actual rate: 115942)
NRF_UART0->ENABLE = 4;
while(1)
{
LED2_ON();
delay_ms(1000);
LED2_OFF();
delay_ms(1000);
uart_print("hello worldings\r\n");
}
}
nrf_helpers.h
/*
Simple low-level hardware abstraction, basically shorter, easily remembered names for the widely used registers
*/
#pragma once
#ifndef NULL
#define NULL ((void*)0)
#endif
#ifndef PACK
#define PACK __attribute__((packed))
#endif
#ifndef ALWAYS_INLINE
#define ALWAYS_INLINE static inline __attribute__((always_inline))
#endif
// Example: HI(P0, 5);
#define HI(port, idx) do{(NRF_ ## port)->OUTSET = 1UL<<(idx);}while(0)
#define LO(port, idx) do{(NRF_ ## port)->OUTCLR = 1UL<<(idx);}while(0)
#define IN(port, idx) ((NRF_ ## port)->IN & (1UL<<(idx)))
#define IN_LOGICAL(port, idx) (!!((NRF_ ## port)->IN & (1UL<<(idx))))
#define IN_SHIFTED(port, idx) (((NRF_ ## port)->IN & (1UL<<(idx)))>>idx)
#define IO_TO_GPI(port, idx) do{(NRF_ ## port)->DIRCLR = 1UL<<(idx);}while(0)
#define IO_TO_GPO(port, idx) do{(NRF_ ## port)->DIRSET = 1UL<<(idx);}while(0)
#define IO_TO_ANALOG(port, idx) do{ uint32_t _tmp_ = (NRF_ ## port)->PIN_CNF[idx]; _tmp_ |= 1UL<<1; (NRF_ ## port)->PIN_CNF[idx] = _tmp_; }while(0)
#define IO_PULLUP_ON(port, idx) do{ uint32_t _tmp_ = (NRF_ ## port)->PIN_CNF[idx]; _tmp_ &= ~(0b11UL<<2); _tmp_ |= 3UL<<2; (NRF_ ## port)->PIN_CNF[idx] = _tmp_; }while(0)
#define IO_PULLDOWN_ON(port, idx) do{ uint32_t _tmp_ = (NRF_ ## port)->PIN_CNF[idx]; _tmp_ &= ~(0b11UL<<2); _tmp_ |= 1UL<<2; (NRF_ ## port)->PIN_CNF[idx] = _tmp_; }while(0)
#define IO_PULLUPDOWN_OFF(port, idx) do{ uint32_t _tmp_ = (NRF_ ## port)->PIN_CNF[idx]; _tmp_ &= ~(0b11UL<<2); (NRF_ ## port)->PIN_CNF[idx] = _tmp_; }while(0)
init.c
/*
Startup code before main(). Memory regions are defined in linker.ld, which exports symbols used here.
*/
#include <stdint.h>
#include "ext_include/nrf52833.h"
#include "nrf_helpers.h"
void nrf_init();
// add some kind of handlers, e.g. LED blinkers in infinite loops
void early_nmi_handler();
void early_hardfault_handler();
void early_mm_handler();
void early_busfault_handler();
void early_usagefault_handler();
void invalid_handler();
extern void main();
extern unsigned int _STACKTOP;
#define INITIAL_VECTOR_TBL_LEN (16+48)
#define FULL_VECTOR_TBL_LEN (16+48)
unsigned int * the_nvic_vector[FULL_VECTOR_TBL_LEN] __attribute__ ((section(".nvic_vector"))) =
{
/* 0x0000 */ (unsigned int *) &_STACKTOP,
/* 0x0004 -15 RESET */ (unsigned int *) nrf_init,
/* 0x0008 -14 NMI */ (unsigned int *) early_nmi_handler,
/* 0x000C -13 HARDFAULT */ (unsigned int *) early_hardfault_handler,
/* 0x0010 -12 MEMMANAGE */ (unsigned int *) early_mm_handler,
/* 0x0014 -11 BUSFAULT */ (unsigned int *) early_busfault_handler,
/* 0x0018 -10 USAGEFAULT */ (unsigned int *) early_usagefault_handler,
/* 0x001C -9 */ (unsigned int *) invalid_handler,
/* 0x0020 -8 */ (unsigned int *) invalid_handler,
/* 0x0024 -7 */ (unsigned int *) invalid_handler,
/* 0x0028 -6 */ (unsigned int *) invalid_handler,
/* 0x002C -5 */ (unsigned int *) invalid_handler,
/* 0x0030 -4 */ (unsigned int *) invalid_handler,
/* 0x0034 -3 */ (unsigned int *) invalid_handler,
/* 0x0038 -2 */ (unsigned int *) invalid_handler,
/* 0x003C -1 */ (unsigned int *) invalid_handler,
/* 0x0040 0 */ (unsigned int *) invalid_handler,
/* 0x0044 1 */ (unsigned int *) invalid_handler,
/* 0x0048 2 */ (unsigned int *) invalid_handler,
/* 0x004C 3 */ (unsigned int *) invalid_handler,
/* 0x0050 4 */ (unsigned int *) invalid_handler,
/* 0x0054 5 */ (unsigned int *) invalid_handler,
/* 0x0058 6 */ (unsigned int *) invalid_handler,
/* 0x005C 7 */ (unsigned int *) invalid_handler,
/* 0x0060 8 */ (unsigned int *) invalid_handler,
/* 0x0064 9 */ (unsigned int *) invalid_handler,
/* 0x0068 10 */ (unsigned int *) invalid_handler,
/* 0x006C 11 */ (unsigned int *) invalid_handler,
/* 0x0070 12 */ (unsigned int *) invalid_handler,
/* 0x0074 13 */ (unsigned int *) invalid_handler,
/* 0x0078 14 */ (unsigned int *) invalid_handler,
/* 0x007C 15 */ (unsigned int *) invalid_handler,
/* 0x0080 16 */ (unsigned int *) invalid_handler,
/* 0x0084 17 */ (unsigned int *) invalid_handler,
/* 0x0088 18 */ (unsigned int *) invalid_handler,
/* 0x008C 19 */ (unsigned int *) invalid_handler,
/* 0x0090 20 */ (unsigned int *) invalid_handler,
/* 0x0094 21 */ (unsigned int *) invalid_handler,
/* 0x0098 22 */ (unsigned int *) invalid_handler,
/* 0x009C 23 */ (unsigned int *) invalid_handler,
/* 0x00A0 24 */ (unsigned int *) invalid_handler,
/* 0x00A4 25 */ (unsigned int *) invalid_handler,
/* 0x00A8 26 */ (unsigned int *) invalid_handler,
/* 0x00AC 27 */ (unsigned int *) invalid_handler,
/* 0x00B0 28 */ (unsigned int *) invalid_handler,
/* 0x00B4 29 */ (unsigned int *) invalid_handler,
/* 0x00B8 30 */ (unsigned int *) invalid_handler,
/* 0x00BC 31 */ (unsigned int *) invalid_handler,
/* 0x00C0 32 */ (unsigned int *) invalid_handler,
/* 0x00C4 33 */ (unsigned int *) invalid_handler,
/* 0x00C8 34 */ (unsigned int *) invalid_handler,
/* 0x00CC 35 */ (unsigned int *) invalid_handler,
/* 0x00D0 36 */ (unsigned int *) invalid_handler,
/* 0x00D4 37 */ (unsigned int *) invalid_handler,
/* 0x00D8 38 */ (unsigned int *) invalid_handler,
/* 0x00DC 39 */ (unsigned int *) invalid_handler,
/* 0x00E0 40 */ (unsigned int *) invalid_handler,
/* 0x00E4 41 */ (unsigned int *) invalid_handler,
/* 0x00E8 42 */ (unsigned int *) invalid_handler,
/* 0x00EC 43 */ (unsigned int *) invalid_handler,
/* 0x00F0 44 */ (unsigned int *) invalid_handler,
/* 0x00F4 45 */ (unsigned int *) invalid_handler,
/* 0x00F8 46 */ (unsigned int *) invalid_handler,
/* 0x00FC 47 */ (unsigned int *) invalid_handler
};
extern unsigned int _DATA_BEGIN;
extern unsigned int _DATA_END;
extern unsigned int _DATAI_BEGIN;
extern unsigned int _BSS_BEGIN;
extern unsigned int _BSS_END;
void nrf_init(void)
{
uint32_t* bss_begin = (uint32_t*)&_BSS_BEGIN;
uint32_t* bss_end = (uint32_t*)&_BSS_END;
while(bss_begin < bss_end)
{
*bss_begin = 0;
bss_begin++;
}
uint32_t* data_begin = (uint32_t*)&_DATA_BEGIN;
uint32_t* data_end = (uint32_t*)&_DATA_END;
uint32_t* datai_begin = (uint32_t*)&_DATAI_BEGIN;
while(data_begin < data_end)
{
*data_begin = *datai_begin;
data_begin++;
datai_begin++;
}
main();
}
linker.ld
MEMORY
{
rom (rx) : ORIGIN = 0x00000000, LENGTH = 512K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 96K /* Leave 32K for stack*/
stack(rwx) : ORIGIN = 0x20017ff8, LENGTH = 0K
}
SECTIONS
{
.nvic_vector : /* Interrupt vector */
{
*(.nvic_vector)
} >rom
. = ALIGN(8);
.text : /* Code run from flash*/
{
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
} >rom
. = ALIGN(8);
_DATAI_BEGIN = LOADADDR(.data);
.data :
{
_DATA_BEGIN = .;
*(.data)
*(.data.*)
_DATA_END = .;
} >ram AT>rom
. = ALIGN(8);
.heap :
{
_HEAP = .;
} >ram
. = ALIGN(8);
.stack :
{
_STACKTOP = .;
} >stack
. = ALIGN(8);
.bss :
{
_BSS_BEGIN = .;
*(.bss)
*(COMMON)
_BSS_END = .;
} >ram
. = ALIGN(8);
}
makefile
# This makefile is made to work with the toolchain downloadable at https://launchpad.net/gcc-arm-embedded
CC = arm-none-eabi-gcc
LD = arm-none-eabi-gcc
SIZE = arm-none-eabi-size
OBJCOPY = arm-none-eabi-objcopy
CFLAGS = -Os -I. -I./ext_include -fno-common -ffunction-sections -ffreestanding -mabi=aapcs -mthumb -mcpu=cortex-m4 -specs=nano.specs -Wall -Winline -fstack-usage -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fno-strict-aliasing
ASMFLAGS = -S -fverbose-asm
LDFLAGS = -mcpu=cortex-m4 -mthumb -nostartfiles -mabi=aapcs -mfloat-abi=hard -mfpu=fpv4-sp-d16 -specs=nano.specs
OBJ = init.o main.o
ASMS = init.s main.s
all: main.bin
$(OBJ): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
main.bin: $(OBJ) $(LIBS)
$(LD) -Tlinker.ld $(LDFLAGS) -o main.elf $^ -lm
$(OBJCOPY) -Obinary --remove-section=.ARM* --remove-section=*bss* main.elf main.bin
$(SIZE) main.elf
reset:
nrfjprog -f nrf52 -r
flash: main.bin
nrfjprog -f nrf52 --program main_full.bin --chiperase -r
#flash_ocd: main.elf
# openocd <something>
stack:
cat *.su
sections:
arm-none-eabi-objdump -h main.elf
syms:
arm-none-eabi-objdump -t main.elf
%.s: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS) $(ASMFLAGS)
asm: $(ASMS)
clean:
rm *.o
I think that's all, except the usual cmsis* core* header files by ARM, and nrf52833.h and system_nrf52833.h by Nordic Semi.
Setting up "bare toolchain" should be fundamentally easy, but it is difficult because of secret information and obfuscation. By just cutting out unnecessary parts (for example: autogenerated startup files and linker scripts) makes it more understandable, until at some point the ends just meet and you are able to do it by providing everything (except the compiler itself, of course). It's quite revealing this is all copy-pasta from my earlier STM32H7 projects and it was running quickly exactly because I did not try to do anything differently (well, first I tried, and it did not lead anywhere.)
But I think my example should be simple/small enough that you can actually go through every line and understand what it does, no?