C2000 的 SCI 没有像 STM32 那样的硬件空闲(IDLE)中断。做 Modbus RTU 这类变长帧协议时,需要靠“总线空闲一段时间”来判断一帧结束,而 SCI 只有收发和 FIFO 中断,没有现成的接收超时。常见做法是用 CPU 定时器,或者在接收中断里软件计时,但这两种都要占 CPU,字符间隔一抖动还容易误判。
这篇记录另一种思路:用 CLB(Configurable Logic Block,可配置逻辑模块)在硬件里搭一个 RX 空闲超时检测器。RX 线持续为高超过设定时间,CLB 直接产生中断,整个过程不占 CPU,阈值也能精确控制。下面把从 SCI 到 CLB tile 的完整配置过一遍。
平台 TMS320F28P550,SysConfig 配置:SCI 负责收字节,CLB 负责检测空闲并产生帧结束中断。
一、实现原理
整体框架
整体信号通路很短:SCI 的 RX 引脚(本例是 GPIO28)→ Input XBAR → CLB1 的 Input 0。
CLB 内部做的事情:
- UART 空闲时 RX 为高,一旦开始收数据,起始位把 RX 拉低,数据位之间也会出现低电平。
- 用一个计数器测量 RX 连续为高的时间:只要 RX 被拉低就清零重计,持续为高就一直累加。
- 计数器累加到设定阈值(即空闲超时)时,说明线路已经空了足够久,这一帧结束了。
- 此时用一个状态机产生一个一次性脉冲(只触发一次,不会因为线路一直空着而连续触发),再由 HLC 把脉冲变成一个 CPU 中断。
这样 CPU 平时只在 SCI 接收中断里收字节,帧结束的判断完全交给 CLB,空闲中断一来就去处理整帧数据。一个 tile 里用到的资源:LUT0、LUT1、Counter0、FSM0、HLC。
二、SCI 基本配置
SCI 这边按普通串口配:
- 实例 mySCI0,波特率 115200,数据位 8,停止位 1,无校验(8N1)。
- 勾选 Use Interrupt 和 Use FIFO,接收用 FIFO 中断收字节。
- RX 中断生成 INT_mySCI0_RX_ISR,在 PIE 里使能。
空闲检测不依赖 SCI 本身的任何超时功能,SCI 只负责把字节收进来,帧结束由后面的 CLB 中断告诉你。
三、RX 引脚经 Input XBAR 接入 CLB
CLB 看不到 SCI 外设内部的信号,只能看引脚电平,所以要把 RX 引脚通过 Input XBAR 引到 CLB:
- INPUT 选 XBAR_INPUT1。
- INPUT Source 选 GPIO28,也就是 SCI 的 RX 脚。
- 勾上 INPUT Lock。
注意: 这里的 INPUT Source 填的是 GPIO 编号,不是芯片封装上的管脚号。两套编号完全不同——如果把封装管脚号当成 GPIO 号填进去,配置语法全对、编译也过,但 CLB 监视的其实是另一根引脚,空闲中断永远不来,而且很难往这个方向怀疑。务必对着数据手册的 GPIO 复用表确认 RX 实际是哪个 GPIO。
四、CLB 实例配置

- 实例 myCLB0,CLB Instance 选 CLB1,勾选 Enable CLB。
- Inputs Used 选 Input 0(就是上一步从 Input XBAR 引进来的 RX)。
- CLB Input 0 下勾选 Register Interrupt Handler、Attach a TILE Config、Initialize TILE。
- CLB Interrupt 生成 INT_myCLB0_ISR,在 PIE 里使能。这个就是后面的“帧结束/空闲”中断。
五、Tile Design(核心逻辑)
tile 里的逻辑是整个方案的核心。按 LUT → 计数器 → 状态机 → HLC 的顺序拆开看,各模块的分工是:
- LUT0:把 RX 取反,得到“RX 当前是否为低”。
- Counter0:空闲计时器,RX 一低就清零,持续为高就累加。
- LUT1:控制计数器什么时候允许计数。
- FSM0:计满阈值时产生一次性脉冲。
- HLC:把脉冲变成 CPU 中断。
5.1 LUT0:RX 取反

- 描述 rx_low = !RX,逻辑式 !i0,Input 0 接 Boundary input 0(也就是引进来的 RX)。
- 输出含义:RX 为低(有数据)时输出 1,RX 为高(空闲)时输出 0。
LUT0 的输出有两个用途:给计数器做复位(Reset),给状态机做输入 e0。
5.2 Counter0:空闲计时器

- 描述 RX idle timer。
- Reset 接 LUT0 Output:RX 一被拉低,计数器立刻清零。也就是每个起始位、每个低电平都会让计时重新开始。
- Counter Enable 接 LUT1 Output:由 LUT1 决定什么时候计数(见 5.3)。
- Direction 选 One(向上计数)。
- Match Reference 1 = 13020:计数到这个值时 Counter 0 Equals Match 1 为真,表示空闲已经够久。
所以这个计数器测的是 RX 连续保持高电平的时间,被打断(拉低)就归零重来,只有真正空闲到 13020 个时钟才会触发 match。
阈值怎么算:
Match = 期望空闲时间 × CLB 模块时钟频率
期望空闲时间 = (每字符位数 / 波特率)× 空闲字符数
8N1 → 每字符 10 位;115200 下一个字符 ≈ 86.8 µs
本例 13020,若 CLB 模块时钟为 100 MHz,对应约 130 µs,差不多是 1.5 个字符时间——正是 Modbus RTU 里常用的字符间隔判据。具体数值要按你自己的 SYSCLK / CLB 时钟和想要的空闲字符数重新换算。
5.3 LUT1:计数使能
- 描述 ctr0_enable = COUNT && RX_high,逻辑式 i0 & !i1 & !i2(配置见图4 下半部分)。
- Input 0 = FSM 0 State 0 Value(s0),Input 1 = FSM 0 State 1 Value(s1),Input 2 = LUT 0 Output(rx_low)。
- 展开:计数使能 = s0 & !s1 & !rx_low,即(状态机处于计数态)且(RX 当前为高)时才计数。
配合 5.2 的 Reset:RX 高时累加;RX 一低,既被 Reset 清零、计数使能也撤掉;状态机一旦完成触发(离开计数态),计数也停下来。
5.4 FSM0:一次性触发状态机

