|
@@ -0,0 +1,503 @@
|
|
|
|
|
+# 13-I2C通信
|
|
|
|
|
+
|
|
|
|
|
+## 原理
|
|
|
|
|
+
|
|
|
|
|
+### I2C 协议
|
|
|
|
|
+
|
|
|
|
|
+- **类型**:同步半双工
|
|
|
|
|
+- **总线**:SCL (时钟) + SDA (数据)
|
|
|
|
|
+- **设备**:主设备 (Master) 发起通信,从设备 (Slave) 响应地址
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ +---+
|
|
|
|
|
+SCL -----+ +---+
|
|
|
|
|
+ | |
|
|
|
|
|
+SDA -----+ +---------
|
|
|
|
|
+ | |
|
|
|
|
|
+ Master Slave
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 通信时序
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ 起始条件 字节传输(MSB first) 应答 停止条件
|
|
|
|
|
+ _____ _____ _____ _____ _____ _____
|
|
|
|
|
+SCL | |_____| |_| |_| |____| |__| |
|
|
|
|
|
+ _ ___ ___ ___ ___ ___ ___
|
|
|
|
|
+SDA X_| 7位地址 |_| R/W |_| 数据MSB |_| ... |_| 应答 |_X_|
|
|
|
|
|
+ |_| |_| |_| |_| |_| |_| |_|
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**时序规则**:
|
|
|
|
|
+1. **起始条件 (Start)**:SCL 高电平时,SDA 下降沿
|
|
|
|
|
+2. **停止条件 (Stop)**:SCL 高电平时,SDA 上升沿
|
|
|
|
|
+3. **数据变化**:SCL 低电平时 SDA 变化
|
|
|
|
|
+4. **数据采样**:SCL 高电平时采样 SDA
|
|
|
|
|
+
|
|
|
|
|
+**数据帧格式**:
|
|
|
|
|
+- 起始条件
|
|
|
|
|
+- 7位从机地址 + R/W 位 (0=写,1=读)
|
|
|
|
|
+- 从机应答 (ACK/NACK)
|
|
|
|
|
+- 数据字节 (MSB first) + ACK/NACK
|
|
|
|
|
+- 停止条件
|
|
|
|
|
+
|
|
|
|
|
+### MPU6050
|
|
|
|
|
+
|
|
|
|
|
+- **类型**:6轴运动跟踪 (3轴加速度 + 3轴陀螺仪)
|
|
|
|
|
+- **接口**:I2C (最大 400kHz)
|
|
|
|
|
+- **从机地址**:0x68 (7位地址)
|
|
|
|
|
+- **关键寄存器**:
|
|
|
|
|
+
|
|
|
|
|
+| 寄存器 | 地址 | 说明 |
|
|
|
|
|
+|--------|------|------|
|
|
|
|
|
+| WHO_AM_I | 0x75 | 读回 0x68 |
|
|
|
|
|
+| PWR_MGMT_1 | 0x6B | 电源管理 (写 0x00 唤醒) |
|
|
|
|
|
+| SMPLRT_DIV | 0x19 | 采样率分频 |
|
|
|
|
|
+| CONFIG | 0x1A | 配置 |
|
|
|
|
|
+| GYRO_CONFIG | 0x1B | 陀螺仪配置 |
|
|
|
|
|
+| ACCEL_CONFIG | 0x1C | 加速度计配置 |
|
|
|
|
|
+| ACCEL_XOUT_H | 0x3B | 加速度 X 高字节 |
|
|
|
|
|
+| ACCEL_XOUT_L | 0x3C | 加速度 X 低字节 |
|
|
|
|
|
+| ACCEL_YOUT_H | 0x3D | 加速度 Y 高字节 |
|
|
|
|
|
+| ACCEL_YOUT_L | 0x3E | 加速度 Y 低字节 |
|
|
|
|
|
+| ACCEL_ZOUT_H | 0x3F | 加速度 Z 高字节 |
|
|
|
|
|
+| ACCEL_ZOUT_L | 0x40 | 加速度 Z 低字节 |
|
|
|
|
|
+| GYRO_XOUT_H | 0x43 | 陀螺仪 X 高字节 |
|
|
|
|
|
+| GYRO_XOUT_L | 0x44 | 陀螺仪 X 低字节 |
|
|
|
|
|
+| GYRO_YOUT_H | 0x45 | 陀螺仪 Y 高字节 |
|
|
|
|
|
+| GYRO_YOUT_L | 0x46 | 陀螺仪 Y 低字节 |
|
|
|
|
|
+| GYRO_ZOUT_H | 0x47 | 陀螺仪 Z 高字节 |
|
|
|
|
|
+| GYRO_ZOUT_L | 0x48 | 陀螺仪 Z 低字节 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## API 表格
|
|
|
|
|
+
|
|
|
|
|
+### 软件 I2C (GPIO 模拟)
|
|
|
|
|
+
|
|
|
|
|
+| 函数 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `void I2C_Start(void)` | 起始条件 |
|
|
|
|
|
+| `void I2C_Stop(void)` | 停止条件 |
|
|
|
|
|
+| `void I2C_SendByte(uint8_t Byte)` | 发送一个字节 |
|
|
|
|
|
+| `uint8_t I2C_ReceiveByte(void)` | 接收一个字节 |
|
|
|
|
|
+| `void I2C_SendAck(uint8_t AckBit)` | 发送应答 (0=ACK, 1=NACK) |
|
|
|
|
|
+| `uint8_t I2C_ReceiveAck(void)` | 接收应答 |
|
|
|
|
|
+| `void MPU6050_Init(void)` | 初始化 MPU6050 |
|
|
|
|
|
+| `uint8_t MPU6050_GetID(void)` | 读取 WHO_AM_I |
|
|
|
|
|
+| `void MPU6050_GetData(int16_t* accX, ..., int16_t* gyroZ)` | 读取全部原始数据 |
|
|
|
|
|
+
|
|
|
|
|
+### 硬件 I2C (STM32 I2C 外设)
|
|
|
|
|
+
|
|
|
|
|
+| 函数 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `I2C_InitTypeDef` | 配置结构体 |
|
|
|
|
|
+| `void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct)` | 初始化 I2C |
|
|
|
|
|
+| `void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState)` | 使能 I2C |
|
|
|
|
|
+| `void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)` | 产生起始条件 |
|
|
|
|
|
+| `void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)` | 产生停止条件 |
|
|
|
|
|
+| `void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)` | 发送数据 |
|
|
|
|
|
+| `uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx)` | 接收数据 |
|
|
|
|
|
+| `void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)` | 配置应答 |
|
|
|
|
|
+| `ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)` | 检测事件 |
|
|
|
|
|
+| `ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT)` | 获取中断状态 |
|
|
|
|
|
+| `void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)` | 发送7位地址 |
|
|
|
|
|
+
|
|
|
|
|
+**关键事件标志**:
|
|
|
|
|
+
|
|
|
|
|
+| 事件宏 | 说明 |
|
|
|
|
|
+|--------|------|
|
|
|
|
|
+| `I2C_EVENT_MASTER_MODE_SELECT` | EV5: 起始条件已发送 |
|
|
|
|
|
+| `I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED` | EV6: 地址已发送 (主发模式) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED` | EV6: 地址已发送 (主收模式) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_BYTE_TRANSMITTING` | EV8: 数据已发送 (DR 空) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_BYTE_TRANSMITTED` | EV8_2: 数据已发送 (BTF=1) |
|
|
|
|
|
+| `I2C_EVENT_MASTER_BYTE_RECEIVED` | EV7: 数据已接收 (RXNE=1) |
|
|
|
|
|
+
|
|
|
|
|
+**I2C_InitTypeDef**:
|
|
|
|
|
+
|
|
|
|
|
+| 成员 | 可选值 | 说明 |
|
|
|
|
|
+|------|--------|------|
|
|
|
|
|
+| `I2C_Mode` | `I2C_Mode_I2C` | I2C 模式 |
|
|
|
|
|
+| `I2C_ClockSpeed` | 0~400000 | SCL 时钟频率 (Hz) |
|
|
|
|
|
+| `I2C_DutyCycle` | `I2C_DutyCycle_16_9` / `I2C_DutyCycle_2` | 快速模式占空比 |
|
|
|
|
|
+| `I2C_Ack` | `I2C_Ack_Enable` / `I2C_Ack_Disable` | 应答使能 |
|
|
|
|
|
+| `I2C_AcknowledgedAddress` | `I2C_AcknowledgedAddress_7bit` / `_10bit` | 应答地址位数 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 完整代码示例
|
|
|
|
|
+
|
|
|
|
|
+### 软件 I2C 读写 MPU6050
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+/*------------------------------------------------
|
|
|
|
|
+ * SoftI2C_MPU6050.c
|
|
|
|
|
+ * 软件 I2C (GPIO 模拟) 读写 MPU6050
|
|
|
|
|
+ * GPIO: PB0(SCL), PB1(SDA)
|
|
|
|
|
+ *------------------------------------------------*/
|
|
|
|
|
+
|
|
|
|
|
+#include "stm32f10x.h"
|
|
|
|
|
+#include <stdint.h>
|
|
|
|
|
+
|
|
|
|
|
+#define MPU6050_ADDR 0x68 // 7 位地址
|
|
|
|
|
+#define MPU6050_ADDR_W (MPU6050_ADDR << 1) // 写地址 0xD0
|
|
|
|
|
+#define MPU6050_ADDR_R ((MPU6050_ADDR << 1) | 1) // 读地址 0xD1
|
|
|
|
|
+
|
|
|
|
|
+/* 引脚定义 */
|
|
|
|
|
+#define I2C_SCL_PORT GPIOB
|
|
|
|
|
+#define I2C_SCL_PIN GPIO_Pin_0
|
|
|
|
|
+#define I2C_SDA_PORT GPIOB
|
|
|
|
|
+#define I2C_SDA_PIN GPIO_Pin_1
|
|
|
|
|
+
|
|
|
|
|
+/* 简易延时 (约 5us @72MHz) */
|
|
|
|
|
+static void delay_us(void)
|
|
|
|
|
+{
|
|
|
|
|
+ uint32_t i;
|
|
|
|
|
+ for (i = 0; i < 10; i++);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- I2C 时序函数 ---- */
|
|
|
|
|
+
|
|
|
|
|
+/* 起始条件:SCL 高时 SDA 下降沿 */
|
|
|
|
|
+void I2C_Start(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN);
|
|
|
|
|
+ GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); // SDA 下降沿
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 停止条件:SCL 高时 SDA 上升沿 */
|
|
|
|
|
+void I2C_Stop(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN);
|
|
|
|
|
+ GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); // SDA 上升沿
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 发送一个字节 (MSB first) */
|
|
|
|
|
+void I2C_SendByte(uint8_t Byte)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t i;
|
|
|
|
|
+ for (i = 0; i < 8; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ if (Byte & 0x80)
|
|
|
|
|
+ GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN);
|
|
|
|
|
+ else
|
|
|
|
|
+ GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN);
|
|
|
|
|
+ Byte <<= 1;
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ }
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 接收一个字节 (MSB first) */
|
|
|
|
|
+uint8_t I2C_ReceiveByte(void)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t i, Byte = 0;
|
|
|
|
|
+ GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); // 释放 SDA
|
|
|
|
|
+ for (i = 0; i < 8; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ Byte <<= 1;
|
|
|
|
|
+ if (GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN))
|
|
|
|
|
+ Byte |= 0x01;
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ }
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ return Byte;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 发送应答:0=ACK, 1=NACK */
|
|
|
|
|
+void I2C_SendAck(uint8_t AckBit)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ if (AckBit == 0)
|
|
|
|
|
+ GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); // ACK
|
|
|
|
|
+ else
|
|
|
|
|
+ GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); // NACK
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ GPIO_SetBits(I2C_SDA_PORT, I2C_SDA_PIN); // 释放 SDA
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 接收应答:返回 0=ACK, 1=NACK */
|
|
|
|
|
+uint8_t I2C_ReceiveAck(void)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t AckBit;
|
|
|
|
|
+ GPIO_ResetBits(I2C_SDA_PORT, I2C_SDA_PIN); // 释放 SDA
|
|
|
|
|
+ GPIO_SetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ delay_us();
|
|
|
|
|
+ AckBit = GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN);
|
|
|
|
|
+ GPIO_ResetBits(I2C_SCL_PORT, I2C_SCL_PIN);
|
|
|
|
|
+ return AckBit;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- MPU6050 操作 ---- */
|
|
|
|
|
+
|
|
|
|
|
+/* 向 MPU6050 写一个字节 */
|
|
|
|
|
+void MPU6050_WriteByte(uint8_t RegAddr, uint8_t Data)
|
|
|
|
|
+{
|
|
|
|
|
+ I2C_Start();
|
|
|
|
|
+ I2C_SendByte(MPU6050_ADDR_W);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_SendByte(RegAddr);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_SendByte(Data);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 从 MPU6050 读一个字节 */
|
|
|
|
|
+uint8_t MPU6050_ReadByte(uint8_t RegAddr)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t Data;
|
|
|
|
|
+ I2C_Start();
|
|
|
|
|
+ I2C_SendByte(MPU6050_ADDR_W);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_SendByte(RegAddr);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_Start(); // 重复起始
|
|
|
|
|
+ I2C_SendByte(MPU6050_ADDR_R);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ Data = I2C_ReceiveByte();
|
|
|
|
|
+ I2C_SendAck(1); // NACK
|
|
|
|
|
+ I2C_Stop();
|
|
|
|
|
+ return Data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 读取多个连续字节 */
|
|
|
|
|
+void MPU6050_ReadMultiBytes(uint8_t RegAddr, uint8_t* pData, uint8_t Len)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t i;
|
|
|
|
|
+ I2C_Start();
|
|
|
|
|
+ I2C_SendByte(MPU6050_ADDR_W);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_SendByte(RegAddr);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ I2C_Start();
|
|
|
|
|
+ I2C_SendByte(MPU6050_ADDR_R);
|
|
|
|
|
+ I2C_ReceiveAck();
|
|
|
|
|
+ for (i = 0; i < Len; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ pData[i] = I2C_ReceiveByte();
|
|
|
|
|
+ if (i < Len - 1)
|
|
|
|
|
+ I2C_SendAck(0); // ACK
|
|
|
|
|
+ else
|
|
|
|
|
+ I2C_SendAck(1); // NACK (最后一个字节)
|
|
|
|
|
+ }
|
|
|
|
|
+ I2C_Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* MPU6050 初始化 */
|
|
|
|
|
+void MPU6050_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ /* 唤醒:写 0x00 到 PWR_MGMT_1 */
|
|
|
|
|
+ MPU6050_WriteByte(0x6B, 0x00);
|
|
|
|
|
+ /* 关闭 SLEEP 模式 (已包含在 0x00) */
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 读 WHO_AM_I 寄存器 */
|
|
|
|
|
+uint8_t MPU6050_GetID(void)
|
|
|
|
|
+{
|
|
|
|
|
+ return MPU6050_ReadByte(0x75);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 读取 6 轴原始数据 */
|
|
|
|
|
+void MPU6050_GetData(int16_t* accX, int16_t* accY, int16_t* accZ,
|
|
|
|
|
+ int16_t* gyroX, int16_t* gyroY, int16_t* gyroZ)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t buf[14];
|
|
|
|
|
+ MPU6050_ReadMultiBytes(0x3B, buf, 14);
|
|
|
|
|
+
|
|
|
|
|
+ *accX = ((int16_t)buf[0] << 8) | buf[1];
|
|
|
|
|
+ *accY = ((int16_t)buf[2] << 8) | buf[3];
|
|
|
|
|
+ *accZ = ((int16_t)buf[4] << 8) | buf[5];
|
|
|
|
|
+ *gyroX = ((int16_t)buf[8] << 8) | buf[9];
|
|
|
|
|
+ *gyroY = ((int16_t)buf[10] << 8) | buf[11];
|
|
|
|
|
+ *gyroZ = ((int16_t)buf[12] << 8) | buf[13];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- GPIO 初始化 ---- */
|
|
|
|
|
+void SoftI2C_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ GPIO_InitTypeDef GPIO_InitStruct;
|
|
|
|
|
+
|
|
|
|
|
+ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
|
|
|
|
|
+
|
|
|
|
|
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
|
|
|
|
|
+ GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
|
|
|
|
|
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
|
|
|
|
|
+ GPIO_Init(GPIOB, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ GPIO_SetBits(GPIOB, I2C_SCL_PIN | I2C_SDA_PIN); // 释放总线 (高电平)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- 主函数 ---- */
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ int16_t accX, accY, accZ;
|
|
|
|
|
+ int16_t gyroX, gyroY, gyroZ;
|
|
|
|
|
+ uint8_t id;
|
|
|
|
|
+
|
|
|
|
|
+ SoftI2C_Init();
|
|
|
|
|
+ MPU6050_Init();
|
|
|
|
|
+ id = MPU6050_GetID();
|
|
|
|
|
+
|
|
|
|
|
+ /* 若 id == 0x68, 表示 MPU6050 正常 */
|
|
|
|
|
+ while (1)
|
|
|
|
|
+ {
|
|
|
|
|
+ MPU6050_GetData(&accX, &accY, &accZ, &gyroX, &gyroY, &gyroZ);
|
|
|
|
|
+ /* 处理数据 ... */
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 硬件 I2C 读写 MPU6050
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+/*------------------------------------------------
|
|
|
|
|
+ * HW_I2C_MPU6050.c
|
|
|
|
|
+ * 硬件 I2C1 (PB6=SCL, PB7=SDA)
|
|
|
|
|
+ *------------------------------------------------*/
|
|
|
|
|
+
|
|
|
|
|
+#include "stm32f10x.h"
|
|
|
|
|
+
|
|
|
|
|
+#define MPU6050_ADDR 0x68
|
|
|
|
|
+
|
|
|
|
|
+static void I2C_Config(void)
|
|
|
|
|
+{
|
|
|
|
|
+ I2C_InitTypeDef I2C_InitStruct;
|
|
|
|
|
+
|
|
|
|
|
+ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
|
|
|
|
|
+ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
|
|
|
|
|
+
|
|
|
|
|
+ /* I2C1: PB6=SCL, PB7=SDA, 复用开漏输出 */
|
|
|
|
|
+ GPIO_InitTypeDef GPIO_InitStruct;
|
|
|
|
|
+ GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
|
|
|
|
|
+ GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏
|
|
|
|
|
+ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
|
|
|
|
|
+ GPIO_Init(GPIOB, &GPIO_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
|
|
|
|
|
+ I2C_InitStruct.I2C_ClockSpeed = 100000; // 100kHz 标准模式
|
|
|
|
|
+ I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
|
|
|
|
|
+ I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
|
|
|
|
|
+ I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
|
|
|
|
|
+ I2C_Init(I2C1, &I2C_InitStruct);
|
|
|
|
|
+
|
|
|
|
|
+ I2C_Cmd(I2C1, ENABLE);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void MPU6050_WriteByte(uint8_t RegAddr, uint8_t Data)
|
|
|
|
|
+{
|
|
|
|
|
+ while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
|
|
|
|
|
+
|
|
|
|
|
+ I2C_GenerateSTART(I2C1, ENABLE);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // EV5
|
|
|
|
|
+
|
|
|
|
|
+ I2C_Send7bitAddress(I2C1, MPU6050_ADDR, I2C_Direction_Transmitter);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // EV6
|
|
|
|
|
+
|
|
|
|
|
+ I2C_SendData(I2C1, RegAddr);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // EV8_2
|
|
|
|
|
+
|
|
|
|
|
+ I2C_SendData(I2C1, Data);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
|
|
|
|
|
+
|
|
|
|
|
+ I2C_GenerateSTOP(I2C1, ENABLE);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static uint8_t MPU6050_ReadByte(uint8_t RegAddr)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t Data;
|
|
|
|
|
+ while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
|
|
|
|
|
+
|
|
|
|
|
+ /* 写寄存器地址 */
|
|
|
|
|
+ I2C_GenerateSTART(I2C1, ENABLE);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
|
|
|
|
|
+
|
|
|
|
|
+ I2C_Send7bitAddress(I2C1, MPU6050_ADDR, I2C_Direction_Transmitter);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
|
|
|
|
|
+
|
|
|
|
|
+ I2C_SendData(I2C1, RegAddr);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
|
|
|
|
|
+
|
|
|
|
|
+ /* 重复起始,读数据 */
|
|
|
|
|
+ I2C_GenerateSTART(I2C1, ENABLE);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
|
|
|
|
|
+
|
|
|
|
|
+ I2C_Send7bitAddress(I2C1, MPU6050_ADDR, I2C_Direction_Receiver);
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
|
|
|
|
|
+
|
|
|
|
|
+ /* 最后一个字节前 NACK + STOP */
|
|
|
|
|
+ I2C_AcknowledgeConfig(I2C1, DISABLE);
|
|
|
|
|
+ I2C_GenerateSTOP(I2C1, ENABLE);
|
|
|
|
|
+
|
|
|
|
|
+ while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); // EV7
|
|
|
|
|
+ Data = I2C_ReceiveData(I2C1);
|
|
|
|
|
+
|
|
|
|
|
+ I2C_AcknowledgeConfig(I2C1, ENABLE);
|
|
|
|
|
+ return Data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int main(void)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t id;
|
|
|
|
|
+
|
|
|
|
|
+ I2C_Config();
|
|
|
|
|
+ /* 唤醒 MPU6050 */
|
|
|
|
|
+ MPU6050_WriteByte(0x6B, 0x00);
|
|
|
|
|
+ id = MPU6050_ReadByte(0x75);
|
|
|
|
|
+
|
|
|
|
|
+ while (1);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 常见坑点
|
|
|
|
|
+
|
|
|
|
|
+1. **应答标志位处理**
|
|
|
|
|
+ - 主接收最后一个字节**必须发 NACK**(非应答),否则从机继续发数据导致总线异常
|
|
|
|
|
+ - 硬件 I2C 下:最后一个字节接收前关 ACK (I2C_AcknowledgeConfig(DISABLE)),同时发 STOP
|
|
|
|
|
+
|
|
|
|
|
+2. **超时处理**
|
|
|
|
|
+ - While 轮询事件标志时务必加**超时**,否则从机无响应会死循环
|
|
|
|
|
+ - 建议实现 `while(!event && timeout--)` 模式
|
|
|
|
|
+
|
|
|
|
|
+3. **I2C 时钟不要超过 400kHz**
|
|
|
|
|
+ - 标准模式 100kHz,快速模式 400kHz
|
|
|
|
|
+ - 超频可能导致通信不稳定
|
|
|
|
|
+
|
|
|
|
|
+4. **软件 I2C 的 GPIO 模式**
|
|
|
|
|
+ - SDA 和 SCL 必须用**开漏输出 (GPIO_Mode_Out_OD)** + 外部上拉电阻 (4.7kΩ)
|
|
|
|
|
+ - 开漏才能实现"线与",多主机时不会短路
|
|
|
|
|
+
|
|
|
|
|
+5. **硬件 I2C 的 GPIO 模式**
|
|
|
|
|
+ - 使用**复用开漏 (GPIO_Mode_AF_OD)**,不是普通推挽
|
|
|
|
|
+ - 时钟使能注意:I2C1 挂 APB1,GPIOB 挂 APB2
|
|
|
|
|
+
|
|
|
|
|
+6. **重复起始条件**
|
|
|
|
|
+ - 读操作时必须发重复起始 (Restart),不可先停再启
|
|
|
|
|
+ - 软件 I2C 只需再调一次 I2C_Start()
|
|
|
|
|
+ - 硬件 I2C 重复调 I2C_GenerateSTART 即可
|
|
|
|
|
+
|
|
|
|
|
+7. **MPU6050 唤醒**
|
|
|
|
|
+ - 上电后 MPU6050 默认 SLEEP 状态,必须写 PWR_MGMT_1 (0x6B) = 0x00 唤醒
|
|
|
|
|
+
|
|
|
|
|
+8. **地址左移**
|
|
|
|
|
+ - STM32 库函数 I2C_Send7bitAddress 要求**7位地址**(0x68),库内部左移加 R/W
|
|
|
|
|
+ - 软件模拟时必须手动左移:0x68 << 1 = 0xD0 (写) / 0xD1 (读)
|