It'd make sense to do some toolchain clean up

This article is part of a series.

My Makefile was a bit of a “get it done” hack. It still will be after this session, but a better one!

Goals

Related Repo: https://github.com/carlynorama/StrippedDownChipRosetta/tree/main/ARM/SAMD21E18/05_Structure

Conditional Find Files

I’m going to switch from the SAMD21E18A to the SAMD21G18A the next post because todbot has an ItsyBitsy M0 Express ready to swipe and it’s bugging me that the LED and the switch don’t match the AVR example.

This means my code may end up being able to support two chips if I get fancy with my Makefile. To start, proof of concept with the original files.

New directory layout

└── Project
    └── SAMD21E18A
    │   └── linker.ld
    |   └── startup.s 
    └── main.s
    └── Makefile

New additions to the Makefile

I had switched to hard coding the files when I split into main.s and startup.s, now it’s time to switch back to a more automated search.

CHIP ?= SAMD21E18A
MCU := cortex-m0plus

LINKER_FILE ?= $(CHIP)/linker.ld

SOURCES = $(wildcard *.s) # only this dir
SOURCES += $(wildcard *.S) # only this dir
SOURCES += startup.s # all dir in VPATH

VPATH=$(CHIP) #will look in this folder for missing files in sources.

OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) #replaces any suffix

This portion sets up the Makefile to look both in the current directory and anywhere added to the VPATH for the files it needs later on. VPATH is a built in variable. The new custom variable CHIP sets the string indicating which SAMD21 chip is currently on deck. The string gets added to the VPATH as a directory name.

Longer aside: I’ve been switching between :=, = and =? for my assignments. Simplistically

::= and :::= are assignment operators too. (More precisely := assigns “simply expanded variables” and = assigns “recursively expanded” variables, see the list of variable flavors)

Sorting out .s vs .S

If you’ve never already, go to a working directory without a makefile in it and type make -p to see all the implicit variables and commands make uses under the hood.

In many setups, adding steps like %.o:%.s or %.o:%.S or %.o:%.c isn’t needed because make already has sensible defaults for “transform every x file into needed y file”

Also, in many setups the assembler will never see human written pure assembly files. All pure assembly will have been written by the compiler.

What’s the general difference between the .S and .s suffix? Well, we can see in the default rules that:

# default
PREPROCESS.S = $(CC) -E $(CPPFLAGS)

