Skip to content

LV020-硬件I2C与FSMC的PB7引脚冲突 ​

一、 问题现象 ​

1. 硬件连接 ​

战舰 V3 上的 AT24C02 挂在 PB6/PB7,正是 I2C1 的默认引脚:原理图 第 3 页 EEPROM 区显示 SCL 接 PB6、SDA 接 PB7,A0~A2 与 WP 全部接地。按 LV015-AT24C02使用实例 里的 HAL 硬件 I2C 流程写完驱动后,读 EEPROM 的每一次调用都超时返回 HAL_ERROR。

2. 实测表现 ​

用寄存器级 trace(直接读 I2C1->CR1、SR1、SR2)看到的并不是“从机不应答”,而是更靠前的一步就卡死了:

观测点现象
置 CR1.START 后轮询 SR1.SB永远为 0
轮询期间读引脚状态SDA、SCL 都静止,没有任何翻转
同一份代码把 I2C1 重映射到 PB8/PB9SB 立刻置位,START 正常发出

前两行说明主机连起始条件都没发出去,第三行说明外设本身是好的——问题出在 PB7 这根线被别人占住了。

二、 原因分析 ​

1. PB7 的复用冲突 ​

STM32F103ZE 上 PB7 的复用功能不止 I2C1_SDA,复用功能表见 数据手册 第 3 章 Pinouts and pin descriptions:

引脚复用功能本板占用情况
PB6I2C1_SCL / TIM4_CH1只与 TIM4 冲突,本板没用 TIM4
PB7I2C1_SDA / TIM4_CH2 / FSMC_NADV与 FSMC 的地址锁存信号撞车

关键在 FSMC 是“上电即占用”:FSMC_BCR1 的复位值是 0x0000_30DB,其中 MBKEN = 1、MUXEN = 1,即复位后 Bank1 已经使能、且工作在复用(NOR/PSRAM)模式。本板 LCD 与外部 SRAM 都挂在 FSMC 上,时钟必须常开。此时 NADV 作为 FSMC 输出被配成复用推挽,空闲时默认输出高电平,把 SDA 钉死在高电平,I2C1 的开漏下拉根本拉不动它,自然永远等不到 SB。

FSMC 一侧的结构见 LV001-FSMC简介,信号定义见 LV005-存储器映射与控制器,LCD 用 FSMC 模拟 8080 时序的例子见 LV020-FSMC模拟8080时序。

2. 官方勘误的定性 ​

《STM32F10xxCDE 勘误手册》V5 第 2.4.9 节「FSMC 和 I2C1 以及 TIM4_CH2」原文(中译):

当使用 FSMC 功能时,NADV 被配置成复用输出,该信号会被默认地置位为 '1',这样就和 TIM4_CH2 和 I2C1 的 SDA 信号发生冲突。

暂时解决办法:当使用 FSMC 的同时,不要使用 TIM4_CH2 作为输出。如果要使用 I2C1 并且封装允许,就把该功能重映射到 PB8/PB9。

对照本板,官方给的两条出路:

官方出路本板可行性
做 FSMC 时不用 TIM4_CH2 输出与 EEPROM 无关,解决不了问题
I2C1 重映射到 PB8/PB924C02 的 SCL/SDA 焊死在 PB6/PB7,除非飞线改板

一条不相关、一条要动硬件,剩下的只有参考手册里的另一个软件开关。

3. AFIO_MAPR2 逃生通道失效 ​

3.1 寄存器定义 ​

《STM32 英文参考手册》V15 第 9.4.7 节「AF remap and debug I/O configuration register2(AFIO_MAPR2)」,偏移 0x1C:

Bit 10 FSMC_NADV: NADV connect/disconnect This bit is set and cleared by software. It controls the use of the optional FSMC_NADV signal. 0: The NADV signal is connected to the output (default) 1: The NADV signal is not connected. The I/O pin can be used by another peripheral.

按手册,往 AFIO->MAPR2 的 bit10 写 1 就能断开 NADV、把 PB7 还给 I2C1,CubeMX 生成的 __HAL_AFIO_FSMCNADV_DISCONNECTED() 做的正是这件事。

3.2 实测结果 ​

c
// flash-app/Drivers/BSP/EEPROM(诊断代码,结论确认后已删除)
AFIO->MAPR2 = 0x00000400U;   /* bit10 = 1,请求断开 NADV */
/* 读回: 0x00000000 */

AFIO->MAPR2 = 0x000003E0U;   /* 非保留位全部写 1,排除掩码问题 */
/* 读回: 0x00000000 */

