网站建设宽度一般都是多少钱,企业智能网站后台管理系统,用什么软件写网站,网站建设入门1、基础知识 1.1、IIC串行总线的组成及工作原理 I2C总线只有两根双向信号线。一根是数据线SDA#xff0c;另一根是时钟线SCL。 1.2、I2C总线的数据传输 I2C总线进行数据传送时#xff0c;时钟信号为高电平期间#xff0c;数据线上的数据必须保持稳定#xff0c;只有在时钟…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、宏定义、头文件
#includereg52.h
#includeintrins.h //_nop_
#define uchar unsigned char
#define uint unsigned intsbit SCLP2^1;
sbit SDAP2^0;
sbit RSP1^0;
sbit RWP1^1;
sbit EP2^5;
sbit duanP2^6;
sbit wei P2^7;
sbit RSTP2^4;#define RS_data RS1
#define RS_command RS0
#define RW_read RW1
#define RW_write RW0
#define E_close E0
#define E_open E1
#define Data P0char 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(i0;ik;i){for(j0;j113;j){;}}
}
关于延时部分的详细程序解释见 链接: 单片机控制一盏灯的亮与灭程序解释
2.2、IIC协议的起始与终止24c02的应答与非应答 这一部分的详细解释见本篇文章第一节。主要是根据时隙写的。
//基本模块设置
//开始状态
void start()
{
SDA1;
_nop_();
SCL1;
_nop_();
SDA0;
_nop_();
SCL0;
_nop_();
}//结束状态
void end()
{
SDA0;
_nop_();
SCL1;
_nop_();
SDA1;
_nop_();
}//应答
void ack()
{
SDA0;
_nop_();
SCL1;
_nop_();
SCL0;
_nop_();
}//非应答
void noack()
{
SDA1;
_nop_();
SCL1;
_nop_();
SCL0;
_nop_();
}2.3、发送字节给2402 这个地方比较难理解下面进行仔细讲解。 先逐行看程序
void sendbyte2402 (uchar dat )定义含有返回值的函数以及要发送的数据为什么要有返回值呢。这是因为这段程序可能会发生错误比如无法与设备通信或者写入数据出错等。通过设置返回值可以让调用者知道这段程序执行的结果从而采取相应的措施或者纠正错误。
具体来说在这段程序中写入或读取数据时需要向设备发送一些命令并检查设备是否回应了正确的应答信号如果设备没有回应或者应答信号有误程序就会出错。如果不设置返回值调用者无法得知这段程序是否执行成功也无法根据执行结果进行后续处理。因此设置返回值可以提高程序的健壮性鲁棒性和可靠性。
uchar i;for(i0;i8;i) //一字节等于8为二进制{}进行循环传输字节一字节是8为二进制 SCL0;_nop_();在前面的基础知识中讲过当时钟线为1时是进行保持的不能改变状态所以为了能够写入数据这里将时钟置为0。延时的目的是确定时钟线已经被拉高可以正确写入数据。 SDAdat7;_nop_();dat 里面的8位2进制向右移动7位赋值给SDA那么SDA里面存的就是高位了。 延时的目的是以确保数据已经稳定传输。 SCL1;拉高时钟线确保状态不再变化数据存起来了。 datdat1;dat进行左移次高位变成了最高位继续进行传输。 SCL0;关于为什么加 scl0;在这段代码中每次发送完一位数据后需要将时钟线 scl 置为0以便下次发送数据。由于后续还需要使用 scl因此需要在每次使用 scl 前将其置为0以避免上次时钟信号的干扰。循环结束后需要再次将 scl 置为0以避免在后续操作中发生意外的时钟信号。
向24C02发送字节的完整程序为
void sendbyte2402 (uchar dat )
{uchar i;for(i0;i8;i) //一字节等于8为二进制{SCL0;_nop_();SDAdat7;_nop_();SCL1;datdat1;}SCL0;//时钟重置
}2.4、从24C02里面读取字节 有了发送数据知识的积累现在我们来看从2402上读字节
uchar readbyte2402() //主体一个字节
{uchar i,dat0;SDA1;for (i0;i8;i){SCL1; // 数据稳定方便读取_nop_(); dat1; // 左移移位并赋值给dat _nop_(); datdat|SDA;_nop_(); SCL0;_nop_(); }return bat;
}详细解释 uchar readbyte2402(): 定义了一个名为readbyte2402的函数它没有输入参数返回类型为uchar即一个字节的无符号整数。 uchar i,dat0;: 定义了两个变量i和dati用于计数dat用于存储读取的数据初始值为0。 SDA1;: 将SDA引脚设置为高电平即输入模式准备读取数据。 if(i0;i8;i): 循环8次依次读取8个位。 SCL1;: 将SCL引脚设置为高电平读取数据前先将时钟线拉高确保数据稳定。 nop();: 空指令延时一段时间。 dat1;: 左移移位将已经读取到的数据向左移一位为下一个数据位腾出位置。 nop();: 空指令延时一段时间。 datdat|SDA;: 将当前读取到的SDA数据位与dat变量进行或运算将结果存储在dat中。 nop();: 空指令延时一段时间。 SCL0;: 将SCL引脚拉低数据线上的数据位已经读取完毕准备读取下一个数据位。 nop();: 空指令延时一段时间。 return dat;: 返回已经读取到的8位数据即一个字节的数据。
2.5、将数据写入到24C02 参数解释
uchar *ptr,uchar addr,uchar nuchar *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(i0;in;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); 告诉芯片要读取命令
SCL0;在这个代码中将时钟置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();*ptrreadbyte2402();
ack();
ptr;
add;
n--;//循环计数器
}
SCL0;
stop();}上面比较细节的地方在这一节里面已经详细说过下面介绍该段代码的整体逻辑。这段代码实现的功能是读取数据。 在读取数据前需要向24C02发送一个写命令告诉它要从哪个地址读取数据。所以在读命令的地方需要先执行写命令然后再发送读命令这样24C02才能正确地读取数据并发送给主控芯片。
接下来进入循环读取数据 首先是告诉地址没有应答表明这一会没有数据写入所以是可以读取数据的。告诉24C02要读取数据产生应答代表可以读数据。指针、地址自加1循环次数自减1完成一次读取。
2.7、数码管锁存
void cmg()
{
duan1;
P00x00;
duan0;wei1;
P00x00;
wei0;RST0; //关时钟DS1302
}这一块解释见博文 链接: 单片机——数码管动态显示
2.8、LCD1602的写命令、读命令操作以及设置工作方式模块初始化
//LCD1602写命令void writecom(uchar command){delay(10);RS_command;RW_write;E_open;Datacommand;_nop_();E_close;}//LCD1602写数据void writedata(uchar da){delay(10);RS_data;RW_write;E_open;Datada;_nop_();E_close;}//LCD初始化void Init(){cmg();delay(15);writecom(0x38);//数据总线8位显示两行。5×7writecom(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]0dat[0]/10;arr[1]0dat[0]%10;arr[2]0dat[1]/10;arr[3]0dat[1]%10;arr[4]0dat[2]/10;arr[5]0dat[2]%10;arr[6]0dat[3]/10;arr[7]0dat[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]0arr1[0]/10;arr[1]0arr1[0]%10;arr[2]0arr1[1]/10;arr[3]0arr1[1]%10;arr[4]0arr1[2]/10;arr[5]0arr1[2]%10;arr[6]0arr1[3]/10;arr[7]0arr1[3]%10;}arr1目前是一个空数组待会会从24c02里面读数据进行存储所以同时也要将arr1处理并放置在arr数组中。 2.10、规定数据显示的位置显示的内容 2.10.1、规定显示的位置 lcdpos(uchar line ,p)
{
uchar pos;
if(line0)
pos0x80;//写在第一行
if(line1)
pos0xc0;//写在第二行
posppos;
writecom(pos);
}这段代码是用于将数据显示在16x2字符LCD上的。首先定义一个长度为4的数组dat存储要显示的数据。接下来是两个函数lcd_pos函数用于设置显示位置其中参数line表示行数p表示列数。因为1602显示器有两行每行可以显示16个字符所以第一行地址从0x80开始第二行地址从0xc0开始列数是在此基础上加上一个偏移量。例如line0p5时pos的值为0x85表示第一行第6个字符位置。 2.10.2、显示数据
lcddat(uchar n,uchar *ptr)
{
uchar i;
for(i0in;i)
{
writedata (*(ptri));
}
}n是数据长度。 *(ptri)是指针操作表示取出指针ptr指向的内存中的值加上偏移量i。这里的偏移量i实际上是循环变量用来控制遍历的数据位置。 在函数lcddat中ptr指向的是一个数组通过遍历数组元素将每个元素的值写入1602显示器中。使用指针的好处在于可以避免数组在函数参数传递时的复制节省内存空间。同时通过传递指针可以在函数内部修改调用者传递进来的数据。
2.11、主函数
void main()
{
uchar i;
Init(); //初始化准备进行显示
delay(100);i0;
lcdpos(0,0) ;//0行0列开始
lcddat(9,stringi); //wuliwuli:,是九个字符显示dis里面的值i0;
lcdpos(1,0) ;//1行0列开始
lcddat(9,string1i); //写数据到24C02给数据让1602显示从2402里面读数据再给1602显示while(1){i0;write2402(dati,0,4);culi();i0;lcdpos(0,10);lcddat(8,arri);i0;read2402(arr1i,0,4);i0;culi1();lcdpos(1,10);lcddat(8,arri);}
}主函数的主要功能是这段代码的主要作用是将数据写入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.完整代码
#includereg52.h
#includeintrins.h //_nop_
#define uchar unsigned char
#define uint unsigned intsbit SCLP2^1;
sbit SDAP2^0;
sbit RSP1^0;
sbit RWP1^1;
sbit EP2^5;
sbit duanP2^6;
sbit wei P2^7;
sbit RSTP2^4;#define RS_data RS1
#define RS_command RS0
#define RW_read RW1
#define RW_write RW0
#define E_close E0
#define E_open E1
#define Data P0char 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(i0;ik;i){for(j0;j113;j){;}}
}//基本模块设置
//开始状态
void start()
{
SDA1;
_nop_();
SCL1;
_nop_();
SDA0;
_nop_();
SCL0;
_nop_();
}//结束状态
void end()
{
SDA0;
_nop_();
SCL1;
_nop_();
SDA1;
_nop_();
}//应答
void ack()
{
SDA0;
_nop_();
SCL1;
_nop_();
SCL0;
_nop_();
}//非应答
void noack()
{
SDA1;
_nop_();
SCL1;
_nop_();
SCL0;
_nop_();
}//发送字节给2402
void sendbyte2402 (uchar dat )
{uchar i;for(i0;i8;i) //一字节等于8为二进制{SCL0;_nop_();SDAdat7;_nop_();SCL1;datdat1;}SCL0;//时钟重置
}//从2402里面读取字节
uchar readbyte2402() //主体一个字节
{uchar i;uchar dat0;SDA1;for (i0;i8;i){SCL1; // 数据稳定方便读取_nop_(); dat1; // 左移移位并赋值给dat _nop_(); datdat|SDA;_nop_(); SCL0;_nop_(); }return dat;
}//将数据写入到24C02
void write2402(uchar *ptr,uchar add, uchar n) //指针地址写入长度
{
uchar i;
start();
_nop_();
sendbyte2402(0xa0);
_nop_();
ack();
_nop_(); //给2402发送写的命令 并应答for(i0;in;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();*ptrreadbyte2402();
ack();
ptr;
add;
n--;//循环计数器
}
SCL0;
end();}//数码管锁存
void cmg()
{
duan1;
P00x00;
duan0;wei1;
P00x00;
wei0;RST0; //关时钟DS1302
}//LCD1602写命令void writecom(uchar command){delay(10);RS_command;RW_write;E_open;Datacommand;_nop_();E_close;}//LCD1602写数据void writedata(uchar da){delay(10);RS_data;RW_write;E_open;Datada;_nop_();E_close;}//LCD初始化void Init(){cmg();delay(15);writecom(0x38);//数据总线8位显示两行。5×7writecom(0x38);writecom(0x38);writecom(0x0e); //显示功能开有光标光标闪烁writecom(0x06); // 光标右移显示屏不移动writecom(0x01); //清屏}//对dat和arr1里面的数据进行处理void culi(){arr[0]0dat[0]/10;arr[1]0dat[0]%10;arr[2]0dat[1]/10;arr[3]0dat[1]%10;arr[4]0dat[2]/10;arr[5]0dat[2]%10;arr[6]0dat[3]/10;arr[7]0dat[3]%10;}void culi1(){arr[0]0arr1[0]/10;arr[1]0arr1[0]%10;arr[2]0arr1[1]/10;arr[3]0arr1[1]%10;arr[4]0arr1[2]/10;arr[5]0arr1[2]%10;arr[6]0arr1[3]/10;arr[7]0arr1[3]%10;}//显示位置lcdpos(uchar line ,p)
{
uchar pos;
if(line0)
pos0x80;//写在第一行
if(line1)
pos0xc0;//写在第二行
posppos;
writecom(pos);
}//显示数据
lcddat(uchar n,uchar *ptr)
{
uchar i;
for(i0;in;i)
{
writedata (*(ptri));
}
}void main()
{
uchar i;
Init(); //初始化准备进行显示
delay(100);i0;
lcdpos(0,0) ;//0行0列开始
lcddat(9,stringi); //wuliwuli:,是九个字符显示dis里面的值i0;
lcdpos(1,0) ;//1行0列开始
lcddat(10,string1i); //写数据到24C02给数据让1602显示从2402里面读数据再给1602显示while(1){i0;write2402(dati,0,4);culi();i0;lcdpos(0,10);lcddat(8,arri);i0;read2402(arr1i,0,4);i0;culi1();lcdpos(1,10);lcddat(8,arri);}
}4.运行结果 ps这里解释一下忘记它是1602了15没地方显示了。由于程序和文章都是边做边写的所以说修改的话上面的子程序也要跟着修改所以就不想修改了。然后子程序就是第二板块内容是边做边写的所以说子程序是在没有运行的情况下写的部分地方可能存在问题在运行完之后也对子程序进行了修改但是难免还是有一些错误。但是完整的程序就是第三部分一定是对的在连线和我端口定义一样的情况下一定是可以做出来的。我自己做不出来就放在草稿箱里改天继续看 本次的24C02的部分有些难度它是1602IIC协议和24C02的综合。较之于之前新添加的部分就是IIC协议的使用和向24C02发数据24C02写数据读24C02里面的数据等。 好啦24C02的部分就到这里啦。如有疑问评论区留言如能帮助到您点个关注呗。