【STM32】I2C接口(一主多从)

本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发

目录

前言

I2C外设简介

IO口初始化

I2C接口配置

I2C时钟配置

I2C初始化

I2C接口使能

I2C外设配置框架

主机的发送与接收

主机发送

主机接收

I2C例程


前言

I2C协议介绍:【通信协议】I2C总线(一主多从)-CSDN博客

本篇博客学习使用STM32的I2C硬件收发电路生成I2C时序。

I2C外设简介

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

  • 支持多主机功能
  • 支持7位/10位地址模式
  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
  • 支持DMA
  • 兼容SMBus协议

STM32F103C8T6 硬件I2C资源:I2C1、I2C2

I2C默认工作于从模式。接口在生成起始条件后自动地从从模式切换到主模式;当产生停止信号时,则从主模式切换到从模式。

IO口初始化

GPIO的其它参数的理解可以阅读下方博客,这里不再赘述。

【STM32】GPIO和AFIO标准库使用框架_gpio afio-CSDN博客

根据引脚定义表GPIOB的10和11可分别复用在单片机内的I2C2接口的SCL和SDA线。再由I2C协议,则可配置这两个引脚的GPIO模式为复用开漏输出模式。

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接口配置

I2C时钟配置

I2C1、I2C2均在APB1总线上

再由RCC时钟树,需要使能I2C外设对应的时钟

cpp 复制代码
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);

I2C初始化

I2C模式

|----------------------|---------|
| I2C_Mode_I2C | I2C模式 |
| I2C_Mode_SMBusDevice | SMBus设备 |
| I2C_Mode_SMBusHost | SMBus主机 |

cpp 复制代码
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;

时钟速度

I2C_ClockSpeed 该参数用来设置时钟频率,这个值不能高于 400KHz

|-----------|------|
| 时钟频率(KHz) | 速度模式 |
| 0~100 | 标准 |
| 101~400 | 快速 |

cpp 复制代码
I2C_InitStructure.I2C_ClockSpeed = 50000;//标准速度

占空比

|--------------------|------------------------------|
| I2C_DutyCycle | 描述 |
| I2C_DutyCycle_16_9 | I2C 快速模式 T_low/T_high = 16/9 |
| I2C_DutyCycle_2 | I2C 快速模式 T_low/T_high = 2 |

注意:该参数只有在I2C 工作在快速模式(时钟工作频率高于 100KHz)下才有意义

小于等于100KHz的标准速度下,占空比为固定的1:1

cpp 复制代码
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//标准速度配置该参数无意义

单片机地址

I2C_AcknowledgedAddres 定义了应答 7 位地址还是 10 位地址

I2C_OwnAddress1 该参数用来设置单片机第一个自身地址,它可以是一个 7 位地址或者一个 10 位地址

单片机作为从机模式下才需配置,这两个参数,不在本博客学习范围内,就随便配置了

cpp 复制代码
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;

应答位使能

发送数据自带接收应答的过程,接收数据自带发送应答的过程,不需要接收与发送应答位就失能

cpp 复制代码
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;

I2C接口使能

I2C接口配置的最后调用即可

cpp 复制代码
I2C_Cmd(I2C2, ENABLE);

I2C外设配置框架

cpp 复制代码
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);

I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2, &I2C_InitStructure);

I2C_Cmd(I2C2, ENABLE);

主机的发送与接收

代码来自江协科技:[10-5] 硬件I2C读写MPU6050_哔哩哔哩_bilibili

cpp 复制代码
/*while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)

while循环等待硬件置事件发送标志位,但硬件I2C有许多事件,就有许多while循环,容易造成堵塞
*/


//超时退出,防止阻塞主循环其他程序
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)
		{
			break;
		}
	}
}

主机发送

一定要对着参考手册的寄存器描述看

cpp 复制代码
	I2C_GenerateSTART(I2C2, ENABLE); //使能起始条件生成电路
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5:等待起始条件发送完成


	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//告诉MPU6050从机,主机要发送数据,自动接收应答位,可使能应答位有关的中断
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6:等待地址发送结束

	//
	I2C_SendData(I2C2, RegAddress);//发送指定数据指定要写的寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8:移位寄存器数据未发送完,就可以将数据放在数据寄存器等着了,这里等待EV8事件发生即可
   //可用for循环这两句发多个字节
	
	I2C_SendData(I2C2, Data);//发送数据
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2:等待移位寄存器和数据寄存器都为空,数据完成后再发送起始条件
	
    I2C_GenerateSTOP(I2C2, ENABLE);

主机接收

cpp 复制代码
uint8_t Data;   //存放接收的一个字节

I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

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);//等待数据发送完,再生成起始条件,等待I2C_EVENT_MASTER_BYTE_TRANSMITTING事件也可行

I2C_GenerateSTART(I2C2, ENABLE);        ///使能重复起始条件生成电路,改变数据传输方向
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5:等待重复起始条件发送完成

I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//告诉MPU6050从机,主机要接收数据
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6:等待地址发送结束

//
I2C_AcknowledgeConfig(I2C2, DISABLE);//在从机发完一个字节之后的应答位时序前,失能ACK应答位,只接受一个字节的数据
I2C_GenerateSTOP(I2C2, ENABLE);//根据序列图建议,这里设置STOP请求

MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7:接收数据寄存器非空
Data = I2C_ReceiveData(I2C2);//接收1个字节
//for循环调用,接收多个字节,只需在最后接收最后一个字节之前,失能应答和使能停止生成电路

I2C_AcknowledgeConfig(I2C2, ENABLE);//使能ACK应答位,恢复初始化设置状态

return Data;

I2C例程

mpu6050的c文件

cpp 复制代码
#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;
	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);
	
	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_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);
	
	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);
	
	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	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_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_ClockSpeed = 50000;
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;
	I2C_Init(I2C2, &I2C_InitStructure);
	
	I2C_Cmd(I2C2, ENABLE);
	
	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);
}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

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;
}
相关推荐
liangbm326 分钟前
数学建模笔记——动态规划
笔记·python·算法·数学建模·动态规划·背包问题·优化问题
GoppViper37 分钟前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
Charles Ray2 小时前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
重生之我在20年代敲代码2 小时前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
我要吐泡泡了哦3 小时前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎
骑鱼过海的猫1233 小时前
【tomcat】tomcat学习笔记
笔记·学习·tomcat
贾saisai5 小时前
Xilinx系FPGA学习笔记(九)DDR3学习
笔记·学习·fpga开发
北岛寒沫5 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
烟雨666_java5 小时前
JDBC笔记
笔记
GEEKVIP5 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