Files
Embedded-Hacking/drivers/0x01_uart_asm_arm/uart.s
2026-03-27 11:19:24 -04:00

150 lines
7.2 KiB
ArmAsm

/**
* FILE: uart.s
*
* DESCRIPTION:
* RP2350 UART Functions.
*
* BRIEF:
* Provides UART initialization, transmit, and receive functions for
* UART0 on the RP2350.
*
* AUTHOR: Kevin Thomas
* CREATION DATE: November 27, 2025
* UPDATE DATE: November 27, 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 Release UART0 from reset and wait until it is ready.
*
* @details Clears the UART0 reset bit in the Reset controller (RESETS->RESET)
* and polls the corresponding bit in RESETS->RESET_DONE until the
* UART0 block is no longer in reset. This ensures UART registers are
* accessible before configuring the peripheral.
*
* @param None
* @retval None
*/
.global UART_Release_Reset
.type UART_Release_Reset, %function
UART_Release_Reset:
ldr r0, =RESETS_RESET // load RESETS->RESET address
ldr r1, [r0] // read RESETS->RESET value
bic r1, r1, #(1<<26) // clear UART0 reset bit
str r1, [r0] // write value back to RESETS->RESET
.UART_Release_Reset_Wait:
ldr r0, =RESETS_RESET_DONE // load RESETS->RESET_DONE address
ldr r1, [r0] // read RESETS->RESET_DONE value
tst r1, #(1<<26) // test UART0 reset-done bit
beq .UART_Release_Reset_Wait // loop until UART0 is out of reset
bx lr // return
/**
* @brief Initialize UART0 (pins, baud divisors, line control and enable).
*
* @details Configures IO_BANK0 pins 0 (TX) and 1 (RX) to the UART function
* and programs the corresponding pad controls in PADS_BANK0. It
* programs the integer and fractional baud divisors (UARTIBRD and
* UARTFBRD), configures UARTLCR_H for 8-bit transfers and FIFOs,
* and enables the UART (UARTCR: UARTEN + TXE + RXE).
* The routine assumes the UART0 base is available at the
* `UART0_BASE` symbol. The selected divisors (IBRD=6, FBRD=33) are
* chosen to match the expected peripheral clock; if your UART
* peripheral clock differs, adjust these values accordingly.
*
* @param None
* @retval None
*/
.global UART_Init
.type UART_Init, %function
UART_Init:
ldr r0, =IO_BANK0_BASE // load IO_BANK0 base
ldr r1, =2 // FUNCSEL = 2 -> select UART function
str r1, [r0, #4] // write FUNCSEL to GPIO0_CTRL (pin0 -> TX)
str r1, [r0, #0x0c] // write FUNCSEL to GPIO1_CTRL (pin1 -> RX)
ldr r0, =PADS_BANK0_BASE // load PADS_BANK0 base
add r0, r0, #0x04 // compute PAD[0] address (PADS + 0x04)
ldr r1, =0x04 // pad config value for TX
str r1, [r0] // write PAD0 config (TX pad)
ldr r0, =PADS_BANK0_BASE // load PADS_BANK0 base again
add r0, r0, #0x08 // compute PAD[1] address (PADS + 0x08)
ldr r1, =0x40 // pad config value for RX (pulldown/IE)
str r1, [r0] // write PAD1 config (RX pad)
ldr r0, =UART0_BASE // load UART0 base address
ldr r1, =0 // prepare 0 to disable UARTCR
str r1, [r0, #0x30] // UARTCR = 0 (disable UART while configuring)
ldr r1, =6 // integer baud divisor (IBRD = 6)
str r1, [r0, #0x24] // UARTIBRD = 6 (integer baud divisor)
ldr r1, =33 // fractional baud divisor (FBRD = 33)
str r1, [r0, #0x28] // UARTFBRD = 33 (fractional baud divisor)
ldr r1, =112 // UARTLCR_H = 0x70 (FIFO enable + 8-bit)
str r1, [r0, #0x2c] // UARTLCR_H = 0x70
ldr r1, =3 // RXE/TXE mask
lsl r1, r1, #8 // shift RXE/TXE into bit positions 8..9
orr r1, r1, #1 // set UARTEN bit (bit 0)
str r1, [r0, #0x30] // UARTCR = enable (UARTEN + TXE + RXE)
bx lr // return
/**
* @brief UART0 transmit (blocking).
*
* @details Waits for TX FIFO to be not full, then writes the lowest 8 bits of r0 to UART0.
* Data to send must be in r0 on entry.
*
* @param r0: byte to transmit (lower 8 bits used)
* @retval None
*/
.global UART0_Out
.type UART0_Out, %function
UART0_Out:
.UART0_Out_Push_Registers:
push {r4-r12, lr} // push registers r4-r12, lr to the stack
.UART0_Out_loop:
ldr r4, =UART0_BASE // base address for uart0 registers
ldr r5, [r4, #0x18] // read UART0 flag register UARTFR into r5
ldr r6, =32 // mask for bit 5, TX FIFO full (TXFF)
ands r5, r5, r6 // isolate TXFF bit and set flags
bne .UART0_Out_loop // if TX FIFO is full, loop
ldr r6, =0xff // mask for the 8 lowest bits
ands r0, r0, r6 // mask off upper bits of r0, keep lower 8 bits
str r0, [r4, #0] // write data to UARTDR
.UART0_Out_Pop_Registers:
pop {r4-r12, lr} // pop registers r4-r12, lr from the stack
bx lr // return
/**
* @brief UART0 receive (blocking).
*
* @details Waits for RX FIFO to be not empty, then reads a byte from UART0 into r0.
*
* @param None
* @retval r0: received byte (lower 8 bits valid)
*/
.global UART0_In
.type UART0_In, %function
UART0_In:
.UART0_In_Push_Registers:
push {r4-r12, lr} // push registers r4-r12, lr to the stack
.UART0_In_loop:
ldr r4, =UART0_BASE // base address for uart0 registers
ldr r5, [r4, #0x18] // read UART0 flag register UARTFR into r5
ldr r6, =16 // mask for bit 4, RX FIFO empty RXFE
ands r5, r5, r6 // isolate RXFE bit and set flags
bne .UART0_In_loop // if RX FIFO is empty, loop
ldr r0, [r4, #0] // load data from UARTDR into r0
.UART0_In_Pop_Registers:
pop {r4-r12, lr} // pop registers r4-r12, lr from the stack
bx lr // return