STM32学习笔记-I2C通信协议

news2024/10/2 10:35:10

文章目录

      • 介绍:
      • 两种实现方式:
      • I2C设备的常用连接方式:
      • I2C协议
      • 时序:
    • STM32硬件I2C框架图
    • I2C外设通讯过程
      • **I2C读写EEPROM**(硬件I2C)

介绍:

  • 两根通信线SCL(时钟线)、SDA(数据线)

  • 同步半双工,支持总线挂载多设备

两种实现方式:

I2C有硬件I2C和软件I2C两种实现方式

硬件I2C:STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了CPU 的工作,且使软件设计更加简单

软件I2C:通过控制IO口高低电平模拟I2C协议所需电平,实现软件I2C直接控制 STM32 的两个 GPIO 引脚,分别用作 SCL 及 SDA,按照上述信号的时序要求,直接像控制 LED 灯那样控制引脚的输出 (若是接收数据时则读取 SDA 电平),就可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它,不管是 ST 生产的控制器还是 ATMEL 生产的存储器,都能按通讯标准交互。由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。

I2C设备的常用连接方式:

image-20230223181031135

I2C协议

image-20230223181409764

  • S代表起始位

  • SLAVE ADDRESS代表从机地址,表示主机要向哪个从机发送数据

  • RW表示读还是写,0表示主机发送数据,1表示主机接收数据

  • A代表应答位,主机向从机发送完从机地址后,被选中的从机会向主机发送一个应答位,表示接收到了主机发送的数据

  • DATA是数据,一次发送八位二进制数据,从机在接收到数据后会发送一个应答位,

  • P是停止位

  • I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。见图数据有效性 。SDA 数据线在 SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时,SDA的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。image-20230223182151880

  • I2C总线上每个设备都有自己的独立地址,主机发起通讯时通过SDA发送设备地址(SLAVE ADDRESS)来查找从机,I2C协议规定从机地址可以是7位也可以是10位,设备地址后一位是数据方向位,第八位或者第十一位,0表示主机发送,1表示主机接收

  • I2C 的数据和地址传输都带响应。响应包括“应答 (ACK)”和“非应答 (NACK)”两种信号。作为数据接收端时,当设备 (无论主从机) 接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答 (ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答 (NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。

  • image-20230223182734721

时序:

  • 起始信号和停止信号

image-20230223182056999

起始条件:SCL高电平期间,SDA从高电平切换到低电平

停止条件:SCL高电平期间,SDA从低电平切换到高电平

  • 发送数据位

SCL低电平期间,主机将数据位放到SDA(高位先行),释放SCL(高电平),从机在SCL高电平期间读取数据位,重复八次发送一个字节

image-20230225183751769

  • 接收数据位

  • 发送应答

主机在接收完一个字节后会在下一个时钟向从机发送一位数据,数据0表示应答,1表示非应答

  • 接收应答

从机在接收完一个字节后会在下一个时钟向主机发送一位数据,数据0表示应答,1表示非应答(主机在接收之前需要释放SDA(高电平))

STM32硬件I2C框架图

image-20230223183044716

  1. STM32F4的I2C引脚橙色部分为I2C的外接通信线,SDA为数据线,SCL为时钟线。SMBA线用于 SMBUS 的警告信号,I2C 通讯没有使用。STM32有多个I2C外设,对应的引脚如下:

    引脚I2C 编号
    I2C1I2C2I2C3
    SCLPB6/PB10PH4/PF1/PB10PH7/PA8
    SDAPB7/PB9PH5/PF0/PB11PH8/PC9
  2. 时钟控制逻辑电路

    紫色部分为I2C的时钟控制逻辑电路,为I2C通信提供时钟。通过寄存器配置I2C的通信速率以及占空比。

  3. I2C 的 SDA 信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存器 (DR)、地址寄存器 (OAR)、PEC 寄存器以及 SDA 数据线。当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过 SDA 信号线发送出去;当从外部接收数据的时候,数据移位寄存器把 SDA 信号线采样到的数据一位一位地存储到“数据寄存器”中。若使能了数据校验,接收到的数据会经过 PCE 计算器运算,运算结果存储在“PEC 寄存器”中。当 STM32 的 I2C 工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32 的自身的“I2C 地址寄存器”的值作比较,以便响应主机的寻址。STM32 的自身 I2C 地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C 设备地址,两个地址分别存储在 OAR1 和 OAR2 中。

  4. 整体控制逻辑负责协调整个 I2C 外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器 (SR1 和 SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解 I2C 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 I2C 中断信号、DMA 请求及各种 I2C 的通讯信号(起始、停止、响应信号等)。

I2C外设通讯过程

作为主发送器

image-20230223204848269

STM32的I2C在通信过程中会产生事件,标志位寄存器里面的数据通过不同的组合可以产生不同的事件。我们可以通过事件来判断I2C通信的信号是否发出。

EV5:SR1寄存器的SB位置1表示起始信号已经发送。

EV6:SR1寄存器的ADDR置1表示地址已经发送

EV8:SR1寄存器的TXE置1表示数据寄存器为空

EV8_2:TXE以及BTF置1,TXE置1表示数据寄存器为空,BTF置1表示通信结束

作为主接收器

image-20230223204913164

EV7:SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空。

这些事件发生之后对应的寄存器位需要软件清零,我们可以通过库函数的方式来监测事件并清零

I2C初始化结构体

typedef struct {
    uint32_t I2C_ClockSpeed; /*!< 设置 SCL 时钟频率,此值要低于 40 0000*/
    uint16_t I2C_Mode; /*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 */
    uint16_t I2C_DutyCycle; /* 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式
    */
    uint16_t I2C_OwnAddress1; /*!< 指定自身的 I2C 设备地址 */
    uint16_t I2C_Ack; /*!< 使能或关闭响应 (一般都要使能) */
    uint16_t I2C_AcknowledgedAddress; /*!< 指定地址的长度,可为 7 位及 10 位 */
} I2C_InitTypeDef;

I2C读写EEPROM(硬件I2C)

EEPROM写时序

image-20230226141908163

  • 首先I2C发送一个起始信号表示开始通信,然后发送从机地址,表示主机想要和哪个设备进行通信,因为设备地址为7位,所以最后一位R/W表示读写位,1表示读,0表示写。

  • 之后从机向主机发送一个应答信号,主机在接收到应答信号后可以发送数据了,EEPROM规定在发送数据之前需要先告诉EEPROM要读或者写入的地址。所以第一个字节用来表示EEPROM的内部地址,从机接收到地址后向主机发送应答表示从机收到,之后主机就可以向从机发送想要写入的数据了。

  • 主机发送数据从机应答后主机就可以发送停止信号表示本次通信结束。

EEPROM读时序

image-20230226142804702

  • 向EEPROM读数据是一个复合时序,由一个写时序和一个读时序去掉中间的停止信号复合而成(停止信号也可以不用去)

  • 首先主机发送设备地址及要读的地址后,EEPROM的指针便指向要读的地址。

  • 然后主机重新发送一个起始信号,读写方向为读,EEPROM便向主机发送指针所指向的地址的数据,主机只需要应答,在读完后发送一个非应答信号表示主机不再接受,通信结束。

代码

代码部分只需要按照I2C的时序,一步一步来即可

发送一字节数据

uint32_t I2C_EE_ByteWrite(u8 WriteAddr,u8 data)
{
  /* 发送开始条件 */
  I2C_GenerateSTART(EEPROM_I2C, ENABLE);

  I2CTimeout = I2CT_FLAG_TIMEOUT;
    
  /* 检测EV5事件并清零寄存器 */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
  }    

  /* 发送EEPROM地址 */
  I2C_Send7bitAddress(EEPROM_I2C, EEPROM_Address, I2C_Direction_Transmitter);
  
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  /* 检测EV6事件并清零寄存器 */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
  }    
      
  /* 发送EEPROM内部地址 */
  I2C_SendData(EEPROM_I2C, WriteAddr);
  
  I2CTimeout = I2CT_FLAG_TIMEOUT;

  /* 检测EV8事件并清零寄存器 */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))  
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
  } 
  /* 发送要写入的数据 */
  I2C_SendData(EEPROM_I2C, data); 
   
  I2CTimeout = I2CT_FLAG_TIMEOUT;

  /* 检测EV8事件并清零寄存器 */
  while(!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
  } 
  
  /* 停止条件 */
  I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
  
  return 1;
}

