1. 项目概述:嵌入式系统中的串行通信基石
在嵌入式系统开发中,设备间的“对话”能力是项目成败的关键。无论是传感器数据采集、显示屏驱动,还是模块间的指令交互,都离不开稳定可靠的通信协议。IIC(Inter-Integrated Circuit)和SCI(Serial Communications Interface,通常指UART)是两种最基础、应用最广泛的串行通信协议,它们如同嵌入式世界的“普通话”和“方言”,各有其适用的场景和优势。我接触过不少项目,从简单的温湿度传感器读取到复杂的多节点控制网络,都绕不开对这两种协议的深入理解和灵活运用。
IIC以其简洁的两线制(SDA数据线、SCL时钟线)和多主多从的架构著称,特别适合板载近距离、中低速的设备互联,比如EEPROM、各类传感器(温湿度、气压、光强)、实时时钟等。而SCI,也就是我们常说的UART,采用异步全双工通信,只需要TX、RX和地线三根线,结构简单,抗干扰能力强,是MCU与PC调试、蓝牙/Wi-Fi模块、GPS模块等外设通信的首选。这次,我们就以Freescale(现NXP)的MC1323x系列微控制器手册为蓝本,抛开枯燥的理论,直接切入工程师最关心的实战细节:IIC的10位地址到底怎么用?SCI的波特率怎么配才能误差最小?中断来了该怎么处理?这些都是在调试时最容易“卡脖子”的问题。
2. IIC协议深度解析:从7位到10位地址的实战演进
IIC协议的精妙之处在于其优雅的硬件仲裁和地址寻址机制。对于大多数应用,7位地址(理论128个设备,实际约112个可用)已经足够。但随着系统复杂度提升,当需要挂载更多同类型设备时,7位地址空间就显得捉襟见肘。这时,10位地址扩展技术就成了救命稻草。
2.1 10位地址寻址的完整流程拆解
手册中描述的10位地址过程,初看时序图可能有些抽象,我们把它翻译成更直白的“对话”流程。关键在于理解那“两个地址字节”和“重复起始条件(Sr)”的作用。
2.1.1 主设备发送,从设备接收(Master-Transmitter -> Slave-Receiver)
这个过程是单向数据流,主设备在寻址后持续发送数据。我们假设主设备要寻址一个10位地址为
0x356
(二进制
1101010110
)的从设备。
-
起始条件(S)与第一字节
:主设备发起起始条件后,发送第一个字节。这个字节的高5位固定是
11110,紧接着是10位地址的最高两位(AD10, AD9),最后一位是读写位(R/W),此时应为0(表示写)。对于地址0x356(0b11 0101 0110),AD10和AD9是11,所以第一个字节是:11110 11 0=0xF6。 -
从设备初步响应(A1)
:总线上所有支持10位寻址的从设备,都会拿收到的这前7位(
1111011)与自己地址的高7位比较。匹配的从设备会回一个应答(ACK,即A1)。注意,此时可能有多个从设备地址高7位相同,都会应答。 -
第二字节与唯一确认(A2)
:主设备接着发送第二个字节,即10位地址中剩下的低8位(AD[8:1])。对于
0x356,低8位是01010110=0x56。只有地址完全匹配的从设备(即高2位为11,低8位为0x56的那个)会回第二个应答(A2)。至此,从设备被唯一寻址。 - 数据传输 :随后,主设备开始发送数据字节,从设备每收到一个字节就回一个ACK。直到主设备发出停止条件(P)或重复起始条件(Sr)。
关键避坑点 :手册特别指出,在主机发送完第一个地址字节后,从设备会产生一个IIC中断。 软件必须忽略此时IIC数据寄存器(IICD)中的内容,不能将其当作有效数据! 这是一个经典的陷阱。许多驱动库或新手代码会在这个中断里盲目读取数据,导致后续通信错乱。正确的做法是,在10位地址模式的中断服务程序中,首先要判断中断来源是地址匹配(IAAS标志),如果是,则直接清除标志,并准备接收或发送第二个地址字节,而不是去处理数据。
2.1.2 主设备接收,从设备发送(Master-Receiver -> Slave-Transmitter)
这个过程需要改变数据传输方向,因此用到了重复起始条件(Sr)。
-
寻址阶段(同发送模式)
:主设备同样先发送两个地址字节(
0xF6和0x56),读写位为0(写),目的是“告诉”从设备:“我要和你通信,你的地址是0x356”。从设备应答A1和A2。 - 重复起始条件(Sr) :主设备不发送停止条件,而是发送一个重复起始条件(Sr)。这个Sr不会释放总线,而是重启了一次通信。
-
方向切换与再次寻址
:主设备再次发送第一个地址字节。
注意,此时字节内容的高7位不变(仍是
11110+AD10+AD9),但读写位(R/W)变成了1(表示读) 。即发送11110 11 1=0xF7。 -
从设备确认为发送方(A3)
:之前被寻址的从设备(
0x356)发现高7位地址匹配,且R/W位为1,便知道自己被要求发送数据,于是发出应答A3。其他从设备要么因为R/W位为1(对于10位设备)而不匹配,要么因为地址高5位不是11110(对于7位设备)而不匹配,因此不会响应。 - 数据接收 :随后,从设备开始发送数据,主设备每接收一个字节回一个ACK(最后一个字节前回NACK)。通信以停止条件(P)结束。
这个过程看似繁琐,实则逻辑严密:先用“写”模式锁定目标从设备,再用“读”模式改变数据流向。理解这一点,就能看懂下面这个对比表格:
| 步骤 | 主发从收 (Master-Transmitter) | 主收从发 (Master-Receiver) | 关键差异与目的 |
|---|---|---|---|
| 1 |
S + 地址字节1 (
0xF6
) + ACK
|
S + 地址字节1 (
0xF6
) + ACK
| 相同,用于初步寻址 |
| 2 |
地址字节2 (
0x56
) + ACK
|
地址字节2 (
0x56
) + ACK
| 相同,用于唯一确认从设备 |
| 3 | 数据字节 + ACK ... |
Sr
+ 地址字节1 (
0xF7
) + ACK
| 核心差异 :后者发送Sr和R/W=1的地址,以切换方向 |
| 4 | 停止条件 (P) | 数据字节 + ACK ... | 后者开始接收数据 |
| 5 | - | 停止条件 (P) | 结束通信 |
2.2 IIC中断与多主仲裁的实战处理
IIC的中断相对统一,通过一个中断向量处理多种事件,需要软件查询状态寄存器来区分。
2.2.1 中断类型与处理要点
- 字节传输完成(TCF) :一个字节(8位数据+1位ACK)传输完成时触发。这是最常用的中断,用于在发送时装载下一个数据,或在接收时读取刚收到的数据。
- 地址匹配(IAAS) :当本设备作为从设备,且收到的呼叫地址与自身地址匹配时触发。 这是实现从设备功能的核心 。进入中断后,软件需要读取状态寄存器中的SRW位,来判断主机接下来是想读(SRW=1)还是写(SRW=0),从而正确配置自身的发送或接收模式。
- 仲裁丢失(ARBL) :在多主系统中,当本设备试图控制总线但失败时触发。例如,本设备在发送数据时,检测到SDA线上为低电平,而自己输出的是高电平,说明有其他主机也在驱动总线且优先级更高。这时必须进入中断,切换为从设备模式,并等待自己的数据被发送。
2.2.2 多主仲裁避坑指南
手册列出了仲裁丢失的几种情况,这里结合实战经验补充几点:
- 总线忙时发起START :在发送START前,必须持续检测总线是否空闲(SDA和SCL均为高电平)。一个常见的错误是,上电初始化或从错误恢复后,没有等待足够时间就强行发起START,导致仲裁失败或干扰其他设备。
- 从模式下发重复START :重复起始条件(Sr)只能由当前控制总线的主设备发起。从设备在任何情况下都不应尝试发送Sr。
- 未请求的STOP :如果从设备检测到STOP条件,但当前并非处于一次完整的传输中(例如,刚被寻址就收到STOP),这可能意味着总线被异常复位或干扰。稳健的从设备代码应能处理这种异常,并回到初始状态等待下一次寻址。
实操心得 :在复杂的多主系统(如多个MCU通过IIC共享传感器数据)中,仲裁逻辑的稳定性至关重要。除了处理好ARBL中断,建议在软件层面增加“退避算法”。例如,一旦检测到仲裁丢失,不要立即重试,而是延时一个随机时间(如1-5ms),这能有效避免多个主设备持续冲突。此外,务必在总线上拉电阻的选择上留足余量,过弱的上拉会导致上升沿过慢,在高速模式下更容易在仲裁采样点出现电平不确定,从而导致通信失败。
3. SCI/UART协议精讲:波特率配置与可靠收发机制
SCI,即我们熟知的UART,其核心在于“异步”——没有统一的时钟线,通信双方依靠预先约定好的波特率进行时序同步。因此,波特率配置的准确性直接决定了通信的成败。
3.1 波特率生成器:从理论计算到寄存器配置
MC1323x的SCI波特率生成器设计得非常精细,它由一个13位整数分频器(SBR)和一个5位小数分频器(BRFA)组成。公式如下:
SCI波特率 = 总线时钟 / (16 * (SBR + BRFD))
其中,
BRFD = BRFA / 32
。
3.1.1 配置步骤与误差控制
假设我们使用16MHz的总线时钟,目标是配置出标准的115200波特率。
-
计算整数分频值SBR
:
SBR = 16,000,000 / (115200 * 16) ≈ 8.68。取整数部分,SBR = 8。 -
计算理论误差
:如果只用整数部分,实际波特率 =
16,000,000 / (16 * 8) = 125,000,误差高达(125000-115200)/115200 ≈ +8.5%。在异步通信中,通常要求误差小于2%(很多标准要求更严),这个误差是不可接受的。 -
引入小数分频BRFA
:我们需要补偿这个
0.68的小数部分。查看手册中的BRFA表,我们需要找到一个BRFD值,使得SBR + BRFD尽可能接近8.68。计算BRFD = 8.68 - 8 = 0.68。在表中查找最接近的BRFD值是0.6875(对应BRFA = 22)。 -
计算最终波特率与误差
:代入公式,实际波特率 =
16,000,000 / (16 * (8 + 0.6875)) = 16,000,000 / (16 * 8.6875) ≈ 115,108。最终误差 =(115108-115200)/115200 ≈ -0.08%,这个精度完全满足要求。
对应的寄存器配置即为:
-
SCI1BDH:SCI1BDL(波特率寄存器):写入SBR = 8 -
SCI1C4(控制寄存器4):写入BRFA = 22
手册中提供了一个非常实用的表格,列出了16MHz总线时钟下常用标准波特率的推荐配置,我们可以直接参考:
| 标准波特率 | SBR[12:0] (十进制) | BRFA[4:0] (十进制) | BRFD (十进制) | 实际波特率 | 误差 |
|---|---|---|---|---|---|
| 1200 | 833 | 11 | 0.34375 | 1199.98 | -0.09% |
| 9600 | 104 | 5 | 0.15625 | 9600.96 | +0.01% |
| 115200 | 8 | 22 | 0.6875 | 115107.91 | -0.08% |
| 230400 | 4 | 11 | 0.34375 | 230215.83 | -0.08% |
注意事项 :这个表格是“黄金参数”,在16MHz下可以直接使用。但如果你的系统总线时钟不是16MHz(例如内部RC振荡器或PLL倍频后的时钟),就必须自己重新计算。一个常见的错误是直接套用表格中的SBR和BRFA值,导致通信失败。务必根据实际总线时钟频率,按照上述步骤计算。
3.2 发送器与接收器:配置细节与数据流控制
3.2.1 发送器(Transmitter)工作流程
使能发送器(TE=1)后,硬件会自动先发送一个“前导码”(Preamble),即一整个字符时间的空闲位(逻辑高电平),用于同步。之后,发送器等待数据。
-
数据写入
:将数据写入数据寄存器
SCID。数据会被转移到发送移位寄存器,同时TDRE(发送数据寄存器空)标志置位,表示可以写入下一个数据。这就是“双缓冲”优势:你可以在当前字节正在串行发送时,准备下一个字节,提高效率。 -
发送完成
:当移位寄存器中最后一个停止位发送完毕,且发送缓冲器
SCID中也没有新数据时,TC(发送完成)标志置位。这在需要关闭发送器或切换引脚功能时非常有用,确保所有数据都已物理发出。 -
发送Break和Idle
:
-
Break
:通过置位
SBK位发送。Break是一个字符时间的逻辑0。常用于LIN总线或某些老式协议中复位或唤醒从机。注意BRK13和M位会影响Break的长度(10/11/13/14个位时间)。 -
Idle
:通过先清零再置位
TE位,可以插入一个空闲字符。这在“空闲线唤醒”模式中,用于在消息间产生足够长的空闲时间,以唤醒处于休眠状态的接收器。
-
Break
:通过置位
3.2.2 接收器(Receiver)的数据采样与同步
这是UART可靠性的核心。接收器使用16倍波特率的时钟对RxD引脚进行采样。
- 起始位检测 :接收器持续以16倍速率采样,寻找“下降沿”——即连续3个高电平采样点后出现一个低电平采样点。一旦找到,它会在第3、5、7个采样点(RT3, RT5, RT7)再次采样,如果其中至少2个是低电平,才确认为有效的起始位,并以此同步位时序。
-
数据位采样
:对每个数据位(包括起始位和停止位),在第8、9、10个采样点(RT8, RT9, RT10)进行采样,取多数值作为该位的最终值。这种“三取二”的机制能有效滤除线上的毛刺噪声。如果这三个采样点值不一致,
NF(噪声标志)会被置位。 -
错误处理
:
- 帧错误(FE) :当停止位被检测为低电平时置位(Break字符也会导致FE)。发生帧错误后,接收器会禁止接收新字符,直到软件清除FE标志。这是一个重要的保护机制。
-
溢出错误(OR)
:当接收数据寄存器(
SCID)中的数据还未被读取,而新的字符已经从移位寄存器转移过来时发生。新字符会丢失。 避免溢出的关键是及时读取SCID。由于是双缓冲,你有一个完整字符的时间(从RDRF置位到下一个字符接收完成)来读取数据。
3.3 高级功能:唤醒、9位模式与回环测试
3.3.1 接收器唤醒(Receiver Wake-up)
在多设备共享总线的系统中(如一主多从的UART网络),唤醒功能可以让非目标从机“装睡”,减少CPU中断开销。
-
空闲线唤醒(Idle-Line)
:设置
WAKE=0。当接收器处于休眠(RWU=1)时,检测到一个完整字符时间的空闲(高电平),便会自动清除RWU唤醒。ILT位控制空闲检测的起点:ILT=0从起始位后开始计数,ILT=1从停止位后开始计数。后者可以避免消息末尾的数据影响空闲检测,更可靠。 -
地址标记唤醒(Address-Mark)
:设置
WAKE=1。当接收器休眠时,只有收到最高位(MSB)为1的数据帧(即地址帧)时才会唤醒。这要求通信协议将地址帧的MSB设为1,数据帧的MSB设为0。
3.3.2 9位数据模式与回环
-
9位模式
:置位
M位即可启用。第9位数据存放在SCIxC3寄存器的T8(发送)和R8(接收)中。常用于简单的多机通信协议(第9位作为地址/数据标识位),或与奇偶校验位共用。 -
回环模式(Loop Mode)
:设置
LOOPS=1且RSRC=0。此时发送器输出直接内部连接到接收器输入,外部引脚断开。 这是调试驱动代码的利器 。你可以不连接任何外部硬件,仅通过自发自收来验证SCI模块的发送和接收功能、中断逻辑是否正确,极大简化了前期开发调试。 -
单线模式(Single-Wire)
:设置
LOOPS=1且RSRC=1。此时TxD引脚既用于发送也用于接收,实现半双工通信。需要外部电路配合(如使能控制),常用于一些简单的两线制通信场景。
4. 实战配置与问题排查实录
理论最终要服务于实践。下面我们以MC1323x为例,给出一个典型的SCI初始化配置流程,并总结常见问题的排查思路。
4.1 SCI初始化配置代码示例(以115200波特率,8N1格式为例)
/**
* @brief 初始化SCI1为115200波特率,8位数据,无校验,1位停止位。
* @param busClockHz 系统总线时钟频率,单位Hz。
*/
void SCI1_Init(uint32_t busClockHz) {
// 1. 禁用SCI收发器,进行配置
SCI1C2 &= ~(SCI_C2_TE_MASK | SCI_C2_RE_MASK);
// 2. 配置波特率 (以16MHz为例,目标115200)
uint16_t sbr;
uint8_t brfa;
float desiredBaud = 115200.0;
float temp;
// 计算整数分频SBR
temp = (float)busClockHz / (desiredBaud * 16.0);
sbr = (uint16_t)temp; // 整数部分
// 计算小数分频BRFA (BRFD = BRFA/32)
float brfd = temp - (float)sbr;
brfa = (uint8_t)(brfd * 32.0 + 0.5); // 四舍五入
// 写入波特率寄存器
SCI1BDH = (uint8_t)((sbr >> 8) & 0x1F); // SBR高5位
SCI1BDL = (uint8_t)(sbr & 0xFF); // SBR低8位
// 写入精细调整值
SCI1C4 = (SCI1C4 & ~SCI_C4_BRFA_MASK) | SCI_C4_BRFA(brfa);
// 3. 配置数据格式:8位数据,无校验,1停止位,禁止所有唤醒功能
SCI1C1 = 0x00; // M=0(8位), PE=0(无校验), PT=0, 其他默认
// 4. 配置中断(示例:使能接收中断和接收错误中断)
SCI1C2 |= SCI_C2_RIE_MASK; // 接收数据寄存器满中断使能
// SCI1C3 |= SCI_C3_ORIE_MASK | SCI_C3_FEIE_MASK | SCI_C3_PEIE_MASK; // 错误中断使能(按需)
// 5. 最后使能收发器
SCI1C2 |= (SCI_C2_TE_MASK | SCI_C2_RE_MASK);
}
/**
* @brief SCI1中断服务例程
*/
void SCI1_IRQHandler(void) {
uint8_t status = SCI1S1;
// 1. 处理接收数据
if (status & SCI_S1_RDRF_MASK) {
// 检查是否有接收错误
if (status & (SCI_S1_OR_MASK | SCI_S1_NF_MASK | SCI_S1_FE_MASK | SCI_S1_PF_MASK)) {
// 处理错误:记录日志、丢弃数据等
uint8_t errorData = SCI1D; // 读数据以清除RDRF,即使数据可能无效
// ... 错误处理代码 ...
} else {
// 读取有效数据
uint8_t receivedData = SCI1D;
// ... 数据处理代码(如放入环形缓冲区)...
}
// 注意:读取SCI1S1后读取SCI1D,会自动清除RDRF标志
}
// 2. 处理发送数据寄存器空(如果需要中断发送)
if ((status & SCI_S1_TDRE_MASK) && (SCI1C2 & SCI_C2_TIE_MASK)) {
// 检查发送缓冲区是否还有数据待发送
// if (txBufferNotEmpty) {
// SCI1D = getNextTxByte();
// } else {
// SCI1C2 &= ~SCI_C2_TIE_MASK; // 数据发完,关闭发送中断
// }
}
// 3. 处理发送完成(TC)中断,通常用于关闭发送器或切换模式
// 4. 处理空闲线(IDLE)中断等...
}
4.2 常见通信问题排查速查表
在实际调试中,通信失败是家常便饭。下面这个表格整理了最常见的问题现象、可能原因和排查步骤,能帮你快速定位问题。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 完全无通信,波形异常 |
1. 波特率配置错误。
2. 引脚复用未配置。 3. 硬件连接错误(TX/RX接反、地线未接)。 4. 收发器未使能(TE/RE位)。 |
1.
首要检查
:用示波器或逻辑分析仪测量TX引脚波形。看是否有数据发出,位宽是否符合预期波特率(1/115200≈8.7μs)。
2. 核对计算:使用前述公式,结合实际系统时钟,重新计算SBR和BRFA。 3. 检查芯片手册,确认使用的TX/RX引脚是否已正确配置为SCI功能(而非GPIO)。 4. 使用万用表检查线路连通性,确认TX接对端RX,共地。 |
| 能发送,不能接收(或反之) |
1. 单向使能错误(只开了TE或RE)。
2. 中断或DMA未正确配置。 3. 对方设备故障或配置不一致。 |
1. 检查SCI控制寄存器C2,确认TE和RE位都已置1。
2. 如果使用中断,检查中断向量表配置、NVIC使能、以及RIE/TIE等中断使能位。 3. 如果使用轮询,检查是否及时读取了RDRF标志或写入了TDRE标志。 4. 交叉测试:用USB转串口工具连接MCU的TX,用PC串口助手查看是否能收到数据,验证发送通路。 |
| 数据错乱、偶尔出错 |
1. 波特率误差过大。
2. 中断服务程序处理过慢,导致溢出(OR)。 3. 噪声干扰(NF置位)。 4. 电平不匹配(如3.3V MCU与5V设备直接连接)。 |
1. 计算并确保波特率误差在2%以内,最好使用手册推荐的配置。
2. 在接收中断中,如果处理数据耗时较长,务必使用环形缓冲区,中断里只做快速存数据操作。 3. 检查硬件:增加适当的滤波电容,确保电源稳定,对于长距离通信使用RS-232/485电平转换。 4. 检查帧格式(数据位、停止位、校验位)是否与对方设备严格一致。 |
| 通信一段时间后死机 |
1. 溢出错误(OR)或帧错误(FE)未及时处理,导致状态机卡死。
2. 缓冲区管理不当,内存溢出。 3. 多任务/中断竞争资源。 |
1. 在中断或主循环中定期检查错误标志(OR, FE, NF, PF),一旦发现立即清除并做恢复处理(如清空缓冲区)。
2. 确保环形缓冲区的读写指针操作是原子性的,或者关中断保护。 3. 对于发送,避免在中断中处理复杂逻辑,防止中断嵌套导致时序问题。 |
| 低功耗模式下通信异常 |
1. 进入低功耗模式前未完成当前传输。
2. 低功耗模式下总线时钟变化,导致波特率错误。 3. 唤醒源配置错误。 |
1. 进入STOP等模式前,等待TC标志置位,确保所有数据已发出。查询RX FIFO是否为空。
2. 查阅手册,确认在使用的低功耗模式(如WAIT, STOP3)下,SCI模块的时钟状态。有时需要禁用SCI或使用特定的低频时钟源。 3. 若使用RxD边沿唤醒(RXEDGIF),确保相应中断使能,且唤醒后时钟系统能及时稳定。 |
最后分享一个我踩过的坑
:在一次使用SCI与蓝牙模块通信的项目中,模块偶尔会无响应。排查了很久,最后发现是MCU的发送代码在等待
TDRE
标志时用了死循环,但在某些极端情况下(如蓝牙模块忙),主机发送过快,导致
TC
标志尚未置位(上一帧刚发完)就开始了下一轮发送,破坏了帧间隔。对于某些依赖严格帧间隔的协议,这会导致问题。解决方案是,在连续发送多字节数据时,不仅检查
TDRE
,在发送最后一个字节后,还应检查
TC
标志,确保整帧完全发送完毕,再开始下一帧操作。这个细节在手册里不会强调,但却是保证长期稳定运行的关键。
199

被折叠的 条评论
为什么被折叠?



