完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 friend0720 于 2016-2-25 17:34 编辑
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
|
|
|
|
|
|
|
|
|
这些程序有点深,我把基础整好再看!
|
|
|
|
|
|
鸿哥,我现在单片机入门了,每个小的知识点的驱动程序都还理解,但是我总想仅仅根据手册就能写出相应的驱动程序,可是自己还是仅能参考别人的程序。很多时候我就不求甚解了,但我还真想把每个延时根据手册做出最佳延时,更重要的是把这些小的程序综合起来我就不会了,另外我看你说把相应的小程序,例如按键,ad,lcd等等都应该整理成相应的模块,然后形成一个逻辑结构,然后用时候随意调用就行,这听起来简单,做起来挺难的,现在你的程序我消化不了,我想知道我究竟要把单片机内部或者各种芯片学到什么程度才能消化你的程序和综合应用起来。想请吴哥为我指点迷津。。。另外还想问问深圳究竟乱不乱啊,北京我一个人来了,可深圳一直想去却不敢去。。。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
本帖最后由 friend0720 于 2016-2-25 17:34 编辑
!!!!!!!!!!!!!!!!!!!!!!! |
|
|
|
|
|
本帖最后由 friend0720 于 2016-2-25 17:35 编辑
!!!!!!!!!!!!!!!!!!!!!!!! |
|
|
|
|
|
本帖最后由 friend0720 于 2016-2-25 17:35 编辑
!!!!!!!!!!!!!!!!!!!!!!!! |
|
|
|
|
|
|
|
|
|
第二十九节:时间篇--利用时钟芯片DS1302实现时钟功能
开场白: DS1302是最常用的时钟芯片。价格不贵。仅仅占用单片机3个IO口。精度能满足大多数要求不高的项目。它有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能。当然,纽扣电池作为备用电源必须比主电源的电压低一点,一个纽扣电池可以备用3年,另外,DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。 在移植本驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。 (1) 功能需求: (a) 利用两位数码管,一共分3个窗口显示。第1个窗口实时显示秒的时间,第2个窗口实时显示分的时间,第3个窗口实时显示小时的时间。 (b) 利用一个独立按键,专门用来在3个窗口之间轮流切换。开机上电默认显示第1个窗口(秒),按一次按键后切换到第2个窗口(分),再按一次就切换到第3个窗口(小时),再按则又切换回第1个窗口(秒),依次循环。 (c) 刚开机上电时,先检查时钟芯片的数据是否由于电池没电的原因被清零,如果是被清零了,那么就要把起始时间重新设置为13年5月2日12点50分36秒。开机上电默认显示第1个窗口(秒)。 (2) 硬件原理: (a) 独立按键的电路请参考第二节的内容。 (b) 动态扫描两位数码管的电路请参考第二十一节。 (4)源码适合的单片机: PIC16F73,晶振为3.579545MHz。 (5)源代码讲解如下: #include #define cnt_delay_cnt1 40 //按键去抖动延时阀值 #define cnt_voice_time 150 //按键声音的持续时间 #define cnt_1s 996 //一秒钟需要多少次while(1)主循环 //补充说明:吴坚鸿程序风格是这样的,凡是输出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 DS1302_SCLK_dr RC0 // DS1302时钟信号7脚输出 #define DS1302_DIO_dr RC1 // DS1302数据信号6脚输出,特别注意此引脚在程序中在输入和输出两种状态的切换 #define DS1302_DIO_sr RC1 // DS1302数据信号6脚输入,特别注意此引脚在程序中在输入和输出两种状态的切换 #define DS1302_CE_dr RC2 // DS1302片选5脚输出 #define WRITE_SECOND 0x80 //设置时间DS1302的相关参数,其实也就是地址 #define WRITE_MINUTE 0x82 #define WRITE_HOUR 0x84 #define WRITE_DATE 0x86 #define WRITE_MONTH 0x88 #define WRITE_YEAR 0x8C #define READ_SECOND 0x81 #define READ_MINUTE 0x83 #define READ_HOUR 0x85 #define READ_DATE 0x87 #define READ_MONTH 0x89 #define READ_YEAR 0x8D #define WRITE_PROTECT 0x8E #define WRITE_CHECK 0xC2 //写入RAM区同一个字节的地址 #define READ_CHECK 0xC3 //读取RAM区同一个字节的地址 void write1302 ( unsigned char addr, unsigned char dat ); //修改时间驱动 unsigned char read1302 ( unsigned char addr ); //读取时间驱动 void ds1302_initial(); //初始化ds1302 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 int while_cnt=0; //1秒钟累加的主循环while(1)次数 unsigned char wd_sec=1; //主窗口变量,上机默认显示第1个窗口(秒) unsigned char second_set=0x00; //读取秒的变量,BCD码数据类型 unsigned char minute_set=0x00; //读取分的变量,BCD码数据类型 unsigned char hour_set=0x00; //读取小时的变量,BCD码数据类型 unsigned char check1302=0; //读取时钟芯片内部RAM区标志位的中间变量 main() //主程序 { initial(); //初始化 while(1) { CLRWDT(); //喂单片机内部自带的看门狗,大家可以不管它 key_service(); //按键服务函数 read_time(); //读取时间,大概将近每秒钟更新一次数据 } } //修改时间驱动 void write1302 ( unsigned char addr, unsigned char dat ) { unsigned char i,temp; TRISC1 = 0; // DS1302数据信号6脚输出 DS1302_CE_dr=0; //CE引脚为低,数据传送中止 asm("nop");asm("nop"); DS1302_SCLK_dr=0; //清零时钟总线 asm("nop");asm("nop"); DS1302_CE_dr = 1; //CE引脚为高,逻辑控制有效 asm("nop");asm("nop"); //发送地址 for ( i=0; i<8; i++ ) //循环8次移位 { DS1302_DIO_dr = 0; CLRWDT(); temp = addr; if(temp&0x01) { DS1302_DIO_dr =1; } else { DS1302_DIO_dr =0; } asm("nop");asm("nop"); addr >>= 1; //右移一位 DS1302_SCLK_dr = 1; asm("nop");asm("nop"); DS1302_SCLK_dr = 0; asm("nop");asm("nop"); } //发送数据 for ( i=0; i<8; i++ ) //循环8次移位 { DS1302_DIO_dr = 0; CLRWDT(); temp = dat; if(temp&0x01) { DS1302_DIO_dr =1; } else { DS1302_DIO_dr =0; } asm("nop");asm("nop"); dat >>= 1; //右移一位 DS1302_SCLK_dr = 1; asm("nop");asm("nop"); DS1302_SCLK_dr = 0; asm("nop");asm("nop"); } DS1302_CE_dr = 0; asm("nop");asm("nop"); } //读取时间驱动 unsigned char read1302 ( unsigned char addr ) { unsigned char i,temp,dat1; DS1302_CE_dr=0; asm("nop");asm("nop"); DS1302_SCLK_dr=0; asm("nop");asm("nop"); DS1302_CE_dr = 1; asm("nop");asm("nop"); TRISC1 = 0; // DS1302数据信号6脚输出 //发送地址 for ( i=0; i<8; i++ ) //循环8次移位 { DS1302_DIO_dr = 0; CLRWDT(); temp = addr; if(temp&0x01) { DS1302_DIO_dr =1; } else { DS1302_DIO_dr =0; } asm("nop");asm("nop"); addr >>= 1; //右移一位 DS1302_SCLK_dr = 1; asm("nop");asm("nop"); DS1302_SCLK_dr = 0; asm("nop");asm("nop"); } //读取数据 TRISC1 = 1; // DS1302数据信号6脚输入 temp=0; for ( i=0; i<8; i++ ) { temp>>=1; CLRWDT(); if(DS1302_DIO_sr==1) { temp=temp+0x80; } asm("nop");asm("nop"); DS1302_SCLK_dr = 1; asm("nop");asm("nop"); DS1302_SCLK_dr = 0; asm("nop");asm("nop"); } DS1302_CE_dr=0; asm("nop");asm("nop"); dat1=temp; return (dat1); } void ds1302_initial() //初始化ds1302 { check1302=read1302 (READ_CHECK); //读取RAM区标志位数据,如果不是特定的0xaa数据,那么就认为电池没电了,时间需要重新更改 if(check1302!=0xaa) { //注意:写入的数据都是BCD码类型 write1302 (WRITE_PROTECT,0X00); //禁止写保护 write1302 (WRITE_CHECK,0xaa); //RAM区重新写入特定的标志位0xaa write1302 (WRITE_YEAR,0x13); //年初始化,13年 write1302 (WRITE_MONTH,0x05); //月初始化,5月 write1302 (WRITE_DATE,0x02); //日初始化,2日 write1302 (WRITE_HOUR,0x12); //小时初始化,12点 write1302 (WRITE_MINUTE,0x50); //分钟初始化,50分 write1302 (WRITE_SECOND,0x36); //秒位初始化,36秒 write1302 (WRITE_PROTECT,0x80); //允许写保护 } } void read_time() //读取时间,大概将近每秒钟更新一次数据 { ++while_cnt; //1秒钟需要的主循环while(1)次数累加 if(while_cnt>=cnt_1s) //将近1秒钟更新一次数据 { while_cnt=0; second_set=read1302 (READ_SECOND); //读取秒 minute_set=read1302 (READ_MINUTE); //读取分钟 hour_set=read1302 (READ_HOUR); //读取小时 } } 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; //按键输入 TRISC0=0; //DS1302时钟芯片的引脚 TRISC1=0; //特别注意此引脚在程序中用来做输入和输出状态的切换 TRISC2=0; ds1302_initial(); //初始化ds1302 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>>4; //把需要显示的数据分解出十位,注意:数据是BCD码类型 number_right=second_set&0x0f; //把需要显示的数据分解出个位,注意:数据是BCD码类型 break; case 2: //在第2个窗口下,显示分的时间 number_left=minute_set>>4; //把需要显示的数据分解出十位,注意:数据是BCD码类型 number_right=minute_set&0x0f; //把需要显示的数据分解出个位,注意:数据是BCD码类型 break; case 3: //在第3个窗口下,显示小时的时间 number_left=hour_set>>4; //把需要显示的数据分解出十位,注意:数据是BCD码类型 number_right=hour_set&0x0f; //把需要显示的数据分解出个位,注意:数据是BCD码类型 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)下集预告: 时间篇—利用时钟芯片DS12C887实现时钟功能 (未完待续,下节更精彩,不要走开哦) |
|
|
|
|
|
|
|
|
|
|
|
|
|
只有小组成员才能发言,加入小组>>
求解外围电路实现的是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 08:10 , Processed in 0.914065 second(s), Total 88, Slave 80 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号