|
@@ -24,6 +24,7 @@ I2C 的 SCL 和 SDA 都采用**开漏输出**(Open-Drain),必须外接**
|
|
|
*上图:I2C 总线硬件连接(开漏输出 + 上拉电阻)(来源:STM32入门教程 PPT 第128页)*
|
|
*上图:I2C 总线硬件连接(开漏输出 + 上拉电阻)(来源:STM32入门教程 PPT 第128页)*
|
|
|
|
|
|
|
|
对比 UART:
|
|
对比 UART:
|
|
|
|
|
+
|
|
|
- **UART**:点对点通信,推挽输出,一对一连接
|
|
- **UART**:点对点通信,推挽输出,一对一连接
|
|
|
- **I2C**:总线式通信,开漏输出,一对多连接(一主多从)
|
|
- **I2C**:总线式通信,开漏输出,一对多连接(一主多从)
|
|
|
|
|
|
|
@@ -72,12 +73,12 @@ SDA ────┘
|
|
|
|
|
|
|
|
### 速度模式
|
|
### 速度模式
|
|
|
|
|
|
|
|
-| 模式 | 最大波特率 |
|
|
|
|
|
-|------|-----------|
|
|
|
|
|
-| 标准模式 (SM) | 100 kbit/s |
|
|
|
|
|
-| 快速模式 (FM) | 400 kbit/s |
|
|
|
|
|
-| 快速增强模式 (FM+) | 1 Mbit/s |
|
|
|
|
|
-| 高速模式 (HS) | 3.4 Mbit/s |
|
|
|
|
|
|
|
+| 模式 | 最大波特率 |
|
|
|
|
|
+| ------------------ | ---------- |
|
|
|
|
|
+| 标准模式 (SM) | 100 kbit/s |
|
|
|
|
|
+| 快速模式 (FM) | 400 kbit/s |
|
|
|
|
|
+| 快速增强模式 (FM+) | 1 Mbit/s |
|
|
|
|
|
+| 高速模式 (HS) | 3.4 Mbit/s |
|
|
|
|
|
|
|
|
STM32F103 的 I2C 外设支持标准模式和快速模式。
|
|
STM32F103 的 I2C 外设支持标准模式和快速模式。
|
|
|
|
|
|
|
@@ -97,14 +98,15 @@ STM32F103 的 I2C 外设支持标准模式和快速模式。
|
|
|
|
|
|
|
|
以 I2C1 为例:
|
|
以 I2C1 为例:
|
|
|
|
|
|
|
|
-| 默认引脚 | 重映射后 |
|
|
|
|
|
-|---------|---------|
|
|
|
|
|
|
|
+| 默认引脚 | 重映射后 |
|
|
|
|
|
+| --------- | --------- |
|
|
|
| PB6 (SCL) | PB8 (SCL) |
|
|
| PB6 (SCL) | PB8 (SCL) |
|
|
|
| PB7 (SDA) | PB9 (SDA) |
|
|
| PB7 (SDA) | PB9 (SDA) |
|
|
|
|
|
|
|
|
引脚模式:**复用开漏输出**(GPIO_Mode_AF_OD)
|
|
引脚模式:**复用开漏输出**(GPIO_Mode_AF_OD)
|
|
|
|
|
|
|
|
为什么用复用 + 开漏?
|
|
为什么用复用 + 开漏?
|
|
|
|
|
+
|
|
|
- **复用**:因为由 I2C 模块内部硬件控制引脚,不是软件 GPIO 控制
|
|
- **复用**:因为由 I2C 模块内部硬件控制引脚,不是软件 GPIO 控制
|
|
|
- **开漏**:为了实现线与逻辑
|
|
- **开漏**:为了实现线与逻辑
|
|
|
|
|
|
|
@@ -159,27 +161,27 @@ I2C_Cmd(I2C1, ENABLE); // 闭合总开关
|
|
|
void MyI2C_Init(void) {
|
|
void MyI2C_Init(void) {
|
|
|
// 1. 开启 GPIOB 时钟
|
|
// 1. 开启 GPIOB 时钟
|
|
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
|
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 2. 开启 AFIO 时钟(重映射需要)
|
|
// 2. 开启 AFIO 时钟(重映射需要)
|
|
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
|
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 3. 重映射 I2C1 到 PB8/PB9
|
|
// 3. 重映射 I2C1 到 PB8/PB9
|
|
|
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
|
|
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 4. 初始化 PB8(SCL) 和 PB9(SDA) 为复用开漏输出
|
|
// 4. 初始化 PB8(SCL) 和 PB9(SDA) 为复用开漏输出
|
|
|
GPIO_InitTypeDef GPIO_InitStructure;
|
|
GPIO_InitTypeDef GPIO_InitStructure;
|
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
|
|
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
|
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
|
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
|
|
GPIO_Init(GPIOB, &GPIO_InitStructure);
|
|
GPIO_Init(GPIOB, &GPIO_InitStructure);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 5. 开启 I2C1 时钟
|
|
// 5. 开启 I2C1 时钟
|
|
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
|
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 6. 复位 I2C1
|
|
// 6. 复位 I2C1
|
|
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
|
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
|
|
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
|
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 7. 初始化 I2C1
|
|
// 7. 初始化 I2C1
|
|
|
I2C_InitTypeDef I2C_InitStructure;
|
|
I2C_InitTypeDef I2C_InitStructure;
|
|
|
I2C_InitStructure.I2C_ClockSpeed = 400000;
|
|
I2C_InitStructure.I2C_ClockSpeed = 400000;
|
|
@@ -189,7 +191,7 @@ void MyI2C_Init(void) {
|
|
|
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
|
|
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
|
|
|
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
|
|
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
|
|
|
I2C_Init(I2C1, &I2C_InitStructure);
|
|
I2C_Init(I2C1, &I2C_InitStructure);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 8. 使能 I2C1
|
|
// 8. 使能 I2C1
|
|
|
I2C_Cmd(I2C1, ENABLE);
|
|
I2C_Cmd(I2C1, ENABLE);
|
|
|
}
|
|
}
|
|
@@ -201,7 +203,7 @@ void MyI2C_Init(void) {
|
|
|
|
|
|
|
|
使用 STM32CubeMX 生成的 HAL 库代码,I2C 初始化通过 `I2C_HandleTypeDef` 结构体完成:
|
|
使用 STM32CubeMX 生成的 HAL 库代码,I2C 初始化通过 `I2C_HandleTypeDef` 结构体完成:
|
|
|
|
|
|
|
|
-~~~c
|
|
|
|
|
|
|
+```c
|
|
|
/* HAL库 — I2C 句柄定义与初始化 */
|
|
/* HAL库 — I2C 句柄定义与初始化 */
|
|
|
I2C_HandleTypeDef hi2c1;
|
|
I2C_HandleTypeDef hi2c1;
|
|
|
|
|
|
|
@@ -234,7 +236,7 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
|
|
|
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
|
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-~~~
|
|
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
关键区别:
|
|
关键区别:
|
|
|
|
|
|
|
@@ -260,15 +262,15 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
|
|
|
|
|
|
|
|
### 关键标志位
|
|
### 关键标志位
|
|
|
|
|
|
|
|
-| 标志位 | 含义 |
|
|
|
|
|
-|--------|------|
|
|
|
|
|
-| **BUSY** (SR2) | 总线忙标志,=1 时总线正在通信 |
|
|
|
|
|
-| **SB** (SR1) | 起始位已发送 |
|
|
|
|
|
-| **ADDR** (SR1) | 地址已发送且收到 ACK(寻址成功) |
|
|
|
|
|
-| **AF** (SR1) | 应答失败(NACK received) |
|
|
|
|
|
-| **TXE** (SR1) | 发送数据寄存器空,可写入下一个数据 |
|
|
|
|
|
-| **BTF** (SR1) | Byte Transfer Finished,字节传输完成(移位寄存器和 DR 均空) |
|
|
|
|
|
-| **RXNE** (SR1) | 接收数据寄存器非空,有数据可读 |
|
|
|
|
|
|
|
+| 标志位 | 含义 |
|
|
|
|
|
+| -------------------- | ------------------------------------------------------------ |
|
|
|
|
|
+| **BUSY** (SR2) | 总线忙标志,=1 时总线正在通信 |
|
|
|
|
|
+| **SB** (SR1) | 起始位已发送 |
|
|
|
|
|
+| **ADDR** (SR1) | 地址已发送且收到 ACK(寻址成功) |
|
|
|
|
|
+| **AF** (SR1) | 应答失败(NACK received) |
|
|
|
|
|
+| **TXE** (SR1) | 发送数据寄存器空,可写入下一个数据 |
|
|
|
|
|
+| **BTF** (SR1) | Byte Transfer Finished,字节传输完成(移位寄存器和 DR 均空) |
|
|
|
|
|
+| **RXNE** (SR1) | 接收数据寄存器非空,有数据可读 |
|
|
|
|
|
|
|
|
### 写数据完整流程
|
|
### 写数据完整流程
|
|
|
|
|
|
|
@@ -284,15 +286,15 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
|
|
|
int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Size) {
|
|
int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Size) {
|
|
|
// === 阶段一:等待总线空闲 ===
|
|
// === 阶段一:等待总线空闲 ===
|
|
|
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
|
|
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// === 阶段二:发送起始位 ===
|
|
// === 阶段二:发送起始位 ===
|
|
|
I2C_GenerateSTART(I2Cx, ENABLE);
|
|
I2C_GenerateSTART(I2Cx, ENABLE);
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB)); // 等待起始位发送完成
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB)); // 等待起始位发送完成
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// === 阶段三:寻址阶段 ===
|
|
// === 阶段三:寻址阶段 ===
|
|
|
I2C_ClearFlag(I2Cx, I2C_FLAG_AF); // 先清除 AF 标志位
|
|
I2C_ClearFlag(I2Cx, I2C_FLAG_AF); // 先清除 AF 标志位
|
|
|
I2C_SendData(I2Cx, Addr & 0xFE); // 地址 + R/W=0(写)
|
|
I2C_SendData(I2Cx, Addr & 0xFE); // 地址 + R/W=0(写)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 等待寻址结果
|
|
// 等待寻址结果
|
|
|
while (1) {
|
|
while (1) {
|
|
|
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR)) // 寻址成功
|
|
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR)) // 寻址成功
|
|
@@ -302,11 +304,11 @@ int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Si
|
|
|
return 1;
|
|
return 1;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 清除 ADDR 标志位(必须:先读 SR1,再读 SR2)
|
|
// 清除 ADDR 标志位(必须:先读 SR1,再读 SR2)
|
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
|
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// === 阶段四:发送数据 ===
|
|
// === 阶段四:发送数据 ===
|
|
|
for (uint16_t i = 0; i < Size; i++) {
|
|
for (uint16_t i = 0; i < Size; i++) {
|
|
|
// 等待可以发送下一个字节
|
|
// 等待可以发送下一个字节
|
|
@@ -320,7 +322,7 @@ int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Si
|
|
|
}
|
|
}
|
|
|
I2C_SendData(I2Cx, pData[i]); // 写入发送数据寄存器
|
|
I2C_SendData(I2Cx, pData[i]); // 写入发送数据寄存器
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 等待最后一个字节发送完成
|
|
// 等待最后一个字节发送完成
|
|
|
while (1) {
|
|
while (1) {
|
|
|
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)) {
|
|
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)) {
|
|
@@ -330,7 +332,7 @@ int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Si
|
|
|
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF)) // 整个字节传输完成
|
|
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF)) // 整个字节传输完成
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// === 阶段五:发送停止位 ===
|
|
// === 阶段五:发送停止位 ===
|
|
|
I2C_GenerateSTOP(I2Cx, ENABLE);
|
|
I2C_GenerateSTOP(I2Cx, ENABLE);
|
|
|
return 0; // 成功
|
|
return 0; // 成功
|
|
@@ -351,9 +353,9 @@ uint8_t Command[] = {0x00, 0x8D, 0x14, 0xAF, 0xA5}; // OLED 开启命令
|
|
|
|
|
|
|
|
int main(void) {
|
|
int main(void) {
|
|
|
OLED_Init(); // 内部调用 MyI2C_Init()
|
|
OLED_Init(); // 内部调用 MyI2C_Init()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
MyI2C_SendBytes(I2C1, 0x78, Command, 5);
|
|
MyI2C_SendBytes(I2C1, 0x78, Command, 5);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// OLED 屏幕点亮
|
|
// OLED 屏幕点亮
|
|
|
while (1);
|
|
while (1);
|
|
|
}
|
|
}
|
|
@@ -365,7 +367,7 @@ OLED 地址是 0x78,这里传的是已经左对齐的地址(最低位为 R/W
|
|
|
|
|
|
|
|
HAL 库将 I2C 写操作封装为两个高级函数,无需手动操作 EV5/EV6/EV7 事件标志位:
|
|
HAL 库将 I2C 写操作封装为两个高级函数,无需手动操作 EV5/EV6/EV7 事件标志位:
|
|
|
|
|
|
|
|
-~~~c
|
|
|
|
|
|
|
+```c
|
|
|
/* HAL库 — I2C 主机发送(直接发送数据,无寄存器地址) */
|
|
/* HAL库 — I2C 主机发送(直接发送数据,无寄存器地址) */
|
|
|
// 原型:HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout)
|
|
// 原型:HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout)
|
|
|
// DevAddress:7位从机地址左移1位(即含R/W位的8位地址)
|
|
// DevAddress:7位从机地址左移1位(即含R/W位的8位地址)
|
|
@@ -401,9 +403,10 @@ HAL_StatusTypeDef ret = HAL_I2C_Mem_Write(
|
|
|
/* HAL库 — 同时写多个寄存器 */
|
|
/* HAL库 — 同时写多个寄存器 */
|
|
|
uint8_t configData[] = {0x6B, 0x01, 0x6C, 0x00, 0x19, 0x09};
|
|
uint8_t configData[] = {0x6B, 0x01, 0x6C, 0x00, 0x19, 0x09};
|
|
|
HAL_I2C_Master_Transmit(&hi2c1, 0xD0, configData, 6, 100);
|
|
HAL_I2C_Master_Transmit(&hi2c1, 0xD0, configData, 6, 100);
|
|
|
-~~~
|
|
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
HAL 库内部自动处理了:
|
|
HAL 库内部自动处理了:
|
|
|
|
|
+
|
|
|
1. 生成起始条件(EV5:SB 标志位)
|
|
1. 生成起始条件(EV5:SB 标志位)
|
|
|
2. 发送地址 + 等待 ADDR 标志(EV6)
|
|
2. 发送地址 + 等待 ADDR 标志(EV6)
|
|
|
3. 逐个字节发送数据 + 等待 TXE/BTF
|
|
3. 逐个字节发送数据 + 等待 TXE/BTF
|
|
@@ -425,51 +428,51 @@ HAL 库内部自动处理了:
|
|
|
```c
|
|
```c
|
|
|
uint8_t MyI2C_ReadByte(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t RegAddr) {
|
|
uint8_t MyI2C_ReadByte(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t RegAddr) {
|
|
|
uint8_t Data;
|
|
uint8_t Data;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 阶段 1-2:起始位
|
|
// 阶段 1-2:起始位
|
|
|
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
|
|
while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
|
|
|
I2C_GenerateSTART(I2Cx, ENABLE);
|
|
I2C_GenerateSTART(I2Cx, ENABLE);
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 阶段 3:写从机地址(写方向)—— 先写寄存器地址
|
|
// 阶段 3:写从机地址(写方向)—— 先写寄存器地址
|
|
|
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
|
|
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
|
|
|
I2C_SendData(I2Cx, Addr & 0xFE);
|
|
I2C_SendData(I2Cx, Addr & 0xFE);
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR));
|
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
|
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 发送寄存器地址
|
|
// 发送寄存器地址
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE));
|
|
|
I2C_SendData(I2Cx, RegAddr);
|
|
I2C_SendData(I2Cx, RegAddr);
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 阶段 4:重复起始位 —— 切换方向
|
|
// 阶段 4:重复起始位 —— 切换方向
|
|
|
I2C_GenerateSTART(I2Cx, ENABLE);
|
|
I2C_GenerateSTART(I2Cx, ENABLE);
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 阶段 5:写从机地址(读方向)
|
|
// 阶段 5:写从机地址(读方向)
|
|
|
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
|
|
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
|
|
|
I2C_SendData(I2Cx, Addr | 0x01);
|
|
I2C_SendData(I2Cx, Addr | 0x01);
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR));
|
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR1);
|
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
|
|
I2C_ReadRegister(I2Cx, I2C_Register_SR2);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 阶段 6:接收数据 + 发 NACK
|
|
// 阶段 6:接收数据 + 发 NACK
|
|
|
I2C_AcknowledgeConfig(I2Cx, DISABLE); // 不发 ACK
|
|
I2C_AcknowledgeConfig(I2Cx, DISABLE); // 不发 ACK
|
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE));
|
|
while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE));
|
|
|
Data = I2C_ReceiveData(I2Cx);
|
|
Data = I2C_ReceiveData(I2Cx);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 阶段 7:停止位
|
|
// 阶段 7:停止位
|
|
|
I2C_GenerateSTOP(I2Cx, ENABLE);
|
|
I2C_GenerateSTOP(I2Cx, ENABLE);
|
|
|
I2C_AcknowledgeConfig(I2Cx, ENABLE); // 恢复 ACK
|
|
I2C_AcknowledgeConfig(I2Cx, ENABLE); // 恢复 ACK
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return Data;
|
|
return Data;
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
### HAL库读操作
|
|
### HAL库读操作
|
|
|
|
|
|
|
|
-~~~c
|
|
|
|
|
|
|
+```c
|
|
|
/* HAL库 — I2C 主机接收(直接读取从机数据,无寄存器地址) */
|
|
/* HAL库 — I2C 主机接收(直接读取从机数据,无寄存器地址) */
|
|
|
// 原型:HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout)
|
|
// 原型:HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout)
|
|
|
uint8_t rxBuffer[6];
|
|
uint8_t rxBuffer[6];
|
|
@@ -507,7 +510,7 @@ int16_t AccZ = (mpuBuffer[4] << 8) | mpuBuffer[5];
|
|
|
int16_t GyroX = (mpuBuffer[8] << 8) | mpuBuffer[9];
|
|
int16_t GyroX = (mpuBuffer[8] << 8) | mpuBuffer[9];
|
|
|
int16_t GyroY = (mpuBuffer[10] << 8) | mpuBuffer[11];
|
|
int16_t GyroY = (mpuBuffer[10] << 8) | mpuBuffer[11];
|
|
|
int16_t GyroZ = (mpuBuffer[12] << 8) | mpuBuffer[13];
|
|
int16_t GyroZ = (mpuBuffer[12] << 8) | mpuBuffer[13];
|
|
|
-~~~
|
|
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
HAL 库的 `HAL_I2C_Mem_Read` 内部自动执行:Start → 地址+W → 寄存器地址 → Repeated Start → 地址+R → NACK → Stop,完全封装了重复起始位和 NACK 发送逻辑。
|
|
HAL 库的 `HAL_I2C_Mem_Read` 内部自动执行:Start → 地址+W → 寄存器地址 → Repeated Start → 地址+R → NACK → Stop,完全封装了重复起始位和 NACK 发送逻辑。
|
|
|
|
|
|
|
@@ -621,20 +624,20 @@ void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data) {
|
|
|
// 读寄存器
|
|
// 读寄存器
|
|
|
uint8_t MPU6050_ReadReg(uint8_t RegAddress) {
|
|
uint8_t MPU6050_ReadReg(uint8_t RegAddress) {
|
|
|
uint8_t Data;
|
|
uint8_t Data;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
MyI2C_Start();
|
|
MyI2C_Start();
|
|
|
MyI2C_SendByte(MPU6050_ADDRESS); // 写方向
|
|
MyI2C_SendByte(MPU6050_ADDRESS); // 写方向
|
|
|
MyI2C_ReceiveAck();
|
|
MyI2C_ReceiveAck();
|
|
|
MyI2C_SendByte(RegAddress); // 指定寄存器地址
|
|
MyI2C_SendByte(RegAddress); // 指定寄存器地址
|
|
|
MyI2C_ReceiveAck();
|
|
MyI2C_ReceiveAck();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
MyI2C_Start(); // 重复起始位
|
|
MyI2C_Start(); // 重复起始位
|
|
|
MyI2C_SendByte(MPU6050_ADDRESS | 0x01); // 读方向
|
|
MyI2C_SendByte(MPU6050_ADDRESS | 0x01); // 读方向
|
|
|
MyI2C_ReceiveAck();
|
|
MyI2C_ReceiveAck();
|
|
|
Data = MyI2C_ReceiveByte();
|
|
Data = MyI2C_ReceiveByte();
|
|
|
MyI2C_SendAck(1); // 发 NACK
|
|
MyI2C_SendAck(1); // 发 NACK
|
|
|
MyI2C_Stop();
|
|
MyI2C_Stop();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return Data;
|
|
return Data;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -659,38 +662,138 @@ void MPU6050_Init(void) {
|
|
|
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
|
|
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
|
|
|
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) {
|
|
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) {
|
|
|
uint8_t DataH, DataL;
|
|
uint8_t DataH, DataL;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
DataH = MPU6050_ReadReg(0x3B); // ACCEL_XOUT_H
|
|
DataH = MPU6050_ReadReg(0x3B); // ACCEL_XOUT_H
|
|
|
DataL = MPU6050_ReadReg(0x3C);
|
|
DataL = MPU6050_ReadReg(0x3C);
|
|
|
*AccX = (DataH << 8) | DataL;
|
|
*AccX = (DataH << 8) | DataL;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
DataH = MPU6050_ReadReg(0x3D); // ACCEL_YOUT_H
|
|
DataH = MPU6050_ReadReg(0x3D); // ACCEL_YOUT_H
|
|
|
DataL = MPU6050_ReadReg(0x3E);
|
|
DataL = MPU6050_ReadReg(0x3E);
|
|
|
*AccY = (DataH << 8) | DataL;
|
|
*AccY = (DataH << 8) | DataL;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
DataH = MPU6050_ReadReg(0x3F); // ACCEL_ZOUT_H
|
|
DataH = MPU6050_ReadReg(0x3F); // ACCEL_ZOUT_H
|
|
|
DataL = MPU6050_ReadReg(0x40);
|
|
DataL = MPU6050_ReadReg(0x40);
|
|
|
*AccZ = (DataH << 8) | DataL;
|
|
*AccZ = (DataH << 8) | DataL;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
DataH = MPU6050_ReadReg(0x43); // GYRO_XOUT_H
|
|
DataH = MPU6050_ReadReg(0x43); // GYRO_XOUT_H
|
|
|
DataL = MPU6050_ReadReg(0x44);
|
|
DataL = MPU6050_ReadReg(0x44);
|
|
|
*GyroX = (DataH << 8) | DataL;
|
|
*GyroX = (DataH << 8) | DataL;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
DataH = MPU6050_ReadReg(0x45); // GYRO_YOUT_H
|
|
DataH = MPU6050_ReadReg(0x45); // GYRO_YOUT_H
|
|
|
DataL = MPU6050_ReadReg(0x46);
|
|
DataL = MPU6050_ReadReg(0x46);
|
|
|
*GyroY = (DataH << 8) | DataL;
|
|
*GyroY = (DataH << 8) | DataL;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
DataH = MPU6050_ReadReg(0x47); // GYRO_ZOUT_H
|
|
DataH = MPU6050_ReadReg(0x47); // GYRO_ZOUT_H
|
|
|
DataL = MPU6050_ReadReg(0x48);
|
|
DataL = MPU6050_ReadReg(0x48);
|
|
|
*GyroZ = (DataH << 8) | DataL;
|
|
*GyroZ = (DataH << 8) | DataL;
|
|
|
}
|
|
}
|
|
|
-```
|
|
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+### 寄存器:软件模拟I2C(尚硅谷风格)
|
|
|
|
|
+
|
|
|
|
|
+~~~c
|
|
|
|
|
+#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
|
|
|
|
|
+#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
|
|
|
|
|
+#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
|
|
|
|
|
+#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
|
|
|
|
|
+#define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)
|
|
|
|
|
+#define I2C_DELAY Delay_us(10)
|
|
|
|
|
+
|
|
|
|
|
+void I2C_Init(void) {
|
|
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
|
|
|
|
|
+ GPIOB->CRH |= GPIO_CRH_MODE10 | GPIO_CRH_MODE11;
|
|
|
|
|
+ GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
|
|
|
|
|
+ GPIOB->CRH |= GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void I2C_Start(void) {
|
|
|
|
|
+ SCL_HIGH; SDA_HIGH; I2C_DELAY;
|
|
|
|
|
+ SDA_LOW; I2C_DELAY;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void I2C_Stop(void) {
|
|
|
|
|
+ SCL_HIGH; SDA_LOW; I2C_DELAY;
|
|
|
|
|
+ SDA_HIGH; I2C_DELAY;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t I2C_Wait4Ack(void) {
|
|
|
|
|
+ SDA_HIGH; SCL_LOW; I2C_DELAY;
|
|
|
|
|
+ SCL_HIGH; I2C_DELAY;
|
|
|
|
|
+ uint8_t ack = READ_SDA;
|
|
|
|
|
+ SCL_LOW; I2C_DELAY;
|
|
|
|
|
+ return ack ? NACK : ACK;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void I2C_SendByte(uint8_t byte) {
|
|
|
|
|
+ for (uint8_t i = 0; i < 8; i++) {
|
|
|
|
|
+ SCL_LOW; I2C_DELAY;
|
|
|
|
|
+ if (byte & 0x80) SDA_HIGH; else SDA_LOW;
|
|
|
|
|
+ I2C_DELAY;
|
|
|
|
|
+ SCL_HIGH; I2C_DELAY;
|
|
|
|
|
+ SCL_LOW; I2C_DELAY;
|
|
|
|
|
+ byte <<= 1;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t I2C_ReadByte(void) {
|
|
|
|
|
+ uint8_t data = 0;
|
|
|
|
|
+ for (uint8_t i = 0; i < 8; i++) {
|
|
|
|
|
+ SDA_HIGH; SCL_LOW; I2C_DELAY;
|
|
|
|
|
+ SCL_HIGH; I2C_DELAY;
|
|
|
|
|
+ data <<= 1;
|
|
|
|
|
+ if (READ_SDA) data |= 0x01;
|
|
|
|
|
+ SCL_LOW; I2C_DELAY;
|
|
|
|
|
+ }
|
|
|
|
|
+ return data;
|
|
|
|
|
+}
|
|
|
|
|
+~~~
|
|
|
|
|
+
|
|
|
|
|
+### 寄存器:硬件I2C外设(尚硅谷风格)
|
|
|
|
|
+
|
|
|
|
|
+~~~c
|
|
|
|
|
+void I2C_Init(void) {
|
|
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
|
|
|
|
|
+ RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
|
|
|
|
|
+ GPIOB->CRH |= GPIO_CRH_MODE10 | GPIO_CRH_MODE11 | GPIO_CRH_CNF10 | GPIO_CRH_CNF11;
|
|
|
|
|
+
|
|
|
|
|
+ I2C2->CR1 &= ~I2C_CR1_SMBUS;
|
|
|
|
|
+ I2C2->CCR &= ~I2C_CCR_FS;
|
|
|
|
|
+ I2C2->CR2 |= 36;
|
|
|
|
|
+ I2C2->CCR |= 180;
|
|
|
|
|
+ I2C2->TRISE |= 37;
|
|
|
|
|
+ I2C2->CR1 |= I2C_CR1_PE;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t I2C_Start(void) {
|
|
|
|
|
+ I2C2->CR1 |= I2C_CR1_START;
|
|
|
|
|
+ uint16_t timeout = 0xffff;
|
|
|
|
|
+ while ((I2C2->SR1 & I2C_SR1_SB) == 0 && timeout--) {}
|
|
|
|
|
+ return timeout ? OK : FAIL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t I2C_SendAddr(uint8_t addr) {
|
|
|
|
|
+ I2C2->DR = addr;
|
|
|
|
|
+ uint16_t timeout = 0xffff;
|
|
|
|
|
+ while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout--) {}
|
|
|
|
|
+ if (timeout > 0) I2C2->SR2;
|
|
|
|
|
+ return timeout ? OK : FAIL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t I2C_SendByte(uint8_t byte) {
|
|
|
|
|
+ uint16_t timeout = 0xffff;
|
|
|
|
|
+ while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout--) {}
|
|
|
|
|
+ I2C2->DR = byte;
|
|
|
|
|
+ timeout = 0xffff;
|
|
|
|
|
+ while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout--) {}
|
|
|
|
|
+ return timeout ? OK : FAIL;
|
|
|
|
|
+}
|
|
|
|
|
+~~~
|
|
|
|
|
|
|
|
### 硬件 I2C(标准库 — EV5/EV6/EV7 事件驱动)
|
|
### 硬件 I2C(标准库 — EV5/EV6/EV7 事件驱动)
|
|
|
|
|
|
|
|
如果使用 STM32 硬件 I2C 外设 + 标准库,MPU6050 驱动通过 I2C 事件标志位控制时序(EV5 = SB起始已发送,EV6 = ADDR寻址成功,EV7 = RXNE数据可读):
|
|
如果使用 STM32 硬件 I2C 外设 + 标准库,MPU6050 驱动通过 I2C 事件标志位控制时序(EV5 = SB起始已发送,EV6 = ADDR寻址成功,EV7 = RXNE数据可读):
|
|
|
|
|
|
|
|
-~~~c
|
|
|
|
|
|
|
+```c
|
|
|
/* 标准库 — 硬件 I2C MPU6050 驱动(基于 I2C2,PB10/PB11) */
|
|
/* 标准库 — 硬件 I2C MPU6050 驱动(基于 I2C2,PB10/PB11) */
|
|
|
|
|
|
|
|
#define MPU6050_ADDRESS 0xD0
|
|
#define MPU6050_ADDRESS 0xD0
|
|
@@ -785,24 +888,24 @@ void MPU6050_Init(void)
|
|
|
MPU6050_WriteReg(0x1B, 0x18);
|
|
MPU6050_WriteReg(0x1B, 0x18);
|
|
|
MPU6050_WriteReg(0x1C, 0x18);
|
|
MPU6050_WriteReg(0x1C, 0x18);
|
|
|
}
|
|
}
|
|
|
-~~~
|
|
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
事件与标志位对应关系:
|
|
事件与标志位对应关系:
|
|
|
|
|
|
|
|
-| I2C 事件 | 标志位 | 说明 |
|
|
|
|
|
-|----------|--------|------|
|
|
|
|
|
-| `I2C_EVENT_MASTER_MODE_SELECT` | SB | EV5:起始位已发送 |
|
|
|
|
|
-| `I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED` | ADDR | EV6:地址已发送且收到 ACK(发送模式) |
|
|
|
|
|
-| `I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED` | ADDR | EV6:地址已发送且收到 ACK(接收模式) |
|
|
|
|
|
-| `I2C_EVENT_MASTER_BYTE_TRANSMITTING` | TXE | EV8:数据寄存器空,可发送下一字节 |
|
|
|
|
|
-| `I2C_EVENT_MASTER_BYTE_TRANSMITTED` | BTF | EV8_2:字节传输完成(DR + 移位寄存器均空) |
|
|
|
|
|
-| `I2C_EVENT_MASTER_BYTE_RECEIVED` | RXNE | EV7:收到一个字节,数据可读 |
|
|
|
|
|
|
|
+| I2C 事件 | 标志位 | 说明 |
|
|
|
|
|
+| ---------------------------------------------- | ------ | ------------------------------------------ |
|
|
|
|
|
+| `I2C_EVENT_MASTER_MODE_SELECT` | SB | EV5:起始位已发送 |
|
|
|
|
|
+| `I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED` | ADDR | EV6:地址已发送且收到 ACK(发送模式) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED` | ADDR | EV6:地址已发送且收到 ACK(接收模式) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_BYTE_TRANSMITTING` | TXE | EV8:数据寄存器空,可发送下一字节 |
|
|
|
|
|
+| `I2C_EVENT_MASTER_BYTE_TRANSMITTED` | BTF | EV8_2:字节传输完成(DR + 移位寄存器均空) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_BYTE_RECEIVED` | RXNE | EV7:收到一个字节,数据可读 |
|
|
|
|
|
|
|
|
### HAL库下的 MPU6050 驱动
|
|
### HAL库下的 MPU6050 驱动
|
|
|
|
|
|
|
|
如果使用 HAL 库,MPU6050 驱动可简化为直接调用 `HAL_I2C_Mem_Write/Read`,不需要自己实现软件 I2C 时序:
|
|
如果使用 HAL 库,MPU6050 驱动可简化为直接调用 `HAL_I2C_Mem_Write/Read`,不需要自己实现软件 I2C 时序:
|
|
|
|
|
|
|
|
-~~~c
|
|
|
|
|
|
|
+```c
|
|
|
/* HAL库 — MPU6050 驱动(基于 HAL_I2C_Mem_Write/Read) */
|
|
/* HAL库 — MPU6050 驱动(基于 HAL_I2C_Mem_Write/Read) */
|
|
|
|
|
|
|
|
#define MPU6050_ADDR_7BIT 0x68 // 7位原始地址(不含R/W位)
|
|
#define MPU6050_ADDR_7BIT 0x68 // 7位原始地址(不含R/W位)
|
|
@@ -846,9 +949,10 @@ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
|
|
|
*GyroY = (buf[10] << 8) | buf[11];
|
|
*GyroY = (buf[10] << 8) | buf[11];
|
|
|
*GyroZ = (buf[12] << 8) | buf[13];
|
|
*GyroZ = (buf[12] << 8) | buf[13];
|
|
|
}
|
|
}
|
|
|
-~~~
|
|
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
对比标准库方案,HAL 库方案的优点:
|
|
对比标准库方案,HAL 库方案的优点:
|
|
|
|
|
+
|
|
|
- **无需手动操作 EV5/EV6/EV7 事件**——HAL 库内部封装
|
|
- **无需手动操作 EV5/EV6/EV7 事件**——HAL 库内部封装
|
|
|
- **`HAL_I2C_Mem_Read` 一次性完成写寄存器地址 + 重复起始 + 读数据**
|
|
- **`HAL_I2C_Mem_Read` 一次性完成写寄存器地址 + 重复起始 + 读数据**
|
|
|
- **地址传入方式不同**:`HAL_I2C_Mem_Write/Read` 的 DevAddress 参数传左对齐地址(含 R/W 位,如 0xD0)
|
|
- **地址传入方式不同**:`HAL_I2C_Mem_Write/Read` 的 DevAddress 参数传左对齐地址(含 R/W 位,如 0xD0)
|
|
@@ -857,14 +961,14 @@ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
|
|
|
|
|
|
|
|
## 6. 硬件 I2C vs 软件 I2C
|
|
## 6. 硬件 I2C vs 软件 I2C
|
|
|
|
|
|
|
|
-| 对比 | 硬件 I2C | 软件 I2C |
|
|
|
|
|
-|------|---------|---------|
|
|
|
|
|
-| CPU 占用 | 低(硬件控制时序) | 高(CPU 逐位模拟) |
|
|
|
|
|
-| 引脚灵活性 | 需指定引脚 | 任意 GPIO |
|
|
|
|
|
-| 速度 | 可达 400kHz | 取决于延时精度 |
|
|
|
|
|
-| 抗干扰 | 好(硬件滤波) | 差 |
|
|
|
|
|
-| 编程复杂度 | 较高(寄存器/标志位多) | 简单直观 |
|
|
|
|
|
-| 适用场景 | 高速、复杂系统 | 低速、原型验证、引脚不足 |
|
|
|
|
|
|
|
+| 对比 | 硬件 I2C | 软件 I2C |
|
|
|
|
|
+| ---------- | ----------------------- | ------------------------ |
|
|
|
|
|
+| CPU 占用 | 低(硬件控制时序) | 高(CPU 逐位模拟) |
|
|
|
|
|
+| 引脚灵活性 | 需指定引脚 | 任意 GPIO |
|
|
|
|
|
+| 速度 | 可达 400kHz | 取决于延时精度 |
|
|
|
|
|
+| 抗干扰 | 好(硬件滤波) | 差 |
|
|
|
|
|
+| 编程复杂度 | 较高(寄存器/标志位多) | 简单直观 |
|
|
|
|
|
+| 适用场景 | 高速、复杂系统 | 低速、原型验证、引脚不足 |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -886,6 +990,7 @@ I2C 总线上必须接上拉电阻。如果未接或阻值过大,SCL/SDA 波
|
|
|
### 地址冲突
|
|
### 地址冲突
|
|
|
|
|
|
|
|
多个 I2C 设备使用相同地址会导致冲突。MPU6050 的 AD0 引脚可改变地址:
|
|
多个 I2C 设备使用相同地址会导致冲突。MPU6050 的 AD0 引脚可改变地址:
|
|
|
|
|
+
|
|
|
- AD0 = 0:地址 0x68
|
|
- AD0 = 0:地址 0x68
|
|
|
- AD0 = 1:地址 0x69
|
|
- AD0 = 1:地址 0x69
|
|
|
|
|
|
|
@@ -903,6 +1008,7 @@ I2C 总线上必须接上拉电阻。如果未接或阻值过大,SCL/SDA 波
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
> 本笔记综合整理自三套 STM32 教程:
|
|
> 本笔记综合整理自三套 STM32 教程:
|
|
|
|
|
+>
|
|
|
> 1. [STM32 HAL库] I2C 基础知识 / 数据收发实验
|
|
> 1. [STM32 HAL库] I2C 基础知识 / 数据收发实验
|
|
|
> 2. 铁头山羊 I2C 系列(4.1~4.8)
|
|
> 2. 铁头山羊 I2C 系列(4.1~4.8)
|
|
|
> 3. 江协科技 I2C 系列(10-1~10-5)
|
|
> 3. 江协科技 I2C 系列(10-1~10-5)
|