IIC协议通信解析,内附完整代码。

news2024/11/29 3:51:10

一:硬件接口

1.1:功能引脚

 1.2:IIC总线通信注意事项

二:通信协议

(1)空闲状态:

(2)起始位:

 (3)有效数据位

(4)应答(非应答)

(5)停止位

IIC模拟代码 


一:硬件接口

1.1:功能引脚

IIC一般由两根线组成,一根时钟线SCL,一根数据线SDA。

SCL: 提供数据交互的时钟

SDA: 进行数据的交互

当然在进行数据交互的时候,两个芯片需要共地

 1.2:IIC总线通信注意事项

(1)对于IIC总线来说,它是可以同时接入多个从机进行通信的,但是注意只能是主机(单片机)进行唯一的数据读写操作。

(2)为了进行通信,每一个可以进行IIC通信的从机的芯片手册上会标出该自己用于IIC通信的地址。(这里我们以AT24c128为例)

可以看到,基本的前面的bit位数据是固定的,但是留出了A1和A0两个bit的控制引脚,我们在电路板中将其对应的引脚置高,那么对应的bit数据就为1,反之则为0。最后一个LSB位,0代表需要对其进行写操作,1代表需要对其进行读操作。 

(3)我们一般使用的IIC协议都是模拟的IIc协议,也就是我们通过时序图来进行对应电平切换模拟出的IIc通信代码。这里需要注意的是:

1:在不进行数据通信时,SCL和SDA引脚必须保持高电平。

2:使用的模拟IIC引脚必须接入上拉电阻,所以SDA和SCL一般配置为开漏输出。

3:SDA引脚既需要输入也需要输出,在读写操作切换时,记得修改该引脚的模式。

二:通信协议

IIC通信由:空闲状态+起始位+有效数据位+应答(非应答)+停止位 组成

(1)空闲状态:

这一时刻SDA和SCL一般都是保持高电平输出。所以我们在进行IIC模拟通信的初           始化时,SDA和SCL引脚都会被配置为高电平。

(2)起始位:

 

 这个时刻,SCL引脚保持高电平不变,SDA引脚电平从高电平跳变为低电平。 

 (3)有效数据位

这里在发送有效数据位时,有一个规范,在SCL引脚为高电平时,SDA的电平不能被修改,这是数据的采样时刻,数据交互时,会在SCL引脚高电平时进行数据的采样。只有在SCL保持低电平时,才能对SDA引脚的电平进行修改(当然也不一定要修改,比如连续发送多个bit1或者bit0) 

根据代码也能很好的看出来,每次在对SDA引脚进行电平修改时,都会将SCL引脚点哦置0,然后修改好SDA引脚电平后,再将SCL引脚电平置高。 

(4)应答(非应答)

 

这里就比较简单了,有应答信号就是SDA引脚有一个低电平,没有的话就是高电平。

(5)停止位

 

IIC模拟代码 

注意如果IIC读写输出出现问题,可以将延时适当调大一些。

// 该代码用于stm32f103c8t6测试没问题

#define I2C_SCL_PORT  GPIOB
#define I2C_SCL_Pin   GPIO_Pin_8

#define I2C_SDA_PORT  GPIOB
#define I2C_SDA_Pin   GPIO_Pin_9

#define OUTPUT 0
#define INPUT 1

static void I2C_Tail(void); //发送结束标志
static void I2C_Head(unsigned short Addrs); // 发送起始标志

static void hal_I2C_Config(void); // 模拟I2C配置函数
static void hal_I2C_SCL_SET(unsigned char i);  // 设置时钟引脚
static void hal_I2C_SDA_SET(unsigned char i);  // 设置数据引脚
static void hal_I2C_Start(void);  // 起始标志
static void hal_I2C_Stop(void);   // 结束标志
static void I2C_Delay(unsigned short t);
static void I2C_ACK(void);   // 有ACK应答
static void I2C_NOACK(void); // 无ACK应答
static void I2C_SendByte(unsigned char data);   // 写一个byte数据时序
static void I2C_WriteByte(unsigned short Addr, unsigned char data); // 发送一个byte
static unsigned char I2C_RecvByte(void); // 读一个字节时序 
static unsigned char I2C_WaitAck(void);  // 返回0,无应答数据 。返回1,有应答数据

