Retarget functions to use UART

Real embedded systems operate without sophisticated debuggers, but many library functions depend on semihosting. You must modify, or retarget, these functions to use the hardware of the target instead of the host system.

To retarget printf() to use the PL011 UART of the model:

  1. Write a driver for the UART. Copy and paste the following code into a new file with the filename pl011_uart.c:

    struct pl011_uart {
        volatile unsigned int UARTDR;        // +0x00
        volatile unsigned int UARTECR;       // +0x04
        const volatile unsigned int unused0[4];    // +0x08 to +0x14 reserved
        const volatile unsigned int UARTFR;        // +0x18 - RO
        const volatile unsigned int unused1;       // +0x1C reserved
        volatile unsigned int UARTILPR;      // +0x20
        volatile unsigned int UARTIBRD;      // +0x24
        volatile unsigned int UARTFBRD;      // +0x28
        volatile unsigned int UARTLCR_H;     // +0x2C
        volatile unsigned int UARTCR;        // +0x30
        volatile unsigned int UARTIFLS;      // +0x34
        volatile unsigned int UARTIMSC;      // +0x38
        const volatile unsigned int UARTRIS;       // +0x3C - RO
        const volatile unsigned int UARTMIS;       // +0x40 - RO
        volatile unsigned int UARTICR;       // +0x44 - WO
        volatile unsigned int UARTDMACR;     // +0x48
    // Instance of the dual timer
    struct pl011_uart* uart;
    // ------------------------------------------------------------
    void uartInit(void* addr) {
        uart = (struct pl011_uart*) addr;
        // Ensure UART is disabled
        uart->UARTCR  = 0x0;
        // Set UART 0 Registers
        uart->UARTECR   = 0x0;  // Clear the receive status (i.e. error) register
        uart->UARTIBRD = PL011_IBRD_DIV_38400;
        uart->UARTFBRD = PL011_FBRD_DIV_38400;
        uart->UARTIMSC = 0x0;                     // Mask out all UART interrupts
        uart->UARTICR  = PL011_ICR_CLR_ALL_IRQS;  // Clear interrupts
        uart->UARTCR  = 0x0 | PL011_CR_UART_ENABLE | PL011_CR_TX_ENABLE | PL011_CR_RX_ENABLE;
    // ------------------------------------------------------------
    int fputc(int c, FILE *f) {
        // Wait until FIFO or TX register has space
        while ((uart->UARTFR & PL011_FR_TXFF_FLAG) != 0x0) {}
        // Write packet into FIFO/tx register
        uart->UARTDR = c;
        // Model requires us to manually send a carriage return
        if ((char)c == '\n') {
            while ((uart->UARTFR & PL011_FR_TXFF_FLAG) != 0x0){}
            uart->UARTDR = '\r';
       return 0;
  2. Modify hello_world.c to use the UART driver, so that the updated file contains:

    #include <stdio.h>
    #include "pl011_uart.h"
    int main (void) {
      printf("hello world\n");
      return 0;

    By redefining fputc() to use the UART you have retargeted printf(). This is because printf() ultimately calls fputc().

  3. Rebuild the image:

    $ armclang -c -g --target=aarch64-arm-none-eabi startup.s
    $ armclang -c -g --target=aarch64-arm-none-eabi hello_world.c
    $ armclang -c -g --target=aarch64-arm-none-eabi pl011_uart.c
    $ armlink --scatter=scatter.txt --entry=start64 startup.o pl011_uart.o hello_world.o
  4. Disassemble the image:

    $ fromelf --text -c __image.axf --output=disasm.txt

    The disassembly in disasm.txt now shows no calls to _sys_write (although other semihosting functions such as _sys_exit will be present).

Previous Next