Skip to main content
stdio is a bare-metal replacement for printf and scanf. It has no dependency on libc and is compiled with -nostdlib. All output and input goes directly through the UART driver.

PRINT

void PRINT(const char *s, ...);
Iterates over the format string character by character. When a % specifier is encountered, it pulls the next argument from the varargs list and routes it to the appropriate alphanumeric conversion or UART output function.

Format specifiers

SpecifierTypeConversion
%dintuart_itoa()uart_puts()
%ffloat (promoted to double in varargs)uart_ftoa()uart_puts()
%schar *uart_puts() directly
%cchar (promoted to int in varargs)uart_putc() directly

Implementation

void PRINT(const char *s, ...){
    va_list args;
    va_start(args, s);

    while (*s) {
        if(*s == '%'){
            char buffer[16];
            s++;
            if (*s == 'f') {
                double v = va_arg(args, double);
                uart_ftoa((float)v, buffer);
                uart_puts(buffer);
            }
            if(*s == 'd'){
                int v = va_arg(args, int);
                uart_itoa(v, buffer);
                uart_puts(buffer);
            }
            if(*s == 's'){
                char *v = va_arg(args, char*);
                uart_puts(v);
            }
            if(*s == 'c'){
                char v = (char)va_arg(args, int);
                uart_putc(v);
            }
            s++;
        }
        else{
            uart_putc(*s++);
        }
    }

    va_end(args);
}

Usage in user processes

Process 1 (User/P1/main.c) prints a counter as a decimal integer:
PRINT("----From P1: %d\n", i);
Process 2 (User/P2/main.c) prints a character cycling through the alphabet:
PRINT("----From P2: %c\n", c);

READ

void READ(char *type, void *returnv);
Reads up to 16 characters from UART into a local buffer, then converts the string to the requested type using the alphanumeric library.

Format specifiers

SpecifierConversionOutput type
"%d"uart_atoi()int written to *returnv
"%f"uart_atof()float written to *returnv

Implementation

void READ(char *type, void *returnv){
    char input[16];

    uart_gets_input(input, sizeof(input));

    if(*type == '%'){
        type++;
        if(*type == 'f'){
            *(float *) returnv = uart_atof(input);
        }
        if(*type == 'd'){
            *(int *) returnv = uart_atoi(input);
        }
    }
}

Usage

int value;
READ("%d", &value);

float temp;
READ("%f", &temp);

Critical section requirement

PRINT must be called between disable_irq() and enable_irq() in user processes. Without this guard, the timer IRQ can preempt mid-print and a context switch to another process will interleave output on the UART, producing garbled characters.
Both user processes follow this pattern:
disable_irq();
PRINT("----From P1: %d\n", i);
enable_irq();
The IRQ is re-enabled immediately after PRINT returns so that the scheduler continues to receive timer interrupts normally.

Build docs developers (and LLMs) love