stm32学习笔记:I2C通信外设原理+实验

 软件实现和硬件实现

串口通信为异步时序,用软件实现很麻烦,基本上用硬件实现
而I2C协议通信为同步时序,软件实现简单且灵活,硬件实现比较麻烦,故软件比较常用
但I2C硬件实现功能比较大,执行效率高,节省软件资源,可以实现完整的多主机通信模型,时序波形归整,通信速率快
故I2C软件实现用于简单环境,若性能要求高,则硬件实现

 

I2C外设简介  

1.(软件只需要写入控制寄存器CR和数据寄存器DR,就可以实现协议,为了实现实时监控时序的状态,软件要读取状态寄存器SR

2.支持多主机模型(固定多主机和可变多主机(stm32,谁要做主机,主机就得跳出来)) 

3.本实验依旧是7位、一个主机(一主多从)

 4.STM32F103C8T6 硬件 I2C 资源: I2C1 、 I2C2(两个独立I2C,硬件只有俩路I2C总线,而软件I2C只要代码存的下,想开几路就开几路) 

I2C的功能图

(1)数据控制部分

1 数据收发的核心部分:数据寄存器和数据移位寄存器 
2 发送数据时,将数据放在数据寄存器,当没有移位时候,数据从数据寄存器转运到移位寄存器,同时,下一个数据送到数据寄存器,然后移位寄存器将数据给SDA,数据寄存器中的数据给移位寄存器,如此往复
3 当数据寄存器转到移位寄存器时,就会置状态寄存器的TXE位为1,表示发送寄存器为空

接收数据

  •  接收数据时,从SDA转到移位寄存器,再转到数据寄存器,同时置标志位RXNE,表示接受寄存器非空,这时候可以把数据从数据寄存器读出来
  • 比较器和地址寄存器时从机模式使用(即再stm32不进行通信的时候,这个stm32支持同时响应两个从机地址
SCL部分 

时钟控制:控制SCL线
时钟控制寄存器(CCR):写对应的位,电路就会执行对应的功能
控制逻辑电路:写入控制寄存器(CR1/CR2),就可以对整个电路进行控制
读取状态寄存器,可以得知电路的工作状态
中断:当内部有一些标志位置1后,可能事件比较紧急,就可以申请中断
如果开启中断,当事件发生后,程序可以跳转到中断函数处理事件

I2C基本结构 

发送数据(从数据控制器到SDA):因为I2C是高位先行,所以移位寄存器是向左移位,在发送和的时候,最高位先移出去,然后是次高位,一次SCL时钟移位一次,移动8次,就把一个字节由高位到低位,一次放到SDA线上
接收数据:数据从SDA经GPIO口,从右边一次移进来,最终移动8次,一个字节就接收完成
使用硬件I2C:两个GPIO口,都要配置成复用开漏输出模式
复用就是GPIO的状态由片上外设来控制
开漏输出:时I2C协议要求的端口配置(GPIO口依旧可以输入)

 主机发送

7位主发送的过程

TxE=1表示数据寄存器空,BTF字节发送结束标志位
SB=1,表示起始条件已经发送
(1)初始化之后:总线默认空闲状态,stm32默认是从模式
(2)stm32需要写入控制寄存器产生起始条件:
控制寄存器CR1中,有个START位,写1,可以产生起始条件,起始条件发生后,这一位由硬件清除,不需要手动侵清除
之后stm32从从模式转换成主模式
(3)检查标志位
EV5事件:SB(start bit)=1,表示起始条件已经发送
(4)发送从机地址:需要写到数据寄存器DR中,写入后,硬件自动把这个字节转到移位寄存器中,再把这个字节发送到IIC总线上
之后硬件会自动接收应答并判断,若没有应答,硬件会置应答失败的标志位,标志位可以申请中断来提醒我们
(5)寻址完成之后,会发生EV6事件,ADDR=1,代表在主模式下发送结束
(6)EV8_1事件:TxE=1,移位寄存器和数据寄存器为空,写入数据寄存器DR进行数据发送,一旦写入DR,因为
移位寄存器也是空的,所以DR会立刻转到移位寄存器进行发送
(7)EV8事件:TxE=1,移位寄存器非空,数据寄存器空,这移位寄存器正在发数据的状态,故数据1的时序产生
一旦检测到EV8事件,就可以写入下一个数据
(8)EV8_2事件:写完后,没有数据可以写了

 主机接收

这里是当前地址读的模式

(1)首先写入start位,产生起始条件,等EV5事件,EV5事件代表起始条件已经发送,

(2)之后是寻址,接收应答,结束后产生EV6事件,代表寻址已经完成

(3)数据1这块,代表数据正在通过移位寄存器进行输入,EV6-1事件,从上图可以看出,数据正在移位,还没收到,所以事件没有标志位,当这个时序单元完成时,硬件会自动根据我们的配置,把应答位发送出去(ACK应答使能,写1,在接收到一个字节后就返回一个应答,写0不给应答),当时序单元完成后,表示移位寄存器已经成功移入一个字节的数据1,这时,移入的一个字节就整体转移到数据寄存器中,同时置RxNE标志位,表示数据寄存器非空,也就是收到一个字节的数据,这个状态就是EV7事件、

(4)当把数据读走后,EV7事件就没有了

(5)EV7_1:结束

(6)由于设置了ACK=0,所以会给出非应答,最后由于设置STOP位,所以产生终止条件

 软件和硬件之间的比较

 (1)硬件IIC的波形比较规整,软件IIC由于添加了延时,时钟周期、占空比可能不规整

SCL低电平写,高电平读(默认下降沿写,上升沿读,硬件IIC数据写入,都是紧贴着下降沿,SCL下降沿,SDA立马切换数据)

(2)在硬件中,应答结束后,从机立刻释放SDA,同时主机立刻拉低SDA,故出现尖锋

SDA接在B11,SCL接在B10 ,软件IIC的两个引脚可以任意更改的,因为都是开漏输出,硬件接在哪个引脚上,程序中就对应操作哪个引脚

但是硬件IIC,通信引脚是不可以任意指定的,查表,由于PB6、PB7被OLED应用,所以用PB10、PB11,软件IIC的代码在此处也适用

程序部分

软件IIC改为硬件IIC

第一步:开启I2C外设,对I2C2外设进行初始化,以替换MyI2C_Init
第二步:控制外设电路,实现指定地址写的时序,以替换WriteReg
第三步:控制外设电路,实现指定地址读的时序,以替换ReadReg

配置IIC外设初始化配置

第一步:开启IIC外设和对应GPIO口的时钟
第二步:把IIC外设和对应的GPIO口初始化为复用开漏模式
第三步:使用结构体,对整个IIC进行配置
第四步:I2C_Cmd,使能I2C

MPU6050.c代码

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS		0xD0		//MPU6050的I2C从机地址

/**
  * 函    数:MPU6050等待事件
  * 参    数:同I2C_CheckEvent
  * 返 回 值:无
  */
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;									//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//循环等待指定事件
	{
		Timeout --;										//等待时,计数值自减
		if (Timeout == 0)								//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;										//跳出等待,不等了
		}
	}
}

