I2C外设简介
- STM32内部集成了硬件I2C收发电路(硬件收发器:自动生产波形,自动翻转电平等),可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担——软件只需要写入控制寄存器CR和DR,还有实时监控时序状态的状态寄存器SR
- 支持多主机模型
- 支持7位/10位地址模式
- 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
- 支持DMA
- 兼容SMBus协议
- STM32F103C8T6 硬件I2C资源:I2C1、I2C2
补:多主机模型:固定多主机(有固定的主机数和固定的从机数)和可变多主机(任何设备,都可以从空闲的状态跳出来作为主机,然后指定通信,之后又跳回来),采用10位地址的时序:起始条件后的两个字节都是寻址,其中前一个字节,是帧头:内容是5位的标志位11110+2位地址+1位读写位,后一个字节:纯粹的8位地址。
I2C框图
引脚对应关系
I2C基本结构(一主多从)
硬件I2C的操作流程:
主机发送
主机接收
软件/硬件波形对比
手册——对应24章
接线图
10-2 硬件I2C读写MPU6050
- 开启I2C外设和对应GPIO口的时钟
- 把I2C外设对应的GPIO口初始化为复用开漏模式
- 使用结构体,对整个I2C进行配置
- I2C_Cmd,使能I2C
相关函数:
void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState); //生产起始条件
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState); //生产结束条件
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState); //配置应答ACK
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data); //数据写入DR寄存器
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx); //读取DR的数值
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction); //发送7位地址的专用函数
//EV——多种监控
I2C_CheckEvent() //基本
I2C_GetLastEvent() //高级
I2C_GetFlagStatus() //基于状态位的标准监控
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
MPU6050.c//27硬件I2C读写MPU6050
//与软件的区别就是MyI2C.c这个文件,硬件是不需要的
//意思是:底层的逻辑会有不同,其他是一样的
#include "stm32f10x.h" // Device header#define MPU_ADD 0xD0
#include "MPU_Reg.h"
//封装指定地址写和指定地址读的时序
//优化:在代码中,存在很多死循环的地方——超时退出void CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT){uint32_t TimeOut;TimeOut=100; while(I2C_CheckEvent(I2Cx, I2C_EVENT)!=SUCCESS){TimeOut--;if(TimeOut==0){break;//错误处理}}
}void MPU_WriteReg(uint8_t RegAddress,uint8_t Data){//用此函数,则会一直传输数据,所以我们需要用标志位去确定它是否操作成功了,这里就要用EV5事件来确定I2C_GenerateSTART(I2C2, ENABLE);//while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);//封装
// while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){
// TimeOut--;
// if(TimeOut==0){
// return;
// }
// }CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//发送函数会自带应答位,所以我们不需要考虑应答,只需要考虑发送后的事件EV6即可I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Transmitter); CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV8_1的事件是提醒应该写入DR数据,不需要等待I2C_SendData(I2C2, RegAddress); //同理,等待对应的事件EV8CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); I2C_SendData(I2C2, Data);//当发送为最后一个数据时,就需要等待EV8_2事件CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTOP(I2C2, ENABLE); }uint8_t MPU_ReadingReg(uint8_t RegAddress){uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE);CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //发送函数会自带应答位,所以我们不需要考虑应答,只需要考虑发送后的事件EV6即可I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Transmitter); CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV8_1的事件是提醒应该写入DR数据,不需要等待I2C_SendData(I2C2, RegAddress); //在最后一个数据,用EV8_2while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS); I2C_GenerateSTART(I2C2, ENABLE);while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS); //主机接收I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Receiver); while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS); //接收从机的数据:规定:在接收数据之前,需要把ACK置0,同时设置停止位STOP//如果读取多个字节,那直接等待EV7事件,读取DR,就能收到数据,在接收最后一个字节之前EV7_1事件,需要把ACK置0,同时设置停止位STOP//如果读取一个字节,那在EV6事件之后,需要把ACK置0,同时设置停止位STOP,在等待EV7事件,不然会多一个字节I2C_AcknowledgeConfig(I2C2, DISABLE);I2C_GenerateSTOP(I2C2,ENABLE);while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS); //读取DRData=I2C_ReceiveData(I2C2); I2C_AcknowledgeConfig(I2C2, ENABLE); //应答值设为1,给从机应答,这样可以使指定地址收多个字节return Data;}void MPU6050_Init(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;GPIO_Init(GPIOB, &GPIO_InitStructure);I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode =I2C_Mode_I2C; //I2C的模式I2C_InitStructure.I2C_ClockSpeed=50000; //时钟频率,要低于400KHzI2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//时钟占空比,只有在时钟频率大于100KHz才有用,小于则固定的1:1;——能更快的传输I2C_InitStructure.I2C_Ack=I2C_Ack_Enable; //应答配置I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit; //STM做从机,可以被响应几位地址I2C_InitStructure.I2C_OwnAddress1=0x00; //自身寄存器,当STM32做从机时,指定STM32的自身地址,方便主机呼叫I2C_Init(I2C2,&I2C_InitStructure);I2C_Cmd(I2C2,ENABLE);MPU_WriteReg(MPU6050_PWR_MGMT_1,0x01); //解除睡眠,选择陀螺仪时钟MPU_WriteReg(MPU6050_PWR_MGMT_2,0x00); //6个轴均不待机MPU_WriteReg(MPU6050_SMPLRT_DIV,0x09); //采样分频为10MPU_WriteReg(MPU6050_CONFIG,0x06); //滤波参数最大MPU_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪和加速度选择最大MPU_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
// //此时的MPU就在进行大量的数据转换,数据存放在其他的寄存器里
}void MPU_Getdata(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{//读取加速度寄存器XYZ轴的高8位和低8位uint8_t DataH, DataL; //定义数据高8位和低8位的变量DataH = MPU_ReadingReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据DataL = MPU_ReadingReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU_ReadingReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据DataL = MPU_ReadingReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU_ReadingReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据DataL = MPU_ReadingReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据*AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU_ReadingReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据DataL = MPU_ReadingReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据*GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU_ReadingReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据DataL = MPU_ReadingReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据*GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU_ReadingReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据DataL = MPU_ReadingReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据*GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回}