It'd make sense to do some toolchain clean up
This article is part of a series.
- Part 1: Hello LED on an AVR (ATtiny45) in C
- Part 2: How can I make programming an ARM chip as hard as possible?
- Part 3: How can I get this SAMD21E18 startup code a little sturdier?
- Part 4: It's ALIVE! (SAMD21E18A, Assembly, No SDK)
- Part 5: And now for 3 ways to set an internal pullup
- Part 6: This Article
- Part 7: Neat, switching to the ItsyBitsy just... works
My Makefile was a bit of a “get it done” hack. It still will be after this session, but a better one!
Goals
- clean up how makefile finds its sources in prep for switching chips
- clean up assembler options for .s vs .S
- break apart OpenOCD tasks
- make launching GDB easier
- automate some of the gdb tasks
- add the .PHONY
- clean up the comments (make headers, trailing whitespace matters)
- small misc improvements
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.
-
Note: The linker script called with the -T flag needs its full path. To see the default linker script that would be used without that flag add
-Wl,--verbose
to the linker flags. -
Note: to get arm-gcc to spit out the list of mcpu options use
arm-none-eabi-gcc --target-help -mcpu=nonsense
where “nonsense” can be anything invalid. from stackoverflow
Longer aside: I’ve been switching between :=
, =
and =?
for my assignments. Simplistically
:=
: “calculate this here, this is final”=
: “store the calculation and run it at every call site”.=?
: “if there isn’t an environment variable, use this”
::=
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:
.s
files are expected to be ready as is to be made into an object.S
files get extra preprocessing.
# 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
- https://www.gnu.org/software/make/manual/html_node/Makefile-Basics.html
- https://www.gnu.org/software/make/manual/html_node/Using-Variables.html
- https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
- https://www.gnu.org/software/make/manual/html_node/Flavors.html
- https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html
- https://www.gnu.org/software/make/manual/html_node/Implicit-Rules.html
- https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html
- https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
- https://make.mad-scientist.net/papers/rules-of-makefiles/
- https://make.mad-scientist.net/papers/how-not-to-use-vpath/
- Makefile tag
gcc & as
- https://sourceware.org/binutils/docs-2.42/as/Invoking.html
- https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html
- https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
GDB
- https://sourceware.org/gdb/current/onlinedocs/gdb.html/Breakpoints.html
- https://sourceware.org/gdb/current/onlinedocs/gdb.html/Save-Breakpoints.html
- https://sourceware.org/gdb/current/onlinedocs/gdb.html/Command-Files.html
- https://sourceware.org/gdb/current/onlinedocs/gdb.html/Extending-GDB.html
- https://sourceware.org/gdb/current/onlinedocs/gdb.html/Sequences.html
- example gdbinit file: https://gist.github.com/CocoaBeans/1879270
- example gdbinit file that loads an extension written in python
Summary
- clean up how makefile finds its sources in prep for switching chips
- clean up assembler options for .s vs .S
- break apart openocd tasks
- make launching gdb easier
- automate some of the gdb tasks
- add the .PHONY
- clean up the comments (make headers, trailing whitespace matters)
- small misc improvements
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.
- Part 1: Hello LED on an AVR (ATtiny45) in C
- Part 2: How can I make programming an ARM chip as hard as possible?
- Part 3: How can I get this SAMD21E18 startup code a little sturdier?
- Part 4: It's ALIVE! (SAMD21E18A, Assembly, No SDK)
- Part 5: And now for 3 ways to set an internal pullup
- Part 6: This Article
- Part 7: Neat, switching to the ItsyBitsy just... works