《最新《C51单片机技术教程》.doc》由会员分享,可在线阅读,更多相关《最新《C51单片机技术教程》.doc(28页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精品资料C51单片机技术教程.2008年12月16日C51单片机技术教程 田希晖 薛亮儒 人民邮电出版社第1 章 C51单片机的组成及结构第2 章 C51单片机指令系统第3 章 单片机的C程序设计第4 章 C51单片机内部资源第5 章 单片机的系统扩展第6 章 单片机的机间通信第7 章 单片机人机交互的C编程第8 章 Keil C51上机指南第9 章 单片机系统综合实例笔记 不是把所有知识点都记下。 而是 记那些需要学习的,或者总结得很好的东西。/指令系统的寻址方式和寻址空间序号寻址方式使用符号和变量寻址空间1立即数#data, #data16程序存储器(片内片外)64KB2直接direct或
2、data, #data16内部RAM 128B 数据存储器(片外RAM)64KB访问SFR 的唯一方法3寄存器R0R7, A, B, Cy, DPTR片内RAM 128B4寄存器间接R0, R1, SP片内RAM 128BR0, R1, DPTR数据存储器(片外RAM)64KB5相对PC+rel(偏移量)程序存储器(片内片外)64KB6变址A+DPTR, A+PC同上7位bit内部RAM(20H2FH),SFR内部RAM数据传送指令示意图C51的数据类型1、基本类型、构造类型、指针类型、空类型2、基本类型:位型(bit)、字符型(char)、整型(int)、长整型(long)、浮点型(floa
3、t)、双精度浮点型(double)3、构造类型:数组类型、结构体、共用体、枚举C51系列单片机将int型变量的 高 位字节数存放在 低 地址字节中。浮点数存储格式 IEEE754标准。24位精度,尾数的最高位始终为“1”,因而不保存,内存中字节存储顺序 如下:1位符号位、8位指数位、23位尾数地址+0+1+2+3内容MMMMEMMSEE其中, S符号位,E阶码,M尾数C51的变量的存储类型存储类型与存储空间的对应关系data直接寻址片内数据存储区,访问速度快(128B)bdata可位寻址片内,允许位与字节混合访问(16B)idata间接寻址片内,可访问片内全部RAM地址空间(256B)pdat
4、a分页寻址片外(256B)xdata片外(64KB)code程序存储器(64KB)变量说明举例:data char var; / 字符变量var 定位在片内数据存储区char code MSG=PARAMETER; / 字符数组MSG 定位在程序存储区unsigned long xdata array100; / 无符号长整型数组 定位在片外RAM区bit lock;unint xdata vector1044;sfr P0=0x80;char bdata flags;sbit flag0=flags0;指针变量说明举例:long xdata *px; / 指针px指向long型 xdata区
5、,指针本身位于在默认存储区,指针长度为2Bchar xdata *data pd; / 指针pd指向字符型 xdata区, 自身在data区, 长度为2Bdata char xdata *pd; / 同上(等效)data int *pn; / 和int *data pn及intr *pn等效,定义一个类型为int型的通用型指针,指针本身在data区,长度为3B。说明:指针指向的存储类型,即 指向哪个存储区。 存储类型的声明位置在数据类型和指针名(如*px)之间,如无此项声明,则此指针型变量为通用型。 指针变量自身的存储类型,即指针处于什么区与自身的长度无关,该声明可位于声明语句的开关,也可在*
6、和变量名之间。#include 特殊功能寄存器定义#include 绝对地址定义位变量及其定义1、 位变量C51定义 一般语法格式:bit 位变量名; 如:bit lock_bit;2、 采用字节寻址变量.位 的方法。 如:bdata int ibase; sbit mybit=ibase15;3、 函数可包含类型为bit的参数,也可将其作为返回值。 但注意,使用(#pragma disable)或包含明确的寄存器组切换(using n)的函数不能返回位值。4、 对特殊功能寄存器的位的定义。例:#include sbit P1_1=P11; / 无名位sbit ac=ACC7; / 无名位RS
7、1=1; RS0=0; / 有名位 sbit OV=0xD02;sbit CY=PSW7; 5、 对位变量定义的限制。位变量不能定义为指针、数组。 逻辑右移 或 算术右移, 取决于计算机系统。/ 看懂下面的就很容易理解 头文件中的绝对地址访问了。一般指针包括3字节:2字节偏移和1字节存储器类型:即地址+0+1+2内容存储器类型偏移量高位偏移量低位其中, 存储器类型编码如下:存储器类型idataxdatapdatadatacode编码值12345例,以xdata类型的0x1234地址作为指针可以表示如下:地址+0+1+2内容0x020x120x34常数作指针时,必须注意正确定义存储类型和偏移。
8、例:把常数0x40写入地址为0x8000的外部数据存储器。#define XBYTE ( (char *) 0x20000L )XBYTE0x8000=0x40;其中, XBYTE被定义为(char *)0x20000L, 0x20000L为一般指针,其存储类型为2,偏移量为0x0000, 这样XBYTE成为指向xdata零地址的指针,而XBYTE0x8000则是外部数据存储器的0x80000绝对地址。/ 后面还有头文件中其化存储区的绝对地址访问定义共用体变量可在不同时间内保存不同类型和长度的数据,从而提供了在同一存储单元中可以分时操作不同类型数据的功能。union Uuint word;st
9、ruct uchar hi; uchar lo; bytes;union U newcount;unit oldcount;newcount.bytes.hi=TH1;newcount.bytes.lo=TL1;oldcount=newcount.word;这样,定时器的计数值即可以按字节使用,也可以按字使用。函数“声明”和“定义”:“定义”是指对函数功能的确定,包括指定函数名、函数值类型,形参及其类型、函数体等,它是一个完整的、独立的函数单位;而“声明”的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以使在调用该函数时系统按此对照检查。函数指针 即函数入口地址。对用函
10、数的指针变量调用函数可归纳为如下几点:(1) 指向函数的指针变量的一般定义形式为:函数值返回类型 (*指针变量名)(函数形参表);(2) 在给函数指针变量赋值时,只需给出函数名。(3) 对指向函数的指针变量进行诸如p+n, p+, p-的运算是没有意义的。C51的库函数1 字符函数库 CTYPE.H extern bit F(char);其中,F可以为 isalpha/ isalnum/ iscntrl/ islower/ isupper/ isdigital extern char F(char);其中,F可以为 toint/ toupper/ tolower2 标准函数库 STDLIB.H
11、 extern T atoX(char *S); 其中,T可以是float/long/int,X与T对应分别为f/l/i void *malloc(unsigned int size); / 申请内存 void free(void *p); / 释放内存 void init mempool(void *p, unsigned int size); / 清零内存区3 数学函数库 MATH.H extern T abs(T val); 其中,T可以是float/long/int/char extern float exp(float x); / e的指数 extern float log/log1
12、0(float x); / e或10的对数 extern float sqrt(float x); / 平方根 extern float sin/cos/tan(float x);/ 三角函数 extern float pow(float x, float y); / x的y次方4 绝对地址访问头文件 ABSACC.H#define XBYTE( (unsigned char *) 0x20000L; PBYTE 3 DBYTE 4 CBYTE 5以上定义用来对C51系列单片机的存储空间进行地址访问,以字节为单位寻址。只需将BYTE换成WORD,就可以实现以字为单位寻址。5 内部函数库 INT
13、RINS.H/ 将变量循环左/右移n位unsigned char _crol_(unsigned char val, unsigned char n); int _irol_ int long _lrol_ longunsigned char _cror_(unsigned char val, unsigned char n); int _iror_ int long _lror_ long/ 对应汇编的NOP指令,延时一个机器周期void _nop_(void); / 测试给定的位参数x是否为1。若为1,返回1,同时将该位复位为0;否则返回0。bit _testbit_(bit x);6 访
14、问SFR和SFR_bit地址头文件REGxxx.H头文件reg51.h、reg52.h等文件中定义了C51单片机中的SFR寄存器名和相关的位变量名。编程举例循环队列是一种FIFO存储结构,在单片机应用程序中经常使用。队列需要队头指针 listhead、队尾指针listtail、队列长度listlen、队列空标志listempty和队列满标志listfull。初始时,listhead=listtail=0, listempty=listfull=0。需要定义两个函数,操作队列 listwrite()、listread()。iswrite()函数的操作思路:if(队列满) / 退出;else/ 将
15、数据写入listtail指向的数组单元listtail+;if(listtail=listlen)listtail=0;listempty=0;if(listhead=listtail) listfull=1;listread() / 函数的操作思路:if(队列空) / 退出else/ 将listtail指向的数组单元的内容读出;listhead+;if(listhead=listlen)listhead=0;listfull=0;if(listtail=listhead)listempty=1;函数的实现:#define listlen=10;unsigned char listlistle
16、n;char listwrite(char x)if(listfull)return 0;else/ 将数据写入listtail指向的数组单元listtail+;if(listtail=listlen)listtail=0;listempty=0;if(listhead=listtail) listfull=1;return 1;char listread(char *x)if(listempty)return 0;else/ 将listtail指向的数组单元的内容读出;listhead+;if(listhead=listlen)listhead=0;listfull=0;if(listtai
17、l=listhead)listempty=1;return 1;习题与思考:(挑了几个问答)1. C语言中的类型是怎么分配的? 什么是赋值操作?2. C语言的函数有什么特性?函数的存储类型和数据类型的意义是什么?3. 当一个函数需要返回多个值时,可以怎么做?4. 编写把字符串s逆转的函数reverse()5. 把上面的函数写成递归函数。第4章 C51单片机的内部资源重点及难点单片机中断和定时与单片机通信的基本概念、单片机的中断系统、单片机的定时/计数器、单片机外部中断源的扩展、数据传递的方式、串行通信控制寄存器、MCS51串行通信工作方式及其应用。基本要求1. 掌握单片机中断和定时与单片机通信
18、的基本概念2. 掌握单片机的中断源、中断控制、中断响应过程的基本概念及单片机系统的功能和使用方法3. 掌握单片机的定时/计数器的初值计算、工作方式控制寄存器的初始化、程序的设计方法和步骤4. 串行通信的基本方式、数据传送的次序、串行通信控制器的每一位的定义、串行通信的四种工作方式5. 了解单片机外部中断源扩展的常用方法和步骤教学内容1、 中断系统2、 定时/计数器3、 串行通信接口无条件传送方式、程序查询方式、中断传送方式中断系统:中断的基本概念、中断源、外部中断、内部中断、中断入口地址中断控制:TCON、IE、IP,SCON中断控制寄存器汇总寄存器名称D7D6D5D4D3D2D1D0定时/计
19、数器TCON(88H)TF1TR1TF0TR0IE1IT1IE0IT0位地址略串行口控制寄存器SCON(98H)SM0SM1SM2RENTB8RB8TIRI位地址略中断允许寄存器IE(A8H)EA/ESET1TX1ET0EX0位地址略中断优先级寄存器IP(B8H)/PSPT1PX1PT0PX0位地址略中断响应:中断响应的条件、中断响应的过程、中断响应时间、中断请求的撤销、寄存器组切换/ 下面的话让人晕 是他没讲清,还是我没搞清呢 不要紧 看懂后面的就可以了。寄存器组切换,在汇编语言中由编程者选择。但对混合语言编程的连接器,汇编程序使用的组可被选定,因而连接器不能像普通存储器那样分配寄存器组。在
20、C51中,寄存器组选择取决于特定的编译器指令。高优先组中断可以中断正在处理的低优先级程序,因而必须注意寄存器组。除非可以确定未使用R0R7(用汇编程序),最好给每种优先级程序分配不同的寄存器组。中断不允许用于外部函数,它对函数目标代码影响有收下几点:(1) 当调用函数时,SFR的ACC、B、DPH、DPL、PSW(当需要时)入栈。(2) 如果不使用寄存器组切换,甚至中断函数所需的所有工作寄存器都入栈。(3) 函数退出前,所有的寄存器内容出栈。(4) 函数由8051的指令RETI终止。中断服务程序使用的任何程序也使用一寄存器组。 递归程序可以使用,它们自己调用自己是因为它们依赖堆栈。在中断程序的
21、编写中应该注意以下几点:(1) 采用开中断总控制开关EA置位中断源的中断允许位;(2) 对外部中断INT0、INT1应选择中断触发方式;(3) 对于多个中断源中断,应设定中断优先级和预置IP;(4) 编写中断服务程序应注意保护现场和恢复现场,以免中断返回时丢失原寄存器、累加器中的信息; (具体要保护哪些东西 视CPU中断机制和需要而定)(5) 若要在挂靠当前中断程序时禁止更高优先级中断,可以采用软件关CPU中断或禁止某中断源中断,在中断返回前再开放中断。中断服务程序的完整语法如下:返回值 函数名 (参数)模式重入interrupt n using ndate:2008年12月17日要画图 !
22、如果通用语言简述就好了,但要描述准确哦。电脑画图麻烦。但要做笔记,还怕麻烦?EG:图 P1.4P1.7接4个LED(即发光二极管),P1.0P1.3接4个KEY(即开关),消抖电路用于产生中断请求信号,通过/INT0/ (负电平输入不方便)。 要求:开始时LED全黑;每中断一次,开关状态反映到LED上,且开关断开时对应LED亮。#include sbit P1_0=P10;sbit P1_7=P17;unsigned char breaks;bit flag;void sevice_int0()interrupt 0 using 2/ INT0中断服务程序,使用第2组寄存器flag=1;/ 置
23、标志breaks=P1; / 存状态void main()IP=0x01; / 置INT0高优先级中断IE0x81; / INT0开中断, CPU开中断for(;)if(flag) / 查询方式P1_4=P1_0;P1_5=P1_1;P1_6=P1_2;P1_7=P1_3;flag=0; / 处理完成清除标志4.3 定时/计数器不管是独立的定时器芯片还是单片机内的定时器都 有以下特点:(1) 定时/计数有多种方式, 可以是计数方式也可以是定时方式。(2) 计数初值可设定,计数溢出时发出中断申请。51单片机有两个加1计数器T0、T1。分别由两个8位寄存器构成。 由TMOD、TCON设定工作方式。
24、 两个定时器都有定时或事件计数功能,可用于定时控制、对外部事件计数和检测等场合。1 定时工作方式计数机周期(fosc/12)2 计数工作方式通过T0(P3.4)和T1(P3.5)对外部脉冲信号计数。当检测到下降沿时,计数器加1.由于检测一个1到0的跳变需要2个机器周期,故最高计数频率为fosc/24。虽然对输入信号占空比无特殊要求,但为了确保某个电平在变化之前至少被采样一次要求电平保持时间至少是一个完整的机器周期。4.3.1 TCON和TMODTCON:TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0TMOD:GATE C/T M1 M0 工作方式: 013位计数器(低5位,高8
25、位)1 16位。2初值自动重装8位3 2个8位,仅适用于T0/ 下面描述得很好工作方式3下,T0被拆成2个独立的8位计数器TL0和TH0。其中TL0即可以计数使用,又可以定时使用,使用T0的控制位和控制引脚信号。在方式3下,T0、T1的设置和使用是不同的。(1) T0方式3TL0:使用T0原有控制资源(控制位和引脚信号)TH0:借用T1的TR1、TF1,只能作8位定时器。(2) T0方式3下的T1T0方式3时,T1仍然可工作于方式02. C/T控制位仍可使T1工作在定时器或计数器方式,只是由于其TR1、TF1被T0的TH0占用,因而没有计数溢出标志可供使用,计数溢出时只能将输出结果送到串行口,
26、即用作串行口波特率发生器。 T0方式3下的T1方式2,因定时初值能自动恢复,用作波特率发生器更合适。定时/计数器的应用实例EG:设单片机的fosc=12MHz, 要求在P1.0脚上输出周期为2ms的方波。分析:周期为2ms的方波要求定时时间间隔1ms,每次时间到P1.0取反。定时器计数率fosc/12, 机器周期12/fosc1us计数次数1000/(12/fosc)1000/1=1000定时初值 -1000(1) 用定时器0的方式1编程,采用查询方式#include sbit P1_0=P10;void main()TMOD=0x01; / T0 方式1TR0=1;/ 启动T0for(;)T
27、H0=-(1000/256);TL0=-(1000%256);while(!TF0); / 查询等待TF0置位P1_0=!P1_0; / TF0=0; / 软件清TF0(2) 用定时器0的方式1编程,采用中断方式void include sbit P1_0=P10;void timer0()interrupt 1 using 1P1_0=!P1_0; / TH0=-(1000/256);TL0=-(1000%256);void main()TMOD=0x01;TH0=-(1000/256);TL0=-(1000%256);EA=1;ET0=1;TR0=1;dowhile(1);。EG:采用10
28、MHz晶振,在P1.0脚上输出周期为2.5s,占空比20%的脉冲信号。分析:由于采用10MHz晶振,使用定时器最大定时几十ms。即10ms定时,周期2.5s需要250次中断,占空比20%,高电平应为50次中断。采用10ms定时,晶振fosc=10MHz,因此需定时器计数次数8333#include #define unchar unsigned charuchar time;/ 计数中断次数uchar period=250; / 周期为2.5s,中断周期10ms, 250次uchar high=50; / 占空比20%void timer0()interrupt 1 using 1TH0=-8
29、333/256;TL0=-8333%256;if(+time=high)P1=0;else if(time=period)time=0;P1=1;void main()TMOD=0x01;TH0=-8333/256;TL0=-8333%256;EA=1;ET0=1;TR0=1;while(1);。EG:设P1口的P1.0和P1.1上有2个开关S1和S2,周期开始时全关。 2s以后S1开,0.1s后S2开,S1保持开2.0s,S2保持开2.4s,周而复始。 采用10MHz晶振。分析:根据要求,P1.0和P1.1开始顺序为:(关关)2s后(关开)0.1s后(开开)1.9s后(开关)0.5s后(关关
30、)采用10MHz晶振,每10ms中断一次,0.1s对应10次,开关变化对应的中断次数位置为0、200、210、400、450;相应的P1.0输出0、1、3、2#include #define uchar unsigned char#define uint unsigned intuchar i;/ 当前状态uint time;/ 中断次数code struct int position;char pattern;next=/ 采用结构数组定义时间和输出的对应关系0, 0X00, 200, 0X01, 210, 0X03, 400, 0X02, 450, 0XFF;void time0() in
31、terrupt 1 using 1TH0=-8333/256;TL0=-8333%256;time+;if(time=nexti.position)if(nexti.pattern=0xff) i=time=0; / 以0XFF判断周期的结束P1=nexti+.pattern;void main()P1=0;time=0;i=1;TMOD=0x01;TH0=-8333/256;TL0=-8333%256;EA=1;ET0=1;TR0=1;for(;)4.4 串行通信接口4.4.1串行通信基础知识1. 数据通信的基本概念通信是指CPU与外围设备之间的数据传送、转换和处理。数据通信方式有两种:并行
32、数据通信和串行数据通信。串行数据通信 是指数据按位顺序进行传送的通信方式。其特点是数据传送是一位一位进行传送的,最少只需要一根传输线即可完成,成本低但速度慢。并行数据通信 是指数据的各位同时进行传送的通信方式。其特点是各数据位同时传送,传送速度快、效率高。2. 异步通信和同步通信 异步串行通信 以字符为单位。/*串行异步传送的字符格式*/ / 书上有,网上有,理解了才是实在。对异步串行通信 的字符格式 作如下说明:起始位、数据位(低位在前/左)、奇偶校验位、停止位、空闲位 同步通信 收发时钟同步,字符与字符之间没有间隙,也不用起始位和停止位,仅在数据块开始时用同步字符SYNC来指示。3. 串行
33、通信的数据通路形式单工(Simplex)形式、全双工(Full-duplex)形式、半双工(Half-duplex)形式。4. 串行接口的基本任务在串行通信中,通信双方都按通信协议进行。 所谓通信协议 就是通信双方必须共同遵守的一种约定,约定包括数据的格式、同步的方式、传送的步骤、检纠错方式及控制字符的定义等。串行接口的任务主要包括以下几个方面。(1) 实现数据格式化(2) 进行串、并转换(3) 控制数据的传输速率(4) 进行传送错误检测5. 波特率和接收/发送时钟异步串行通信的传送速率一般为509600波特,常用于计算机到CRT终端和字符打印机之间的通信、直通电报以及无线电通信的数据发送等。
34、4.4.2 C51串行接口C51单片机有一个强编程的全双工异步串行通信接口,它可作UART用,也可作同步移位寄存器,其帧格式可有8位、10位或11位,并能设置各种波特率,给使用者带来很大的设计灵活性。1. 串行口寄存器结构 2. 串行接口控制SBUFSCON:SM0 SM1 SM2 REM TB8 RB8 TI RIPCON:SMOD / / / GF1 GF0 PD IDL (由于PCON寄存器不能进行位寻址,因此,表中写出的是“位序”而不是“位地址”)3. 串行接口的工作方式方式0 8位同步移位寄存器,串行数据通过RXD输入/输出,TXD输出移位时钟脉冲。固定波特率fosc/12方式1 1
35、0位异步通信方式,1位起始位(0)、8位数据位和1位停止位(1)。方式2和3 9位异步通信接口,一帧信息由11位组成。多机通信4. 波特率的设计方式0、2波特率固定;方式1、3波特率由定时器T1的溢出率确定。(1) 方式0的波特率不受SMOD位的影响。 固定为fosc/12(2) 方式2的波特率2smod*fosc/64 (3) 方式1、3的波特率2smod*定时器T1的溢出率T1的溢出周期12/fosc*(M-X)5. 串行口初始化(1) 串行口波特率串行口波特率常用于和微机的通信,选用晶振和波特率都相对固定。表串行口常用的波特率及相应的设置串行口的常用波特率串行口工作方式波特率fosc=6
36、MHzfosc=12MHzfosc=11.059MHzSMOD TMOD TH1SMOD TMOD TH1SMOD TMOD TH1方式01MHz 方式2375k1 187.5k1 0 方式1或方式362.5k1 20 FFH19.2k1 20 FDH9.6k0 20 FDH4.8k1 20 F3H0 20 FAH2.4k1 20 F3H1 20 F3H0 20 F4H1.2k1 20 FFH0 20 E6H0 20 F8H6001 20 E6H0 20 CCH0 20 D0H3000 20 CCH0 20 98H0 20 A0H137.51 20 1DH0 20 1DH0 20 2EH110
37、0 20 E6H0 10 FEEBH0 10 FEFFH(2) 初始化步骤 确定定时器T1的工作方式,即编程TMOD寄存器; 计算定时器T1的初值,即装载TH1、TL1; 启动定时器T1,即编程TCON中的TR1位; 确定串行口的控制,即编程SCON; 串行口中中断方式工作时,须开CPU和源中断,即编程IE寄存器。6. 串行口的应用编程 EG:单片机fosc=11.0592MHz,波特率9600, 各设置32个字节的队列缓冲区用于发送接收。 设计单片机和终端或另一计算机通信的程序。分析:单片机串行口初始化为9600波特,中断程序双向处理字符,程序双向缓冲字符。背景程序可以“放入”和“提取”在缓
38、冲区的字符串,而实际传入和传出SBUF的动作由中断完成。Loadmsg函数加载缓冲数组,标志发送开始。缓冲区分为发(t)和收(r)缓冲。缓冲区通过两种指示(进in和出out)和一些标志(满full、空empty、完成done)管理。队列缓冲区32字节长为循环队列,由简单的逻辑与(&)操作管理,它比取模(%)操作运行更快。当r_in=r_out接收缓冲(r_buf)区满,不能再有字符插入;当t_in=t_out,发送缓冲区(t_buf)空,发送中断清除,停止UART请求。 具体程序如下:#include #define uchar unsigned charuchar xdata r_buf32
39、;/ 背景程序“放入”和“提取”字符的队列缓冲区uchar xdata t_buf32;uchar r_in, r_out, t_in, t_out; / 队列指针bit r_full, t_empty, t_done;/ 缓冲区状态标志code uchar m=this is a test programrn;void serial() interrupt 4 using 1/*串行口中断服务程序,从RI、TI判别接收或发送中断,由软件清除。判别缓冲区状态(满full和空empty)和全部发送完成(done);*/if(RI & !r_full)r_bufr_in=SBUF;RI=0;r_i
40、n=+r_in&0x1f;if(r_in=r_out)r_full=1;else if(TI & !t_empty)SBUF=t_buft_out;TI=0;t_out=+t_out&0x1f;if(t_out=t_in)t_empty=1;else if(TI)TI=0;t_done=1;void loadmsg(uchar code *msg)/*此函数把字符放入发送缓冲区,准备发送*/while( (*msg!=0) & (t_in+1)t_out)&0x1f)!=0)/*测试缓冲区满*/t_buft_in=*msg;msg+;t_in=+t_in&0x1f;if(t_done)TI=1;t_empty=t_done=0; /*若完成重新开始*/void pr