# default
COMPILE.S = $(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c

# default
LINK.S = $(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)

# default
COMPILE.s = $(AS) $(ASFLAGS) $(TARGET_MACH)

# default
LINK.s = $(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH)

%: %.s
#  commands to execute (built-in):
	$(LINK.s) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.s
#  commands to execute (built-in):
	$(COMPILE.s) -o $@ $<

%: %.S
#  commands to execute (built-in):
	$(LINK.S) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.S
#  commands to execute (built-in):
	$(COMPILE.S) -o $@ $<

%.s: %.S
#  commands to execute (built-in):
	$(PREPROCESS.S) $< > $@

S files “might include C preprocessing statements (#define, #ifdef, and etc.), but they should not contain C statements” (microchip online docs: brief FAQ answer , output files list)

I’ve been personally sloppy on whether my hand rolled assembly should get the .s or .S suffix because of all the .___ compiler directives (.thumb, .syntax unified) and C style comments in them threw me for a loop. In a related sloppiness, I’ve been using the -x assembler-with-cpp option without questioning it for the .s files, even though they shouldn’t need it.

Back when working on separating the startup file, I did confirm the actual assembler works just fine with this flavor of assembly, so I’m going to get a little less sloppy and use the assembler for .s and the complier with -x assembler-with-cpp for .S files.

My main tool variables and build rules now look like

TOOL_BASE := arm-none-eabi-
CC = $(TOOL_BASE)gcc
AS = $(TOOL_BASE)as
## Default
all: $(OBJECTS) $(TARGET).elf
	
## Assembler
%.o:%.s
	$(AS) $(ASFLAGS) -c $< -o $@

%.o:%.S
	$(CC) -x assembler-with-cpp -c $(CFLAGS) $< >$@

## Linker
$(TARGET).elf: $(OBJECTS)
	 $(CC) $(LDFLAGS) $(OBJECTS) $(LINKONLYOBJECTS)  -T./$(LINKER_FILE) -o $@

Improving OpenOCD shortcuts

OpenOCD can be way way fancier than anything I’m doing, but I’ve tired to modularize the project a bit. The previous Makefile only had the one “load-program and launch servers” command. I’ve made some more rules and pushed the asscoiated files into their own directory. I’ve tried to show both stowing options in files and calling commands directly from the Makefile.

New Files

└── Project
    └── openocd
    │   └── segger.cfg
    |   └── SAMD21E18A.cfg 
    |   └── flash_program_prep.cfg 
    └── SAMD21E18A
    │   └── linker.ld
    |   └── startup.s 
    └── main.s
    └── Makefile

segger.cfg

# Segger J-Link EDU, SWD modere
adapter driver jlink
transport select swd

SAMD21E18A.cfg

same name as used for $(CHIP)

set CHIPNAME at91samd21e18
source [find target/at91samdXX.cfg]

flash_program_prep.cfg

init
reset halt
at91samd bootloader 0  #clear bootloader 

New variables

PROGRAMMER := openocd
PRGMR_FILES_DIR := openocd/
PROGRAMMER_COMMON = -f $(PRGMR_FILES_DIR)segger.cfg  #load hardware
PROGRAMMER_COMMON += -f $(PRGMR_FILES_DIR)$(CHIP).cfg #load chip description

FLASH_FLAGS = $(PROGRAMMER_COMMON)
FLASH_FLAGS += -f $(PRGMR_FILES_DIR)flash_program_prep.cfg
FLASH_FLAGS += -c "program $(TARGET).elf verify reset exit"

New rules

# Program the device.  
flash: $(TARGET).elf
	$(PROGRAMMER) $(FLASH_FLAGS)

#fns == "flash and serve"
fns: $(TARGET).elf
	$(PROGRAMMER) $(PROGRAMMER_COMMON) \
	-f $(PRGMR_FILES_DIR)flash_program_prep.cfg \
	-c "program $(TARGET).elf verify"

serve:
	$(PROGRAMMER) $(PROGRAMMER_COMMON)

talk: 
	telnet localhost 4444

.PHONY: talk serve

Improving GDB shortcuts

Like with OpenOCD, GDB encompasses huge capabilities, which I’m only lightly touching on. Again, did some modularization and some convenience instructions. Again trying to show performing some tasks inline in the Makefile and others in external files.

GDB has a whole auto-loading schema of rules and extensions that I’m not touching AT ALL, but I’ve put some links in the references.

New Files

└── Project
    └── gdb
    │   └── main_breakpoints.txt
    |   └── mem_locs.txt  
    └── openocd
    │   └── segger.cfg
    |   └── SAMD21E18A.cfg 
    |   └── flash_program_prep.cfg 
    └── SAMD21E18A
    │   └── linker.ld
    |   └── startup.s 
    └── main.s
    └── Makefile

main_breakpoints.txt

break loop
break _start
break multiPinPullup

One way to generate this file would be to quickly add all functions with rbreak . and del [break point number] or clear [filename:linenum] unwanted ones (see current with info break or just i b). Once the breakpoints look the way that works use save breakpoints [filename] to generate the file.

For an example usage see the new Makefile rule below.

mem_locs.txt

Comments on own line only.

# for the direction set
x/1x 0x41004400 
# for the current output values
x/1x 0x41004410
# for the PINCFG settings, PA07 is top
x/1x 0x41004444 

Example usage, witch gdb session open try source gdb/mem_locs.txt and the responses should load.

Tool Def & Variables

DEBUGGER = $(TOOL_BASE)gdb
DEBUGGER_FILES_DIR = gdb/
DEBUG_FLAGS = -iex "target extended-remote localhost:3333"
DEBUG_FLAGS += -ex "layout regs"
DEBUG_FLAGS += -ex "source $(DEBUGGER_FILES_DIR)main_breakpoints.txt"

New Rule

debug: $(TARGET).elf
	$(DEBUGGER) $^ $(DEBUG_FLAGS) #$^ inserts ALL the listed input files. 

Adding in PHONY

.PHONY chiefly protect against a rule/file name collision preventing a rule from running. I hadn’t been having that trouble, but it’s the right thing to do. (There are other more specific reasons to use it, read the linked doc)

## Phony List 
.PHONY: all talk debug res_check clean serve fnl flash symb disamb symb_o disamb_o

Helpful References

Makefiles

gcc & as

GDB

Summary

Next to update the actual circuit!

Full Makefile

###############################################################################
# Makefile for a simple SAMD21 Assembly project
###############################################################################

TARGET ?= combined

CHIP ?= SAMD21E18A
MCU := cortex-m0plus

###############################################################################
## Resoruces
LINKER_FILE ?= $(CHIP)/linker.ld

SOURCES = $(wildcard *.s)# only this dir
SOURCES += $(wildcard *.S)# only this dir
SOURCES += startup.s# all dir in VPATH

VPATH=$(CHIP)#will look in this folder for missing files in sources.

OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))#replaces any suffix

