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。
完整实现
#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
验证方法与通过条件
- 启动 QEMU 后,终端进入交互状态,无报错退出。
- 在终端键盘输入任意字符(如
abc),终端应逐字符原样显示abc。 - 连续输入多行文本,每个字符都能即时回显,说明轮询接收与发送链路均正常。
满足以上条件即判定通过。若输入无回显,优先检查 UART_BASE 是否与平台一致,以及 LSR 位定义是否正确。