The reset handler or startup code is the first piece of software to execute after a system reset.Typically, the reset handler is used for setting up configuration data for the C startup code (such as address range for stack and heap memories), which then branches into the C startup code.
actions done by startup code:
- load .data section fromm FLASH to RAM.
- Resrve .bss section in RAM.
- Initialize .bss with zeroes.
- Initialize the stack pointer (SP)
- Call main(Entry function) function. (it is not a must to call the entry function main but main is the common used name)
is a compiler where the target is different from the host.
is the set of binaries: compiler + linker + librarian + any other tools you need to produce the executable (+ shared libraries, etc) for the target.
it also contains binaries to debug the application of the target
it also comes with other binaries which help you to analyze the executable:
- Explain diff. sections of the executable file.
- Disassembly.
- Extract symbols and size.
- convert executable into another formats.
- provide c std libraries.
- compiler, assembler, linker: arm-none-eabi-gcc
- linker: arm-none-eabi-ld
- assembler: arm-none-eabi-as
- Elf file Analyser: arm-none-eabi-objdump
- Format converter: arm-none-eabi-objcopy
Note: object file is relocatable (it includes processor specific machine code with no absolute address). the true address will be assigned by the linker & linker script.
arm-none-eabi-gcc -c main.c -o main.o
Architecture must be selected.so we add -mcpu=cortex-m4
arm-none-eabi-gcc -c -mcpu=cortex-m4 main.c -o main.o
Identify if we work with mthARM instructions or Thumb instructions
arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb main.c -o main.o
-S to generate assembly file
arm-none-eabi-gcc -S -mcpu=cortex-m4 -mthumb main.c -o main.s
Add libraries too ,final:
arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -std=gnu11 main.c -o main.o
Instead of writing all these commands in the cmd (command window), create a makefile.
makefile:
CC = arm-none-eabi-gcc
MACK = cortex-m4
CFLAGS = -c -mcpu=$(MACK) -mthumb -std=gnu11
main.o: main.c
$(CC) $(CFLAGS) $^ -o $@
- resolve all undefined symbols of different object files
- merge similar sections of different object files
Analysing file:
arm-none-eabi-objdump
-h, --[section-]headers Display the contents of the section headers
arm-none-eabi-objdump main.o -h
to save it in file instead of showing it on the cmd window.
arm-none-eabi-objdump main.o -h >main_log1.s
-d, --disassemble Display assembler contents of executable sections
arm-none-eabi-objdump main.o -d >main_log2.s
-D, --disassemble-all Display assembler contents of all sections
arm-none-eabi-objdump main.o -D >main_log3.s
makefile:
CC = arm-none-eabi-gcc
MACK = cortex-m4
CFLAGS = -c -mcpu=$(MACK) -mthumb -std=gnu11
main.o: main.c
$(CC) $(CFLAGS) $^ -o $@
GPIO_prog.o: GPIO_prog.c
$(CC) $(CFLAGS) $^ -o $@
when loading program into memory.
Section memory address during loadtime. i.e. .data section in flash at loadtime.
when processor starts running the program.
Section physical memory address during Runtime. i.e. .data section has a copy in RAM at Runtime.
.data section located in Flash in loadtime but copied to RAM in runtime.
.bss doesnit have LMA.
you must reserve RAM space for .bss section by knowing its size and initial address in RAM ,then initialize those memory spaces with zeroes.
This is typically done by "Startup code".
Linker helps you to determine the final size of the bss section, so obtain the size information from the linkerscript symbols.
- responsiple for setting up the enviroment for the main user code to run.
- code written in startup file runs before main, so startup code calls main.
- Some parts of startup file is target dependent,like the vector table, it also may initialise the stack pointer , the way you access stack pointer is target dependent.
- sometimes startup code may initialise some coprocessors like FPU, for some floating point operations, FPU should be turned on, otherwise it would generate exception.
- Startup file takes care of vector table placement in code memory as required by the ARM-cortex-mx processor..
- Startup file may take care of stack reinitialisation.
- Startup file is responsiple of .data and .bss sections initialisation in main memory by properly copying .data from FLASH to RAM, proper initialisation of .bss section.
-
Create file: STM32_F446reStartup.c
- Create a vector table for your MC. vector tables are MC dependent.
- Write startup code which initialises .data and .bss sections in RAM.
- Call main.
Vector table is a collection of addresses, so we can create an array to hold MSP and handler's addresses.
This array can be of type uint32_t.
uint32_t Vectors[] = {
MSP,
ResetHandlerAddress,
NMI_HandlerAddress,
.
.
.
.
};
The huge number of implementations for all exceptions as it cause writing overhead and large code size. Also application writer shouldn't write the ISR implementations inside startup file.
- A common handler for all these exceptions and IRQs called alias
- These common handler will be as a weak function.
All exceptions and IRQs will have the same address of the common handler except the ones who were implemented in the application.
Alias: let the programmers to give alias name for a function.
For example:
/*DEFAULT HANDLER*/
void Default_Handler(void);
void Default_Handler(void)
{
while(1);
}
/*HANDLER IMPLEMENTAIONS*/
void NMI_Handler (void) __attribute__((weak,alias("Default_Handler")));
void HardFault_Handler (void) __attribute__((weak,alias("Default_Handler")));
void MemManage_Handler (void) __attribute__((weak,alias("Default_Handler")));
-
another problem which is: the vector table will be stored in .data section ,but it should be in certain address.
-
even if you think that you can initialise .data section in this certain address ,you don't sure if vector table is placed at the first address of .data section or not.
-
the solution is to create a section for the vector table ,say: .isr_section.
-
to do so, use:
__ attribute__((section(".isr_vector")))
Using GNU compiler Collection -> Extensions to the C Language Family -> Common Variable Attributes -> section ("section-name")
i.e.:
uint32_t Vectors[] __attribute__((section(".isr_vector"))) =
{
STACK_START,
(uint32_t)&Reset_Handler,
(uint32_t)&NMI_Handler ,
(uint32_t)&HardFault_Handler ,
(uint32_t)&MemManage_Handler ,
.
.
.
};
you better look @ STM32_F446reStratup.c.
update makefile:
CC = arm-none-eabi-gcc
MACK = cortex-m4
CFLAGS = -c -mcpu=$(MACK) -mthumb -std=gnu11
main.o: main.c
$(CC) $(CFLAGS) $^ -o $@
GPIO_prog.o: GPIO_prog.c
$(CC) $(CFLAGS) $^ -o $@
STM32F446reStartup.o: STM32F446reStartup.c
$(CC) $(CFLAGS) $^ -o $@
make STM32_F446reStartup.o
it will make an error
error: 'Reset_Handler' undeclared here (not in a function);
so update startup file and implement Reset_Handler
void Reset_Handler(void) __attribute__ ((naked, section("Random_section")));
void Reset_Handler(void)
{
/*1- Copy .data from FLASH to SRAM*/
/*2- Initialize .bss section in SRAM to zero*/
/*3- Call init function of standard library*/
/*4- Call main*/
}
To test the section placement:/p>
$ make STM32_F446reStartup.o
$ arm-none-eabi-objdump STM32_F446reStartup.o -h
Compiler by default doesn't raise warnings.
to activate warning use: -Wall
update CFLAGS in makefile.
CFLAGS = -c -mcpu=$(MACH) -std=gnu11 -mthumb -Wall
We want to make clean project by deleting .o and .elf files.
it is better to use linux commands
open git bash as it support linux commands:
$ rm
$ rm --help
-r, -R, --recursive remove directories and their contents recursively
-f, --force ignore nonexistent files and arguments, never prompt
$ rm -rf *.o *.elf
add this in makefile and run make @ git bash
CC = arm-none-eabi-gcc
MACK = cortex-m4
CFLAGS = -c -mcpu=$(MACK) -mthumb -std=gnu11
main.o: main.c
$(CC) $(CFLAGS) $^ -o $@
GPIO_prog.o: GPIO_prog.c
$(CC) $(CFLAGS) $^ -o $@
STM32F446reStartup.o: STM32F446reStartup.c
$(CC) $(CFLAGS) $^ -o $@
clean:
rm -rf *.o *.elf
- it is a text file which explains how different sections of the object files should be merged to create o/p file.
- Linker and locator combination assigns unique absolute addresses to different sections of the o/p file by referring to address information mentioned in the linkerscript.
- linker script also includes the code and data memory address and size information(in linker script you mention your memories and their addresses).
- linker scripts are written using the GNU linker command language.
- GNU linker script has the file extension of .ld.
- You must provide the linker with the linker script file @ the linking phase using -T option.
To write a Linker script you have to understand some commands, used to explain various info in the linker script.
- ENTRY
- MEMORY
- SECTIONS
- KEEP
- ALIGN
- AT>
-
Used to set the "entry point address" informationin the header of the final elf file generated.
-
In our case, the "Reset_Handler" is the entry point into the application, first code that wxwcutes right after reset.
-
Debugger uses this information to locate the first function to execute.
-
Not mandatory command to use but required when you debug the elf file using the debugger GDB.
syntax:
ENTRY(Reset_Hnadler)
Exercise:
Create Stm32F446_LS.ld
open it using any text editor for ex. NPP (notepad++).
and write the ENTRY command
Stm32F446_LS.ld
ENTRY(Reset_Handler)
-
This command allows you to describe diff. memories present in the target, and their start address and size info.
-
The linker uses information mentioned in this command to assign addresses to merged sections (relocation).
-
the information given under this command also helps the linker to calculate total code and data memory consumed so far, and throw an error message if data, code,.... areas can not fit inti available size..
-
By using MEMORY command, you can fine tune various memories available in your target and allow diff. sections to occupy diff. areas.
-
Typically one linkerscript has one MEMORY command.
syntax:
MEMORY { label_name(attributes) : ORIGIN = origin_address, LENGTH = size }
in side the body you can write many memory statement in this format {}.
R: READ SECTION
W: WRITE SECTION
X: Containing executable code.
attributes an be written capital or small letters. And can be combined together.
update the linkerscript file
Stm32F446_LS.ld
ENTRY(Reset_Handler)
MEMORY
{
FLASH(rx) : ORIGIN = 0x08000000 , LENGTH = 512k
SRAM(rwx) : ORIGIN = 0x20000000 , LENGTH = 128k
}
-
SECTION command is used to create diff. o/p sections in the final elf file generated.
-
By SECTION command you can instruct the linker how to merge the i/p section to yield an o/p section.
-
SECTION command also controls the order in which diff. o/p sections will appear in the final elf file.
-
By using SECTION command, you also mention the placement of a section in the memory region. Ex. you instruct the linker to place the .text section in the flash memory region which is described by the MEMORY command.
syntax:
SECTION { section_name : { }>(VMA) AT> (LMA) }
update the linkerscript file
Stm32F446_LS.ld
ENTRY(Reset_Handler)
MEMORY
{
FLASH(rx) : ORIGIN = 0x08000000 , LENGTH = 512k
SRAM(rwx) : ORIGIN = 0x20000000 , LENGTH = 128k
}
SECTION
{
.Text:
{
*(.isr_vector)
*(.text)
*(.rodata)
}>FLASH
.data:
{
*(.data)
}>SRAM AT> FLASH
.bss:
{
*(.bss)
}>SRAM
}
Vector table must be at the first address @ .text. As the H.W will execute from the first place @Flash.
To say merge .text section of input files use the wild card character.
* is the wild card character.
$ make clean
$ make STM32_F446reStartup.o
$ arm-none-eabi-gcc *.o -T Stm32F446_LS.ld -nostdlib -o final.elf
$ arm-none-eabi-objdump -h final.elf
update makefile
makefile
CC = arm-none-eabi-gcc
MACK = cortex-m4
CFLAGS = -c -mcpu=$(MACK) -mthumb -std=gnu11 -Wall
LDFLAGS = -T Stm32F446_LS.ld -nostdlib -Wl,-Map=final.map
main.o: main.c
$(CC) $(CFLAGS) $^ -o $@
GPIO_prog.o: GPIO_prog.c
$(CC) $(CFLAGS) $^ -o $@
RCC_Program.o: RCC_Program.c
$(CC) $(CFLAGS) $^ -o $@
STM32_F446reStartup.o: STM32_F446reStartup.c
$(CC) $(CFLAGS) $^ -o $@
final.elf: main.o GPIO_prog.o STM32_F446reStartup.o RCC_Program.o
$(CC) $(LDFLAGS) $^ -o $@
all: main.o GPIO_prog.o STM32_F446reStartup.o RCC_Program.o final.elf
clean:
rm -rf *.o *.elf
-
We can instruct the linker to create a special file called map file.by which we can analyze resources allocation and replacement in the memory.
-
There is a command to instruct the linker to create mapfile.
that is why we added :
-Wl,-Map=final.map
in LDFLAGS in makefile.
Analyse the symboltable generated by linker:
$ arm-none-eabi-nm final.elf
With redirection into a file:
$ arm-none-eabi-nm final.elf >symbolTable.txt
Even though the C/C++ variable and the linker symbol have the same name, they don't represent the same thing. In C, x is a variable name with the address &x and content Y. For linker symbols, x is an address, and that address contains the value Y.
-
Copy .data from FLASH to SRAM
"to do this we must get the size of .data section"
/*1- Copy .data from FLASH to SRAM*/ uint32_t size = (uint32_t)&_edata - (uint32_t)&_sdata ;"get the source and destination addresses"
/*start of .data section in Flash*/ uint8_t* SrcPtr = (uint8_t*)&_etext ; /*start of .data section in SRAM*/ uint8_t* DestPtr = (uint8_t*)&_sdata ;"for loop for Copying data"
uint32_t Counter = 0; for(Counter=0; Counter < size; Counter++) { *DestPtr = *SrcPtr ; DestPtr++; SrcPtr++; } -
Initialize .bss section in SRAM to zero
"update the .bss size"
size = (uint32_t)&_ebss - (uint32_t)&_sbss ;"update the destination (note: .bss with no source)"
DestPtr = (uint8_t*)&_sbss ;"for loop for initialising data"
for(Counter=0; Counter < size; Counter++) { *DestPtr = 0U ; /* *DestPtr++=0; */ DestPtr++; } -
Call init function of standard library
-
call main
main();
don't forget to put infinite loop at the end of Reset_Handler
while(1);
update STM32_f446reStartup.c with Reset_Handler implementation
STM32_f446reStartup.c
extern uint32_t _sdata;
extern uint32_t _edata;
extern uint32_t _sbss ;
extern uint32_t _ebss ;
extern uint32_t _etext ;
void Reset_Handler(void) ;
void Reset_Handler(void)
{
/*1- Copy .data from FLASH to SRAM*/
uint32_t size = (uint32_t)&_edata - (uint32_t)&_sdata ;
uint8_t* SrcPtr = (uint8_t*)&_etext ; /*start of .data section in Flash*/
uint8_t* DestPtr = (uint8_t*)&_sdata ; /*start of .data section in SRAM*/
uint32_t Counter = 0;
for(Counter=0; Counter < size; Counter++)
{
*DestPtr = *SrcPtr ;
DestPtr++;
SrcPtr++;
}
/*2- Initialize .bss section in SRAM to zero*/
size = (uint32_t)&_ebss - (uint32_t)&_sbss ;
DestPtr = (uint8_t*)&_sbss ;
for(Counter=0; Counter < size; Counter++)
{
*DestPtr = 0U ; /* *DestPtr++=0; */
DestPtr++;
}
/*3- Call init function of standard library*/
/*4- Call main */
main();
while(1);
}
we generated our final executable, our goal now is to download and debug the ececutable.
download the file into the flash of he MCU, then run our program on the MCU.
Target is connected to the host through debud adapter(Debugger), also called (ICD) In-Circuit Debugger or (ICP) In-Circuit Programmer.
it does protocol conversion, it converts from the hpst protocol (USB), to the target native protocol (SWD or JTAG), called the target interface.
On the the host machine, you have to run some applications to talk to this debug adapter, to talk to the target, like send command to the target, debug the board, do various other things.
There are mant applications could be used, one of them is OpenOCD.
1- OCD aims to provide debugging, In-System programming, and boundary scan testing for embedded target devices.
2- Free and opensource host application, allows you to program, debug, analyze your application using GDB(GNU Debugger)
3- OpenOCD currently supports many types of debug adapters: USB-Based, Parallel-Port Based, other standalone boxes that run OpenOCD internally.
4- GDB Debug: it allows ARM7, ARM9, XScale, Cortex-m3, cortex-m4, Intel Quark based cores to be debugged via DGB protocol, you can get the full list via documentation of OpenOCD.
5- Flash programming: Flash writing is supported for external NOR-Flash, NAND-Flash, several internal flashes
So we can use Open OCD for 2 main purposes:
1- Download the extecutable in the internal flash of the UC
2- Debug the code using GDB
1- Programming adapters are used to get access to the debug interface of the target with native protocol signaling, such as SWD or JTAG, since Host doesn't support such interfaces.
2- It does protocol conversion, for example commands and messages coming from host application in the form of USB packets will be converted to equivalent debug interface signaling (SWD or JTAG) and vice versa.
3- Mainly debug adapters help you to download and debug the code.
Some examples of Popular debug adapters
- Up to 1M Byte /sec download speed
-Multiple CPUs: 8051, PIC12, RX, ARM7/9/11, Cortex MIRIA
-
Target interface: 20 pin
-
so you can access the debug interface, trace interface, & other things
-
JTAG, SWD, SWO, ETM
-
extended debug Capabilities for cortex.M devices
-
You Can Control the processor, set break points, read/write memory contents, While processor is funning @ full speed .
-
Target Connector :
1- 10 Pin 2 - 20 Pin -
High speed data & instruction trace are streamed directly to your PC enabling you to analyze detailed programming behaviour
-
Provided by ST Microelectronics.
-
It is an In-Circuit debugger and programmer for STM8, STM32 MCUs.
-
the Single Wire Interface Module(SWIM) and JTAG/Serial Wire Debug(SWD) are used to cimmunicate with any STM8 or STM32 MCUs located on an application board.
-
Some hacks are done by flashing J-Link firmware on this adapter.
-
If you're using Discovery or Nucleo boards, you don't have to worry about buying a debug adapter, because you have embedded debug adapters on the board itself.(Stlink - V2A) but this debug adapter supports only SWD protocol, the host application can't use JTAG signaling to debug this board, only SWD, if you're using external debug adapter tou can use JTAG.
This DP(Debug port) knows how to talk to the processor Don't forget your goal, you want to talk to the processor, throw data on the processor bus system, by this you can access bus interfaces AHB, APB. AHB-AP: AHB Access Port/Point, There are various access points in the processor, for ex you want to talk to the memory and to the core, especially the flash memory, this allows you to access AHB interconnect where the AHB peripherals are connected, like SRAM, flash, other peripherals, also you can control the core, put breakpoints, watch point, so with the AHB AP you can access the flash, other memories, processor.
this means if you want to program the flash by a hex file:
Sent over the host -> St Link driver -> adapter(USB Packet) -> Board (SWD packet) -> UC -> DP -> AHP AP -> AHB interconnect -> Flash controller(interface) -> Flash memory
This SWD packet will carry the address information as well as the data to reach the destination, even you can reach the SRAM, .., this is what happens in the background.
In our case for Nucleo board, we are not using any external debugger adapters because we have on board debug adapter
- Download and install OpenOCD.
- Install the client sw to talk to the OpenOCD, you can use Talnet client or GDB client.(for windows, we can use PUTTY to make Talnet connection to OpenOCD).
- Run OpenOCD with board configuration file.
- Connect to OpenOCD(server) via Talnet client or GDB client.
- Issue commands over Talnet client or GDB client to OpenOCD to download and debug the code.
- Download and install OpenOCD.
- google -> openOCD -> Getting OpenOCD -> Unofficial binary packages -> Windows 32/64-bit -> show all -> xpack-openocd-0.12.0-2-win32-x64.zip
-
Extrct it in our folder -> bin -> copy path -> right click om "my computer" -> advanced system settings -> Enviroment variables -> system variables -> single click on "path" -> Edit -> New -> paste the path -> ok -> ok -> ok
-
open the downloaded folder again and follow this path openocd\scripts\board then search for your board
- Install the client sw to talk to the OpenOCD, you can use Talnet client or GDB client.(for windows, we can use PUTTY to make Talnet connection to OpenOCD).
to make sure you downloaded OpenOCD well, go back to the folder, open the git bash window
$ openocd
$ openocd --help
$ openocd -f board/st_nucleo_f4.cfg
xPack Open On-Chip Debugger 0.12.0+dev-01312-g18281b0c4-dirty (2023-09-04-22:32) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : The selected transport took over low-level target control. The results mi ght differ compared to plain JTAG/SWD srst_only separate srst_nogate srst_open_drain connect_deassert_srst Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : clock speed 2000 kHz Error: open failed
update makefile with this role as load role:
makefile
CC = arm-none-eabi-gcc
MACK = cortex-m4
CFLAGS = -c -mcpu=$(MACK) -mthumb -std=gnu11 -Wall
LDFLAGS = -T Stm32F446_LS.ld -nostdlib -Wl,-Map=final.map
main.o: main.c
$(CC) $(CFLAGS) $^ -o $@
GPIO_prog.o: GPIO_prog.c
$(CC) $(CFLAGS) $^ -o $@
RCC_Program.o: RCC_Program.c
$(CC) $(CFLAGS) $^ -o $@
STM32_F446reStartup.o: STM32_F446reStartup.c
$(CC) $(CFLAGS) $^ -o $@
final.elf: main.o GPIO_prog.o STM32_F446reStartup.o RCC_Program.o
$(CC) $(LDFLAGS) $^ -o $@
all: main.o GPIO_prog.o STM32_F446reStartup.o RCC_Program.o final.elf
clean:
rm -rf *.o *.elf
load:
openocd -f board/st_nucleo_f4.cfg
$ make clean
$ make all
$ make load
xPack Open On-Chip Debugger 0.12.0+dev-01312-g18281b0c4-dirty (2023-09-04-22:32) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : The selected transport took over low-level target control. The results mi ght differ compared to plain JTAG/SWD srst_only separate srst_nogate srst_open_drain connect_deassert_srst Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : clock speed 2000 kHz Error: open failed makefile:30: recipe for target 'load' failed make: *** [load] Error 1
connect the board and then open two git bash terminals:
one for server:
$ make load
one for client:
$ arm-none-eabi-gdb
$ target remote localhost:3333
Note: the word monitor is added to the command in case of using gdb client
$ monitor reset init
$ monitor flash write_image erase final.elf
$ monitor reset halt
$ monitor resume
to get these commands use google: openocd general commands
at this point the application should be done.
for our application, the led is on.
now we will set a breakpoint @ GPIO_SetPinVal
$ monitor reset init
open the symbol table to know the address of the function.
it is 0800043c
$ monitor bp 0800043c 2 hw
monitor resume
to remove breakpoint:
$ monitor rbp all
-
Open PUTTY
-
choose Telnet
-
port number is 4444
-
Then press Open
$ reset init
$ flash write_image erase final.elf
$ reset halt
$ resume
Memory access command:
$ mdw address count
Breakpoint and Watchpoint commands:
$ bp address 2 hw


