嵌入式学习 day55:ARM汇编——串口通信
一、串口的基本概念
串行、异步、全双工
和 51 单片机的串口一样:
-
串行:数据逐位发送
-
异步:没有共享时钟,靠约定好的波特率同步
-
全双工:TX 和 RX 两根线,收发可以同时进行
引脚
UART1 用到两个引脚:
-
TX:发送数据
-
RX:接收数据
开发板上通过 USB 转串口芯片把 USB 信号转成 UART 信号,电脑端看到的是一个虚拟串口。
寄存器
普通通信用到的几个:
| 寄存器 | 地址 | 作用 |
|---|---|---|
| UCR1 | 基地址+0x80 | 控制寄存器1,使能 UART |
| UCR2 | 基地址+0x84 | 控制寄存器2,数据格式、收发使能 |
| UCR3 | 基地址+0x88 | 控制寄存器3,RXD 输入选择 |
| UFCR | 基地址+0x90 | FIFO 控制,参考时钟分频 |
| USR1 | 基地址+0x94 | 状态寄存器1,接收状态 |
| USR2 | 基地址+0x98 | 状态寄存器2,发送状态 |
| UBIR | 基地址+0xA4 | 波特率分子 |
| UBMR | 基地址+0xA8 | 波特率分母 |
| URXD | 基地址+0x00 | 接收数据 |
| UTXD | 基地址+0x40 | 发送数据 |
UART1 基地址 0x0202_0000,UCR1 地址 0x0202_0080,依此类推。
二、UART1 初始化
初始化分六步:
1. 引脚复用
c
IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0); IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0); IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0); IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
把 TX、RX 两个引脚复用成 UART1 功能,电气特性配 0x10B0。和 LED、蜂鸣器一样,先复用、再配电气特性。
2. 软件复位
c
UART1->UCR2 &= ~(1 << 0);
UCR2 的 bit0 是 SRST。写 0 触发软件复位,复位收发状态机、FIFO 和部分寄存器。硬件在 4 个模块时钟周期后自动结束。
3. 配置 UCR2
c
unsigned int r = UART1->UCR2; r |= (1 << 14); // IRTS = 1,忽略RTS引脚 r &= ~(1 << 8); // PREN = 0,无校验 r &= ~(1 << 6); // STPB = 0,1位停止位 r |= (1 << 5); // WS = 1,8位数据 r |= (1 << 2); // TXEN = 1,使能发送 r |= (1 << 1); // RXEN = 1,使能接收 UART1->UCR2 = r;
配置结果:8位数据、1位停止位、无校验、收发都开、忽略硬件流控。
4. 配置 UCR3
c
UART1->UCR3 |= (1 << 2);
UCR3 的 bit2 是 RXDMUXSEL。手册里明确写了 I.MX6ULL 里这个位必须置 1,否则 RXD 输入不对,收不到数据。
5. 配置波特率
c
UART1->UFCR &= ~(7 << 7); UART1->UFCR |= (5 << 7); UART1->UBIR = 999; UART1->UBMR = 43402;
-
UFCR 的 7~9 位是
RFDIV,参考时钟分频。值 5 对应 1 分频。 -
UBIR:波特率分子减一,
999实际是 1000 -
UBMR:波特率分母减一,
43402实际是 43403
I.MX6ULL 的波特率公式:
text
波特率 = 参考时钟 / (16 × (UBMR + 1) / (UBIR + 1))
假设参考时钟是 80MHz:
text
波特率 = 80,000,000 / (16 × 43403 / 1000)
= 80,000,000 / 694.448
= 115200
正好是 115200。
注意:必须先写 UBIR,再写 UBMR。手册里写了,如果只写一个,BRM 会忽略,直到另一个也写了。
6. 使能 UART
c
UART1->UCR1 |= (1 << 0);
UCR1 的 bit0 是 UARTEN,UART 模块的总开关。放在最后,先配好所有参数,再开总开关。
三、发送数据
发送一个字节
c
void putc(unsigned char c)
{
while((UART1->USR2 & (1 << 3)) == 0);
UART1->UTXD = c;
}
USR2 的 bit3 是 TXDC(Transmit Complete):
-
TXDC = 1:发送就绪,可以写新数据
-
TXDC = 0:发送器忙,要等
流程:轮询 TXDC,等它变 1,然后写 UTXD。
发送字符串
c
void puts(const char *s)
{
while (*s)
{
putc(*s++);
}
putc('\n');
}
遍历字符串,逐个字节调用 putc。最后多发一个 '\n',方便电脑端看的时候换行。
和 51 单片机的区别
| 部分 | 51 | I.MX6ULL |
|---|---|---|
| 等待时机 | 先写SBUF,再等TI | 先等TXDC,再写UTXD |
| 标志位 | TI(发送完成) | TXDC(发送就绪) |
| 清标志 | 手动清TI | 读USR2自动清 |
51 是"写完等发完",I.MX6ULL 是"等能写了再写"。I.MX6ULL 有 FIFO,只要 FIFO 没满就能写,不用等上一个字节完全发出去。
四、接收数据
c
unsigned char getc(void)
{
while((UART1->USR2 & (1 << 0)) == 0);
return UART1->URXD & 0xFF;
}
USR2 的 bit0 是 RDR(Receive Data Ready):
-
RDR = 1:接收缓冲有数据可读
-
RDR = 0:没有数据,等待
流程:轮询 RDR,等它变 1,然后读 URXD。
& 0xFF 是为了只取低 8 位,URXD 的高位可能是状态信息。
轮询接收的特点
getc 是阻塞式的:没有数据就一直在 while 里等。这样主循环会被卡住,做不了别的事。
如果需要边接收边做别的事,要用中断方式。
五、中断接收(可选)
如果要用中断接收,还要:
1. UCR1 的 RRDYEN 置 1
c
UART1->UCR1 |= (1 << 9);
2. 配置 GIC
c
GIC_SetPriority(UART1_IRQn, 0); system_interrupt_register(UART1_IRQn, uart1_irq_handler); GIC_EnableIRQ(UART1_IRQn);
3. 中断服务函数
c
void uart1_irq_handler(void)
{
if ((UART1->USR1 & (1 << 9)) != 0)
{
char c = UART1->URXD;
// 处理接收到的字符
}
}
USR1 的 bit9 是 RRDY(Receiver Ready),有数据可读时置 1。读 URXD 后自动清零。
中断接收的好处是:数据来了才处理,不占用主循环。主循环可以继续跑其他逻辑。
六、主程序
c
int main(void)
{
system_interrupt_init();
clock_init();
led_init();
beep_init();
key_init();
gpt1_init();
uart1_init();
while(1)
{
putc('A');
delay_us(1000 * 1000);
}
return 0;
}
初始化顺序:
-
中断系统初始化
-
时钟初始化
-
LED、蜂鸣器、按键初始化
-
GPT1 初始化(用于延时)
-
UART1 初始化
主循环里每秒发送一个 'A'。
七、今日总结
今天学了串口通信,核心要点:
-
UART 是异步串行全双工通信:TX、RX 两根线,双方波特率必须一致
-
初始化流程:引脚复用 → 软件复位 → 配 UCR2(数据格式)→ 配 UCR3(RXDMUXSEL)→ 配波特率 → 使能 UART
-
UCR2 配置:8位、1停止、无校验、收发使能、忽略 RTS
-
UCR3 配置:RXDMUXSEL = 1,必须
-
波特率公式:
波特率 = 参考时钟 / (16 × (UBMR+1) / (UBIR+1)) -
发送:轮询 USR2 的 TXDC 位,就绪后写 UTXD
-
接收:轮询 USR2 的 RDR 位,就绪后读 URXD;或用中断
更多推荐

所有评论(0)