一、I2C外设简介

1、STM32内部集成了I2C收发电路,可以硬件自动执行时钟生成、起始终止条件生成、应答位收发、收据收发等功能,减轻CPU负担。

2、支持多主机模型;

3、支持7位/10位地址模式;

4、支持不同的通讯速度,标准速度(100KHz),快速400KHz;

5、支持DMA;

二、硬件引脚与复用

1、定义表

外设默认引脚重映射引脚总线
I2C1

PB6-SCL

PB7-SDA

PB8-SCL

PB9-SDA

APB1
I2C2

PB10-SCL

PB11-SDA

无重映射APB1

注:GPIO硬性要求:必须分配为GPIO_Mode_AF_OD,复用开漏输出,不能用推挽。

I2C总线是线与结构,需要外部4.7k--10kΩ上拉电阻,芯片内部上拉能力不足,不加上拉不稳定。空闲时,SDA、SCL被拉到高电平。

2、关键寄存器

1)I2C_CR1控制寄存器1

        PE(bit0):I2C外设使能位,1开启;

        START(bit8):写1,硬件产生起始信号;

        STOP(bit9):写1,硬件产生停止信号;

        ACK(bit10):应答机制,1:接收时发送ACK,0:接收时发送NACK;

2)I2C_CR2控制寄存器2

        FREQ[5:0]:APB1时钟频率,单位MHz;

3)I2C_OAR1/OAR2 自身从机地址寄存器

        主机模式基本不用,做从机时填写7bit/10bit地址。

4)I2C_DR 数据寄存器

         写DR:发送数据;读DR:接受数据。硬件移位寄存器自动完成位收发。DR读写会自动清除部分状态标志。

5)I2C_CCR时钟控制寄存器,计算SCL频率

        标准频率:100KHz,快速模式:400KHz。

        fscl=fapb1/2*CCR。PCLK1就是APB1的时钟(36MHz)。

100KHz标准模式:CCR=180,36M/(2*180) =100K 。

6)I2C_SR1状态寄存器1(事件+错误标志,只读)

标志位含义清除条件
SB(bit0)EV5:起始信号也发出读SR1,再写DR
ADDR(bit1)EV6:地址匹配应答成功读SR1,再读SR2
BTF(bit2)EV8_2:字节传输完成,移位寄存器+DR全部空读写DR/读SR1+SR2
RxNE(bit6)EV7:接收寄存器非空,收到数据读DR
TXE(bit7)EV8:发送寄存器空,可以写新数据写DR
AE(bit10)应答识别,从机没有ACK软件写0清零

7)I2C_SR2状态寄存器2(只读,必须配合SR1读取)

        MSL(bit0):1:主机模式;0:从机;

        BUSY(bit1):总线通信中,0表示总线空闲;

        TRA(bit2):1:发送模式,0:接收模式;

三、标准库

I2C初始化结构体详解

uint32_t I2C_ClockSpeed:SCL频率,0~400KHz。

uint16_t I2C_Mode:I2C_Mode_I2C 或者I2C_Mode_SMBus。

uint16_t I2C_DutyCycle:I2C_DutyCycle_2(Duty=0标准模式)或I2C_DutyCycle_16_9(Duty =1快速模式)。

DutyCycle =SCL时钟高电平时间:低电平时间

I2C_DutyCycle_2是高:低=1:2;标准模式下,无视这个参数,高:低=1:1

I2C_DutyCycle_16_9是:高:低=9:16。

uint16_t I2C_OwnAddress1:本机从地址,主机模式填0。

uint16_t I2C_Ack:I2C_Ack_Enable或I2C_Ack_Disable,接收是否发送ACK。

uint16_t I2C_AcknowledgedAddress:地址模式,I2C_AcknowledgedAddress_7bit或I2C_AcknowledgedAddress_10bit,一般选7位。

库函数I2C_Init()内部自动设置CR2.FREQ,CCR、TRISE,不需要手动写寄存器。

四、事件EV编号

标准库定义、轮询核心。

事件宏说明

EV5:

I2C_EVENT_MASTER_MODE_SELECT

起始条件发送完成,SB置1

EV6:

I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 

地址发送完成,从机应答,主机发送模式

EV6:

I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED

地址发送完成,主机接收模式

EV7:

I2C_EVENT_MASTER_BYTE_RECEIVED

RxNE=1,DR收到1字节数据,可以读DR

EV8:

I2C_EVENT_MASTER_BYTE_TRANSMITTING

TXE=1,DR空,可以写下一个字节

EV8_2:

I2C_EVENT_MASTER_BYTE_TRANSMITTED

BTF+TXE都置1,移位+DR全部空,适合发STOP

注:EV8只代表DR寄存器空,移位寄存器还在往外发字节,EV8_2才是整字节全部移位输出完毕,发STOP必须等EV8_2,否则停止信号错乱。

五、代码部分

1、写时序:Start→从机地址W→ACK→寄存器地址→ACK→数据1→ACK→数据2→ACK→STOP;

注:

