4. 裸机串口轮询输入

任务目标

在 RISC-V 裸机环境下,用轮询方式从 UART 接收字符,并把收到的字符原样回显到串口终端。核心是:不断查询 UART 状态寄存器,判断接收数据是否就绪,就绪则读取数据寄存器并写回发送寄存器。

环境与依赖

  • 目标平台:RISC-V 裸机(如 QEMU virt 机器或真实开发板)。
  • 工具链:riscv64-unknown-elf-gcc、riscv64-unknown-elf-objcopy。
  • UART 基址:本案例以 QEMU virt 的 NS16550A 为例,基址 0x10000000。若换平台,只需修改 UART_BASE。

寄存器与轮询逻辑

NS16550A 使用内存映射寄存器,偏移 0x00 为数据寄存器,偏移 0x05 为线路状态寄存器(LSR)。LSR 的 bit0(0x01)为 1 表示接收数据就绪,bit5(0x20)为 1 表示发送保持寄存器为空。轮询接收即循环读取 LSR,检测 bit0;发送前检测 bit5。

读 LSR (0x05) bit0=1 ? 读 RBR (0x00) 写 THR (0x00) bit5=1 ? 否 否 是 是

完整实现

#define UART_BASE 0x10000000UL
#define UART_RBR  0x00   /* 接收缓冲寄存器 */
#define UART_THR  0x00   /* 发送保持寄存器 */
#define UART_LSR  0x05   /* 线路状态寄存器 */
#define LSR_DR    0x01   /* 接收数据就绪 */
#define LSR_THRE  0x20   /* 发送保持寄存器空 */

static inline unsigned char mmio_read(unsigned long addr) {
    return *(volatile unsigned char *)addr;
}

static inline void mmio_write(unsigned long addr, unsigned char val) {
    *(volatile unsigned char *)addr = val;
}

/* 轮询发送一个字符 */
static void uart_putc(char c) {
    while (!(mmio_read(UART_BASE + UART_LSR) & LSR_THRE))
        ;
    mmio_write(UART_BASE + UART_THR, (unsigned char)c);
}

/* 轮询接收一个字符(阻塞直到有数据) */
static char uart_getc(void) {
    while (!(mmio_read(UART_BASE + UART_LSR) & LSR_DR))
        ;
    return (char)mmio_read(UART_BASE + UART_RBR);
}

/* 主循环:收到即回显 */
void main(void) {
    for (;;) {
        char c = uart_getc();
        uart_putc(c);
    }
}

编译与运行

将上述代码保存为 main.c,配合启动汇编与链接脚本(本案例聚焦 UART 逻辑,启动文件按平台常规提供)。以 QEMU virt 为例:

riscv64-unknown-elf-gcc -march=rv64imac -mabi=lp64 -nostdlib \
  -T link.ld start.S main.c -o uart.elf
riscv64-unknown-elf-objcopy -O binary uart.elf uart.bin
qemu-system-riscv64 -machine virt -bios none -kernel uart.elf -nographic

验证方法与通过条件

  1. 启动 QEMU 后,终端进入交互状态,无报错退出。
  2. 在终端键盘输入任意字符(如 abc),终端应逐字符原样显示 abc。
  3. 连续输入多行文本,每个字符都能即时回显,说明轮询接收与发送链路均正常。

满足以上条件即判定通过。若输入无回显,优先检查 UART_BASE 是否与平台一致,以及 LSR 位定义是否正确。