/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8
	
	I2C_SendData(I2C2, Data);												//硬件I2C发送数据
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTOP(I2C2, ENABLE);											//硬件I2C生成终止条件
}
/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成重复起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);		//硬件I2C发送从机地址,方向为接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);		//等待EV6
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);									//在接收最后一个字节之前提前将应答失能
	I2C_GenerateSTOP(I2C2, ENABLE);											//在接收最后一个字节之前提前申请停止条件
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);				//等待EV7
	Data = I2C_ReceiveData(I2C2);											//接收数据寄存器
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);									//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
	
	return Data;
}

/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);		//开启I2C2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为复用开漏输出
	/*I2C初始化*/
	I2C_InitTypeDef I2C_InitStructure;						//定义结构体变量
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;				//模式,选择为I2C模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;				//时钟速度,选择为50KHz
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;		//时钟占空比,选择Tlow/Thigh = 2
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;				//应答,选择使能
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//应答地址,选择7位,从机模式下才有效
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;				//自身地址,从机模式下才有效
	I2C_Init(I2C2, &I2C_InitStructure);						//将结构体变量交给I2C_Init,配置I2C2
	
	/*I2C使能*/
	I2C_Cmd(I2C2, ENABLE);									//使能I2C2,开始运行
	
	/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);				//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);				//电源管理寄存器2,保持默认值0,所有轴均不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);				//采样率分频寄存器,配置采样率
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);					//配置寄存器,配置DLPF
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);			//陀螺仪配置寄存器,选择满量程为±2000°/s
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);			//加速度计配置寄存器,选择满量程为±16g
}


/**
  * 函    数:MPU6050获取ID号
  * 参    数:无
  * 返 回 值:MPU6050的ID号
  */
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}

/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
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;								//定义数据高8位和低8位的变量
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

 main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;								//定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	MPU6050_Init();		//MPU6050初始化
	
	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:");		//显示静态字符串
	ID = MPU6050_GetID();				//获取MPU6050的ID号
	OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号
	
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);		//获取MPU6050的数据
		OLED_ShowSignedNum(2, 1, AX, 5);					//OLED显示数据
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

MPU6050.h代码 

#ifndef __MPU6050_H
#define __MPU6050_H

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);

#endif

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值