STM32 零基础可移植教程 18:I2C 入门,先用扫描器找一找总线上有没有设备
前面我们已经把串口、定时器、PWM、ADC、DMA 这些基础链路跑了一圈。
从这一篇开始,进入常见总线。
先讲 I2C。
很多新手一上来就想读传感器:
bash
读温度
读姿态
读 EEPROM
读 OLED ID
然后程序一跑,什么都没有。
这时候很容易怀疑:
bash
是不是寄存器地址错了?
是不是 HAL 函数不会用?
是不是模块坏了?
但在真正读寄存器之前,有一个更基础的问题要先确认:
bash
I2C 总线上到底有没有设备回应?
所以这一篇不急着读某一个具体芯片。
我们先做一个通用工具:
bash
I2C Scanner
它的作用很简单:
bash
从 0x03 扫到 0x77
看看哪些 7 位地址有设备 ACK
然后通过串口打印出来
这篇只解决一个明确目标:
bash
用 STM32 扫描 I2C 总线地址,确认设备是否在线
但在动手之前,先把 I2C 是什么、怎么工作、以及为什么选它讲清楚。
道理通了,后面写代码才不懵。
I2C 是什么
I2C(Inter-Integrated Circuit,也常写作 IIC)是一种串行通信总线,由 Philips 在 1980 年代设计。
它最大的特点是:
bash
只用两根线,就能挂很多设备
这两根线分别是:
bash
SCL(Serial Clock):时钟线,由主机产生
SDA(Serial Data):数据线,双向传输数据
在 STM32 开发中,I2C 最常见的应用场景是:
bash
一块 STM32 当主机
总线上挂多个传感器 / EEPROM / OLED
每个设备靠不同的地址区分

I2C 的工作原理
1. 主从结构
I2C 总线上只有两种角色:
bash
主机(Master):发起通信的一方,负责产生 SCL 时钟
从机(Slave):响应主机的一方,每个从机有一个唯一地址
一条 I2C 总线上可以有多个主机和多个从机。
但对入门来说,只需要记住最常见的用法:
bash
STM32 = 主机
传感器 / EEPROM / OLED = 从机
主机永远主动发起通信,从机只能被动回应。
这一点和 UART 完全不同------UART 两端是平等的,谁都可以随时发数据。
2. 地址怎么区分设备
每一个 I2C 从机都有一个 7 位(或 10 位)的设备地址。
主机在通信开始时先广播一个地址:
bash
主机:0x68 在不在?
地址是 0x68 的设备拉低 SDA 回应一个 ACK,表示"我在"。
其他地址的设备不理这件事。
这就是 I2C Scanner 的核心原理:
bash
对 0x03 到 0x77 的每个地址问一遍
看谁回 ACK
所以扫到地址,说明总线上确实有这个设备。
3. 开漏输出和上拉电阻------为什么必须有上拉
I2C 的 SCL 和 SDA 引脚都是开漏输出(Open Drain)。
简单理解就是:
bash
设备可以把线拉低(接地)
但不会主动把线推高(接电源)
总线要回到高电平,靠的是上拉电阻把线拉到 VCC。
为什么这样设计?
假设有三个设备的 SDA 连在同一条线上:
bash
如果设备 A 推高、设备 B 推低
就相当于电源和地直接短路
开漏输出天然避免了这个问题:
bash
设备只拉低,不推高
谁都不推高,就不会打架
高电平由上拉电阻统一提供
所以记住一句话:
bash
没有上拉电阻,I2C 总线不工作
很多模块(MPU6050、OLED 小板)板子上已经焊了 4.7k 或 10k 上拉电阻,这时就不用再加。
但如果你自制的板子忘了加上拉,I2C 一定扫不到设备。

4. 一次完整的 I2C 通信过程
一次完整的 I2C 通信大致是这样:
Start 和 Stop 这两根线的时序变化,就定义了通信的开始和结束。
中间传输数据时,SCL 的每个脉冲对应一个 bit。
ACK 很关键:
bash
ACK = 对方收到了
NACK = 没收到,或没这个地址的设备
初学者不需要手写这些时序,HAL 库已经封装好了。
但知道有这些东西,调试时看波形就不会一脸茫然。

5. 7 位地址和 8 位地址的关系
I2C 入门最容易踩的坑就是地址。
很多资料写:
bash
AT24C02 地址:0xA0
也有资料写:
bash
AT24C02 地址:0x50
到底哪个对?
都对你。
bash
0x50 是 7 位设备地址
0xA0 是左移一位之后、末位填 0 的 8 位写地址
I2C 在线上实际发送 8 位:7 位地址 + 1 位读写位。
所以:
bash
写地址 = 0x50 << 1 | 0 = 0xA0
读地址 = 0x50 << 1 | 1 = 0xA1
在代码中使用 STM32 HAL 库时,多数函数要求传入左移后的值。
但我们在应用层和串口打印时,统一使用 7 位地址。
打印的是 0x50,传给 HAL 的才是 (uint16_t)(0x50 << 1)。
这样和芯片手册上的 7 位地址对应,移植和查资料都更清楚。

为什么用 I2C 而不是 UART 或 SPI
三种总线各有各的适用场景,不是说谁一定比谁好。
先看对比:
|
特性
|
UART
|
SPI
|
I2C
|
| --- | --- | --- | --- |
|
最少信号线
|
2(TX/RX)
|
4(SCK/MOSI/MISO/CS)
|
2(SCL/SDA)
|
|
多设备
|
不支持
|
支持,但每增加一个设备多加一根 CS
|
支持,靠地址区分,不额外占用引脚
|
|
速度
|
通常 ≤ 1 Mbps
|
几 MHz 到几十 MHz
|
标准 100 kHz,快速 400 kHz,高速 3.4 MHz
|
|
全双工
|
是
|
是
|
否(半双工)
|
|
硬件复杂度
|
低
|
中
|
低
|
|
典型用途
|
调试、GPS/蓝牙模块
|
显示屏、高速 ADC、Flash
|
传感器、EEPROM、RTC、小 OLED
|
场景 1:只接一个模块,三种都行
比如你只想用蓝牙透传,UART 最简单。
一块 STM32 的 TX 接模块的 RX,RX 接模块的 TX,串口助手就能看到数据。
场景 2:接多个传感器,I2C 优势明显
假设你要接 3 个传感器:温度、湿度、气压。
用 UART:
bash
需要 3 个 USART,6 根数据线
而且 STM32 上 USART 数量有限
用 SPI:
bash
SCK + MOSI + MISO = 3 根公用
但每个传感器还要一根独立的 CS = 再加 3 根
总共 6 根线
用 I2C:
bash
SCL + SDA = 2 根线
3 个传感器全部并在这两根线上
每个传感器有不同地址,靠地址区分
这就是 I2C 最大的优势:
bash
设备越多,省线越明显
场景 3:高速传输,SPI 更适合
I2C 跑 100 kHz 或 400 kHz,SPI 可以跑几 MHz 甚至更高。
如果你要驱动一块彩色 TFT 屏幕,每秒钟要刷大量像素数据,SPI 更合适。
I2C 的 OLED 只是 128 × 64 像素的小屏,数据量小很多。
场景 4:距离远,UART 更可靠
I2C 和 SPI 都是板内总线,设计上就没考虑远距离。
UART 可以加 RS-232、RS-485 收发器,跑到几米甚至上千米。
一句话总结
bash
接传感器 / EEPROM / RTC / 小 OLED → 首选 I2C,线最少
接 TFT 彩屏 / 高速 ADC → 首选 SPI,速度快
调试口 / GPS / 蓝牙透传 → 首选 UART,简单直接
对于这篇的 Scanner 来说,目标是找到 I2C 总线上的设备,和别的总线没有关系。
但你可能在同一个工程里同时用到 UART(串口打印)+ I2C(传感器)------这非常正常。
本篇目标
最终现象:
如果没有接任何 I2C 设备,串口打印:
bash
Scanning I2C bus...
No I2C device found.
如果接了一个常见 I2C 设备,串口可能打印:
bash
Scanning I2C bus...
Found 1 device(s): 0x68
或者:
bash
Found 1 device(s): 0x3C
或者:
bash
Found 1 device(s): 0x50
本篇用到的外设:
bash
I2C
USART printf
本篇跑通标准:
-
CubeMX 能正确配置 I2C;
-
Keil 编译通过;
-
串口能打印扫描结果;
-
能解释 7 位地址和 8 位地址的区别;
-
知道 I2C 为什么需要上拉电阻;
-
总线扫不到设备时,知道先查哪些地方。
准备工作
你需要准备:
|
项目
|
说明
|
| --- | --- |
|
STM32 开发板
|
任意带 I2C 的 STM32 都可以
|
|
下载器
|
ST-LINK/V2 或板载 ST-LINK
|
|
串口工具
|
用来看扫描结果
|
|
一个 I2C 设备
|
EEPROM、MPU6050、OLED、I2C LCD 小板都可以
|
|
杜邦线
|
外接模块时使用
|
|
原理图
|
确认 I2C 的 SCL/SDA 引脚
|
如果你暂时没有 I2C 模块,也可以先把工程跑起来。
没有设备时,扫描结果应该是:
bash
No I2C device found.
这说明程序至少跑通了。
但要验证 I2C 通信,最终还是需要接一个 I2C 设备。
常见设备地址举例:
|
设备
|
常见 7 位地址
|
说明
|
| --- | --- | --- |
|
AT24C02 EEPROM
| 0x50 |
有些资料会写 8 位写地址 0xA0
|
|
MPU6050
| 0x68
或 0x69
|
取决于 AD0 引脚电平
|
|
SSD1306 OLED
| 0x3C
或 0x3D
|
常见 0.96 寸 OLED
|
|
PCF8574 I2C LCD 小板
| 0x27
或 0x3F
|
不同小板地址不同
|

硬件连接
I2C 最少需要两根信号线:
bash
SCL:时钟线
SDA:数据线
再加上供电和地:
|
I2C 模块
|
STM32 开发板
|
| --- | --- |
|
VCC
|
3.3V 或模块要求的电源
|
|
GND
|
GND
|
|
SCL
|
STM32 I2C SCL
|
|
SDA
|
STM32 I2C SDA
|
注意三件事。
第一,GND 必须共地。
这个和串口、PWM 接回 ADC 一样。没有共地,电平就没有共同参考。
第二,确认模块电压。
很多 I2C 模块可以 3.3V 供电,也有些模块默认接 5V。STM32 的 I2C 引脚不一定都能承受 5V,上拉到 5V 时要特别小心。
入门阶段建议:
bash
模块 VCC 接 3.3V
SCL/SDA 上拉也到 3.3V
第三,确认上拉电阻。
前面原理部分讲过,I2C 必须靠上拉电阻才能工作。
很多模块板子上已经焊了 4.7k 或 10k 上拉电阻。
如果不确定,查一下模块原理图,或者用万用表量一下 SCL/SDA 到 VCC 之间的电阻。
裸芯片或自制板子尤其要注意这一点------忘了加上拉,I2C 一定扫不到设备。
CubeMX 配置步骤
1. 复制前面的串口工程
建议从第 07 篇 USART printf 工程复制一份,改名为:
bash
18_i2c_scanner
因为这篇需要串口打印扫描结果。
如果你重新建工程,也可以按第一篇流程:
-
选择芯片型号;
-
SYS -> Debug设置为Serial Wire; -
配置 USART,用于
printf(); -
配置 I2C;
-
生成 Keil 工程。

2. 配置 I2C
选择你要用的 I2C,比如:
bash
I2C1
模式选择:
bash
I2C
常见引脚示例:
|
I2C
|
SCL
|
SDA
|
| --- | --- | --- |
|
I2C1
|
PB6
|
PB7
|
|
I2C2
|
PB10
|
PB11
|
具体以你的芯片和开发板原理图为准。
注意:
bash
不是任意 GPIO 都能直接当硬件 I2C 的 SCL/SDA
要选芯片复用功能里支持 I2C_SCL、I2C_SDA 的引脚。

3. 设置 I2C 参数
入门阶段先用标准模式:
|
配置项
|
推荐值
|
说明
|
| --- | --- | --- |
|
I2C Speed Mode
|
Standard Mode
|
先用 100 kHz,稳定优先
|
|
I2C Clock Speed
|
100000 Hz
|
常见标准速度
|
|
Duty Cycle
|
2
|
标准设置
|
|
Own Address
|
0
|
主机模式下先不用
|
|
Addressing Mode
|
7-bit
|
常见 I2C 设备都是 7 位地址
|
|
General Call
|
Disable
|
先不用
|
|
No Stretch Mode
|
Disable
|
允许从机拉伸时钟
|
这一篇 STM32 当主机。
模块、传感器、EEPROM 当从机。
所以我们不会用 Own Address 做通信,只保持默认即可。

