【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;
}
相关推荐
吉大一菜鸡1 小时前
FPGA学习(基于小梅哥Xilinx FPGA)学习笔记
笔记·学习·fpga开发
森旺电子2 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
CCSBRIDGE3 小时前
Magento2项目部署笔记
笔记
不过四级不改名6774 小时前
蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)
stm32·嵌入式硬件·蓝桥杯
小A1594 小时前
STM32完全学习——SPI接口的FLASH(DMA模式)
stm32·嵌入式硬件·学习
Rorsion4 小时前
各种电机原理介绍
单片机·嵌入式硬件
亦枫Leonlew4 小时前
微积分复习笔记 Calculus Volume 2 - 5.1 Sequences
笔记·数学·微积分
爱码小白5 小时前
网络编程(王铭东老师)笔记
服务器·网络·笔记
LuH11245 小时前
【论文阅读笔记】Learning to sample
论文阅读·笔记·图形渲染·点云
一棵开花的树,枝芽无限靠近你7 小时前
【PPTist】组件结构设计、主题切换
前端·笔记·学习·编辑器