2006-06-10 14:50:00 yzxtc 阅读数 2242
  • 巫妖王51单片机开发板配套视频课程

    本课程是巫妖王51单片机开发板的配套视频课程,本课程的目标是用少的时间带大家初级入门51单片机。配合巫妖王51单片机开发板,让大家花费少的时间少的钱就能轻松开启单片机学习之路。

    19003 人正在学习 去看看 朱有鹏

单片机期末作业-----用单片机实现记录10次暂停时间的秒表

本次实验程序在lab2000P实验箱上验证了,任何人可以下栽使用,若有好的意见,请与yzx_xue@163.com联系

硬件上,只要把地址为0x8000的片选信号接到led/key的片选口即可

软件流程和程序

//单片机实现10路秒表程序流程
1:系统初始化;
2:等待键盘按键,启动秒表;
3:若键盘有按键且不是回车键,记录当前时间
4:如果是回车键或者记录的次数为10时,停止秒表,负责goto 3:
5:显示第一次按键的时间,等待按键;
6:判断按键,
 若是数字键,显示相应暂停时的时间
 若是UP/DOWN,显示前/后一次暂停时间
 若是复位键,复位系统 goto 1:
 若是暂停键,goto 1:
 若是退出键,退出系统;

//操作说明

//程序运行后,按任意键开始计数,然后按任意键(除回车键)记录一次当前时间。当记录次数为10时或者按回车键,秒表停止计数。
//led显示初次暂停时间,按数字键,led显示相对应暂停的时间;按上/下键,显示上/下一次暂停时间,reset键是复位键,EXIT退出程序。按PAUSE,led显示00.00.00,再按任意键,秒表从上一次停止时间开始计数。


键盘布局
/*7  8  9  A  ENTER  RESET */
/*4  5  6  B    ?         ? */
/*1  2  3  C    ? PAUSE */
/*0  F  Exit  D  DOWN  UP */
?用于系统的扩展。

 


键盘布局
/*7  8  9  A  ENTER  RESET */
/*4  5  6  B    ?            ? */
/*1  2  3  C    ?          PAUSE */
/*0  F  Exit  D  DOWN  UP */
?用于系统的扩展。

实验程序:

//StopWatch System V1.0 for my MCU final homework
//记录十次暂停时间,支持数字查询暂停时间。时间精确到10ms级
//Designed by:yzx_xue@163.com

#include<reg51.h>
#include<absacc.h>
///////////////////////////////////////////////////
#define  LEDPOS  XBYTE[0x8002]
#define  LEDDATA XBYTE[0x8004]
#define  KEYIN         XBYTE[0x8001]
#define  KEYOUT         XBYTE[0x8002]

#define  ENTER 0xff
#define  UP 0xf8
#define  DOWN 0xf9
#define  PAUSE 0xfa
#define  RESET 0xfe
#define  QUIT   0x0e


typedef unsigned char uchar;
typedef unsigned int uint;

#define  MAX_LED_NUM  6
uchar ledbuff[MAX_LED_NUM];
uchar code ledcode[2][10]={{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f},
      {0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef}};
unsigned char code KEYCODE[4][6]={
  {0x07,0x08,0x09,0x0a,0xff,0xfe},   /*7 8 9  A ENTER RESET */
  {0x04,0x05,0x06,0x0b,0xfd,0xfc},   /*4 5 6  B   ?  ? */
  {0x01,0x02,0x03,0x0c,0xfb,0xfa},   /*1 2 3  C   ? PAUSE */
  {0x00,0x0f,0x0e,0x0d,0xf9,0xf8}};   /*0 F EXIt  D DOWN  UP */
struct  time_swatch {
 uchar hour;
 uchar min;
 uchar sec;
 uint msec;
 }run={0,0,0,0};
struct time_swatch pause[10];
uint count=0;
//////////////////////////////////////////////
void  systeminit(void);
void  disp(void);
bit keybhit(void);
uchar getkey(void);
void push(struct time_swatch);
void delay(uchar i);
void reset(void);
void ledbuffinit(void)
     {
        uchar i;
        for(i=0;i<MAX_LED_NUM-1;i++)
                 ledbuff[i]=ledcode[i%2][0];
        ledbuff[MAX_LED_NUM-1]=ledcode[0][0];
        }
void main()
        {
        uchar key=0;
 uchar i,index,used;
 do{
                i=0;index=0;used=0;
  systeminit();
                push(run);
  while(!keybhit()) disp();
  key=getkey();
  TR0=1;
  do{
      while(!keybhit())  { push(run);disp();   }
      key=getkey();
      pause[i].hour=run.hour; //记录一次暂停
      pause[i].min=run.min;
      pause[i].sec=run.sec;
      pause[i].msec=run.msec;
                           i++;
                           used++;
  }while(key!=ENTER&&i<10); //当按了回车键之后或者暂停的次数为10,
                //stop the swatch
  TR0=0;
                key=0;
                used--;
  while(key!=RESET)
   {
   while(!keybhit())
     { push(pause[index]);
    disp();
                                delay(2000);
      }     //end
   key=getkey();
   if(key>=0&&key<=9)
    index=key;
   else switch(key) {
                                  case RESET:  reset();break;
             case PAUSE:  key=RESET; break;
             case UP :    index=(index==used)?0:(index+1); break;
             case DOWN:   index=(index==0)?used:(index-1); break;
             case QUIT:   return;
                                  default :     ;
                                         }
   }  //end display record
 }while(key!=QUIT);
}//end main
void timer0(void) interrupt 1 using 1{
 if(++count==2000)
  {
     count=0;
     if(++run.sec==60)
       { run.sec=0;
          if(++run.min==60)
   {run.min=0;
    run.hour++;
   }
       }
          }
       run.msec=count/20;
       return ;}
void  systeminit(void){
        uchar i=0;
 TMOD|=0x02;
 TH0=0x06;
 TL0=0x06;
 TCON=0x00;
 IE|=0x82;
        for(i=0;i<10;i++)
                {pause[i].hour=0;
                pause[i].min=0;
                pause[i].sec=0;
                pause[i].msec=0;
                }
     }
void delay(uchar i){ while(--i>0) ;}
void  disp(){
 uchar pos=0x20,i;
 for(i=0;i<MAX_LED_NUM;i++)
  {
  LEDPOS=pos;
  LEDDATA=ledbuff[i];
  delay(50);
  pos>>=1;
  LEDDATA=0x00;
  }
 }
bit keybhit()
 {
 uchar st;
 KEYOUT=0x00;
 st=KEYIN;
 return (~st&0x0f);
 }
uchar getkey()
 {
 uchar scancode,rescancode;
 uchar i=0,col=0,row;
 scancode=0x20;
 while(col<=5)
  {
  KEYOUT=~scancode;
  rescancode=KEYIN;
  if((rescancode&0x0f)!=0x0f)
  {
   for(i=0;i<50;i++);
   rescancode=KEYIN;
   if((rescancode&0x0f)!=0x0f){
                                switch(rescancode&0x0f)
                                       {case 0x07:      row=0;break;
                                        case 0x0b:      row=1;break;
                                        case 0x0d:      row=2;break;
                                        case 0x0e:      row=3;break;
                                        }
                                KEYOUT=0;
                                do delay(10);while(keybhit());
    return KEYCODE[row][col]; }
    }
  else scancode>>=1;
                 col++;
 }//end while
 }
void push(struct time_swatch a){
 ledbuff[0]=ledcode[0][a.min/10];
 ledbuff[1]=ledcode[a.sec%2][a.min%10]; //每隔一秒显示一次小数点。
 ledbuff[2]=ledcode[0][a.sec/10];
 ledbuff[3]=ledcode[a.sec%2][a.sec%10];
 ledbuff[4]=ledcode[0][a.msec/10];
 ledbuff[5]=ledcode[0][a.msec%10];
 }
void reset(void)        {
     count=0;
     run.hour=0;
     run.min=0;
     run.sec=0;
     run.msec=0;
}

2015-02-03 21:49:45 LLX123654 阅读数 1467
  • 巫妖王51单片机开发板配套视频课程

    本课程是巫妖王51单片机开发板的配套视频课程,本课程的目标是用少的时间带大家初级入门51单片机。配合巫妖王51单片机开发板,让大家花费少的时间少的钱就能轻松开启单片机学习之路。

    19003 人正在学习 去看看 朱有鹏

采用51单片机设计一个计时器前期准备

一、所执行的功能

    1、可以计时;

2、可以存储时间;

3、可以查看存储的时间;

二、器材

1、51单片机(STC89C52RC)及单片机最小系统;

2、7段数码管及74HC573锁存器2个;

3、按键;

4、电源;

三、硬件电路的设计

    1、51单片机最小系统:电源、复位电路、晶振电路、下载电路、51单片机

2、显示电路

3、按键电路

四、分析单片机编程的内部资源

1、I/O口的使用;

2、定时/计数器的使用;

五、软件编写流程的设计

1、定义端口(数码管显示端口,定义按键输入的端口,每个端口分配不同的名字)

2、硬件资源的初始化(初始化定时计数器,初始化LED数码管、初始化按键)

3、定时计数器:用于精确计时的产生;

4、I/O口:LED数码管显示;按键检测;

5、功能的初始化(工作状态初始化、显示器初始化);

6、工作状态:a、等待状态;b、计数状态;c、存储状态;

六、程序代码

#include<reg52.h>

/*****************端口定义****************/

typedef unsignedint UINT16;

typedef unsignedchar UINT8;

#define DATA P0

sbitseg_sel=P2^0;   //段选

sbitbit_sel=P2^1;   //位选

sbitstart=P2^2;  //开始

sbitstore=P2^3;  //存储

sbitdiskey=P2^4;  //回显

sbitreset=P2^5;   //复位

UINT8 codeseg_table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40};

UINT8 codebit_table[]={0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F};

UINT8n_ten_ms,n_second,n_minute;

UINT8 run_flag;

UINT8temp_buf[5][3];

UINT8 arry_point;

/************延时函数*************/

void delayms(UINT8ms)

{

       UINT8 x,y;

        for (x=ms;x>0;x--)

          for(y=112;y>0;y--);

}

/********定时器初始化**********/

voidInitTimer0(void) 

{

    TMOD = 0x01;  //定时器模式寄存器设置

    TH0 = 0x0DC;  //定时器赋初值时间50微秒

    TL0 = 0x000;

    EA = 1;   //打开总开关

    ET0 = 1;  //打开中断

    //TR0 = 1;  //打开定时器

}

/**********数码管的初始化********/

voiddisplay_led(UINT8 which_bit,UINT8 which_number)

{

        bit_sel=1;        

        DATA=bit_table[which_bit];

        bit_sel=0;

         seg_sel=1;

        DATA=0x00;

        seg_sel=0;

        seg_sel=1;

        DATA=seg_table[which_number];

        seg_sel=0;  

}

/**************数码管显示***********/

void display(UINT8which_bit,UINT8 which_number)

{

  UINT8 ge,ten;

  ten=which_number/10;

  ge=which_number%10;

  display_led(5,16);

  delayms(5);

  display_led(2,16);

  delayms(5);

  if(which_bit==1)

  {

    display_led(6,ten);

    delayms(5);

    display_led(7,ge);

    delayms(5);

  }

  if(which_bit==2)

  {

    display_led(3,ten);

    delayms(5);

    display_led(4,ge);

    delayms(5);

  }

  if(which_bit==3)

  {

    display_led(0,ten);

    delayms(5);

    display_led(1,ge);

    delayms(5);

  }

}

 

 

/*************回显按键函数***************/

voiddiskey_keyscan()

{

  if(diskey==0)

  {

    delayms(5);

    if(diskey==0)

    {

      arry_point--;

      n_ten_ms=temp_buf[arry_point][0];

      n_second=temp_buf[arry_point][1];

      n_minute=temp_buf[arry_point][2];

    }

    while(!diskey);

  }

 

}

/*************开始键函数*************/

voidstart_keyscan()

  if(start==0)

  {

    delayms(5);

     if(start==0)

       {

         run_flag=~run_flag;

       }

       while(!start);

  }

 

  if(run_flag)

  {

    TR0=1;

  }

  else if(!run_flag)

  {

    TR0=0;

    diskey_keyscan();

  }

}

/*************复位按键函数***********/

void reset_keyscan()

{

   if(reset==0)

   {

     delayms(5);

     if(reset==0)

     {

       n_ten_ms=0;

       n_second=0;

       n_minute=0;

       run_flag=0;

      

     }

     while(!reset);

   }

  

}

/************存储按键函数************/

voidstore_keyscan()

{

   if(store==0)

   {

     delayms(5);

     if(store==0)

     {

        if((arry_point>=0)&&(arry_point<5))

         {

           temp_buf[arry_point][0]=n_ten_ms;

           temp_buf[arry_point][1]=n_second;

           temp_buf[arry_point][2]=n_minute;

           arry_point++;

         }

     }

     while(!store);

   }

  

}

 

/*************按键扫描函数***********/

void keyscan()

{

  start_keyscan();

  delayms(5);

  reset_keyscan();

  delayms(5);

  store_keyscan();

  delayms(5);

}

/**********扫描函数************/

void reflash()

{

  display(1,n_ten_ms);

  display(2,n_second);

  display(3,n_minute);

}

/**********主函数**************/

int main(void)

{

        InitTimer0();

   while(1)

   {

     keyscan();

     reflash();

   }

}

 

 

/**********中断***************/

voidTimer0Interrupt(void) interrupt 1

{

    TH0 = 0x0DC;

    TL0 = 0x000;

    n_ten_ms++;

     if(n_ten_ms>=100)

     {

       n_ten_ms=0;

       n_second++;

       if(n_second>=60)

       {

         n_second=0;

         n_minute++;

         if(n_minute>=60)

         {

            n_minute=0;

         }

       }

     }

}


2018-01-28 10:50:24 huangshangcheng 阅读数 16659
  • 巫妖王51单片机开发板配套视频课程

    本课程是巫妖王51单片机开发板的配套视频课程,本课程的目标是用少的时间带大家初级入门51单片机。配合巫妖王51单片机开发板,让大家花费少的时间少的钱就能轻松开启单片机学习之路。

    19003 人正在学习 去看看 朱有鹏

 

开头唠一唠:

          趁着寒假的时间,也趁着课程设计正好是做一个万年历。就打算好好从头到尾来一遍。涨涨知识。首先说的是本人也是小白一颗,大神们能帮忙指正错误的话,不胜感激。写博客只是为了总结经验,要是帮到一部分人就更好了。我想是从硬件到软件都介绍的详细一点,还想说一说自己遇到的一些问题,可能要写的长一点。代码的话我会在后面上传。好,闲话不多说。进入正题。

概述:

         首先说一下我用到的东西,硬件方面(电路都是自己拿万能板焊的):一片51单片机,一块12864液晶,一片ds1302时钟芯片,四个按键。还有些电容、电阻、晶振什么的,下面讲到的时候再说吧。主要的就这么多吧。再简单说一下按键的功能吧,假设按键分别是k1,k2,k3,k4。首先lcd主界面是显示的当前的日期时间和四路闹钟的时间。附图。k1,k2,k3,k4最开始被按下时分别对应的功能是k1:进入时间设定模式;k2:进入日期设定模式;k3:进入闹钟设定模式;k4:进入秒表计数模式。进入不同的模式后,四个按键有都有了新的功能,首先k4一直是退出,就是退出到最开始的选四种模式。k1,k2,k3对于日期和时间设定模式是一样的功能k1:数值加1,k2:数值减1,k3:更换调的是小时还是分钟抑或年份还是月份。对于闹钟模式,k1:数值加1,k2:更换调的是小时还是分钟,k3:更换调的是第几个闹钟。对于秒表模式,k1:第一次按是开始计数,然后再按就是记录一下当前是多少秒,最多可以记录9次。k2:暂停/开始,k3:重新计数。有点绕得慌,简单的的说就是有两重循环。要是还没理解,可以看后面的代码。

 一:硬件电路

       这部分怎么说,说简单也挺简单的。但其中有个梗我现在还没过去。就是最开始我打算自己焊个下载电路在上面的,结果总是下不进去程序。这部分算是题外话了,但还是想简单说一说。最开始打算用CH340芯片直接usb转uart的,结果芯片买回来发现好像没有直插的。自己腐板子什么的又嫌太麻烦。最后打算先用usb转九针串口转成rs232电平,再用max232转成uart电平的。照着电路图一顿焊,结果果然不出我所料,不可能一下就成功下进去程序。就找问题啊,找啊找,找啊找。好像是找到了一个,就是51下程序不是有一个断电在上电的过程吗?我是这样做的,但其中好像有问题,断的这个电应该只是单片机的电,而不包括max232的电。于是又改电路,改完还是不行。算了,这个我以后搞明白了再来说说吧。

       其余的应该就不算什么难的了,找一个51最小系统原理图照着焊呗,没什么太大的问题的。法

       对了,还有几个小的点,提一提吧。51的P0口是相当于集电极开路的门电路的,记得接上拉电阻。LCD屏导完程序时,最开始如果什么也不显示的话,记得调一下3脚接的电位器调一下背光。

 

二.软件设计

    1.按键检测

        这一部分在我最开始看来是没有什么大文章的,也没有什么可以值得写的,有点基础的人几分钟就可以把程序写出了。可是当自己正真写的时候,才知道自己不懂得太多,要学的也太多。单片机的IO口最普通的两种功能,输入和输出嘛。记得自己学stm32时,IO口的输入输出是要在最开始初始化的是定义的。也就是IO口在同一时刻只能有一种功能吧,总不能又输入有输出吧。可是51呢?让我懵逼,在任何地方,包括启动文件里都没有定义IO口是输入还是输出。这让我很郁闷,总不会我让一个IO口输出一个高电平后,还可以从IO口读输入吧,那样不一直应该读到的就是我输出的高电平吗。直到我好好研究了一波51IO口的内部电路,才明白其中的玄机。

这里是最简单的P1口的内部结构图。有点数电基础的人大概可以看明白。具体我就不讲了。你可以参考这里http://www.eeworld.com.cn/mcu/article_2017120236473_2.html

