0%

10-3I2C通信外设&硬件I2C读写MPU6050

I2C外设简介

•STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
•支持多主机模型
•支持7位(最常见,7位地址+读写位)/10位地址模式(为了挂载128个以上的设备)
•支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
•支持DMA(读写多字节时,使用DMA自动转运数据)
•兼容SMBus协议(I2C改进的协议)

•STM32F103C8T6 硬件I2C资源:I2C1、I2C2

I2C框图

I2C框图
左侧的SDA和SCL是I2C通信引脚,下方的SMBALERT是smbus使用的,I2C用不到。

上方是SDA,数据控制部分。
数据收发的核心,是数据寄存器(DATA REGISTER)和数据移位寄存器,需要发送数据时,把这个数据写道数据寄存器DR,当移位寄存器没有数据移位时,这个数据寄存器的值就会转到移位寄存器里,在移位过程中,可以把下一个数据放到数据寄存器DR中等待,前一个数据移位完成,下一个数据直接到移位寄存器。
当数据由数据寄存器转移到数据移位寄存器时,置状态寄存器的TXE位为1,表示发送寄存器为空。
接收数据时,数据一位一位地从引脚到移位寄存器中,一个字节的数据收集好之后,数据整体从移位寄存器转移到数据寄存器,置标志位RXNE,表示接收寄存器非空,此时可以从数据寄存器读出数据。

下方为SCL,时钟控制部分。

I2C基本结构

I2C基本结构
移位寄存器和数据寄存器DR配合,是通信的核心。
由于I2C是高位先行,移位寄存器是向左移位,从高位到低位移位。一个SCL时钟移位一次,八次把一个字节传输完成。
接收时,数据从GPIO口移进来,移位8次,一个字节就接收完成了。
使用硬件I2C时,对应的2个GPIO口配置为复用开漏输出模式

主机发送

主机发送

主机接收

主机接收

常用函数

1
2
void I2C_DeInit(I2C_TypeDef* I2Cx);

I2C恢复缺省配置

1
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);

I2C初始化

1
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);

I2C结构体初始化

1
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C使能

1
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C生成起始条件,调用此函数生成起始条件。

1
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C生成终止条件。

1
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

配置在收到一个字节之后,是否给从机应答

1
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);

I2C发送数据,写入数据到数据寄存器DR。

1
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

读取DR的数据作为返回值,接收数据

1
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);

发送7位地址

硬件I2C读写MPU6050

MPU6050.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "stm32f10x.h"                  // Device header
#include "MPU6050_REG.h"

#define MPU6050_ADDRESS 0xD0

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;//超时退出while,避免死循环
Timeout = 10000;
while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)//检测事件是否发送
{
Timeout--;
if(Timeout == 0)//超时退出
{
break;
}
}
}


void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//指定地址写
{

I2C_GenerateSTART(I2C2,ENABLE);//生成起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发送
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送从机地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件是否发送

I2C_SendData(I2C2,RegAddress);//发送寄存器地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//监测EV8事件是否发送

I2C_SendData(I2C2,Data);//发送数据
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件是否发送

I2C_GenerateSTOP(I2C2,ENABLE);//产生停止条件
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;

I2C_GenerateSTART(I2C2,ENABLE);//生成起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发送

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送从机地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件是否发送

I2C_SendData(I2C2,RegAddress);//发送寄存器地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8事件是否发送

I2C_GenerateSTART(I2C2,ENABLE);//生成重复的起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发送

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);//发送从机地址,读写位为读
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//监测EV6事件是否发送

I2C_AcknowledgeConfig(I2C2,DISABLE);//只需要读取一个字节,在EV6事件之后,立刻把ACK位置0
I2C_GenerateSTOP(I2C2,ENABLE);//申请产生终止条件

MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//监测EV7事件是否发送

Data = I2C_ReceiveData(I2C2);

I2C_AcknowledgeConfig(I2C2,ENABLE);//把ACK置1,默认状态下ACK应该为1

return Data;
}

void MPU6050_Init(void)
{
//MyI2C_Init();

//硬件I2C初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);//开启I2C2的时钟,I2C1和I2C2都是APB1的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB的时钟
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//此处必须设置为复用开漏模式,开漏是I2C协议的设计要求,复用是把GPIO的控制权交给硬件外设。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
//I2C初始化
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_16_9;//占空比,16_9就是低电平和高电平时间是16:9的关系
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//应答位配置
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//STM32作为从机,可以相应几位地址,这里配置成7位
I2C_InitStructure.I2C_OwnAddress1 = 0x00;//STM32作为从机时使用,自身地址
I2C_Init(I2C2,&I2C_InitStructure);

I2C_Cmd(I2C2,ENABLE);//I2C2使能

MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
MPU6050_WriteReg(MPU6050_CONFIG,0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH,DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;

DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}