mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-08-15 15:40:19 +02:00
Added bare-metal blinky
This commit is contained in:
+258
-591
@@ -1,605 +1,272 @@
|
||||
# RP2350 GPIO Coprocessor Blink - RAW Assembler, NO SDK
|
||||
<img src="https://github.com/mytechnotalent/RP2350_Blink_Driver/blob/main/RP2350_Blink_Driver.png?raw=true">
|
||||
|
||||
## Overview
|
||||
## FREE Reverse Engineering Self-Study Course [HERE](https://github.com/mytechnotalent/Reverse-Engineering-Tutorial)
|
||||
|
||||
This is a **bare-metal assembly program** for the **Raspberry Pi RP2350** microcontroller that demonstrates the use of the **ARM Cortex-M33 coprocessor interface** to control GPIO pins. Unlike traditional GPIO control using direct memory-mapped register writes, this version uses the RP2350's **hardware GPIO coprocessor (CP0)** with **MCRR (Move to Coprocessor from two ARM Registers)** instructions.
|
||||
<br>
|
||||
|
||||
The program blinks an LED connected to **GPIO16** with a 500ms on/off cycle using the specialized coprocessor instructions for high-performance GPIO operations.
|
||||
# RP2350 Blink Driver
|
||||
An RP2350 blink driver written entirely in Assembler.
|
||||
|
||||
---
|
||||
# Code
|
||||
```assembler
|
||||
/**
|
||||
* FILE: main.s
|
||||
*
|
||||
* DESCRIPTION:
|
||||
* RP2350 Bare-Metal GPIO16 Blink, Coprocessor Version.
|
||||
* Minimal bare‑metal LED blink on the RP2350 using direct coprocessor
|
||||
* (MCRR) instructions to manipulate GPIO control registers. This bypasses
|
||||
* SDK abstractions and demonstrates register‑level control in assembler.
|
||||
*
|
||||
* AUTHOR: Kevin Thomas
|
||||
* CREATION DATE: October 5, 2025
|
||||
* UPDATE DATE: October 5, 2025
|
||||
*/
|
||||
|
||||
## What Makes This Special?
|
||||
.syntax unified // use unified assembly syntax
|
||||
.cpu cortex-m33 // target Cortex-M33 core
|
||||
.thumb // use Thumb instruction set
|
||||
|
||||
### Traditional GPIO Control vs Coprocessor Control
|
||||
/**
|
||||
* Memory addresses and constants.
|
||||
*/
|
||||
.equ IO_BANK0_BASE, 0x40028000 // base address of IO_BANK0
|
||||
.equ PADS_BANK0_BASE, 0x40038000 // base address of PADS_BANK0
|
||||
.equ SIO_BASE, 0xD0000000 // base address of SIO block
|
||||
.equ GPIO16_CTRL, 0x84 // io[16].ctrl offset
|
||||
.equ GPIO16_PAD, 0x44 // pads io[16] offset
|
||||
.equ GPIO16_BIT, (1<<16) // bit mask for GPIO16
|
||||
.equ GPIO_OUT_SET, 0x18 // SIO->GPIO_OUT_SET offset
|
||||
.equ GPIO_OUT_XOR, 0x28 // SIO->GPIO_OUT_XOR offset
|
||||
.equ GPIO_OE_SET, 0x38 // SIO->GPIO_OE_SET offset
|
||||
.equ STACK_TOP, 0x20082000 // top of non-secure SRAM
|
||||
.equ STACK_LIMIT, 0x2007A000 // stack limit (32 KB below top)
|
||||
|
||||
**Traditional Method (Direct Register Access):**
|
||||
```assembly
|
||||
LDR R0, =SIO_BASE ; Load SIO base address
|
||||
LDR R1, =GPIO16_BIT ; Load GPIO bit mask
|
||||
STR R1, [R0, #GPIO_OUT_SET] ; Write to memory-mapped register
|
||||
```
|
||||
/**
|
||||
* Initialize the .vectors section. The .vectors section contains vector
|
||||
* table.
|
||||
*/
|
||||
.section .vectors, "ax" // vector table section
|
||||
.align 2 // align to 4-byte boundary
|
||||
|
||||
**Coprocessor Method (This Implementation):**
|
||||
```assembly
|
||||
MOVS R4, #16 ; GPIO pin number
|
||||
MOVS R5, #1 ; Value to write
|
||||
MCRR p0, #4, R4, R5, c0 ; Single coprocessor instruction
|
||||
```
|
||||
|
||||
### Advantages of the Coprocessor Approach:
|
||||
1. **Faster execution** - Single instruction instead of multiple memory operations
|
||||
2. **More atomic** - Less chance of race conditions
|
||||
3. **Hardware-accelerated** - Direct hardware interface
|
||||
4. **Cleaner code** - No need to manage bit masks and base addresses
|
||||
5. **RP2350-specific** - Takes advantage of advanced features
|
||||
|
||||
---
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- **RP2350-based board** (e.g., Raspberry Pi Pico 2)
|
||||
- **LED** connected to **GPIO16** (or use built-in LED if available)
|
||||
- Optional: Current-limiting resistor (typically 330Ω - 1kΩ)
|
||||
- **USB cable** for programming
|
||||
|
||||
### GPIO16 Connection
|
||||
```
|
||||
RP2350 GPIO16 ----[330Ω]----[LED]---- GND
|
||||
(Resistor) (Anode to GPIO, Cathode to GND)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Coprocessor Interface
|
||||
|
||||
The RP2350 implements a **custom GPIO coprocessor (CP0)** accessible through the ARM Cortex-M33's coprocessor interface. This is documented in the RP2350 datasheet.
|
||||
|
||||
#### MCRR Instruction Format
|
||||
```
|
||||
MCRR p0, #opcode, Rt, Rt2, CRm
|
||||
```
|
||||
|
||||
Where:
|
||||
- `p0` = Coprocessor 0 (GPIO coprocessor)
|
||||
- `#opcode` = Operation code (4 for GPIO operations)
|
||||
- `Rt` = First source register (GPIO pin number in R4)
|
||||
- `Rt2` = Second source register (value in R5)
|
||||
- `CRm` = Coprocessor register (c0 for OUT, c4 for OE)
|
||||
|
||||
### GPIO Coprocessor Operations Used
|
||||
|
||||
#### 1. `gpioc_bit_out_put(gpio, value)`
|
||||
**Purpose:** Set or clear a GPIO output pin
|
||||
**Encoding:** `MCRR p0, #4, R4, R5, c0`
|
||||
**Parameters:**
|
||||
- R4 = GPIO pin number (16 in our case)
|
||||
- R5 = Output value (0 or 1)
|
||||
|
||||
**Example:**
|
||||
```assembly
|
||||
MOVS R4, #16 ; GPIO16
|
||||
MOVS R5, #1 ; Set HIGH
|
||||
MCRR p0, #4, R4, R5, c0
|
||||
```
|
||||
|
||||
#### 2. `gpioc_bit_oe_put(gpio, enable)`
|
||||
**Purpose:** Set output enable for a GPIO pin
|
||||
**Encoding:** `MCRR p0, #4, R4, R5, c4`
|
||||
**Parameters:**
|
||||
- R4 = GPIO pin number (16 in our case)
|
||||
- R5 = Output enable (1 to enable output)
|
||||
|
||||
**Example:**
|
||||
```assembly
|
||||
MOVS R4, #16 ; GPIO16
|
||||
MOVS R5, #1 ; Enable output
|
||||
MCRR p0, #4, R4, R5, c4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory Map & Register Addresses
|
||||
|
||||
### Critical Memory Addresses Used
|
||||
|
||||
| Address | Register | Purpose |
|
||||
|--------------|-----------------------------------|--------------------------------------|
|
||||
| `0xE000ED88` | CPACR (Coprocessor Access Control)| Enable CP0 access |
|
||||
| `0x40038044` | PADS_BANK0_GPIO16 | Pad configuration for GPIO16 |
|
||||
| `0x40028084` | IO_BANK0_GPIO16_CTRL | Function select for GPIO16 |
|
||||
| `0x20082000` | STACK_TOP | Initial stack pointer |
|
||||
| `0x2007A000` | STACK_LIMIT | Stack limit for overflow protection |
|
||||
|
||||
### Register Details
|
||||
|
||||
#### CPACR (Coprocessor Access Control Register) - 0xE000ED88
|
||||
```
|
||||
Bits [1:0] - CP0 access privileges
|
||||
00 = No access
|
||||
01 = Privileged access only
|
||||
10 = Reserved
|
||||
11 = Full access (used in this code)
|
||||
```
|
||||
|
||||
#### PADS_BANK0_GPIO16 - 0x40038044
|
||||
```
|
||||
Bit 8 (ISO) - Isolate pad (0 = normal operation)
|
||||
Bit 7 (OD) - Output disable (0 = output enabled)
|
||||
Bit 6 (IE) - Input enable (1 = input buffer enabled)
|
||||
Bits [5:4] - Drive strength
|
||||
Bits [3:2] - Slew rate
|
||||
Bit 1 (PDE) - Pull-down enable
|
||||
Bit 0 (PUE) - Pull-up enable
|
||||
```
|
||||
|
||||
#### IO_BANK0_GPIO16_CTRL - 0x40028084
|
||||
```
|
||||
Bits [4:0] (FUNCSEL) - Function select
|
||||
0 = JTAG (TDI)
|
||||
1 = SPI
|
||||
2 = UART
|
||||
3 = I2C
|
||||
4 = PWM
|
||||
5 = SIO (Software controlled I/O) ← Used for GPIO
|
||||
6-8 = PIO
|
||||
9 = USB
|
||||
31 = NULL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Structure & Execution Flow
|
||||
|
||||
### 1. Vector Table (`.vectors` section)
|
||||
```assembly
|
||||
.section .vectors, "ax"
|
||||
.align 2
|
||||
.global _vectors
|
||||
/**
|
||||
* Vector table section.
|
||||
*/
|
||||
.global _vectors // export symbol
|
||||
_vectors:
|
||||
.word STACK_TOP /* 0x20082000 - Initial Stack Pointer */
|
||||
.word Reset_Handler + 1 /* Reset Handler (Thumb bit set) */
|
||||
```
|
||||
.word STACK_TOP // initial stack pointer
|
||||
.word Reset_Handler + 1 // reset handler (Thumb bit set)
|
||||
|
||||
**Why `+ 1`?**
|
||||
The Cortex-M33 uses the Thumb instruction set exclusively. The LSB (least significant bit) of function pointers must be set to 1 to indicate Thumb mode. This is automatically handled by the processor.
|
||||
|
||||
### 2. Stack Initialization
|
||||
```assembly
|
||||
/**
|
||||
* @brief Reset handler for RP2350.
|
||||
*
|
||||
* @details Entry point after reset. Performs:
|
||||
* - Stack initialization
|
||||
* - Coprocessor enable
|
||||
* - GPIO16 pad/function configuration
|
||||
* - Branches to main() which contains the blink loop
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.global Reset_Handler // export Reset_Handler
|
||||
.type Reset_Handler, %function // mark as function
|
||||
Reset_Handler:
|
||||
/* Initialize stack pointers for Cortex-M33 */
|
||||
LDR R0, =STACK_TOP
|
||||
MSR PSP, R0 /* Process Stack Pointer */
|
||||
|
||||
LDR R0, =STACK_LIMIT
|
||||
MSR MSPLIM, R0 /* Main Stack Pointer Limit */
|
||||
MSR PSPLIM, R0 /* Process Stack Pointer Limit */
|
||||
|
||||
LDR R0, =STACK_TOP
|
||||
MSR MSP, R0 /* Main Stack Pointer */
|
||||
BL Init_Stack // initialize MSP/PSP and limits
|
||||
BL Enable_Coprocessor // enable CP0 in CPACR for MCRR
|
||||
B main // branch to main loop
|
||||
.size Reset_Handler, . - Reset_Handler
|
||||
|
||||
/**
|
||||
* @brief Initialize stack pointers.
|
||||
*
|
||||
* @details Sets Main and Process Stack Pointers (MSP/PSP) and their limits.
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.type Init_Stack, %function
|
||||
Init_Stack:
|
||||
LDR R0, =STACK_TOP // load stack top
|
||||
MSR PSP, R0 // set PSP
|
||||
LDR R0, =STACK_LIMIT // load stack limit
|
||||
MSR MSPLIM, R0 // set MSP limit
|
||||
MSR PSPLIM, R0 // set PSP limit
|
||||
LDR R0, =STACK_TOP // reload stack top
|
||||
MSR MSP, R0 // set MSP
|
||||
BX LR // return
|
||||
|
||||
/**
|
||||
* @brief Enable coprocessor access.
|
||||
*
|
||||
* @details Grants full access to coprocessor 0 (CP0) via CPACR.
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.type Enable_Coprocessor , %function
|
||||
Enable_Coprocessor:
|
||||
LDR R0, =0xE000ED88 // CPACR address
|
||||
LDR R1, [R0] // read CPACR
|
||||
ORR R1, R1, #0x3 // set CP0 full access
|
||||
STR R1, [R0] // write CPACR
|
||||
DSB // data sync barrier
|
||||
ISB // instruction sync barrier
|
||||
BX LR // return
|
||||
|
||||
/**
|
||||
* 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 the infinite blink loop:
|
||||
* - Set GPIO16 high
|
||||
* - Delay ~500 ms
|
||||
* - Set GPIO16 low
|
||||
* - Delay ~500 ms
|
||||
* - Repeat forever
|
||||
*
|
||||
* @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:
|
||||
BL GPIO16_Config // configure pads and FUNCSEL for GPIO16
|
||||
.Loop:
|
||||
BL GPIO16_Set // set GPIO16 high
|
||||
BL Delay_500ms // ~500 ms delay
|
||||
BL GPIO16_Clear // set GPIO16 low
|
||||
BL Delay_500ms // ~500 ms delay
|
||||
B Loop // loop forever
|
||||
.Pop_Registers:
|
||||
POP {R4-R12, LR} // pop registers R4-R12, LR from the stack
|
||||
BX LR // return to caller
|
||||
|
||||
/**
|
||||
* @brief Configure GPIO16 for SIO control.
|
||||
*
|
||||
* @details Sets pad control (IE, OD, ISO) and FUNCSEL = 5 (SIO). Enables OE.
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.type GPIO16_Config, %function
|
||||
GPIO16_Config:
|
||||
.GPIO16_Config_Push_Registers:
|
||||
PUSH {R4-R12, LR} // push registers R4-R12, LR to the stack
|
||||
.GPIO16_Config_Modify_Pad:
|
||||
LDR R3, =PADS_BANK0_BASE + GPIO16_PAD // pad control address
|
||||
LDR R2, [R3] // read pad config
|
||||
BIC R2, R2, #0x80 // clear OD
|
||||
ORR R2, R2, #0x40 // set IE
|
||||
BIC R2, R2, #0x100 // clear ISO
|
||||
STR R2, [R3] // write pad config
|
||||
.GPIO16_Config_Modify_IO:
|
||||
LDR R3, =IO_BANK0_BASE + GPIO16_CTRL // IO control address
|
||||
LDR R2, [R3] // read IO config
|
||||
BIC R2, R2, #0x1F // clear FUNCSEL
|
||||
ORR R2, R2, #5 // set FUNCSEL=5
|
||||
STR R2, [R3] // write IO config
|
||||
.GPIO16_Config_Enable_OE:
|
||||
MOVS R4, #16 // GPIO number
|
||||
MOVS R5, #1 // enable output
|
||||
MCRR p0, #4, R4, R5, c4 // gpioc_bit_oe_put(16, 1)
|
||||
.GPIO16_Config_Pop_Registers:
|
||||
POP {R4-R12, LR} // pop registers R4-R12, LR from the stack
|
||||
BX LR // return
|
||||
|
||||
/**
|
||||
* @brief Set GPIO16 high.
|
||||
*
|
||||
* @details Drives GPIO16 output = 1 via coprocessor MCRR.
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.type GPIO16_Set, %function
|
||||
GPIO16_Set:
|
||||
.GPIO16_Set_Push_Registers:
|
||||
PUSH {R4-R12, LR} // push registers R4-R12, LR to the stack
|
||||
.GPIO16_Set_Load_Operands:
|
||||
MOVS R4, #16 // GPIO number
|
||||
MOVS R5, #1 // logic high
|
||||
.GPIO16_Set_Execute:
|
||||
MCRR p0, #4, R4, R5, c0 // gpioc_bit_out_put(16, 1)
|
||||
.GPIO16_Set_Pop_Registers:
|
||||
POP {R4-R12, LR} // pop registers R4-R12, LR from the stack
|
||||
BX LR // return to caller
|
||||
|
||||
/**
|
||||
* @brief Clear GPIO16 (set low).
|
||||
*
|
||||
* @details Drives GPIO16 output = 0 via coprocessor MCRR.
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.type GPIO16_Clear, %function
|
||||
GPIO16_Clear:
|
||||
.GPIO16_Clear_Push_Registers:
|
||||
PUSH {R4-R12, LR} // push registers R4-R12, LR to the stack
|
||||
.GPIO16_Clear_Load_Operands:
|
||||
MOVS R4, #16 // GPIO number
|
||||
MOVS R5, #0 // logic low
|
||||
.GPIO16_Clear_Execute:
|
||||
MCRR p0, #4, R4, R5, c0 // gpioc_bit_out_put(16, 0)
|
||||
.GPIO16_Clear_Pop_Registers:
|
||||
POP {R4-R12, LR} // pop registers R4-R12, LR from the stack
|
||||
BX LR // return to caller
|
||||
|
||||
/**
|
||||
* @brief Busy‑wait Delay_500ms loop.
|
||||
*
|
||||
* @details Consumes ~2,000,000 cycles to approximate ~500 ms at boot clock.
|
||||
*
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
.type Delay_500ms, %function
|
||||
Delay_500ms:
|
||||
.Delay_500ms_Push_Registers:
|
||||
PUSH {R4-R12, LR} // push registers R4-R12, LR to the stack
|
||||
.Delay_500ms_Setup:
|
||||
LDR R2, =2000000 // loop count (~500 ms)
|
||||
.Delay_500ms_Loop:
|
||||
SUBS R2, R2, #1 // decrement counter
|
||||
BNE .Delay_500ms_Loop // branch until zero
|
||||
.Delay_500ms_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
|
||||
```
|
||||
|
||||
**Cortex-M33 Stack Features:**
|
||||
- **Dual stack pointers:** MSP (Main) and PSP (Process)
|
||||
- **Stack limit registers:** MSPLIM and PSPLIM for overflow detection
|
||||
- **Security feature:** Prevents stack overflow from corrupting memory
|
||||
<br>
|
||||
|
||||
### 3. Coprocessor Access Enablement
|
||||
```assembly
|
||||
/* Enable coprocessor access - set CP0 to full access */
|
||||
LDR R0, =0xE000ED88 /* CPACR address */
|
||||
LDR R1, [R0] /* Read current value */
|
||||
ORR R1, R1, #0x3 /* Set CP0 bits [1:0] to 11 (full access) */
|
||||
STR R1, [R0] /* Write back */
|
||||
DSB /* Data Synchronization Barrier */
|
||||
ISB /* Instruction Synchronization Barrier */
|
||||
```
|
||||
|
||||
**Critical Step:** Without this, MCRR instructions will trigger a **UsageFault** exception!
|
||||
|
||||
**Memory Barriers:**
|
||||
- `DSB` - Ensures all memory operations complete before proceeding
|
||||
- `ISB` - Flushes instruction pipeline to ensure new settings take effect
|
||||
|
||||
### 4. GPIO Pad Configuration
|
||||
```assembly
|
||||
/* Configure pad for GPIO16 */
|
||||
LDR R3, =0x40038044 /* &pads_bank0_hw->io[16] */
|
||||
LDR R2, [R3] /* load current config */
|
||||
BIC R2, R2, #0x80 /* clear OD (bit 7) - enable output */
|
||||
ORR R2, R2, #0x40 /* set IE (bit 6) - enable input buffer */
|
||||
STR R2, [R3] /* store updated config */
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- Clears OD (Output Disable) bit → Allows pin to drive output
|
||||
- Sets IE (Input Enable) bit → Enables input buffer for reading
|
||||
- Preserves other settings (pull-ups, drive strength, etc.)
|
||||
|
||||
### 5. Function Selection
|
||||
```assembly
|
||||
/* Set GPIO16 function to SIO (Software I/O) */
|
||||
LDR R3, =0x40028084 /* &io_bank0_hw->io[16].ctrl */
|
||||
LDR R2, [R3] /* load current config */
|
||||
BIC R2, R2, #0x1F /* clear FUNCSEL bits [4:0] */
|
||||
ORR R2, R2, #5 /* set FUNCSEL = 5 (SIO) */
|
||||
STR R2, [R3] /* store updated config */
|
||||
```
|
||||
|
||||
**Function 5 (SIO):**
|
||||
- Routes GPIO to software control
|
||||
- Required before using coprocessor GPIO operations
|
||||
- Other functions route to hardware peripherals (UART, SPI, etc.)
|
||||
|
||||
### 6. Pad Isolation
|
||||
```assembly
|
||||
/* Un-isolate the pad */
|
||||
LDR R3, =0x40038044 /* &pads_bank0_hw->io[16] */
|
||||
LDR R2, [R3] /* load current config */
|
||||
BIC R2, R2, #0x100 /* clear ISO bit (bit 8) */
|
||||
STR R2, [R3] /* store updated config */
|
||||
```
|
||||
|
||||
**ISO bit:**
|
||||
- When set (1): Pad is isolated from peripherals
|
||||
- When clear (0): Pad is connected and functional
|
||||
- Must be cleared for GPIO to work
|
||||
|
||||
### 7. Enable GPIO Output
|
||||
```assembly
|
||||
/* Enable output using coprocessor */
|
||||
MOVS R4, #16 /* GPIO16 */
|
||||
MOVS R5, #1 /* Enable output */
|
||||
MCRR p0, #4, R4, R5, c4 /* gpioc_bit_oe_put(16, 1) */
|
||||
```
|
||||
|
||||
**This is the first coprocessor instruction!**
|
||||
- Sets the Output Enable (OE) bit for GPIO16
|
||||
- Makes the pin drive its output value
|
||||
- Uses coprocessor register c4 (OE control)
|
||||
|
||||
### 8. Main Blink Loop
|
||||
```assembly
|
||||
blink_loop:
|
||||
/* Turn LED ON */
|
||||
MOVS R4, #16 /* GPIO16 */
|
||||
MOVS R5, #1 /* HIGH */
|
||||
MCRR p0, #4, R4, R5, c0 /* gpioc_bit_out_put(16, 1) */
|
||||
|
||||
/* Delay ~500ms */
|
||||
LDR R2, =2000000 /* 2M cycles */
|
||||
delay_on:
|
||||
SUBS R2, R2, #1 /* Decrement */
|
||||
BNE delay_on /* Loop until zero */
|
||||
|
||||
/* Turn LED OFF */
|
||||
MOVS R4, #16 /* GPIO16 */
|
||||
MOVS R5, #0 /* LOW */
|
||||
MCRR p0, #4, R4, R5, c0 /* gpioc_bit_out_put(16, 0) */
|
||||
|
||||
/* Delay ~500ms */
|
||||
LDR R2, =2000000 /* 2M cycles */
|
||||
delay_off:
|
||||
SUBS R2, R2, #1 /* Decrement */
|
||||
BNE delay_off /* Loop until zero */
|
||||
|
||||
B blink_loop /* Repeat forever */
|
||||
```
|
||||
|
||||
**Delay Calculation:**
|
||||
- 2,000,000 cycles at ~4-12 MHz boot clock
|
||||
- Approximately 500ms per delay
|
||||
- Total period: ~1 second (1Hz blink rate)
|
||||
|
||||
---
|
||||
|
||||
## Build Process
|
||||
|
||||
### Prerequisites
|
||||
1. **ARM GNU Toolchain** installed and in PATH:
|
||||
- `arm-none-eabi-as` (Assembler)
|
||||
- `arm-none-eabi-ld` (Linker)
|
||||
- `arm-none-eabi-objcopy` (Object copy utility)
|
||||
2. **Python** (for UF2 conversion)
|
||||
3. **uf2conv.py** script (located two directories up: `../../uf2conv.py`)
|
||||
|
||||
### Build Script (`build.bat`)
|
||||
```batch
|
||||
@echo off
|
||||
REM Build script for RP2350 GPIO16 blink
|
||||
|
||||
echo Building GPIO16 blink...
|
||||
|
||||
REM Assemble main code
|
||||
arm-none-eabi-as -mcpu=cortex-m33 -mthumb gpio16_blink.s -o gpio16_blink.o
|
||||
|
||||
REM Assemble image definition
|
||||
arm-none-eabi-as -mcpu=cortex-m33 -mthumb image_def.s -o image_def.o
|
||||
|
||||
REM Link with linker script
|
||||
arm-none-eabi-ld -T linker.ld gpio16_blink.o image_def.o -o gpio16_blink.elf
|
||||
|
||||
REM Create raw binary
|
||||
arm-none-eabi-objcopy -O binary gpio16_blink.elf gpio16_blink.bin
|
||||
|
||||
REM Create UF2 bootloader format
|
||||
python ..\..\uf2conv.py -b 0x10000000 -f 0xe48bff59 -o gpio16_blink.uf2 gpio16_blink.bin
|
||||
```
|
||||
|
||||
### Build Command
|
||||
```powershell
|
||||
.\build.bat
|
||||
```
|
||||
|
||||
### Build Flags Explained
|
||||
|
||||
**`-mcpu=cortex-m33`**
|
||||
- Target the Cortex-M33 processor in RP2350
|
||||
- Enables M33-specific features (stack limits, DSB/ISB instructions, coprocessor)
|
||||
|
||||
**`-mthumb`**
|
||||
- Generate Thumb-2 instruction set (mandatory for Cortex-M)
|
||||
- Provides 16-bit and 32-bit instruction mix for code density
|
||||
|
||||
**`-T linker.ld`**
|
||||
- Use custom linker script to define memory layout
|
||||
- Places code at flash start (0x10000000)
|
||||
|
||||
**UF2 Parameters:**
|
||||
- `-b 0x10000000` - Base address (RP2350 flash start)
|
||||
- `-f 0xe48bff59` - Family ID for RP2350
|
||||
- `-o gpio16_blink.uf2` - Output filename
|
||||
|
||||
---
|
||||
|
||||
## Flashing Instructions
|
||||
|
||||
### Method 1: UF2 Bootloader (Easiest)
|
||||
|
||||
1. **Disconnect** the RP2350 from USB
|
||||
2. **Hold the BOOTSEL button** on the board
|
||||
3. **Connect USB cable** while holding BOOTSEL
|
||||
4. **Release BOOTSEL** - Board appears as USB mass storage device
|
||||
5. **Copy** `gpio16_blink.uf2` to the RP2350 drive
|
||||
6. **Board automatically reboots** and starts running the program
|
||||
|
||||
### Method 2: OpenOCD with Debug Probe
|
||||
|
||||
If you have a debug probe (e.g., Raspberry Pi Debug Probe, CMSIS-DAP):
|
||||
|
||||
```bash
|
||||
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
|
||||
-c "adapter speed 5000" \
|
||||
-c "program gpio16_blink.elf verify reset exit"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging & Troubleshooting
|
||||
|
||||
### Problem: LED doesn't blink
|
||||
|
||||
**Check 1: Coprocessor Access**
|
||||
- Ensure CPACR is set correctly
|
||||
- Without this, MCRR triggers UsageFault
|
||||
- Verify DSB/ISB barriers are present
|
||||
|
||||
**Check 2: GPIO Configuration**
|
||||
- Verify FUNCSEL is set to 5 (SIO)
|
||||
- Check OD bit is cleared (output enabled)
|
||||
- Ensure ISO bit is cleared (pad connected)
|
||||
|
||||
**Check 3: Hardware Connections**
|
||||
- LED polarity (anode to GPIO, cathode to GND)
|
||||
- Current-limiting resistor present
|
||||
- GPIO16 physically connected
|
||||
|
||||
**Check 4: Clock Speed**
|
||||
- Boot clock may vary (4-12 MHz typical)
|
||||
- Delay timing depends on actual clock
|
||||
- Adjust delay constant if needed
|
||||
|
||||
### Problem: Build fails
|
||||
|
||||
**Missing toolchain:**
|
||||
```
|
||||
'arm-none-eabi-as' is not recognized...
|
||||
```
|
||||
Solution: Install ARM GNU Toolchain and add to PATH
|
||||
|
||||
**Missing uf2conv.py:**
|
||||
```
|
||||
python: can't open file 'uf2conv.py'
|
||||
```
|
||||
Solution: Ensure uf2conv.py is in `../../` relative to build directory
|
||||
|
||||
**Assembly errors:**
|
||||
```
|
||||
Error: bad instruction 'mcrr'
|
||||
```
|
||||
Solution: Ensure `-mcpu=cortex-m33` flag is used (M33 supports coprocessor)
|
||||
|
||||
### Problem: Board doesn't appear as USB drive
|
||||
|
||||
**Solution 1:** Try different USB cable (must support data, not just power)
|
||||
**Solution 2:** Hold BOOTSEL earlier (before connecting USB)
|
||||
**Solution 3:** Check USB port functionality
|
||||
|
||||
---
|
||||
|
||||
## Performance Analysis
|
||||
|
||||
### Instruction Cycle Counts (Approximate)
|
||||
|
||||
**Traditional GPIO Write (3-4 instructions):**
|
||||
```assembly
|
||||
LDR R0, =SIO_BASE ; 2 cycles
|
||||
LDR R1, =GPIO16_BIT ; 2 cycles
|
||||
STR R1, [R0, #offset] ; 2 cycles
|
||||
; Total: ~6 cycles + memory latency
|
||||
```
|
||||
|
||||
**Coprocessor GPIO Write (2 instructions):**
|
||||
```assembly
|
||||
MOVS R4, #16 ; 1 cycle
|
||||
MOVS R5, #1 ; 1 cycle
|
||||
MCRR p0, #4, R4, R5, c0 ; 1 cycle (hardware accelerated)
|
||||
; Total: ~3 cycles
|
||||
```
|
||||
|
||||
**Performance Gain: ~50% faster execution**
|
||||
|
||||
### Memory Usage
|
||||
|
||||
| Section | Size | Location |
|
||||
|-------------|---------|----------------|
|
||||
| Vector Table| 8 bytes | 0x10000000 |
|
||||
| Code (.text)| ~140 bytes | 0x10000008 |
|
||||
| Total Flash | ~148 bytes | Flash |
|
||||
| Stack | 32 KB | RAM (0x2007A000-0x20082000) |
|
||||
|
||||
**UF2 File Size:** 512 bytes (minimum UF2 block size)
|
||||
|
||||
---
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Extending to Multiple GPIOs
|
||||
|
||||
To control multiple GPIOs with coprocessor:
|
||||
|
||||
```assembly
|
||||
; Blink GPIO16 and GPIO17 alternately
|
||||
blink_multi:
|
||||
; Turn GPIO16 ON, GPIO17 OFF
|
||||
MOVS R4, #16
|
||||
MOVS R5, #1
|
||||
MCRR p0, #4, R4, R5, c0
|
||||
|
||||
MOVS R4, #17
|
||||
MOVS R5, #0
|
||||
MCRR p0, #4, R4, R5, c0
|
||||
|
||||
; Delay...
|
||||
|
||||
; Turn GPIO16 OFF, GPIO17 ON
|
||||
MOVS R4, #16
|
||||
MOVS R5, #0
|
||||
MCRR p0, #4, R4, R5, c0
|
||||
|
||||
MOVS R4, #17
|
||||
MOVS R5, #1
|
||||
MCRR p0, #4, R4, R5, c0
|
||||
```
|
||||
|
||||
### Reading GPIO with Coprocessor
|
||||
|
||||
The RP2350 also supports GPIO read operations:
|
||||
|
||||
```assembly
|
||||
; Read GPIO state using MRRC (Move to ARM Registers from Coprocessor)
|
||||
MOVS R4, #16 ; GPIO to read
|
||||
MRRC p0, #opcode, R4, R5, c0 ; Result in R5
|
||||
```
|
||||
|
||||
*(Exact opcode may vary - consult RP2350 datasheet)*
|
||||
|
||||
### Interrupt-Driven Delays
|
||||
|
||||
For production code, replace busy-wait delays with timer interrupts:
|
||||
|
||||
```assembly
|
||||
; Configure SysTick for 1ms interrupts
|
||||
LDR R0, =0xE000E010 ; SysTick base
|
||||
LDR R1, =4000 ; Reload value for 1ms at 4MHz
|
||||
STR R1, [R0, #4] ; SYST_RVR
|
||||
MOV R1, #7 ; Enable + interrupt + clock
|
||||
STR R1, [R0, #0] ; SYST_CSR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
### Official Documentation
|
||||
1. **RP2350 Datasheet** - Complete hardware reference
|
||||
- Section on GPIO Coprocessor (CP0)
|
||||
- Register maps and bit definitions
|
||||
- Electrical characteristics
|
||||
|
||||
2. **ARM Cortex-M33 Technical Reference Manual**
|
||||
- Coprocessor interface specification
|
||||
- MCRR/MRRC instruction encoding
|
||||
- Stack limit and security features
|
||||
|
||||
3. **ARM Architecture Reference Manual (ARMv8-M)**
|
||||
- Thumb-2 instruction set
|
||||
- System control registers (CPACR, etc.)
|
||||
- Exception handling
|
||||
|
||||
### Instruction Reference
|
||||
|
||||
**MCRR (Move to Coprocessor from ARM Registers):**
|
||||
```
|
||||
MCRR coproc, opc1, Rt, Rt2, CRm
|
||||
coproc: p0-p15 (coprocessor number)
|
||||
opc1: 4-bit operation code
|
||||
Rt: First source register
|
||||
Rt2: Second source register
|
||||
CRm: Coprocessor register
|
||||
```
|
||||
|
||||
**DSB (Data Synchronization Barrier):**
|
||||
- Ensures completion of memory operations
|
||||
- Required after CPACR modification
|
||||
|
||||
**ISB (Instruction Synchronization Barrier):**
|
||||
- Flushes instruction pipeline
|
||||
- Required after system control changes
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
### Version 1.0 (October 2025)
|
||||
- Initial release
|
||||
- Implements GPIO16 blink using coprocessor
|
||||
- 500ms on/off cycle
|
||||
- CPACR enablement for CP0 access
|
||||
- Proper initialization sequence
|
||||
|
||||
---
|
||||
|
||||
## License & Credits
|
||||
|
||||
This code is provided as-is for educational and experimental purposes.
|
||||
|
||||
**Created for:** Bare-metal RP2350 development
|
||||
**Target Board:** Raspberry Pi Pico 2 (RP2350)
|
||||
**Architecture:** ARM Cortex-M33
|
||||
**Assembly Syntax:** GNU AS (Unified ARM Syntax)
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
1. **Add interrupt-driven timing** instead of busy-wait loops
|
||||
2. **Implement GPIO read operations** using MRRC
|
||||
3. **Create PWM effects** using variable duty cycles
|
||||
4. **Multi-GPIO patterns** (Knight Rider, binary counter, etc.)
|
||||
5. **Add error handling** for fault exceptions
|
||||
6. **Clock configuration** for precise timing
|
||||
7. **Low-power modes** using WFI (Wait For Interrupt)
|
||||
|
||||
---
|
||||
|
||||
## Contact & Support
|
||||
|
||||
For issues, questions, or contributions related to this coprocessor implementation, please refer to:
|
||||
- RP2350 community forums
|
||||
- ARM Cortex-M33 documentation
|
||||
- Raspberry Pi Pico SDK examples
|
||||
|
||||
**Happy coding with coprocessors!** 🚀
|
||||
## License
|
||||
[Apache License 2.0](https://github.com/mytechnotalent/RP2350_Blink_Driver/blob/main/LICENSE)
|
||||
|
||||
Reference in New Issue
Block a user