AFIO->MAPR  = 0x00000001U;   /* 同一个 AFIO 里的 MAPR,作为对照组 */
/* 读回: 0x00000001 */

整个 MAPR2 不响应写操作,而同一个 AFIO 里的 MAPR(偏移 0x04)读写正常,因此可以排除 AFIO 时钟未开、总线错误、地址算错这类原因。

3.3 两个容易踩的坑 ​

(1)中文参考手册里查不到 AFIO_MAPR2。《STM32 中文参考手册》V10 对应的是 2009 年的 RM0008 英文第 10 版,而 MAPR2(含 FSMC_NADV、TIM9~14 重映射)是之后的版本才补进去的,查这些位必须看英文 V15。手册里搜不到不等于寄存器不存在。

(2)手册之间说法不一致时,以硅片实测为准。勘误只说“NADV 默认置 1 会与 SDA 冲突”,给出的解法是重映射,从未承诺 FSMC_NADV 位一定写得进去;实测写不进去,属于文档之外的行为,只能当作本片的既成事实记下来。

三、 方案与实现 ​

1. 方案对比 ​

方案前提代价
硬件 I2C1 走 PB8/PB9飞线把 EEPROM 两脚从 PB6/PB7 挪到 PB8/PB9,并置 I2C1_REMAP改板,量产不现实
软件模拟 I2C 走 PB6/PB7无,直接位操作占 CPU,速率只有几十 kHz

24C02 容量仅 256 字节、单次读写几毫秒,软件模拟完全够用,于是把驱动固定在软件模拟这条路上。

2. 工程实现 ​

驱动用宏把两条实现隔离,公共层只依赖“总线层接口”,两份实现各自用 #if 整体包起来,切换只改一个宏。宏为 0 时硬件版文件整份不参与编译,main() 里的 MX_I2C1_Init() 调用也被同一个宏跳过——I2C1 外设既不开时钟也不占引脚。宏定义与结论记录在 bsp_eeprom.h 的注释块里,软件模拟实现见 bsp_eeprom_sw.c:

c
// flash-app/Drivers/BSP/EEPROM/bsp_eeprom.h
/* 0 = 软件模拟(PB6/PB7 位操作), 1 = 硬件 I2C1 */
#ifndef BSP_EEPROM_USE_HW_I2C
#define BSP_EEPROM_USE_HW_I2C        0
#endif

软件模拟的时序细节见 LV010-I2C时序与软件模拟,器件本身的读写流程见同目录的 LV010-AT24C02简介。

四、 验证与结论 ​

1. 三组对照实验 ​

实验操作结果
1保留 FSMC,把 I2C1 重映射到 PB8/PB9SB = 1、MSL = 1,START 正常,证明外设无故障
2关掉 FSMC 时钟(清 RCC->AHBENR 的 FSMCEN)立刻恢复正常,START 能发、EEPROM 能读能写
3保留 FSMC,I2C1 用默认的 PB6/PB7SB 永不置位,SDA/SCL 静止

实验 1、2 证明 I2C 外设和 EEPROM 都是好的,实验 3 复现问题,变量唯一地锁定在 PB7 的 FSMC 争用上。

2. 运行验证 ​

换回软件模拟后,用串口 CLI 读 0x0C 起的 8 个字节:

text
> eeprom read 0x0C 8
000C  01 02 03 04 05 06 07 08

3. 结论与适用边界 ​

  • 只要 LCD 或外部 SRAM 走 FSMC、而 EEPROM 又焊在 PB6/PB7,硬件 I2C1 就不可用:NADV 的默认高电平与 SDA 直接冲突,且 FSMC_NADV 位在本片写不进去。
  • 想回到硬件 I2C,必须同时满足两个前提:EEPROM 改到 PB8/PB9(飞线改板),并置 I2C1_REMAP。
  • 用软件模拟期间,PB6/PB7 不能再当普通 IO 使用。

五、 延伸与参考 ​

1. 其它 I2C 勘误 ​

将来若真要用硬件 I2C,同一份勘误手册里还有两条会影响驱动写法:

节号内容对驱动的影响
2.9.1EV7、EV7_1、EV6_1、EV2、EV8、EV3 必须抢在当前字节传输之前处理逐字节查询死等会出问题,官方建议用 DMA,或把 I2C 中断设为最高优先级
2.9.2不完全兼容 SMBus v2.0,对非法字节不发 NACK需要上层校验机制(SMBALERT、ARA、主机提醒协议)

2. 参考资料 ​


本文档由 markdowncli 技能辅助生成