单片机——IIC协议与24C02

1、基础知识
1.1、IIC串行总线的组成及工作原理
I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
在这里插入图片描述
1.2、I2C总线的数据传输
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
1.3、模拟子程序
1.3.1、起始信号
实际上是严格按照时序进行编写的
在这里插入图片描述

Void I2CStart(void)
{       SDA = 1;//数据线为高电平
	SomeNop(  );//保持一段时间
	SCL = 1;//时钟线为高电平
	SomeNop(  );//保持一段时间
	SDA = 0;//数据线为低电平
	SomeNop(  );//保持一段时间
}

1.3.2、终止信号
在这里插入图片描述
同上面原理一样

void I2cStop(void)
{
	SDA = 0;
	SomeNop(  );
	SCL = 1;
	SomeNop(  );
	SDA = 1;
	SomeNop(  );
}

1.3.3应答I2C总线
在这里插入图片描述

void I2cACK(void)
{
	SDA = 0;
	SomeNop(  );
	SCL = 1;
	SomeNop(  );
	SCL = 0;
	SomeNop(  );
}

1.3.4非应答I2C总线
在这里插入图片描述

void I2cNOACK(void)
{
	SDA = 1;
	SomeNop(  );
	SCL = 1;
	SomeNop(  );
	SCL = 0;
	SomeNop(  );
}

2.程序
2.0、宏定义、头文件

#include<reg52.h>
#include<intrins.h>	  //_nop_
#define uchar unsigned char
#define uint unsigned int

sbit SCL=P2^1;
sbit SDA=P2^0;
sbit RS=P1^0;
sbit RW=P1^1;
sbit E=P2^5;
sbit duan=P2^6;
sbit wei =P2^7;
sbit RST=P2^4;

#define RS_data RS=1
#define RS_command RS=0
#define RW_read   RW=1
#define RW_write  RW=0
#define	 E_close  E=0
#define	 E_open  E=1
#define	  Data   P0

char   string[]={"wuliwuli:"} ;
char   string1[]={"clemmence:"};
uchar arr[8];
uchar arr1[8];
uchar dat[]={12,13,14,15};

2.1、延时部分

void delay(uint k)
{
 uint i,j;
for(i=0;i<k;i++)
  {
	for(j=0;j<113;j++)
  {
   ;
  }
  }
}

关于延时部分的详细程序解释见
链接: 单片机控制一盏灯的亮与灭程序解释

2.2、IIC协议的起始与终止,24c02的应答与非应答
这一部分的详细解释见本篇文章第一节。主要是根据时隙写的。

//基本模块设置
//开始状态
void  start()
{
SDA=1;
_nop_();
SCL=1;
_nop_();
SDA=0;
_nop_();
SCL=0;
_nop_();
}

//结束状态
void  end()
{
SDA=0;
_nop_();
SCL=1;
_nop_();
SDA=1;
_nop_();
}

//应答
void  ack()
{
SDA=0;
_nop_();
SCL=1;
_nop_();
SCL=0;
_nop_();
}


//非应答
void  noack()
{
SDA=1;
_nop_();
SCL=1;
_nop_();
SCL=0;
_nop_();
}

2.3、发送字节给2402
这个地方比较难理解,下面进行仔细讲解。
先逐行看程序

void sendbyte2402 (uchar dat )

定义含有返回值的函数,以及要发送的数据,为什么要有返回值呢。这是因为这段程序可能会发生错误,比如无法与设备通信或者写入数据出错等。通过设置返回值可以让调用者知道这段程序执行的结果,从而采取相应的措施或者纠正错误。

具体来说,在这段程序中,写入或读取数据时需要向设备发送一些命令,并检查设备是否回应了正确的应答信号,如果设备没有回应或者应答信号有误,程序就会出错。如果不设置返回值,调用者无法得知这段程序是否执行成功,也无法根据执行结果进行后续处理。因此,设置返回值可以提高程序的健壮性(鲁棒性)和可靠性。

uchar i;
 for(i=0;i<8;i++)	  //一字节等于8为二进制
 {

 }

进行循环传输字节,一字节是8为二进制

  SCL=0;
   _nop_();

在前面的基础知识中讲过,当时钟线为1时,是进行保持的,不能改变状态,所以为了能够写入数据,这里将时钟置为0。延时的目的是,确定时钟线已经被拉高,可以正确写入数据。

   SDA=dat>>7;
   _nop_();

dat 里面的8位2进制向右移动7位,赋值给SDA,那么SDA里面存的就是高位了。
延时的目的是以确保数据已经稳定传输。

 SCL=1;

拉高时钟线,确保状态不再变化,数据存起来了。

 dat=dat<<1;

dat进行左移,次高位变成了最高位,继续进行传输。

 SCL=0;

关于为什么加 scl=0;:在这段代码中,每次发送完一位数据后,需要将时钟线 scl 置为0,以便下次发送数据。由于后续还需要使用 scl,因此需要在每次使用 scl 前将其置为0,以避免上次时钟信号的干扰。循环结束后,需要再次将 scl 置为0,以避免在后续操作中发生意外的时钟信号。

向24C02发送字节的完整程序为

void sendbyte2402 (uchar dat )
{
 uchar i;
 for(i=0;i<8;i++) //一字节等于8为二进制
 {
   SCL=0;
   _nop_();
   SDA=dat>>7;
   _nop_();
   SCL=1;
   dat=dat<<1;
 }
   SCL=0;//时钟重置 
}

2.4、从24C02里面读取字节
有了发送数据知识的积累,现在我们来看从2402上读字节

uchar readbyte2402() //主体一个字节
{
 uchar i,dat=0;
 SDA=1;
 for (i=0;i<8;i++)
 {
   SCL=1;	  // 数据稳定,方便读取
   _nop_();	   
	dat<<=1;	//	左移移位并赋值给dat	
	_nop_();	  		
	dat=dat|SDA;
	 _nop_();	  
	 SCL=0;
	 _nop_();	   
 }
 return bat;
}

详细解释
uchar readbyte2402(): 定义了一个名为readbyte2402的函数,它没有输入参数,返回类型为uchar,即一个字节的无符号整数。
uchar i,dat=0;: 定义了两个变量i和dat,i用于计数,dat用于存储读取的数据,初始值为0。
SDA=1;: 将SDA引脚设置为高电平,即输入模式,准备读取数据。
if(i=0;i<8;i++): 循环8次,依次读取8个位。
SCL=1;: 将SCL引脚设置为高电平,读取数据前先将时钟线拉高,确保数据稳定。
nop();: 空指令,延时一段时间。
dat<<=1;: 左移移位,将已经读取到的数据向左移一位,为下一个数据位腾出位置。
nop();: 空指令,延时一段时间。
dat=dat|SDA;: 将当前读取到的SDA数据位与dat变量进行或运算,将结果存储在dat中。
nop();: 空指令,延时一段时间。
SCL=0;: 将SCL引脚拉低,数据线上的数据位已经读取完毕,准备读取下一个数据位。
nop();: 空指令,延时一段时间。
return dat;: 返回已经读取到的8位数据,即一个字节的数据。

2.5、将数据写入到24C02
参数解释

uchar *ptr,uchar addr,uchar n
uchar *ptr:指向一个uchar类型的指针,该指针指向一块内存区域,用于存储要写入24C02的数据。
uchar addr:表示24C02内存的地址,指示从哪个地址开始写入数据。
uchar n:表示要写入的数据长度,即写入多少个字节的数据。
sendbyte2402(0xa0);

这行代码是向 24C02 发送一个写命令,并指定设备地址为 0xa0。在 I2C 协议中,设备地址的最高位是固定为 0 的,其余七位由硬件接线或软件设置。在这个代码中,设备地址为 0xa0,也就是二进制的 1010000,其中最高位为 0。这个地址是由厂商定义的,用来识别 24C02 存储器芯片。发送完这个命令后,接下来的操作就是向 24C02 写入数据。
之后就是根据数据长度进行循环,给地址和指针发送数据,并且产生应答,每经过一次循环,地址和指针自加1。
将数据写入到24C02的完整代码为

