简介:这是一份基于AT89C51单片机的万年历毕业设计论文,面向电子信息、嵌入式相关专业的学生,用于课程设计或毕业设计参考。文档系统介绍了硬件结构与软件设计方法:以AT89C51为控制器,配合串行时钟芯片DS1302记录日历和时间,使用LCD12864液晶显示年、月、日、周、时、分、秒及农历信息,并支持闰年补偿、农历转换、电子闹钟、计时器和指定日期查询等功能。内容涵盖课题背景、目的意义、器件选型、外围电路、C语言编程与调试结果分析。资源包内为1个doc文档,大小794KB,正文约26页,结构完整,包含系统总体设计、器件引脚说明、硬件电路和软件流程,可作为单片机应用、电子万年历课题的写作模板和设计参照,文档还给出了设计步骤与调试方法,便于参照实现。已有328人学习下载,适合需要快速梳理AT89C51+DS1302+LCD12864方案的读者。
1. 项目概述与整体思路拆解
1.1 这个题目到底在做什么
说起大学四年的电子信息类、自动化类专业的毕业设计,"万年历"绝对算得上经典中的经典。很多同学拿到这个题目,第一反应是——这不就是个电子表吗?有什么好做的?但等你真正开始动手,你会发现它几乎囊括了单片机应用开发的所有基础知识点:最小系统设计、时钟芯片的使用、液晶显示驱动、按键交互处理、掉电保护机制,甚至低功耗设计思路。可以说,这个题目是一个"麻雀虽小,五脏俱全"的综合训练项目。
我见过不少同学在选题时看不起这类基础项目,觉得不如做什么智能小车、物联网监控之类的高大上。但如果你去问已经工作几年的嵌入式工程师,他们大多会告诉你:万年历这个项目做扎实了,比很多看起来花哨的课题都值钱。原因很简单,它涉及的技术栈非常贴近工业产品研发的基本功——你不是在玩一块开发板,而是在设计一个具备“产品雏形”的独立系统。AT89C51这颗芯片虽然老,但它的架构、时序逻辑、外设扩展方式,在今天学习STC、STM32甚至GD32时依然通用。把它的原理吃透了,后面学什么芯片都会快很多。
1.2 设计目标的定位与分析
在做这类毕业设计时,第一个坑是目标不清晰。很多同学上来就想着"我要做一个功能越多越好的万年历",结果做出来的东西像一个大杂烩——又要显示温度、又要做闹钟、又要整倒计时,代码写了一堆,最后连最基本的走时准确都保证不了。毕设答辩时被老师一句"你的时钟误差每天多少秒"问得当场卡壳。
我做这个项目时,给自己定了几条清晰的目标,给各位同学做个参考:
- 基本时间显示:年、月、日、星期、时、分、秒,这是底线功能
- 走时准确可靠:在常温环境下,月误差不超过10秒,这靠DS1302这类专用时钟芯片很容易实现
- 掉电时间不丢失:断电再上电,时间不用重新设置,这是"万年历"区别于"电子表"的核心
- 简单的人机交互:通过按键完成时间校准和闹钟设置,交互逻辑清晰
- 信息完整展示:用LCD1602或者其他字符型液晶清晰展示所有信息
- 系统稳定、不乱码、不跑飞:这是对固件质量的基本要求
这里有一个特别重要的设计取舍问题——很多人会想:既然AT89C51内部有定时器,为什么我不用定时器来做时钟,非要外接一个DS1302芯片?我的答案是:彻底放弃这个念头。定时器做时钟有两个致命缺陷,第一个是51单片机的定时器依靠晶振的机器周期累加计时,但AT89C51内部的定时器没有独立的RTC供电机制,单片机一掉电,时间就全部丢失,所谓的"万年历"就名存实亡了;第二个是定时器中断会占用CPU资源,而且长时间运行累计误差会非常大,即便你通过软件补偿,也很难做到高精度。而DS1302这种专用时钟芯片内部有独立的振荡电路和计数器,只要给它接一个32.768kHz的晶振和一块纽扣电池,它就能自己走时,单片机掉电了它还在工作。所以,外接RTC芯片不是可选项,而是必选项。
2. 硬件系统设计:从最小系统到外围电路
2.1 AT89C51最小系统的搭建要点
AT89C51是美国ATMEL公司(现在已经并入Microchip)在Intel 8051内核基础上推出的Flash版本单片机,内部有4KB的Flash程序存储器和128字节的RAM。4KB对于这个项目来说,如果全用汇编来实现,需要精打细算;但如果用C51来写,代码量基本上会非常紧张,甚至可能不够用。这里就要说到一个很多初学者没注意到的选型问题——AT89C51的4KB Flash确实捉襟见肘,所以我们在做毕设时,通常会选择AT89S51或者AT89S52,尤其是AT89S52,它的Flash是8KB,价格相差不大,兼容性几乎一致,Board上引脚完全兼容,程序可以直接烧录。如果你的指导老师硬性规定了必须用AT89C51,那你写程序时就要刻意优化代码体积,避免使用大段的printf格式化输出和浮点运算,否则编译后程序会超出内存。用Keil写代码时,遇到"*** ERROR L107: ADDRESS SPACE OVERFLOW"这类报错,八成就是代码量超出了芯片的存储空间限制,只能精简逻辑或换大Flash的芯片。
最小系统本身很简单,但要注意几个细节:
首先是晶振电路。AT89C51外接晶振一般用12MHz,这是计算波特率最方便的频率,因为12MHz除以12分频正好是1MHz的机器周期。也可以使用11.0592MHz,做串口通信时计算波特率能精确到比特级,但如果只做万年历不做串口通信,12MHz就够用了。晶振两端分别接两个20pF到30pF的负载电容,电容的另一端接地,这两个电容的作用是配合晶振起振,并微调振荡频率。没有这两个电容晶振也可能起振,但频率会不稳定,走时可能时快时慢。
其次是复位电路。AT89C51是高电平复位,RST引脚上电瞬间需要一个高电平脉冲,常用方案是一个10uF电解电容连接VCC和RST引脚,同时RST引脚通过一个10kΩ电阻下拉到地,这样就构成了典型的阻容上电复位电路。如果想要手动复位,再在RST和地之间并联一个按键开关即可。按下按键时RST直接被拉到高电平,单片机复位。
然后是EA/VPP引脚的问题。AT89C51内部有程序存储器和外部程序存储器之分,EA引脚接高电平(VCC),表示优先使用内部程序存储器,这是绝大多数应用场景的标准接法。如果你的板子上这个引脚悬空了,单片机可能会试图从外部存储器取指令,导致程序完全跑不起来。这个引脚别看它不起眼,我见过太多初学者的板子,程序下载后毫无反应,查了半天最后发现是EA引脚没接好。
2.2 显示方案选型:LCD1602还是数码管
万年历项目的显示方案,主流有几种:数码管动态扫描、LCD1602字符液晶、LCD12864点阵液晶。我的建议是如果不做特殊创新,LCD1602加一块PCF8574转I2C模块是最省心的方案。数码管做动态扫描不仅占用IO口多,而且显示信息量很有限,年月日时分秒六个信息组在4位数码管上要翻页显示,使用体验比较差。LCD1602一行能显示16个字符,两行加起来32个字符,刚好能把这些信息分成两行完整展示。我实际用的布局是:第一行显示年/月/日/星期,第二行显示时/分/秒和温度。内容展示完整,无需翻页,人机交互非常直观。
LCD1602本身是一个比较慢的器件,写数据和写命令都需要一定的延时。很多人直接用软件延时去满足时序,但这里有个小技巧:如果你的单片机晶振用的是12MHz,那么每条机器周期是1微秒,LCD1602要求的写时序最小脉宽通常手册上给的是几百纳秒到微秒级别,但实践下来,直接把延时设置到1ms甚至更宽都没问题,显示效果不会变差,反而更稳定。不过需要注意——如果采用P0口直连LCD1602数据线的方式,切记P0口必须外接上拉电阻,因为P0口内部是开漏输出结构。如果不加上拉,LCD1602的数据线会处于高阻态,显示会出现乱码、缺笔画或者完全无显示的现象。如果用AT89C51的P1口或P2口连接数据线,则可以省去上拉——这两个口内部有上拉电阻。这是个很容易被忽视的硬件细节,画PCB或者在面包板上做验证时务必注意。
2.3 时钟芯片的选择与供电设计
DS1302是DALLAS公司(现在也是Maxim旗下)推出的一款串行接口实时时钟芯片,它最大的优势是自带涓流充电电路,可以直接给后备电池充电,同时它只需要3根线(CE、SCLK、I/O)就能与单片机通信,极大节省IO资源。这个芯片和DS18B20一样,采用单总线或者类SPI的通信协议,对时序要求比较苛刻,尤其是读操作时,数据是在SCLK下降沿之后才被稳定输出,程序员在编写字节读写函数时,时序上差一个机器周期都会导致读回的数据是乱的。
在供电设计上,DS1302的VCC1接3.6V左右的纽扣电池(常见的CR2032电压为3V,也可以用2节AA电池串联),VCC2接系统主电源5V。DS1302内部会自动选择电压较高的一侧供电。当系统断电时,它自动切换为电池供电,依靠32.768kHz晶振继续走时。这里有个非常关键的细节——32.768kHz晶振的两脚之间需要接两个6pF的负载电容到地,而且晶振要紧挨着芯片放置,走线要短,否则容易不起振或者走时误差很大。很多同学做出来发现时钟走得飞快(一天快几个小时),十有八九是晶振电路设计问题或者买到了劣质晶振。
DS1302的时序编写是整个软件部分最核心的难点之一,我后面会单独写一节详细讲。
2.4 按键与附加功能电路
按键电路设计相对简单,通常使用独立按键接P3口的低四位(P3.0-P3.3)。独立按键一端接IO口,另一端接地,IO口内部上拉(AT89C51的P3口内部有上拉电阻)。按键未按下时IO口读为高电平,按下时接地为低电平。这里唯一需要注意的是软件消抖——按键在按下和释放的瞬间,由于机械触点的弹性,会产生大约5ms到10ms的电平抖动,如果不做消抖处理,一次按键可能被识别成多次。消抖的方法有两种:一种是硬件RC滤波,一种是软件延时消抖,我在项目中使用的是软件消抖,思路是:检测到低电平后,延时10ms再读一次,如果仍然是低电平,就确认按键有效。这种处理方式简单可靠,完全满足项目需求。
这个项目还可以附加一个温度显示功能,传感器选DS18B20,单总线数字温度传感器,测量范围-55℃到+125℃,精度正负0.5℃,用一根IO口就能通信。加上DS18B20之后,LCD第二行末尾就能显示当前环境温度,功能更加丰富,在设计报告的"功能创新"章节里也能多写一笔。
3. 软件流程与核心功能模块实现
3.1 主程序的整体流程图思维
万年历的软件不复杂,但逻辑必须清晰。我把整个软件架构拆成四个层次:系统初始化层、数据访问层、业务逻辑层、显示交互层。
系统初始化层包括单片机的IO口配置、LCD1602初始化、DS1302初始化、DS18B20初始化,在main函数的最开始完成。数据访问层封装了对DS1302的读写函数,包括单字节读、单字节写、时间数组的批量读、批量写,以及BCD码和十进制的转换函数——这一点非常重要,DS1302内部存储的时间数据是BCD码格式,0x23就代表十进制的23,如果你直接把读到的字节当作十进制的数去处理,显示出来就会出错。业务逻辑层负责时间校准、闹钟判断、星期计算等应用功能。显示交互层负责把时间数据格式化输出到LCD1602上。
整个main函数的骨架大致是这样的思路:
void main(void) { unsigned char time_buf[7]; Init_System(); // IO口配置 Init_LCD1602(); // 液晶初始化 Init_DS1302(); // 时钟芯片初始化 Init_DS18B20(); // 温度传感器初始化 DS1302_GetTime(time_buf); // 开机读取一次当前时间 while(1) { Scan_Key(); // 按键扫描,处理时间校准 Refresh_Display(); // 刷新LCD显示内容 Delay_Short(); // 循环间隙延时 } }主循环不阻塞、不死等,每个功能模块都保持"调用后立刻返回"的特性,这样系统的实时性和稳定性才有保障。我做这个项目排查过的最大的一个bug,就是在LCD刷屏时延时太长,导致按键扫描不及时,按下去半秒钟才有反应,后来优化了刷新逻辑才解决了问题。
3.2 DS1302时序详解:从底层驱动到头文件
DS1302的驱动是整个项目中技术含量最高的部分,我单独拿出来详细讲。DS1302的数据传输是字节级的,命令字首先通过I/O引脚逐位移入,然后根据命令字的第0位(LSB)判断后续数据方向——为0表示写数据,为1表示读数据。时序上用的都是标准的SPI类接口思路,但没有片选信号的极性要求,而是用CE拉高启动通信,结束后拉低复位。
单片机写一个字节到DS1302的操作,典型代码如下:
void DS1302_WriteByte(unsigned char dat) { unsigned char i; for(i = 0; i < 8; i++) { DS1302_IO = dat & 0x01; // 先发送最低位 DS1302_SCLK = 1; // 产生时钟上升沿 Delay_Short(); DS1302_SCLK = 0; dat >>= 1; } }读取一个字节的操作,典型代码如下:
unsigned char DS1302_ReadByte(void) { unsigned char i, dat = 0; DS1302_IO = 1; // 释放总线,准备接收 for(i = 0; i < 8; i++) { dat >>= 1; if(DS1302_IO) dat |= 0x80; DS1302_SCLK = 1; // 时钟上升沿 Delay_Short(); DS1302_SCLK = 0; } return dat; }读数据时有一个很容易踩的坑,就是读时序需要"先拉低SCLK,在SCLK为低电平时读取IO数据,然后SCLK拉高,再拉低,进入下一个周期",这样每个SCLK的低电平期间读取一次数据。如果搞反了时序,读出来的数据经常是错位或全0的。我在调试时对比过DS1302手册中的时序图,反复确认了下降沿和上升沿与数据采样的对应关系,最终的读函数才稳定下来。
读写时间数据的时候,推荐的做法是定义一组数组变量,通过DS1302的"BURST模式"一次读写完所有时间寄存器:
void DS1302_GetTime(unsigned char *buf) { unsigned char i; DS1302_CE = 0; DS1302_SCLK = 0; DS1302_CE = 1; DS1302_WriteByte(0xBF); // 读突发模式命令 for(i = 0; i < 7; i++) { buf[i] = DS1302_ReadByte(); } DS1302_CE = 0; }对应的写时间函数,把写保护位(WP)关掉之后再写,写完恢复写保护,防止误操作改变时间:
void DS1302_SetTime(unsigned char *buf) { unsigned char i; DS1302_CE = 0; DS1302_SCLK = 0; DS1302_CE = 1; DS1302_WriteByte(0x8E); // 写保护寄存器地址 DS1302_WriteByte(0x00); // 关闭写保护 DS1302_CE = 0; DS1302_CE = 1; DS1302_WriteByte(0xBE); // 写突发模式命令 for(i = 0; i < 7; i++) { DS1302_WriteByte(buf[i]); } DS1302_CE = 0; DS1302_CE = 1; DS1302_WriteByte(0x8E); DS1302_WriteByte(0x80); // 恢复写保护 DS1302_CE = 0; }这里各位同学写代码时一定注意:时间寄存器的最低字节bit7是写保护位,置1时禁止写操作。如果你往DS1302写时间死活写不进去,先检查这个位是不是没被清零。另外,写操作前DS1302的CE必须从低拉高,待所有字节写完后CE再拉低,这中间CE不能有多次拉低拉高的动作,否则通信会中断。
3.3 时间设置与按键交互逻辑
万年历的按键交互,我的设计方案是三个独立按键:设置键(SET)、加键(ADD)、减键(SUB)。这个是很多产品交互里的标准做法,流程是:
平时处于正常显示状态。按下SET键一次,进入"年"设置状态,当前设置的年份位闪烁提示用户;此时按ADD和SUB,年份会在当前值基础上加1或减1。再按一次SET,切换到"月"设置状态,以此类推,依次为日、星期、时、分、秒。全部调完之后再按一次SET,退出设置状态,回到正常显示模式,同时把校准后的时间写回DS1302。这里有一个逻辑细节——在设置模式下,显示的时间和DS1302内部的时间是脱钩的,你调整的是一个缓冲区里的临时变量,当退出设置模式时才一次性写入DS1302,这样可以避免边改边写导致的时间寄存器数据不连贯。
按键消抖处理我已提到过,再补充一点:代码中设置一个"按键锁定"标志位,当按键处理完一次动作后,必须等待按键释放(检测到高电平)才允许下一次按键生效。否则长按的时候,程序会连续触发几十次的加数操作,用户根本没法精确设置时间。
3.4 显示刷新与信息布局
LCD1602的显示刷新,我建议在主循环里用一个计数变量做分时刷新。比如每次循环对计数器加1,0的时候刷新LCD第一行,1的时候刷新LCD第二行,2的时候读取一次DS18B20温度值。这样做的目的是避免在一个循环周期内集中做太多I/O操作导致主循环响应变慢。
显示内容的格式化,用C语言可以实现得很优雅:
sprintf(disp_buf, "%02d-%02d-%02d %s", year, month, day, week_str[week]); LCD1602_WriteString(0, 0, disp_buf); sprintf(disp_buf, "%02d:%02d:%02d %2d.%dC", hour, minute, second, temp_int, temp_dec); LCD1602_WriteString(0, 1, disp_buf);第一行显示日期信息加星期,第二行显示时间信息加温度,年月日用横杠分隔,时间用冒号分隔,温度精确到一位小数。星期部分要提前准备一个字符串数组,把"MON"到"SUN"或者"一"到"日"放进去,按DS1302读回来的星期值直接索引。
这里建议大家在调试时把所有十六进制时间数据先转成十进制再显示,不要直接在sprintf里传入BCD码。可以写一个独立的BCD转十进制函数,负责任的程序员应该在一开始就统一约定好"数据结构层使用BCD码,业务逻辑层和显示层使用十进制",省的后面越改越乱。
4. 常见问题与排查实录
4.1 DS1302读写失败的排查
这是万年历项目里翻车率最高的一个问题。DS1302读回来的时间全是0xFF或者0x00,或者有时正常有时不正常,排查顺序如下:
- 第一步检查供电:VCC2接5V了吗?VCC1电池装了吗?如果用万用表量VCC2对地电压不是5V,先解决电源问题
- 第二步检查晶振:DS1302的X1和X2引脚之间接的32.768kHz晶振是否完好?可以用示波器探头测量X1引脚,正常应该能看到一个32.768kHz的振荡波形,约1V峰峰值。如果量不到,优先换一个晶振试试
- 第三步检查时序:用示波器看CE、SCLK、I/O三个引脚的波形,判断波形时序是否符合手册要求。I/O上的数据命令字是否正确,时钟信号是否有毛刺,SCLK是否在空闲时处于低电平
- 第四步检查代码:重点确认命令字的bit0是否正确(读用1,写用0),以及读数据时是否在正确的时钟沿采样
如果以上四步都排查过还是有问题,还有一个容易被忽略的点——DS1302的驱动代码里,IO口设置有方向切换。在单片机里,DS1302的I/O引脚是双向的,写操作时该引脚配置为输出模式,读操作时需要切换为输入模式。如果用的是标准51单片机的准双向IO口(如P1口、P3口),无需切换方向,直接读写就行;但如果用增强型51单片机(如STC系列),IO口有四种模式,配置不对就会导致读回来全0或全1。我在用STC89C52调试时,就遇到过读回来全是0x00的情况,后来把IO口配置为"准双向口"模式才恢复正常。这个细节具有普适性,各位一定注意。
4.2 程序烧录异常与代码超容量的应对
用STC-ISP或者USB转串口下载器给AT89C51烧录程序时,有几个常见问题:
如果目标芯片型号选错了,问题会出现在连接稳定性和烧录成功率上。Keil工程中芯片型号选择AT89C51或AT89S52均可,但下载器软件里的芯片型号必须和你手头的实体芯片严格一致。另外下载时要把单片机断电再上电,因为AT89系列芯片进入ISP下载模式需要冷启动——先点击下载按钮,然后给电路板重新上电,下载器才能捕获到芯片的引导程序入口。
代码超容量的应对方法前面提过,最有效的两个手段是:第一,所有延时函数都采用循环递减而非嵌套循环的方式写,这样在优化等级下代码体积会小很多;第二,避免调用printf等重量级库函数,自己在LCD1602上实现字符转换。如果这两种方法都用完了还是超出4KB,那就和指导老师沟通换AT89S52,这是最务实的方案。
4.3 显示乱码和走时不准的处理
LCD1602显示乱码的原因,第一部分是硬件连接问题,数据线有没有接错,P0口有没有加上拉电阻,对比度调节电位器是否调到了合适的位置(V0引脚电压一般为0.5V-1V,具体需要边调边观察);第二部分是初始化时序问题,LCD1602上电后需要等待15ms以上才能执行初始化命令,初始化时写0x38(8位模式)、0x0C(开显示关光标)、0x06(光标右移)、0x01(清屏)这几个命令之间,也要有合适的延时,建议每条命令之间至少延时5ms,待清屏指令完成后延时1.64ms以上。
走时不准,首先要排除的是晶振频率偏移——DS1302外接32.768kHz晶振,之所以选这个频率,是因为2的15次方等于32768,内部计数器正好每秒钟触发一次中断,如果晶振频率有偏差,走时必然不准。便宜晶振的精度一般是±20ppm,这个级别的误差对应每天大约1.7秒的偏差,月误差约51秒,属于可以接受的范围。如果走时误差大到每天几分钟甚至更夸张,那基本可以断定是晶振电路设计问题,比如负载电容取值不匹配、PCB走线过长、晶振靠近发热元件等。合适的负载电容通常为6pF到12.5pF,具体根据晶振规格书选取,如果规格书丢失,一般用6pF,配合微调电容进行校准。
另外还有一个很多人不知道的细节。DS1302的走时精度还受到其内部"充电"功能的影响。如果你的设计用到了DS1302的涓流充电功能(TCR寄存器设置低四位),它会在VCC2供电期间对VCC1的电池进行小电流充电。这个功能本身没问题,但它会发热影响晶振频率吗?不会,但充放电过程会导致电源电压微小波动,在超高精度场景下会有影响。毕设场景下完全不用管,直接把TCR寄存器设置为0x00(关闭涓流充电)最省心,纽扣电池本身就能用两三年,没有必要充电。
5. 项目的经验总结与扩展方向
做这个基于AT89C51的万年历毕设,从拿到题目到完稿,前前后后花了大概一周多的时间,包括画原理图、焊接板子、编写调试代码、撰写设计报告。整个过程中最大的体会是:这个项目一点也不"简单",它把一个嵌入式产品开发全流程走了一遍——需求分析、方案论证、硬件设计、软件编码、系统联调、文档撰写,每个环节都有实实在在的难点需要攻克。尤其是DS1302的时序调试和LCD1602的初始化细节,如果没有认真的态度,很容易在这里卡上好几天。
有一个维度值得单独说一说:单片机IO口的驱动能力问题。AT89C51每个IO口在输出高电平时,驱动能力非常有限(准双向口上拉电流只有几十微安至几百微安),所以如果你外接了一个需要大电流驱动的器件(比如蜂鸣器、继电器),直接接在IO口上,轻则驱动不了,重则烧毁IO口甚至整个芯片。一定要加三极管或者达林顿管来放大驱动电流。这个经验在排查"板子能跑但电路不动作"类问题时非常关键,很多时候问题不是出在代码上,而是出在硬件驱动力不够。
如果后续想把题目做得更有亮点,可以在以下几个方面进行扩展:
- 增加农历显示:通过查表法实现公历到农历的转换,这是国内万年历的刚需功能,也能体现算法能力
- 增加闹钟模块:DS1302内部有闹钟寄存器,可以设置每天的闹钟时间,到点驱动蜂鸣器发音
- 增加掉电检测:用单片机的INT0外部中断引脚接一个电源检测电路,检测到掉电瞬间进入低功耗模式,保存现场数据到EEPROM
- 升级为主控为STC89C52RC或者STC12C5A60S2:IO口更多,Flash更大,还能用内部的掉电唤醒功能
- 增加红外遥控设置功能:用红外接收头替代物理按键,人机交互更有科技感
最后再分享一个调试阶段的实用小技巧。我在调试时,会在DS1302_GetTime函数返回后,往LCD的某个固定位置显示一个"时间数据有效性"的标志,比如正常读回的数据必须满足:年的BCD码在0x00到0x99之间,月的BCD码在0x01到0x12之间,日的BCD码在0x01到0x31之间。如果不满足,就显示一条"数据异常"的提示。这个小功能看着不起眼,但对定位"读回数据全是垃圾"的问题帮助极大。整套系统上电后,在接好电池和晶振的前提下,读回的时间应该是正确的实时值而不是全0。我建议各位在做毕设调试时,都养成分层校验数据的习惯,这会让你在排查问题时省下大把的时间。
本文还有配套的精品资源,点击获取