设备:主设备 (Master) 发起通信,从设备 (Slave) 响应地址
+---+
SCL -----+ +---+
| |
SDA -----+ +---------
| |
Master Slave
起始条件 字节传输(MSB first) 应答 停止条件
_____ _____ _____ _____ _____ _____
SCL | |_____| |_| |_| |____| |__| |
_ ___ ___ ___ ___ ___ ___
SDA X_| 7位地址 |_| R/W |_| 数据MSB |_| ... |_| 应答 |_X_|
|_| |_| |_| |_| |_| |_| |_|
时序规则:
数据帧格式:
| 寄存器 | 地址 | 说明 |
|---|---|---|
| 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 低字节 |
| 函数 | 说明 |
|---|---|
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_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 |
应答地址位数 |
/*------------------------------------------------
* 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);
/* 处理数据 ... */
}
}
/*------------------------------------------------
* 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);
}
应答标志位处理
超时处理
while(!event && timeout--) 模式I2C 时钟不要超过 400kHz
软件 I2C 的 GPIO 模式
硬件 I2C 的 GPIO 模式
重复起始条件
MPU6050 唤醒
地址左移