//将数据写入到24C02
void write2402(uchar *ptr,uchar add, uchar n) //指针,地址,写入长度
{
uchar i;
start();
_nop_();
sendbyte2402(0xa0);
_nop_();
ack();
_nop_();	 //给2402发送写的命令 ,并应答

for(i=0;i<n;i++)
{
 _nop_();
sendbyte2402(add);
 _nop_();
 ack();
 _nop_();
sendbyte2402( *ptr);
_nop_();
 ack();
 _nop_();
 add++;
 ptr++;
}
end();
delay(10);
}

这里面调用了许多空字节进行延时。那么自己定义的子程序delay和nop的区别在于:
delay指令是用于延时的,它可以在程序中让CPU暂停执行一段时间,等待一段时间后再继续执行后续的指令。延时的时间长度可以通过给delay指令传递参数来指定,一般以毫秒或微秒为单位。delay指令会消耗CPU资源,因此需要慎重使用,避免过度延时导致程序响应变慢或卡死。
nop指令是空操作指令,它的作用是让CPU执行一条空指令,不进行任何操作,只是等待一个时钟周期后继续执行下一条指令。nop指令通常用于占位或者空转,以便程序的执行时间满足特定的要求。相比delay指令,nop指令的执行速度更快,不会消耗过多的CPU资源,但是无法精确控制时间长度。
2.6、使用I2C协议从24C02芯片中读取n个字节的数据

sendbyte2402(add);	 //告诉地址
noack();   //不应答就开始读数据

不应答就开始读取数据的意思是。应答表示24C02正在接受数据,如果接收器返回非应答信号,就说明接收器不准备接收数据,可以开始读取数据了。

sendbyte2402(0xa1);	

告诉芯片要读取命令

SCL=0;

在这个代码中,将时钟置0是为了防止在读完数据后,芯片还继续发送一些不必要的I2C命令。这个时候将时钟线拉低,芯片就无法再继续发送命令了,从而确保传输的安全性。

时钟线在正常的I2C通信过程中会有起伏和变化,但是在数据读取结束后,需要确保时钟线处于空闲状态(即没有起伏和变化),才能避免意外产生I2C命令。将时钟线拉低,就是让时钟线始终保持低电平,确保空闲状态。

I2C从24C02里面读数据的完整代码是

 //使用I2C协议从24C02芯片中读取n个字节的数据
 void read2402(uchar *ptr,uchar add, uchar n)
 {
start();
_nop_();
sendbyte2402(0xa0);
_nop_();
ack();
_nop_();


while(n) //循环n次,每次读取一个字节
{
sendbyte2402(add);	 //告诉地址
noack();   //不应答就开始读数据
_nop_();

start();
sendbyte2402(0xa1);	//告诉24C02,读命令	 告诉芯片要读取数据
ack();

*ptr=readbyte2402();
ack();
ptr++;
add++;
n--;//循环计数器 
}
SCL=0; 
stop();
 }

上面比较细节的地方,在这一节里面已经详细说过,下面介绍该段代码的整体逻辑。这段代码实现的功能是读取数据。
在读取数据前需要向24C02发送一个写命令,告诉它要从哪个地址读取数据。所以在读命令的地方需要先执行写命令,然后再发送读命令,这样24C02才能正确地读取数据并发送给主控芯片。

接下来进入循环,读取数据
首先是告诉地址,没有应答,表明这一会没有数据写入,所以是可以读取数据的。告诉24C02,要读取数据,产生应答,代表可以读数据。指针、地址自加1,循环次数自减1,完成一次读取。

2.7、数码管锁存

void cmg()
{
duan=1;
P0=0x00;
duan=0;

wei=1;
P0=0x00;
wei=0;

RST=0;	//关时钟DS1302
}

这一块解释见博文
链接: 单片机——数码管动态显示

2.8、LCD1602的写命令、读命令操作以及设置工作方式模块(初始化)

//LCD1602写命令
 void writecom(uchar command)
 {
 delay(10);
 RS_command;
 RW_write;
 E_open;
 Data=command;
 _nop_();
 E_close;
 }
 
 //LCD1602写数据
 void writedata(uchar da)
 {
 delay(10);
 RS_data;
 RW_write;
 E_open;
 Data=da;
 _nop_();
 E_close;
 }
 

 //LCD初始化
 void Init()
 {
 cmg();
 delay(15);
 writecom(0x38);//数据总线8位,显示两行。5×7
 writecom(0x38);
 writecom(0x38);

 writecom(0x0e);  //显示功能开,有光标,光标闪烁
 writecom(0x06); 		// 光标右移,显示屏不移动
 writecom(0x01); 	   //清屏
 }
 }