void hal_I2C_SDA_IO_SET(unsigned char IOMode); // OUTPUT 或者 INPUT
unsigned char hal_IC_SDA_INPUT(void);
unsigned char I2C_ReadByte(unsigned short Addr); // 读一个字节数据


void hal_eeprom_Init(void)
{
  hal_I2C_Config();
}

unsigned char I2C_ReadByte(unsigned short Addr)
{
  unsigned char dat;
	hal_I2C_Start();
	I2C_SendByte(0xA0);
	I2C_WaitAck();
	
	I2C_SendByte((Addr>>8)&0xFF);
	I2C_WaitAck();
	
	I2C_SendByte(Addr&0xFF);
	I2C_WaitAck();
	
	hal_I2C_Start();
	I2C_SendByte(0xA1); // 发送 1010 0001 最低位的1代表从该芯片读取数据
	I2C_WaitAck();
	
	dat = I2C_RecvByte();
	I2C_NOACK();
	hal_I2C_Stop();
	return dat;
}

static unsigned char I2C_RecvByte(void)
{
   unsigned char i = 0;
	 unsigned char RecvByte = 0;
	
   hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
	 hal_I2C_SDA_SET(1);
	 hal_I2C_SDA_IO_SET(INPUT);
	 
	 for(; i < 8; i++)
	{
     	RecvByte<<=1;
		  I2C_Delay(1);
		  hal_I2C_SCL_SET(1);
		  I2C_Delay(1);
		  if(hal_IC_SDA_INPUT()) {
			  RecvByte |= 0x01;
			}
			hal_I2C_SCL_SET(0);
	}
	hal_I2C_SDA_IO_SET(OUTPUT);
	I2C_Delay(1);
	return RecvByte;
}

void hal_I2C_Read( unsigned short Addrs, unsigned char *pBuf, unsigned short Len)
{
  unsigned short i = 0;
	
	I2C_Head(Addrs);
	
	hal_I2C_Start();
	I2C_SendByte(0xA1); // 发送 1010 0001 最低位的1代表从该芯片读取数据
	I2C_WaitAck();
	
	for( i = 0; i < Len; i++)
	{
	  pBuf[i] = I2C_RecvByte();
		if(i == Len - 1) I2C_NOACK();
		else I2C_ACK();
	}
	
	hal_I2C_Stop();
}

static void I2C_WriteByte(unsigned short Addr, unsigned char data)
{
  hal_I2C_Start(); // 发送有效数据起始标志
	
	// 该芯片最低位的一个bit位代表读/写:0 代表写入数据 || 1 代表读出数据
	I2C_SendByte(0xA0); // 发送需要通信的硬件地址(对应的硬件手册上有!!!!!)
	I2C_WaitAck();
	
	I2C_SendByte((Addr>>8)&0xFF); // 发送写入的地址(高字节地址)
	I2C_WaitAck();
	
	I2C_SendByte(Addr&0xFF);    // 发送写入的地址(低字节地址)
	I2C_WaitAck();
	
	I2C_SendByte(data);
	I2C_WaitAck();
	
	hal_I2C_Stop();  // 发送有效数据结束标志
	I2C_Delay(20000);
}


static unsigned char I2C_WaitAck(void)
{
  hal_I2C_SDA_SET(1);
	hal_I2C_SDA_IO_SET(INPUT);  // 设置SDA引脚为输入引脚
	hal_I2C_SCL_SET(1);
	I2C_Delay(1);
	if(hal_IC_SDA_INPUT()) { // 该引脚为高电平,代表无应答
		hal_I2C_SDA_IO_SET(OUTPUT);  // 设置SDA引脚为输出引脚
	  return 0;
	}
	
	hal_I2C_SCL_SET(0);
	hal_I2C_SDA_IO_SET(OUTPUT); 
	I2C_Delay(1);
	return 1;
}

static void I2C_Head(unsigned short Addrs)
{
    hal_I2C_Start(); // 发送有效数据起始标志
	
	  // 该芯片最低位的一个bit位代表读/写:0 代表写入数据 || 1 代表读出数据
	  I2C_SendByte(0xA0); // 发送需要通信的硬件地址(对应的硬件手册上有!!!!!)
	  I2C_WaitAck();
	
	  I2C_SendByte((Addrs>>8)&0xFF); // 发送写入的地址(高字节地址)
	  I2C_WaitAck();
	
	  I2C_SendByte(Addrs&0xFF);    // 发送写入的地址(低字节地址)
	  I2C_WaitAck();
}

