TMOD=0x20; TL1=0xff; TH1=0xff; SCON=0x50; PCON=0x80;
IE=0x90; TR1=1; EA=1; }
void getch(void) interrupt 4 { RI=0;
SBUF =SBUF;//接收寄存器的值赋给发送寄存器 while(TI==0)//等待发送结束 {; } TI=0; }
void main(void) {
initmpu(); while(1)
{ } }
调试助手设置 :(57600,e,8,1)
上面这个程序实现了57600波特率下中断方式的通讯,但是有一句话很怪异:SBUF=SBUF;前面提到过,SBUF是两个寄存器的名字,一个是发送缓冲寄存器,一个是接收缓冲寄存器,但两个寄存器物理上独立。发送的只能发送,接收的只能接受。由硬件识别那个是发送的那个是接收的,所以这句话是合法的,也是有效的,因为省去了一条语句,节省了中断函数所用的时间。
实际上,上面这个程序没有什么实际意义,因为它把计算机送给他的数没有经任何处理又还给了计算机。这里只是为了验正以前出错的原因而设计的一个验证性实验。
好了,串口通讯实验告一段落了。由于本作者水平实在不高,错误是难免的,描述不清也是难免的,当你看着感觉好理解的地方多半会是我从平凡老师的教程上copy下来得,你感觉难以理解的语句,可能多半是我的“杰作”但我最终还是想让这篇文档可以缩短你上手的时间,尤其是你在根本没接触过单片机的情况下。(和我前两周一样)前面提到了一个串口调试助手的停止位的问题,我还没有弄清楚它到底怎样给程序产生的影响,我想要弄明白这个问题恐怕要亲手做以下串口调试助手。接下来,如果你有兴趣和时间,你也可以亲手做这个串口调试助手。祝你在以后的工作中事事顺利!
请注意:本文档所在的文件夹中装有本文档的九个实验程序,九个程序的功能和结果本文档已指出,故不再各个程序中叙述,文件夹中的实验序号和本文档中的序号是对应着的。
AT89C51串口编程
前言
本文档是为单片机初学者写的有关串口通讯编程的说明文档。使用的单片机硬件是最通用的AT89C51单片机,编程语言为c语言。本文档不是系统的介绍单片机知识的教程,而是为了使您尽快掌握串口编程方法的技术说明。
本文档前几部分大量内容摘自平凡老师的单片机教程,此教程是单片机入门的良好教材,但由于那本教程是由汇编语言描述的,对于时间不充足的同学来说,学习汇编会浪费一些时间,所以我还是整理了我们要了解的内容写到了本文档中。对于时间比较充分,也有兴趣学习汇编语言的同学可以先阅读平凡老师的“单片机教程”,然后从本文档第六部分看起。该教程网址:http://www.21icsearch.com/pmcu/dpjjx/ndpjjx2.htm 我也已经下载了此教程,存在本实验室电脑中。
单片机的基本认识
一台能够工作的计算机要有这样几个部份构成:CPU(进行运算、控制)、RAM(数据存储)、ROM(程序存储)、输入/输出设备(例如:串行口、并行输出口等)。在个人计算机上这些部份被分成若干块芯片,安装一个称之为主板的印刷线路板上。而在单片机中,这些部份,全部被做到一块集成电路芯片中了,所以就称为单片(单芯片)机,而且有一些单片机中除了上述部份外,还集成了其它部份如A/D,D/A等。
天!电脑中的CPU一块就要卖上千块钱,这么多东西做在一起,还不
得买个天价!再说这块芯片也得非常大了。
不,价格并不高,从几元人民币到几十元人民币,体积也不大,一般用40脚封装,当然功能多一些单片机也有引脚比较多的,如68引脚,功能少的只有10多个或20多个引脚,有的甚至只8只引脚。 为什么会这样呢?
功能有强弱,另外这种芯片的生产量很大,技术也很成熟,51系列的单片机已经做了十几年,所以价格就低了。
既然如此,单片机的功能肯定不强,干吗要学它呢?
话不能这样说,实际工作中并不是任何需要计算机的场合都要求计算机有很高的性能,一个控制电冰箱温度的计算机难道要用P4?应用的关键是看是否够用,是否有很好的性能价格比。
实现一个发光二极管的闪烁体会对单片机的c语言编程
买回来一块c51单片机,要想使用它 首先要做必要的连线。我们实验室通常会给我们提供已经接好线的板子,所以我们不必自己去连线,这里需要说明的一点是,一块单片机要工作起来首先需要复位,所谓复位就是在单片机的RST引脚持续的加上两个机器周期的高电平,使单片机回到工作的状态。(机器周期在串口波特率的计算中介绍)我做实验时使用的板子是一块较复杂的板子的一部分,那块板子的复位方式是其他器件的程序给单片机复位,可是我做实验时那个器件不需要工作,所以,单片机一直不能复位。如果你遇到这种情况,请按任何一本书上介绍的加电复位选择合适的电容和电阻,按书上的连线图焊接好,如果你用的是我以前用过的板子,这块板子已经接好了复位电路,无须多考虑。请打开一本单片机的书,找到单片机的引脚图。我们用的板子在p1.0脚接有一个发光二极管,当p1.0为高电压时,二极管点亮,为低电压时,二极管熄灭。我们现在想让小灯每隔0.5s闪烁一次,实际上就是要灯亮0.5s,再灭0.5s,也就是说要P10不断地输出高和低电平。
怎样实现这个要求呢?我们首先给出程序,然后对照程序分析。 实验1//程序开始
#include
sbit P10=P1^0; //sbit是单片机c程序新的关键字,用于定义位变量 void Delay(uint i)//延时程序,i是时间参数 { uint j; for(;i>0;i--) for(j=0;j<125;j++) {;} }
void main() { for(;;) {
P10=0; //单片机内部给p10脚加低电平,关闭小灯 Delay(500);//延时0.5s
P10=1; //单片机内部给p10脚加高电平,点亮小灯 Delay(500); //延时 0.5S } }
在以上程序中,我们可以看出,p10=1这条语句的含义就是p10加高电压,这个高电压不是1v,而是5v。p10=0则是p10加低电压。为什么是p10=1是给p10加5v电压而不是1v,我们不用管。现在从主函数看起,主函数内部是个for循环,此循环是让程序不停的在循环体内部转圈。循环体中,首先让灯暗,然后调用延时函数delay,此函数的作用是延时0.5s,然后点亮小灯,然后又是延时,延时完了进入下一次的循环……这样,程序就每隔0.5s就给p10加一次电压,加的电压高低更替,并且永远循环下去。其结果是让小灯不停的闪烁。Delay函数纯粹是用软件的运行来消耗时间以达到延时的目的,至于为什么参数是500时,延时为0.5s,以后再谈。
写好了程序,下一步我们的任务是把程序烧写到单片机的存储器中去,但是单片机是不认识c语言的,所以需要我们预先编译成单片机认识的.hex格式文件,此步骤,用keil c软件。然后将.hex文件用编程器写到单片机里,把单片机插回板子,加上电,就可以看到小灯在闪烁了。Keil c和编程器的用法语言描述不太直观,如果您没有用过,建议您现场请教实验室其他同学。
三、几个基本概念
1、位的含义
通过上面的实验我们已经知道:一盏灯亮或者说一根线的电平的高低,可以代表两种状态:0和1。实际上这就是一个二进制位,因此我们就把一根线称之为一“位”,用BIT表示。上面程序中的语句sbit P10=P1^0中,sbit就是定义位变量的新的关键字。 2、字节的含义
一根线可以表于0和1,两根线可以表达00,01,10,11四种状态,也就是可以表于0到3,而三根可以表达0-7,计算机中通常用8根线放在一起,同时计数,就可以表过到0-255一共256种状态。这8根线或者8位就称之为一个字节(BYTE)。为什么一个字节是8位数而不是其它数,这只是人为地规定。 3、存储器简介
存储器就是用来存放数据的地方。它是利用电平的高低来存放数据的,也就是说,它存放的实际上是电平的高、低,而不是我们所习惯认为的1234这样的数字。
存储器按功能可以分为只读和随机存取存储器两大类。所谓只读,从字面上理解就是只可以从里面读,不能写进去,它类似于我们的书本,发到我们手回之后,我们只能读里面的内容,不可以随意更改书本上的内容。只读存储器的英文缩写为ROM(READ ONLY MEMORY)我们只有用编程器对之进行编程。
所谓随机存取存储器,即随时可以改写,也可以读出里面的数据,它类似于我们的黑板,我可以随时写东西上去,也可以用黑板擦擦掉重写。随机存储
有关串口通讯试验的几个问题的思考 前一章中用的是串口的方式1,即无奇偶校验位,如果想要加进奇偶校验,就要采用方式2或方式三。本实验采用方式三。
前一章的实验全是用的是9600的波特率,这是为了入门的方便。在实际的工作中对波特率是有更高要求的。单片机串口通讯的波特率高低对整个系统性能有着重要影响。所以,能获得多高的稳定波特率、怎样获得较高的稳定的波特率是接下来要讨论的。
? 用查询方式实现57600的波特率(加奇偶校验)
奇偶校验是为了发送接收数据的过程中不出错的一种设置。其基本思想是:计算出发送的每一帧数据“1”的个数,如果为奇数个,则校验位置1,如果为偶数个,则校验位置0,这样每帧数据加上自己的校验位1的个数都是偶数个。此种方式叫偶校验。反之,是奇校验。在单片机中,硬件自己可以进行奇偶校验。单片机的PSW寄存器的P位就是用来做奇偶校验的。当累加寄存器ACC(是八位寄存器)中1的个数是奇数时,P的值是1,当ACC中1的个数是偶数时,P的值是0。这样其实硬件自己完成了偶校验。 1 0 0 1 0 1 0 1 累加器ACC示意表
当ACC中的值按如上设置时,PSW中的P位应是0。我们前面提到过,串口控制寄存器SCON中的TB8位用于发送校验位,只要把硬件统计出来的校验值赋给TB8就可以了。C语句实现如下:
ACC=SBUF; TB8=P;
奇偶校验中每一帧数据格式如下: 起数。。。 。。。 。。。 。。。 。。。 。。。 数校停始 据 其中的校验位就是TB8的数据。
据 验 止 奇偶校验的实现过程是这样的(以偶校验为例):串口调试助手每发送一个字节(8位)数据,就统计这八位中1的个数,并且自己记录下。单片机收到这个字节的8位数据后,把它放进ACC,然后把P的值给发送的第九位TB8,串口调试助手接收到单片机送回的一帧数据后,检测TB8,看是否和自己刚才计算的校验值相等,如相等,则此帧数据发送,回显正确;如不等,则回显是错误的。
好了,有了这些,请看以下程序: 实验七
#include
void main(void)//波特率57600,加偶校验,采用查询方式 {
TMOD=0x20;//定时器1方式2 TL1=0xff;//置数,波特率57600 TH1=0xff;
SCON=0xd8;//串口工作方式3有奇偶校验 PCON=0x80;//波特率加倍 TR1=1;//启动定时器
while(1)//无限循环 {
while(RI==0)//等待接收完毕 {
}
RI=0;//清除接收标志
ACC=SBUF;//将接收到的数据送给累加器ACC
TB8=P;//将校验位赋给要发送的第九位
SBUF=ACC;//向pc发送刚接收到的数据
while(TI==0)//等待发送结束 {
}
TI=0;//清零结束标志
} }
调试助手设置 :(57600,e,8,1)
经调试,查询方式可以达到57600的波特率,并且可以加奇偶校验,这就是说,比以前的9600的波特率通讯速度提高了6倍,以前要发送6分钟的工作现在只要1分中就可以了。请看下面的用中断作的程序:
实验八
#pragma small #include
void initmpu(void)//串口初始化 {
TMOD=0x20;
TL1=0xff; //波特率57600 TH1=0xff;
SCON=0xd8; //方式3,有偶校验
}
PCON=0x80; //波特率加倍
IE=0x90; TR1=1;
void getch(void) interrupt 4 //中断函数 {
RI=0;
ACC=SBUF;//加偶校验
TB8=P; SBUF=ACC;
while(TI==0)//等待发送结束 { }
TI=0; }
void main(void) {
initmpu();//调用初始化函数 while(1)//空循环
{
}
}
调试助手设置 :(57600,e,8,1)
大家请注意:上面的程序不能正常工作!因为在向串口送数,回显的过程中总会丢失字节.比如发送字符串:abcdefghijklmn接收到的总是少字符.这时候,如果把串口调试助手的停止位设置为2则可以正常回显!经过实验,把波特率降低,也就是说降到19200或28800,也可以正常工作.很怪!这个问题困扰了我好几天.后来和学长多次讨论得出以下原因:计算机向串口发数据是连续发的,每位数据之间的时间间隔是1/57600s,每帧数据之间的时间间隔也是1/57600s(大概也就是17us)当接收完一帧数据发生中断时,单片机硬件自己要花费3~8个机器周期的时间转到中断函数的地方处理中断,如果中断函数过长,那么本次中断没有处理完,下次中断又来了,单片机不能及时响应,产生了错误。对照着以上的中断函数,中断函数要做的事情有一件费时最长:等待向pc发送完数据。而这段时间不用害怕,因为pc向单片机发数据同样要这么长时间。两帧数据之间的间隔只有发送一位的时间间隔,也就是17us,在这个时间内,单片机要响应中断(3~8个机器周期),处理中断函数中的其他语句,估算一下,以上程序中处理语句的时间在10个机器周期左右,再加上响应中断的时间,按18个机器周期计算,大概要19us左右,产生了未处理完一次中断,又来了下一次中断的情况。要想用中断方式达到57600的波特率,要减少中断函数中的代码,所以只能放弃奇偶校验,如不想放弃奇偶校验,则只能降低波特率。否则,只能选用查询方式。请看以下程序:
实验九
#pragma small #include
void initmpu(void) //57600波特率,无奇偶校验,中断方式 {
=0,RB8为接收到的停止位。在方式2或方式3中,RB8为接收到的第9位数据。
TI: 发送中断标志。在方式0中,第8位发送结束时,由硬件置位。在其它方式的发送停止位前,由硬件置位。TI置位既表示一帧信息发送结束,同时也是申请中断,可根据需要,用软件查询的方法获得数据已发送完毕的信息,或用中断的方式来发送下一个数据。TI必须用软件清0。
RI: 接收中断标志位。在方式0,当接收完第8位数据后,由硬件置位。在其它方式中,在接收到停止位的中间时刻由硬件置位(例外情况见于SM2的说明)。RI置位表示一帧数据接收完毕,可用查询的方法获知或者用中断的方法获知。RI也必须用软件清0。 ? 电源控制寄存器PCON: SMOD 此寄存器只有第一位SMOD和本文档有关。 SMOD:串行口波特率加倍位
1――方式1,3波特率=定时器1溢出率/16;方式2波特率为Fosc/32。 0――方式1,3波特率=定时器1溢出率/32;方式2波特率为Fosc/64
8051单片机的全双工串行口可编程为4种工作方式,现分述如下: 方式0为移位寄存器输入/输出方式。本文档不用,故不叙述,有兴趣的话可以看教程。
方式1为波特率可变的10位异步通讯接口方式。发送或接收一帧信息,包括1个起始位0,8个数据位和1个停止位1。
输出: 当CPU执行一条指令将数据写入发送缓冲SBUF时,就启动发送。串
行数据从TXD引脚输出,发送完一帧数据后,就由硬件置位TI。
输入: 在(REN)=1时,串行口采样RXD引脚,当采样到1至0的跳变时,确认是开始位0,就开始接收一帧数据。只有当(RI)=0且停止位为1或者(SM2)=0时,停止位才进入RB8,8位数据才能进入接收寄存器,并由硬件置位中断标志RI;否则信息丢失。所以在方式1接收时,应先用软件清零RI和SM2标志。 方式2
方式2为固定波特率的11位异步通讯接口方式。它比方式1增加了一位可程控为1或0的第9位数据。输出: 发送的串行数据由TXD端输出一帧信息为11位,附加的第9位来自SCON寄存器的TB8位,用软件置位或复位。它可作为多机通讯中地址/数据信息的标志位,也可以作为数据的奇偶校验位。当CPU执行一条数据写入SUBF的指令时,就启动发送器发送。发送一帧信息后,置位中断标志TI。
输入: 在(REN)=1时,串行口采样RXD引脚,当采样到1至0的跳变时,确认是开始位0,就开始接收一帧数据。在接收到附加的第9位数据后,当(RI)=0或者(SM2)=0时,第9位数据才进入RB8,8位数据才能进入接收寄存器,并由硬件置位中断标志RI;否则信息丢失。且不置位RI。再过一位时间后,不管上述条件时否满足,接收电路即行复位,并重新检测RXD上从1到0的跳变。工作方式3
方式3为波特率可变的11位异步通讯接口方式。除波特率外,其余与方式2相同。
4、波特率的选择
如前所述,在串行通讯中,收发双方的数据传送率(波特率)要有一定
的约定。在89C51串行口的四种工作方式中,方式0和2的波特率是固定的,而方式1和3的波特率是可变的,由定时器T1的溢出率控制。以下讨论中,FOSC是晶振的频率。 A、方式0
方式0的波特率固定为晶振频率的1/12。 B、方式2
方式2的波特率由PCON中的选择位SMOD来决定,可由下式表示: 波特率=2的SMOD次方除以64再乘一个fosc,也就是当SMOD=1时,波特率为1/32fosc,当SMOD=0时,波特率为1/64fosc C、方式1和方式3
定时器T1作为波特率发生器,其公式如下: 波特率=定时器T1溢出率乘2的SMOD次方除以32 波特率=(2^SMOD)*(定时器1的溢出率)/32 T1溢出率= T1计数率/产生溢出所需的周期数
式中T1计数率的含义是:一秒钟计数的次数。它取决于它工作在定时器状态还是计数器状态。当工作于定时器状态时,T1计数率为fosc/12(即一个机器周期);当工作于计数器状态时,T1计数率为外部输入频率,此频率应小于fosc/24。产生溢出所需周期与定时器T1的工作方式、T1的预置值有关。
定时器T1工作于方式0:溢出所需周期数=8192-x (X为预置数) 定时器T1工作于方式1:溢出所需周期数=65536-x 定时器T1工作于方式2:溢出所需周期数=256-x
因为方式2为自动重装入初值的8位定时器/计数器模式,所以用它来做波特率发生器最恰当。
当时钟频率选用11.0592MHZ时,容易获得标准的波特率,所以很多单片机系统选用这个频率的晶振。
好了,先面我们给出串口初始化的过程,前面讲的几个寄存器会在下面用到。
串口初始化的歩骤:
a) 确定定时器1的工作方式――编程TMOD寄存器; b) 计算定时器1的初值――装载TH1,TL1; c) 启动定时器1――编程TCON中的TR1位; d) 确定串行口的工作方式――编程SCON;
e) 串行口在中断方式工作时,须开CPU和源中断――编程IE寄存器。 为了说明以上的基本过程,请看串口通讯的第一个实验。本实验的目的很简单,就是从pc机向单片机发送数据,然后单片机紧接着把pc发送的数据送回pc在显示器上显示出来。我们目前所要做的工作只是给单片机编程,让单片机能够接收到pc发送的数据,并且接收到pc发送的数据以后在送给pc,至于pc怎样发送,接受,由串口调试助手来完成。串口调试助手要陪伴我们整个实验过程,让我们先认识一下吧.
从上图我们可以看见,左上角第一个设置是串口的选择,计算机上有两个串口,我也分不清那个是COM1那个是COM2,你可以试一下。波特率的设置必须和你已经写到单片机里的程序设置的波特率一致!校验位也要和单片机程序一至,数据位是八位,停止位是一位。一切设置好后,在助手的下面的文本区填入要发送的数据,点击发送后如果上面的大文本框内能够正确的显示出来的话,实验就成功了。以后我们描述调试助手的设置用下面的格式:
(波特率,校验位,数据位,停止位)
比如上图的设置描述为(9600,n,8,1)其中,n为无校验。 下面请看程序:
实验四
#include
unsigned char
a;
main(void) //主程序(判断单片机串口是否正常工作)
TMOD=0x20; // 采用定时器1的工作方式2 TL1=0xfd; //计算定时器1的初值,装载TL1,TH1 TH1=0Xfd;
//波特率(9600)=(2^SMOD)*(定时器1的溢出率)/32
//溢出速率=(计数速率)/ [256-(TH1)]
//计数速率与TMOD寄存器中C/T(TMOD.6)的状态有关。当C/T=0时,//计数速率为Fosc/12,当C/T=1时计数速率取决与外部输入时钟频率,//但此频率不能超过Fosc/24。
SCON=0X50;
//允许串口接受数据,工作方式为1,无奇偶校验位。
PCON=0x00; TR1=1; while(1) {
//串口波特率不加倍。
//启动定时器1计数,TR1是TCON.6
while(RI==0) //判断串口缓冲区是否接受完数据(RI==1表示接受完) {;
} //接受完了跳出循环,没接收完等待接受 RI=0;
//硬件置位,软件清零
a=SBUF; //接受串口缓冲区数据。 SBUF=a;
//通过串口缓冲区向外发送数据。
while(TI==0) //判断串口缓冲区是否发送完数据(TI=1时表示发送完) { ; // 发送完跳出循环,没发送完等待发送 } TI=0; } }
调试助手设置 :(9600,n,8,1)
//硬件置位,软件清零。
把这个程序编译,烧进单片机,就可以通过调试助手向单片机发送数据了。上面的助手有一个自动发送的选项,用这个选项可以方便调试过程。如果没有回显或显是不正确,请检查:
1、 单片机是否插上了板子,并且给板子加上了电。
2、 串口调试助手的设置是否正确(波特率,停止位,校验位,以及串口是否打开)
3、 程序是否烧写正确。
上面的程序使用查询方式做得,所谓查询,就是单片机不停的查询RI是否为1,TI是否为1。。。。。。这样单片机出了处理串口的数据别的什么都干不了了,所以我们下面有了另一中方式――――中断方式。此种方式,一旦串口请求中断,就处理串口。其他时间单片机可以干别的事情。请看程序:
实验五
#pragma small //存储模式定义(一般不必深究) #include
unsigned char a;
void initmpu(void)//串口、中断、初始化设置子函数
//11.0592MH晶振下,波特率9600,无奇偶校验
{
TMOD=0x20;//定时器1的工作方式2 TL1=0xfd; //装载计数初值 TH1=0xfd;
SCON=0x50; //采用串口工作方式1,无奇偶校验 PCON=0x00;//串口波特率不加倍
IE=0x90;//开总中断,开串口中断 TR1=1;//启动定时器1 }
void getch(void) interrupt 4//中断源编号为4,即串口中断
{
RI=0;//清除中断标志,硬件置位,软件清零 a=SBUF; //接受串口缓冲区数据。 SBUF=a;
while(TI==0)//判断是否发送结束:TI==1为结束。 {; //等待发送结束 }
TI=0; //发送结束标志清零 }
void main(void) //主函数,中断方式串口通讯 {
initmpu(); //调用初始化串口子函数 while(1)//无限空循环,此处可以加其它功能 {;
} }
调试助手设置 :(9600,n,8,1)
//通过串口缓冲区向外发送数据。
上面这个程序就是用的中断方式,我们说过,中断方式可以同时做其它事情,现在把上面的程序稍加改写,请看边让小灯闪烁边进行串口通讯的程序。
实验六
#pragma small //存储模式定义 #include
unsigned char a;
sbit P10=P1^0; //定义P10
void initmpu(void)//串口、中断、初始化设置子函数
//11.0592MH晶振下,波特率9600,无奇偶校验 {
TMOD=0x20; //定时器1的工作方式2 TL1=0xfd; //装载计数初值 TH1=0xfd;
SCON=0x50; //采用串口工作方式1,无奇偶校验 PCON=0x00; //串口波特率不加倍
IE=0x90; //开总中断,开串口中断 TR1=1; //启动定时器1 }
void getch(void) interrupt 4//中断源编号为4,即串口中断 {
RI=0; //清除中断标志,硬件置位,软件清零 a=SBUF; //接受串口缓冲区数据。 SBUF=a;
while(TI==0) //判断是否发送结束:TI==1为结束。 {; //等待发送结束 }
//通过串口缓冲区向外发送数据。
TI=0; //发送结束标志清零 }
void main(void) //主函数,中断方式串口通讯 {
unsigned char j, k; P10=0;
initmpu(); //调用初始化串口子函数 while(1) //无限空循环,此处可以加其它功能 {
for(j=0;j<500;j++) for(k=0;k<200;k++) {;
} //软件延时
P10=!P10; //控制小灯 } }
调试助手设置 :(9600,n,8,1)
好了,如果以上几个实验你都实验成功并且掌握了串口通讯的流程,基本任务完成了,下面一部分是我在做串口通讯实验时遇到的几个问题,有的解决了,有的还没有解决,我都记录了下来,请您参考。相信您如果认真的做了如下几个实验,你将会对串口通讯有更深入一些的理解。
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说综合文库AT89C51串口通讯教程在线全文阅读。
相关推荐: