Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

Building an STM32 Keil MDK Project in Pure Assembly and Blinking an LED

Tech 2

Target hardware and tools

  • MCU/board: STM32F103 series (e.g., STM32-F103-MINI)
  • Toolchain: Keil MDK-ARM (uVision5 or newer)
  • Optional: Serial terminal for observation

1. A minimal Keil project written entirely in assembly

1.1 Create the project

  1. Open Keil uVision, create a new project (example name: asm_demo).
  2. Choose your STM32F103 device.
  3. In the Pack Installer dialog, enable at least:
    • CMSIS → Core
    • Device → Startup (provides system startup and vector table if you choose to keep it; for a pure-assembly demo you can also provide your own minimal vector table)

1.2 Add an assembly source file

  • Right‑click Source Group 1 → Add New Item → Asm Module.
  • Name it demo.s.

1.3 Example assemb code

The following snippet demonstrates calls, link register usage, and a simple infinite loop. It is written for ARMASM/Thumb on Cortex‑M (little‑endian) and exports __main as entry.

    AREA    |.text|, CODE, READONLY
    THUMB
    REQUIRE8
    PRESERVE8

    EXPORT  __main
    ENTRY

__main
    ; initialize some scratch registers
    MOVS    r4, #3
    MOVS    r5, #7

    BL      add_pair         ; r6 = r4 + r5
    BL      mix_registers    ; r7 = r6 ^ r5
    BL      clobber_example  ; demonstrate prologue/epilogue with PUSH/POP

stay_here
    B       stay_here

; r6 = r4 + r5
add_pair
    ADDS    r6, r4, r5
    BX      lr

; r7 = r6 XOR r5
mix_registers
    EORS    r7, r6, r5
    BX      lr

; shows function frame with saved registers
clobber_example
    PUSH    {r0, r1, lr}
    MOVS    r0, #0x12
    ADDS    r1, r0, #0x34
    ; do something trivial to keep the pipeline busy
    EORS    r0, r0, r1
    POP     {r0, r1, pc}

Save the file and build.

1.4 Run-time debugging (stepping and watch windows)

  • Select a debugger (Options for Target → Debug → your debug adapter, e.g., ST-LINK).
  • Start a debug session.
  • Set breakpoints on labels like __main or mix_registers.
  • Open the Registers and Watch windows and add r4–r7 to observe changes as you single‑step.

1.5 Produce and inspect the HEX file

  • Enable Output → Create HEX File in target options, then rebuild.

  • The Intel HEX format encodes binary data as ASCII records:

    • Each line: ":" [byte count] [address] [record type] [data…] [checksum] CRLF
    • Example first non‑data record often seen for STM32: :020000040800F2
      • 02 → byte count (2 data bytes)
      • 0000 → address field (0x0000)
      • 04 → record type (Extended Linear Address)
      • 0800 → data (upper 16 bits of the 32‑bit linear address = 0x0800 → base 0x0800_0000)
      • F2 → checksum
  • The first eight data bytes at the image’s base address (0x0800_0000 for STM32F1) are the vector table’s first two entries:

    • Word 0: initial Main Stack Pointer value (MSP)
    • Word 1: Reset_Handler address (LSB set becuase Thumb state) Because Cortex‑M is little‑endian, each 32‑bit word is stored least‑significant byte first in the HEX data records that map to 0x0800_0000.

1.6 Section and image size

  • After a build, Keil prints a size summary (or check the .map file):
    • Code and RO-data (Flash)
    • RW-data (initial values stored in Flash; copied to RAM at startup)
    • ZI-data (zero‑initialized data in RAM; not stored in HEX, only size reserved)
  • These correspond to segments the linker emits and what eventually appears in the HEX (only what must reside in non‑volatile memory is emitted as data records).

2. LED blink (1 Hz) in pure assembly

Below is a standalone assembly program with a minimal vector table and a software delay. It enables the GPIOC clock and configures PC2 as push‑pull output (change the pin if your board’s LED is on a different GPIO, e.g., PC13). It then toggles the pin roughly once per second.

Notes:

  • RCC base: 0x4002_1000; RCC_APB2ENR at 0x4002_1018
  • GPIOC base: 0x4001_1000
    • GPIOC_CRL (pins 0–7) at 0x4001_1000
    • GPIOC_CRH (pins 8–15) at 0x4001_1004
    • GPIOC_BSRR at 0x4001_1010
  • For PC2, use CRL (bits for pin 2 are [11:8]); MODE=10 (2 MHz), CNF=00 → nibble value 0x2
  • BSRR lower half sets a pin; upper half (bit+16) resets a pin
    ;============================================================
    ; Minimal STM32F103 LED blink on PC2 (active-high)
    ;============================================================

    LED_PIN_BIT         EQU     2

    RCC_APB2ENR         EQU     0x40021018
    GPIOC_CRL           EQU     0x40011000
    GPIOC_BSRR          EQU     0x40011010

    IOPCEN_BIT          EQU     (1<<4)            ; enable GPIOC clock

    ; Stack
    Stack_Size          EQU     0x00000400
    AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem               SPACE   Stack_Size
__initial_sp

    ; Vector table (minimal)
    AREA    RESET, DATA, READONLY
__Vectors
    DCD     __initial_sp           ; Initial MSP
    DCD     Reset_Handler          ; Reset vector

    ; Code
    AREA    |.text|, CODE, READONLY
    THUMB
    REQUIRE8
    PRESERVE8
    ENTRY

Reset_Handler
    ; enable GPIOC clock
    LDR     r0, =RCC_APB2ENR
    LDR     r1, [r0]
    ORR     r1, r1, #IOPCEN_BIT
    STR     r1, [r0]

    ; configure PC2: MODE=10 (2 MHz), CNF=00 → nibble = 0b0010
    LDR     r0, =GPIOC_CRL
    LDR     r2, [r0]
    ; clear bits [11:8] for pin 2
    LDR     r3, =0xFFFFF0FF        ; mask to clear the nibble for pin 2
    AND     r2, r2, r3
    ; set MODE/CNF for pin 2
    ORR     r2, r2, #(0x2 << (LED_PIN_BIT*4))
    STR     r2, [r0]

main_loop
    ; set PC2 high
    LDR     r0, =GPIOC_BSRR
    MOVS    r1, #(1<<LED_PIN_BIT)
    STR     r1, [r0]

    BL      delay_long

    ; set PC2 low
    MOVS    r1, #(1<<(LED_PIN_BIT+16))
    STR     r1, [r0]

    BL      delay_long
    B       main_loop

; crude delay loop (~1 s depending on clock; adjust constants as needed)
; Uses r4–r6 as counters, preserves lr
delay_long
    PUSH    {r4, r5, r6, lr}
    MOVS    r4, #0
    MOVS    r5, #0
    MOVS    r6, #0

.dl0
    ADDS    r4, r4, #1
    CMP     r4, #900
    BCC     .dl0

    MOVS    r4, #0
    ADDS    r5, r5, #1
    CMP     r5, #900
    BCC     .dl0

    MOVS    r4, #0
    MOVS    r5, #0
    ADDS    r6, r6, #25
    CMP     r6, #25
    BCC     .dl0

    POP     {r4, r5, r6, pc}

    END

Build the target with "Create HEX File" enabled and program the device using your preferred tool. If your LED is connected to a different pin, update LED_PIN_BIT and the GPIO register addresses/nibbles accordingly.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.