RP2350 Button Driver
March 23, 2026 · View on GitHub
FREE Reverse Engineering Self-Study Course HERE
VIDEO PROMO HERE
RP2350 Button Driver
An RP2350 Button driver written entirely in ARM Assembler.
Install ARM Toolchain (Windows / RP2350 Cortex-M33)
Official Raspberry Pi guidance for RP2350 ARM recommends the Arm GNU Toolchain from developer.arm.com.
Official References
Install (PowerShell)
$url = "https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-mingw-w64-x86_64-arm-none-eabi.zip"
$zipPath = "$env:TEMP\arm-toolchain-15-x64-win.zip"
$extractPath = "$env:TEMP\arm-extract"
$dest = "$HOME\arm-toolchain-15"
Invoke-WebRequest -Uri $url -OutFile $zipPath
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force
Move-Item "$extractPath\arm-gnu-toolchain-*" $dest -Force
Get-ChildItem -Path $dest | Select-Object Name
Add Toolchain To User PATH (PowerShell)
$toolBin = "$HOME\arm-toolchain-15\bin"
$currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($currentUserPath -notlike "*$toolBin*") {
[Environment]::SetEnvironmentVariable("Path", "$currentUserPath;$toolBin", "User")
}
Close and reopen your terminal after updating PATH.
Verify Toolchain
arm-none-eabi-as --version
arm-none-eabi-ld --version
arm-none-eabi-objcopy --version
Build This Project
.\build.bat
Button + LED Wiring (Pico 2 Target)
- GP15 (Pin 20) → tactile button → GND (internal pull-up enabled)
- GP16 (Pin 21) → 330 ohm resistor → LED anode
- LED cathode → GND (Pin 23)
Hardware
Raspberry Pi Pico 2 w/ Header BUY
USB A-Male to USB Micro-B Cable BUY
Raspberry Pi Pico Debug Probe BUY
Complete Component Kit for Raspberry Pi BUY
10pc 25v 1000uF Capacitor BUY
10% PiShop DISCOUNT CODE - KVPE_HS320548_10PC
Build
.\build.bat
Clean
.\clean.bat
Tutorial
Part I — Foundations (Chapters 1–6)
Chapter 1: What Is a Computer?
- Introduction
- The Fetch-Decode-Execute Cycle
- The Three Core Components
- Microcontroller vs Desktop Computer
- What Is RP2350?
- What Is ARM Cortex-M33?
- What Is Assembly Language?
- Why Learn Assembly?
- What We Will Build
- Summary
Chapter 2: Number Systems
- Introduction
- Decimal (Base 10)
- Binary (Base 2)
- Hexadecimal (Base 16)
- Number Prefixes in ARM Assembly
- Bit Masks
- Register Values as Hex
- Summary
Chapter 3: Memory
- Introduction
- Addresses and Bytes
- Words
- Endianness
- The RP2350 Memory Map
- Flash Memory — Where Our Code Lives
- SRAM — Where the Stack Lives
- Peripheral Registers — Memory-Mapped I/O
- Addresses in Our Firmware
- Alignment
- Summary
Chapter 4: What Is a Register?
- Introduction
- General-Purpose Registers
- Special-Purpose Registers
- Stack Limit Registers
- The Program Status Register (xPSR)
- CPACR — Coprocessor Access Control
- Register Usage in Our Button Driver
- Summary
Chapter 5: Load-Store Architecture
- Introduction
- The Load-Store Rule
- Load Instructions in Our Firmware
- Store Instructions in Our Firmware
- The Read-Modify-Write Pattern
- Push and Pop — Stack Access
- Why Load-Store?
- Summary
Chapter 6: Fetch-Decode-Execute Cycle in Detail
- Introduction
- The Three Phases
- The Cortex-M33 Pipeline
- Sequential Execution Example
- Branch Penalty
- Conditional Execution
- The Clock
- Summary
Part II — The ARM Instruction Set (Chapters 7–12)
Chapter 7: ARM Cortex-M33 ISA Overview
- Introduction
- What Is an ISA?
- Thumb-2: Mixed 16-bit and 32-bit Instructions
- Instruction Categories
- Condition Flags
- Unified Assembly Syntax
- Instruction Encoding Example
- Complete Instruction Map for This Driver
- Summary
Chapter 8: Immediate and Upper-Immediate Instructions
- Introduction
- What Is an Immediate Value?
- Thumb-2 Modified Immediate Encoding
- The Barrel Shifter
- Constants That Don't Fit: The ldr Pseudo-Instruction
- Immediate vs. Pseudo-Instruction Usage in Our Driver
- The movw/movt Pair
- Summary
Chapter 9: Arithmetic and Logic Instructions
- Introduction
- Arithmetic Instructions
- Logic Instructions
- Shift Instructions
- Instruction Summary Table
- Summary
Chapter 10: Memory Access — Load and Store Deep Dive
- Introduction
- Register-Indirect Addressing
- Register-Indirect with Offset
- PC-Relative Loads (Literal Pools)
- Stack Operations: push and pop
- System Register Transfers: msr
- Coprocessor Memory Access
- Memory Access Timing
- Summary
Chapter 11: Branch Instructions
- Introduction
- Unconditional Branch: b
- Conditional Branches
- Condition Code Summary
- How the Processor Evaluates Conditions
- Branch Encoding
- Control Flow in main.s
- Summary
Chapter 12: Jumps, Calls, and Returns
- Introduction
- bl — Branch with Link (Function Call)
- bx lr — Branch to Link Register (Return)
- b — Tail Call (Branch Without Link)
- The Call Stack in Action
- Link Register vs. Stack
- The Thumb Bit
- Summary
Part III — Assembly Programming (Chapters 13–17)
Chapter 13: Pseudo-Instructions
- Introduction
- ldr Rd, =value — Load Constant
- .type — Symbol Type Declaration
- .global — Export Symbol
- .size — Function Size
- .equ — Define Constant
- .include — Include File
- Pseudo-Instructions vs. Directives
- Summary
Chapter 14: Assembler Directives
- Introduction
- Processor Configuration Directives
- Section Directives
- Alignment Directives
- Symbol Directives
- Data Directives
- Constant Directives
- Label Directives
- KEEP Directive (Linker)
- Summary
Chapter 15: Calling Convention and Stack Frames
- Introduction
- The AAPCS Register Convention
- Arguments and Return Values
- Saving and Restoring Registers
- Stack Frame Layout
- Leaf Functions
- The Full Call Chain
- Summary
Chapter 16: Bitwise Operations for Hardware Programming
- Introduction
- The Problem: Modifying Individual Bits
- Setting a Bit: orr
- Clearing a Bit: bic
- Clear-Then-Set Pattern
- Testing a Bit: tst
- Bitwise AND for Masking: and
- Bitwise XOR for Inversion: eor
- Shift for Bit Extraction: lsr
- Complete Button Read Sequence
- The Complete Pad Configuration Sequence
- Summary
Chapter 17: Memory-Mapped I/O
- Introduction
- The RP2350 Address Space
- How Peripherals Respond to Writes
- How Peripherals Respond to Reads
- Constants as Address Guides
- The MMIO Access Pattern
- The Coprocessor Exception
- Volatile Access
- Summary
Part IV — RP2350 Hardware (Chapter 18)
Chapter 18: The RP2350 — Architecture and Hardware
- Introduction
- RP2350 Overview
- Block Diagram
- Peripherals Used by Our Driver
- The GPIO System
- Boot Process
- Dual-Core and Security
- Summary
Part V — Build System (Chapters 19–20)
Chapter 19: The Linker Script — Placing Code in Memory
Chapter 20: The Build Pipeline — From Assembly to Flashable Binary
- Introduction
- The Build Script
- Step 1: Assembly (Source to Object Files)
- Step 2: Linking (Object Files to ELF)
- Step 3: Binary Extraction (ELF to Raw Binary)
- Step 4: UF2 Conversion (Binary to UF2)
- Flashing
- The Complete Pipeline
- Summary
Part VI — Source Code Walkthroughs (Chapters 21–29)
Chapter 21: Boot Metadata — image_def.s
- Introduction
- The Complete Source
- Line-by-Line Walkthrough
- Binary Layout
- Why This File Exists
- Summary
Chapter 22: Constants File — constants.s
Chapter 23: Stack and Vector Table — stack.s and vector_table.s
- Introduction
- vector_table.s — The Complete Source
- stack.s — The Complete Source
- Initialization Order
- Summary
Chapter 24: Boot Sequence — reset_handler.s
- Introduction
- The Complete Source
- Line-by-Line Walkthrough
- Initialization Order Dependencies
- Why Reset_Handler Has No push/pop
- The Complete Boot Timeline
- Summary
Chapter 25: Oscillator Init — xosc.s
- Introduction
- The Complete Source
- Init_XOSC — Line-by-Line
- Enable_XOSC_Peri_Clock — Line-by-Line
- Both Functions Are Leaf Functions
- Timing
- Summary
Chapter 26: Reset Controller — reset.s
- Introduction
- The Complete Source
- Line-by-Line Walkthrough
- Why This Is Necessary
- The Read-Modify-Write vs. Atomic Clear
- Leaf Function
- Summary
Chapter 27: GPIO Configuration — gpio.s
- Introduction
- The Complete Source
- GPIO_Config — Line-by-Line
- GPIO_Set — Line-by-Line
- GPIO_Clear — Line-by-Line
- GPIO_Config vs. Button_Init
- Summary
Chapter 28: Button Driver — button.s
- Introduction
- The Complete Source
- Button_Init — Line-by-Line
- Button_Read — Line-by-Line
- Button_IsPressed — Line-by-Line
- The Three Functions Compared
- Hardware Circuit
- mrc vs. mcrr
- Summary
Chapter 29: Application Entry Point — main.s
- Introduction
- The Complete Source
- Line-by-Line Walkthrough
- Control Flow Diagram
- Timing Analysis
- Why Button_Read Instead of Button_IsPressed?
- Summary
Part VII — Full Integration (Chapter 30)
Chapter 30: Full Integration — Build, Flash, Wire, and Test
- Introduction
- The Complete System
- Boot Order
- Building the Firmware
- Hardware Wiring
- Flashing the Firmware
- Testing
- Memory Layout
- What We Built
- Summary
main.s Code
/**
* FILE: main.s
*
* DESCRIPTION:
* RP2350 Button Driver Main Application.
*
* BRIEF:
* Main application entry point for RP2350 button driver. Monitors button
* on GPIO15 and controls LED on GPIO16 based on button state.
*
* AUTHOR: Kevin Thomas
* CREATION DATE: November 2, 2025
* UPDATE DATE: November 28, 2025
*/
.syntax unified // use unified assembly syntax
.cpu cortex-m33 // target Cortex-M33 core
.thumb // use Thumb instruction set
.include "constants.s"
/**
* Initialize the .text section.
* The .text section contains executable code.
*/
.section .text // code section
.align 2 // align to 4-byte boundary
/**
* @brief Main application entry point.
*
* @details Implements button monitoring loop. LED on GPIO16 lights up
* when button on GPIO15 is pressed.
*
* @param None
* @retval None
*/
.global main // export main
.type main, %function // mark as function
main:
.Push_Registers:
push {r4-r12, lr} // push registers r4-r12, lr to the stack
.GPIO16_Config:
ldr r0, =PADS_BANK0_GPIO16_OFFSET // load PADS_BANK0_GPIO16_OFFSET
ldr r1, =IO_BANK0_GPIO16_CTRL_OFFSET // load IO_BANK0_GPIO16_CTRL_OFFSET
ldr r2, =16 // load GPIO number
bl GPIO_Config // call GPIO_Config
.Button_Init:
bl Button_Init // initialize button on GPIO15
.Loop:
bl Button_Read // read button state (0=pressed, 1=released)
cmp r0, #0 // compare with 0 (pressed)
beq .Button_Pressed // branch if button pressed (r0==0)
.Button_Released:
ldr r0, =16 // load GPIO number
bl GPIO_Clear // turn off LED
b .Loop_Delay // continue to delay
.Button_Pressed:
ldr r0, =16 // load GPIO number
bl GPIO_Set // turn on LED
.Loop_Delay:
ldr r0, =10 // 10ms debounce delay
bl Delay_MS // call Delay_MS
b .Loop // loop forever
.Pop_Registers:
pop {r4-r12, lr} // pop registers r4-r12, lr from the stack
bx lr // return to caller
/**
* Test data and constants.
* The .rodata section is used for constants and static data.
*/
.section .rodata // read-only data section
/**
* Initialized global data.
* The .data section is used for initialized global or static variables.
*/
.section .data // data section
/**
* Uninitialized global data.
* The .bss section is used for uninitialized global or static variables.
*/
.section .bss // BSS section