Monday, May 08, 2006

newlib printf through UART on GNU ARM toolchain

To make function like printf and puts to work on the GNUARM toolchain, the Angel SWI handler for the AngelSWI_Reason_Write command needs to be implemented. This command is indicated through value 5 in register r0 when the Angel SWI call is made. Register r1 will point to an array of 3 32-bit values. The first is a file pointer which I will ignore because I do not do any other file operation except writing to stdout. The second is a pointer to the array of char's to print. The third contains the length of the string. Below is the code snippet for the SWI handler:
__swi_handler:
cmp r0,#5
bne .Ldone

/* skip file pointer and store char pointer in r0
and length into r1 */

ldr r0,[r1,#4]!
ldr r1,[r1,#4]

add r1,r1,r0 /* now r1 points to end of chars to print */

mov r2,#UARTA_BASE

.Lnext:
ldr r3,[r2,#LSR]
ands r3,r3,#LSR_THRE
beq .Lnext

ldrb r3,[r0],#1
str r3,[r2,#THR]

/* append CR if we see a NL */

cmp r3,#0x0A /* NL? */
moveq r3,#0x0D /* CR */
streq r3,[r2,#THR]

cmp r0,r1
blt .Lnext
mov r0,#0
.Ldone:
movs pc,lr
I am being a little bit sloppy above by not checking that the SWI instruction that triggers the call does in fact have a 0x123456 as its argument which is required for Angel SWI. Of course, the ARM exception vectors needs to be configured accordingly:
__vec_start__:
LDR PC, Reset_Addr
LDR PC, Undef_Addr
LDR PC, SWI_Addr
LDR PC, PAbt_Addr
LDR PC, DAbt_Addr
NOP
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr

Reset_Addr: .word start
Undef_Addr: .word Undef_Handler
SWI_Addr: .word __swi_handler
PAbt_Addr: .word PAbt_Handler
DAbt_Addr: .word DAbt_Handler
.word 0
IRQ_Addr: .word IRQ_Handler
FIQ_Addr: .word FIQ_Handler
And the UART needs to be configured properly before use. I have the code below in my crt0.S to initialize the UART. You need to program the DLL and DLH (Divisor Latch registers) according to your UART clock and the desired baud rate:

  mov    r0,#UARTA_BASE
mov r1,#0x07
str r1,[r0,#FCR] /* reset and enable FIFOs */
mov r1,#0x00
str r1,[r0,#IER] /* no interrupt */
mov r1,#0x80
str r1,[r0,#LCR] /* to program divisor */
mov r1,#0x41 /* change this if clock change! */
str r1,[r0,#DLL]
mov r1,#0x00
str r1,[r0,#DLH]
mov r1,#0x03
str r1,[r0,#LCR] /* 8 bit, 1 stop bit */

No comments: