2016-07-09 07:17:52 softn 阅读数 1808
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4003 人正在学习 去看看 朱有鹏
共用体除非必要,否则我们不推荐使用,枚举的用法比较简单,在本书 19 章的项目实践中有很好的示例,这节课我们先来练习一下结构体的使用。下边这个程序的功能是一个带日期的电子钟,相当于一个简易万年历了,并且加入了按键调时功能。学有余力的同学看到这里,不妨先不看我们提供的代码,自己写写试试。如果能够独立写一个按键可调的万年历程序,单片机可以说基本入门了。如果自己还不能够独立完成这个程序,那么还是老规矩,先抄并且理解,而后自己独立默写出来,并且要边默写边理解。

本例直接忽略了星期这项内容,通过上、下、左、右、回车、ESC 这 6 个按键可以调整时间。这也是一个具有综合练习性质的实例,虽然在功能实现上没有多少难度,但要进行的操作却比较多而且烦琐,同学们可以从中体会到把繁杂的功能实现分解为一步步函数操作的必要性以及方便灵活性。简单说一下这个程序的几个要点,方便大家阅读理解程序。
  1. 把 DS1302 的底层操作封装为一个 DS1302.c 文件,对上层应用提供基本的实时时间的操作接口,这个文件也是我们的又一个功能模块了,我们的积累也越来越多了。
  2. 定义一个结构体类型 sTime 用来封装日期时间的各个元素,又用该结构体定义了一个时间缓冲区变量 bufTime 来暂存从 DS1302 读出的时间和设置时间时的设定值。需要注意的是在其它文件中要使用这个结构体变量时,必须首先再声明一次 sTime 类型;
  3. 定义一个变量 setIndex 来控制当前是否处于设置时间的状态,以及设置时间的哪一位,该值为 0 就表示正常运行,1~12 分别代表可以修改日期时间的 12 个位;
  4. 由于这节课的程序功能要进行时间调整,用到了 1602 液晶的光标功能,添加了设置光标的函数,我们要改变哪一位的数字,就在 1602 对应位置上进行光标闪烁,所以 Lcd1602.c在之前文件的基础上添加了两个控制光标的函数;
  5. 时间的显示、增减、设置移位等上层功能函数都放在 main.c 中来实现,当按键需要这些函数时则在按键文件中做外部声明,这样做是为了避免一组功能函数分散在不同的文件内而使程序显得凌乱。
  1. /***************************DS1302.c 文件程序源代码*****************************/
  2. #include <reg52.h>
  3. sbit DS1302_CE = P1^7;
  4. sbit DS1302_CK = P3^5;
  5. sbit DS1302_IO = P3^4;
  6. struct sTime { //日期时间结构体定义
  7. unsigned int year; //年
  8. unsigned char mon; //月
  9. unsigned char day; //日
  10. unsigned char hour; //时
  11. unsigned char min; //分
  12. unsigned char sec; //秒
  13. unsigned char week; //星期
  14. };
  15. /* 发送一个字节到 DS1302 通信总线上 */
  16. void DS1302ByteWrite(unsigned char dat){
  17. unsigned char mask;
  18. for (mask=0x01; mask!=0; mask<<=1){ //低位在前,逐位移出
  19. if ((mask&dat) != 0){ //首先输出该位数据
  20. DS1302_IO = 1;
  21. }else{
  22. DS1302_IO = 0;
  23. }
  24. DS1302_CK = 1; //然后拉高时钟
  25. DS1302_CK = 0; //再拉低时钟,完成一个位的操作
  26. }
  27. DS1302_IO = 1; //最后确保释放 IO 引脚
  28. }
  29. /* 由 DS1302 通信总线上读取一个字节 */
  30. unsigned char DS1302ByteRead(){
  31. unsigned char mask;
  32. unsigned char dat = 0;
  33. for (mask=0x01; mask!=0; mask<<=1){ //低位在前,逐位读取
  34. if (DS1302_IO != 0){ //首先读取此时的 IO 引脚,并设置 dat 中的对应位
  35. dat |= mask;
  36. }
  37. DS1302_CK = 1; //然后拉高时钟
  38. DS1302_CK = 0; //再拉低时钟,完成一个位的操作
  39. }
  40. return dat; //最后返回读到的字节数据
  41. }
  42. /* 用单次写操作向某一寄存器写入一个字节,reg-寄存器地址,dat-待写入字节 */
  43. void DS1302SingleWrite(unsigned char reg, unsigned char dat){
  44. DS1302_CE = 1; //使能片选信号
  45. DS1302ByteWrite((reg<<1)|0x80); //发送写寄存器指令
  46. DS1302ByteWrite(dat); //写入字节数据
  47. DS1302_CE = 0; //除能片选信号
  48. }
  49. /* 用单次读操作从某一寄存器读取一个字节,reg-寄存器地址,返回值-读到的字节 */
  50. unsigned char DS1302SingleRead(unsigned char reg){
  51. unsigned char dat;
  52. DS1302_CE = 1; //使能片选信号
  53. DS1302ByteWrite((reg<<1)|0x81); //发送读寄存器指令
  54. dat = DS1302ByteRead(); //读取字节数据
  55. DS1302_CE = 0; //除能片选信号
  56. return dat;
  57. }
  58. /* 用突发模式连续写入 8 个寄存器数据,dat-待写入数据指针 */
  59. void DS1302BurstWrite(unsigned char *dat){
  60. unsigned char i;
  61. DS1302_CE = 1;
  62. DS1302ByteWrite(0xBE); //发送突发写寄存器指令
  63. for (i=0; i<8; i++){ //连续写入 8 字节数据
  64. DS1302ByteWrite(dat[i]);
  65. }
  66. DS1302_CE = 0;
  67. }
  68. /* 用突发模式连续读取 8 个寄存器的数据,dat-读取数据的接收指针 */
  69. void DS1302BurstRead(unsigned char *dat){
  70. unsigned char i;
  71. DS1302_CE = 1;
  72. DS1302ByteWrite(0xBF); //发送突发读寄存器指令
  73. for (i=0; i<8; i++){ //连续读取 8 个字节
  74. dat[i] = DS1302ByteRead();
  75. }
  76. DS1302_CE = 0;
  77. }
  78. /* 获取实时时间,即读取 DS1302 当前时间并转换为时间结构体格式 */
  79. void GetRealTime(struct sTime *time){
  80. unsigned char buf[8];
  81. DS1302BurstRead(buf);
  82. time->year = buf[6] + 0x2000;
  83. time->mon = buf[4];
  84. time->day = buf[3];
  85. time->hour = buf[2];
  86. time->min = buf[1];
  87. time->sec = buf[0];
  88. time->week = buf[5];
  89. }
  90. /* 设定实时时间,时间结构体格式的设定时间转换为数组并写入 DS1302 */
  91. void SetRealTime(struct sTime *time){
  92. unsigned char buf[8];
  93. buf[7] = 0;
  94. buf[6] = time->year;
  95. buf[5] = time->week;
  96. buf[4] = time->mon;
  97. buf[3] = time->day;
  98. buf[2] = time->hour;
  99. buf[1] = time->min;
  100. buf[0] = time->sec;
  101. DS1302BurstWrite(buf);
  102. }
  103. /* DS1302 初始化,如发生掉电则重新设置初始时间 */
  104. void InitDS1302(){
  105. unsigned char dat;
  106. struct sTime code InitTime[] = { //2013 年 10 月 8 日 12:30:00 星期二
  107. 0x2013,0x10,0x08, 0x12,0x30,0x00, 0x02
  108. };
  109. DS1302_CE = 0; //初始化 DS1302 通信引脚
  110. DS1302_CK = 0;
  111. dat = DS1302SingleRead(0); //读取秒寄存器
  112. if ((dat & 0x80) != 0){ //由秒寄存器最高位 CH 的值判断 DS1302 是否已停止
  113. DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
  114. SetRealTime(&InitTime); //设置 DS1302 为默认的初始时间
  115. }
  116. }
