Skip to content

TXE & RXNE

1. TXE (Transmit Empty) —— 发送寄存器“空”了

  • 全称:Transmit Data Register Empty
  • 人话:“老板(CPU),我的盘子空了,你可以把下一道菜(数据)端给我了!
  • 状态
    • 0 (Reset):盘子里有菜,正在忙着往外运,千万别放新菜,否则会把旧的压烂。
    • 1 (Set):盘子空了,赶紧放新菜,不然我就闲着了。

2. RXNE (Receive Not Empty) —— 接收寄存器“不空”

  • 全称:Receive Data Register Not Empty
  • 人话:“老板(CPU),信箱里有信(数据)来了,赶紧拿走!
  • 状态
    • 0 (Reset):信箱是空的,别来拿,拿了也是空气(乱码)。
    • 1 (Set):信箱里有货,快读走!如果你不读走,下一封信来了就会把这封信挤爆(发生 Overrun 错误)。

二、 核心原理:为什么需要它们?(双缓冲机制)

新手容易以为:单片机发送数据就是 CPU 直接把数据扔到电线上。 错! 其实中间隔着两个“房间”。

我们以 发送 (TX) 为例,内部其实有两个寄存器:

  1. TDR (发送数据寄存器):相当于备餐区
  2. Shift Register (移位寄存器):相当于传菜员(真正干活把数据一位一位挤出去的地方)。

TXE 的工作流程:

  1. 你写代码 SPI_SendData(0x55)
  2. 数据 0x55 掉进 TDR (备餐区)。此时 TXE = 0(忙)。
  3. 硬件自动检查:传菜员(移位寄存器)有空吗?
  4. 如果有空,硬件瞬间把 0x55TDR 挪到 移位寄存器
  5. 重点来了! 此时数据还在移位寄存器里慢慢发,但 TDR 已经空了
  6. TXE 变回 1
  7. CPU 看到 TXE=1,虽然上一个字节还没发完(还在传菜员手里),但 CPU 已经可以把下一个字节放入 TDR 了。

这就是“流水线”作业,效率极高!

三、 代码怎么写?(标准套路)

在写“轮询模式”(Polling)的驱动时,这两个标志位是必须判断的。

1. 发送数据(标准姿势)

C

// 这里的 waiting 就是在等 TXE 变 1
// 意思:只要 TXE 不是 1 (还没空),我就死等,不往里写
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);

// 终于空了,写入数据,标志位会自动清除
SPI_I2S_SendData(SPI1, byte);

2. 接收数据(标准姿势)

C

// 这里的 waiting 就是在等 RXNE 变 1
// 意思:只要 RXNE 不是 1 (还没收到),我就死等,不读
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);

// 终于有数据了,读出来,RXNE 会自动变回 0
data = SPI_I2S_ReceiveData(SPI1);

四、 资深工程师的“避坑指南” (如果你用 STM32,必看!)

这里有两个新手一定会掉进去的深坑,请务必记在小本本上:

坑 1:TXE 不等于“发送完成”!(最痛的领悟)

  • 误区:很多新手在 SPI 发送完最后一个字节后,看到 TXE == 1,就立马拉高 CS 引脚(结束通信)。
  • 后果数据发丢了!
  • 原因:回顾上面的原理,TXE=1 只是代表 TDR(备餐区) 空了,但数据还在 移位寄存器(传菜员) 里正在发呢!你这时候拉高 CS,传菜员干到一半就被你掐断了。
  • 解决:发送最后一个字节并想结束传输时,不能只看 TXE,必须看 TC (Transmission Complete) 标志!
    • Wait TXE=1 -> 发送数据
    • Wait TXE=1 (表示放入TDR成功)
    • Wait TC=1 (表示移位寄存器也发完了,彻底空闲) -> 拉高 CS

坑 2:RXNE 如果不读,后果很严重 (Overrun 错误)

  • 如果你开了接收中断,但因为某些原因(比如中断里处理太慢,或者逻辑写错了)没有把数据读走(即没有读取 DR 寄存器)。
  • 硬件会发现:“卧槽,新数据来了,老数据还在!”
  • 这时候会触发 ORE (Overrun Error)
  • 后果:一旦触发 ORE,很多单片机(特别是 STM32)的串口或 SPI 会锁死,再也收不到数据,直到你手动清除 ORE 标志。

坑 3:如何清除这两个标志?

  • 不像有些标志位需要你写 ClearFlag() 函数。
  • TXE:只要你往数据寄存器里(Write DR),TXE 自动清零。
  • RXNE:只要你从数据寄存器里(Read DR),RXNE 自动清零。
  • 警告:如果你调试时在 Watch 窗口里查看了 DR 寄存器的值,调试器会把这个值“读”走,导致代码里的 RXNE 突然消失,让你查不出 Bug。

总结

  1. TXE = 能不能新数据?(1=能,0=不能)
  2. RXNE = 有没有到新数据?(1=有,0=没有)
  3. 大坑:TXE 变 1 不代表物理发送结束,断开连接前一定要查 TC