这里设置了超时回调函数,方便快速定位到错误代码处,方便调试

超时回调函数

static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{
	/* 使用串口 printf 输出错误信息,方便调试 */
	printf("I2C 等待超时!errorCode = %d",errorCode);
	return 0;
}

读取指定地址数据

uint8_t I2C_EE_ReadByte(u8 ReadAddr,u8 *data)
{
    //检测I2C是否繁忙
	while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))   
    {
    	if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
    }
	//开始
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);
	/* 设置超时等待时间 */
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(00);
	}
	
	//发送设备地址,方向为写
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_Address, I2C_Direction_Transmitter);
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
	}
	I2C_Cmd(EEPROM_I2C, ENABLE);
	//发送要读的地址
	I2C_SendData(EEPROM_I2C,ReadAddr);
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(22);
	}
	//重新开始
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);
			/* 设置超时等待时间 */
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(33);
	}
	
	
	//发送设备地址,方向为读
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_Address, I2C_Direction_Receiver);
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(44);
	}
	//关闭自动应答(在初始化I2C结构体的时候设置了自动应答)
	I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(55);
	}

	*data = I2C_ReceiveData(EEPROM_I2C);	
	//结束
	I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
	return 0;
}

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

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

相关文章

C语言中的强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#xff0c;如下所示&am…