static void I2C_Tail(void)
{
  hal_I2C_Stop();  // 发送有效数据结束标志
	I2C_Delay(20000);
}

void hal_I2C_Write(unsigned short Addrs, unsigned char* pBuf, unsigned short Len)
{
	unsigned short tmp = 0;
	unsigned short i = 0, j = 0;
	unsigned short page = 0;
	unsigned short size;
	
	if(Addrs%EEPROM_PAGE_SIZE) 
	{
	  tmp = EEPROM_PAGE_SIZE-(Addrs%EEPROM_PAGE_SIZE); // 拿到当前页还有多少字节可写入
		if(tmp > Len)
		{
	     tmp = Len;	// 后续写入操作,需要tmp来做循环因子
		}
	}
	// 先将数据写入当前页
	if(tmp)
	{
		I2C_Head(Addrs);
		// 正式写入数据
		for(i = 0; i < tmp; i++)
		{
		  I2C_SendByte(pBuf[i]);
			I2C_WaitAck();
		}
		I2C_Tail();
	}
	
	Len -= tmp;   // 将已经发送过的数据个数减掉
	Addrs += tmp; // 将写入的地址更新
	
	page = Len/EEPROM_PAGE_SIZE; // int整形除法,只会得到整数
	size = Len%EEPROM_PAGE_SIZE; // 不足一页还有几个字节
	
	for(i = 0; i < page; i++)
	{
	  I2C_Head(Addrs);
		
		for(j = 0; j < EEPROM_PAGE_SIZE; j++)
		{
		  I2C_SendByte(pBuf[tmp + j]);
			I2C_WaitAck();
		}
		I2C_Tail();
		Addrs += EEPROM_PAGE_SIZE;
		tmp += EEPROM_PAGE_SIZE;
	}
	
	if(size)
	{
	  I2C_Head(Addrs);
		
		for(i = 0; i < size; i++)
		{
		  I2C_SendByte(pBuf[tmp + i]);
			I2C_WaitAck();
		}
		I2C_Tail();
	}
	
}

static void I2C_SendByte(unsigned char data)
{
  unsigned char i = 0;
	for(; i < 8; i++)
	{
		hal_I2C_SCL_SET(0);
		I2C_Delay(1);
	  if(data&0x80){
		  hal_I2C_SDA_SET(1);
		} else {
		  hal_I2C_SDA_SET(0);
		}
		I2C_Delay(1);
		hal_I2C_SCL_SET(1);
		I2C_Delay(1);
		data<<=1;
	}
	
	hal_I2C_SCL_SET(0);
  I2C_Delay(1);
	hal_I2C_SDA_SET(1);
  I2C_Delay(1);
}


static void I2C_ACK(void)
{
   hal_I2C_SCL_SET(0);
	 I2C_Delay(1); 
	 hal_I2C_SDA_SET(0); // SDA数据线置低,进行数据位应答。
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(1);
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
}


static void I2C_NOACK(void)
{
   hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
	 hal_I2C_SDA_SET(1); // SDA数据线置高,不进行数据应答。
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(1);
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
}

static void hal_I2C_Start(void)
{
  hal_I2C_SDA_SET(1);
	I2C_Delay(1);
	hal_I2C_SCL_SET(1);
	I2C_Delay(1);
	hal_I2C_SDA_SET(0);
	I2C_Delay(1);
}

static void hal_I2C_Stop(void)
{
  hal_I2C_SDA_SET(0);
	I2C_Delay(1);
	hal_I2C_SCL_SET(1);
	I2C_Delay(1);
	hal_I2C_SDA_SET(1);
	I2C_Delay(1);
}


static void hal_I2C_Config(void)
{
   GPIO_InitTypeDef GPIO_Struct;
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	 
	 GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_OD;
	 GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Struct.GPIO_Pin = I2C_SCL_Pin | I2C_SDA_Pin;
	 GPIO_Init(GPIOB, &GPIO_Struct);
	
	 hal_I2C_SDA_SET(1);
	 hal_I2C_SCL_SET(1);
	
}


