完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
|
|
本帖最后由 jianhong_wu 于 2013-7-16 13:12 编辑
第三十节:时间篇--利用时钟芯片DS12C887实现时钟功能 开场白: DS12C887最大的好处就是不用外挂纽扣电池。因为它内部集成了一个锂电池,系统掉电情况下可自行精确走10年。至于其他的功能,基本上DS1302有的功能它都有,DS1302没有的功能它也有。缺点是价格比较贵,原装正品要20元左右一个,体积大,占用单片机的IO口资源也很多,因为它是用并口方式跟单片机通讯,共需要 13个IO。大部分的家用消费类电子是用不起的,但是在很多工控领域,这个芯片还是很受欢迎的。 在使用这个芯片的时候,有一个地方要特别注意。千万不要为了节省1个IO口而把芯片的片选引脚CS直接接地,否则得不偿失,时钟数据会不准确。鸿哥当年吃过这样的亏。 (1) 功能需求: (a) 利用两位数码管,一共分3个窗口显示。第1个窗口实时显示秒的时间,第2个窗口实时显示分的时间,第3个窗口实时显示小时的时间。 (b) 利用一个独立按键,专门用来在3个窗口之间轮流切换。开机上电默认显示第1个窗口(秒),按一次按键后切换到第2个窗口(分),再按一次就切换到第3个窗口(小时),再按则又切换回第1个窗口(秒),依次循环。 (c) 刚开机上电时,把起始时间设置为13年5月2日12点50分36秒。开机上电默认显示第1个窗口(秒)。 (2) 硬件原理: (a)独立按键的电路请参考第二节的内容。 (b)动态扫描两位数码管的电路请参考第二十一节。 (c)芯片引脚的详细连接电路网上很多,请自己查找。 (4)源码适合的单片机: PIC16F74,晶振为3.579545MHz。 (5)源代码讲解如下: #include #define uchar unsigned char #define uint unsigned int #define cnt_delay_cnt1 40 //按键去抖动延时阀值 #define cnt_voice_time 150 //按键声音的持续时间 //补充说明:吴坚鸿程序风格是这样的,凡是输出IO后缀都是_dr,凡是输入的IO后缀都是_sr #define seg_0_dr RB6 //任意7个IO口接数码管的seg引脚 #define seg_1_dr RB5 #define seg_2_dr RB4 #define seg_3_dr RB3 #define seg_4_dr RC7 #define seg_5_dr RB0 #define seg_6_dr RB1 #define com_left_dr RB2 //任意2个IO口接数码管的com引脚 #define com_right_dr RB7 #define beep_dr RA2 //蜂鸣器输出 #define key_sr1 RC4 //独立按键输入<窗口切换>键 #define dsirq_sr RC0 //此引脚必须外部加上拉电阻(4K到20K之间都没问题) #define dscs_dr RC1 #define dsas_dr RC2 #define dsrw_dr RC3 #define dsds_dr RC5 void CloseDs12c887(void); void ds12C887_check(); unsigned char GetSeconds(void); //是二进制码,不是BCD码 unsigned char GetMinutes(void);//是二进制码,不是BCD码 unsigned char GetHours(void);//是二进制码,不是BCD码 unsigned char GetDate(void); unsigned char GetMonth(void); unsigned char GetYear(void); unsigned char GetCentury(void); unsigned char GetXingQi(void); void SetXingQi(unsigned char xq); void SetTime(unsigned char chSeconds,unsigned char chMinutes,unsigned char chHours);//是二进制码,不是BCD码 void SetDate(unsigned char chDate,unsigned char chMonth,unsigned char chYear);//是二进制码,不是BCD码 void write(unsigned char add,unsigned char date); unsigned char read_ds(unsigned char add); void Clear_Irq(void); void ds12c887_initial(); //初始化ds12c887 void read_time(); //读取时间 void initial();//初始化 void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗 void display_drive(); //数码管驱动程序,放在定时中断里 void display_seg(unsigned char seg); //编码转换程序,放在display_drive里 void key_scan(); //按键扫描函数,放在定时中断里,有人说不应该把子程序放在中断里,别听他们,信鸿哥无坎坷。 void key_service(); //按键服务函数,放在main函数循环里 unsigned char number_left=0; //左边数码管显示的内容 unsigned char number_right=0; //右边数码管显示的内容 unsigned char dis_step=1; //扫描的步骤 unsigned char key_lock1=0; //按键自锁标志 unsigned int delay_cnt1=0; //延时计数器的变量 unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时 unsigned char key_sec=0; //哪个按键被触发 unsigned char wd_sec=1; //主窗口变量,上机默认显示第1个窗口(秒) unsigned char second_set=0x00; //读取秒的变量,是二进制码,不是BCD码 unsigned char minute_set=0x00; //读取分的变量,是二进制码,不是BCD码 unsigned char hour_set=0x00; //读取小时的变量,是二进制码,不是BCD码 main() //主程序 { initial(); //初始化 while(1) { CLRWDT(); //喂单片机内部自带的看门狗,大家可以不管它 key_service(); //按键服务函数 read_time(); //读取时间,每秒钟更新一次数据 } } void write(unsigned char add,unsigned char date) //发送一个字节数据到内部特定地址 { TRISD=0x00; //设置数据线为输出 dscs_dr=0; dsas_dr=1; dsds_dr=1; dsrw_dr=1; PORTD=add; asm("nop"); asm("nop"); dsas_dr=0; dsrw_dr=0; PORTD=date; asm("nop"); asm("nop"); dsrw_dr=1; dsas_dr=1; dscs_dr=1; } unsigned char read_ds(unsigned char add) //读取内部特定地址的数据 { unsigned char ds_date; TRISD=0x00; //设置数据线为输出 dscs_dr=0; dsas_dr=1; dsds_dr=1; dsrw_dr=1; PORTD=add; asm("nop"); asm("nop"); dsas_dr=0; dsds_dr=0; TRISD=0xff; //设置数据线为输入 asm("nop"); asm("nop"); ds_date=PORTD; asm("nop"); asm("nop"); dsds_dr=1; dsas_dr=1; dscs_dr=1; return ds_date; } void ds12C887_check() //检测芯片内部时候已经准备好了 { unsigned char ds_check=0xff; unsigned int time_out_cnt=0; while(1) // { ds_check=read_ds(0x0a); ds_check=ds_check&0x80; if(ds_check==0x00)break; time_out_cnt++; //超时计数器 if(time_out_cnt>2000)//这里的2000数据是我凭感觉加上去的,没验证过 { break; } } } void Clear_Irq(void) //清除中断输出IRQ的中断电平信号 { unsigned char c_temp; c_temp=read_ds(0x0c); } unsigned char GetSeconds(void) //从时钟芯片读取秒字节,是二进制码,不是BCD码 { unsigned char rd; ds12C887_check(); rd=read_ds(0x00); return rd; } unsigned char GetMinutes(void) //从时钟芯片读取分字节,是二进制码,不是BCD码 { unsigned char rd; ds12C887_check(); rd=read_ds(0x02); return rd; } unsigned char GetHours(void) //从时钟芯片读取小时字节,是二进制码,不是BCD码 { unsigned char rd; ds12C887_check(); rd=read_ds(0x04); return rd; } unsigned char GetDate(void) //从时钟芯片读取日字节,是二进制码,不是BCD码 { unsigned char rd; ds12C887_check(); rd=read_ds(0x07); return rd; } unsigned char GetXingQi(void) //读取星期 { unsigned char rd; ds12C887_check(); rd=read_ds(0x06); return rd; } unsigned char GetMonth(void) //从时钟芯片读取月字节 { unsigned char rd; ds12C887_check(); rd=read_ds(0x08); return rd; } unsigned char GetYear(void) //从时钟芯片读取年字节 { unsigned char rd; ds12C887_check(); rd=read_ds(0x09); return rd; } unsigned char GetCentury(void) //从时钟芯片读取世纪字节 { unsigned char rd; ds12C887_check(); rd=read_ds(0x32); return rd; } void SetTime(unsigned char chSeconds,unsigned char chMinutes,unsigned char chHours) //设定秒,分,小时,是二进制码,不是BCD码 { ds12C887_check(); write(0x00,chSeconds); ds12C887_check(); write(0x02,chMinutes); ds12C887_check(); write(0x04,chHours); } void SetXingQi(unsigned char xq) //设定星期 { ds12C887_check(); write(0x06,xq); } void SetDate(unsigned char chDate,unsigned char chMonth,unsigned char chYear) //设定日,月,年,是二进制码,不是BCD码 { ds12C887_check(); write(0x07,chDate); ds12C887_check(); write(0x08,chMonth); ds12C887_check(); write(0x09,chYear); } void ds12c887_initial() //初始化ds12c887 { write(0x0b,0x96); //配置寄存器,具体什么含义我也忘了,估计是打开设置模式以及相关设置,照抄即可 write(0x0a,0x20); //配置寄存器,具体什么含义我也忘了,估计是打开设置模式以及相关设置,照抄即可 write(0x0b,0x96); //配置寄存器,具体什么含义我也忘了,估计是打开设置模式以及相关设置,照抄即可 write(0x32,0x14); //设置为21世纪 SetTime(36,50,12); //设置秒,分,小时 12点50分36秒 SetDate(2,5,13); //设置日,月,年 13年5月2日 write(0x0b,0x16); // 具体什么含义我也忘了,估计是关闭设置模式或打开时钟,照抄即可 } void read_time() //读取时间,大概将近每秒钟更新一次数据 { if(dsirq_sr==0) //一秒钟产生中断输出低电平 { second_set=GetSeconds(); //是二进制码,不是BCD码 minute_set=GetMinutes(); //是二进制码,不是BCD码 hour_set=GetHours(); //是二进制码,不是BCD码 Clear_Irq(); //清除中断引脚低电平 } } void interrupt timer1rbint(void) //中断程序入口 { if(TMR1IE==1&&TMR1IF==1) //定时中断程序 { TMR1IF=0; TMR1ON=0; if(voice_time_cnt) //控制蜂鸣器声音的长短 { beep_dr=1; //蜂鸣器响 --voice_time_cnt; //蜂鸣器响的声音长短的计数延时 } else { asm("nop"); //没别的意思,纯粹为了保持if和else各自括号内的队伍对称。不是鸿哥有"对称控"的洁癖,我只是感觉这样可以起到耗时平衡的小作用。 beep_dr=0; //蜂鸣器停止 } key_scan(); //按键扫描函数 display_drive(); //数码管驱动程序,放在定时中断里 TMR1H=0xFF; TMR1L=0xC8; TMR1ON=1; } } void initial()//初始化 { ADCON0=0x41; //设置AD模式 ADCON1=0x04; //RA0作为AD输入通道 TRISB=0x00; //数码管IO口设置成输出 TRISC7=0; TRISA2=0; //蜂鸣器输出 TRISC4=1; //按键输入 //时钟芯片的IO口方向设置 TRISC0=1; //此引脚必须外部加上拉电阻(4K到20K之间都没问题) TRISC1=0; TRISC2=0; TRISC3=0; TRISC5=0; TRISD=0x00; ds12c887_initial(); //初始化ds12c887 T1CON=0x24; //定时中断配置 TMR1H=0xFE; TMR1L=0xEF; INTCON=0xC0; TMR1IF=0; TMR1IE=1; PEIE=1; //外围中断允许 GIE=1; TMR1ON=1; } void display_drive() //数码管驱动程序,放在定时中断里 { switch(wd_sec) //窗口显示变量,人机界面的主线 { case 1: //在第1个窗口下,显示秒的时间 number_left=second_set/10; number_right=second_set%10; break; case 2: //在第2个窗口下,显示分的时间 number_left=minute_set/10; number_right=minute_set%10; break; case 3: //在第3个窗口下,显示小时的时间 number_left=hour_set/10; number_right=hour_set%10; break; } seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; com_left_dr=1; com_right_dr=1; //在即将更换下一位数码管时,先把让它两个什么都不显示,让显示过度更加平稳 asm("nop"); //空指令延时 asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); switch(dis_step) { case 1: //扫描左边的数码管 display_seg(number_left); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=0; //选中左数码管 com_right_dr=1; break; case 2: //扫描右边的数码管 display_seg(number_right); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=1; com_right_dr=0; //选中右数码管 break; } delay1(50); //每一位数码管显示的停留延时时间,有疑问的朋友请自己尝试改成计数延时的方式, //鸿哥认为在此种环境下,在定时中断里用死延时delay1(50)是最佳的方式 ++dis_step; //下一次中断扫描另外一位的数码管,轮流扫描 if(dis_step>2) { dis_step=1; } } //不是鸿哥不懂爱,如果不是用任意IO口,而是直接用一个并口(比如51单片机中的P1口),那么就不用那么费力, //直接用查数组(俗称查表)的方式就可以替代display_seg这个编码转换程序, void display_seg(unsigned char seg) //编码转换程序,,放在display_drive里 { switch(seg) //switch指令,单片机中的战斗机,鸿哥的最爱! { case 0: //显示"0" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=0; seg_5_dr=1; seg_6_dr=1; break; case 1: //显示"1" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; case 2: //显示"2" seg_0_dr=1; seg_1_dr=0; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 3: //显示"3" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 4: //显示"4" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=1; seg_5_dr=0; seg_6_dr=1; break; case 5: //显示"5" seg_0_dr=0; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 6: //显示"6" seg_0_dr=0; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 7: //显示"7" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=1; seg_6_dr=0; break; case 8: //显示"8" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 9: //显示"9" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 10: //什么也不显示,空 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; } } void delay1(unsigned int de) { unsigned int t; for(t=0;t void key_scan() //按键扫描函数 { if(key_sr1==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock1=0; //按键自锁标志清零 delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock1==0) //有按键按下,且是第一次被按下 { ++delay_cnt1; //延时计数器 if(delay_cnt1>cnt_delay_cnt1) { delay_cnt1=0; key_lock1=1; //自锁按键置位,避免一直触发 key_sec=1; //触发1号键 } } } void key_service() //按键服务函数 { switch(key_sec) //按键服务状态切换 { case 1:// 1号键 专门用来切换3个窗口 wd_sec++; //在3个窗口之间切换 if(wd_sec>3) { wd_sec=1; } voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零, break; } } (6)下集预告: 时间篇—结合CPLD芯片实现超高精度的计时 (未完待续,下节更精彩,不要走开哦) 评分 |
|
|
|
松翰单片机SN8P2511 SN8P2711B SN8P2501B SN8P2722 SN8P1613 SN8P2612 SN8P2522
|
|
|
|
|
|
本帖最后由 friend0720 于 2016-2-25 17:37 编辑
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
很久以前的想法终于付诸行动了,哈哈。做技术都不容易,鸿哥这样的人更是少之又少,无私奉献,慷慨,乐于分享,鸿哥存在,分享无限。所以,我自己51平台的移植也要和大家分享,哥们,鸿哥这些程序很实用,来的陡(方言,意思是 来的直接,呵呵),有空仔细研究研究吧。 按键的硬件电路大家需要多动脑子,就蜂鸣器 鸿哥说的是8050三极管驱动,在PIC平台当然能行,但是在51平台,51上电IO口是高电平状态,所以需要思考了,怎样避免上电蜂鸣器就响,那么我的方案是用9012、9013、9014都可以,蜂鸣器低电平有效,高电平关闭,这样可以避免一上电蜂鸣器就发声。如图: 软件需要注意的程序中有说明,特别注意定时器中断时间不能太长,否则会漏扫按键。我的习惯是采用多文件联编,不知道怎么搞的大家自行百度,谢谢合作! 题外话:有些网友可能困惑,没有有源蜂鸣器,肿么办?那么Stone告诉你,LED一样可以,大家明白了否?呵呵。 |
|
|
|
|
|
|
|
|
|
来看看。。。。我才看懂几节。。。。。大家都发声。。。。看看,,,,大家都有多少学习到了。。。。我的要求是一星期学会吴大师的一节。。。。。吴大师早发财。。。。。把我们都收了吧。。。。
|
|
|
|
|
|
第二十三节:利用74HC595静态驱动数码管。。。。595的级联就是9片595的ST...SH全部并联在一起。。。4520输出的串行数据只连接第一个595的DS端。。。第二个595的DS端连接第一个595的Q7’端串行数据输出端。。。下面的依次类推。。。吴大师。。。。在实际的开发中。。。。不会有一下要用9个595.。。。9个2003吧。。。。这样是不是经济上不划算。。。。顶一下。。。吴大师。。。
|
|
|
|
|
|
只有小组成员才能发言,加入小组>>
求解外围电路实现的是4脚给持续低电平复位并正常工作,高电平不工作的原因
2081 浏览 1 评论
3625 浏览 3 评论
PIC1946程序有一个变量在运行过程中恢复初始值其他变量保持不变
2334 浏览 2 评论
2761 浏览 0 评论
PIC16F1825的RC5引脚,在主程序中操作无效,在中断中可以改变是为什么?
4022 浏览 5 评论
970浏览 0评论
用XC8编译PIC18F25K80时提示下面Error,求怎么解决这个问题
6353浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-1 10:37 , Processed in 1.282199 second(s), Total 105, Slave 90 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号