DS1302.c 最终向外提供出与具体时钟芯片寄存器位置无关的、由时间结构类型 sTime 作为接口的实时时间的读取和设置函数,如此处理体现了我们前面提到过的层次化编程的思想。应用层可以不关心底层实现细节,底层实现的改变也不会对应用层造成影响,比如说日后你可能需要换一款时钟芯片,而它与 DS1302 的操作和时间寄存器顺序是不同的,那么你需要做的也仅是针对这款新的时钟芯片设计出底层操作函数,最终提供出同样的以 sTime 为接口的操作函数即可,应用层无需做任何的改动。
  1. /***************************Lcd1602.c 文件程序源代码*****************************/
  2. #include <reg52.h>
  3. #define LCD1602_DB P0
  4. sbit LCD1602_RS = P1^0;
  5. sbit LCD1602_RW = P1^1;
  6. sbit LCD1602_E = P1^5;
  7. /* 等待液晶准备好 */
  8. void LcdWaitReady(){
  9. unsigned char sta;
  10. LCD1602_DB = 0xFF;
  11. LCD1602_RS = 0;
  12. LCD1602_RW = 1;
  13. do {
  14. LCD1602_E = 1;
  15. sta = LCD1602_DB; //读取状态字
  16. LCD1602_E = 0;
  17. } while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重复检测直到其等于 0 为止
  18. }
  19. /* 向 LCD1602 液晶写入一字节命令,cmd-待写入命令值 */
  20. void LcdWriteCmd(unsigned char cmd){
  21. LcdWaitReady();
  22. LCD1602_RS = 0;
  23. LCD1602_RW = 0;
  24. LCD1602_DB = cmd;
  25. LCD1602_E = 1;
  26. LCD1602_E = 0;
  27. }
  28. /* 向 LCD1602 液晶写入一字节数据,dat-待写入数据值 */
  29. void LcdWriteDat(unsigned char dat){
  30. LcdWaitReady();
  31. LCD1602_RS = 1;
  32. LCD1602_RW = 0;
  33. LCD1602_DB = dat;
  34. LCD1602_E = 1;
  35. LCD1602_E = 0;
  36. }
  37. /* 设置显示 RAM 起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
  38. void LcdSetCursor(unsigned char x, unsigned char y){
  39. unsigned char addr;
  40. if (y == 0){ //由输入的屏幕坐标计算显示 RAM 的地址
  41. addr = 0x00 + x; //第一行字符地址从 0x00 起始
  42. }else{
  43. addr = 0x40 + x; //第二行字符地址从 0x40 起始
  44. }
  45. LcdWriteCmd(addr | 0x80); //设置 RAM 地址
  46. }
  47. /* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */
  48. void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
  49. LcdSetCursor(x, y); //设置起始地址
  50. while (*str != '\0'){ //连续写入字符串数据,直到检测到结束符
  51. LcdWriteDat(*str++);
  52. }
  53. }
  54. /* 打开光标的闪烁效果 */
  55. void LcdOpenCursor(){
  56. LcdWriteCmd(0x0F);
  57. }
  58. /* 关闭光标显示 */
  59. void LcdCloseCursor(){
  60. LcdWriteCmd(0x0C);
  61. }
  62. /* 初始化 1602 液晶 */
  63. void InitLcd1602(){
  64. LcdWriteCmd(0x38); //16*2 显示,5*7 点阵,8 位数据接口
  65. LcdWriteCmd(0x0C); //显示器开,光标关闭
  66. LcdWriteCmd(0x06); //文字不动,地址自动+1
  67. LcdWriteCmd(0x01); //清屏
  68. }
