By Martin Ribelotta | September 30, 2020
In the previous articles, you learned how to write initialization code and put it in the correct place in the processor memory. Now you will learn how to generate the proper binary for send to ROM and will see various tricks to simplify your live with gnu make and Makefile.
Reviewing what has been done up to this point
You have done a C source to startup your cortex-m processor in vendor neutral way, you have a linker script to place all code and data in particular areas of your memory and little platform dependent memory layout specification file.
Now you need to compiler all and put it in a binary file suitable to write in memory (flash, rom, eeprom or any non volatile memory from your device)
So far, you have this structure of files:
with these contents:
main.c
: A simple empty main programint main() { return 0; }
start.c
: The previous made startup codelink.ld
: The file created in previous article whit the instructions to place code in memorymemory.ld
: Device dependent memory map used bylink.ld
The simple way to do this is invoking the compiler directly:
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o src/main.o -c src/main.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o src/start.o -c src/start.c
arm-none-eabi-gcc -o firmware.elf -nostartfiles -mcpu=cortex-m3 -mthumb src/main.o src/start.o -lnosys -Llib -Tlink.ld
And you can write better bash script with variables:
BINARY=firmware.elf
CC=arm-none-eabi-gcc
LD=arm-none-eabi-gcc
ARCH=-mcpu=cortex-m3 -mthumb
CFLAGS=$ARCH -Og -g3
LDFLAGS=$ARCH -nostartfiles -lnosys -Llib -Tlink.ld
$CC $CFLAGS -o src/main.o -c src/main.c
$CC $CFLAGS -o src/start.o -c src/start.c
$LD -o $BINARY src/main.o src/start.o $LDFLAGS
But this not scale well with large project because build all files all times.
Makefile to the rescue
For this purpose, a better script tool is make, an ancient utility from old unix systems in the category of “dependency management tool”. Precisely, “make” reads a file with rules between target and dependencies and executes the specified actions to obtain each target as long as the dependencies are newer than the existing objective (or that objective does not exist)
- Read makefile file, and find first dependency in the form of:
target: dependency other-dependency more-dependencies <-- tab --> command to generate "target" <-- tab --> other command to generate the target <-- tab --> More commands to generate the target
The
<-- tab -->
is character 0x08 orTAB
. This is very important and major smart editors take care with this… you not replaceTAB
by spaces, spaces produce malformed Makefile and the program fail with an error if encounter other character instead of TABs as first char in the command list. - Check if the dependency exist and not have any dependency
- If the dependency not exist, find a rule to create it and perform the step 2 in this dependency
- If the dependency exist, and is newer than the target perform the commands to obtain the target
- If the target exist and is newer than any dependency end with success
As you see, the make behavior is performed in tree over all dependencies and sub-dependencies for all targets.
For our example, the file dependency tree is like this:
With this, you can write our first Makefile:
all: firmware.elf
src/main.o: src/main.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o src/main.o -c src/main.c
src/start.o: src/start.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o src/start.o -c src/start.c
firmware.elf: src/main.o src/start.o
arm-none-eabi-gcc -o firmware.elf -nostartfiles -mcpu=cortex-m3 -mthumb src/main.o src/start.o -lnosys -Llib -Tlink.ld
clean:
rm src/main.o src/start.o firmware.elf
Now, when you modify main.c
only this file is recompiled and all objects was
linked again. The way to recompile all is delete all *.o
files and the *.elf
file. This is performed with the target clean
.
A cool thing of make, is the existence of PHONY
targets. Target with
convenient name but no represent any files, only a dependency step, like all
target that acts as an alias for real target, or only a convenient name to
perform some actions like clean
that remove all generated files.
The way to stop the clean
target to work is create an empty file with this
name (clean
, without extension) and, when you try to run make clean
the
make, return without actions because the “file” is already created and
actualized.
To prevent this, all “phony” rules need to mark as PHONY using this line:
.PHONY: all clean
After this, the existence of file with name of all
or clean
is ignored and
the commands if this rules is executed ever.
Use wildcards and variables to simplify the makefile
Like shell script, the Makefile file, can be simplified with variables, but unlike bash script you can use wildcards to match multiple files with one rule (yeeeeeessss, bash can do this using the respective wildcards but not without specify a complex script to track dependencies)
TARGET=firmware.elf
OBJECTS=src/main.o src/start.o
CROSS=arm-none-eabi-
CC=$(CROSS)gcc
LD=$(CROSS)gcc
ARCH_FLAGS=-mcpu=cortex-m3 -mthumb
CFLAGS=$(ARCH_FLAGS) -Og -g3
LDFLAGS=$(ARCH_FLAGS) -nostartfiles -lnosys -Llib -Tlink.ld
all: $(TARGET)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET): $(OBJECTS)
$(LD) -o $@ $^ $(LDFLAGS)
clean:
rm $(OBJECTS) $(TARGET)
.PHONY: all clean
In this version, we can take advantages of wildcard to specify rules.
The construction
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
Is specified for all pair of *.o
files that depends on corresponding *.c
file. The tokens $@
and $<
is the form to refer the target and the
dependency list in the command list.
This is effectively expanded to:
src/main.o: src/main.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o src/main.o -c src/main.c
src/start.o: src/start.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o src/start.o -c src/start.c
A disadvantage of this script is that you must specify the name of the *.o files instead of your sources *.c. This can be solved using some make functions…
Make make great (again?)
Other tricks of make is the ability to call some processing functions inside the script like file find with sh wildcards, string substitutions and other happy and funny eggs:
TARGET=firmware.elf
SOURCES=$(wildcard src/*.c)
LIBPATH=lib
LIBS=nosys
LDSCRIPTS=link.ld
OBJECTS=$(patsubst %.c, %.o, $(SOURCES))
CROSS=arm-none-eabi-
CC=$(CROSS)gcc
LD=$(CROSS)gcc
ARCH_FLAGS=-mcpu=cortex-m3 -mthumb
CFLAGS=$(ARCH_FLAGS) -Og -g3
LDFLAGS=$(ARCH_FLAGS)
LDFLAGS+=-nostartfiles
LDFLAGS+=$(addprefix -l, $(LIBS))
LDFLAGS+=$(addprefix -L, $(LIBPATH))
LDFLAGS+=$(addprefix -T, $(LDSCRIPTS))
all: $(TARGET)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET): $(OBJECTS)
$(LD) -o $@ $^ $(LDFLAGS)
clean:
rm -fr $(OBJECTS) $(TARGET)
.PHONY: all clean
The first interesting function is wildcard
, this perform like ls
in bash,
and make replace this occurrence with a list of existing file names that
match the wildcard specified:
SOURCES=$(wildcard src/*.c)
Is transformed by make in to:
SOURCES=src/main.c src/start.c
And, with patsubst
you can obtain the corresponding names of object files:
OBJECTS=$(patsubst %.c, %.o, $(SOURCES))
Is substituted by make for:
OBJECTS=src/main.o src/start.o
And yes, you can use this as a list of final target dependency.
Other interesting function is addprefix
that is used to improve libraries,
library path, and linker script handling:
LIBPATH=lib
LIBS=nosys
LDSCRIPTS=link.ld
...
LDFLAGS=$(ARCH_FLAGS)
LDFLAGS+=-nostartfiles
LDFLAGS+=$(addprefix -l, $(LIBS))
LDFLAGS+=$(addprefix -L, $(LIBPATH))
LDFLAGS+=$(addprefix -T, $(LDSCRIPTS))
In this case, when make found this $(addprefix -l, $(LIBS))
, append -l
to
all wolds in $(LIBS)
variable. If LIBS contains nosys
this is expandend to:
LDFLAGS+=-lnosys
If in addition, LIBS contains nosys nano m
this expands to:
LDFLAGS+=-lnosys -lnano -lm
Linking all libraries in this binary.
Moving generated files to own directory
If you see, the compilation process generate an *.o
file for every *.c
file.
This increase rapidly the garbage in the source tree when you compile a project
and the only form to maintain the code clean is to track all *.o
files and
remove this in clean phase.
Is desirable to move all generated files to own directory in order to maintain the source code clean in any build conditions.
To do this, you need some magic around makefile:
TARGET=firmware
SOURCES=$(wildcard src/*.c)
LIBPATH=lib
LIBS=nosys
LDSCRIPTS=link.ld
OUT=out
OBJECTS=$(addprefix $(OUT)/, $(patsubst %.c, %.o, $(SOURCES)))
TARGET_ELF=$(addsuffix .elf, $(addprefix $(OUT)/, $(TARGET)))
CROSS=arm-none-eabi-
CC=$(CROSS)gcc
LD=$(CROSS)gcc
ARCH_FLAGS=-mcpu=cortex-m3 -mthumb
CFLAGS=$(ARCH_FLAGS) -Og -g3
LDFLAGS=$(ARCH_FLAGS)
LDFLAGS+=-nostartfiles
LDFLAGS+=$(addprefix -l, $(LIBS))
LDFLAGS+=$(addprefix -L, $(LIBPATH))
LDFLAGS+=$(addprefix -T, $(LDSCRIPTS))
all: $(TARGET_ELF)
$(OUT)/%.o: %.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET_ELF): $(OBJECTS)
mkdir -p $(dir $@)
$(LD) -o $@ $^ $(LDFLAGS)
clean:
rm -fr $(OUT)
.PHONY: all clean
Now, the target is not specified as an *.elf file, instread of it, TARGET is now a generic name suitable to convert to an *.elf, *.bin, *.hex or whatever you need:
TARGET=firmware
In addition to this, the function addsuffix
is introduced, this is analog
to addprefix
but prepends the text instead of append:
OBJECTS=$(addprefix $(OUT)/, $(patsubst %.c, %.o, $(SOURCES)))
TARGET_ELF=$(addsuffix .elf, $(addprefix $(OUT)/, $(TARGET)))
In the rule section, you need to specify $(OUT)/%.o
as a wildcard target
and take care with directory creation in the $(OUT) directory.
By example, if you try to compile src/modules/mod1/mymod.c
the compile
process need to create out/src/modules/mod1/
to leave mymod.o
result. This
is the purpose of the line mkdir -p $(dir $@)
create directory obtained by
$(dir ...)
function from the target in secure way (flag -p). If the directory
exist, not fail, and if the subdirectories not exist create all parts needed.
all: $(TARGET_ELF)
$(OUT)/%.o: %.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET_ELF): $(OBJECTS)
mkdir -p $(dir $@)
$(LD) -o $@ $^ $(LDFLAGS)
clean:
rm -fr $(OUT)
.PHONY: all clean
At the end, the clean
target is substantially simplified, now only need to
remove $(OUT)
directory.
Sound of (make) silence
The output produced by make in the terminal is very verbose, this includes the complete command with all parameters, extra commands like mkdir and others output like directory changes.
It is very inconvenient when you only need to view the progress of the process (hey! good name for a 60’s music)
To avoid this, if you prepends @
to any command it is silently executed but
the command not appear in your terminal.
With this, the Makefile part of rules can change like that:
$(OUT)/%.o: %.c
@echo CC $<
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET_ELF): $(OBJECTS)
@echo LD $@
@mkdir -p $(dir $@)
@$(LD) -o $@ $^ $(LDFLAGS)
clean:
@echo CLEAN
@rm -fr $(OUT)
.PHONY: all clean
In addition, you can use now, the command echo
to show a custom message to the
console indicating the actual phase of compiler…
Now, the output of makefile is like this:
$> make
CC src/main.c
CC src/start.c
LD out/firmware.elf
Much easy to seek the output!
Sorry no sorry, I need the verbosity back
In some cases, the silent behavior is a pain in the ass, specially when you are debugging the compilation process and need to known specifically what is the command executed in reality.
To maintain the both word, you need to use some variable expansion trick:
TARGET=firmware
SOURCES=$(wildcard src/*.c)
LIBPATH=lib
LIBS=nosys
LDSCRIPTS=link.ld
OUT=out
VERBOSE=n # <-- Addition here!
...
ifeq ($(VERBOSE),y)
Q=
else
Q=@
endif
all: $(TARGET_ELF)
$(OUT)/%.o: %.c
@echo CC $<
@mkdir -p $(dir $@)
$(Q)$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET_ELF): $(OBJECTS)
@echo LD $@
@mkdir -p $(dir $@)
$(Q)$(LD) -o $@ $^ $(LDFLAGS)
clean:
@echo CLEAN
$(Q)rm -fr $(OUT)
.PHONY: all clean
Now, the selectable lines is pretended with $(Q)
variable (award for my name
originality) and this variable is assigned/not-assigned with @
character
depending of the contents of VERBOSE
variable (y
or whatever, by default
the variable is n
)
To regain the verbose behavior your only need to override the variable in the command line:
Silent output:
$> make all
CC src/main.c
CC src/start.c
LD out/firmware.elf
Verbose output:
$> make all VERBOSE=y
CC src/main.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o out/src/main.o -c src/main.c
CC src/start.c
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -Og -g3 -o out/src/start.o -c src/start.c
LD out/firmware.elf
arm-none-eabi-gcc -o out/firmware.elf out/src/vectors_vendor.o out/src/main.o out/src/start.o -mcpu=cortex-m3 -mthumb -nostartfiles -lnosys -Llib -Tlink.ld
Now, the code is contained in *.elf
file, this is not directly the image in
memory but contains the code sparse in various sections and is suitable to
program the processor using tools like openocd or gdb. This is sufficient to run
simple program in the MCU but lack the ability to handle the platform specific
interrupts (like uart tx/rx, dma, or other peripherals)
This is time to probe the firmware in real hardware
These cheap and blue trinket…
A good cortex-m platform to test the code is the omnipresent bluepill board, a little pcb in dip40 containing a cheap stm32f103c8t6 with 64K of flash and 20K of ram, 8MHz/32.768KHz crystals, 3.3v regulator and usb connector attached to the MCU. This board can be obtained in eBay for around 2usd.
The other instrument required is the programmer. In the same line to extreme cheap tools, the choice is stlink v2 in chinese version (with questionable legality about STMicroelectronics intellectual property), a very cheap (2.5usd in eBay) programmer for cortex-m processor.
You can see the connections needed to program the bluepill at next:
You can refer to this blog for a more detailed connection instruction and some arduino-like playground.
Now, you are ready to program these software into the bluepill, the previous created linker script is configured to put code in stm32f103 flash (0x08000000) and data in ram (0x20000000).
For program it, our preference is the openocd utility. This program enable the access to jtag/swd with many transports (dongles) and many targets (processors).
Openocd work as a proxy between the debugger (GDB) and the jtag/swd board (stlink). This translate the commands like start/stop/pause/load into interface specific commands (in this case stlink commands over USB). The dongle, translate this to SWD/JTAG and perform operations to write flash, stop the processor, read registers or memory, etc.
Additionally, openocd can execute TCL scripts to automatize some operations and treads without the intervention of debugger.
Starting openocd
For call openocd you need to specify a one or more configuration scripts and, additionally, zero or more commands to execute.
openocd -f <stript> -f <another script> ... -c "command" -c "other command"
The default installation of openocd came with many scripts for specify common boards, communication interfaces, processors and other utilities:
openocd/scripts/
├── bitsbytes.tcl
├── board
│ ├── adapteva_parallella1.cfg
│ ├── atmel_samv71_xplained_ultra.cfg
│ ├── minispartan6.cfg
│ ├── nxp_imx7sabre.cfg
...
│ ├── st_nucleo_l4.cfg
│ ├── ti_beaglebone.cfg
│ ├── ti_tmdx570ls31usb.cfg
│ ├── xmc4800-relax.cfg
│ ├── xmos_xk-xac-xa8_arm.cfg
│ └── zy1000.cfg
├── interface
│ ├── altera-usb-blaster.cfg
│ ├── arm-jtag-ew.cfg
│ ├── buspirate.cfg
│ ├── cmsis-dap.cfg
│ ├── ft232r.cfg
│ ├── ftdi
│ │ ├── digilent_jtag_hs3.cfg
│ │ ├── flossjtag.cfg
...
│ │ ├── openocd-usb.cfg
│ │ └── openocd-usb-hs.cfg
│ ├── imx-native.cfg
│ ├── jlink.cfg
│ ├── jtag_vpi.cfg
...
│ ├── stlink-v2.cfg
│ ├── vsllink.cfg
│ └── xds110.cfg
└── target
├── at91sam3ax_4x.cfg
├── atsamv.cfg
├── efm32.cfg
├── fm3.cfg
├── imx6.cfg
├── imx8m.cfg
...
├── ti_rm4x.cfg
├── ti_tms570.cfg
├── xmc1xxx.cfg
├── xmc4xxx.cfg
├── xmos_xs1-xau8a-10_arm.cfg
└── zynq_7000.cfg
By example:
openocd -f board/stm32f4discovery.cfg
Start openocd for pre configured STM32F4 discovery board. The *.cfg
file
configure the interface in the board, select the MCU and start server for
debugger and telnet server for commands.
Some boards not specify any debug adapter and need to specify before the board file:
openocd -f interface/cmsis-dap.cfg -f board/atmel_samv71_xplained_ultra.cfg
This start connection for SAMv71 XPlained Pro with the integrated CMSIS-DAP debugger.
If your board is not supported, but your CPU is listed in target/
directory
you can specify the required debug interface and required processor directly:
openocd -f interface/cmsis-dap.cfg -f target/stm32h7x.cfg
This start connection over CMSIS-DAP interface to an unspecified stm32h7 CPU in unknown board.
For a generic stm32f103 board connected over an stlink dongle you need to do:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg
If your board is correctly attached you can see a message like this:
xPack OpenOCD, 64-bit Open On-Chip Debugger 0.10.0+dev (2019-07-17-11:25)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Info : STLINK V2J29S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.239604
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections
Now, the openocd is listening the debugger like GDB from a socket in localhost
on port 3333
(this may change from script or via commands) and for telnet
commands in port 4444
For test it you can open an other terminal and execute telnet using:
telnet localhost 4444
This start a connection to openocd and provide a promt to send commands.
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
>
To list connected device you do:
> targets
TargetName Type Endian TapName State
-- ------------------ ---------- ------ ------------------ ------------
0* stm32f1x.cpu hla_target little stm32f1x.cpu running
You may stop the current attached target CPU with:
> reset halt
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x080002a4 msp: 0x20005000
And list the register CPU state with:
===== arm v7m registers
(0) r0 (/32): 0x00000001
(1) r1 (/32): 0x20004FA4
(2) r2 (/32): 0x20000138
(3) r3 (/32): 0x00000003
(4) r4 (/32): 0x00000000
(5) r5 (/32): 0x00000001
(6) r6 (/32): 0x00000001
(7) r7 (/32): 0x00000C23
(8) r8 (/32): 0x20004FA4
(9) r9 (/32): 0x08001FF5
(10) r10 (/32): 0xFDF734DF
(11) r11 (/32): 0xF3DFDFB7
(12) r12 (/32): 0xFFEFFF9F
(13) sp (/32): 0x20005000
(14) lr (/32): 0xFFFFFFFF
(15) pc (/32): 0x080002A4
(16) xPSR (/32): 0x01000000
(17) msp (/32): 0x20005000
(18) psp (/32): 0x5950F7BC
(19) primask (/1): 0x00
(20) basepri (/8): 0x00
(21) faultmask (/1): 0x00
(22) control (/2): 0x00
===== Cortex-M DWT registers
The program have varios predefined scripts to do some rutinary things with the
boards. The most usefull is the command program
that encapsulate flash unlock
(if necesary), flash erase, file handling (bin, hex, s19, elf, etc), cpu reset
and openocd exit at finishing.
From the help:
program <filename> [address] [verify] [reset] [exit]
write an image to flash, address is only required for binary images.
verify, reset, exit are optional (command valid any time)
By example you can do:
program out/firmware.elf verify reset
And this program, verify and reset (and run) the program out/firmware.elf
Puting all together in a single command line
Yes, funny things are funny but open two terminals to do the program is really a pain.
Ideally you can do all from an one command in a single script (ideally: Makefile)
For this you can instruct to openocd for execute commands after the script read
with the argument -c "command"
. With it, you can specify the program algoritm
in one line:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "init" \
-c "reset halt" \
-c "program out/firmware.elf verify reset exit"
The first command init
is to force initialization of all openocd subsystems
before to execute any other command (by default first read all config file,
execute all commands and next execute automatically initialization of interfaces
and target)
Secondly, you need to stop the processor (if not are stoped) due to prevent
multiple access to the memory bus for flash programming. The command reset halt
do it perfectly (profit, exist reset run
and reset init
both documented
in opencod manual)
Finally the command program
do all the magic taking the *.elf file (you may
specify a bin, hex or s19 file) and write it in flash, next verify the
correctness of data write, reset the processor (putting it running) and terminate
openocd session. Hey, very convenient!
Integrating in Makefile
You can get the last command and insert in Makefile with little changes:
program: $(TARGET_ELF)
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "init" \
-c "reset halt" \
-c "program $(TARGET_ELF) verify reset exit"
You can be more generic put all -f
files and -c
commands in a variables and
using $(addprefix)
to build command line, or use a specific variable to store
openocd name (this enable to change the openocd executable from command line)
like this:
OOCD=openocd
OOCD_FLAGS:=-f interface/stlink.cfg -f target/stm32f1x.cfg
OOCD_CMDS:=-c "init"
OOCD_CMDS+=-c "reset halt"
OOCD_CMDS+=-c "program $(TARGET_ELF) verify reset exit"
program: $(TARGET_ELF)
$(Q)$(OOCD) $(OOCD_FLAGS) $(OOCD_CMDS)
But is only suggar for more confortable Makefile functionalities ¯\_(ツ)_/¯
,
the real thing to do next is put a real work in our main program!!!
See you next week!
Apendix. Clean maded Makefile
With comments…
# Use this name to select the final filename of your *.elf, *.bin, *.hex etc
TARGET=firmware
# Add your source files using wildcard or directly putting the file path
SOURCES=$(wildcard src/*.c)
# Add library path for find *.ld and *.a files
LIBPATH=lib
# Add libraries (in any library path location) or compiler bundled library
LIBS=nosys
# Specify your linker script here
LDSCRIPTS=link.ld
# And the output directory of your object files
OUT=out
# translate all *.c in $(OUT)/*.o
OBJECTS=$(addprefix $(OUT)/, $(patsubst %.c, %.o, $(SOURCES)))
# Compose final ELF file name
TARGET_ELF=$(addsuffix .elf, $(addprefix $(OUT)/, $(TARGET)))
# CROSS provides flexibility to select your toolchain prefix
CROSS=arm-none-eabi-
CC=$(CROSS)gcc
LD=$(CROSS)gcc
# Add the architecture dependent flags here
ARCH_FLAGS=-mcpu=cortex-m3 -mthumb
# And other C compiler flags here
CFLAGS=$(ARCH_FLAGS) -Og -g3
# Build linker flags using $(ARCH_FLAGS) first
LDFLAGS=$(ARCH_FLAGS)
# Prevent the use of compiler provided start files. You provide user initialization
LDFLAGS+=-nostartfiles
# Extra library path
LDFLAGS+=$(addprefix -L, $(LIBPATH))
# Build extra flags with contents of LIBS LIBPATH and LDSCRIPTS variables
LDFLAGS+=$(addprefix -l, $(LIBS))
# Linker scripts
LDFLAGS+=$(addprefix -T, $(LDSCRIPTS))
# Use verbose or quiet build
ifeq ($(VERBOSE),y)
Q=
else
Q=@
endif
all: $(TARGET_ELF)
$(OUT)/%.o: %.c
@echo CC $<
@mkdir -p $(dir $@)
$(Q)$(CC) $(CFLAGS) -o $@ -c $<
$(TARGET_ELF): $(OBJECTS)
@echo LD $@
@mkdir -p $(dir $@)
$(Q)$(LD) -o $@ $^ $(LDFLAGS)
clean:
@echo CLEAN
$(Q)rm -fr $(OUT)
.PHONY: all clean
OOCD=openocd
OOCD_FLAGS:=-f interface/stlink.cfg -f target/stm32f1x.cfg
OOCD_CMDS:=-c "init"
OOCD_CMDS+=-c "reset halt"
OOCD_CMDS+=-c "program $(TARGET_ELF) verify reset exit"
program: $(TARGET_ELF)
$(Q)$(OOCD) $(OOCD_FLAGS) $(OOCD_CMDS)