4. 裸机开发环境搭建

UART串口,说白了就是嵌入式世界里最基础的“对话通道”。不管你是调试内核、打印日志,还是跟传感器交换数据,十有八九都得靠它。我个人习惯,拿到一块新板子,第一件事就是先把串口调通——串口通了,心就定了。

这一章,咱们就围绕蜂鸟E203的UART模块,把协议、寄存器、轮询收发、串口助手调试这些事,一个一个说清楚。

4.1 UART协议基础:一帧数据长什么样?

UART是异步串行通信,没有时钟线。收发双方得事先约定好波特率、数据位、校验位、停止位。一帧数据,就像一列小火车:

字段 长度 说明
起始位 1 bit 拉低电平,告诉接收方“准备收数据”
数据位 5~8 bit 通常用8位,LSB先发
校验位 0/1 bit 奇校验或偶校验,可选
停止位 1/1.5/2 bit 拉高电平,表示帧结束

为什么要有起始位和停止位?你想想看,没有时钟线,接收方怎么知道什么时候开始采样?起始位的下降沿,就是那个“开饭了”的信号。我在项目中遇到过,有人把停止位设成2位,结果波特率一高,数据吞吐直接砍半——其实大多数场景1位停止位就够了。

关键参数速查

  • 波特率:115200 bps 最常用,调试够快,线长也不容易出错
  • 数据位:8位,兼容ASCII和二进制
  • 校验位:无校验(NONE),省事
  • 停止位:1位

4.2 蜂鸟E203 UART寄存器详解

蜂鸟E203的UART模块,寄存器不多,但个个关键。我刚开始看的时候,觉得就那么几个寄存器,应该不难。结果一上手,发现控制位和状态位的配合,还是有点讲究的。

先看寄存器列表:

偏移地址 寄存器名 读写 功能
0x00 UART_TXDATA 只写 发送数据寄存器,写入即触发发送
0x04 UART_RXDATA 只读 接收数据寄存器,读取得到收到的字节
0x08 UART_TXCTRL 读写 发送控制寄存器,使能、中断控制
0x0C UART_RXCTRL 读写 接收控制寄存器,使能、中断控制
0x10 UART_IE 读写 中断使能寄存器
0x14 UART_IP 只读 中断挂起寄存器,查看哪个中断发生了
0x18 UART_DIV 读写 波特率分频寄存器,核心配置

这里重点说两个:UART_DIV 和 UART_TXDATA / UART_RXDATA。

UART_DIV 决定了波特率。计算公式很简单:

波特率 = 系统时钟频率 / (UART_DIV + 1)

比如系统时钟 16 MHz,想要 115200 bps:

UART_DIV = 16000000 / 115200 - 1 ≈ 138

嗯,这里要注意:除法结果要四舍五入取整。我见过有人直接截断,结果波特率偏了2%,数据偶尔就错位了。

UART_TXDATA 和 UART_RXDATA 都是8位有效数据,但读取/写入时,要留意状态位。发送前,得确认发送FIFO不满;接收前,得确认接收FIFO非空。这就是轮询模式的核心逻辑。

个人经验:调试时,先把UART_DIV算好写进去,然后读回来确认一下。有些板子的时钟频率跟手册标称值有偏差,读回来验证能省很多排查时间。

4.3 轮询模式发送与接收

轮询模式,说白了就是CPU死等。虽然效率不高,但逻辑简单,非常适合裸机调试。咱们先画个流程图,把整体逻辑理清楚。

UART轮询模式收发流程图 UART轮询模式收发流程图 初始化UART 需要发送数据? 检查TX FIFO是否满 写入UART_TXDATA 检查RX FIFO是否非空 读取UART_RXDATA

流程清楚了,咱们直接上代码。先看发送一个字节:

void uart_send_byte(uint8_t data)
{
    // 等待发送FIFO不满
    while (*(volatile uint32_t *)UART_TXCTRL & (1 << 0));
    // 写入数据
    *(volatile uint32_t *)UART_TXDATA = data;
}

接收一个字节:

uint8_t uart_recv_byte(void)
{
    // 等待接收FIFO非空
    while (!(*(volatile uint32_t *)UART_RXCTRL & (1 << 0)));
    // 读取数据
    return (uint8_t)(*(volatile uint32_t *)UART_RXDATA);
}

发送字符串就更简单了,循环调用发送函数就行:

void uart_send_string(const char *str)
{
    while (*str)
    {
        uart_send_byte(*str++);
    }
}

我曾经踩过的坑:轮询发送时,如果TX FIFO满了还继续写,数据会丢失。我一开始没加等待判断,结果打印出来的字符串总是缺几个字符。排查了半天,才发现是发送太快,FIFO溢出了。所以,发送前一定要检查FIFO状态,别偷懒。

4.4 串口助手调试实战

代码写好了,怎么验证?串口助手就是你的“照妖镜”。我个人习惯用SSCOM或者MobaXterm,轻量、稳定、不乱码。

调试步骤很简单:

  1. 硬件连接:蜂鸟E203的UART引脚(TX、RX、GND)连到USB转串口模块,再插电脑。
  2. 配置串口助手:波特率115200、数据位8、无校验、停止位1。
  3. 烧录程序:把编译好的裸机程序下载到FPGA。
  4. 观察输出:复位板子,看串口助手窗口有没有打印信息。
  5. 发送测试:在串口助手输入字符,看板子能不能正确回显。

这里给一个简单的回显测试代码:

void uart_echo_test(void)
{
    uint8_t ch;
    uart_send_string("UART Echo Test Started\r\n");
    while (1)
    {
        ch = uart_recv_byte();
        uart_send_byte(ch);
    }
}

你想想看,如果串口助手发送一个'A',板子回显一个'A',说明收发通路都通了。如果没反应,先检查接线,再检查波特率配置。我遇到过最诡异的一次,是USB转串口模块的TX和RX接反了——嗯,这种低级错误,犯过一次就记住了。

调试小技巧:如果串口打印乱码,99%是波特率不匹配。用示波器看TX引脚的波形,量一下位宽,反推实际波特率,跟配置值对比,偏差超过3%就容易丢数据。

4.5 本章小结

UART串口通信,说难不难,说简单也不简单。协议层面,起始位、数据位、停止位这些概念要烂熟于心。寄存器层面,UART_DIV算准波特率,TXDATA和RXDATA配合状态位做轮询。实战层面,串口助手是调试利器,回显测试是最快的验证手段。

把这一章的内容吃透,后续的调试工作就有了“眼睛”和“嘴巴”。串口通了,你就能看到程序在干什么,也能给它下指令——这才是嵌入式调试的起点。