为了本例的具体需求,在之前文件的基础上添加两个控制光标效果打开和关闭的函数,虽然函数都很简单,但为了保持程序整体上良好的模块化和层次化,还是应该在液晶驱动文件内以函数的形式提供,而不是由应用层代码直接来调用具体的液晶写命令操作。
/***************************keyboard.c 文件程序源代码****************************/
(此处省略,可参考之前章节的代码)
  1. /*****************************main.c 文件程序源代码******************************/
  2. #include <reg52.h>
  3. struct sTime { //日期时间结构体定义
  4. unsigned int year;
  5. unsigned char mon;
  6. unsigned char day;
  7. unsigned char hour;
  8. unsigned char min;
  9. unsigned char sec;
  10. unsigned char week;
  11. };
  12. bit flag200ms = 1; //200ms 定时标志
  13. struct sTime bufTime; //日期时间缓冲区
  14. unsigned char setIndex = 0; //时间设置索引
  15. unsigned char T0RH = 0; //T0 重载值的高字节
  16. unsigned char T0RL = 0; //T0 重载值的低字节
  17. void ConfigTimer0(unsigned int ms);
  18. void RefreshTimeShow();
  19. extern void InitDS1302();
  20. extern void GetRealTime(struct sTime *time);
  21. extern void SetRealTime(struct sTime *time);
  22. extern void KeyScan();
  23. extern void KeyDriver();
  24. extern void InitLcd1602();
  25. extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
  26. extern void LcdSetCursor(unsigned char x, unsigned char y);
  27. extern void LcdOpenCursor();
  28. extern void LcdCloseCursor();
  29. void main(){
  30. unsigned char psec=0xAA; //秒备份,初值 AA 确保首次读取时间后会刷新显示
  31. EA = 1; //开总中断
  32. ConfigTimer0(1); //T0 定时 1ms
  33. InitDS1302(); //初始化实时时钟
  34. InitLcd1602(); //初始化液晶
  35. //初始化屏幕上固定不变的内容
  36. LcdShowStr(3, 0, "20 - - ");
  37. LcdShowStr(4, 1, " : : ");
  38. while (1){
  39. KeyDriver(); //调用按键驱动
  40. if (flag200ms && (setIndex == 0)){ //每隔 200ms 且未处于设置状态时,
  41. flag200ms = 0;
  42. GetRealTime(&bufTime); //获取当前时间
  43. if (psec != bufTime.sec){ //检测到时间有变化时刷新显示
  44. RefreshTimeShow();
  45. psec = bufTime.sec; //用当前值更新上次秒数
  46. }
  47. }
  48. }
  49. }
  50. /* 将一个 BCD 码字节显示到屏幕上,(x,y)-屏幕起始坐标,bcd-待显示 BCD 码 */
  51. void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd){
  52. unsigned char str[4];
  53. str[0] = (bcd >> 4) + '0';
  54. str[1] = (bcd&0x0F) + '0';
  55. str[2] = '\0';
  56. LcdShowStr(x, y, str);
  57. }
  58. /* 刷新日期时间的显示 */
  59. void RefreshTimeShow(){
  60. ShowBcdByte(5, 0, bufTime.year);
  61. ShowBcdByte(8, 0, bufTime.mon);
  62. ShowBcdByte(11, 0, bufTime.day);
  63. ShowBcdByte(4, 1, bufTime.hour);
  64. ShowBcdByte(7, 1, bufTime.min);
  65. ShowBcdByte(10, 1, bufTime.sec);
  66. }
  67. /* 刷新当前设置位的光标指示 */
  68. void RefreshSetShow(){
  69. switch (setIndex){
  70. case 1: LcdSetCursor(5, 0); break;
  71. case 2: LcdSetCursor(6, 0); break;
  72. case 3: LcdSetCursor(8, 0); break;
  73. case 4: LcdSetCursor(9, 0); break;
  74. case 5: LcdSetCursor(11, 0); break;
  75. case 6: LcdSetCursor(12, 0); break;
  76. case 7: LcdSetCursor(4, 1); break;
  77. case 8: LcdSetCursor(5, 1); break;
  78. case 9: LcdSetCursor(7, 1); break;
  79. case 10: LcdSetCursor(8, 1); break;
  80. case 11: LcdSetCursor(10, 1); break;
  81. case 12: LcdSetCursor(11, 1); break;
  82. default: break;
  83. }
  84. }
  85. /* 递增一个 BCD 码的高位 */
  86. unsigned char IncBcdHigh(unsigned char bcd){
  87. if ((bcd&0xF0) < 0x90){
  88. bcd += 0x10;
  89. }else{
  90. bcd &= 0x0F;
  91. }
  92. return bcd;
  93. }
  94. /* 递增一个 BCD 码的低位 */
  95. unsigned char IncBcdLow(unsigned char bcd){
  96. if ((bcd&0x0F) < 0x09){
  97. bcd += 0x01;
  98. }else{
  99. bcd &= 0xF0;
  100. }
  101. return bcd;
  102. }
  103. /* 递减一个 BCD 码的高位 */
  104. unsigned char DecBcdHigh(unsigned char bcd){
  105. if ((bcd&0xF0) > 0x00){
  106. bcd -= 0x10;
  107. }else{
  108. bcd |= 0x90;
  109. }
  110. return bcd;
  111. }
  112. /* 递减一个 BCD 码的低位 */
  113. unsigned char DecBcdLow(unsigned char bcd){
  114. if ((bcd&0x0F) > 0x00){
  115. bcd -= 0x01;
  116. }else{
  117. bcd |= 0x09;
  118. }
  119. return bcd;
  120. }
  121. /* 递增时间当前设置位的值 */
  122. void IncSetTime(){
  123. switch (setIndex){
  124. case 1: bufTime.year = IncBcdHigh(bufTime.year); break;
  125. case 2: bufTime.year = IncBcdLow(bufTime.year); break;
  126. case 3: bufTime.mon = IncBcdHigh(bufTime.mon); break;
  127. case 4: bufTime.mon = IncBcdLow(bufTime.mon); break;
  128. case 5: bufTime.day = IncBcdHigh(bufTime.day); break;
  129. case 6: bufTime.day = IncBcdLow(bufTime.day); break;
  130. case 7: bufTime.hour = IncBcdHigh(bufTime.hour); break;
  131. case 8: bufTime.hour = IncBcdLow(bufTime.hour); break;
  132. case 9: bufTime.min = IncBcdHigh(bufTime.min); break;
  133. case 10: bufTime.min = IncBcdLow(bufTime.min); break;
  134. case 11: bufTime.sec = IncBcdHigh(bufTime.sec); break;
  135. case 12: bufTime.sec = IncBcdLow(bufTime.sec); break;
  136. default: break;
  137. }
  138. RefreshTimeShow();
  139. RefreshSetShow();
  140. }
  141. /* 递减时间当前设置位的值 */
  142. void DecSetTime(){
  143. switch (setIndex){
  144. case 1: bufTime.year = DecBcdHigh(bufTime.year); break;
  145. case 2: bufTime.year = DecBcdLow(bufTime.year); break;
  146. case 3: bufTime.mon = DecBcdHigh(bufTime.mon); break;
  147. case 4: bufTime.mon = DecBcdLow(bufTime.mon); break;
  148. case 5: bufTime.day = DecBcdHigh(bufTime.day); break;
  149. case 6: bufTime.day = DecBcdLow(bufTime.day); break;
  150. case 7: bufTime.hour = DecBcdHigh(bufTime.hour); break;
  151. case 8: bufTime.hour = DecBcdLow(bufTime.hour); break;
  152. case 9: bufTime.min = DecBcdHigh(bufTime.min); break;
  153. case 10: bufTime.min = DecBcdLow(bufTime.min); break;
  154. case 11: bufTime.sec = DecBcdHigh(bufTime.sec); break;
  155. case 12: bufTime.sec = DecBcdLow(bufTime.sec);
  156. default: break;
  157. }
  158. RefreshTimeShow();
  159. RefreshSetShow();
  160. }
  161. /* 右移时间设置位 */
  162. void RightShiftTimeSet(){
  163. if (setIndex != 0){
  164. if (setIndex < 12){
  165. setIndex++;
  166. }else{
  167. setIndex = 1;
  168. }
  169. RefreshSetShow();
  170. }
  171. }
  172. /* 左移时间设置位 */
  173. void LeftShiftTimeSet(){
  174. if (setIndex != 0){
  175. if (setIndex > 1){
  176. setIndex--;
  177. }else{
  178. setIndex = 12;
  179. }
  180. RefreshSetShow();
  181. }
  182. }
  183. /* 进入时间设置状态 */
  184. void EnterTimeSet(){
  185. setIndex = 2; //把设置索引设置为 2,即可进入设置状态
  186. LeftShiftTimeSet(); //再利用现成的左移操作移到位置 1 并完成显示刷新
  187. LcdOpenCursor(); //打开光标闪烁效果
  188. }
  189. /* 退出时间设置状态,save-是否保存当前设置的时间值 */
  190. void ExitTimeSet(bit save){
  191. setIndex = 0; //把设置索引设置为 0,即可退出设置状态
  192. if (save){ //需保存时即把当前设置时间写入 DS1302
  193. SetRealTime(&bufTime);
  194. }
  195. LcdCloseCursor(); //关闭光标显示
  196. }
  197. /* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
  198. void KeyAction(unsigned char keycode){
  199. if ((keycode>='0') && (keycode<='9')){ //本例中不响应字符键
  200. }else if (keycode == 0x26){ //向上键,递增当前设置位的值
  201. IncSetTime();
  202. }else if (keycode == 0x28){ //向下键,递减当前设置位的值
  203. DecSetTime();
  204. }else if (keycode == 0x25){ //向左键,向左切换设置位
  205. LeftShiftTimeSet();
  206. }else if (keycode == 0x27){ //向右键,向右切换设置位
  207. RightShiftTimeSet();
  208. }else if (keycode == 0x0D){ //回车键,进入设置模式/启用当前设置值
  209. if (setIndex == 0){ //不处于设置状态时,进入设置状态
  210. EnterTimeSet();
  211. }else{ //已处于设置状态时,保存时间并退出设置状态
  212. ExitTimeSet(1);
  213. }
  214. }else if (keycode == 0x1B){ //Esc 键,取消当前设置
  215. ExitTimeSet(0);
  216. }
  217. }
  218. /* 配置并启动 T0,ms-T0 定时时间 */
  219. void ConfigTimer0(unsigned int ms){
  220. unsigned long tmp; //临时变量
  221. tmp = 11059200 / 12; //定时器计数频率
  222. tmp = (tmp * ms) / 1000; //计算所需的计数值
  223. tmp = 65536 - tmp; //计算定时器重载值
  224. tmp = tmp + 28; //补偿中断响应延时造成的误差
  225. T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
  226. T0RL = (unsigned char)tmp;
  227. TMOD &= 0xF0; //清零 T0 的控制位
  228. TMOD |= 0x01; //配置 T0 为模式 1
  229. TH0 = T0RH; //加载 T0 重载值
  230. TL0 = T0RL;
  231. ET0 = 1; //使能 T0 中断
  232. TR0 = 1; //启动 T0
  233. }
  234. /* T0 中断服务函数,执行按键扫描和 200ms 定时 */
  235. void InterruptTimer0() interrupt 1{
  236. static unsigned char tmr200ms = 0;
  237. TH0 = T0RH; //重新加载重载值
  238. TL0 = T0RL;
  239. KeyScan(); //按键扫描
  240. tmr200ms++;
  241. if (tmr200ms >= 200){ //定时 200ms
  242. tmr200ms = 0;
  243. flag200ms = 1;
  244. }
  245. }