由上图可见,要正确地从引脚上读入外部信息,必须先使场效应管关断,以便由外部输入的信息确定引脚的状态。为此,在作引脚读入前,必须先对该端口写入l。具有这种操作特点的输入/输出端口,称为准双向I/O口。8051单片机的P1、P2、P3都是准双向口。P0端口由于输出有三态功能,输入前,端口线已处于高阻态,无需先写入l后再作读操作。弄懂IO口的内部结构之后。我就直接上程序了,慢慢研究吧。注释的和没有用到的部分大家就不要纠结了。

/*************************************************************************************************
程序说明:按键的检测程序(基于51单片机),现在只有独立按键检测函数
Author:	xingcheng
IO说明:按键接的
**************************************************************************************************/

#include"key.h"
sbit KeyPort2=P1^5;
sbit KeyPort0=P1^7;						
sbit KeyPort1=P1^6;
sbit KeyPort3=P1^4;	//自己焊的按键接的单片机引脚
//sbit KeyPort2=P1^2;
//sbit KeyPort0=P1^0;						
//sbit KeyPort1=P1^1;
//sbit KeyPort3=P1^3;

/************************************************************************
函数名称:key_scan()
函数功能:4个独立按键检测
输入参数:无
返回值:KeyV		通过返回值的不同来确定哪个按键被按下
*************************************************************************/ 
uchar key_scan()
{
	uchar KeyV;
	KEYPORT=0xff;		//从51IO口读数据,一般要先给锁存器写1,
						//具体请参考51IO口内部结构						
	if(KeyPort0==0||KeyPort1==0||KeyPort2==0||KeyPort3==0)		//判断是否有按键按下
	//这里改成if((P3&0xf0)!=0xf0)总是错,原因可能是P3读数据不是从引脚读的
	//而是从锁存器读的,一直是0xff
	{
		delay_ms(10);		//防止抖动(拿板子实验时,发现这里延不延时并无影响)
		 if(KeyPort0==0)		//判断哪个按键被按下//
		{	
			KeyV=K1;
		}
		else if(KeyPort1==0)
		{
			KeyV= K2;
		}
		else if(KeyPort2==0)
		{	
			KeyV=K3;
		}
		else if(KeyPort3==0)
		{
			KeyV=K4;
		}
		else 
		{
			KeyV= 0;
		}		  			//判断哪个按键被按下//
		
		if(KeyV != 0)		//有按键按下时,进行松手检测
              while(KeyPort0==0||KeyPort1==0||KeyPort2==0||KeyPort3==0);
		delay_ms(10);		//延时消抖(拿板子实验,这里延时非常必要)
   }

    return KeyV;			//返回KeyV来标识哪一个按键被按下
}

/*****************************有时间再完善连按,长按等功能************************/

/*		while((KEYPORT&0Xf0)!=NO_KEY)
		{
			delay_ms(15);
			PressCnt--;
			if(PressCnt==0)
				{
					PressCnt=SHORTCNT;
					return KeyV;
				}
		}
	}
	delay_ms(15);
	if((KEYPORT&0Xf0)==NO_KEY)
	{
		ReleaseCon=0;
		return KeyV;
	}
  */
#ifndef __KEY_H#define _KEY_H

#include<reg52.h>#include"delay.h"

#ifndef uchar #define uchar  unsigned char#endif

#define KEYPORT P3  //   四个按键接在了P3口的四个引脚#define NO_KEY   0xf0#define K1    0X01#define K2    0X02#define K3    0X03#define K4   0X04#define KEYSUB    0X02#define KEYADD    0X01#define KEYSET    0X04#define KEYNEXT   0X03 //K1,2,3,4,和这些是一样的,只是写.c文件时#define LONGCNT  150#define SHORTCNT 12

 

      uchar key_scan();

#endif


 

  2.lcd12864

     这个就是真的没什么好说的了。就是记得调电位器调背光。对了,还有一个 好坑的地方,不知道各位有没有解决方法,就是那个光标(一闪一闪的那个)每次移动都是两个字两个字的移。上程序。

/***********************************************************************
程序功能:12864液晶驱动程序
其他:   只包括基本的字符串显示功能
*************************************************************************/
 

#include <LCD12864.h>
#define uchar unsigned char
#define uint  unsigned int
#define LCD_data  P0             //数据口

 
/*******************************************************************
函数名称:delay(int ms)
函数功能:延时
输入参数:ms  要延时的ms数                                                                                                                                                                      
返回值:  无
 *******************************************************************/
 void delay(int ms)
 {
     while(ms--)
 {
       uchar i;
  for(i=0;i<250;i++)  
   {
    ; ; ; ;
   }
 }
 } 

 /*******************************************************************
函数名称:lcd_busy()
函数功能:检测LCD忙状态。
输入参数:无
返回值:  result result为1时,忙等待;result为0时,闲,可写指令数据

 *******************************************************************/
bit lcd_busy()
  {                          
     bit result;
     LCD_RS = 0;
     LCD_RW = 1;
     LCD_EN = 1;
      delay_ms(1);
     result = (bit)(LCD_data&0x80);
     LCD_EN = 0;
     return(result); 
  }
 /*******************************************************************/                                                               
 /*写指令数据到LCD                                                  */
 /*RS=L,RW=L,E=高脉冲,D0-D7=指令码。                             */                                                                
 /*******************************************************************/
 void lcd_wcmd(uchar cmd)
 {                          
    while(lcd_busy());
     LCD_RS = 0;
     LCD_RW = 0;
     LCD_EN = 0;
     delay_ms(1);
     LCD_data = cmd;
    delay_ms(1);
     LCD_EN = 1;
    delay_ms(1);
     LCD_EN = 0;  
 }
 /*******************************************************************/
 /*写显示数据到LCD                                                  */
 /*RS=H,RW=L,E=高脉冲,D0-D7=数据。                               */
 /*******************************************************************/
 void lcd_wdat(uchar dat)
 {                          
    while(lcd_busy());
     LCD_RS = 1;
     LCD_RW = 0;
     LCD_EN = 0;
     LCD_data = dat;
    delay_ms(1);
     LCD_EN = 1;
   delay_ms(1);
     LCD_EN = 0; 
 }
 /*******************************************************************/
 /*  LCD初始化设定                                                  */
 /*******************************************************************/
 void lcd_init()
 { 
 

    LCD_PSB = 1;         //并口方式
     
     lcd_wcmd(0x34);      //扩充指令操作
     delay_ms(5);
     lcd_wcmd(0x30);      //基本指令操作
     delay_ms(5);
     lcd_wcmd(0x0C);      //显示开,关光标
     delay(5);
     lcd_wcmd(0x01);      //清除LCD的显示内容
     delay(5);
 }
 

/*********************************************************/
 /* 设定显示位置       X:行数                Y:列数                                   */
 /*********************************************************/
 void lcd_pos(uchar X,uchar Y)
 {                          
    uchar  pos;
    if (X==0)
      {X=0x80;}
    else if (X==1)
      {X=0x90;}
    else if (X==2)
      {X=0x88;}
    else if (X==3)
      {X=0x98;}
    pos = X+Y ;  
    lcd_wcmd(pos);     //显示地址
 }
 
/*********************************************************/
 /* 在设定位置显示字符(串)                                         */
 /*********************************************************/
 void zifu_dis (uchar X,uchar Y,uchar *dis)
 {
      uchar i;
  lcd_pos(X,Y);   
      i = 0;
     while(dis[i] != '\0')
      {                         //显示字符
        lcd_wdat(dis[i]);
        i++;
      }
 }

/**************dis_12864.h***************/#ifndef __LCD12864_H__#define __LCD12864_H__

#include"delay.h" #include <reg52.h>#define uchar unsigned char #define uint  unsigned int /*12864端口定义*/ #define LCD_data  P0             //数据口 sbit LCD_RS  =  P2^3;            //寄存器选择输入  sbit LCD_RW  =  P2^4;            //液晶读/写控制 sbit LCD_EN  =  P2^5;            //液晶使能控制 sbit LCD_PSB =  P3^3;            //串/并方式控制 

/*函数声明*/ void delay(int ms); void lcd_init();  void beep(); void  dataconv(); void lcd_pos(uchar X,uchar Y);  //确定显示位置 void zifu_dis (uchar X,uchar Y,uchar *dis); #endif


 

3.ds1302时钟

  直接给程序,相应的资料大家可以网上搜的。   

 

/**************************************************************************
  
                   		THE REAL TIMER DS1302 DRIVER LIB
  
             		COPYRIGHT (c)   2005 BY JJJ.
                       		--  ALL RIGHTS RESERVED  --
  
   File Name:       DS1302.h
   Author:          Jiang Jian Jun
   Created:         2003/7/21
   Modified:		NO
   Revision: 		1.0
   re
***************************************************************************/
#include"ds1302.h"

/***************************************************************************
函数名称:DS1302InputByte(unsigned char d)
函数功能:实时时钟写入一个字节(内部函数)
输入参数:d			要写入的数据
返回值:无
***************************************************************************/
void DS1302InputByte(unsigned char d) 	
{ 
    unsigned char i;
    ACC = d;
    for(i=8; i>0; i--)
    {
        DS1302_IO = ACC0;           	//相当于汇编中的 RRC
        DS1302_CLK = 1;
        DS1302_CLK = 0;
        ACC = ACC >> 1; 
    } 
}

/***************************************************************************
函数名称:DS1302OutputByte(void)
函数功能:实时时钟读取一个字节(内部函数)
输入参数:无
返回值:ACC			读到的数据
***************************************************************************/
unsigned char DS1302OutputByte(void) 	
{ 
    unsigned char i;
    for(i=8; i>0; i--)
    {
        ACC = ACC >>1;         			//相当于汇编中的 RRC
        ACC7 = DS1302_IO;
        DS1302_CLK = 1;
        DS1302_CLK = 0;
    } 
    return(ACC); 
}

/***************************************************************************
函数名称:Write1302(unsigned char ucAddr, unsigned char ucDa)	
函数功能:往实时时钟指定地址写数据
输入参数:ucAddr		要写数据的地址
		  ucDa			要写入的数据
返回值:无
***************************************************************************/
void Write1302(unsigned char ucAddr, unsigned char ucDa)	
{
    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302InputByte(ucAddr);       	// 地址,命令
    DS1302InputByte(ucDa);       	// 写1Byte数据
//    DS1302_CLK = 1;
    DS1302_RST = 0;
} 

/***************************************************************************
函数名称:Read1302(unsigned char ucAddr)	
函数功能:读取ds1302某地址的数据
输入参数:ucAddr		要读数据的地址
返回值:  ucData		读出的数据
***************************************************************************/
unsigned char Read1302(unsigned char ucAddr)	//读取ds1302某地址的数据
{
    unsigned char ucData;
    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302InputByte(ucAddr|0x01);        // 地址,命令 
    ucData = DS1302OutputByte();         // 读1Byte数据
//    DS1302_CLK = 1;
    DS1302_RST = 0;
    return(ucData);
}

/***************************************************************************
函数名称:DS1302_SetProtect(bit flag)        
函数功能:是否写保护
输入参数:flag		
返回值:  无 
其他:flag为1时,0x8E对应的control register最高位为1,写保护开启
***************************************************************************/
void DS1302_SetProtect(bit flag)        //是否写保护
{
	if(flag)
		Write1302(0x8E,0x80);
	else
		Write1302(0x8E,0x00);
}

/***************************************************************************
函数名称:DS1302_SetTime(unsigned char Address, unsigned char Value)        
函数功能:向指定寄存器写时间
输入参数:Address		寄存器地址
		  Value			要写入的时间(hex码)
返回值:  无 
其他:可以先用宏定义定义好year,month,hour等的地址
***************************************************************************/
void DS1302_SetTime(unsigned char Address, unsigned char Value)        // 设置时间函数
{
	DS1302_SetProtect(0);
	Write1302(Address, ((Value/10)<<4 | (Value%10))); 		//将hex码转化为BCD码
}

/***************************************************************************
函数名称:DS1302_GetTime(SYSTEMTIME *Time)
函数功能:读出日期和时间,将它们存入Time这个结构体中
输入参数:*Time		要存日期和时间的结构体的地址
返回值:  无 
***************************************************************************/
void DS1302_GetTime(SYSTEMTIME *Time)
{
	unsigned char ReadValue;
	ReadValue = Read1302(DS1302_SECOND);
	Time->Second = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);	//八进制转为十进制
	ReadValue = Read1302(DS1302_MINUTE);
	Time->Minute = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
	ReadValue = Read1302(DS1302_HOUR);
	Time->Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
	ReadValue = Read1302(DS1302_DAY);
	Time->Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);	
	ReadValue = Read1302(DS1302_WEEK);
	Time->Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
	ReadValue = Read1302(DS1302_MONTH);
	Time->Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);
	ReadValue = Read1302(DS1302_YEAR);
	Time->Year = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);	
}

/***************************************************************************
函数名称:DateToStr(SYSTEMTIME *Time)	 
函数功能:将读出的日期变成便于显示的字符形式
输入参数:*Time		要存字符的结构体
返回值:无 
***************************************************************************/
void DateToStr(SYSTEMTIME *Time)	 
{
	Time->DateString[0] = Time->Year/10+0x30 ;	 //·分离个位和十位
	Time->DateString[1] = Time->Year%10+0x30 ;
	Time->DateString[2] = '-';
	Time->DateString[3] = Time->Month/10+0x30;
	Time->DateString[4] = Time->Month%10+0x30 ;
	Time->DateString[5] = '-';
	Time->DateString[6] = Time->Day/10+0x30 ;
	Time->DateString[7] = Time->Day%10+0x30 ;  //用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了
	Time->DateString[8] = '\0';
}

/***************************************************************************
函数名称:TimeToStr(SYSTEMTIME *Time)
函数功能:将读出的时间变成便于显示的字符形式
输入参数:*Time		要存字符的结构体
返回值:无 
***************************************************************************/
void TimeToStr(SYSTEMTIME *Time)
{
	Time->TimeString[0] = Time->Hour/10+0x30 ;
	Time->TimeString[1] = Time->Hour%10+0x30 ;
	Time->TimeString[2] = ':';
	Time->TimeString[3] = Time->Minute/10+0x30 ;
	Time->TimeString[4] = Time->Minute%10+0x30 ;
	Time->TimeString[5] = ':';
	Time->TimeString[6] = Time->Second/10+0x30;
	Time->TimeString[7] = Time->Second%10+0x30 ;//用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了
	Time->DateString[8] = '\0';
}

/***************************************************************************
函数名称:Initial_DS1302(void)
函数功能:初始化ds1302
输入参数:无
返回值:无 
***************************************************************************/
void Initial_DS1302(void)
{
	unsigned char Second=Read1302(DS1302_SECOND);
	if(Second&0x80)		  
		DS1302_SetTime(DS1302_SECOND,0);
}
				   
/********************************************************************************
void BurstWrite1302(unsigned char *pWClock)	//往ds1302写入时钟数据(多字节方式)
{
    unsigned char i;
    Write1302(0x8e,0x00);         	// 控制命令,WP=0,写操作
    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302InputByte(0xbe);        	// 0xbe:时钟多字节写命令
    for (i = 8; i>0; i--)     		//8Byte = 7Byte 时钟数据 + 1Byte 控制
    {
        DS1302InputByte(*pWClock); 	// 写1Byte数据
        pWClock++;
    }
    DS1302_CLK = 1;
    DS1302_RST = 0;
} 

void BurstRead1302(unsigned char *pRClock)	//读取ds1302时钟数据(时钟多字节方式)
{
    unsigned char i;
    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302InputByte(0xbf);             	// 0xbf:时钟多字节读命令 
    for (i=8; i>0; i--) 
    {
       *pRClock = DS1302OutputByte();   // 读1Byte数据 
       pRClock++;
    }
    DS1302_CLK = 1;
    DS1302_RST = 0;
}

void DS1302_TimeStop(bit flag)           // 是否将时钟停止
{
	unsigned char Data;
	Data=Read1302(DS1302_SECOND);
	DS1302_SetProtect(0);
	if(flag)
		Write1302(DS1302_SECOND, Data|0x80);
	else
		Write1302(DS1302_SECOND, Data&0x7F);
}
********************************************************************************/

 
#ifndef _DS1302_H#define _DS1302_H

#include<reg52.h>#include"delay.h"#include<intrins.h>

#ifndef uchar #define uchar unsigned char#endifsbit  DS1302_CLK = P3^6;            //实时时钟时钟线引脚sbit  DS1302_IO  = P3^5;              //实时时钟数据线引脚sbit  DS1302_RST = P3^4;              //实时时钟复位线引脚//sbit  DS1302_CLK = P3^6;            //实时时钟时钟线引脚//sbit  DS1302_IO  = P3^4;              //实时时钟数据线引脚//sbit  DS1302_RST = P3^5;              //实时时钟复位线引脚

sbit  ACC0 = ACC^0;sbit  ACC7 = ACC^7;

typedef struct __SYSTEMTIME__{ unsigned int Second; unsigned char Minute; unsigned char Hour; unsigned char Week; unsigned char Day; unsigned char Month; unsigned char  Year; unsigned char DateString[9]; unsigned char TimeString[9];

}SYSTEMTIME; //定义的时间类型

#define AM(X) X#define PM(X) (X+12)                      // 转成24小时制#define DS1302_SECOND 0x80#define DS1302_MINUTE 0x82#define DS1302_HOUR  0x84 #define DS1302_WEEK  0x8A#define DS1302_DAY  0x86#define DS1302_MONTH 0x88#define DS1302_YEAR  0x8C#define DS1302_RAM(X) (0xC0+(X)*2)    //用于计算ds1302RAM地址的宏

void DS1302InputByte(unsigned char d) ;unsigned char DS1302OutputByte(void)  ;void Write1302(unsigned char ucAddr, unsigned char ucDa);void DS1302_SetProtect(bit flag);unsigned char Read1302(unsigned char ucAddr);void DS1302_SetTime(unsigned char Address, unsigned char Value);void DS1302_GetTime(SYSTEMTIME *Time);void DateToStr(SYSTEMTIME *Time);void TimeToStr(SYSTEMTIME *Time);void Initial_DS1302(void);#end

 

   主要的程序模块到这里基本上就算准备好了。完整的程序我压缩一下上传到资源吧,(没办法想赚点积分,理解理解),其实到这步,大家应该把完整的程序写出来也不是问题了。

   再来说说其他的吧。在使用keil软件时,总是报这样的错误*** ERROR L107: ADDRESS SPACE OVERFLOW。也是多方查找才找到问题所在。就是我们所定义变量是定义在51的RAM里的,而且供变量存储的只有256或者128个字节(看型号吧),这里看网上说在变量前面加idata,然而并不管用。还是尽量节省RAM吧。只读的数组定义前面加上code,全局变量尽量少点。不行就只能换单片机了毕竟51是一个资源很少 的单片机,不适合一些大工程。最后加上张效果图

 

算了,我还是把,所有的程序也贴上来吧,也不在乎那几个积分啦 。

下面的是按键处理程序(这个才是核心程序),和主函数。我从KEIL上复制过来的时候改了一下把edit configuration里的Encode in ANSI 改成了Chinese GB2312.要不然复制过来时中文是乱码。你复制到自己的工程里时应该要改回来吧。

#include"keyProcess.h"
void array2show(ARRAY2SHOW *arrayshow0,uchar wch);	 //函数声明//
void sec2show(SYSTEMTIME *secshow);
SYSTEMTIME showtime;  
extern SYSTEMTIME CurrentTime;
extern ARRAY2SHOW  Alarmandshow;
/****************************************************************************************************
函数名称:key_process(uchar mode)
函数功能:按键处理函数(调节日期,时间,秒表,闹钟)
输入参数:mode		用来选择模式,是修改日期,时间还是闹钟
返回值:无
****************************************************************************************************/
void key_process(uchar mode)
{	
	
	uchar Wch=0;
	uchar flag=0;
	uchar AlarmWch=0;
	uchar HourSecWch=0;
	uchar temp=0;
	switch(mode)		//在最外层循环中检测按键,确定要设置什么
	{
		DS1302_GetTime(&CurrentTime);
		case MODE0:			//设置时间
			showtime=CurrentTime;
			while(1)
			{	 				
				DateToStr(&CurrentTime);
				zifu_dis(1,0,&CurrentTime.DateString[0]); 		//修改时间不影响从1302读日期显示
				//(麻烦的思想)TArray3=show2array3(&CurrentTime.TimeString[0]);	//将显示的字符形式变成可以直接加1的形式
				if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//检测有没有按键按下,有按键按下才执行操作
				{
					switch(key_scan())			//再次检测按键
					{	  
						case K3:				//K3按下,选择时间的哪一位被更改
								 
								Wch++;
								if(Wch==3)
									Wch=0;
								break;						
						case K1:			   //K1按下,数字加一
							   //(麻烦的思想)TArray3[TimeWch]++;			//转化成单个字符形式显示 
								if(Wch==0)
								{
									showtime.Hour++;
									if(showtime.Hour==24)
										showtime.Hour=0;
								}
								else if(Wch==1)
								{
									showtime.Minute++;
									if(showtime.Minute==60)
										showtime.Minute=0;
								}
								else if(Wch==2)
								{
									showtime.Second++;
								 	if(showtime.Second==60)
										showtime.Second=0;
								}
								TimeToStr(&showtime);
								zifu_dis(0,0,&showtime.TimeString[0]); 
								break;
	
						case K2:			   //K2按下,数字减一
								//(麻烦的思想)TArray3[TimeWch]--;
								//(麻烦的思想)zifu_dis(1,0,array32show(TArray3)); 
								if(Wch==0)
								{
									showtime.Hour--;
									if(showtime.Hour==0xff)
										showtime.Hour=0;
								}
								else if(Wch==1)
								{
									showtime.Minute--;
									if(showtime.Minute==0xff)
										showtime.Minute=0;
								}
								else if(Wch==2)
								{
									showtime.Second--;
									if(showtime.Second==0xff)
										showtime.Second=0;
								}
								TimeToStr(&showtime);
								zifu_dis(0,0,&showtime.TimeString[0]); 
								break;
	
						case K4:  			  //K4按下,确定修改,
								flag=1;break;
					 }	 	
			
				}
				 if(flag==1)		 //flag为1时,确定修改,将1302里的时间重置,并退到最初的模式检测
				 {		
					DS1302_SetTime(DS1302_HOUR,showtime.Hour);	
					DS1302_SetTime(DS1302_MINUTE,showtime.Minute);
					DS1302_SetTime(DS1302_SECOND,showtime.Second);
					Wch=0;
					flag=0;
				 	break;
				 } 	
			}
			 break;
		case MODE1:			//设置日期
			showtime=CurrentTime;
			while(1)
			{
				DS1302_GetTime(&CurrentTime);
				TimeToStr(&CurrentTime);
				zifu_dis(0,0,&CurrentTime.TimeString[0]); 		   //修改日期,不影响从1302读时间显示
				//(麻烦的思想)DArray3=show2array3(&CurrentTime.DateString);	   //将显示的字符形式变成可以直接加1的形式
				if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//检测有没有按键按下,有按键按下才执行操作
				{
					switch(key_scan())	//再次检测按键
					{
						case K3:			    //K3按下,选择日期的哪一位被更改
								Wch++;
								if(Wch==3)
									Wch=0;
								break;
						case K1:			   //K1按下,数字加一
							    //(麻烦的思想)DArray3[DateWch]=DArray3[DateWch]+1;
								//(麻烦的思想)zifu_dis(0,0,array32show(DArray3)); 
								if(Wch==0)
									showtime.Year++;
								else if(Wch==1)
								{
									showtime.Month++;
									if(showtime.Month==13)
										showtime.Month=1;
								}		
								else if(Wch==2)
								{
									showtime.Day++;
									if(showtime.Month==1||showtime.Month==3||showtime.Month==5||showtime.Month==7||showtime.Month==8||showtime.Month==10||showtime.Month==12)
										if(showtime.Day==32)
											showtime.Day=0;
									else if(showtime.Month==2)
										if(showtime.Day=30)
											showtime.Day=0;
									else
										if(showtime.Day==31)
											showtime.Day=0;	
								}
								DateToStr(&showtime);
								zifu_dis(1,0,&showtime.DateString[0]); 
								break;
						case K2:			   //K2按下,数字减一
								//(麻烦的思想)DArray3[DateWch]--;
								//(麻烦的思想)zifu_dis(0,0,array32show(DArray3)); 
								if(Wch==0)
									showtime.Year--;
								else if(Wch==1)
									showtime.Month--;
								else if(Wch==2)
									showtime.Day--;
								DateToStr(&showtime);
								zifu_dis(1,0,&showtime.DateString[0]); 
								break;
						case K4: 
											   //K4按下退出此循环,回到模式检测循环
								flag=1;
								break;
					 }
				}
				 if(flag==1)	//flag为1时,确定修改,将1302里的日期重置,并退到最初的模式检测
				 {
				 	DS1302_SetTime(DS1302_YEAR,showtime.Year);	
					DS1302_SetTime(DS1302_MONTH,showtime.Month);
					DS1302_SetTime(DS1302_DAY,showtime.Day);
					flag=0;
					Wch=0;
				 	break;
					
				 }
			}
			 break;
		case MODE2:			//设置闹钟
			while(1)
			{	
				DS1302_GetTime(&CurrentTime);
				DateToStr(&CurrentTime);
				TimeToStr(&CurrentTime);
				zifu_dis(0,0,&CurrentTime.TimeString[0]); 	 //在设置闹钟时不让时间的显示停下
				if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//检测有没有按键按下,有按键按下才执行操作
				{
					switch(key_scan())	//再次检测按键
					{
						case K3:			    //K1按下,选择哪一个闹钟被更改
								AlarmWch++;
								if(AlarmWch==4)
									AlarmWch=0;
								break;
						case K2:			   //K2按下,选择闹钟的小时还是秒被更改
							    HourSecWch++;
								if(HourSecWch==2)
									HourSecWch=0;
								break;							   
						case K1:			   //K3按下,数字加1
								Alarmandshow.Alarm[AlarmWch][HourSecWch]++;
								if(Alarmandshow.Alarm[AlarmWch][HourSecWch]==60)
									Alarmandshow.Alarm[AlarmWch][HourSecWch]=0;
								array2show(&Alarmandshow,AlarmWch);
					
								zifu_dis(2+AlarmWch%2,2+AlarmWch/2*3,&Alarmandshow.showstring[0]);
								break;
						case K4: 			   //K4按下退出此循环,回到模式检测循环
								flag=1;break;
					 }
				}
				 if(flag==1)
				 {
				 	AlarmWch=0;
					HourSecWch=0;		//最好要将AlarmWch,HourSecWch清零,后面要用
				 	flag=0;
				 	break;
					
				 }
			}	
			break;	
		case MODE3:					 //秒表
				
				while(1)	//此层循环用来显示秒表的初始界面	  	
				{
					temp=0;
					showtime.Second=0;
					lcd_init();		
					zifu_dis(0,3,"00.0");
					if(key_scan()==K1)		//K1按下,秒表开始计时
					{
						while(1)	//此层循环是秒表开始后的循环
							{		 
								delay_ms(73);		//再算上程序执行的时间,一共为100ms				 
								sec2show(&showtime);
								zifu_dis(0,3,&showtime.TimeString[0]);	
								if(flag==0)
								showtime.Second++;		//每过100ms,Second++,
								switch(key_scan())
								{
									case K1:
										zifu_dis(temp/3+1,temp*3%9,&showtime.TimeString[0]);			
										temp++;		//读一下秒表,记录下
										if(temp==9)
											temp=0;
										break;
									case K2:
										flag=~flag;
										break;
									case K3:
										flag=2;
										break;
									case K4:
										flag=1;
										break;
								}	
								if(flag==2||flag==1)
								{
									if(flag==2)
										flag=0;
									break;	
								}
							}
					}
					if(flag==1)
					{
						flag=0;
						break;
					}
				}
			lcd_init();
			for(;AlarmWch<4;AlarmWch++)
			{
				//arrayshow.array2[AlarmWch][HourSecWch]=0;
				array2show(&Alarmandshow,AlarmWch);
				zifu_dis(AlarmWch/2+2,AlarmWch%2*3+2,&Alarmandshow.showstring[0]);
			}
			zifu_dis(2,0,"闹钟");
			AlarmWch=0;
			HourSecWch=0;
			break;				
	}
}
 
