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

news2024/11/25 3:08:31

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

目录

前言

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外设对应的时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);

I2C初始化

I2C模式

I2C_Mode_I2C  I2C模式
I2C_Mode_SMBusDeviceSMBus设备
I2C_Mode_SMBusHostSMBus主机
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;

时钟速度 

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

时钟频率(KHz)速度模式
0~100标准
101~400快速
I2C_InitStructure.I2C_ClockSpeed = 50000;//标准速度

 占空比

I2C_DutyCycle描述
I2C_DutyCycle_16_9I2C 快速模式 T_low/T_high = 16/9
I2C_DutyCycle_2I2C 快速模式 T_low/T_high = 2

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

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

I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//标准速度配置该参数无意义

 单片机地址

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

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

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

I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;

 应答位使能

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

I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;

I2C接口使能 

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

I2C_Cmd(I2C2, ENABLE);

I2C外设配置框架

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


/*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;
		}
	}
}

主机发送

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

	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);

主机接收 

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;

注: 当TxE或BTF位置位时,停止条件应安排在出现EV8_2事件时 

 I2C例程

mpu6050的c文件

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2061581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进程间的通信3——IPC对象通信->共享内存、网络通信

一、共享内存 1、原理 直接对实际物理内存进行操作&#xff0c;不用先拷贝到用户空间再到内核空间&#xff08;物理内存&#xff09;。 2、特点 &#xff08;1&#xff09;共享内存是一块内核预留的空间&#xff1b; &#xff08;2&#xff09;最高效的通信方式。 3、操作 产…

进阶SpringBoot之 Mybatis 框架

Maven 仓库 导入 mybatis-spring-boot-starter 的 jar 包 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version> </dependency>新建 Web…

跨平台快递追踪系统共享

物流追踪一站式平台推荐&#xff1a;固乔快递查询助手 在快速发展的电商时代&#xff0c;物流追踪已成为商家和消费者日常不可或缺的一部分。无论是商家需要监控订单状态&#xff0c;还是消费者期待及时了解包裹动向&#xff0c;一个高效、便捷的物流追踪平台都显得尤为重要。…

多模态大模型视觉特征嵌入语言模型特征流程-(以MMLM的forward源码解读)

文章目录 前言一、多模态大模型的forward方法(llava)1、位置2、源码3、模型输入3、图像编码与文本格式输入4、大语言模型5、计算loss6、重点内容提示二、prepare_inputs_labels_for_multimodal关键函数解读1、视觉编码2、input_ids循环遍历处理3、寻找图像位置token-batch循环4…

公开课观后感:密歇根大学python for everyone

从2024年1月17日到2024年8月20日&#xff0c;终于将密歇根大学的python for everyone的python公开课跟完。站在一月份规划的时刻来看&#xff0c;比我想象中花费的时间更多&#xff0c;我当时肯定没有想到要花上整整七个月的时间才能将这个公开课的内容看完&#xff0c;毕竟这个…

【C/C++】菱形继承问题

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

编译 onigmo 库

onigmo github: https://github.com/k-takata/Onigmo 测试环境&#xff1a;Windows 我编译库时习惯于在 vs code 下 git clone 文件后&#xff0c;再执行相应的编译操作 而 vs code 提供的终端一般是 git bash 和 powershell 在编译 windows 下运行的库》.lib 和 .dll 不能直接…

Linux设置内网时间同步

背景&#xff1a;公司有三台服务器检测到同步外网的时间&#xff0c;现需要将其修改为同步公司内网自己搭建的ntp服务器 1、登录服务器检查 同步外网无疑 2、修改配置文件&#xff0c;同步内网ntp服务器时间 配置文件源内容如下&#xff1a; 修改后如下&#xff1a; [rootl…

Redis—持久化机制

Redis持久化机制 1. RDB1.1 实现方式1.2 实现原理 2. AOF2.1 实现方式2.2 AOF文件载入2.3 AOF重写2.4 重写触发 3. RDB vs AOF3.1 RDB3.2 AOF3.3 如何选择&#xff1f; 4. Redis 4.0 混合持久化 Redis的持久化机制有两种持久化机制&#xff0c;分别是 RDB 和 AOF 1. RDB Redi…

Python和MATLAB谐波生成导图

&#x1f3af;要点 绘制三次谐波生成透射功率谱、对数对数图表示半导体曲面二次谐波生成&#xff0c;分析判断材料特性谐波均值估计计算边际似然&#xff08;贝叶斯统计&#xff09;二次谐波散射分析胶体染料分子结构交流电谐波波形傅立叶分析分析旋转各向异性谐波高次谐波非线…

TMGM:7月日本贸易收支可能受到显著走强的日元影响

经济学家和市场参与者预计今年将再次加息美联储可能助推美元/日元的看跌延续 7月日本贸易平衡可能受到显著走强的日元影响7月日本的贸易平衡比预期更差&#xff0c;但赤字大约是5月的一半&#xff0c;约为1月的三分之一。7月进口量增长超出预期&#xff0c;而较强的日元可能影…

模型 闭环原理

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。反馈驱动&#xff0c;持续循环&#xff0c;缺陷亦被放大。 1 闭环原理的应用 1.1 闭环原理解读 AI自我训练&#xff0c;从人工智能变成人工智障 这里主要使用闭环原理来解释 AI 自我训练导致的问题。…

基于STM32F103的FreeRTOS系列(十一)·信号量·二值信号量与计数信号量详细使用以及移植教程

目录 1. 信号量简介 1.1 同步和互斥 1.1.1 同步 1.1.2 互斥 1.1.3 总结 1.2 分类 1.2.1 二值信号量 1.2.2 计数信号量 1.2.3 互斥信号量 1.2.4 递归信号量 2. 信号量控制块 3. 常用信号量API函数 3.1 创建信号量函数 3.1.1 创建二值信号量 xSemap…

实验七:独立按键实验

硬件电路图和题目; LED1-LD8是 P2口8个管脚 mian.c #include<reg52.h>sbit But1=P3^1 ; sbit But2=P3^0 ; sbit But3=P3^2 ; sbit But4=P3^3 ;sbit LED1 =P2^0 ; sbit LED2 =P2^1 ; sbit LED3 =P2^2 ; sbit LED4 =P2^3 ;#define PRESS_1 1 #define PRESS_…

数据库多表设计:深入理解一对多、一对一、多对多关系 【后端 12】

数据库多表设计&#xff1a;深入理解一对多、一对一、多对多关系 在数据库设计中&#xff0c;表之间的关系决定了如何组织和存储数据。常见的表关系包括一对多、一对一和多对多。在不同的业务场景下&#xff0c;我们会选择不同的关系模式进行数据库设计。本文将通过具体案例介绍…

linux Qt QkeyEvent及驱动键盘按键捕获

基于正点原子 QT中有专门的类处理键盘事件的类QKeyEvent 1.include “QKeyEvent” 查看它的说明中的描述 也就是说接受按键事件在keyPressEvent和keyReleaseEvent这两个函数&#xff0c;继续查看 重构这个函数 查看输入的QKeyEvent类&#xff0c;发现有一个方法key返回哪一个按…

MinerU pdf文档解析markdown格式、内容提取

参考&#xff1a; https://github.com/opendatalab/MinerU/blob/master/README_zh-CN.md demo在线网址&#xff1a; https://opendatalab.com/OpenSourceTools/Extractor/PDF/detail

Robot Operating System——创建动态链接文件项目的步骤

大纲 初始化环境创建Package代码添加依赖&#xff08;package.xml&#xff09;修改编译描述find_package寻找依赖库指定代码路径和编译类型&#xff08;动态库&#xff09;设置头文件路径链接依赖的库 编译测试参考资料 在 《Robot Operating System——创建可执行文件项目的步…

大数据-93 Spark 集群 Spark SQL 概述 基本概念 SparkSQL对比 架构 抽象

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

VMware虚拟机nat无法联通主机

VMware在nat模式下主机无法ping通虚拟机 原因&#xff1a; 虚拟机和对应的网卡不在一个网段 虚拟机开启了防火墙 解决方法: 首先判断虚拟机的网络ip是否和网卡在一个网段上 判断虚拟机使用的网卡 nat模式在VMware虚拟机中一般只有一个对应的网卡 如图笔者的nat网卡为VM…