main.c 主文件,负责所有应用层的功能实现,文件比较长,还是那句话“不难但比较烦琐”,希望对具体问题分析细化能力还不太强的同学们把这个文件多练习几遍,学习一下其中把具体问题逐步细化并一步步实现出来的编程思想,多进行此类练习,锻炼程序思维能力,将来遇到具体项目设计需求的时候,你很快就可以找到方法并实现它们了。
2019-03-13 15:34:05 qq_42240908 阅读数 246
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

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

基于单片机的电子钟程序设计与调试 蓝桥杯赛题满分答案分享!
#include “reg52.h”
#include “ds1302.h”
#include “ds18b20.h”
#define u8 unsigned char
#define u16 unsigned char
u8 duan[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf,0xc6};
u8 wei[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
u8 time[3]={0};
u8 alarm[3]={0};
sbit S7=P3^0;
sbit S6=P3^1;
sbit S5=P3^2;
sbit S4=P3^3;
int time0=0;
int time1=0;
int time2=0;
u8 setnum=0;
u8 wz=0;
u8 timeup=0;
u8 alarmset=0;
void delay()
{
int i=10000;
while(i–);
}
void smxs(u8 m,u8 n)
{
u8 i=100;
P2=0xc0;
P0=wei[m];
P2=0xe0;
P0=duan[n];
while(i–);
P0=0xff;
}
void open_init()
{
P2=0xa0;
P0=0;
P2=0x80;
P0=0xff;
Write_Ds1302(80,50);
Write_Ds1302(82,59);
Write_Ds1302(84,23);
}
void time_init()
{
TMOD=0x11;
TH0=(65536-1000)/256;
TL0=(65536-1000)%256;
TH1=(65536-1000)/256;
TL1=(65536-1000)%256;

EA=1;
ET0=1;
ET1=1;
TR1=0;
TR0=1;

}
int main()
{
u8 t;
open_init();
time_init();

 while(1)
 {
	 //显示温度
	 if(!S4)
	 {
	  	TR0=0;
	  	while(!S4)
	  	{
		  	t=temp();
			smxs(5,t/100);
			smxs(6,t/10%10);
			smxs(7,11);		  
	  	}
	  	TR0=1;
	 }

	 //闹钟响了
	if(timeup)
	{
		while(1)
		{
			if(!S4||!S5||!S6||!S7||time2==3000)
			{
				while(!S4||!S5||!S6||!S7);
				P2=0x80;
				P0=0xff;
				TR1=0;
				timeup=0;
				time2=0;
				break;
			}
		}

	}
	//闹钟设置
	if(!S6)
	 {	
	  	delay();
	  	if(!S6)
	  	{
			setnum++;
			alarmset=1;
			while(!S6);
		   	while(setnum<4)
		  {
			if(!S6)
			{	
				delay();
	  			if(!S6)
				{				
					while(!S6);
					setnum++;
				}
			}
			if(!S5)
			{
			  	delay();
	  			if(!S5)
				{
					while(!S5);	
					if(setnum==1)
						if(alarm[0]<23)
							alarm[0]+=1;
						else 
							alarm[0]=0;
					
					if(setnum==2)
						if(alarm[1]<59)
							alarm[1]+=1;
						else 
							alarm[1]=0;
					
					if(setnum==3)
						if(alarm[2]<59)
							alarm[2]+=1;
						else 
							alarm[2]=0;	
				}	
			}

			if(!S4)
			{
				delay();
				if(!S4)
				{
					while(!S4);	
					if(setnum==1)
						if(alarm[0]>0)
							alarm[0]-=1;
						else 
							alarm[0]=23;
					
					if(setnum==2)
						if(alarm[1]>0)
							alarm[1]-=1;
						else 
							alarm[1]=59;
					
					if(setnum==3)
						if(alarm[2]>0)
							alarm[2]-=1;
						else 
							alarm[2]=59;	
			   }
			}		
		}

	   }
	   setnum=0;
	   alarmset=0;
	 }
	//时间设置
	 if(!S7)
	 {	
	  delay();
	  if(!S7)
	  {
		setnum++;
		while(!S7);
		while(setnum<4)
		{
			if(!S7)
			{	
				delay();
	  			if(!S7)
				{				
					while(!S7);
					setnum++;
				}
			}
			if(!S5)
			{
			  	delay();
	  			if(!S5)
				{
					while(!S5);	
					if(setnum==1)
						if(time[0]<23)
							Write_Ds1302(84,time[0]+1);
						else 
							Write_Ds1302(84,0);
					
					if(setnum==2)
						if(time[1]<59)
							Write_Ds1302(82,time[1]+1);
						else 
							Write_Ds1302(82,0);
					
					if(setnum==3)
						if(time[2]<59)
							Write_Ds1302(80,time[2]+1);
						else 
							Write_Ds1302(80,0);	
				}	
			}

			if(!S4)
			{
				delay();
				if(!S4)
				{
					while(!S4);	
					if(setnum==1)
						if(time[0]>0)
							Write_Ds1302(84,time[0]-1);
						else 
							Write_Ds1302(84,23);
					
					if(setnum==2)
					    if(time[1]>0)
							Write_Ds1302(82,time[1]-1);
						else 
							Write_Ds1302(82,59);      
					
					if(setnum==3)
						if(time[2]>0)
							Write_Ds1302(80,time[2]-1);
						else 
							Write_Ds1302(80,59);
			   }
			}
		
		
		}
		setnum=0;
	 }
   }




}

}
timer0()interrupt 1
{
time[0]=Read_Ds1302(85);
time[1]=Read_Ds1302(83);
time[2]=Read_Ds1302(81);

if(time[0]==alarm[0]&&time[1]==alarm[1]&&time[2]==alarm[2])
{
	timeup=1;
	TR1=1;			
}
if(alarmset==1)
{

if(setnum==1)
{
  	time0++;
	if(time0<=500)
	{
		smxs(0,alarm[0]/10);
		smxs(1,alarm[0]%10);
		smxs(2,10);
		smxs(3,alarm[1]/10);
		smxs(4,alarm[1]%10);
		smxs(5,10);
		smxs(6,alarm[2]/10);
		smxs(7,alarm[2]%10);
	}
	if(time0>500 && time0<1000)
	{
	  	smxs(2,10);
		smxs(3,alarm[1]/10);
		smxs(4,alarm[1]%10);
		smxs(5,10);
		smxs(6,alarm[2]/10);
		smxs(7,alarm[2]%10);
	}
	if(time0==1000)
		time0=0;
	
	
	TH0=(65536-1000)/256;
	TL0=(65536-1000)%256;
	return;
}
if(setnum==2)
{
  	time0++;
	if(time0<=500)
	{
		smxs(0,alarm[0]/10);
		smxs(1,alarm[0]%10);
		smxs(2,10);
		smxs(3,alarm[1]/10);
		smxs(4,alarm[1]%10);
		smxs(5,10);
		smxs(6,alarm[2]/10);
		smxs(7,alarm[2]%10);
	}
	if(time0>500 && time0<1000)
	{
	  	smxs(0,alarm[0]/10);
		smxs(1,alarm[0]%10);
		smxs(2,10);
		smxs(5,10);
		smxs(6,alarm[2]/10);
		smxs(7,alarm[2]%10);
	}
	if(time0==1000)
		time0=0;
	
	
	TH0=(65536-1000)/256;
	TL0=(65536-1000)%256;
	return;
}

if(setnum==3)
{
  	time0++;
	if(time0<=500)
	{
		smxs(0,alarm[0]/10);
		smxs(1,alarm[0]%10);
		smxs(2,10);
		smxs(3,alarm[1]/10);
		smxs(4,alarm[1]%10);
		smxs(5,10);
		smxs(6,alarm[2]/10);
		smxs(7,alarm[2]%10);
	}
	if(time0>500 && time0<1000)
	{
	  	smxs(0,alarm[0]/10);
		smxs(1,alarm[0]%10);
		smxs(2,10);
		smxs(3,alarm[1]/10);
		smxs(4,alarm[1]%10);
		smxs(5,10);
	}
	if(time0==1000)
		time0=0;
	
	
	TH0=(65536-1000)/256;
	TL0=(65536-1000)%256;
	return;
  } 
}

if(setnum==1)
{
  	time0++;
	if(time0<=500)
	{
		smxs(0,time[0]/10);
		smxs(1,time[0]%10);
		smxs(2,10);
		smxs(3,time[1]/10);
		smxs(4,time[1]%10);
		smxs(5,10);
		smxs(6,time[2]/10);
		smxs(7,time[2]%10);
	}
	if(time0>500 && time0<1000)
	{
	  	smxs(2,10);
		smxs(3,time[1]/10);
		smxs(4,time[1]%10);
		smxs(5,10);
		smxs(6,time[2]/10);
		smxs(7,time[2]%10);
	}
	if(time0==1000)
		time0=0;
	
	
	TH0=(65536-1000)/256;
	TL0=(65536-1000)%256;
	return;
}
if(setnum==2)
{
  	time0++;
	if(time0<=500)
	{
		smxs(0,time[0]/10);
		smxs(1,time[0]%10);
		smxs(2,10);
		smxs(3,time[1]/10);
		smxs(4,time[1]%10);
		smxs(5,10);
		smxs(6,time[2]/10);
		smxs(7,time[2]%10);
	}
	if(time0>500 && time0<1000)
	{
	  	smxs(0,time[0]/10);
		smxs(1,time[0]%10);
		smxs(2,10);
		smxs(5,10);
		smxs(6,time[2]/10);
		smxs(7,time[2]%10);
	}
	if(time0==1000)
		time0=0;
	
	
	TH0=(65536-1000)/256;
	TL0=(65536-1000)%256;
	return;
}

if(setnum==3)
{
  	time0++;
	if(time0<=500)
	{
		smxs(0,time[0]/10);
		smxs(1,time[0]%10);
		smxs(2,10);
		smxs(3,time[1]/10);
		smxs(4,time[1]%10);
		smxs(5,10);
		smxs(6,time[2]/10);
		smxs(7,time[2]%10);
	}
	if(time0>500 && time0<1000)
	{
	  	smxs(0,time[0]/10);
		smxs(1,time[0]%10);
		smxs(2,10);
		smxs(3,time[1]/10);
		smxs(4,time[1]%10);
		smxs(5,10);
	}
	if(time0==1000)
		time0=0;
	
	
	TH0=(65536-1000)/256;
	TL0=(65536-1000)%256;
	return;
}
smxs(0,time[0]/10);
smxs(1,time[0]%10);
smxs(2,10);
smxs(3,time[1]/10);
smxs(4,time[1]%10);
smxs(5,10);
smxs(6,time[2]/10);
smxs(7,time[2]%10);	



TH0=(65536-1000)/256;
TL0=(65536-1000)%256;	

}

timer1()interrupt 3
{

if(timeup)
{
   time1++;
   time2++;
   if(time1<=200)
   {
   	 P2=0x80;
	 P0=0xfe;
   }
   if(time1>=200 && time1<400)
   {
   	P2=0x80;
	P0=0xff;
   }
   if(time1==400)
   {
   	time1=0;
   }
}

TH1=(65536-1000)/256;
TL1=(65536-1000)%256;	

}
//ds1302.c
#include <reg52.h>
#include <intrins.h>

sbit SCK=P1^7;
sbit SDA=P2^3;
sbit RST = P1^3; // DS1302??

void Write_Ds1302_Byte(unsigned char temp)
{
unsigned char i;
for (i=0;i<8;i++)
{
SCK=0;
SDA=temp&0x01;
temp>>=1;
SCK=1;
}
}

void Write_Ds1302( unsigned char address,unsigned char dat )
{
RST=0;
nop();
SCK=0;
nop();
RST=1;
nop();
Write_Ds1302_Byte((address/10)*16+address%10);
Write_Ds1302_Byte((dat/10)*16+dat%10);
RST=0;
}

unsigned char Read_Ds1302 ( unsigned char address )
{
unsigned char i,temp=0x00;
RST=0;
nop();
SCK=0;
nop();
RST=1;
nop();
Write_Ds1302_Byte((address/10)16+address%10);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0;
nop();
RST=0;
SCK=0;
nop();
SCK=1;
nop();
SDA=0;
nop();
SDA=1;
nop();
temp=temp/16
10+temp%16;
return (temp);
}

//ds1302.h
#ifndef __DS1302_H
#define __DS1302_H

void Write_Ds1302_Byte(unsigned char temp);
void Write_Ds1302( unsigned char address,unsigned char dat );
unsigned char Read_Ds1302 ( unsigned char address );

#endif
//ds18b20.h
#ifndef __ONEWIRE_H
#define __ONEWIRE_H

int temp();
bit init_ds18b20(void);
void Write_DS18B20(unsigned char dat);
void Delay_OneWire(unsigned int t);
unsigned char Read_DS18B20(void);
#endif
//ds18b20.c

#include “reg52.h”

sbit DQ = P1^4; //单总线接口

//单总线延时函数
void Delay_OneWire(unsigned int t) //STC89C52RC
{
unsigned char i;
while(t–)
{
for(i=0;i<10;i++);
}
}

//通过单总线向DS18B20写一个字节
void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
DQ = 0;
DQ = dat&0x01;
Delay_OneWire(5);
DQ = 1;
dat >>= 1;
}
Delay_OneWire(5);
}

