MCQs, fill-in-blanks, true/false, and interview questions on IDE setup, startup files, linker scripts, and the build process.
15
MCQ
8
Fill in Blanks
8
True / False
10
Interview Q&A
Score Tracker
0 Correct
0 Wrong
0 Attempted
Section 1: Multiple Choice Questions
1. What does the linker script’s MEMORY block define?
Correct! The MEMORY block declares the physical memory regions: FLASH (origin 0x08000000, 1024K) and RAM (origin 0x20000000, 128K). This tells the linker where it can place code and data.
2. What is the first thing the Reset_Handler does when the STM32F407 powers on?
Correct! Reset_Handler first copies .data from Flash to SRAM, then zeros .bss, then optionally calls SystemInit(), and finally calls main().
3. What GCC flag enables generation of full debug symbols (best for step-through debugging)?
Correct! -g3 includes maximum debug information (DWARF level 3) in the ELF file. -O0 disables optimization. Both are used together in Debug builds: -O0 -g3.
4. Which protocol does ST-LINK use to communicate with the STM32F407 for debugging?
Correct! SWD (Serial Wire Debug) is a 2-wire ARM debug protocol (SWDIO + SWDCLK). It is the modern replacement for the older 4-wire JTAG and is used exclusively on Cortex-M processors for flashing and debugging.
5. What does the __attribute__((weak)) modifier do when applied to a function?
Correct!weak means the linker uses this definition ONLY if no other strong (non-weak) definition with the same name exists. This is how interrupt handlers work: the startup file provides weak defaults, and you override them by defining your own with the same name in C.
6. In a linker script, what does >RAM AT> FLASH mean for the .data section?
Correct!>RAM AT> FLASH sets the VMA (Virtual Memory Address = runtime address in RAM) and the LMA (Load Memory Address = where it is stored in Flash). Startup code copies from Flash LMA to RAM VMA.
7. What does the arm-none-eabi-size command show?
Correct! arm-none-eabi-size shows text+data = Flash usage, data+bss = SRAM usage at runtime. Essential for checking that your firmware fits within the MCU’s memory limits.
8. What is the role of the BSRR register compared to the ODR register for GPIO output?
Correct! BSRR is write-only. Bits [15:0] = set pins, bits [31:16] = reset pins. A single 32-bit write is atomic — no risk of ISR corruption. ODR requires read-modify-write which is non-atomic.
9. Which file extension contains code, data, AND debug symbols all in one?
Correct! ELF (Executable and Linkable Format) contains everything: code, data, section headers, symbol table, and debug information (DWARF). The debugger loads the ELF to map machine code addresses back to source lines.
10. What does the -ffunction-sections -fdata-sections + --gc-sections combination achieve?
Correct! -ffunction-sections and -fdata-sections put each function/variable in its own linker section. The linker’s –gc-sections then removes sections that are never referenced, reducing the binary size significantly.
11. What is OpenOCD and what role does it play in STM32 debugging?
Correct! OpenOCD (Open On-Chip Debugger) bridges the IDE’s GDB client and the hardware. Chain: IDE GDB client — OpenOCD (GDB server + ST-LINK driver) — ST-LINK USB — SWD — STM32F407.
12. The GCC flag -mcpu=cortex-m4 does what?
Correct! -mcpu=cortex-m4 enables Cortex-M4 specific optimizations and instruction set. It also implicitly sets architecture flags. The FPU must be separately enabled with -mfpu=fpv4-sp-d16.
13. What happens if you press F6 (Step Over) in the debugger when the current line contains a function call?
Correct! Step Over (F6) executes the entire function call as one step. To enter the function line by line, use Step Into (F5 in most configurations). Step Over is useful when you trust a function works and don’t want to debug inside it.
14. What is the vector table and where must it be placed on STM32F407?
Correct! The vector table is an array of addresses (function pointers). Entry 0 = initial SP, Entry 1 = Reset_Handler, Entries 2+ = exception and IRQ handlers. The hardware reads this table at boot from the start of Flash (0x08000000).
15. What does KEEP(*(.isr_vector)) in the linker script do?
Correct! With –gc-sections, the linker removes sections not referenced by other code. The vector table is referenced by the hardware (not code), so KEEP() is required to prevent the linker from deleting it.
Section 2: Fill in the Blanks
1. The GCC ARM compiler executable is called
Answer: gcc — Full name: arm-none-eabi-gcc. The “none” means no OS, “eabi” = Embedded ABI.
2. The section that stores code and read-only data in Flash is called
Answer: .text — Contains compiled function code. .rodata (read-only data like string literals) is usually part of this Flash region too.
3. The two-wire ARM debug protocol used by ST-LINK is called
Answer: SWD (Serial Wire Debug). Uses SWDIO and SWDCLK pins. ARM Cortex-M specific.
4. To convert an ELF file to a raw binary, use: arm-none-eabi- -O binary
Answer: objcopy — arm-none-eabi-objcopy converts ELF to other formats: -O binary for .bin, -O ihex for .hex.
5. The linker script keyword that prevents a section from being garbage-collected is
Answer: KEEP — KEEP(*(.isr_vector)) ensures the vector table is not removed by –gc-sections linker optimization.
6. In BSRR, to clear (reset) pin PA5, write 1 to bit position of the BSRR register.
Answer: 21 — BSRR[31:16] = Reset bits. BR5 = bit position 16+5 = 21. Expression: BSRR = (1U << 21).
7. The startup assembly file handles the vector table and which is the first code to run on reset.
Answer: Reset_Handler — The processor jumps to Reset_Handler after reading its address from vector table entry 1 (offset 0x04 from Flash start).
8. The GCC flag -mthumb tells the compiler to generate instructions instead of 32-bit ARM instructions.
Answer: Thumb (Thumb-2) — Cortex-M processors always run in Thumb state. Thumb-2 uses a mix of 16-bit and 32-bit instructions for good code density and performance.
Section 3: True or False
1. STM32CubeIDE requires you to install the GCC ARM toolchain separately from the official ARM website.
2. The .bss section stores initial values for uninitialized global variables in Flash.
3. You can use the Memory Browser in STM32CubeIDE to read and write peripheral registers directly during a debug session.
4. A program compiled with Debug settings (-O0 -g3) will behave identically to one compiled with Release settings (-O2) in all cases.
5. The .elf file is the recommended format for programming production units in the field.
6. On an STM32F407, if you write to GPIOA registers without first enabling the GPIOA clock in RCC_AHB1ENR, the write silently has no effect.
7. The linker script must be rewritten every time you add a new source file to the project.
8. The STM32F407 Discovery board’s on-board ST-LINK circuit is itself a separate STM32 MCU (STM32F103).
Section 4: Interview Questions
Q1: Explain the role of the linker script in an embedded project. Why can’t you just compile and run?+
Model Answer:
On a desktop PC, the OS loader reads the ELF and places code/data in appropriate virtual memory. The desktop linker knows the OS’s conventions automatically.
On a bare-metal MCU, there is no OS. The linker must be explicitly told: “put code at 0x08000000 (Flash), put stack at the top of SRAM, put initialized data in Flash but copy to 0x20000000 at runtime.” The linker script provides all of this information.
Without a correct linker script, the binary would be placed at wrong addresses, the processor would fetch garbage as instructions, or initialized globals would never be copied to SRAM.
Q2: A program works in Debug build but fails in Release build. What is the most likely cause?+
Model Answer:
The most likely cause is a missing volatile keyword on a hardware register pointer or an ISR-shared variable.
Debug build uses -O0: no optimization, every access goes to actual memory.
Release build uses -O2 or -O3: the compiler caches values in CPU registers and may eliminate “redundant” reads. A polling loop that checks a hardware register without volatile gets optimized to an infinite loop or removed entirely.
Fix: audit all hardware register accesses and ISR-shared variables for missing volatile. Also check for uninitialized variables that happen to have the right value in Debug due to stack patterns.
Q3: What is the difference between .data and .bss sections? How does each affect Flash usage?+
Model Answer:
.data: Initialized global/static variables. int x = 42; — the value 42 must be stored somewhere persistent (Flash) and copied to SRAM at startup. Occupies BOTH Flash (LMA, for storage) and SRAM (VMA, for runtime).
.bss: Zero-initialized global/static variables. int y; — the startup code just zeroes the SRAM region. No values need to be stored in Flash. Occupies ONLY SRAM. This saves Flash space.
Implication: For embedded systems with large lookup tables or configuration arrays, prefer const (goes to .rodata in Flash, no SRAM copy) or explicitly zero-initialize large arrays to put them in .bss (no Flash cost).
Q4: Why use BSRR instead of ODR for GPIO output in interrupt-driven code?+
Model Answer:
ODR requires read-modify-write: read ODR, OR/AND the bit, write back. This is a non-atomic 3-step operation. If an interrupt fires between the read and write steps, and the ISR also modifies ODR, the main code’s write will overwrite the ISR’s change (or vice versa).
BSRR is write-only and atomic: a single 32-bit write either sets (bits [15:0]) or resets (bits [31:16]) specific GPIO pins. The hardware handles this in one clock cycle — no read needed, no window for interrupt corruption.
Best practice: always use BSRR for GPIO output in any code that may be interrupted. ODR read-modify-write is only safe in main() when interrupts are disabled.
Q5: Describe the complete tool chain: from .c source file to executing firmware on the board.+
Model Answer:
Preprocessor (cpp): Expands #includes and #defines into a single translation unit.
Compiler (arm-none-eabi-gcc cc1): Converts C source to ARM assembly (.s).
Assembler (arm-none-eabi-as): Converts assembly to machine code object file (.o).
Linker (arm-none-eabi-ld): Combines all .o files using the linker script to create an ELF executable with correct memory addresses.
objcopy: Extracts the binary from ELF into .bin or .hex format for flashing.
OpenOCD + ST-LINK: Writes the binary to STM32F407 Flash via SWD protocol over USB.
Q6: What would happen if the linker script had the wrong Flash start address (e.g., 0x00000000 instead of 0x08000000)?+
Model Answer:
The binary would be linked with all code and data at wrong addresses. When flashed to the board and run:
The vector table would be placed at the wrong offset in Flash. When the hardware tries to read the initial SP and Reset_Handler address, it would get garbage values.
The processor would jump to a garbage address for Reset_Handler and immediately trigger a HardFault.
The program would never reach main().
This is why the linker script ORIGIN address must exactly match the MCU’s Flash start address as defined in the reference manual. STM32F407 Flash is always at 0x08000000.
Q7: How do you override a default (weak) interrupt handler in STM32CubeIDE?+
Model Answer:
Simply define a function with the exact same name as the handler in any .c file in your project. The linker automatically uses your strong definition over the startup file’s weak one.
/* In main.c or any .c file: */
void SysTick_Handler(void)
{
/* Your ISR code here — overrides the weak default */
tick_count++;
}
The function name must match exactly the weak alias in the startup file. Handler names are defined in the CMSIS device header (e.g., stm32f407xx.h) or in the startup .s file.
Q8: What is semihosting? When is it useful and what are its limitations?+
Model Answer:
Semihosting is a mechanism where the embedded program routes standard I/O calls (printf, fopen) through the debug connection to the host computer’s console. The MCU executes a special BKPT (breakpoint) instruction that OpenOCD intercepts and handles on the host side.
Useful for: Quick debug logging during development without needing a UART connection. Just enable semihosting in the debug configuration and use printf() normally.
Limitations:
Extremely slow — each printf() call requires a complete round-trip over the debug connection.
Halts the MCU while waiting for the host to respond (not real-time safe).
Requires an active debug session — cannot be used in production or standalone operation.
Must be disabled before deploying: without an active debugger, a semihosting call hangs the MCU indefinitely at the BKPT instruction.
Q9: How do you find which functions are using the most Flash in your embedded project?+
Model Answer:
Three main approaches:
Map file: The .map file (generated by -Wl,-Map=output.map) lists every section, symbol, and its address and size. Search for .text sections in the map file.
nm command:arm-none-eabi-nm --size-sort -r led_blink.elf lists all symbols sorted by size, largest first. Immediately shows which functions are biggest.
objdump disassembly:arm-none-eabi-objdump -d led_blink.elf shows disassembly of all sections. Count instructions per function for detailed analysis.
If Flash is full: enable -Os (optimize for size) instead of -O2, use –gc-sections to remove unused code, move large lookup tables to external Flash, or reduce printf usage (heavyweight function).
Q10: Explain what HardFault is and list 3 common causes in STM32 embedded code.+
Model Answer:
HardFault is a processor exception triggered when an unrecoverable error occurs. The processor saves its state and jumps to HardFault_Handler. In the default startup file, this loops forever.
3 Common Causes:
NULL or invalid pointer dereference: Accessing address 0x00000000 or an uninitialized pointer. The default weak HardFault_Handler is also at address 0 in some linker configurations causing recursive faults.
Stack overflow: A function’s local variables exceed available stack space. The stack pointer goes below the stack bottom and corrupts other memory, leading to HardFault.
Unaligned memory access: Some data access patterns on Cortex-M (especially with -O2 and certain struct layouts) can trigger a bus fault that escalates to HardFault.
Debugging HardFault: read the CFSR (Configurable Fault Status Register) at 0xE000ED28 and BFAR/MMAR registers to understand the exact fault cause.
Module 1 Complete!
You have finished both lessons of Module 1. Proceed to Module 2: Number Systems and Data Types to master binary, hex, and C data types for embedded systems.
2 Comments