First, -specs=XYZ.specs asks the linker to look at the file XYZ.specs for additional linker library info. For example, here are some of the lines in the nano.specs file
%rename link nano_link %rename link_gcc_c_sequence nano_link_gcc_c_sequence ... *cpp_unique_options: -isystem =/include/newlib-nano %(nano_cpp_unique_options)
*nano_libc: -lc_nano ...
Basically an internal language to describe actions and what files to include. The content of the specs file would depend on a number of factors including compiler, MCU, etc.
For nano.specs, the main takeaway is that it includes the nanolib.
Moving on to nanolib vs. newlib. The short of it is that newlib is the libc implementation for embedded systems (vs. "regular" glibc for big machines like Linux), and nanolib is a further optimized library for embedded system based on microcontrollers. Nanolib even comes with "small" printf by default to further reduce the output program size. For a bit more background, I have written a blog post here. https://imagecraft.com/blog/2019/04/embedded-gcc-libraries-newlib-vs-nanolib/
To fully answer all your questions (CMSIS, how does GCC compile and link...), it would require a lot more than a simple answer that can fit in here. I would recommend looking up "GNU Embedded ARM" toolchain, and start with its documentation. The basic operations are simple: the compiler compile your files into object files (.o) and the linker combines them with libc into a working program. The major difficulty in building programs for an ARM MCU is which vendor libraries to use to access the peripherals. CMSIS is a way to solve the "generic" ARM MCU requirements, but most programs require much more than that.
STM32 is particular good that ST provides many libraries and even a GUI tool (CubeMX), but is also exceptionally bad that there are no less than 3 major versions (Standard Peripheral Library or SPL, HAL, and Low Level LL) of the libraries, and according to some people, are all broken in many ways.
Answer from Richard at ImageCraft on Stack OverflowWorked for me on Ubuntu 16.04:
sudo apt install gdb-arm-none-eabi
I had the same issue with Ubuntu 18.04. To install “gcc-arm-none-eabi” on Ubuntu 18.04.
sudo apt-get install gcc-arm-none-eabi
Using this command system install all binary into /usr/bin folder. But Some binaries are not found here. so, I am using its alternative way as below. it's working for me.
If you want to use below arm-none-eabi utility.
arm-none-eabi-gdb
arm-none-eabi-as
arm-none-eabi-objcopy
Download the ARM-GCC toolchain from gnu-mcu-eclipse/arm-none-eabi-gcc
I have downloaded "gnu-mcu-eclipse-arm-none-eabi-gcc-6.3.1-1.1-20180331-0618-centos64" for my x64 System.
After downloaded successfully Extract the compressed file. Go to
/gnu-mcu-eclipse-arm-none-eabi-gcc-6.3.1-1.1-20180331-0618-centos64/gnu-mcu-eclipse/arm-none-eabi-gcc/6.3.1-1.1-20180331-0618/bin
Copy the GDB and objcopy into /usr/bin Directory
sudo cp arm-none-eabi-gdb /usr/bin/
sudo cp arm-none-eabi-objcopy /usr/bin/
After copy you can use the GCC and GDB.
Your main() terminates. What happens next depends on what follows the __main call in startup_stm32f100xb.s; possibly a forced reset, or an endless loop (until watchdog reset, if enables).
In embedded systems, it is not normal for main() to return.
The "Enable HSI" is perhaps unnecessary or even incorrect. The core clock is HSI by default. A clock must already have been established for the code to be executing at all, and enabling the HSI will either not have any effect, or disable a previously established clock. The system clocking will have been set up in system_stm32f10x.c, and if you have specific clocking requirements, you need to modify that to suit your board and/or application. Using the HSI directly allows 8 MHz operation, feeding it to the PLL supports up-to 64 MHz, and the chip will run at up to 72 MHz when the PLL is fed from an external 4-16 MHz oscillator (HSE).
If you are not establishing the clock correctly in system_stm32f10x.c (or leaving it running HSI-direct), then it is possible that the execution is stalled in SystemInit() in system_stm32f10x.c waiting for PLL lock, or if you have it really wrong is not running at all.
To be working on any bare-metal processor at the board-bring-up level, you should ideally have a hardware debugger (JTAG/SWI), so you can use your tool-chain's debugger to determine exactly what your code is doing and where it is falling over. You can at least determine whether it is even running as far as main(). There is a significant initialisation implemented by startup_stm32f100xb.s and system_stm32f10x.c before main() run. The latter normally requires modification to suit your target.
Here is a complete example derived from one that blinks pc13 (untested at pc8, but worked at pc13, can test it if you have issues)
flash.s
@.cpu cortex-m0
@.cpu cortex-m3
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl dummy
dummy:
bx lr
blinker01.c
void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
#define GPIOCBASE 0x40011000
#define RCCBASE 0x40021000
int notmain ( void )
{
unsigned int ra;
ra=GET32(RCCBASE+0x18);
ra|=1<<4; //enable port c
PUT32(RCCBASE+0x18,ra);
//config
ra=GET32(GPIOCBASE+0x04);
ra&=~(0xF<<0); //PC8
ra|= (0x1<<0); //PC8
PUT32(GPIOCBASE+0x04,ra);
while(1)
{
PUT32(GPIOCBASE+0x10,1<<(8+0));
for(ra=0;ra<200000;ra++) dummy(ra);
PUT32(GPIOCBASE+0x10,1<<(8+16));
for(ra=0;ra<200000;ra++) dummy(ra);
}
return(0);
}
flash.ld
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
Makefile
ARMGNU = arm-none-eabi
#ARMGNU = arm-linux-gnueabi
AOPS = --warn --fatal-warnings -mcpu=cortex-m0
AOPS3 = --warn --fatal-warnings -mcpu=cortex-m3
COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m0 -march=armv6-m
COPS32 = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m3 -march=armv7-m
all : blinker01.bin
clean:
rm -f *.bin
rm -f *.o
rm -f *.elf
rm -f *.list
flash.o : flash.s
$(ARMGNU)-as $(AOPS) flash.s -o flash.o
blinker01.o : blinker01.c
$(ARMGNU)-gcc $(COPS) -mthumb -c blinker01.c -o blinker01.o
blinker01.bin : flash.ld flash.o blinker01.o
$(ARMGNU)-ld -o blinker01.elf -T flash.ld flash.o blinker01.o
$(ARMGNU)-objdump -D blinker01.elf > blinker01.list
$(ARMGNU)-objcopy blinker01.elf blinker01.bin -O binary
or
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m0 -march=armv6-m -mthumb -c blinker01.c -o blinker01.o
arm-none-eabi-ld -o blinker01.elf -T flash.ld flash.o blinker01.o
arm-none-eabi-objdump -D blinker01.elf > blinker01.list
arm-none-eabi-objcopy blinker01.elf blinker01.bin -O binary
You dont need to mess with the clock initially it comes out of reset using the internal oscillator. So unless you have your own firmware that sets it to something else you wont need to set it to HSI. A clock init would be needed to change it to use an external clock and/or to use the PLL.
GPIOC->CRH &= (uint32_t)(0x00000002);
This changes the register from 0x44444444 to 0x44444444 (sorry see edit below). which is another way of saying you didnt do anything. You want 0x44444442 for the 2mhz if you want to read-modify-write which is cleaner, but not necessary since we know the power on reset state you should do Like I did above read the value mask off the bits to zero then set the bits you want, the whole pattern. Or you can just shove the 0x44444442 or 0x44444441 or 0x44444443.
had you done an or equal then it would have made 0x44444446 which is an open drain output. So if there is a pull up then it would light the led perhaps, depends, but you would want to use open drain for sinking more than sourcing. Which end of the led is the gpio pin connected to do you need to source it or sink it to turn it on? You set the output to 1 to set the output high, which on open drain just means dont sink. Although for an input setting odr pulls it high for input pull up/down, but as an open drain output not sure if setting odr pulls it up. easier to try push-pull first and if you just want it on then BSRR makes it easy to set or reset an individual pin in the port rather than touching the whole port with odr.
Open Drain Mode: A “0” in the Output register activates the N-MOS while a “1” in the Output register leaves the port in Hi-Z. (the P-MOS is never activated)
So first step change your code to set the port to a push-pull output, and then if you want to really be from scratch you can replace the rest of the code you borrowed and own all of it. Dont have to use the put32/get32 assembly I use, I have many reasons and tons of experience but that is the beauty of bare metal, do your own thing, you can use the volatile pointer approach as well which is likely what the header files you linked do, and simplify the bootstrap. If you make sure that your entry point function never exits and you dont rely on .data nor .bss then you can reduce your bootstrap to
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word main
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
hang: b hang
Or define a C function for the other vectors or a dumping ground for the unused ones. Note that I didnt put all the vectors in the bootstrap, depends on the chip and core but could be hundreds of vectors (up to 128 or 256)
And match the amount of ram you have for the stack pointer initialization value (0x20001000 in the example above).
EDIT, actually, sorry 0x44444444&=0x00000002 = 0x00000000, which sets all the ports to analog inputs. you want it to be 0x44444441 or 0x44444442 or 0x44444443