这是保证“一帧只触发一次”的关键。两个状态位 s0、s1 编码三个状态:
| IDLE | 0, 0 | 等待数据,RX 一直空闲 |
| COUNTING | 0, 1 | 帧已开始,正在给空闲计时 |
| DONE | 1, 0 | 空闲超时已到,已触发过一次 |
外部输入:
- e0 = LUT0 Output(rx_low,RX 为低)
- e1 = Counter 0 Equals Match 1(空闲计满)
转移逻辑(对应 SysConfig 里的状态式):
- IDLE:RX 一被拉低(e0=1,帧开始)→ 进入 COUNTING,开始计时。
- COUNTING:还没计满(e1=0)就留在 COUNTING;计满(e1=1)→ 进入 DONE。期间 RX 每被拉低一次计数器就清零重计,所以只有连续空闲到阈值才会真的计满。
- DONE:已经触发过,停在这里;直到 RX 再次被拉低(新一帧)才回到起点重新武装。
FSM 输出式 !s1 & s0 & e1:只有在 COUNTING 态、且这一刻 e1 变为真时输出才为 1。下一拍状态就跳到 DONE,s0 变 0,输出立刻回 0。所以输出是一个单拍脉冲——线路后面一直空着也不会再触发,直到下一帧重新武装。
为什么要做 one-shot: 空闲时 RX 一直是高,计数器计满后如果不锁住,会反复满足条件、中断连发。用 DONE 态把它停住,等新帧来再放开,就能保证一帧只产生一次空闲中断。
5.5 HLC:产生中断

- High Level Controller 的 Event 0(e0)接 FSM 0 Output。
- HLC Program 0 的 Instruction 0 写 INTR 1。
含义:FSM 给出那个一次性脉冲时,触发 HLC 的 e0,执行 INTR 1,向 CPU 发出 CLB 中断,进入 INT_myCLB0_ISR。这就是我们要的“串口空闲中断”。
六、整体时序
把一帧从头到尾串起来看:
| 帧前空闲 | 高 | 0 | 停在 0 | IDLE | 0 |
| 收到起始位 | 低 | 1 | 清零 | IDLE→COUNTING | 0 |
| 帧内数据 | 翻转 | 翻转 | 随低电平反复清零 | COUNTING | 0 |
| 帧结束转空闲 | 高 | 0 | 从 0 开始累加 | COUNTING | 0 |
| 空闲达 13020 | 高 | 0 | = Match1 | COUNTING→DONE | 1(单拍) |
| 继续空闲 | 高 | 0 | 已停 | DONE | 0 |
| 下一帧起始位 | 低 | 1 | 清零 | DONE→… | 0 |
中断只在“空闲达阈值”那一拍产生一次。
七、软件侧处理(ISR)
CPU 这边的分工:
- INT_mySCI0_RX_ISR:把收到的字节存进接收缓冲区(环形或乒乓)。
- INT_myCLB0_ISR:清中断标志,置“帧结束”标志或给接收任务发信号,由上层去解析整帧。
ISR 骨架(函数名以 SysConfig 生成的为准,具体 API 按你用的 driverlib 对应,这里只示意流程):
// SCI 接收中断:收字节
interrupt void INT_mySCI0_RX_ISR(void)
{
// FIFO 里有数据就一直读
while (SCI_getRxFIFOStatus(mySCI0_BASE) != SCI_FIFO_RX0)
rxBuf[rxLen++] = SCI_readCharNonBlocking(mySCI0_BASE);
SCI_clearInterruptStatus(mySCI0_BASE, SCI_INT_RXFF);
SCI_clearOverflowStatus(mySCI0_BASE);
Interrupt_clearACKGroup(/* 对应 PIE 组 */);
}
// CLB 空闲中断:帧结束
interrupt void INT_myCLB0_ISR(void)
{
// 关键:清掉 CLB / HLC 中断标志,否则不会再次进入
CLB_clearInterruptTag(myCLB0_BASE);
frameLen = rxLen; // 记录本帧长度
rxLen = 0; // 复位,准备下一帧
frameReady = 1; // 通知主循环 / 任务:一帧收完
Interrupt_clearACKGroup(/* 对应 PIE 组 */);
}
重点是 INT_myCLB0_ISR 里一定要清掉 CLB 的中断标志,否则中断不会再次进入;剩下的 frameReady / frameLen 交给上层去解析。
八、小结
用 CLB 做空闲检测的好处:帧结束判断在硬件里完成,CPU 零开销,阈值精确可调,比软件计时稳。适合 Modbus RTU 等没有固定帧长、要靠总线空闲分帧的场景。
几个要点:
- RX 引脚要经 Input XBAR 进 CLB,INPUT Source 填的是 GPIO 编号,别填成封装管脚号。
- 计数器阈值 = 空闲时间 × CLB 时钟,按实际时钟和想要的空闲字符数算。
- FSM 的 one-shot 是关键,保证一帧只触发一次中断。
- ISR 里务必清 CLB 中断标志。




