完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
第二节 独立按键与蜂鸣器
C51平台移植(测试通过)。 谢谢鸿哥这些不错的帖子,在移植的过程中学到了很多,有进一步的认识,也了解了项目中需要注意的问题,学到了一些思想,其实编程就是一种思想。比如,矩阵按键,分段扫描,很实用,满足基本的实时性要求。 如有bug,欢迎指正! |
|
|
|
讲的确实不错 我们一定要一点一滴的去学习 不能盲目 没有目的性的去学习
|
|
|
|
|
|
谢谢吴大师的分享,谢谢!
|
|
|
|
|
|
本帖最后由 friend0720 于 2016-2-25 17:39 编辑
!!!!!!!!!!!!!!!!!!!!!!! |
|
|
|
|
|
最近准备玩单片机,看了第一节后,就忍不住,继续看第二节,但是发现有很多一下子看不过来,于是把它都做成了文档慢慢消化,一路上看了很多回复,感觉鸿哥交给我们的不是代码,而是一个个项目的方案,编程的思路。我是刚刚学的51单片机,对于编写具体的某些模块还好,但是要做成项目感觉却无从下手。 其实鸿哥的这些代码还不算什么,真正伟大的是鸿哥的无私分享 。。。谢谢鸿哥的分享!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
第三十一节:时间篇--结合CPLD芯片实现超高精度的计时
开场白: 鸿哥从来不相信单片机能实现超高精度的时间功能,因此我从来不会关注定时中断的初始值计算公式,当我用单片机实现时间功能时,凭的是感觉以及事后校正定时中断的次数来实现时间功能。但是如果有人问吴坚鸿“CPLD或FPGA能实现高精度的时间功能,你信吗?”我当然信,而且深信不疑。如果认为光速是没有延时,那么假设晶振是零误差的情况下,我认为CPLD或FPGA是可以做到无误差的时间。因为CPLD和单片机有本质的区别。单片机是靠执行一条指令所耗的时间来达到时间功能,而CPLD是靠硬件锁存器以及计数器来达到时间的功能。一个是软件方法,一个是硬件方法。 懂了CPLD或FPGA,你可以自己任意“制造”任何一款74系列的外围数字芯片,通讯协议也是你自己设计,自己定制,想怎么玩就怎么玩。现在的逻辑分析仪和数字示波器也是用CPLD或FPGA来做的。玩单片机的人,你可以不学嵌入式,你可以不学VC,你也可以不学DSP,但是你不可以不学CPLD或FPGA,不然你就要错过自己DIY数字电路的美妙体验。 这一节为了实现超高精度的倒计时功能,我采用了单片机跟CPLD结合的方案。单片机仅仅负责显示和按键操作,剩下的时间功能和LED实时输出信号都是由CPLD完成。CPLD可以精确地捕捉到晶振的每一个下降沿,我用1M的有源晶振作为CPLD的”心脏脉冲“输入,因此我可以记录和显示0.000001秒。但是这一节我只用了4位数码管,因此我只保留小数点后面3位数。同时我又采用的是第二十一节不带小数点的数码管,因此当数码管显示”0001”秒就代表”0.001” 秒,”9752” 秒就代表”9.752” 秒。 (1) 功能需求: 利用4位数码管实时显示秒的时间。开机默认初始值是0.000秒(数码管显示0000),此时一个LED灭。当按下启动按键后,LED灯马上变亮,同时计时开始,我们可以看到数码管显示的时间在实时变化。当时间从0.000秒计时到9.752秒时,计时结束,LED灯马上变灭。此时再按启动按键则重新开始新一轮的计时。 (2) 硬件原理: (a)因为LED灯的输出要求高速实时,因此让CPLD来直接控制LED灯。 (b)独立按键的电路请参考第二节的内容。 (c)动态扫描两位数码管的电路请参考第二十一节。 (d)单片机与CPLD的IO连接时,中间串接470欧的电阻。 (e)CPLD的IO口不能同时为输入和输出,两者只能选其一,因此想实现1根单片机IO跟CPLD进行双向数据通信,在物理硬件上必须让1根单片机的IO连接2根CPLD的IO口,当往CPLD输入数据时,必须让另外一根输出的IO口输出-高阻态Z。 (3)单片机与CPLD的通信协议。 本节 CPLD的VHDL程序是我开发的,因此通信协议也是我自己定义设计的。为了节省IO口而又能兼顾到速度,这里采用的是半并口的方式,也就是一个脉冲只能发送或者读取4位数据。要读取或者写入一个28位的完整数据需要分7个脉冲来完成。其他细节的时序我都封装在相关驱动的子程序中,可以查看我的源代码、 (4)源码适合的单片机: PIC16F74,无源晶振为3.579545MHz。 (5)VHDL源码适合的CPLD:EPM570T100C5,有源晶振为1MHz。 开发环境为Quartus II。 (6)单片机的C语言源代码讲解如下: #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_4_dr RB2 //4个IO口接数码管的com引脚 #define com_3_dr RB7 #define com_2_dr RC0 #define com_1_dr RC1 #define beep_dr RA2 //蜂鸣器输出 #define key_sr1 RC4 //独立按键输入<启动>键 #define cpld_hl2_dr RC2 //CPLD的译码地址选择,选择输入或者写入的是4位数据总线 #define cpld_hl1_dr RC5 //CPLD的译码地址选择,选择输入或者写入的是4位数据总线 #define cpld_hl0_dr RA1 //CPLD的译码地址选择,选择输入或者写入的是4位数据总线 #define cpld_rw_dr RD7 //CPLD的读写信号 #define cpld_clk_dr RD6 //CPLD的总线发送数据的时钟信号 #define cpld_luck_dr RD5 //CPLD的锁存捕捉当前瞬间的计时数值,下降沿有效 #define cpld_start_dr RD4 //CPLD的启动或停止计时信号,上升沿启动,低电平停止。 #define cpld_finish_sr RA3 //CPLD的计时结束输出信号,高电平时表示CPLD正在运行计时,还没倒计时结束 //另外4根单片机IO口RD3,RD2,RD1,RD0,作为数据总线 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 int read_cpld(); //读取CPLD内部运行的当前时间,单片机跟CPLD的通讯协议都在这里 void set_time(unsigned int time); //设置CPLD内部运行的时间,单片机跟CPLD的通讯协议都在这里 unsigned char number_4=0; //第4位数码管显示的内容 unsigned char number_3=0; //第3位数码管显示的内容 unsigned char number_2=0; //第2位数码管显示的内容 unsigned char number_1=0; //第1位数码管显示的内容 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 run_time=0; //实时运行的显示时间 unsigned char start_flag=0; //开始运行的标志 unsigned char run_step=1; //运行步骤变量 main() //主程序 { initial(); //初始化 while(1) { CLRWDT(); //喂单片机内部自带的看门狗,大家可以不管它 key_service(); //按键服务函数 read_time(); //实时高速读取时间,虽然显示的时间跟实际的时间有点滞后,但是没有任何影响,因为LED持续亮的时间9.752秒是超级精确的。 } } void read_time() //读取时间 { if(start_flag==1) //计时开始 { switch(run_step) { case 1: cpld_start_dr=1; //产生上升沿,给CPLD立即运行的指令信号,开始计时 asm("nop"); asm("nop"); run_step=2; //切换到下一个步骤 break; case 2: run_time=read_cpld(); //读取实时时间显示,虽然显示时间跟实时时间有滞后,但是没任何影响 if(cpld_finish_sr==0) //如果CPLD已经计时从0.000秒到9.752秒结束,则结束进入此条件状态, { run_time=read_cpld(); //显示最后停止了的时间,这个时间肯定不会是滞后的。 start_flag=0; //结束开始运行的标志 cpld_start_dr=0; //取消给CPLD的开始信号,为下一次的上升沿启动信号做准备 run_step=1; //切换回第一个步骤 } break; } } } unsigned int read_cpld() //读取CPLD内部运行的当前时间 { unsigned long time_temp; //读取CPLD内部晶振脉冲数的中间变量,包含小数点后面6位的数据 unsigned int read_temp; //读取CPLD内部晶振脉冲数的中间变量,包含小数点后面3位的数据,我们显示的就是这个数据 time_temp=0x00000000; TRISD0=1; //设置数据总线的方向寄存器为输入 TRISD1=1; TRISD2=1; TRISD3=1; cpld_luck_dr=1; //锁存捕捉CPLD瞬间的计时数据 asm("nop"); asm("nop"); cpld_luck_dr=0; asm("nop"); asm("nop"); cpld_rw_dr=1; //读 cpld_hl2_dr=1; //地址译码,读取数据总线的第24位至27位 cpld_hl1_dr=1; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=1; //地址译码,读取数据总线的第20位至23位 cpld_hl1_dr=0; cpld_hl0_dr=1; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=1; //地址译码,读取数据总线的第16位至19位 cpld_hl1_dr=0; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第12位至15位 cpld_hl1_dr=1; cpld_hl0_dr=1; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第8位至11位 cpld_hl1_dr=1; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第4位至7位 cpld_hl1_dr=0; cpld_hl0_dr=1; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第0位至3位 cpld_hl1_dr=0; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 time_temp<<=1; if(RD3==1)time_temp=time_temp+1; time_temp<<=1; if(RD2==1)time_temp=time_temp+1; time_temp<<=1; if(RD1==1)time_temp=time_temp+1; time_temp<<=1; if(RD0==1)time_temp=time_temp+1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 read_temp=time_temp/1000; //本来是小数点后面6位的数据,而我们只提取小数点后面3位的 return read_temp; } void set_time(unsigned int time) //设置CPLD内部运行的时间 { unsigned long time_temp; TRISD0=0; //设置数据总线的方向寄存器为输出 TRISD1=0; TRISD2=0; TRISD3=0; time_temp=time; time_temp=time_temp*1000; //我们设置的数据是小数点后面3位的,因此乘以1000 time_temp<<=4; cpld_rw_dr=0; //写 cpld_hl2_dr=1; //地址译码,读取数据总线的第24位至27位 cpld_hl1_dr=1; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=1; //地址译码,读取数据总线的第20位至23位 cpld_hl1_dr=0; cpld_hl0_dr=1; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=1; //地址译码,读取数据总线的第16位至19位 cpld_hl1_dr=0; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第12位至15位 cpld_hl1_dr=1; cpld_hl0_dr=1; asm("nop"); asm("nop"); cpld_clk_dr=0;//时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第8位至11位 cpld_hl1_dr=1; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第4位至7位 cpld_hl1_dr=0; cpld_hl0_dr=1; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 cpld_hl2_dr=0; //地址译码,读取数据总线的第0位至3位 cpld_hl1_dr=0; cpld_hl0_dr=0; asm("nop"); asm("nop"); cpld_clk_dr=0; //时钟信号线,低电平有效 if(time_temp>=0x80000000) //判断最高位 RD3=1; else RD3=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD2=1; else RD2=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD1=1; else RD1=0; time_temp<<=1; if(time_temp>=0x80000000) //判断最高位 RD0=1; else RD0=0; time_temp<<=1; asm("nop"); cpld_clk_dr=1; //时钟信号线,高电平无效 } 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输入通道,本程序中没有用到AD,不用管它 TRISB=0x00; //数码管IO口设置成输出 TRISC7=0; TRISC0=0; TRISC1=0; TRISA2=0; //蜂鸣器输出 TRISC4=1; //按键输入 TRISC2=0; //控制CPLD的通讯接口 TRISC5=0; TRISD=0x00; TRISA1=0; TRISA3=1; //检查CPLD内部是否计时结束。 set_time(9752); //设定计时时间为9.752秒 T1CON=0x24; //定时中断配置 TMR1H=0xFE; TMR1L=0xEF; INTCON=0xC0; TMR1IF=0; TMR1IE=1; PEIE=1; //外围中断允许 GIE=1; TMR1ON=1; } void display_drive() //数码管驱动程序,放在定时中断里 { number_4=run_time/1000; //分解变量第4位用来显示 number_3=run_time%1000/100; //分解变量第3位用来显示 number_2=run_time%100/10; //分解变量第2位用来显示 number_1=run_time%10; //分解变量第1位用来显示 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_4_dr=1; //在即将更换下一位数码管时,先把让4个数码管什么都不显示,让显示过度更加平稳 com_3_dr=1; com_2_dr=1; com_1_dr=1; asm("nop"); //空指令延时 asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); switch(dis_step) { case 1: //扫描第4位的数码管 display_seg(number_4); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_4_dr=0; //选中第4位数码管 com_3_dr=1; com_2_dr=1; com_1_dr=1; break; case 2: //扫描第3位的数码管 display_seg(number_3); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_4_dr=1; //选中第3位数码管 com_3_dr=0; com_2_dr=1; com_1_dr=1; break; case 3: //扫描第2位的数码管 display_seg(number_2); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_4_dr=1; //选中第2位数码管 com_3_dr=1; com_2_dr=0; com_1_dr=1; break; case 4: //扫描第1位的数码管 display_seg(number_1); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_4_dr=1; //选中第1位数码管 com_3_dr=1; com_2_dr=1; com_1_dr=0; break; } delay1(50); //每一位数码管显示的停留延时时间,有疑问的朋友请自己尝试改成计数延时的方式, //鸿哥认为在此种环境下,在定时中断里用死延时delay1(50)是最佳的方式 ++dis_step; //下一次中断扫描另外一位的数码管,轮流扫描 if(dis_step>4) { 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号键 专门用来重新启动计时 start_flag=1; //开始启动计时 voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零, break; } } (7)CPLD的VHDL源代码讲解如下: LIBRARY IEEE; --需要包含的文件库 USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_ARITH.ALL; USE IEEE.STD_LOGIC_UNSIGNED.ALL; entity EPM570 is PORT ( clk: in STD_LOGIC; --低电平表示数据有效 start: in STD_LOGIC; --启动倒计时信号,上升沿则启动,低电平停止 rw: in STD_LOGIC; --高电平表示读数据,低电平表示写数据 mode: in STD_LOGIC; --下降沿可以立即锁存瞬间高速变化的晶振计数器的数值,方便单片机读取数据在数码管上显示 jingzhen_clk: in STD_LOGIC; --接1M的有源晶振的脉冲输入,CPLD靠此最原始的下降沿产生高精度时间 finish: out STD_LOGIC; --计时结束的输出标志,单片机靠判断此IO口才知道计时结束 valve_dr: out STD_LOGIC; --接LED输出 --注意: --CPLD的IO口不能同时为输入和输出,两者只能选其一,因此想实现1根单片机IO跟CPLD进行双向数据通信,在物理 --硬件上必须让1根单片机的IO连接2根CPLD的IO口,当往CPLD输入数据时,必须让另外一根输出的IO口输出高阻态Z。 bus03 : in STD_LOGIC_VECTOR(3 DOWNTO 0); --相对CPLD来说,是4位的总线数据输入,用同样的4根单片机IO连接 out03 : out STD_LOGIC_VECTOR(3 DOWNTO 0); --相对CPLD来说,是4位的总线数据输出,用同样的4根单片机IO连接 hl : in STD_LOGIC_VECTOR(2 DOWNTO 0)); --这里的3根IO口用来做地址译码 end; architecture behv1 of EPM570 is signal run_time_flag:STD_LOGIC; --中间信号标志,等于1时表示内部计时正在进行中 signal run_time_temp:STD_LOGIC_VECTOR(27 DOWNTO 0);--中间信号,当计时刚刚结束时,单片机读取此计数完毕的数据 signal luck_time:STD_LOGIC_VECTOR(27 DOWNTO 0); --中间信号,单片机读取数据的中间信号 signal set_time:STD_LOGIC_VECTOR(27 DOWNTO 0); --中间信号,设定的时间 signal run_time:STD_LOGIC_VECTOR(27 DOWNTO 0);--中间信号,实际正在运行的时间的脉冲计数 BEGIN process(bus03,hl,clk,rw,luck_time) --写入或者读取一个28位的总线数据,一次只能读写4位,28位要分7次才能完成 begin case hl is when "000"=> --地址(选中第0位至第3位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(3 DOWNTO 0)<=bus03; else --读 out03<=luck_time(3 DOWNTO 0); end if; end if; when "001"=> --地址(选中第4位至第7位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(7 DOWNTO 4)<=bus03; else --读 out03<=luck_time(7 DOWNTO 4); end if; end if; when "010"=> --地址(选中第8位至第11位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(11 DOWNTO 8)<=bus03; else --读 out03<=luck_time(11 DOWNTO 8); end if; end if; when "011"=> --地址(选中第12位至第15位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(15 DOWNTO 12)<=bus03; else --读 out03<=luck_time(15 DOWNTO 12); end if; end if; when "100"=> --地址(选中第16位至第19位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(19 DOWNTO 16)<=bus03; else --读 out03<=luck_time(19 DOWNTO 16); end if; end if; when "101"=> --地址(选中第20位至第23位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(23 DOWNTO 20)<=bus03; else --读 out03<=luck_time(23 DOWNTO 20); end if; end if; when "110"=> --地址(选中第24位至第27位的数据) if clk='0' then if rw='0' then --写 out03<="ZZZZ"; --相对于CPLD输入数据时,它的输出IO必须全部输出高阻态Z,否则2根IO有可能产生短路现象 set_time(27 DOWNTO 24)<=bus03; else --读 out03<=luck_time(27 DOWNTO 24); end if; end if; when others=> null; end case; end process; process(jingzhen_clk,start,set_time) --高速捕捉晶振的下降沿,同时计数,一旦发现等于设定计数值,马上停止,并且关闭LED输出 begin if start ='0' then --处于停止复位的状态 valve_dr<='0'; --关闭LED输出 run_time_flag<='0'; --中间信号,等于1时代表CPLD内部正在计数, run_time<="0000000000000000000000000000"; --脉冲计数器重新清零 elsif run_time =set_time then --当晶振下降沿的次数等于我们单片机设定的计数值的状态 valve_dr<='0'; --,马上关闭LED输出 run_time_temp<=run_time; --马上把最终结束时的计数器赋给中间变量,方便单片机读取 run_time_flag<='0'; --清除正在运行计时的标志 finish<='0'; --输出低电平告诉单片机计时已经结束了! elsif jingzhen_clk'event and jingzhen_clk='0' then --捕捉晶振每一个下降沿 valve_dr<='1'; --开启LED输出 finish<='1'; --输出高电平告诉单片机计时正在运行中,还没结束 run_time_flag<='1'; --1表示正在运行计时的标志 run_time<=run_time+1; --累加脉冲的下降沿次数 run_time_temp<=run_time; --把下降沿的次数及时赋给中间信号,方便单片机读取数据 end if; end process; process(mode,run_time,run_time_flag,run_time_temp) --单片机通过读取此中间信号来获取CPLD内部实际运行的计时 begin if mode'event and mode='0' then --一个下降沿来锁定瞬间高速变化的计时值,方便单片机读取 if run_time_flag='0' then --当CPLD已经结束计数时,就读取run_time_temp的计时 luck_time<=run_time_temp; else luck_time<=run_time;--当CPLD还没结束计数时,就读取run_time的计时 end if; end if; end process; end behv1; (8)下集预告: 从下一节开始,我准备花几章节的内容来讲常用的几种温湿度采集方式。下一节的标题是:利用热敏电阻来采集温度。 (未完待续,下节更精彩,不要走开哦) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
只有小组成员才能发言,加入小组>>
求解外围电路实现的是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 12:55 , Processed in 0.919035 second(s), Total 90, Slave 82 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号