//从DS18B20读取一个字节
unsigned char Read_DS18B20(void)
{
unsigned char i;
unsigned char dat;

for(i=0;i<8;i++)
{
	DQ = 0;
	dat >>= 1;
	DQ = 1;
	if(DQ)
	{
		dat |= 0x80;
	}	    
	Delay_OneWire(5);
}
return dat;

}

//DS18B20设备初始化
bit init_ds18b20(void)
{
bit initflag = 0;

DQ = 1;
Delay_OneWire(12);
DQ = 0;
Delay_OneWire(80);
DQ = 1;
Delay_OneWire(10); 
initflag = DQ;     
Delay_OneWire(5);

return initflag;

}

int temp()
{
int temp;
unsigned char tml,tmh;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
tml=Read_DS18B20();
tmh=Read_DS18B20();
temp=tmh;
temp<<=8;
temp|=tml;
temp=temp*0.625+0.5;
return temp;
}

2019-05-04 21:13:47 weixin_44453465 阅读数 864
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

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

五一劳动节过了,我又回来了,上次说到哪来了?说到我把例程都抄完了,流程图也清晰度画了出来,那么,对单片机的程序的总体结构和需要的器件都有个大致的了解,所以,这一博客(本期),将会有如下内容:

  • 需要的功能
  • 既然例程的流程图出来了,我自己需要做的电子钟的流程图也可以类比画出来
  • 通过流程图解释介绍电子钟需要的相关功能与操作方法