4. GPIO 上拉怎么选
如果 CubeMX 的 GPIO 页面里能看到 I2C 引脚,通常模式会是:
bash
Alternate Function Open Drain
这是 I2C 的典型配置。
Pull-up/Pull-down 怎么选?
入门建议:
bash
如果外部模块已经有上拉电阻,内部 Pull-up 可以不依赖它
如果不确定,可以先看模块原理图
有些工程会在 CubeMX 里把 I2C 引脚设成 Pull-up。
但 STM32 内部上拉通常比较弱,不建议把它当成唯一可靠上拉。
更推荐硬件上有 4.7k 或 10k 上拉电阻。

5. 配置 USART printf
这篇仍然需要串口输出扫描结果。
如果你已经完成第 07 篇,就直接沿用。
常见参数:
bash
115200
8 数据位
无校验
1 停止位

6. 生成 Keil 工程
点击:
bash
GENERATE CODE

打开 Keil 后,先编译一次。
如果 CubeMX 原始工程都编译不过,先处理基础工程,不要急着加扫描代码。
Keil 工程生成和编译
本篇新增两个文件:
bash
Core/Inc/app_i2c_scanner.h
Core/Src/app_i2c_scanner.c
如果你手动新建 .c 文件,记得在 Keil 工程树里添加:
bash
Core/Src/app_i2c_scanner.c
否则会出现:
bash
undefined symbol App_I2CScanner_Scan
这不是函数写错了,而是 .c 文件没有参与编译。

完整代码
1. 新建 Core/Inc/app_i2c_scanner.h
bash
#ifndef APP_I2C_SCANNER_H
#define APP_I2C_SCANNER_H
#include "main.h"
#include <stdint.h>
#ifndef APP_I2C_SCANNER_MAX_FOUND
#define APP_I2C_SCANNER_MAX_FOUND 16u
#endif
typedef struct
{
uint8_t address_7bit[APP_I2C_SCANNER_MAX_FOUND];
uint8_t count;
uint8_t overflow;
} App_I2CScanner_Result;
void App_I2CScanner_Init(void);
HAL_StatusTypeDef App_I2CScanner_IsReady7Bit(uint8_t address_7bit);
HAL_StatusTypeDef App_I2CScanner_Scan(App_I2CScanner_Result *result);
#endif
2. 新建 Core/Src/app_i2c_scanner.c
bash
#include "app_i2c_scanner.h"
/*
* Default I2C is I2C1.
* If your project uses I2C2 or another I2C instance, change this macro.
*/
#ifndef APP_I2C_SCANNER_HANDLE
#define APP_I2C_SCANNER_HANDLE hi2c1
#endif
/*
* STM32 HAL I2C APIs expect the 7-bit device address shifted left by 1.
* This module exposes 7-bit addresses to the application layer.
*/
#ifndef APP_I2C_SCANNER_TRIALS
#define APP_I2C_SCANNER_TRIALS 2u
#endif
#ifndef APP_I2C_SCANNER_TIMEOUT_MS
#define APP_I2C_SCANNER_TIMEOUT_MS 10u
#endif
#ifndef APP_I2C_SCANNER_ADDR_MIN
#define APP_I2C_SCANNER_ADDR_MIN 0x03u
#endif
#ifndef APP_I2C_SCANNER_ADDR_MAX
#define APP_I2C_SCANNER_ADDR_MAX 0x77u
#endif
extern I2C_HandleTypeDef APP_I2C_SCANNER_HANDLE;
void App_I2CScanner_Init(void)
{
}
HAL_StatusTypeDef App_I2CScanner_IsReady7Bit(uint8_t address_7bit)
{
return HAL_I2C_IsDeviceReady(&APP_I2C_SCANNER_HANDLE,
(uint16_t)(address_7bit << 1),
APP_I2C_SCANNER_TRIALS,
APP_I2C_SCANNER_TIMEOUT_MS);
}
HAL_StatusTypeDef App_I2CScanner_Scan(App_I2CScanner_Result *result)
{
uint8_t addr;
HAL_StatusTypeDef status;
if (result == 0)
{
return HAL_ERROR;
}
result->count = 0u;
result->overflow = 0u;
for (addr = APP_I2C_SCANNER_ADDR_MIN; addr <= APP_I2C_SCANNER_ADDR_MAX; addr++)
{
status = App_I2CScanner_IsReady7Bit(addr);
if (status == HAL_OK)
{
if (result->count < APP_I2C_SCANNER_MAX_FOUND)
{
result->address_7bit[result->count] = addr;
result->count++;
}
else
{
result->overflow = 1u;
}
}
}
return HAL_OK;
}
核心函数是:
bash
HAL_I2C_IsDeviceReady()
它会向指定地址发起一次测试。
如果设备回应 ACK,返回:
bash
HAL_OK
如果没有设备回应,或者总线异常,就不会返回 HAL_OK。
注意这一行:
bash
(uint16_t)(address_7bit << 1)
我们对外使用 7 位地址。
传给 HAL 时,左移 1 位。
这样串口打印出来的地址和大多数芯片手册里的 7 位地址保持一致。
main.c 调用方式
1. 添加头文件
在 main.c 顶部添加:
bash
/* USER CODE BEGIN Includes */
#include "app_i2c_scanner.h"
#include <stdio.h>
/* USER CODE END Includes */
2. 初始化后打印提示
确认 CubeMX 已生成:
bash
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
然后在 USER CODE BEGIN 2 中添加:
bash
/* USER CODE BEGIN 2 */
App_I2CScanner_Init();
printf("\r\nI2C scanner test\r\n");
printf("Use 7-bit address format.\r\n");
/* USER CODE END 2 */
3. while 循环里扫描并打印
bash
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
App_I2CScanner_Result result;
uint8_t i;
printf("\r\nScanning I2C bus...\r\n");
if (App_I2CScanner_Scan(&result) == HAL_OK)
{
if (result.count == 0u)
{
printf("No I2C device found.\r\n");
}
else
{
printf("Found %u device(s): ", result.count);
for (i = 0u; i < result.count; i++)
{
printf("0x%02X ", result.address_7bit[i]);
}
printf("\r\n");
if (result.overflow != 0u)
{
printf("Warning: too many devices, result list overflow.\r\n");
}
}
}
else
{
printf("I2C scan failed.\r\n");
}
HAL_Delay(3000);
/* USER CODE END 3 */
}
这里每 3 秒扫描一次。
你可以先不接设备,看它打印:
bash
No I2C device found.
再接上设备,看地址是否出现。
这样排查会很清楚。
编译、下载和验证
1. 不接 I2C 设备先验证程序
先只下载程序,不接任何 I2C 模块。
串口应该打印:
bash
I2C scanner test
Use 7-bit address format.
Scanning I2C bus...
No I2C device found.
如果这一步都没有输出,先回去查 USART,不要急着查 I2C。
2. 接上一个 I2C 模块
接线示例:
bash
模块 VCC -> 3.3V
模块 GND -> GND
模块 SCL -> STM32 I2C SCL
模块 SDA -> STM32 I2C SDA
然后复位开发板。
如果接的是 MPU6050,可能看到:
bash
Found 1 device(s): 0x68
如果接的是 EEPROM,可能看到:
bash
Found 1 device(s): 0x50
如果接的是 OLED,可能看到:
bash
Found 1 device(s): 0x3C
3. 拔掉 SDA 或 SCL 做反向验证
如果你想确认扫描结果真的来自 I2C 模块,可以做一个小测试:
-
正常接线,扫描到地址;
-
拔掉 SDA;
-
复位或等下一次扫描;
-
地址应该消失;
-
再接回 SDA,地址应该恢复。