详细解释见博客
链接: 单片机——LCD1602

2.9、对数据进行处理
在程序的最开始我们定义了要显示的字符和要存储的数据。

char   string[]={"wuliwuli:"} ;
char   string1[]={"clemmence:"};
uchar arr[8];
uchar arr1[8];
uchar dat[]={12,13,14,15};

对bat数组里的四个元素进行处理,并放在arr中

 void culi()
 {
 arr[0]='0'+dat[0]/10;
 arr[1]='0'+dat[0]%10;
 arr[2]='0'+dat[1]/10;
 arr[3]='0'+dat[1]%10;
 arr[4]='0'+dat[2]/10;
 arr[5]='0'+dat[2]%10;
 arr[6]='0'+dat[3]/10;
 arr[7]='0'+dat[3]%10;
 }
 }

函数culi()是将bat数组中四个元素(即bat[0]、bat[1]、bat[2]和bat[3])转换成对应的两位数字,保存在arr数组中。每个元素先除以10,得到十位上的数字,然后对10取余数,得到个位上的数字。这样就将四个数值分别处理成了两位数字,便于在1602液晶屏上显示。
arr[0]=‘0’+bat[0]/10;
在这个代码中, ‘0’ 是一个 ASCII 码值,表示字符 “0”。‘0’ + arr1[0]/10 将 arr1[0] 的值除以 10,得到十位上的数字,然后将其转换为字符 “0” 到 “9” 中的一个。由于字符在计算机中以 ASCII 码的形式表示,“0” 的 ASCII 码值是 48,所以可以用 ‘0’ 加上任何数字来获得相应的 ASCII 码字符。


 void culi1()
 {
 arr[0]='0'+arr1[0]/10;
 arr[1]='0'+arr1[0]%10;
 arr[2]='0'+arr1[1]/10;
 arr[3]='0'+arr1[1]%10;
 arr[4]='0'+arr1[2]/10;
 arr[5]='0'+arr1[2]%10;
 arr[6]='0'+arr1[3]/10;
 arr[7]='0'+arr1[3]%10;
 }

arr1目前是一个空数组,待会会从24c02里面读数据,进行存储,所以同时也要将arr1处理并放置在arr数组中。
2.10、规定数据显示的位置显示的内容
2.10.1、规定显示的位置

 lcdpos(uchar line ,p)
{
uchar pos;
if(line==0)	 
pos=0x80;//写在第一行
if(line==1)	 
pos=0xc0;//写在第二行
pos=p+pos;
writecom(pos);
}

这段代码是用于将数据显示在16x2字符LCD上的。首先,定义一个长度为4的数组dat,存储要显示的数据。接下来是两个函数,lcd_pos函数用于设置显示位置,其中参数line表示行数,p表示列数。因为1602显示器有两行,每行可以显示16个字符,所以第一行地址从0x80开始,第二行地址从0xc0开始,列数是在此基础上加上一个偏移量。例如,line=0,p=5时,pos的值为0x85,表示第一行第6个字符位置。
2.10.2、显示数据

lcddat(uchar n,uchar *ptr)
{
uchar i;
for(i=0;i<n;i++)
{
writedata (*(ptr+i));
}
}

n是数据长度。
*(ptr+i)是指针操作,表示取出指针ptr指向的内存中的值,加上偏移量i。这里的偏移量i实际上是循环变量,用来控制遍历的数据位置。
在函数lcddat中,ptr指向的是一个数组,通过遍历数组元素,将每个元素的值写入1602显示器中。使用指针的好处在于,可以避免数组在函数参数传递时的复制,节省内存空间。同时,通过传递指针,可以在函数内部修改调用者传递进来的数据。

2.11、主函数

