4、MCP协议地址与仲裁:7位地址与10位地址模式、多主机仲裁机制、时钟拉伸
好,咱们今天聊点硬核的。MCP协议里,地址和仲裁这块,说白了就是「谁跟谁说话」和「抢着说话怎么办」的问题。我在做嵌入式调试工具的时候,被这两个问题坑过不止一次,尤其是多主机抢总线那会儿,波形乱得跟一锅粥似的。
4.1 7位地址模式:经典中的经典
MCP协议最常用的就是7位地址模式。你想想看,一条总线上挂几十个设备,每个设备得有个唯一ID吧?7位地址,理论上支持128个设备,但实际中保留了一些特殊地址,比如广播地址0x00,所以可用的大概是112个左右。
地址的传输格式是这样的:主机先发一个起始条件,然后紧跟着7位地址,再加一个读写位(0表示写,1表示读)。这8位数据,从机收到后,如果地址匹配,就会在第9个时钟周期拉低SDA线,发送一个ACK应答。
关键点:地址是高位在前(MSB first),别搞反了。我见过有人把地址0x50写成0x0A,结果从机死活不应答,查了半天才发现是字节序搞错了。
// 7位地址发送示例(伪代码)
void mcp_send_7bit_addr(uint8_t addr, bool is_read) {
uint8_t byte = (addr << 1) | (is_read ? 1 : 0);
// 发送8位数据(7位地址 + 1位读写)
for (int i = 7; i >= 0; i--) {
set_sda((byte >> i) & 0x01);
clock_pulse();
}
// 等待从机ACK
release_sda();
clock_pulse();
if (read_sda() != 0) {
// 无应答,处理错误
}
}
4.2 10位地址模式:扩展的玩法
7位地址不够用怎么办?MCP协议设计了10位地址模式。说实话,我在消费类电子产品里很少见到,但在一些工业传感器阵列里确实用过。
10位地址的发送分两步走:
- 第一帧:发送11110xx + 读写位,其中xx是10位地址的高2位。这其实是个保留地址段,告诉从机「我要用10位地址了」。
- 第二帧:发送剩下的8位地址(低8位)。
从机在每帧之后都要回复ACK。嗯,这里要注意:10位地址模式下,读写操作的处理方式跟7位地址不太一样。如果是写操作,两步走完直接发数据;如果是读操作,需要先发一个伪写操作来告诉从机你要读,然后重新发起始条件,再发读命令。
我的习惯:能用7位地址就别用10位。10位地址的时序更复杂,而且很多廉价从机芯片根本不支持。我在一个项目里被迫用10位地址,结果发现某款国产传感器手册写支持,实际波形死活不对,最后换了7位地址的型号才搞定。
| 特性 | 7位地址 | 10位地址 |
|---|---|---|
| 最大设备数 | 112(可用) | 1024 |
| 传输帧数 | 1帧 | 2帧 |
| 兼容性 | 几乎所有设备 | 部分高端设备 |
| 时序复杂度 | 低 | 中 |
4.3 多主机仲裁机制:谁抢到算谁的
MCP协议允许总线上有多个主机。你想想看,两个主机同时想发数据,SDA线不就打架了吗?仲裁机制就是解决这个问题的。
仲裁的原理其实很简单:谁先输出低电平,谁就赢。因为MCP的SDA线是开漏输出,低电平是「强拉」,高电平是「释放」。如果主机A想发1(释放),主机B想发0(拉低),那SDA线实际就是低电平。主机A检测到SDA跟自己想发的不一致,就知道自己仲裁失败了,乖乖释放总线。
我曾经踩过的坑:仲裁失败的主机必须释放SDA和SCL线,并且不能立即重试。正确的做法是等待总线空闲(检测到停止条件)后再重新发起传输。我见过有人仲裁失败后立刻重试,结果总线一直乱,最后加了个随机延时才解决。
仲裁发生在两个阶段:
- 地址阶段:多个主机同时发地址,谁的地址小(二进制值小)谁赢。因为地址小的主机更早拉低SDA。
- 数据阶段:如果地址相同,那就继续仲裁数据位。数据小的主机赢。
这里有个有意思的点:仲裁过程中不会丢失数据。赢的主机继续正常传输,输的主机自动退出,从机完全感知不到仲裁发生过。这就是MCP协议优雅的地方。
4.4 时钟拉伸:从机的「喘口气」时间
时钟拉伸,英文叫Clock Stretching。说白了就是从机告诉主机:「你慢点,我还没准备好。」
正常MCP通信中,时钟由主机控制。但从机可以在某些时刻(通常是ACK之后)拉低SCL线,迫使主机等待。主机检测到SCL被拉低,就会暂停时钟,直到从机释放SCL。
什么时候会用到时钟拉伸?
- 从机处理数据慢:比如EEPROM在写数据时,需要几毫秒才能完成。这时候从机拉低SCL,主机等着,直到写完成。
- 从机内部缓冲区满:一些传感器采集数据需要时间,拉伸时钟让主机别催。
我建议:写主机驱动时,一定要给时钟拉伸设置超时。我遇到过一款温湿度传感器,拉伸时间长达50ms,如果主机没有超时处理,整个系统就卡死了。一般超时设为100ms比较安全。
// 时钟拉伸处理示例
bool wait_for_clock_release(uint32_t timeout_ms) {
uint32_t start = get_tick_ms();
while (read_scl() == 0) { // SCL被从机拉低
if (get_tick_ms() - start > timeout_ms) {
return false; // 超时,处理错误
}
// 等待,不做其他操作
}
return true;
}
4.5 知识体系总览
下面这张图是我自己整理的MCP地址与仲裁的核心逻辑,你看一眼就能把整个流程串起来:
这张图把地址发送、仲裁判断、时钟拉伸串在了一起。你从起始条件开始,顺着箭头走一遍,基本就明白整个流程了。
总结一下:7位地址是主流,10位地址是扩展;仲裁靠的是「谁先拉低谁赢」的硬件机制;时钟拉伸是从机控制节奏的手段。这三个知识点,你在写驱动或者用逻辑分析仪抓波形时,几乎天天都会碰到。
好了,这一章就到这里。记住,MCP协议的精髓不在于它有多复杂,而在于它用最简单的硬件设计,解决了多设备通信的核心问题。你把这些机制吃透了,后面分析协议栈或者写Ghidra插件时,会顺手很多。