/***********************************************************************************************
函数名称:array32show(uchar *array3)
函数功能:将存在array[3]里的小时,分钟,秒转换成可以直接显示的形式
输入参数:*array3 		array[3]的首地址
返回值:  show			show[9]的首地址,可以直接用来显示
*************************************************************************************************/
/*uchar *array32show(uchar *array3)
{
	uchar show[5];
	show[0] = *array3/10+0x30 ;
	show[1] = *array3++%10+0x30 ;
	show[2] = ':';
	show[3] = *array3/10+0x30 ;
	show[4] = *array3%10+0x30 ;//用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了
	show[5] = '\0';
	return show;	
} *///没有用到

/***********************************************************************************************
函数名称:show2array3(uchar *show)
函数功能:将存在show[]里的可直接显示的字符转换成可以直接加一的array[3]
输入参数:*show 		show数组的首地址
返回值:  array3		array数组的首地址,可以直接用来做加一操作
*************************************************************************************************/
/*uchar *show2array3(uchar *show)
{
	uchar array3[3];
	array3[0]=(show[0]-0x30)*10+(show[1]-0x30);
	array3[1]=(show[3]-0x30)*10+(show[4]-0x30);		
	array3[2]=(show[6]-0x30)*10+(show[7]-0x30);
	return array3;	
}*/ 

void array2show(ARRAY2SHOW *arrayshow0,uchar wch)
{
	arrayshow0->showstring[0] = arrayshow0->Alarm[wch][0]/10+0x30 ;
	arrayshow0->showstring[1] = arrayshow0->Alarm[wch][0]%10+0x30 ;
	arrayshow0->showstring[2] =':';
	arrayshow0->showstring[3] = arrayshow0->Alarm[wch][1]/10+0x30 ;
	arrayshow0->showstring[4] = arrayshow0->Alarm[wch][1]%10+0x30 ;
	//用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了
	arrayshow0->showstring[5] = '\0';
}

void sec2show(SYSTEMTIME *secshow)
{
	secshow->TimeString[0]=secshow->Second/100+0x30;
	secshow->TimeString[1]=secshow->Second%100/10+0x30;
	secshow->TimeString[2]='.';
	secshow->TimeString[3]=secshow->Second%10+0x30;
	secshow->TimeString[4]='\0';
}
 
#ifndef __KEYPROCESS_H
#define _KEYPROCESS_H

#include<reg52.h>
#include<stdio.h>
#include"delay.h"
#include"key.h"
#include"ds1302.h"
#include"LCD12864.h"


#ifndef uchar
	#define uchar 	unsigned char
#endif

typedef struct _ARRAYSHOW_
{
	unsigned char   showstring[6];
	unsigned char  Alarm[4][2];
}ARRAY2SHOW;

typedef struct _SHOW_
{
	unsigned char   showstring[6];
	unsigned char  array2[4][2];
}show;

#define MODE0 		0X00
#define MODE1		0X01
#define MODE2 		0X02
#define MODE3		0X03
#define TIMESET		MODE0	
#define DATESET		MODE1
#define ALARMSET	MODE2
#define SECCON		MODE3

void key_process(uchar mode);

#endif

/***********************************************************************************
程序说明:利用12864液晶和ds1302配合按键实现 万年历,四路可调闹钟,秒表(基于51单片机)
作者:哈尔滨工程大学  黄子炫
***********************************************************************************/
#include <reg52.h>
#include<stdio.h>
#include"delay.h"
#include"ds1302.h"
#include"LCD12864.h"
#include"key.h"
#include"buzzer.h"
#include"keyProcess.h"
SYSTEMTIME  CurrentTime;	//存储当前从ds1302中读到的时间日期等
ARRAY2SHOW  Alarmandshow;	//存储闹钟的时间,和用于闹钟显示的字符串
char code table[7][20]={{"星期壹"},{"星期贰"},{"星期叁"},{"星期肆"},{"星期伍"},{"星期陆"},{"星期日"}};
sbit led=P1^7;
void main()
{	
	uchar mode;
	Initial_DS1302();				//ds1302初始化
//	DS1302_SetTime(DS1302_HOUR,10);	
//	DS1302_SetTime(DS1302_MINUTE,0);
//	DS1302_SetTime(DS1302_SECOND,0);//向ds1302中写初始时间
//	DS1302_SetTime(DS1302_YEAR,17);	
//	DS1302_SetTime(DS1302_MONTH,1);
//	DS1302_SetTime(DS1302_DAY,16);	//向ds1302中写初始日期
	DS1302_SetTime(DS1302_WEEK,3);
	
	lcd_init();						//lcd12864初始化
	zifu_dis(2,0,"闹钟");
	zifu_dis(2,2,"00:00");
	zifu_dis(2,5,"00:00");
	zifu_dis(3,2,"00:00");
	zifu_dis(3,5,"00:00");			 //设置闹钟的初始显示
  	
	while(1)
	{
		if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)
		{				
			switch (key_scan())
			{	
				case K1: mode=MODE0;break;		//MODE0设置时间
				case K2: mode=MODE1;break;		//MODE0设置日期
				case K3: mode=MODE2;break;		//MODE0设置闹钟
				case K4: mode=MODE3;break;		//MODE0设置秒表
			}
			key_process(mode);				   //按键处理函数
		}		
		DS1302_GetTime(&CurrentTime);
		DateToStr(&CurrentTime);
		TimeToStr(&CurrentTime);
		zifu_dis(0,0,&CurrentTime.TimeString[0]); 
		zifu_dis(1,0,&CurrentTime.DateString[0]); 		 //读出ds1302里的时间,在lcd上显示
		zifu_dis(1,4,table[CurrentTime.Week]);
		
		if((CurrentTime.Hour==Alarmandshow.Alarm[0][0]&&CurrentTime.Minute==Alarmandshow.Alarm[0][1])||
		   (CurrentTime.Hour==Alarmandshow.Alarm[1][0]&&CurrentTime.Minute==Alarmandshow.Alarm[1][1])||
		   (CurrentTime.Hour==Alarmandshow.Alarm[2][0]&&CurrentTime.Minute==Alarmandshow.Alarm[2][1])||
		   (CurrentTime.Hour==Alarmandshow.Alarm[3][0]&&CurrentTime.Minute==Alarmandshow.Alarm[3][1]))
		   //检查所设的闹钟时间和现在的时间是否一致,是则响蜂鸣器。
		   
				buzzer_delay();
	}		
}
												
这个是蜂鸣器要用到的,就是一个IO口拉高拉低。
 
#include"buzzer.h"

void buzzer_on(void)
{
		BuzzerPort=0;
}

void buzzer_off(void)
{
		BuzzerPort=1;
}

void buzzer_delay(void)
{
		BuzzerPort=0;
		delay_ms(400);
		BuzzerPort=1;
		delay_ms(400);
}
#ifndef __BUZZER_H
#define _BUZZER_H

#include<reg52.h>
#include"delay.h"

#ifndef uchar
	#define uchar 	unsigned char
#endif

sbit BuzzerPort=P2^2;

void buzzer_on(void);
void buzzer_off(void);
void buzzer_delay(void);

#endif

 完工~

 

 

 

 

 

 

 

 

 

 

   

 

 

 

 

     

 

 

         

 

 

搜索

2019-04-19 16:19:35 sinat_30457013 阅读数 561
  • 巫妖王51单片机开发板配套视频课程

    本课程是巫妖王51单片机开发板的配套视频课程,本课程的目标是用少的时间带大家初级入门51单片机。配合巫妖王51单片机开发板,让大家花费少的时间少的钱就能轻松开启单片机学习之路。

    19003 人正在学习 去看看 朱有鹏

本例子为用汇编在普中单片机上实现一个简单的秒表功能,加一个清零按键。

新建keil工程,新建.asm文件并加入工程,不添加startup.a51文件

在写代码之前,讲一些后面写51汇编必要的基础知识,因为汇编与硬件息息相关,这些不知道就没法写

1、单片机上电后的大致运行过程

单片机内部有自带的检测烧写程序,当上电后在一定引脚上检测到一定信号,则接收串口发来的数据烧写到程序存储器中(从0000H开始),

再执行烧写好的程序,否则执行上一次烧写的程序。