1)发送完最后一个字节,不能看TXE(EV8)就发送STOP,必须等BTF=1(EV8_2),BTF:数据字节传输完。

2)TXE=1只代表数据寄存器DR空,BTF=1代表DR和移位寄存器全部空,字节真正全部搬运到总线上发完

3)所以while必须加超时,防止总线卡死程序。

4)GPIO必须复用开漏模式。

参数说明:RegAddr寄存器地址,Data:要写入的值。

#define MPU6050_ADDRESS	0xD0
//超时处理函数
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint16_t Timeout =10000;
	while(I2C_CheckEvent(I2C2,I2C_EVENT)!=SUCCESS)
	{
		Timeout--;
		if(0 ==Timeout)
		{
            //超时处理代码,比如报警等
			break;
		}	
	}
}
//单字节写
void MPU6050_WriteReq(uint8_t RegAddr,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,RegAddr);//写数据到DR
	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);
}

多字节写:连续写一串数据,比如连续写多个寄存器。中间字节:只需要等待EV8(TXE=1),就可以填充下一个DR;只有最后一个字节必须等待EV8_2(BTF=1)再发STOP。

2、读时序

写传感器标准流程:写寄存器地址→重复起始→读数据。时序如下:

Start→从机地址→ACK→寄存器地址W→ACK→RepeatStart→从机地址R→ACK→接收N个字节→NACK→STOP。

核心铁律:

1)最后一个字节之前,提前关闭ACK,不能读完数据再关。读完数据之后才关ACK,硬件I2C的第9位应答是提前预配置,硬件在SCL第9个脉冲输出ACK/NACK,等读完DR再改寄存器,第9个脉冲已经发送完毕。

2)多字节:倒数第2字节收到后关闭ACK。

3)单字节:EV6(接收模式选定)之后立刻关闭ACK。

4)每一步必须等待对应事件,不能直接跑代码。

5)所有循环,工程代码必须加延时,防止卡死。

6)如果只能读一次,第2次直接卡死,忘记恢复I2C_AcknowledgeConfig(I2C2,ENABLE);

7)读出的数据全是0或者0xFF,NACK时机错误。

8)和软件I2C本质区别:

软件I2C接收:收到8bit数据后,CPU再决定输出ACK/NACK。

硬件I2C接收:第9位应答的配置,必须在这一字节的SCL第9个时钟到来之前提前设置完毕。

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);
	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);//函数自动将最低位置1,不用写|0x01
	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);//在接收最后一个字节前,要提前设置非应答ACK和STOP
	Data = I2C_ReceiveData(I2C2);//
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);	//恢复应答
	return Data;
}

完整工程:硬件I2C读取MPU6050参数

MPU6050.c文件

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

//硬件I2C是非阻塞的,软件I2C包含延时函数,是阻塞式的

#define MPU6050_Address 0xD0

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout =10000;
	while(I2C_CheckEvent(I2C2,I2C_EVENT) == !SUCCESS)//监测EV5事件是否发生
	{
		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);
	I2C_SendData(I2C2,Data);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	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);
	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);//函数自动将最低位置1,不用写|0x01
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6
	//开始接收数据
	I2C_AcknowledgeConfig(I2C2,DISABLE);//这4行,用for循环可以实现读取多个字节
	I2C_GenerateSTOP(I2C2,ENABLE);//
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//在接收最后一个字节前,要提前设置非应答ACK和STOP
	Data = I2C_ReceiveData(I2C2);//
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);	//恢复应答
	return Data;
}

void MPU6050_Init()
{
	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_Pin=GPIO_Pin_10|GPIO_Pin_11 ;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz ;
	GPIO_Init(GPIOB,& GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode =I2C_Mode_I2C;
	I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//STM32作为从机时参数
	I2C_InitStructure.I2C_ClockSpeed =50000;//50kHz
	I2C_InitStructure.I2C_DutyCycle =I2C_DutyCycle_2;//>100kHz时才有用	
	I2C_InitStructure.I2C_OwnAddress1 =0x00;//STM32作为从机时参数
	I2C_Init(I2C2,&I2C_InitStructure);
	
	I2C_Cmd(I2C2,ENABLE);
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//解除睡眠模式及时钟选择
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//XYZ轴不睡眠
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//10分频
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);//滤波参数
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪最大量程
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计最大量程
		
}

MPU6050.h文件

#ifndef __MPU6050_H
#define __MPU6050_H

uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);
void MPU6050_Init();
void MPU6050_GegData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
	              int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ);
uint8_t	MPU6050_GetID();

main函数

#include "stm32f10x.h"                  
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY ,AZ,GX,GY,GZ;
int8_t ID;
int main()
{
	OLED_Init();
	MPU6050_Init();
	OLED_ShowString(1,1,"ID:");
	ID =MPU6050_GetID();
	OLED_ShowHexNum(1,4,ID,2);
	while(1)
	{
		MPU6050_GegData(&AX,&AY,&AZ,&GX,&GY,&GZ);
		OLED_ShowSignedNum(2,1,AX,5);
		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);	
        delay_ms(100);		
	}
}

本文完!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