51单片机学习(上)

最近收拾东西时居然发现了之前买的STM32单片机(普中STM32-F1),之前差点挂闲鱼卖了,很好奇是什么勇气让我直接买了STM32的板子?哈哈哈,买回来直接吃灰,连光盘都在。现在闲暇之余准备系统地学习一下单片机子,没准还能造些有意思的小玩意,那还是从89C51开始吧,哈哈。

单片机(Micro Controller Unit),简称MCU。内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能。单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机,LED等)的控制。单片机跟计算机相比,单片机算是一个袖珍版计算机,一个芯片就能构成完整的计算机系统。但在性能上,与计算机相差甚远,但单片机成本低、体积小、结构简单,在生活和工业控制领域大有所用,而且学习使用单片机是了解计算机原理与结构的最佳选择。

我目前使用的是STC89C52单片机,这种类型的单片机资料也比较多。

STC89C52简介

STC(南通国芯微电子)公司的51单片机系列是8位单片机,所以RAM为512字节,ROM:8K(Flash),工作频率:11.0592MHz(有些是12MHz),命名规则如下:

STC89C52结构

STC89C52系列单片机的内部结构框图如下图所示。STC89C52单片机中包含中央处理器(CPU)、程序存储器(Flash)、数据存储器(SRAM)、定时/计数器、UART串口、I/O接口、EEPROM、看门狗等模块。STC89C52系列单片机几乎包含了数据采集和控制中所需的所有单元模块,可称得上一个微机片上系统。内部结构图:

GPIO与管脚图