最近两期思路
下期(4)预告:画出试验电路(不是成品电路)的原理图(顺便介绍成品电子钟概念外观结构),电路图,元件清单
下下期(5)预告:做出试验电路的电路板,组装
之后的大量:编写程序

正文开始:

需要的功能

  1. 时间方面,可以显示公历,农历,提示节日与节气
  2. 显示温度
  3. 可以设定3组闹钟,并且每组闹钟的开关与时间可以单独控制
  4. 可以储存最近一次保存的时间,闹钟等相关数据
  5. 电压表时刻监测电池电压,提示是否充电
  6. 屏幕背光可以单独控制亮灭及其亮度,做到节电的目的
  7. 使用一个感应按键在某些情况下方便操作
  8. 硬件方面:增加锂电池充电模块
  9. 增加程序下载调试接口

画出流程图

这个流程图一画出来,用了一个晚自习的时间酝酿,终于是有一个大致的结构了,没有画出来之前,脑袋中就只有上面那8条的概念,只知道应该挺复杂的,画出来之后,唉呀妈呀,绝对对于我来说是一个大工程了
在这里插入图片描述

流程图的解释

首先,开始运行程序

第一步:初始化各部分硬件

  • 配置T0定时器,按键扫描(按键分为短按,长按连续加数,长按切换模式)和闹铃(滴滴,滴滴,滴滴)
  • 配置T1定时器,背光的亮度是可以控制的,通过调节背光Led的占空比,节能,晚上又不晃眼
  • 读取存储芯片中数据,包括时间和闹钟,倒计时
  • 初始化时钟模块,设置芯片中上一次备份的时间
  • 初始化液晶:配置好液晶引脚并清屏
  • 初始化温度,启用温度转换确定温度传感器没有坏
  • 等待2秒时间
  • 到此时所有初始化差不多完成了,就只剩下电压表功能(数模转换芯片),想想可以省略掉了,因为电压可以直接读取出来(只要芯片没有坏),就不要初始化也行,坏了也没有办法啊

第一步结束

第二步:进入初始界面(显示各种必要信息)
在这里插入图片描述

  • 显示时间和日期
  • 刷新提示标识,若当天有节日或节气则有提示
  • 刷新倒计时天数
  • 刷新温度显示
  • 刷新电源电压
  • 刷新闹钟提示标识,若3闹钟有一个打开就有闹钟提示

第二步结束
晚自习结束,下次更新

这是一条富强民主文明和谐自由平等公正法制爱国敬业诚信友善的的分割线(2019.5.4)D10

第三步:进入(while)循环

  • 检测是否有按键按下,若为否
  • 检测是否为模式1(持续更新主页时间温度等信息),是的话继续
  • 200ms刷新一次时间于电压,并判断一次闹钟时间是否到达
  • 2s刷新一次温度(因为温度转换时间比较长)
  • 3s刷新1次日期:为什么是3秒呢,因为有一个功能是农历日期与公历日期持续切换状态,间隔为3秒换一次
  • 每一天结束的时候刷新日期(日期数据加一天),刷新倒计时(倒计时减一天)到此循环结束

主页状态就一直像上面的方式运行保持电子钟的时间走动