void hal_I2C_SDA_IO_SET(unsigned char IOMode)
{
  if(0 == IOMode) {
	 GPIO_InitTypeDef GPIO_Struct;
	 GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_OD;
	 GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Struct.GPIO_Pin =  I2C_SDA_Pin;
	 GPIO_Init(GPIOB, &GPIO_Struct);
	} 
	if(1 == IOMode) {
	 GPIO_InitTypeDef GPIO_Struct;
	 GPIO_Struct.GPIO_Mode = GPIO_Mode_IPU;
	 GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Struct.GPIO_Pin =  I2C_SDA_Pin;
	 GPIO_Init(GPIOB, &GPIO_Struct);
	}
}


static void hal_I2C_SDA_SET(unsigned char i)
{
  if(i) {
	   GPIO_SetBits(I2C_SDA_PORT,I2C_SDA_Pin);
	 } else {
	   GPIO_ResetBits(I2C_SDA_PORT,I2C_SDA_Pin);
	 }
}


static void hal_I2C_SCL_SET(unsigned char i)
{
   if(i) {
	   GPIO_SetBits(I2C_SCL_PORT,I2C_SCL_Pin);
	 } else {
	   GPIO_ResetBits(I2C_SCL_PORT,I2C_SCL_Pin);
	 }
}

static void I2C_Delay(unsigned short t)
{
  unsigned short i=50,j,c; 
   c = t;
   for(j=0; j<c; j++)
   {
	   while(i) 
	   { 
				i--; 
	   } 
	}
}

unsigned char hal_IC_SDA_INPUT(void)
{
  return GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_Pin);
}

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

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

相关文章

最近跳槽,压力真大...

前几天&#xff0c;跟个老朋友吃饭&#xff0c;他最近想跳槽去大厂&#xff0c;觉得压力很大&#xff0c;问我能不能分享些所谓的经验套路。 每次有这类请求&#xff0c;都觉得有些有趣&#xff0c;不知道你发现没有大家身边真的有很多人不知道怎么面试&#xff0c;也不知道怎…

【玩转Docker小鲸鱼叭】MacOS系统配置Docker镜像加速器

当我们通过 docker pull拉取镜像时&#xff0c;如果不指定仓库&#xff0c;默认从 Docker Hub &#xff08;docker.io&#xff09;获取镜像&#xff0c;而国内用户访问Docker Hub仓库时&#xff0c;通常速度很忙&#xff0c;经常超时导致拉取镜像失败&#xff0c;所以通常要通过…

上海亚商投顾:沪指震荡调整 CPO概念股持续大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日震荡调整&#xff0c;保险等权重板块走低&#xff0c;上证50跌超1.5%&#xff0c;创业板指较为抗跌。CPO、…

【新固态格式化】

新固态格式化 初始化硬盘 从管理进入磁盘管理 Windows 7及其以后的系统建议使用GPT MBR 是 Master Boot Record 的缩写&#xff0c;是一种传统而常用的磁盘布局。GPT 是 Globally Unique Identifier Partition Table 的缩写&#xff0c;是一种与 UEFI 相关的新磁盘布局。其…

Day23 实战篇 ——Jmeter压力测试实战

Day23 实战篇 ——Jmeter压力测试实战 文章目录 Day23 实战篇 ——Jmeter压力测试实战一、分布式压测原理二、分布式环境配置Slaves机器配置Master机器配置参数化文件配置三、分布式压测执行Slave机器执行Master机器执行四、常见问题处理问题一问题二问题三问题四项目中使用Jme…

redis缓存设计-Redis(八)

上篇文章介绍了redis缓存设计&#xff0c;热点key&#xff0c;bigkey注意事项。 原创 redis缓存设计-Redis&#xff08;七&#xff09;https://blog.csdn.net/ke1ying/article/details/131268967 命令使用 hgetall&#xff0c;lrange&#xff0c;smembers&#xff0c;zrange…

【初识Linux】——01Linux系统

目录索引 Linux介绍&#xff1a;Linux历史&#xff1a;Linux系统应用&#xff1a;*服务器系统&#xff1a;**嵌入式系统&#xff1a;**桌面应用系统&#xff1a;**版本&#xff1a;* Linux系统的安装&#xff1a;虚拟机&#xff1a;安装VMware&#xff1a;安装centOS操作系统&a…

【AI实战】开源可商用的中英文大语言模型baichuan-7B,从零开始搭建

【AI实战】开源可商用的中英文大语言模型baichuan-7B&#xff0c;从零开始搭建 baichuan-7B 简介baichuan-7B 中文评测baichuan-7B 搭建参考 baichuan-7B 简介 baichuan-7B 是由百川智能开发的一个开源可商用的大规模预训练语言模型。基于 Transformer 结构&#xff0c;在大约…