单片机执行的第一条指令是程序存储器的0000H里内容,一般在这里写一个跳转指令到主程序起始地址(比如1000H之类的,只是举个例子,不一定这里)

在执行主程序时,若发生中断,会跳到程序存储器的固定地址(比如T0会跳到000BH处),一般在这写个跳转指令,使程序跳转到中断处理程序处

这里的固定地址就是所谓的中断向量。所以我们写51汇编程序首先得在这些地址处写好跳转指令

 2、部分常用到的寄存器

单片机配置相关的特殊寄存器比如P0TMOD等就不说了,和写C语言的时候差不多,写汇编接触比较多的是工作寄存器

AB寄存器、DPTR寄存器、PSW寄存器、SP寄存器等,这里特别提下工作寄存器,其他自行百度即可。

工作寄存器有4组,每组都是8个工作寄存器R0~R7,通过PSW中的RS1、RS0两位来选择使用哪一组,如果不选,默认是选择第0组。
RS1RS0组合为00时,选中第0组工作寄存器,R0~R7地址为00H~07H;(这里的地址为内部RAM区)
RS1RS0组合为01时,选中第1组工作寄存器,R0~R7地址为08H~0FH;
RS1RS0组合为10时,选中第2组工作寄存器,R0~R7地址为10H~17H;
RS1RS0组合为11时,选中第3组工作寄存器,R0~R7地址为18H~1FH。
进入中断时,可以通过改变RS1RS0来分别使用不同的R0-R7

 351单片机的存储器结构

https://images2018.cnblogs.com/blog/1104670/201806/1104670-20180622130913264-464046793.png

写汇编代码一定要清楚上面那张图,因为汇编是直接操作上述存储器,不同区域的存储器可能会用到不一样的指令。

程序存储器用于保存程序编译后的指令,以及只需要读取,不需要修改的常量,读取用MOVC命令实现,例子如下

disBuf: DB 00H,01H,02H,03H,04H,05H,06H,07H
MOV DPTR,#disBuf
MOV A,00H
;代表读取disBuf的第一个,01H则读第二个
MOVC A,@A+DPTR;rom取数据

这里我还有点不懂的地方,DB这样的伪指令在单片机里难道是只能用来定义常量(在rom里)的吗?8086中好像是变量

我试了几次也查了下没找到不错的回答,最后定义变量是直接   ‘变量名 EQU  内部ram的地址来实现的

数据存储器即ram区,分为片内和片外,访问用到的指令不一样,片内直接MOV就行,片外ram只能通过A寄存器来读取或访问(具体指令看后面附上的表)

 这里可以发现51单片机数据存储区和代码存储区是分开的,从这点来看它是哈佛结构的,但是取指和读数据又是分时的,网上有人说这是冯诺伊曼结构的特点。。。所以有争论

 

4、需要掌握的调试技巧

汇编毕竟直接操作存储器,所以debug使用非常重要,下面稍微提一下关键的几个使用

https://images2018.cnblogs.com/blog/1104670/201806/1104670-20180622134136763-1689878131.png

第一块地方从左到右分别是:重置(丛头开始)、全速运行、停止、单步运行(进入子程序)、单步运行(子程序不进去单步运行)

第二块能看到常用的寄存器的值,这里的R0是指第一组的R0,想看其他组得用第三块的功能

第三块用于查看存储器里的值,如下:

Memory窗口中输入地址值,得到的结果是程序代码区的内容。要查看各种内存区域的内容,只要在Address框内输入字母:地址即可显示相应的内存值。其中字母可以是CDIX,分别代表的意义是:

  C:代码存储空间

  D:直接寻址片内存储空间

  I :间接寻址片内存储空间

  X:扩展的外部RAM空间

如输入“d:0x30”就可显示直接寻址片内30H存储空间的内容了。

还可以使用右键“Modify Memory”选项修改指定内存的内容。

第四块、在指定地方打断点

 

5、汇编代码注意点

我个人最容易错的地方就是,给某个寄存器赋常数值经常忘记打#号,汇编和C语言的习惯不一样,赋常数值前面一定要带#号,否则就会被当成该数值代表的地址里的内容

MOV R00x01就会把内部RAM地址为0x01的存储单元里内容拷到R0,要想给R0赋值为1应写为:MOV R0#0x01

还有就是代码段自己手动安排起始位置一定要保证各段之间没有重合(我不知道能不能自动安排)

 

好了,知道了这些,对照这指令就可以开始写汇编代码了,我直接贴上来吧

duan       EQU     P0;

wei        EQU     P2;

key        BIT     P3.7;

 

ORG   0000H

AJMP  MAIN;绝对转移指令,2kb范围(11位)内跳转 LJMP16位 64kb范围内跳转

;短转移指令的功能是先使程序计数器PC加1两次(即:取出指令码),然后把加2后的地址和rel相加作为目标转移地址。因此,短转移指令是一条相对转移指令,是一条双字节双周期指令

 

 

ORG   0030H;指明后面的程序从程序存储器的0030H单元开始存放

DELAY200US:            ;@11.0592MHz

    NOP

    NOP

    NOP

    PUSH 30H

    PUSH 31H

    MOV 30H,#2

    MOV 31H,#179

NEXT:

    DJNZ 31H,NEXT

    DJNZ 30H,NEXT

    POP 31H

    POP 30H

    RET

 

 

 

ORG 0060H

;DISPLAY子程序

DISPLAY:

PUSH ACC;不能写A,此处ACC代表地址,push后跟地址,代表把地址内的内容压入栈中

PUSH 00H;R0

PUSH 06H;R6

PUSH 07H;R7

PUSH 83H;DPH

PUSH 82H;DPL

MOV R6,#01H;位选数据,01指的是缓冲区最低位数据

MOV R7,#08H;循环次数

FLAG:

MOV duan,#0x00;消影

MOV A,R6

CPL A;取反

MOV wei,A;位选

 

MOV   A,#disBufDat

ADD   A,R7

SUBB  A,#0X08

MOV   R0,A

MOV   A,@R0;读出要显示的数据到A

MOV   DPTR,#disTab

MOVC  A,@A+DPTR;从rom取数据,取出要显示的数据对应的段码   

MOV   duan,A;段选

 

MOV A,R6

RL A

MOV R6,A;更新下一次位选

 

LCALL DELAY200US

DJNZ R7,FLAG

POP 82H;DPL

POP 83H;DPH

POP 07H

POP 06H

POP 00H

POP ACC

RET

 

 

ORG 0100H

;定时器中断0初始化

T0_INIT:

MOV TMOD,#0X01

MOV TH0,#0X3C

MOV TL0,#0XB0

SETB EA

SETB TR0

SETB ET0

RET

 

ORG 0130H

;T0中断处理程序

INT_TIMERE0:

PUSH ACC

SETB RS0

MOV TH0,#0X3C

MOV TL0,#0XB0

INC R0

MOV A,R0

SUBB A,#0X14

JB CY,SECFLAG

MOV R0,#0x00

INC sec

SECFLAG:

CLR  RS0

POP ACC

RETI

 

 

ORG 000BH ;定时器/计数器T0入口地址

LJMP INT_TIMERE0 ;跳转到定时器/计数器中断服务程序中去   

 

disTab: DB 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00,0x40;0-f,空白,横杠的段选数据

disBufDat EQU 47H;定义显示缓冲数据变量区,8个

disBufDatHead EQU 40H //单片机上显示在最左边

sec EQU 48H

   

   

;主程序

ORG 0180H

MAIN:

MOV SP,#0X60;将0x60到0x7f设为堆栈区

LCALL T0_INIT

MOV disBufDatHead,#0X00

MOV disBufDatHead+1,#0X00

MOV disBufDatHead+2,#0X11

MOV disBufDatHead+3,#0X11

MOV disBufDatHead+4,#0X11

MOV disBufDatHead+5,#0X11

MOV disBufDatHead+6,#0X11

MOV disBufDatHead+7,#0X11

MOV sec,#0X3A

 

WHILE:

JB key,KEYSCAN

MOV sec,0x00

KEYSCAN:

MOV A,sec

SUBB A,#3CH;超过60s归零

JB   CY,CLEAR

MOV sec,#0X00;clr加ram地址无效

CLEAR:

MOV A,sec

MOV B,#0AH

DIV AB;A/B,商存到A中,余数存B中

MOV disBufDatHead,A

MOV disBufDatHead+1,B

LCALL DISPLAY

LJMP   WHILE;循环

END;

   

大致思路就是先初始化T0,和显示的缓冲区,再在后面的while循环里不断把sec变量拆开写入显示缓冲区(若大于等于60清零),调用DISPLAY子程序显示,若按键按下,清零sec

显示子程序比较难看懂,功能就是把内部ram40h47h8个字节当作显示缓冲区,这八个字节对应8个数码管,每个数码管显示对应字节里的内容

本来是打算按键用外部中断的。。。结果一用时钟就不走了,找了半天没找到问题,只能先完成任务,以后有空再说。

 

指令说明:https://blog.csdn.net/qq_35535992/article/details/52702922

https://wenku.baidu.com/view/33a31c6e793e0912a21614791711cc7931b778c5.html

 

