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死等。虽然效率不高,但逻辑简单,非常适合裸机调试。咱们先画个流程图,把整体逻辑理清楚。
流程清楚了,咱们直接上代码。先看发送一个字节:
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,轻量、稳定、不乱码。
调试步骤很简单:
- 硬件连接:蜂鸟E203的UART引脚(TX、RX、GND)连到USB转串口模块,再插电脑。
- 配置串口助手:波特率115200、数据位8、无校验、停止位1。
- 烧录程序:把编译好的裸机程序下载到FPGA。
- 观察输出:复位板子,看串口助手窗口有没有打印信息。
- 发送测试:在串口助手输入字符,看板子能不能正确回显。
这里给一个简单的回显测试代码:
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配合状态位做轮询。实战层面,串口助手是调试利器,回显测试是最快的验证手段。
把这一章的内容吃透,后续的调试工作就有了“眼睛”和“嘴巴”。串口通了,你就能看到程序在干什么,也能给它下指令——这才是嵌入式调试的起点。