3、多从机地址分配策略:7位地址与10位地址、地址冲突避免

地址分配这事儿,看着简单,坑却不少。我刚开始做I2C项目时,觉得不就是给每个设备配个地址嘛,结果一上电,两个从机都不响应——地址冲突了。后来查了半天,才发现是某款传感器默认地址和另一片EEPROM撞车了。

说白了,I2C总线上的每个从机都得有唯一地址。STM32作为主机,发地址帧时,所有从机都会收到,但只有地址匹配的那个才会应答。所以地址分配是通信的第一步,也是故障的高发区。

3.1 7位地址 vs 10位地址

I2C规范定义了两种地址长度:7位和10位。STM32的I2C外设都支持这两种模式,但用法和场景差别很大。

特性 7位地址 10位地址
地址范围 0x00 ~ 0x7F(128个) 0x000 ~ 0x3FF(1024个)
保留地址 0x00~0x07、0x78~0x7F 0x000~0x00F(部分保留)
可用地址数 约112个 约1008个
帧格式 1字节地址+1位R/W 2字节地址(首字节11110xx+后8位)
STM32支持 全系列原生支持 需配置OAR1寄存器
典型应用 传感器、EEPROM、RTC 复杂系统、多路复用器

7位地址是主流。市面上90%以上的I2C器件都只用7位地址。比如常见的AT24C02 EEPROM,地址是0x50~0x57;MPU6050陀螺仪,地址是0x68或0x69。这些地址在数据手册里都会明确给出。

10位地址呢?说实话,我在实际项目中用得不多。它主要解决的是总线挂载设备太多的问题。比如一个系统里接了20多个传感器,7位地址就不够用了。但10位地址的帧格式更复杂,传输效率也低一些。

关键区别:7位地址的地址字节是 AAAAAAA + R/W,而10位地址的首字节是 11110AA + R/W,其中AA是10位地址的高两位。这个格式决定了10位地址的兼容性——有些老旧的从机芯片根本不认。

3.2 STM32的地址配置实战

STM32的I2C外设通过OAR1和OAR2寄存器来配置自身地址。注意,这里说的是STM32作为从机时的地址。但作为主机,我们只需要知道目标从机的地址,然后发出去就行。

配置7位地址很简单。以STM32F1的I2C1为例:

// 配置I2C1的自身地址(从机模式)
I2C1->OAR1 = (0x50 << 1) | I2C_OAR1_ADD0;  // 7位地址0x50,左移1位
// 或者用HAL库
hi2c1.Init.OwnAddress1 = 0x50;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
HAL_I2C_Init(&hi2c1);

10位地址的配置稍微麻烦点:

// 10位地址0x02A3
hi2c1.Init.OwnAddress1 = 0x02A3;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_10BIT;
HAL_I2C_Init(&hi2c1);

// 寄存器层面
I2C1->OAR1 = (0x02A3 << 1) | I2C_OAR1_ADDMODE;  // 置位ADDMODE表示10位

个人习惯:我一般用7位地址,除非系统里从机数量超过20个。10位地址的调试成本更高,很多逻辑分析仪对10位地址的解析也不够友好。

3.3 地址冲突的常见场景

地址冲突,说白了就是两个从机用了同一个地址。STM32发地址帧时,两个从机都会应答,总线就乱套了。

我遇到过几种典型情况:

  • 同型号器件:两片AT24C02,默认地址都是0x50。如果不改地址引脚,直接冲突。
  • 不同厂商的器件:某款温湿度传感器和某款气压计,默认地址都是0x76。这最坑,因为数据手册里不会告诉你别的器件用啥地址。
  • 地址引脚悬空:有些芯片的地址引脚内部有上拉或下拉,悬空时可能读到不确定电平,导致地址漂移。

我曾经踩过的坑:有一款国产加速度传感器,数据手册说默认地址是0x18。结果我焊了三片上去,全部不响应。后来用逻辑分析仪抓波形,发现它实际应答的地址是0x19。查了勘误表才知道,芯片内部有个保留位,默认是1。所以,拿到新器件,第一件事就是用I2C扫描程序扫一遍它的真实地址。

3.4 地址冲突的避免策略

避免地址冲突,有几种成熟的做法:

  1. 硬件地址引脚:大多数I2C器件都有A0/A1/A2引脚,通过接VCC或GND来改变地址。比如AT24C02,地址是0x50 + (A2<<2) + (A1<<1) + A0。这样最多可以挂8片。
  2. 地址扫描:上电后,主机先发一个广播扫描,看看哪些地址有应答。如果发现两个从机应答同一个地址,就报错。
  3. I2C多路复用器:用PCA9548这类芯片,把总线分成多路。每路挂不同的从机,地址可以重复。
  4. 地址映射表:在项目设计阶段,就把所有器件的地址列出来,人工检查是否有冲突。

我个人的做法是:项目初期就建一个地址表,把所有从机的型号、默认地址、可配置地址、实际使用的地址都写清楚。这样后期调试时,一眼就能看出问题。

3.5 地址分配的知识体系

下面这张图,把地址分配的核心逻辑串起来了:

I2C多从机地址分配策略 地址分配策略 7位地址 10位地址 地址冲突 范围0x00~0x7F 保留地址避开 首字节11110AA格式 同型号撞车 不同厂商撞车 避免策略:硬件引脚 + 地址扫描 + 多路复用器 + 地址映射表 核心原则 每个从机必须有唯一地址 设计阶段建立地址映射表,避免后期调试踩坑

3.6 地址扫描的实用代码

调试时,我经常用一段地址扫描代码,快速找出总线上有哪些设备。这段代码在STM32上跑一遍,就能看到所有应答的地址:

// I2C地址扫描函数
void I2C_ScanBus(I2C_HandleTypeDef *hi2c)
{
    uint8_t device_count = 0;
    printf("I2C Bus Scanning...\r\n");
    
    for (uint8_t addr = 0x01; addr < 0x7F; addr++)
    {
        // 发送起始条件+地址+写位,等待应答
        if (HAL_I2C_IsDeviceReady(hi2c, (addr << 1), 1, 10) == HAL_OK)
        {
            printf("Found device at 0x%02X\r\n", addr);
            device_count++;
        }
    }
    
    printf("Scan complete. %d device(s) found.\r\n", device_count);
}

这段代码会遍历0x01到0x7E的所有地址。如果某个地址有应答,就打印出来。注意,0x00是通用呼叫地址,0x7F是保留地址,一般不用。

实用技巧:扫描时,如果发现某个地址有应答,但你的原理图上没接这个器件,那就要警惕了——可能是地址冲突,也可能是某个器件的地址引脚焊错了。

3.7 地址映射表模板

我建议每个项目都建一个地址映射表。下面是我常用的模板:

器件型号 功能 默认地址 配置引脚 实际地址 备注
AT24C02 EEPROM 0x50 A2=GND, A1=GND, A0=GND 0x50 存储配置参数
MPU6050 6轴IMU 0x68 AD0=GND 0x68 姿态数据
BMP280 气压计 0x76 SDO=GND 0x76 高度测量
PCF8574 IO扩展 0x20 A2=VCC, A1=VCC, A0=VCC 0x27 控制LED

这个表在原理图设计阶段就要填好。如果发现两个器件实际地址相同,赶紧改引脚配置或者换型号。别等到PCB打样回来再改,那成本就高了。