2019-02-12 09:11:20 weixin_43425544 阅读数 121
  • 巫妖王51单片机开发板配套视频课程

    本课程是巫妖王51单片机开发板的配套视频课程,本课程的目标是用少的时间带大家初级入门51单片机。配合巫妖王51单片机开发板,让大家花费少的时间少的钱就能轻松开启单片机学习之路。

    19003 人正在学习 去看看 朱有鹏

使用单片机时,编程会高频率用到延时,如led灯闪烁,蜂鸣器长短鸣,秒表应用等等。首先考虑软件延时,但这个时间不精确,占用硬件资源。使用延时函数是,其他函数不能运行。这个方案cut掉。硬件延时,嗯,误差非常小。但成本较高,且参数调节不便。这个也不行。选择采用定时器调节时间,不占用cpu时间,能与CPU并行工作,实现精确的定时和计数。又可以通过编程设置其他工作方式和其他参数,因此使用非常方便。下面介绍定时器的使用。

概述

定时器系统是单片机内部一个独立的硬件部分,它与cpu和晶振通过内部某些控制线连接并相互作用,cpu一旦设置开启定时功能后,定时器便在晶振的作用下自动计时,但定时器的计数器计满后,会产生中断。计数时间一次为12/晶振频率。在晶振频率为11.0595mhz时,计数一次时间约等于1.09us。
89c52单片机定时器系统有三个定时器/计数器,分别是定时器T0,定时器T1,T2定时器。他们既有定时器功能,也有计数器功能。T0,T1有四种工作方式,T2有三种工作模式。

内部结构

定时器结构
由上图可知,定时器系统有两个寄存器组成,分别是TCON,TMOD。还可看出tcon控制外部中断,tmod控制定时器工作方式。tmod寄存器分为两部分,高四位为t1定时器控制位,低四位为t0定时器控制位。t0定时器与th0,tl0两个8位计数器有关,。t1定时器与th1,tl1两个8位计数器有关。上图信息就这么多,接下来看看两个寄存器相关数据。

寄存器TCON

寄存器TCON
TF1:定时器 1 溢出标志。当定时器/计数器 1 溢出时,由 硬件置位;当主机响应中断,
转向中断服务程序时,由硬件清零。
TR1:定时器 1 运行控制位, 由软件置位/ 复位来开启或关闭定时器/计数器 1。
TF0:定时器 0 溢出标志。当定时器/计数器 0 溢出时,由 硬件置位;当主机响应中断,
转向中断服务程序时,由硬件清零。
TR0:定时器 0 运行控制位,由 软件置位/ 复位来开启或关闭定时器/计数器 0。
IE1:外部中断 1 跳变中断请求标志,当检测到 INT1 发生 1 到 0 的跳变时,由硬件置位;当主机响应中断, 转向中断服务程序时,由硬件清零。
IT1:外部中断 1 触发方式控制位,由 软件置位或清零来选择外部中断 1 的跳变/电平触发中断请求。IT1=0 时,外部中断 1 为电平触发方式,当 INT1 输入低电平时,置位 IE1。
采用电平触发方式时,外部中断源必须保持低电平有效,直到该中断被 CPU 响应,同时在该中断服务程序执行完之前,外部中断源必须被清除,否则将产生另一次中断。IT1=1 时,外部中断 1 为边沿触发方式,在对 INT1 的相邻两次采样中,如果一个周期中为高电平,接下来的周期为低电平,则置位 IE1,表示外部中断 1 正在向 CPU 申请中断。直到该中断被CPU 响应时,才被 硬件清零。
IE0:外部中断 0 跳变中断请求标志,当检测到 INT1 发生 1 到 0 的跳变时,由硬件置位;当主机响应中断, 转向中断服务程序时,由硬件清零。
IT0:外部中断 0 触发方式控制位,应用同 IT1。

这个寄存器与中断有关,支持位寻址,就是可以对其每一位进行单独操作。定时器工作就是在一个特定的间隔(与晶振有关)加1,等到加到定时器溢满时,会触发外部中断。这两个定时器都是16位可编程定时器/计数器。最大可装2的16次方。,就是65535.,定时器在晶振为11.0592MHZ时,间隔约等于1.09us。
(关于中断的知识在上一篇文章有详细介绍,在这里就不累赘。)

寄存器TMOD

寄存器TMOD
GATE:门控制位,当 GATEx=1 时,控制寄存器 TCON 的 TRx=1(x=0 或 1)。当 GATEx=0 时,定时器启动与停止仅受寄存器中的TRx来控制(x=0 或 1)。
C / :定时器、计数器方式选择位,该位为 1 时为计数器,为 0 时为定时器。
M0:定时器/计数器工作模式选择位。
M1:定时器/计数器工作模式选择位。
注:高四位是T1定时器控制位,低四位是T0定时器控制位

工作方式如下图所示
4种工作方式
这个寄存器是控制定时器的工作方式与哪个定时器工作。tmod寄存器支持位寻址,编程时只可以是总线式,不可以单个控制。因为两个定时器命名一样,单个控制会弄混。下面示范写法:
TMOD=0X01; // 0000 0001
可以看出只有最低位为1,即T0定时器的M0=1;对照上图数据可知,这是使用T0定时器的定时功能中的工作方式1,就是16位定时器。
TMOD=0X20; //0010 0000
这个看数据手册得知是T1定时器的定时功能中的工作方式2,即具有自动重载的8位定时器。

附:定时器使用需要用到中断,这里将中断的中断源优先级放在下面。
中断源优先级

定时器使用

中断函数

在介绍定时器使用时,先介绍中断函数,C51的中断格式如下
void 函数名()iinterrupt 工作组
{
中断内容;
}
中断函数不能返回值,所以前缀为void,函数名可以任意取,但一般建议使用有意义的名字,到时候检查也可以明白是什么函数,interrupt是c语言中的一个关键字–中断,记住就行。工作组就是对应中断源,比如说,使用T1定时器,那中断源就是定时器1中断,这时工作组就是3。下面示范:
void timer_t1() interrupt 3
{
TH1=(0XE0);
TL1=(0X07);
}
上面这个实例很容易理解,对着手册看就知道是T1定时器中断。

定时器初值计算

中断函数明白后,如何定时还是不清楚。开启定时器后,定时器就会开始计数,每次加1的间隔是固定的,而且到达最大值就会溢出,触发中断。这样子的话我们可以设定一个初值,初值到最大值的时间假设为50ms,那样的话定是的效果就达到了。定时器加1时间间隔约等于1.09us,定时器在没有赋值时默认初值为0,最大值为65535,计算可得655351.09us约等于72ms,没有赋初值一次定时最大为72ms。可以设置一个初值,就拿50ms来说,501000/1.09约等于45872,也就是说经过45872次计数时间为50ms,那初值就是65535-45872=19663。

大概了解定时器,来看看如何使用,定时器是由16位可编程寄存器组成,分为高8位,即THX(X=1或X=0)低8位TLX(X=1或X=0)。为了更好定时,肯定会选择赋初值。这里介绍一种简单的方法,不用计算。既然它们分为两部分,可以利用这一特点。举个例子:
选择10ms时间,T1定时器。这里以晶振为12MHZ为准,因为11.0592MHZ计算麻烦,这样计数一次就是1us;
TH1=(65535-10000)/256;//表示初值为55535,/256表示高8位的初值,很好理解,低 8位最多存2的8次方=256个数,每满一次高8位加1, /256表示高8位加了多少次。

TL1=(65535-10000)%256;// %256表示不满256最后留下的数。

使用步骤

计算知道后,来看看定时器使用步骤:

  1. 对TMOD赋值,确定T0和T1的工作方式
  2. 计算初值,赋值TH0,TL0或TH1,TH1
  3. 对IE赋值,启动中断
  4. TR0或TR1置位,启动定时器
  5. 处理中断函数,定时器中断后变成默认值0,要重新赋初值

例程

#inlclude<reg52.h>   //头文件
sbit led=P1^1;   //位定义
unsigned char count;  //定义计数次数变量
mian()  //主函数
{
	TMOD=0X01;   //设置定时器T0 定时器功能 工作模式1
	TH0=(65536-50000)/256;     //赋初值   50ms
	TL0=(65536-50000)%256;
	EA=1;   //打开总中断
	ET0=1;  //开定时器0中断
	TR0=1;  //启动定时器	
}
void timer_t0() intterrupt 1
{
	TH0=(65536-50000)/256;    //重新赋值
	TL0=(65536-50000)%256;
	count++;     //每中断一次加1
	if(count==20)  //count==20时,说明1秒到
	{
		count=0;  //count清零,重新等待1秒的到来
		led=~led;   //led状态取反
	}
}

上面注释已经很清楚,按照步骤来,一步一步设置参数,基本不会出错。在中断函数中设置一个标志位,中断变化,变化成何值时,再状态变化。基本就是这个套路。给个建议,中断函数不要写太多东西,不然会出错。试想一下,假如进入中断需5ms,但在中断函数中命令要运行10ms,命令没有运行完,又进入中断,就会出错。

总结

定时器就这样子,不会很难,一些命令在数据手册都有,忘记了就重新看一下,写多了就会记住·,重要的是记住步骤,记住编程思想,在写之前在脑中想一下步骤,或在纸上把思路画一下,那里不通就会跃然于纸上,再稍加思索一般就行了。

没有更多推荐了,返回首页