这个测试很适合新手建立感觉:
bash
不是程序随便打印了一个地址
而是真的总线上有设备回应
移植到其他板子的修改点
|
要改的地方
|
为什么要改
|
在哪里改
|
| --- | --- | --- |
|
I2C 实例
|
可能用 I2C1、I2C2 或 I2C3
|
CubeMX,APP_I2C_SCANNER_HANDLE
|
|
SCL/SDA 引脚
|
不同板子引脚不同
|
CubeMX Pinout 和原理图
|
|
I2C 速度
|
有些设备只适合 100 kHz,有些支持 400 kHz
|
CubeMX I2C 参数
|
|
上拉电阻
|
I2C 必须有上拉
|
硬件原理图或外接电阻
|
|
设备供电电压
|
影响 SCL/SDA 上拉电平
|
模块说明和开发板电平
|
|
地址范围
|
有些特殊设备地址可能不在常用范围
| APP_I2C_SCANNER_ADDR_MIN/MAX |
|
扫描结果容量
|
总线上设备很多时可能超过 16 个
| APP_I2C_SCANNER_MAX_FOUND |
|
HAL 地址格式
|
HAL 通常要 7 位地址左移 1 位
| App_I2CScanner_IsReady7Bit() |
如果你用 I2C2,把代码里的默认句柄改成:
bash
#define APP_I2C_SCANNER_HANDLE hi2c2
如果你用 I2C3,就改成:
bash
#define APP_I2C_SCANNER_HANDLE hi2c3
常见问题排查
1. 串口没有任何输出
这不是 I2C 问题。
先按第 07 篇排查 USART:
-
TX/RX/GND 是否接对;
-
波特率是否一致;
-
printf()是否已经重定向; -
Keil 是否勾选 MicroLIB;
-
MX_USARTx_UART_Init()是否执行; -
串口助手是否选对端口。
2. 一直显示 No I2C device found
优先按这个顺序查:
|
检查项
|
说明
|
| --- | --- |
|
VCC/GND
|
模块是否供电,GND 是否共地
|
|
SCL/SDA
|
是否接反,是否接到 CubeMX 配置的那两个引脚
|
|
上拉电阻
|
模块或板子上是否有上拉
|
|
模块电压
|
3.3V 模块不要接错,5V 模块注意电平
|
|
I2C 实例
|
代码默认 hi2c1,实际是否用 I2C2
|
|
地址左移
|
代码内部已经左移,不要传 8 位地址再左移
|
I2C 扫不到设备时,硬件问题比代码问题更常见。
3. SCL/SDA 接反了会怎样
通常扫不到设备。
如果你不确定模块引脚顺序,先不要凭感觉接。
回去看模块丝印或原理图。
有些模块的引脚顺序是:
bash
VCC GND SCL SDA
有些是:
bash
GND VCC SCL SDA
看错一排针脚,很正常,也很耽误时间。
4. 扫出来的地址和资料不一样
先判断资料写的是 7 位地址还是 8 位地址。
比如资料写:
bash
0xA0
扫描器打印:
bash
0x50
这是正常的。
因为:
bash
0xA0 >> 1 = 0x50
再比如资料写:
bash
0xD0
扫描器打印:
bash
0x68
也是正常的:
bash
0xD0 >> 1 = 0x68
5. 程序卡住或者扫描很慢
本篇每个地址超时默认是:
bash
#define APP_I2C_SCANNER_TIMEOUT_MS 10u
扫描 0x03 到 0x77,如果很多地址都没有回应,整个扫描会花一点时间。
如果你觉得太慢,可以适当减小超时,比如:
bash
#define APP_I2C_SCANNER_TIMEOUT_MS 3u
但太短也可能导致慢设备来不及回应。
入门阶段先用 10 ms,稳定优先。
6. I2C 总线卡死
有时从机异常、程序下载中断、SCL/SDA 被拉低,可能导致总线卡死。
常见现象:
bash
SDA 一直低
SCL 一直低
扫描不到任何设备
复位 STM32 也没用
先做简单处理:
-
给 I2C 模块断电再上电;
-
复位 STM32;
-
检查是否有短路;
-
降低 I2C 速度到 100 kHz;
-
确认上拉电阻。
更完整的"总线恢复"后面 I2C 排坑篇再讲。
7. 编译报 hi2c1 未定义
说明你的工程里 I2C 句柄不叫 hi2c1,或者没有开启 I2C1。
解决方法:
-
看 CubeMX 里开启的是 I2C1 还是 I2C2;
-
打开
i2c.c,确认句柄名; -
修改:
bash
#define APP_I2C_SCANNER_HANDLE hi2c1
比如实际是 I2C2,就改成:
bash
#define APP_I2C_SCANNER_HANDLE hi2c2
8. 编译报 undefined symbol App_I2CScanner_Scan
通常是:
bash
app_i2c_scanner.c 没有加入 Keil 工程
解决方法:
-
右键
Application/User/Core; -
选择
Add Existing Files to Group; -
添加
Core/Src/app_i2c_scanner.c; -
重新编译。
本篇小结
这一篇我们没有急着读某个具体传感器,而是先做了一个通用 I2C Scanner。
你现在应该知道:
-
I2C 至少有 SCL 和 SDA 两根信号线;
-
I2C 总线需要上拉电阻;
-
STM32 当主机,模块通常当从机;
-
扫描器的本质是看某个地址有没有设备 ACK;
-
应用层建议统一显示 7 位地址;
-
传给 STM32 HAL 时,通常要把 7 位地址左移 1 位;
-
扫不到设备时,优先查供电、共地、SCL/SDA、上拉、电平和 I2C 实例。
下一篇继续 I2C:
STM32 I2C 读写寄存器:先读一个设备 ID 或配置寄存器。
到时候我们会在扫描到地址的基础上,再讲 HAL_I2C_Mem_Read() 和 HAL_I2C_Mem_Write() 怎么用。