第四步:若检测到按键按下
注意此处内容是写程序的核心,程序的结构就是根据操作方式写出来的,所以看看下面的图片理解一下这个闹钟是如何操作的
在如下主页状态下
在这里插入图片描述

  • 点按公农:切换另外一种日期格式显示3秒后恢复
  • 长按公农:直接显示另外一种日期格式
  • 点按调试:进入调时间界面
    在这里插入图片描述
    如图,时间需要设置公历日期,公历日期对应的农历日期
    (突然发现没有年份,日后再补,需要年份,因为存在闰月关系)
    (为什么公历农历都有设置呢不能自动对应吗,可以自动对应但是算法复杂,水平不够,所以用查表的方法每过一天日期自动加1并显示就行,在程序中公历和农历没有对应的关系)
    光标自动移动起提示作用,上键点按加1,长按连加,按确认键光标移动到下一个位置,最后按下确认保存时间,更改模式,初始化主页,进入while循环
  • 长按调时:进入倒计时(倒数日)设定界面
    在这里插入图片描述
    此处要用上公历与倒计时的对应关系,为了操作方便,可以设置目标日期与目标天数,比如设置目标日期时,会自动计算出天数并显示在右方,想想此处的程序应该会比较麻烦,慢慢来
  • 点按闹钟:进入闹钟设置时间界面
    在这里插入图片描述
    设置方法与上面的方法相似,此界面只能设置闹钟时间,并不能设置闹钟开关
  • 长按闹钟:进入闹钟开关界面
    在这里插入图片描述
    如果只是想查看闹钟时间,再次按下闹钟退出,或者说继续操作,上打开开关,回车下一个开关
  • 此处还有一个开关是感应开关,不需要按下的那种,手靠近就能感应的到,由于控制背光与闹钟响铃时的静音

再提示一下:注意上面内容是写程序的核心,程序的结构就是根据操作方式写出来的,所以看看上面的图片理解一下这个闹钟是如何操作的

完。其他细节详见最上方流程图

这是一条富强民主文明和谐自由平等公正法制爱国敬业诚信友善的的分割线(2019.5.5)D11

感谢你能看到结尾
便捷目录(粗体为目前位置):
51单片机电子钟 是如何做成的(1)—写在前面
51单片机电子钟 是如何做成的(2)—分析例程结构
51单片机电子钟 是如何做成的(3)—电子钟总体结构与操作
51单片机电子钟 是如何做成的(4)—试验电路的 原理图 元件清单绘制
51单片机电子钟 是如何做成的(5)—试验电路 的电路板制作(热转印法)

以上目录更新于(2019.5.29)D35

2019-09-06 18:41:00 weixin_44477509 阅读数 576
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

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

基于51单片机的电子钟设计

设计要求:
(1) 电源采用12V电池供电;
(2) 采用MCS-51单片机控制,画出控制系统的硬件电路图,并设计PCB板;
(3) 编制控制程序,并画出程序框图,并用Preteus软件进行仿真;
设计内容:
(1) 24进制时间显示;
(2) 调时功能;
(3) 闹钟功能;
(4) 照明功能;
本文附了部分代码和图片展示,全文档请下载!

void main()
{
 TMOD=0x01;
 TH0=(65536-50000)/256;
 TL0=(65536-50000)/256;
 EA=1;
 ET0=1;
 TR0=1;
 option=0;
 alarmflag1=0;
 hour0=0,minute0=0,sceond0=0,hour1=0,minute1=0,sceond1=0,hour2=0,minute2=0;
 while(1)
 {
  functionchoice();
  lightfunction();
  alarmfunction();
  delay();
 }
}
/*******功能选择*******/
void functionchoice()
{
 if(P1_3==1)
  key0=1;
 if(P1_3==0&&key0==1)
 {
  key0=0;
  option++; 
  if(option==3)
   option=0; 
 }
 if(option==0)
 {
  hour1=hour0,minute1=minute0,sceond1=sceond0;
  TR0=1;
  timecontrol();
  timeshow(hour0,minute0,sceond0);
  delay();
 }  
 if(option==1)
 {
  hour0=hour1,minute0=minute1,sceond0=sceond1;
  TR0=0;
  keycontrol(hour1,minute1,sceond1);
  hour1=t[2],minute1=t[1],sceond1=t[0];
  timeshow(hour1,minute1,sceond1);
  delay();
 }
 if(option==2)
 {
  TR0=0;
  keycontrol(hour2,minute2,sceond2);
  hour2=t[2],minute2=t[1],sceond2=t[0];
  timeshow(hour2,minute2,sceond2);
  delay();
 }   
}

PCB电路印刷板设计

电路原理图设计
全文档连接:https://download.csdn.net/download/weixin_44477509/11683217

2010-02-05 11:31:00 pslyjvm 阅读数 1665
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

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

;单片机电子钟汇编程序
;-----------------------------------
HOU_S BIT  P2.7;时十位
HOU_G BIT  P2.6;时个位
MIN_S BIT  P2.5;分十位
MIN_G BIT  P2.4;分个位
SEC_S BIT  P2.3;秒十位
SEC_G BIT  P2.2;秒个位
H_KEY BIT  P3.4;时调整键
M_KEY BIT  P3.5;分调整键
LEDBUS EQU  P0;显示数据总线
SECOND EQU  30H;秒寄存器
MINUTE EQU  31H;分寄存器
HOUR   EQU  32H;时寄存器
TIM_1  EQU  33H;定时器0中断次数
TIM_2  EQU  34H;定时器1中断次数
;-----------------------------------
ORG   0000H
LJMP  INIT
ORG   000BH
LJMP  TIMER0
ORG   30H
;-----------------------------------
;单片机初始化
INIT:
      MOV  SECOND,#0;秒置0
   MOV  MINUTE,#0;分置0
   MOV  HOUR,#0;开机后显示0
   MOV  TIM_1,#10;中断10次为0.5秒
   MOV  TIM_2,#2;0.5*2=1秒
   MOV  SP,#5FH;堆栈指针指向5FH
   MOV  TMOD,#01H;定时器0为模式1
   MOV  TH0,#03CH
   MOV  TL0,#0B0H;50MS初值(晶振12M)
   SETB ET0
   SETB TR0
   SETB EA
;------------------------------------
;主程序
MAIN:
     JNB  H_KEY,HT;时调整键按下转到HT
  JNB  M_KEY,MT;分调整键按下转到MT
  ACALL DISP;调用子程序
  AJMP MAIN;转到LOOP继续检测控制键的状态
;--------------------------------------
;时间调整
;---------------分调整------------------
MT:
   ACALL  DISP;调用显示子程序
   JNB  M_KEY,MT;判断按键是否松开
   INC  MINUTE;分加一
   MOV  A,MINUTE
   CJNE A,#60,MAIN;判断是否加到60
   MOV  MINUTE,#0;分到60变为0
   MOV  SECOND,#0;秒置0
   AJMP MAIN
;----------------时调整------------------
HT:
   ACALL  DISP;调用显示子程序
   JNB  H_KEY,HT;判断按键是否松开
   INC  HOUR;时加1 
   MOV  A,HOUR
   CJNE A,#24,MAIN
   MOV  HOUR,#0;判断时是否到24,到24后清0
   AJMP  MAIN