“ChatGPT之父”Sam Altman:如何成功?

背靠微软&#xff0c;OpenAI能拳打谷歌&#xff0c;脚踢Meta&#xff0c;它背后的男人&#xff0c;必然不简单。 让我们来看一看&#xff0c;Sam Altman是如何一步步成长为今天这个搅动全世界的男人。 山姆奥特曼&#xff08;Sam Altman&#xff09; 成长和创业经历 在YC创始…

代码随想录【Day27】| 39. 组合总和、40. 组合总和 II、131. 分割回文串

39. 组合总和 题目链接 题目描述&#xff1a; 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明&#xff1a; 所有数字&#xff08;包括 tar…

JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac

JavaScript 高级2 &#xff1a;构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…

MySQL之EXPLAIN

使用方法 查询结果分析 id&#xff1a;识别符 select_type&#xff1a;表示查询的类型 table&#xff1a;输出结果集的表 partitions&#xff1a;匹配的分区 type&#xff1a;表示表的连接类型 possible_keys&#xff1a;表示查询时&#xff0c;可能使用的索引 key&#xff1a…

jni-Demo-基于linux(c++ java)

跑一个jni 的最简单的Demo需要提前准备 VsCode 编译器、win10下&#xff0c;vscode中集成linux操作系统、c编译器&#xff08;gcc、g&#xff09;&#xff0c;java编译器&#xff08;jdk1.8&#xff09;参考&#xff1a;https://mangocool.com/1653030123842.htmlJniDemo类&…

【分享】灌溉制度设计小程序VB源代码

说明 根据作物需水特性和当地气候、土壤、农业技术及灌水技术等因素制定的灌水方案。主要内容包括灌水次数、灌水时间、灌水定额和灌溉定额。灌溉制度是规划、设计灌溉工程和进行灌区运行管理的基本资料&#xff0c;是编制和执行灌区用水计划的重要依据。 1—计划湿润土层允…

spring面试题总结

1、spring是什么&#xff1f; spring是一个轻量级IOC和AOP容器框架&#xff0c;是为Java应用程序提供基础性服务的一套框架&#xff0c;目的是用于简化企业应用的开发&#xff0c;开发者只需要关注业务需求即可&#xff1a; core container 容器组件 spring context&#xff0c…

@ConfigurationProperties在方法上的使用

文章目录1. 前言2. 先说结论3. 代码解释1. Component ConfigurationProperties2. EnableConfigurationProperties ConfigurationProperties3. Bean ConfigurationProperties1. 前言 在学习spring的时候&#xff0c;ConfigurationProperties应该经常被使用到&#xff0c;作用…

一文弄清混合云架构模式

