一、串口的基本概念

串行、异步、全双工

和 51 单片机的串口一样:

  • 串行:数据逐位发送

  • 异步:没有共享时钟,靠约定好的波特率同步

  • 全双工:TX 和 RX 两根线,收发可以同时进行

引脚

UART1 用到两个引脚:

  • TX:发送数据

  • RX:接收数据

开发板上通过 USB 转串口芯片把 USB 信号转成 UART 信号,电脑端看到的是一个虚拟串口。

寄存器

普通通信用到的几个:

寄存器地址作用
UCR1基地址+0x80控制寄存器1,使能 UART
UCR2基地址+0x84控制寄存器2,数据格式、收发使能
UCR3基地址+0x88控制寄存器3,RXD 输入选择
UFCR基地址+0x90FIFO 控制,参考时钟分频
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 单片机的区别

部分51I.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;
}

初始化顺序:

  1. 中断系统初始化

  2. 时钟初始化

  3. LED、蜂鸣器、按键初始化

  4. GPT1 初始化(用于延时)

  5. 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;或用中断

更多推荐