STM32F103C8T6 I2C例程没反应?三步定位时钟、引脚与初始化问题

发布时间:2026/9/10 11:24:13
STM32F103C8T6 I2C例程没反应?三步定位时钟、引脚与初始化问题 简介本资源是一套面向嵌入式初学者与STM32开发者的I2C通信实战例程聚焦STM32F103C8T6芯片在OLED显示驱动等典型场景下的I2C主从通信实现。内容覆盖RCC时钟配置、PB6/PB7引脚复用为开漏模式、I2C初始化与读写时序控制等核心环节并包含完整OLED指令交互逻辑可直接用于传感器数据可视化或人机界面开发。压缩包共1597个文件以295个.h头文件和292个.c源码为主干辅以223个编译中间文件.o/.d、129个依赖信息.crf及19个可执行镜像.hex/.axf工程结构完整支持Keil MDK一键编译烧录包体大小18.91MB目录组织清晰含详细Readme与HTML使用说明。目前已有435人学习下载提供从寄存器级配置到OLED图形显示的全流程代码支撑特别适合动手实践I2C协议、理解STM32标准外设库调用逻辑及调试常见通信异常。1. 为什么 STM32F103C8T6 的 I2C 例程在 21ic 下载后常“没反应”这不是代码问题而是时序、引脚与外设初始化的三重校准很多刚拿到 21ic 上下载的STM32F103C8T6_I2C例程比如驱动 BH1750 光照传感器、AT24C02 EEPROM 或 OLED 屏幕的开发者烧录后串口无输出、I2C 设备不响应、逻辑分析仪抓不到有效波形——第一反应是“例程有 bug”或“板子坏了”。但真实情况往往是这份基于标准外设库SPL或早期 HAL 库的例程预设了特定硬件条件——它默认你使用的是标准最小系统板如“蓝 pill”且 I2C 引脚已按 STM32F103C8T6 数据手册第 39 页明确规定的复用功能AFIO正确配置同时它隐含依赖 72MHz 系统时钟下精确计算出的 CCR 和 TRISE 值。一旦你的板子晶振频率不同如 8MHz 外部晶振未倍频、I2C 引脚被误接至非标准 GPIO例如 PB6/PB7 被占用改用 PA9/PA10 模拟、或未启用 AFIO 时钟I2C 外设就根本不会启动。本文不讲抽象协议只聚焦于从 21ic 下载的原始例程出发如何在真实硬件上逐层验证、定位并修复这三类最常见失效点覆盖从原理图核对、寄存器级调试到逻辑分析仪实测的完整闭环。2. 从 21ic 下载的 I2C 例程结构解析识别 SPL/HAL 差异、时钟树依赖与引脚硬编码陷阱2.1 判定例程所用库类型及对应初始化路径21ic 平台上的 STM32F103C8T6 I2C 例程主要分两类基于标准外设库Standard Peripheral Library, SPL和基于早期 HAL 库如 HAL v1.5.x。二者初始化逻辑差异极大直接影响你后续修改方向。SPL 例程特征主文件中必含#include stm32f10x_i2c.hI2C 初始化函数名为I2C_Init()其参数结构体为I2C_InitTypeDef关键字段包括I2C_ClockSpeed单位 Hz、I2C_Mode标准/快速模式、I2C_DutyCycle仅快速模式需设、I2C_OwnAddress1本机地址。HAL 例程特征主文件含#include stm32f10xx_hal_i2c.h初始化调用HAL_I2C_Init()参数为I2C_HandleTypeDef结构体其中Init.ClockSpeedHz、Init.DutyCycle、Init.OwnAddress1字段命名相似但底层寄存器配置逻辑由 HAL 封装不可直接操作 CCR/TRISE。提示打开例程中的main.c搜索I2C_Init或HAL_I2C_Init即可快速判定。若两者皆无极可能是软件模拟 I2Cbit-banging此时需检查 GPIO 初始化及延时函数精度。2.2 时钟树配置是 I2C 正常工作的前提必须与例程假设一致STM32F103C8T6 的 I2C 外设时钟源为 APB1 总线最大 36MHz其波特率由CCRClock Control Register和TRISEMaximum Rise Time Register共同决定。21ic 例程通常假设系统时钟为 72MHzHSE 经 PLL 倍频此时 APB1 36MHz。若你的板子使用内部 8MHz RC 振荡器HSI且未配置 PLL则 APB1 实际为 8MHz原例程中I2C_ClockSpeed 100000100kHz的配置将完全失效。验证方法在SystemInit()后添加如下代码通过串口打印实际 APB1 频率// 使用 USART1 输出需先初始化串口 uint32_t apb1_freq RCC_GetPCLK1Freq(); // 标准外设库函数 printf(APB1 Clock: %d Hz\r\n, apb1_freq);若输出非 36000000则必须重新计算 CCR 值。以标准模式 100kHz 为例公式为CCR (APB1_Freq / (2 * I2C_ClockSpeed))当DutyCycle I2C_DutyCycle_2若 APB18MHz则CCR 8000000/(2*100000) 40而原例程中CCR常为0x20对应 36MHz 下的值。2.3 引脚复用配置是物理层连通的关键必须与原理图严格匹配21ic 例程默认使用 STM32F103C8T6 的硬件 I2C1 接口即PB6SCL和 PB7SDA。但“最小系统板”存在两种常见变体标准版PB6/PB7 直接连 I2C 插座上拉电阻通常 4.7kΩ已焊接兼容 Arduino 版为适配 Uno 引脚定义常将 I2C 改接到PA9SCL和 PA10SDA此时需软件模拟Software I2C或重映射Remap。检查原理图如stm32f103c8t6最小系统板原理图确认 I2C 物理引脚。若实际使用 PB6/PB7必须确保RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_AFIO, ENABLE);—— AFIO 时钟使能SPL 必须GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);—— 若需重映射如改用 PA9/PA10GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;—— I2C 引脚速度必须设为 50MHz数据手册要求。注意HAL 库中__HAL_RCC_AFIO_CLK_ENABLE()和__HAL_AFIO_REMAP_I2C1_ENABLE()是等效操作但 HAL 例程常省略重映射代码导致引脚错位。3. 在真实硬件上跑通 I2C从 GPIO 初始化到逻辑分析仪波形验证的四步实操3.1 第一步剥离外设用 GPIO 模拟 SDA/SCL 波形验证物理连接在不依赖 I2C 外设的情况下用普通 GPIO 输出方波确认硬件链路无短路、上拉有效// SPL 示例配置 PB6/PB7 为推挽输出 GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); while(1) { GPIO_SetBits(GPIOB, GPIO_Pin_6); // SCL 高电平 GPIO_ResetBits(GPIOB, GPIO_Pin_7); // SDA 低电平 Delay_ms(10); GPIO_ResetBits(GPIOB, GPIO_Pin_6); // SCL 低电平 GPIO_SetBits(GPIOB, GPIO_Pin_7); // SDA 高电平 Delay_ms(10); }用万用表测量 PB6/PB7 对地电压应交替显示 3.3V/0V若始终为高3.3V说明上拉电阻缺失或 I2C 设备短路若始终为低0V说明存在对地短路。此步可排除 70% 的硬件故障。3.2 第二步配置 I2C 外设并捕获起始信号START condition在确认 GPIO 可控后恢复 I2C 初始化但仅发送 START 信号用逻辑分析仪如 Saleae Logic 8抓取 PB6/PB7// SPL 初始化片段APB136MHz, 100kHz 标准模式 I2C_InitTypeDef I2C_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_I2C1, ENABLE); I2C_DeInit(I2C1); I2C_InitStruct.I2C_ClockSpeed 100000; I2C_InitStruct.I2C_Mode I2C_Mode_Sm; I2C_InitStruct.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 0x00; // 不作为从机 I2C_InitStruct.I2C_Ack I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); // 发送 START I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待 START 发送完成逻辑分析仪设置采样率 ≥ 1MHz触发条件为 PB6SCL下降沿 PB7SDA高→低跳变。正常波形应显示SCL 为高时SDA 由高变低START。若无此波形检查I2C_Cmd()是否执行、I2C_GenerateSTART()参数是否为ENABLE、以及I2C_CheckEvent()是否超时卡死此时需查I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)是否为 SET。3.3 第三步读取从机地址并验证 ACK 响应I2C 通信成败的核心在于从机是否返回 ACKSDA 被拉低。以 AT24C02地址 0x50为例// 发送从机地址写模式 I2C_Send7bitAddress(I2C1, 0x50, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待 ADDR0 // 若此处超时说明从机未响应检查地址是否正确0x50 vs 0xA0、电源是否正常AT24C02 需 1.8~5.5V、WP 引脚是否接地写保护用逻辑分析仪观察地址字节0x50发送后第 9 个时钟周期SCL 高电平期间SDA 应被从机拉低ACK。若 SDA 保持高电平NACK则从机未工作或地址错误。3.4 第四步完整读写 EEPROM 并用串口打印校验结果以向 AT24C02 写入一字节0xAA到地址0x00再读回校验为例uint8_t write_data 0xAA; uint8_t read_data; // 写操作START - 地址(W) - 内存地址 - 数据 - STOP I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, 0x50, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, 0x00); // 内存地址 0x00 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, write_data); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); Delay_ms(10); // AT24C02 写入周期约 5ms // 读操作START - 地址(W) - 内存地址 - RESTART - 地址(R) - 读数据 - STOP I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, 0x50, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, 0x00); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); // RESTART while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, 0x50, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); I2C_AcknowledgeConfig(I2C1, DISABLE); // 关闭 ACK准备 STOP read_data I2C_ReceiveData(I2C1); I2C_GenerateSTOP(I2C1, ENABLE); printf(Write: 0x%02X, Read: 0x%02X\r\n, write_data, read_data); // 应输出 Write: 0xAA, Read: 0xAA提示I2C_AcknowledgeConfig(I2C1, DISABLE)必须在读取最后一个字节前执行否则从机会继续发送下一字节导致总线异常。4. I2C 通信的深度排错用寄存器快照定位 BUSY、ARLO、AF 标志位异常4.1 理解 I2C 状态寄存器SR1/SR2的关键标志位含义当 I2C 通信卡死如I2C_CheckEvent无限等待不能只看高级 API必须直读状态寄存器。STM32F103C8T6 的 I2C_SR1 寄存器中以下三位最常指示故障根源标志位宏定义含义典型原因BUSYI2C_SR1_BUSY总线被占用SCL 或 SDA 为低外部设备拉低 SDA 未释放、上拉电阻过大、物理短路ARLOI2C_SR1_ARLO仲裁丢失多主机场景本例程极少出现可忽略AFI2C_SR1_AF从机未应答NACK从机地址错误、从机未上电、WP 引脚悬空EEPROM、I2C 速率过高在I2C_CheckEvent超时后立即读取uint16_t sr1 I2C1-SR1; printf(I2C_SR1 0x%04X\r\n, sr1); if(sr1 I2C_SR1_BUSY) printf(BUSY: SCL/SDA stuck low!\r\n); if(sr1 I2C_SR1_AF) printf(AF: Slave NACK! Check address/power.\r\n);4.2 解决 BUSY 标志位持续置位的硬件级方案BUSY1表示 SCL 或 SDA 至少一根线被外部设备或短路拉低。此时 I2C 外设拒绝发起新通信。不要复位芯片而应用万用表测 PB6/PB7 对地电压若 0.8V说明被强拉低断开所有 I2C 从机仅保留上拉电阻再测电压——若仍低则 MCU 引脚损坏若断开从机后电压恢复正常3.3V则逐个接入从机定位故障设备对于 AT24C02检查 WP 引脚若 WP1高电平写操作会触发 NACK但 BUSY 不会置位若 WP 悬空可能因干扰导致内部逻辑异常拉低 SDA。提示某些国产替代芯片如 GD32F103C8T6的 I2C 外设对 BUSY 清除更敏感需在检测到 BUSY 后执行I2C_SoftwareResetCmd(I2C1, ENABLE)再DISABLE强制释放总线。4.3 针对“没反应啊”场景的三分钟快速诊断表当用户反馈“烧录后 I2C 没反应”按此表顺序排查90% 问题可在 3 分钟内定位步骤操作预期现象问题定位1. 测电压万用表测 PB6/PB7 对地电压上电后均为 3.3V上拉有效若任一为 0V → 短路或从机故障2. 查时钟串口打印RCC_GetPCLK1Freq()输出 36000000若非此值 → 重配系统时钟3. 抓波形逻辑分析仪接 PB6/PB7运行 START 代码SCL 高时 SDA 下降沿若无 → I2C 外设未使能或引脚配置错4. 验地址修改I2C_Send7bitAddress地址为 0x50、0x3C、0x78 等常见值某一地址触发 ACK若全无 → 从机未供电或地址线接错如 A0/A1/A2此表直接对应stm32f103c8t6引脚图中 PB6/PB7 的物理位置、i2c时序图中 START 的定义、以及i2c通信协议的地址帧格式将抽象概念转化为可触摸的物理动作。5. 进阶技巧在无逻辑分析仪条件下用 UART 打印 I2C 状态机实现“软调试”5.1 构建 I2C 状态流转日志替代波形分析当缺乏逻辑分析仪时可通过精细插入 UART 打印将 I2C 硬件状态“翻译”为可读文本。以I2C_CheckEvent为例将其替换为带状态码的日志函数#define I2C_LOG_EVENT(event, desc) do { \ printf([I2C] Wait %s (0x%04X)..., desc, event); \ uint32_t timeout 0x5000; \ while((I2C1-SR1 event) 0) { \ if(--timeout 0) { printf( TIMEOUT!\r\n); return ERROR; } \ } \ printf( OK\r\n); \ } while(0) // 使用示例 I2C_GenerateSTART(I2C1, ENABLE); I2C_LOG_EVENT(I2C_EVENT_MASTER_MODE_SELECT, START sent); I2C_Send7bitAddress(I2C1, 0x50, I2C_Direction_Transmitter); I2C_LOG_EVENT(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, Addr ACK received);输出效果[I2C] Wait START sent (0x0001)... OK [I2C] Wait Addr ACK received (0x0002)... TIMEOUT!明确指出卡在“地址应答”环节直接指向从机供电或地址问题无需猜测。5.2 利用 I2C 错误中断IT实现自动故障捕获开启 I2C 错误中断当发生 AF、ARLO、BERR 时自动进入中断服务程序ISR记录错误类型并锁死 LED// 使能错误中断 I2C_ITConfig(I2C1, I2C_IT_ERR, ENABLE); NVIC_EnableIRQ(I2C1_ER_IRQn); // ISR void I2C1_ER_IRQHandler(void) { uint16_t sr1 I2C1-SR1; if(sr1 I2C_SR1_AF) { printf(ERR: Address NACK\r\n); GPIO_SetBits(GPIOC, GPIO_Pin_13); // 红灯亮 } if(sr1 I2C_SR1_BERR) { printf(ERR: Bus Error\r\n); GPIO_SetBits(GPIOC, GPIO_Pin_14); // 绿灯亮 } I2C_ClearFlag(I2C1, I2C_FLAG_AF | I2C_FLAG_BERR); // 清标志 }此法将瞬时故障如 NACK转化为稳定可观察的 LED 状态适合产线快速筛选不良板。5.3 针对 BH1750 等传感器的专用初始化序列验证以bh1750 软件i2c常见问题为例其初始化需发送0x10连续测量模式指令但部分例程遗漏了“发送指令后需延时 24ms 等待测量完成”。若直接读数据会得到全 0// 正确流程增加延时 I2C_SendData(I2C1, 0x10); // 连续测量模式 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); Delay_ms(24); // 关键BH1750 数据手册规定 // 读取 2 字节数据 I2C_GenerateSTART(I2C1, ENABLE); I2C_Send7bitAddress(I2C1, 0x23, I2C_Direction_Receiver); // ... 后续读取此类设备特定时序必须严格对照其数据手册如bh1750 数据手册而非依赖通用 I2C 例程。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询