当我们在说云架构的时候&#xff0c;通常指的并不是云平台的自身架构&#xff0c;而是基于云平台的软件系统基础架构。云平台的自身架构满足了很多通用层面的需求&#xff0c;例如对象存储&#xff0c;弹性主机&#xff0c;虚拟网络等等&#xff0c;只有云服务厂商的工程师才会…

Win10系统内置杀毒软件Windows Defender卸载方法

Windows10系统自带的Windows Defender杀毒软件&#xff0c;偶尔会使你的电脑CPU满负荷运载。 其实我们完全可以删除这个Windows10自带的杀毒软件&#xff0c;我们忍耐总是有限度的&#xff0c;所以&#xff0c;你可以选择删除它&#xff1b; 怎么删除呢&#xff1f;下面有个方…

Pthon--自动化实用技巧篇--文件目录处理

为什么要讲这一篇&#xff0c;主要是因为这个在自动化测试框架或者脚本的编写的时候会用到&#xff0c;还是比较方便的。看上述两个函数。getcwd()、chdir()。使用 os.getcwd() 函数获得当前工作目录。使用 os.chdir()函数改变当前工作目录。所以在用chdir()函数的时候别忘记指…

数据类型和变量

目录 ​编辑 一、字面常量 1、字面常量的定义 2、字面常量的分类 二、数据类型 三、变量 1、变量的概念 2、语法格式 3、整型变量 &#xff08;1&#xff09;整型变量 &#xff08;2&#xff09;长整型变量 &#xff08;3&#xff09;短整型变量 &#xff08;4&a…

带你深入了解c语言指针后续

前言 &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:介绍c语言中有关指针更深层的知识. 金句分享: ✨在该…

shell学习3

目录 一、查找并删除重复文件 二、列举文件类型统计信息 三、只列出目录 一、查找并删除重复文件 重复文件是同一个文件的多个副本。有时候我们需要删除重复的文件&#xff0c;只保留其中一份。通过查看文件内容来识别重复文件是件挺有意思的活儿。可以结合多种shell工具来完…

阶段六:服务框架基础(第二章Day-MQ(服务异步通讯))

阶段六&#xff1a;服务框架基础&#xff08;第二章Day-MQ&#xff08;服务异步通讯&#xff09;&#xff09;Day-RabbitMQ1.初识MQ1.1.同步和异步通讯1.1.1.同步通讯1.1.2.异步通讯1.2.技术对比&#xff1a;2.快速入门2.1.安装RabbitMQ 【重要】2.1.1、安装RabbitMQ&#xff0…

换掉 Maven,我就用Gradle,急速编译

相信使用Java的同学都用过Maven&#xff0c;这是一个非常经典好用的项目构建工具。但是如果你经常使用Maven&#xff0c;可能会发现Maven有一些地方用的让人不太舒服&#xff1a; Maven的配置文件是XML格式的&#xff0c;假如你的项目依赖的包比较多&#xff0c;那么XML文件就…

[Datawhale][CS224W]图机器学习(六)

目录一、简介二、概述三、算法四、PageRank的缺点五、Python实现迭代法参考文献一、简介 PageRank&#xff0c;又称网页排名、谷歌左侧排名、PR&#xff0c;是Google公司所使用的对其搜索引擎搜索结果中的网页进行排名的一种算法。 佩奇排名本质上是一种以网页之间的超链接个…

蒙特卡洛随机模拟

蒙特卡洛随机模拟 简介 蒙特卡洛模拟是在计算机上模拟项目实施了成千上万次&#xff0c;每次输入都随机选择输入值。由于每个输入很多时候本身就是一个估计区间&#xff0c;因此计算机模型会随机选取每个输入的该区间内的任意值&#xff0c;通过大量成千上万甚至百万次的模拟…

笔记本触摸板没反应怎么办?处理方法看这些

触摸板在笔记本电脑中是非常重要的一部分&#xff0c;很多用户都会选择使用触摸板代替鼠标。然而&#xff0c;有时你可能会发现&#xff0c;你的笔记本电脑触摸板没反应&#xff0c;无法正常使用。这对于日常使用来说是非常困扰的&#xff0c;但不用担心&#xff0c;我们将在这…