解决安卓12限制32个线程

Android 12及以上用户在使用Termux时&#xff0c;有时会显示[Process completed (signal 9) - press Enter]&#xff0c;这是因为Android 12的PhantomProcesskiller限制了应用的子进程&#xff0c;最大允许应用有32个子进程。 这里以ColorOS 12.1为例&#xff08;其他系统操作略…

状态机编程实例-嵌套switch-case法

嵌入式软件开发中&#xff0c;状态机编程是一个比较实用的代码实现方式&#xff0c;特别适用于事件驱动的系统。 本篇&#xff0c;以一个炸弹拆除的小游戏为例&#xff0c;介绍状态机编程的思路。 C/C语言实现状态机编程的方式有很多&#xff0c;本篇先来介绍最简单最容易理解…

uni-app uni-file-picker文件上传实现拍摄从相册选择获取图片上传文档服务器

前言 最近在使用uni-app写H5移动端&#xff0c;有一个从手机拍摄从相册选择获取图片上传到文档服务器功能。 查阅uni-app发现关于上传图片&#xff0c;uni-file-picker文件上传&#xff0c;uni.chooseImage&#xff0c;uni.uni.uploadFile 它和pc端原理差不多&#xff0c;都是…

5年测试经验,测试老鸟总结功能测试——全测试点覆盖

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 功能测试主要包括…

使用vscode编写并运行typescript代码

1.安装vsCode Visual Studio Code - Code Editing. Redefined 2.安装nodejs 下载 | Node.js 中文网 3.打开vscode&#xff0c;在vscode里面打开终端&#xff08;快捷键是ctrl~) 查看是否成功安装node和npm&#xff1a; node -v npm -v 在终端中输入如下命令并…

ELK详细安装配置

1.安装jdk1.8&#xff08;略&#xff09; 2.安装配置本机防火墙&#xff08;略&#xff09; 3.ELK版本选择 参考&#xff1a;支持一览表 | Elastic 选择支持java8的版本 4.版本6安装 root依次用户执行 wget https://artifacts.elastic.co/downloads/elasticsearch/elast…

C++基础(1)——程序内存模型和引用

前言 本文主要介绍了C中内存模型的四区及存放的数据&#xff0c;引用的基本语法。 1.1&#xff1a;代码区&#xff08;程序运行前&#xff09; 1&#xff1a;存放CPU执行的机器指令 2&#xff1a;代码区是共享的&#xff0c;共享的目的是对于频繁被执行的程序&#xff0c;只…

在Centos Stream 9上Docker的实操教程(七) - Docker上实现MYSQL实现主从复制

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

第3章 作业(268EF)【网络安全】

第3章 作业【网络安全】 前言推荐第3章 作业268EF如何不用计算机计算求模 最后 前言 2023-6-19 15:49:17 以下内容源自《网络安全》 仅供学习交流使用 推荐 第2章 作业&#xff08;2456&#xff09;【网络安全】 第3章 作业 2 3.2什么是MAC? MAC&#xff1a;消息认证码…

网页设计实习周记范文5篇(合集)

网页设计实习周记(一) 本周主要是做网站维护更新。 网站要注意经常维护更新内容&#xff0c;保持内容的新鲜&#xff0c;不要一做好就放在那儿不变了&#xff0c;只有不断地给它补充新的内容&#xff0c;才能够吸引住浏览者。 通过目前的实习&#xff0c;在设计方面我感觉自己有…

探究设备管理系统在工业领域的应用

在现代工业领域&#xff0c;设备是生产过程中至关重要的组成部分。有效管理和维护设备对于保障生产的连续性、提高生产效率和降低成本至关重要。而设备管理系统的引入为企业提供了一种综合性的解决方案&#xff0c;能够全面监控、维护和优化设备的运行状态。本文将探讨设备管理…

Linux MySQL 备份与恢复 日志管理

数据库备份 备份策略 完全备份 每次备份都备份完整的数据库。 备份数据最大&#xff0c;每次都要完整备份。但是恢复最方便差异备份 只备份上一次完全备份后的更新数据。 第一次完整备份&#xff0c;之后备份第一次没有备份的内容增量备份 每次备份只备份上一次完全备份或增量备…