void main()
{
uchar i;
Init();		//初始化,准备进行显示
delay(100);

i=0;
lcdpos(0,0)	;//0行0列开始
lcddat(9,string+i);		//wuliwuli:,是九个字符,显示dis里面的值

i=0;
lcdpos(1,0)	;//1行0列开始
lcddat(9,string1+i);	

//写数据到24C02,给数据让1602显示,从2402里面读数据,再给1602显示
 while(1)
 {
   i=0;
   write2402(dat+i,0,4);

   culi();
   i=0;
   lcdpos(0,10);
   lcddat(8,arr+i);

   i=0;
   read2402(arr1+i,0,4);

   i=0;
   culi1();
   lcdpos(1,10);
   lcddat(8,arr+i);
 }
}

主函数的主要功能是这段代码的主要作用是将数据写入24C02存储器,读取该存储器中的数据并将其显示在LCD1602液晶屏上。主要是对前面函数的调用。大体步骤:
初始化并准备显示。这里定义了一个uchar类型的变量i,然后调用了初始化函数Init()和延时函数delay(),以准备显示。
在LCD1602上显示字符串。这里设置了光标的初始位置,然后在LCD1602上显示了两个字符串。第一个字符串是从变量dis中取出来的,并在第0行第0列开始显示,显示长度为9个字符。第二个字符串是从变量dis1中取出来的,并在第1行第0列开始显示,显示长度为9个字符。
将数据写入24C02存储器。这里使用了一个无限循环,不断地将数据写入24C02存储器。使用函数write2402(),将变量dat中的数据写入存储器的地址0,写入4个字节。
读取24C02存储器中的数据,并在LCD1602上显示。这里调用了culi()和culi1()函数,用于控制延时时间。然后使用函数read2402()从24C02存储器中读取数据,将其存储在变量arr1中,从地址0开始读取4个字节。最后,在LCD1602上显示变量arr中的8个字符。
3.完整代码

#include<reg52.h>
#include<intrins.h>	  //_nop_
#define uchar unsigned char
#define uint unsigned int

sbit SCL=P2^1;
sbit SDA=P2^0;
sbit RS=P1^0;
sbit RW=P1^1;
sbit E=P2^5;
sbit duan=P2^6;
sbit wei =P2^7;
sbit RST=P2^4;

#define RS_data RS=1
#define RS_command RS=0
#define RW_read   RW=1
#define RW_write  RW=0
#define	 E_close  E=0
#define	 E_open  E=1
#define	  Data   P0

char   string[]={"wuliwuli:"} ;
char   string1[]={"clemence:"};
uchar arr[8];
uchar arr1[8];
uchar dat[]={12,13,14,15};

// 延时部分
void delay(uint k)
{
 uint i,j;
for(i=0;i<k;i++)
  {
	for(j=0;j<113;j++)
  {
   ;
  }
  }
}

//基本模块设置
//开始状态
void  start()
{
SDA=1;
_nop_();
SCL=1;
_nop_();
SDA=0;
_nop_();
SCL=0;
_nop_();
}

//结束状态
void  end()
{
SDA=0;
_nop_();
SCL=1;
_nop_();
SDA=1;
_nop_();
}

//应答
void  ack()
{
SDA=0;
_nop_();
SCL=1;
_nop_();
SCL=0;
_nop_();
}


//非应答
void  noack()
{
SDA=1;
_nop_();
SCL=1;
_nop_();
SCL=0;
_nop_();
}

//发送字节给2402
void sendbyte2402 (uchar dat )
{
 uchar i;
 for(i=0;i<8;i++) //一字节等于8为二进制
 {
   SCL=0;
   _nop_();
   SDA=dat>>7;
   _nop_();
   SCL=1;
   dat=dat<<1;
 }
   SCL=0;//时钟重置 
}

//从2402里面读取字节
uchar readbyte2402() //主体一个字节
{
 uchar i;
 uchar dat=0;
 SDA=1;
 for (i=0;i<8;i++)
 {
   SCL=1;	  // 数据稳定,方便读取
   _nop_();	   
	dat<<=1;	//	左移移位并赋值给dat	
	_nop_();	  		
	dat=dat|SDA;
	 _nop_();	  
	 SCL=0;
	 _nop_();	   
 }
 return dat;
}