GPIO(General Purpose Intput Output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出。51 单片机芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。不过GPIO 最简单的应用还属点亮LED 灯了,只需通过软件控制GPIO 输出高低电平即可。当然 GPIO 还可以作为输入控制,比如在引脚上接入一个按键,通过电平的高低判断按键是否按下等操作。

这些管脚只需要结合开发板原理图就可以确定,哪些管脚和哪些外部设备相连接:

(1)电源引脚:引脚图中的VCC、GND 都属于电源引脚。 (2)晶振引脚:引脚图中的XTAL1、XTAL2 都属于晶振引脚。 (3)复位引脚:引脚图中的RST/VPD 属于复位引脚,不做其他功能使用。 (4)下载引脚:51 单片机的串口功能引脚(TXD、RXD)可以作为下载引脚使用。 (5)GPIO 引脚:引脚图中带有Px.x 等字样的均属于GPIO 引脚。GPIO 占用了芯片大部分的引脚,共达32个,分为了4组,P0、P1、P2、P3,每组为8个IO,而且在P3组中每个IO都具备额外功能,只要通过相应的寄存器设置即可配置对应的附加功能,同一时刻,每个引脚只能使用该引脚的一个功能。

① 51单片机所有IO口都是双向的,即可以作为输入也可以作为输出使用。

② 由于P0口是漏极开路的,所以要操作P0 口必须外接上拉电阻,其他P1、P2、P3 口都内部自带上拉电阻,可以不加,如果要增强IO口驱动能力,可以外接上拉电阻。

开发环境搭建

1、安装Keil5(汇编器、编译器 + 依赖库 + 头文件),Keil5也是一个IDE,可以直接在里面写代码。

2、Keil5也可以用SDCC替代,http://sdcc.sourceforge.net/,SDCC 是一个可重定向、优化的标准 C(ANSI C89、ISO C99、ISO C11)编译器套件,它针对基于 Intel MCS51 的微处理器(8032、8051、8052 等)。

3、安装STC-ISP (烧录器),也可以使用普中ISP,STC-ISP功能会多一些。

4、但是最好还是用VSCode方便一些,快捷键用着也方便,安装 Embedded IDE(https://docs.em-ide.com)插件即可,Embedded IDE会提供8051、STM8、 Cortex-M、RISC-V… 项目的开发、编译、烧录等功能。安装好Embedded IDE后,直接可以Ctrl点进去就能查看源代码,比较方便。

注意:SDCC和Keil5的头文件是不一样的,SDCC用的是 8052.h,Keil5用的是 REGX52.H

1、GPIO之LED流水灯

通过LED流水灯实验,认识GPIO。通过STC-ISP软件生成延时代码,完成LED流水灯的功能:

 1//此文件中定义了单片机的一些特殊功能寄存器
 2#include <REGX52.H> 
 3#include <INTRINS.H>
 4
 5// 睡眠x毫秒
 6void DelayXms(unsigned int x){
 7	unsigned char i, j;
 8	while(x--){
 9		_nop_();
10		i = 2;
11		j = 199;
12		do
13		{
14			while (--j);
15		} while (--i);
16	}
17}
18
19void main(){
20	while(1){
21		P2 = 0xFE; //1111 1110
22		DelayXms(500);
23		
24		P2 = 0xFD; //1111 1101
25		DelayXms(500);
26		
27		P2 = 0xFB; //1111 1011
28		DelayXms(500);
29		
30		P2 = 0xF7; //1111 0111
31		DelayXms(500);
32		
33		P2 = 0xEF; //1110 1111
34		DelayXms(500);
35		
36		P2 = 0xDF; //1101 1111
37		DelayXms(500);
38		
39		P2 = 0xBF; //1011 1111
40		DelayXms(500);
41		
42		P2 = 0x7F; //0111 1111
43		DelayXms(500);
44	}
45}

2、按键抖动与消抖

对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。

 1#include <REGX52.H>
 2
 3void DelayXms(unsigned int x)
 4{
 5	unsigned char i, j;
 6    while (x--)
 7    {
 8        i = 2;
 9        j = 199;
10        do
11        {
12            while (--j);
13        } while (--i);
14    }
15}
16
17void main(){
18    while (1)
19    {
20        P2_1 = 1;
21        if(P3_1 == 0){
22            DelayXms(20); // 消除按下抖动
23            while (P3_1 == 0);
24            DelayXms(20); // 消除松手抖动
25            P2_0 = ~P2_0; // 取反就是开和关
26        }
27    }
28}

3、数码管MCU扫描

LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成8字型的器件。数码管一般有两种驱动方式:

① 单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间

② 专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可

下面是通过单片机直接扫描的方式来完成的数据显示示例:

  1#include <REG52.H>
  2#include <INTRINS.H>
  3#include <stdlib.h>
  4
  5typedef unsigned char uint8;
  6
  7uint8 hexArr[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,
  8                  0x80, // -> 10 小数点
  9                  0x40, // -> 11 负 号
 10            }; 
 11
 12void DelayXms(unsigned int x) //@11.0592MHz
 13{
 14    unsigned char i, j;
 15    while (x--)
 16    {
 17        _nop_(); // 空语句
 18        i = 2;
 19        j = 199;
 20        do
 21        {
 22            while (--j)
 23                ;
 24        } while (--i);
 25    }
 26}
 27
 28
 29/**
 30 * x: LED 0-7
 31 * num: 0-9,10为小数点, 11为负号
 32 **/
 33void showNumLed(uint8 x, uint8 num)
 34{
 35    switch (x)
 36    {
 37    case 0: P2_2 = 1; P2_3 = 1; P2_4 = 1; break;
 38    case 1: P2_2 = 0; P2_3 = 1; P2_4 = 1; break;
 39    case 2: P2_2 = 1; P2_3 = 0; P2_4 = 1; break;
 40    case 3: P2_2 = 0; P2_3 = 0; P2_4 = 1; break;
 41    case 4: P2_2 = 1; P2_3 = 1; P2_4 = 0; break;
 42    case 5: P2_2 = 0; P2_3 = 1; P2_4 = 0; break;
 43    case 6: P2_2 = 1; P2_3 = 0; P2_4 = 0; break;
 44    case 7: P2_2 = 0; P2_3 = 0; P2_4 = 0; break;
 45    default:
 46        break;
 47    }
 48
 49    // P0 = 0x7D; -> 6
 50    P0 = hexArr[num];
 51}
 52
 53void numToCharArray(char arr[], long num){ // 123456
 54    int i;
 55    if(num < 0) num = -num;
 56    for (i = 7; i >= 0; i--)
 57    {
 58       if(num == 0) break;
 59       arr[i] = num % 10;
 60       num /= 10;
 61    }
 62}
 63
 64void showLongByLED(long num){
 65    int i;
 66    int tmp;
 67    // 如果是负数
 68    if(num < 0){
 69        //showNumLed(1, 10);
 70        if(num < -9999999L){
 71            // 溢出
 72            while (1)
 73            {
 74                for (i = 0; i < 8; i++)
 75                {
 76                    showNumLed(i, 11);
 77                    DelayXms(2);
 78                    P0 = 0x00; // 消影
 79                }
 80            }
 81        }else {
 82            char ch[8] = {0};
 83            numToCharArray(ch, num);
 84            for(i = 0; i < 8; i++){
 85                if(ch[i] != 0){ // 0390 9999
 86                    tmp = i;
 87                    while(1){
 88                        showNumLed(tmp - 1, 11);
 89                        DelayXms(2);
 90                        P0 = 0x00;
 91                        for (i = tmp; i < 8; i++)
 92                        {
 93                            showNumLed(i, ch[i]);
 94                            DelayXms(2);
 95                            P0 = 0x00; // 消影
 96                        }
 97                    }
 98                }
 99            }
100        }
101    }else if(num == 0){
102        showNumLed(8, 0);
103    }else {
104        char ch[8] = {0};
105        numToCharArray(ch, num);
106        for(i = 0; i < 8; i++){
107            if(ch[i] != 0){
108                tmp = i;
109                while(1){
110                    for (i = tmp; i < 8; i++)
111                    {
112                        showNumLed(i, ch[i]);
113                        DelayXms(2);
114                        P0 = 0x00; // 消影
115                    }
116                }
117            }
118        }
119    }
120}
121
122
123void showLongByLEDAuto(long num){
124    int i;
125    int tmp;
126    // 如果是负数
127    if(num < 0){
128        //showNumLed(1, 10);
129        if(num < -9999999L){
130            // 溢出
131            for (i = 1; i <= 8; i++)
132            {
133                showNumLed(i, 11);
134                DelayXms(2);
135                P0 = 0x00; // 消影
136            }
137        }else {
138            
139        }
140    }else if(num == 0){
141        showNumLed(8, 0);
142    }else {
143        char ch[8] = {0};
144        numToCharArray(ch, num);
145        for(i = 0; i < 8; i++){
146            if(ch[i] != 0){
147                tmp = i;
148                for (i = tmp; i < 8; i++)
149                {
150                    showNumLed(i, ch[i]);
151                    DelayXms(2);
152                    P0 = 0x00; // 消影
153                }
154            }
155        }
156    }
157}
158
159void main()
160{
161    int i;
162    int flag = 0;
163    for (i = 0; i < 10; i++)
164    {
165         showNumLed(0, i);
166         DelayXms(100);
167    }
168
169    showLongByLED(-8900999L);
170    while(1);
171}

这种通过单片机扫描的方式还是过于复杂,而且比较浪费单片机的计算机资源,推荐还是用专用驱动芯片,内部自带显存、扫描电路,单片机只需告诉它显示什么即可,TM1640就是这样一种芯片:

TM1640是一种LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU数字接口、数据锁存器LED驱动等电路。本产品性能优良,质量可靠。主要应用于电子产品LED显示屏驱动。采用SOP28、SSOP28的封装形式。

4、LCD1602显示字符

直接使用下面封装好的函数即可,以后需要模块化的地方一定要模块化,形成代码复用:

LCD1602.h:

 1#ifndef __LCD1602_H__
 2#define __LCD1602_H__
 3
 4//用户调用函数:
 5void LCD_Init();
 6void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
 7void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
 8void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
 9void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
10void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
11void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
12
13#endif //!__LCD1602_H__

LCD1602.c:

  1#include <REG52.H>
  2
  3//引脚配置:
  4sbit LCD_RS=P2^6;
  5sbit LCD_RW=P2^5;
  6sbit LCD_EN=P2^7;
  7#define LCD_DataPort P0
  8
  9//函数定义:
 10/**
 11  * @brief  LCD1602延时函数,12MHz调用可延时1ms
 12  * @param  无
 13  * @retval 无
 14  */
 15void LCD_Delay()
 16{
 17	unsigned char i, j;
 18
 19	i = 2;
 20	j = 239;
 21	do
 22	{
 23		while (--j);
 24	} while (--i);
 25}
 26
 27/**
 28  * @brief  LCD1602写命令
 29  * @param  Command 要写入的命令
 30  * @retval 无
 31  */
 32void LCD_WriteCommand(unsigned char Command)
 33{
 34	LCD_RS=0;
 35	LCD_RW=0;
 36	LCD_DataPort=Command;
 37	LCD_EN=1;
 38	LCD_Delay();
 39	LCD_EN=0;
 40	LCD_Delay();
 41}
 42
 43/**
 44  * @brief  LCD1602写数据
 45  * @param  Data 要写入的数据
 46  * @retval 无
 47  */
 48void LCD_WriteData(unsigned char Data)
 49{
 50	LCD_RS=1;
 51	LCD_RW=0;
 52	LCD_DataPort=Data;
 53	LCD_EN=1;
 54	LCD_Delay();
 55	LCD_EN=0;
 56	LCD_Delay();
 57}
 58
 59/**
 60  * @brief  LCD1602设置光标位置
 61  * @param  Line 行位置,范围:1~2
 62  * @param  Column 列位置,范围:1~16
 63  * @retval 无
 64  */
 65void LCD_SetCursor(unsigned char Line,unsigned char Column)
 66{
 67	if(Line==1)
 68	{
 69		LCD_WriteCommand(0x80|(Column-1));
 70	}
 71	else if(Line==2)
 72	{
 73		LCD_WriteCommand(0x80|(Column-1+0x40));
 74	}
 75}
 76
 77/**
 78  * @brief  LCD1602初始化函数
 79  * @param  无
 80  * @retval 无
 81  */
 82void LCD_Init()
 83{
 84	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
 85	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
 86	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
 87	LCD_WriteCommand(0x01);//光标复位,清屏
 88}
 89
 90/**
 91  * @brief  在LCD1602指定位置上显示一个字符
 92  * @param  Line 行位置,范围:1~2
 93  * @param  Column 列位置,范围:1~16
 94  * @param  Char 要显示的字符
 95  * @retval 无
 96  */
 97void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
 98{
 99	LCD_SetCursor(Line,Column);
100	LCD_WriteData(Char);
101}
102
103/**
104  * @brief  在LCD1602指定位置开始显示所给字符串
105  * @param  Line 起始行位置,范围:1~2
106  * @param  Column 起始列位置,范围:1~16
107  * @param  String 要显示的字符串
108  * @retval 无
109  */
110void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
111{
112	unsigned char i;
113	LCD_SetCursor(Line,Column);
114	for(i=0;String[i]!='\0';i++)
115	{
116		LCD_WriteData(String[i]);
117	}
118}
119
120/**
121  * @brief  返回值=X的Y次方
122  */
123int LCD_Pow(int X, int Y)
124{
125	unsigned char i;
126	int Result=1;
127	for(i=0;i<Y;i++)
128	{
129		Result*=X;
130	}
131	return Result;
132}
133
134/**
135  * @brief  在LCD1602指定位置开始显示所给数字
136  * @param  Line 起始行位置,范围:1~2
137  * @param  Column 起始列位置,范围:1~16
138  * @param  Number 要显示的数字,范围:0~65535
139  * @param  Length 要显示数字的长度,范围:1~5
140  * @retval 无
141  */
142void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
143{
144	unsigned char i;
145	LCD_SetCursor(Line,Column);
146	for(i=Length; i>0; i--)
147	{
148		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
149	}
150}
151
152/**
153  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
154  * @param  Line 起始行位置,范围:1~2
155  * @param  Column 起始列位置,范围:1~16
156  * @param  Number 要显示的数字,范围:-32768~32767
157  * @param  Length 要显示数字的长度,范围:1~5
158  * @retval 无
159  */
160void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
161{
162	unsigned char i;
163	unsigned int Number1;
164	LCD_SetCursor(Line,Column);
165	if(Number >= 0)
166	{
167		LCD_WriteData('+');
168		Number1=Number;
169	}
170	else
171	{
172		LCD_WriteData('-');
173		Number1=-Number;
174	}
175	for(i=Length; i>0; i--)
176	{
177		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
178	}
179}
180
181/**
182  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
183  * @param  Line 起始行位置,范围:1~2
184  * @param  Column 起始列位置,范围:1~16
185  * @param  Number 要显示的数字,范围:0~0xFFFF
186  * @param  Length 要显示数字的长度,范围:1~4
187  * @retval 无
188  */
189void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
190{
191	unsigned char i,SingleNumber;
192	LCD_SetCursor(Line,Column);
193	for(i=Length; i>0; i--)
194	{
195		SingleNumber = Number/LCD_Pow(16,i-1)%16;
196		if(SingleNumber < 10)
197		{
198			LCD_WriteData(SingleNumber+'0');
199		}
200		else
201		{
202			LCD_WriteData(SingleNumber-10+'A');
203		}
204	}
205}
206
207/**
208  * @brief  在LCD1602指定位置开始以二进制显示所给数字
209  * @param  Line 起始行位置,范围:1~2
210  * @param  Column 起始列位置,范围:1~16
211  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
212  * @param  Length 要显示数字的长度,范围:1~16
213  * @retval 无
214  */
215void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
216{
217	unsigned char i;
218	LCD_SetCursor(Line,Column);
219	for(i=Length; i>0; i--)
220	{
221		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
222	}
223}

5、定时器与中断系统

定时器

51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。

1、定时器用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。 2、替代长时间的Delay,提高CPU的运行效率和处理速度。

51系列单片机一定有基本的2个定时器,但是STC89C52具有3个定时器(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源。需要注意的是:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的。而且通常我们使用的都是基本的2个定时器:T0与T1。

定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔一段时间,计数单元的数值就增加一,当计数单元数值增加到"设定的闹钟提醒时间"时,计数单元就会向中断系统发出中断申请,产生"响铃提醒",使程序跳转到中断服务函数中执行。

STC89C52的T0和T1均有四种工作模式:

13位定时器/计数器、16位定时器/计数器(常用)、8位自动重装模式、两个8位计数器

以定时器T0为例,通过定时器中断控制D1指示灯间隔1秒闪烁。

中断系统

中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。

当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当儿个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。

当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序),发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。

STC89C52系列单片机提供了8个中断请求源,它们分别是:外部中断O(INTO)、定时器0中断、外部中断1(INT1)、定时器1中断、串口(UART)中断、定时器2中断、外部中断2(INT2)、外部中断3(INT3)。所有的中断都具有4个中断优先级。中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等,下面是STC89C51的中断号:

寄存器是连接软硬件的媒介。在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,就像每一个寄存器背后都连接了一根导线,控制着电路的连接方式。寄存器相当于一个复杂机器的"操作按钮" 。

通过配置中断部分的寄存器就可以理解为什么需要做这样的配置:

时钟Demo

 1#include <REGX51.H>
 2#include "LCD1602.h"
 3
 4unsigned int s, m, h;
 5
 6void Timer0Init(void)		//1毫秒@11.0592MHz
 7{
 8	// 配置定时器 -----------------------
 9	TMOD &= 0xF0;		// 设置定时器模式
10	TMOD |= 0x01;		// 设置定时器模式
11	TL0 = 0x66;		// 设置定时初始值
12	TH0 = 0xFC;		// 设置定时初始值
13	TF0 = 0;		// 清除TF0标志
14	TR0 = 1;		// 定时器0开始计时
15
16	// 配置中断 -------------------------
17	ET0 = 1;
18	EA = 1;
19	PT0 = 0;
20}
21
22
23void main()
24{
25		LCD_Init();
26		Timer0Init();
27	
28		LCD_ShowString(1, 1, "Clock:");
29		LCD_ShowString(2, 3, ":");
30		LCD_ShowString(2, 6, ":");
31    while (1){
32			// LCD显示属于耗时操作,不要放在定时器中断中完成
33			LCD_ShowNum(2, 1, h, 2);
34			LCD_ShowNum(2, 4, m, 2);
35			LCD_ShowNum(2, 7, s, 2);
36		}
37}
38
39 // 中断函数
40void Timer0_Routine() interrupt 1 
41{
42	static unsigned int T0Count = 0;
43	TL0 = 0x66;		// 设置定时初始值
44	TH0 = 0xFC;		// 设置定时初始值
45	T0Count++;
46	
47	if(T0Count > 1000){	
48		T0Count = 0;
49		s++;
50		// 时、分、秒
51		if(s >= 60){
52			s = 0;
53			m++;
54		}
55		
56		if(m >= 60){
57			m = 0;
58			h++;
59		}
60		
61		if(h >= 24){
62			h = 0;
63		}
64	}
65}

最终效果如下所示: