In this lesson, you will learn how to direct the linker to place specific segments of your compiled code and data into precise physical memory addresses on your microcontroller.
When you compile C code, the compiler generates object files containing sections like .text (executable code), .data (initialized global variables), and .bss (uninitialized variables). While the compiler creates these sections, it doesn't know where your specific hardware’s Flash or RAM begins and ends. The linker script acts as the "map" that tells the linker exactly where these sections belong.
Defining Memory Regions
First, you define your hardware’s memory map using the MEMORY command. This tells the toolchain the name, origin, and length of your physical memory banks:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (rwx): ORIGIN = 0x20000000, LENGTH = 20K
}
Mapping Sections to Regions
Next, you use the SECTIONS command to place your code and data into these regions. You use the > operator to assign a section to a specific memory area:
SECTIONS
{
.text : {
*(.vectors) /* Interrupt vectors */
*(.text*) /* All compiled code */
} > FLASH
.data : {
_sdata = .; /* Symbol for start of data */
*(.data*)
_edata = .; /* Symbol for end of data */
} > RAM AT > FLASH
}
The AT > FLASH syntax is crucial for the .data section. Your initialized variables reside in Flash (so they persist while power is off) but must be copied to RAM by your startup code before main() runs. The linker handles the math to track where the data "lives" (Flash) versus where it "runs" (RAM).
By mastering these scripts, you gain total control over your memory layout, ensuring your interrupt vectors are at the start of Flash, your stack is placed safely at the end of RAM, and your performance-critical code is positioned where it executes fastest.
Key Takeaway: Linker scripts are the essential bridge between abstract compiled code and physical hardware, providing the map that defines where every byte of your firmware resides in memory.