###############################################################################
## Tools
TOOL_BASE := arm-none-eabi-
CC := $(TOOL_BASE)gcc
AS := $(TOOL_BASE)as
OBJD := $(TOOL_BASE)objdump
DEBUGGER := $(TOOL_BASE)gdb
NM := $(TOOL_BASE)nm

PROGRAMMER := openocd

###############################################################################
## Flags

## Options common to compile, link and assembly rules
COMMON = -mcpu=$(MCU)
COMMON += -mthumb

## Compile options common for all C compilation units.
CFLAGS = $(COMMON)
CFLAGS += -Wall
CFLAGS += -g
#CFLAGS += -Os #compression

ASFLAGS = $(CFLAGS)

## Linker flags
LDFLAGS = $(COMMON)
LDFLAGS += --specs=nosys.specs -nostdlib -lgcc
LDFLAGS += -Wl,-Map=$(TARGET).map

# Programming & Debugging 
PRGMR_FILES_DIR = openocd/
PROGRAMMER_COMMON = -f $(PRGMR_FILES_DIR)segger.cfg#load hardware
PROGRAMMER_COMMON += -f $(PRGMR_FILES_DIR)$(CHIP).cfg#load chip description

FLASH_FLAGS = $(PROGRAMMER_COMMON)
FLASH_FLAGS += -f $(PRGMR_FILES_DIR)flash_program_prep.cfg
FLASH_FLAGS += -c "program $(TARGET).elf verify reset exit"

DEBUGGER_FILES_DIR = gdb/
DEBUG_FLAGS = -iex "target extended-remote localhost:3333"
DEBUG_FLAGS += -ex "layout regs"
DEBUG_FLAGS += -ex "source $(DEBUGGER_FILES_DIR)main_breakpoints.txt"

###############################################################################
## Rules

all: $(OBJECTS) $(TARGET).elf

## Assembler
%.o:%.s
	$(AS) $(ASFLAGS) $< -o $@

%.o:%.S
	$(CC) -x assembler-with-cpp -c $(CFLAGS) $< >$@

## Linker
$(TARGET).elf: $(OBJECTS)
	 $(CC) $(LDFLAGS) $(OBJECTS) $(LINKONLYOBJECTS)  -T./$(LINKER_FILE) -o $@
	
## Simple Checking 
%.lss: $(TARGET).elf
	$(OBJD) -h -S $< >$@

disamb: $(TARGET).elf
	$(OBJD) --disassemble $<

disamb_o: $(OBJECTS)
	$(OBJD) --disassemble $^

symb: $(TARGET).elf
	$(NM) --numeric-sort $<

symb_o: $(OBJECTS)
	$(NM) --numeric-sort $^

## Programming & Debugging  
flash: $(TARGET).elf
	$(PROGRAMMER) $(FLASH_FLAGS)


fnl: $(TARGET).elf
	$(PROGRAMMER) $(PROGRAMMER_COMMON) \
	-f $(PRGMR_FILES_DIR)flash_program_prep.cfg \
	-c "program $(TARGET).elf verify"

serve:
	$(PROGRAMMER) $(PROGRAMMER_COMMON)

talk: 
	telnet localhost 4444

debug: $(TARGET).elf
	$(DEBUGGER) $(TARGET).elf $(DEBUG_FLAGS)

## Utilities
clean:
	-rm -rf $(OBJECTS) $(TARGET).elf $(TARGET).map

# "resource check" - to get list of items make expects to find
res_check:
	@echo $(SOURCES)
	@echo $(OBJECTS)

## Phony List 
.PHONY: all talk debug res_check clean serve fnl flash symb disamb symb_o disamb_o

This article is part of a series.