//将数据写入到24C02
void write2402(uchar *ptr,uchar add, uchar n) //指针,地址,写入长度
{
uchar i;
start();
_nop_();
sendbyte2402(0xa0);
_nop_();
ack();
_nop_();	 //给2402发送写的命令 ,并应答

for(i=0;i<n;i++)
{
 _nop_();
sendbyte2402(add);
 _nop_();
 ack();
 _nop_();
sendbyte2402( *ptr);
_nop_();
 ack();
 _nop_();
 add++;
 ptr++;
}
end();
delay(10);
}

 //使用I2C协议从24C02芯片中读取n个字节的数据
 void read2402(uchar *ptr,uchar add, uchar n)
 {
start();
_nop_();
sendbyte2402(0xa0);
_nop_();
ack();
_nop_();


while(n) //循环n次,每次读取一个字节
{
sendbyte2402(add);	 //告诉地址
noack();   //不应答就开始读数据
_nop_();

start();
sendbyte2402(0xa1);	//告诉24C02,读命令	 告诉芯片要读取数据
ack();

*ptr=readbyte2402();
ack();
ptr++;
add++;
n--;//循环计数器 
}
SCL=0; 
end();
 }


//数码管锁存
void cmg()
{
duan=1;
P0=0x00;
duan=0;

wei=1;
P0=0x00;
wei=0;

RST=0;	//关时钟DS1302
}


//LCD1602写命令
 void writecom(uchar command)
 {
 delay(10);
 RS_command;
 RW_write;
 E_open;
 Data=command;
 _nop_();
 E_close;
 }
 
 //LCD1602写数据
 void writedata(uchar da)
 {
 delay(10);
 RS_data;
 RW_write;
 E_open;
 Data=da;
 _nop_();
 E_close;
 }

 //LCD初始化
 void Init()
 {
 cmg();
 delay(15);
 writecom(0x38);//数据总线8位,显示两行。5×7
 writecom(0x38);
 writecom(0x38);

 writecom(0x0e);  //显示功能开,有光标,光标闪烁
 writecom(0x06); 		// 光标右移,显示屏不移动
 writecom(0x01); 	   //清屏
 }

 //对dat和arr1里面的数据进行处理
 void culi()
 {
 arr[0]='0'+dat[0]/10;
 arr[1]='0'+dat[0]%10;
 arr[2]='0'+dat[1]/10;
 arr[3]='0'+dat[1]%10;
 arr[4]='0'+dat[2]/10;
 arr[5]='0'+dat[2]%10;
 arr[6]='0'+dat[3]/10;
 arr[7]='0'+dat[3]%10;
 }

 void culi1()
 {
 arr[0]='0'+arr1[0]/10;
 arr[1]='0'+arr1[0]%10;
 arr[2]='0'+arr1[1]/10;
 arr[3]='0'+arr1[1]%10;
 arr[4]='0'+arr1[2]/10;
 arr[5]='0'+arr1[2]%10;
 arr[6]='0'+arr1[3]/10;
 arr[7]='0'+arr1[3]%10;
 }

//显示位置
 lcdpos(uchar line ,p)
{
uchar pos;
if(line==0)	 
pos=0x80;//写在第一行
if(line==1)	 
pos=0xc0;//写在第二行
pos=p+pos;
writecom(pos);
}

//显示数据
lcddat(uchar n,uchar *ptr)
{
uchar i;
for(i=0;i<n;i++)
{
writedata (*(ptr+i));
}
}

void main()
{
uchar i;
Init();		//初始化,准备进行显示
delay(100);

i=0;
lcdpos(0,0)	;//0行0列开始
lcddat(9,string+i);		//wuliwuli:,是九个字符,显示dis里面的值

i=0;
lcdpos(1,0)	;//1行0列开始
lcddat(10,string1+i);	

//写数据到24C02,给数据让1602显示,从2402里面读数据,再给1602显示
 while(1)
 {
   i=0;
   write2402(dat+i,0,4);

   culi();
   i=0;
   lcdpos(0,10);
   lcddat(8,arr+i);

   i=0;
   read2402(arr1+i,0,4);

   i=0;
   culi1();
   lcdpos(1,10);
   lcddat(8,arr+i);
 }
}

