3. 裸机串口轮询输出
任务说明
本案例在 RISC-V 裸机环境下初始化 UART 控制器,使用轮询方式逐字节发送字符串,最终在串口终端打印 Hello RISC-V。目标平台以 QEMU virt 机器为例,UART 采用 16550 兼容控制器,基地址 0x10000000。
关键寄存器与流程
THR(偏移 0x00):发送保持寄存器,写入待发送字节。LSR(偏移 0x05):线路状态寄存器,bit5(THRE)为 1 表示发送保持寄存器空,可写入下一字节。- 初始化:设置
LCR(偏移 0x03)为 0x03,即 8 位数据、无校验、1 位停止位。 - 发送:对每个字符轮询
LSR的 THRE 位,置位后写入THR。
完整代码
以下为最小可复现实现,包含 UART 初始化、轮询发送函数与 main 入口。链接脚本与启动代码沿用项目既有配置,本案例只关注 UART 部分。
#include <stdint.h>
#define UART0_BASE 0x10000000UL
#define UART_THR (UART0_BASE + 0x00)
#define UART_LCR (UART0_BASE + 0x03)
#define UART_LSR (UART0_BASE + 0x05)
#define UART_LSR_THRE 0x20
static inline void mmio_write8(uintptr_t addr, uint8_t v)
{
*(volatile uint8_t *)addr = v;
}
static inline uint8_t mmio_read8(uintptr_t addr)
{
return *(volatile uint8_t *)addr;
}
void uart_init(void)
{
/* 8 位数据位,无校验,1 位停止位 */
mmio_write8(UART_LCR, 0x03);
}
void uart_putc(char c)
{
/* 轮询等待发送保持寄存器为空 */
while ((mmio_read8(UART_LSR) & UART_LSR_THRE) == 0) {
/* busy wait */
}
mmio_write8(UART_THR, (uint8_t)c);
}
void uart_puts(const char *s)
{
while (*s) {
uart_putc(*s++);
}
}
int main(void)
{
uart_init();
uart_puts("Hello RISC-V\n");
for (;;) {
/* 保持运行 */
}
return 0;
}
运行与验证
使用 QEMU 运行(假设已编译出 kernel.elf,入口为 main):
qemu-system-riscv64 -machine virt -nographic -bios none -kernel kernel.elf
观察串口终端输出,若出现 Hello RISC-V 并换行,即判定通过。若终端无输出,依次检查:UART 基地址是否为 0x10000000、LCR 是否写入 0x03、LSR 的 THRE 位轮询条件是否正确、字符串是否以 \0 结尾。