STM32学习笔记14-I2C硬件控制

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


  1. 开启I2C外设和对应GPIO口的时钟
  2. 把I2C外设对应的GPIO口初始化为复用开漏模式
  3. 使用结构体,对整个I2C进行配置
  4. 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);

cpp 复制代码
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); //同理,等待对应的事件EV8
	CheckEvent(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_2
	while(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); 
	
	//读取DR
	Data=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;  //时钟频率,要低于400KHz
	I2C_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);	//采样分频为10
	MPU_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;						//数据拼接,通过输出参数返回

	
}
相关推荐
ShiMetaPi41 分钟前
【GM3568JHF】FPGA+ARM异构开发板烧录指南
stm32·单片机·嵌入式硬件
前路不黑暗@1 小时前
C语言:操作符详解(二)
c语言·开发语言·经验分享·笔记·学习·学习方法·visual studio
蜡笔小电芯2 小时前
【STM32】STM32H750 CubeMX 配置 USB CDC 虚拟串口笔记
笔记·stm32·嵌入式硬件
xiaoxiaoxiaolll2 小时前
金刚石基植入体新突破!Adv. Funct. Mater. 报道首例增材制造固态摩擦电能量收集器
学习
x.Jessica2 小时前
网络的构成元素
网络·学习·计算机网络
快乐zbc2 小时前
数学建模Topsis法笔记
笔记·数学建模
悠哉悠哉愿意2 小时前
【Python语法基础学习笔记】if语句
笔记·python·学习
杜子不疼.3 小时前
《Python学习之第三方库:开启无限可能》
开发语言·python·学习
岑梓铭4 小时前
考研408《计算机组成原理》复习笔记,第五章(1)——CPU功能和结构
笔记·考研·408·计算机组成原理·计组