4.运行结果
在这里插入图片描述
ps:这里解释一下,忘记它是1602了,15没地方显示了。由于程序和文章都是边做边写的,所以说修改的话,上面的子程序也要跟着修改,所以就不想修改了。然后子程序,就是第二板块内容是边做边写的,所以说子程序是在没有运行的情况下写的,部分地方可能存在问题,在运行完之后也对子程序进行了修改,但是难免还是有一些错误。但是完整的程序,就是第三部分一定是对的,在连线和我端口定义一样的情况下,一定是可以做出来的。(我自己做不出来就放在草稿箱里,改天继续看)
本次的24C02的部分有些难度,它是1602,IIC协议和24C02的综合。较之于之前新添加的部分就是IIC协议的使用和向24C02发数据,24C02写数据,读24C02里面的数据等。
好啦,24C02的部分就到这里啦。如有疑问,评论区留言,如能帮助到您,点个关注呗。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux实操之进程管理

文章目录一、基本介绍二、显示系统执行的进程基本介绍三、ps详解四、终止进程kill和killall介绍:●基本语法常用选项五、查看进程树pstree基本语法常用选项一、基本介绍 1&#xff0e;在LINUX中&#xff0c;每个执行的程序都称为一个进程。每一个进程都分配一个ID号(pid,进程号…

【SCL】实现简单算法--冒泡排序

使用SCL语言实现一个冒泡排序的简单算法 文章目录 目录 文章目录 前言 二、实现排序 1.读取存储器地址&#xff08;PEEK&#xff09;指令 2.编写程序 总结 前言 本文我们来一起使用SCL来实现一个简单的算法——冒泡排序&#xff1b;它可以对少量数据进行从小到大或从大到小排序…

【Linux】GDB的安装与使用

安装安装gdb的具体步骤如下&#xff1a;1、查看当前gdb安装情况rpm -qa | grep gdb如果有&#xff0c;则可以先删除&#xff1a;rpm -e --nodeps 文件名如果没有&#xff0c;则进行下一步。2、下载gdb源码包或者直接apt安装。apt命令安装&#xff1a;sudo apt install gdb源码包…

Qt之QPainter绘制多个矩形/圆形(含源码+注释)

一、绘制示例图 下图绘制的是矩形对象&#xff0c;但是将绘制矩形函数&#xff08;drawRect&#xff09;更改为绘制圆形&#xff08;drawEllipse&#xff09;即可绘制圆形。 二、思路解释 绘制矩形需要自然要获取矩形数据&#xff0c;因此通过鼠标事件获取每个矩形的rect数…

一个完整的渗透学习路线是怎样的?如何成为安全渗透工程师?

前言 1/我是如何学习黑客和渗透&#xff1f; 我是如何学习黑客和渗透测试的&#xff0c;在这里&#xff0c;我就把我的学习路线写一下&#xff0c;让新手和小白们不再迷茫&#xff0c;少走弯路&#xff0c;拒绝时间上的浪费&#xff01; 2/学习常见渗透工具的使用 注意&…

SpringBoot集成 SpringSecurity安全框架

文章目录一、CSRF跨站请求伪造攻击二、项目准备三、认识 SpringSecurity3.1 认证&#x1f380;①直接认证&#x1f380;②使用数据库认证3.2 授权&#x1f361;①基于角色授权&#x1f361;②基于权限的授权&#x1f361;③使用注解判断权限3.3 "记住我"3.4 登录和注…

【JavaEE】如何将JavaWeb项目部署到Linux云服务器?

写在前面 大家好&#xff0c;我是黄小黄。不久前&#xff0c;我们基于 servlet 和 jdbc 完善了博客系统。本文将以该系统为例&#xff0c;演示如何将博客系统部署到 Linux 云服务器。 博客系统传送门&#xff1a; 【JavaEE】前后端分离实现博客系统&#xff08;页面构建&#…

arcpy基础篇(3)-处理空间数据

ArcPy的数据访问模块arcpy.da&#xff0c;可以控制会话、编辑操作、游标、表或要素类与NumPy数组之间相互转换的函数以及对版本化和复本工作流的支持。 1.使用游标访问数据 游标是一个数据库术语&#xff0c;它主要用于访问表格中的每一行记录或者向表中插入新的记录。在ArcG…

【数据结构】Java实现单链表

目录 1. ArrayList的缺陷 2. 链表 2.1 链表的概念及结构 2.2 接口的实现 3. 动手实现单链表 3.1 重写SeqList接口方法 3.2 在当前链表头部添加节点&#xff08;头插&#xff09; 3.3 在 第index位置添加节点&#xff08;任意位置&#xff09; 3.4 在当前链表尾部添加…