;-----------------------------------------
;显示子程序
DISP:
     MOV  DPTR,#LEDTAB;数码管显示首地址送DPTR
  MOV  A,SECOND ;秒放入ACC
  MOV  B,#10  
  DIV  AB
  MOVC A,@A+DPTR
  MOV  LEDBUS,A
  CLR  SEC_S
  ACALL D1MS
  SETB SEC_S
  MOV  A,B
  MOVC A,@A+DPTR
  MOV  LEDBUS,A
  CPL  SEC_G
  ACALL D1MS
  SETB SEC_G
  MOV  A,MINUTE;分放入ACC
  MOV  B,#10;B放入10
  DIV  AB;A/B,商在A里,余数在B里
  MOVC A,@A+DPTR;查表取分十位段码
  MOV  LEDBUS,A;段码送LEDBUS显示
  CLR  MIN_S;打开分十位显示
  ACALL D1MS;延迟1MS
  SETB MIN_S;关闭分十位显示
  MOV  DPTR,#LEDTAB2
  MOV  A,B
  MOVC A,@A+DPTR;查表取分个位段码
  MOV  LEDBUS,A
  CLR  MIN_G;打开分个位显示
  ACALL D1MS;延迟1MS
  SETB MIN_G;关闭分十位显示
  MOV  A,HOUR
  MOV  B,#10
  DIV  AB;拆分小时的十位和个位
  JZ  DISP0;十位为0,不显示十位
  MOV DPTR,#LEDTAB
  MOVC A,@A+DPTR
  MOV  LEDBUS,A
  CLR  HOU_S;打开时十位显示
  ACALL D1MS
  SETB HOU_S
DISP0:
     MOV  DPTR,#LEDTAB2
  MOV  A,B
  MOVC A,@A+DPTR
  MOV  LEDBUS,A
  CLR  HOU_G;打开时个位显示
  ACALL D1MS
  SETB HOU_G;关闭时个位显示
  RET
;--------------------------------------
;定时器0中断服务程序
TIMER0:
       PUSH ACC;入栈保护ACC和PSW
    PUSH PSW
       MOV  A,#0B0H;定时器0中断服务子程序
    ADD  A,TL0;同步修正
    MOV  TL0,A
    MOV  TH0,#03CH;重装定时器0初值
    DJNZ TIM_1,RETI_1
    MOV  TIM_1,#10;中断10次为0.5秒
    DJNZ TIM_2,RETI_1
    MOV  TIM_2,#2;1秒时间到
    INC  SECOND;秒加一
    MOV  A,SECOND
    CJNE A,#60,RETI_1
    MOV  SECOND,#0;秒到60变0
    INC  MINUTE;分加1
    MOV  A,MINUTE
    CJNE A,#60,RETI_1
    MOV  MINUTE,#0;分到60变0
    INC  HOUR;时加1
    MOV  A,HOUR
    CJNE A,#24,RETI_1
    MOV  HOUR,#0;时到24变0
RETI_1:
       POP PSW
    POP ACC
    RETI
;----------------------------------------
;延迟子程序
D1MS:
     MOV R7,#2;延迟1MS子程序
D_1:
     MOV R6,#250;延迟时间估算250*2*2=1000微秒
  DJNZ R6,$
  DJNZ R7,D_1
  RET
;----------------------------------------
;数码管段码表
LEDTAB:  ;不带点的数码管字码表
       DB  0C0H,0F9H,0A4H,0B0H,99H;0,1,2,3,4
    DB  92H,82H,0F8H,80H,90H;5,6,7,8,9
LEDTAB2:  ;带点的数码管字码表
        DB  40H,79H,24H,30H,19H;0,1,2,3,4
  DB  12H,02H,78H,00H,10H;5,6,7,8,9
END

 

 

 

 

 

 

 

注释:

        相信有很多爱好单片机的朋友都用单片机制作过电子钟,这的确是一个很好的锻炼课题。可是当在你享受成功的快乐或是在朋友面前炫耀的时候,你会突然间发现你当初对着电视校准的电子钟的时间竟然变快或是变慢了。于是你就尝试用各种方法来调整它的走时精度,但是最终的效果还是不尽人意,只好每过一段时间手动调整一次了。渐渐的你有点烦了,不再去管它或是直接弃之不用。

我和大家一样对此深有体会,于是我开始查找翻阅资料,试图找出一个解决的好方法。终于有一天……

废话太多——stop

原因分析:

1.单片机电子钟的计时脉冲基准是由外部晶振的频率经过12分频后提供,采用内部的定时/计数器来实现计时功能。所以,外接晶振频率精确度直接影响电子钟计时的准确性。

2. 单片机电子钟利用内部定时/计数器溢出产生中断(12M晶振一般为50ms)再乘以相应的倍率来实现秒、分、时的转换。大家都知道从定时/计数器产生中断请求到响应中断需要3-8个机器周期(如不明白请参考其它资料),定时中断子程序中的数据入栈和重装定时/计数器的初值还需要占用数个机器周期,还有从中断入口转到中断子程序也要占用一定的机器周期。    例如:

       ORG       00H

       LJMP      START

       ORG       0BH

       LJMP      TIMER                 ;2个机器周期

       ORG       30H

START:

       MOV      30H,       #0         

       MOV      31H,       #0

       MOV      32H,       #0

       MOV      33H,       #0

       MOV      20H,       #10       

       MOV      21H,       #2

       MOV      SP,   #40H            

       MOV      IP,   #00H

       MOV      IE,   #82H                     ;开EA﹑ET0

       MOV      TMOD,   #01H              ;定时器模式1     

       MOV      TH0,       #03CH           ;50MS初裝值

       MOV      TL0,       #0B0H

       SETB      TR0                             ;启动TR0

LOOP:

       ……

 

TIMER:                                             ;定時器中断子程序

       PUSH      ACC                            ;2个机器周期

       PUSH      PSW                            ;2个机器周期

       MOV      TL0,              #0B0H+6+3         

       MOV      TH0,       #03CH

       ……

 

                            RETI

                            END     

从上面的例子大家可以看出从中断入口到定时/计数器初值的低8位装入需要占用2+2+2=6个机器周期。所以我们在编程时一般会把这8个机器周期加入定时/计数器的初值。但是从定时/计数器溢出中断请求到执行中断需要几个机器周期(3-8个机器周期)我们很难确定其准确值,因此导致了电子钟计时不准。

解决方法:

1.采用高精度晶振方案

 虽然采用高精度的晶振可以稍微提高电子钟计时的精确度,但是其并不是导致电子钟计时不准的主要因素,而且高精度的晶振价格较高,所以不必采用此方案。

2.动态同步修正方案

 从程序入手,采用动态同步修正方法给定时/计数器赋初值。动态同步修正方法:由于定时/计数器溢出后又会从0开始自动加数,固在给定时/计数器再次赋值前将定时/计数器低位(TL0)中的值和初始值相加后一并送入定时/计数器中,此时定时/计数器中的值即为动态同步修正后的准确值。

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