3. 从机模式初始化:地址配置、时钟拉伸、中断使能

好,咱们进入第三章。这一章讲的是I2C从机模式的初始化,说白了就是让芯片准备好当“小弟”,随时响应主机的呼叫。

我个人习惯把从机初始化拆成三个核心动作:地址配置、时钟拉伸控制、中断使能。这三件事没做好,后面跑起来全是坑。

3.1 地址配置——你的设备叫什么名字

I2C总线上每个从机都得有个唯一地址。主机发地址的时候,从机得认出“这是在叫我”。

地址分两种:7位地址和10位地址。绝大多数场景用7位就够了。10位地址主要是为了扩展,但兼容性处理起来麻烦一些。

核心要点:7位地址范围是0x00~0x7F,但0x00是通用呼叫地址,0x01~0x07保留,实际可用的是0x08~0x77。

配置地址时,我一般这么写(以STM32 HAL库为例):

hi2c1.Init.OwnAddress1 = 0x32;      // 7位地址,左对齐
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
// 如果要用双地址,可以再配OwnAddress2
HAL_I2C_Init(&hi2c1);

这里有个细节——地址对齐方式。有些芯片要求地址左对齐(高7位有效),有些是右对齐。我曾在某款国产MCU上栽过跟头:手册写的是7位地址,但实际寄存器要填8位,最低位是读写位。结果我填了0x32,主机发0x64,死活对不上。查了两天才发现是对齐问题。

注意:配置地址后,务必用逻辑分析仪抓一下主机发出的地址帧,确认从机是否ACK响应。不要只看寄存器值。

3.2 时钟拉伸——谁慢谁说了算

时钟拉伸(Clock Stretching)是I2C从机的一个“刹车”机制。从机处理不过来的时候,可以把SCL线拉低,告诉主机“你慢点,我还没准备好”。

这个功能在从机需要做耗时处理时特别有用。比如从机收到数据后要写入EEPROM,写操作需要几毫秒,这时候拉低SCL,主机就会等着。

配置时钟拉伸,不同芯片差异很大。有些是自动的,有些需要手动使能。我拿一个典型配置举例:

// 以NXP LPC系列为例
LPC_I2C->I2CONSET = (1 << 2);  // 使能时钟拉伸
// 或者在某些芯片上:
I2C->CR2 |= I2C_CR2_NACK;      // 用NACK代替时钟拉伸

我的经验:时钟拉伸不是所有从机都支持。如果你的主机不支持时钟拉伸(比如某些老旧的主控),从机这边就得关掉这个功能,否则会死锁。我曾经在一个项目里,从机开了时钟拉伸,主机是某款工控板,结果主机直接挂死——因为它不知道SCL被拉低是什么意思。

要不要开时钟拉伸?我建议按这个表来判断:

场景 建议 原因
从机处理速度远快于总线速率 关闭 没必要,反而增加复杂度
从机需要做耗时操作(如Flash写入) 开启 防止数据丢失或总线冲突
主机不支持时钟拉伸 关闭 否则主机死锁
不确定主机是否支持 先关闭,测试后再开 安全第一

3.3 中断使能——让CPU知道有人找你

从机模式几乎必须用中断。轮询?别想了,I2C时序那么快,CPU根本忙不过来。

中断使能通常包括这几个:

  • 地址匹配中断——主机呼叫本机地址时触发
  • 接收数据中断——从机收到数据时触发
  • 发送数据中断——主机请求数据时触发
  • 停止条件中断——主机发送停止信号时触发
  • 错误中断——总线错误、仲裁丢失等

配置代码大致长这样:

// 使能I2C中断
I2C->CR1 |= I2C_CR1_ITEVTEN;   // 事件中断
I2C->CR1 |= I2C_CR1_ITBUFEN;   // 缓冲区中断
I2C->CR1 |= I2C_CR1_ITERREN;   // 错误中断

// 使能NVIC
NVIC_EnableIRQ(I2C_EV_IRQn);
NVIC_EnableIRQ(I2C_ER_IRQn);

重要:中断优先级要合理设置。I2C中断不能太低,否则数据可能溢出;也不能太高,否则影响其他实时任务。我一般设在中优先级,比定时器低,比串口高。

中断服务函数里,我习惯用状态机来处理。收到地址匹配中断后,判断是读还是写,然后跳转到对应的处理分支。千万别在中断里做耗时操作,比如打印日志、调用延时函数——那是给自己挖坑。

3.4 初始化流程总览

把上面三件事串起来,完整的初始化流程是这样的:

  1. 使能I2C外设时钟——别漏了,漏了啥都跑不起来
  2. 配置GPIO——SCL和SDA设为开漏输出,带上拉
  3. 配置I2C时序——设置速率(100kHz或400kHz)
  4. 配置从机地址——7位或10位,注意对齐
  5. 配置时钟拉伸——按需开启或关闭
  6. 使能中断——事件中断、缓冲区中断、错误中断
  7. 使能I2C外设——最后一步,使能外设

嗯,这里要注意:顺序不能乱。我见过有人先使能外设再配地址,结果外设直接进入错误状态。

I2C从机初始化流程图 1. 使能I2C外设时钟 2. 配置GPIO(开漏+上拉) 3. 配置I2C时序(速率) 4. 配置从机地址 5. 配置时钟拉伸 6. 使能中断 7. 使能I2C外设 关键检查点 • 地址对齐方式 • 时钟拉伸兼容性 常见错误 • 先使能外设再配地址

3.5 避坑指南

最后,我把这些年踩过的坑总结一下:

  • 地址冲突——总线上两个从机地址一样,数据就乱了。设计阶段就要规划好地址分配。
  • 忘记配置GPIO模式——SCL和SDA必须是开漏输出,推挽输出会烧总线。我见过有人用推挽输出,结果总线一直拉高,通信全废。
  • 中断优先级太低——数据来了CPU没空处理,溢出丢数据。优先级要适中。
  • 时钟拉伸超时——从机拉低SCL太久,主机可能超时报错。一般建议拉伸时间不超过10ms。
  • 初始化顺序颠倒——先使能外设再配参数,外设可能进入未知状态。严格按照流程来。