Python|蓝桥杯进阶第四卷——图论

欢迎交流学习~~ 专栏&#xff1a; 蓝桥杯Python组刷题日寄 蓝桥杯进阶系列&#xff1a; &#x1f3c6; Python | 蓝桥杯进阶第一卷——字符串 &#x1f50e; Python | 蓝桥杯进阶第二卷——贪心 &#x1f49d; Python | 蓝桥杯进阶第三卷——动态规划 ✈️ Python | 蓝桥杯进阶…

四、快速上手 ODM 操作 Mongodb

文章目录一、ODM 的选择和安装二、MongoEngine 模型介绍三、文档的嵌套模型四、使用 ODM 查询数据4.1 查询一个文档4.2 条件查询4.3 统计、排序和分页五、使用 ODM 新增数据六、使用 ODM 修改和删除数据一、ODM 的选择和安装 MongoEngine&#xff1a; 使用最为广泛的 ODM。htt…

【C++】命名空间

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、命名空间产生的背景二、命名空…

基础篇:07-Nacos注册中心

1.Nacos安装部署 1.1 下载安装 nacos官网提供了安装部署教程&#xff0c;其下载链接指向github官网&#xff0c;选择合适版本即可。如访问受阻可直接使用以下最新稳定版压缩包&#xff1a;&#x1f4ce;nacos-server-2.1.0.zip&#xff0c;后续我们也可能会更改为其他版本做更…

图论学习(五)

极图 l部图的概念与特征 定义&#xff1a;若简单图G的点集V有一个划分&#xff1a; 且所有的Vi非空&#xff0c;Vi内的点均不邻接&#xff0c;设G是一个l部图。 如果l2&#xff0c;则G就是偶图。n阶无环图必是n部图。若l1<l2≤n&#xff0c;则任意的l1部图也是l2部图。…

【毕业设计】基于SpringBoot+Vue论坛管理系统【源码(完整源码请私聊)+论文+演示视频+包运行成功】

您好&#xff0c;我是码农飞哥&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通 &#x1f601; 2. 毕业设计专栏&…

JavaScript学习笔记(7.0)

<!--* Author: RealRoad1083425287qq.com* Date: 2023-03-13 14:50:18* LastEditors: Mei* LastEditTime: 2023-03-13 15:08:54* FilePath: \vscode\鼠标跟随.html* Description: * * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. --> <!DOCTYPE …

Vue3(递归组件) + 原生Table 实现树结构复杂表格

一、递归组件 什么是递归&#xff0c;Javascript中经常能接触到递归函数。也就是函数自己调用自己。那对于组件来说也是一样的逻辑。平时工作中见得最多应该就是菜单组件&#xff0c;大部分系统里面的都是递归组件。文章中我做了按需引入的配置&#xff0c;所以看不到我引用组…

什么是让ChatGPT爆火的大语言模型(LLM)

什么是让ChatGPT爆火的大语言模型(LLM) 更多精彩内容: https://www.nvidia.cn/gtc-global/?ncidref-dev-876561 文章目录什么是让ChatGPT爆火的大语言模型(LLM)大型语言模型有什么用&#xff1f;大型语言模型如何工作&#xff1f;大型语言模型的热门应用在哪里可以找到大型语言…

西安石油大学C语言期末真题实战

很简单的一道程序阅读题&#xff0c;pa’默认为a【0】&#xff0c;接下来会进行3次循环 0 1 2 输出结果即可 前3题就是一些基础定义&#xff0c;在此不多赘述 要注意不同的数据类型的字节数不同 a<<2 b>>1&#xff08;b>>1;就是说b自身右位移一位&#xff08…

支付系统设计:消息重试组件封装

文章目录前言一、重试场景分析一、如何实现重试1. 扫表2. 基于中间件自身特性3. 基于框架4. 根据公司业务特性自己实现的重试二、重试组件封装1. 需求分析2. 模块设计2.1 持久化模块1. 表定义2. 持久化接口定义3. 持久化配置类2.2 重试模块1.启动2.重试3. 业务端使用1. 引入依赖…
最新文章