數(shù)字時鐘在FPGA設(shè)計中較為常見,其主要包含時鐘、分鐘、秒鐘,以及必備的校時功能,當然還可以根據(jù)需要加入年、月,日,周,星期或者鬧鐘設(shè)置等元素。
在這里,我們給小伙伴分享如何通過FPGA實現(xiàn)顯示年、月、日、周、時、分、秒且能精準校時的數(shù)字時鐘。
一、設(shè)計任務(wù)
通過FPGA編程驅(qū)動實時時鐘芯片DS1340Z,實現(xiàn)時間寫入和讀出的功能,驅(qū)動旋轉(zhuǎn)編碼器獲取操作信息,根據(jù)編碼器操作信息控制數(shù)字時鐘的邏輯(包括時間調(diào)節(jié),顯示控制),最后驅(qū)動數(shù)碼管顯示數(shù)字時鐘信息。
二、設(shè)計準備
硬件:小腳丫FPGA核心板、時鐘芯片DS1340Z、旋轉(zhuǎn)編碼器、數(shù)碼管
軟件:Quartus Prime/Lattice Diamond
三、設(shè)計結(jié)構(gòu)
四、設(shè)計原理
1.實時時鐘芯片DS1340Z模塊
實時時鐘芯片DS1340Z模塊電路圖如下(上拉電阻未顯示),模塊電路中有電池座,電池電壓范圍為1.3V~5.5V,當安裝電池后底板掉電不影響實時時鐘芯片的運行,重新上電后讀取實時時鐘數(shù)據(jù)。另外,芯片內(nèi)部集成了起振電阻電容,只需外置時鐘晶振32.768KHz直接連接即可。
2.旋轉(zhuǎn)編碼器模塊
機械增量式旋轉(zhuǎn)編碼器,旋轉(zhuǎn)過程中提供周期性輸出,不能定位絕對位置,只能用于感知運動方向和增量,其電路圖如下,本設(shè)計所使用的旋轉(zhuǎn)編碼器為EC11系列的,支持按動開關(guān),共有5個管腳:
1、2管腳支持按動開關(guān),如同獨立按鍵連接方式。
3、4、5管腳支持旋轉(zhuǎn)編碼,4腳為公共端,3、5管腳分別為旋轉(zhuǎn)編碼器的A、B相輸出,如上圖所示,4腳接地,3、5管腳則需接上拉電阻,同時為了降低輸出脈沖信號的抖動干擾,增加了電容到地做硬件去抖。
五、設(shè)計驅(qū)動過程
1.DS1340Z驅(qū)動設(shè)計
DS1340Z支持I2C通信400KHz快速模式同時兼容100KHz的標準模式,還有兩種模式下時序中的各種時間參數(shù),本設(shè)計中,通過分頻得到400KHz的時鐘。
I2C時序基本單元(啟動、停止、發(fā)送、接收、發(fā)應(yīng)答、讀應(yīng)答)協(xié)議里統(tǒng)一的,所以所以基本單元狀態(tài)的設(shè)計也是不需要調(diào)整的。
啟動時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)同智能接近系統(tǒng)設(shè)計實驗。
發(fā)送單元和讀應(yīng)答單元合并,時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)同智能接近系統(tǒng)設(shè)計實驗。
接收單元和寫應(yīng)答單元合并,時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)同智能接近系統(tǒng)設(shè)計實驗。
停止時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)同智能接近系統(tǒng)設(shè)計實驗。
DS1340Z芯片有很多寄存器,用于存儲實時時鐘的時間信息,同時芯片支持連續(xù)讀、寫寄存器操作(寄存器地址自加1),其設(shè)計程序?qū)崿F(xiàn)如下:
連續(xù)寫寄存器:
MAIN:begin if(cnt_main>=6'd11)//對MAIN中的子狀態(tài)執(zhí)行控制cnt_main cnt_main<=6'd0;?? // elsecnt_main<=cnt_main+1'b1;?? case(cnt_main) 6'd0:beginstate<=START;end?? //I2C通信時序中的START 6'd1:begindata_wr<=8'hd0;state<=WRITE;end???? //寫地址為8'hd0 6'd2:begindata_wr<=8'h00;state<=WRITE;end???? //8'h00,起始寄存器 6'd3:begindata_wr<=adj_sec;state<=WRITE;end?? //00寄存器地址,寫秒 6'd4:begindata_wr<=adj_min;state<=WRITE;end?? //01寄存器地址,寫分 6'd5:begindata_wr<=adj_hour;state<=WRITE;end? //02寄存器地址,寫時 6'd6:begindata_wr<=adj_week;state<=WRITE;end? //03寄存器地址,寫周 6'd7:begindata_wr<=adj_day;state<=WRITE;end?? //04寄存器地址,寫日 6'd8:begindata_wr<=adj_mon;state<=WRITE;end?? //05寄存器地址,寫月 6'd9:begindata_wr<=adj_year;state<=WRITE;end? //06寄存器地址,寫年 6'd10:begindata_wr<=8'h40;state<=WRITE;end? ?//07寄存器地址,8'h40 6'd11:beginstate<=STOP;end??? //I2C通信時序中的STOP default:state<=IDLE;//如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end |
連續(xù)讀寄存器:
MAIN:begin if(cnt_main>=6'd32)//對MAIN中的子狀態(tài)執(zhí)行控制cnt_main cnt_main<=6'd12;???????? //否則只執(zhí)行時間讀取操作 elsecnt_main<=cnt_main+1'b1;?? case(cnt_main) 6'd12:beginstate<=START;end?? //I2C通信時序中的START 6'd13:begindata_wr<=8'hd0;state<=WRITE;end//寫地址為8'hd0 6'd14:begindata_wr<=8'h00;state<=WRITE;end//8'h00,寄存器初始地址 6'd15:beginstate<=START;end?? //I2C通信時序中的START 6'd16:begindata_wr<=8'hd1;state<=WRITE;end//讀地址為8'hd1 6'd17:beginack<=ACK;state<=READ;end??? //讀秒 6'd18:beginrtc_sec<=rtc_data_r;end 6'd19:beginack<=ACK;state<=READ;end??? //讀分 6'd20:beginrtc_min<=rtc_data_r;end 6'd21:beginack<=ACK;state<=READ;end??? //讀時 6'd22:beginrtc_hour<=rtc_data_r;end 6'd23:beginack<=ACK;state<=READ;end??? //讀周 6'd24:beginrtc_week<=rtc_data_r;end 6'd25:beginack<=ACK;state<=READ;end??? //讀日 6'd26:beginrtc_day<=rtc_data_r;end 6'd27:beginack<=ACK;state<=READ;end??? //讀月 6'd28:beginrtc_mon<=rtc_data_r;end 6'd29:beginack<=ACK;state<=READ;end??? //讀年 6'd30:beginrtc_year<=rtc_data_r;end 6'd31:beginack<=NACK;state<=READ;end?? //控制 6'd32:beginstate<=STOP;end??? //I2C通信時序中的STOP,讀取完成標志 default:state<=IDLE;//如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end |
上面兩段程序就是對于DS1340Z芯片的兩種操作,調(diào)時間和讀時間,對于時鐘來說因為有電池供電,實時時鐘一直都處于工作狀態(tài),當給FPGA上電時只需要讀時間即可,只有遇到時間不對的時候才需要調(diào)時間,所以DS1340Z驅(qū)動模塊平時都在循環(huán)讀取時間,所以如果將調(diào)時間和讀時間的時序操作融合到同一個狀態(tài)下時,對于cnt_main要加以控制,cnt_main初值為12,且運行軌跡在12~32之間,控制程序調(diào)整如下:
if(cnt_main>=6'd32)//對MAIN中的子狀態(tài)執(zhí)行控制cnt_main if(set_flag)cnt_main<=6'd0;?? //當set_flag被置位時才會執(zhí)行時間寫入操作 elsecnt_main<=6'd12;???????? //否則只執(zhí)行時間讀取操作 elsecnt_main<=cnt_main+1'b1;?? |
上面set_flag為時間調(diào)整標志位,只有按動編碼器在調(diào)時間模式時需要用到寫時間數(shù)據(jù)的操作流程,可以根據(jù)按鍵脈沖置位set_flag并自鎖,每次完成寫入操作后再將set_flag復(fù)位。程序?qū)崿F(xiàn)如下:
reg set_flag; always@(posedgeclkornegedgerst_n)begin if(!rst_n)set_flag<=1'b0; elseif(cnt_main==5'd11)set_flag<=1'b0;? //完成寫入時間操作復(fù)位set_flag elseif(key_set)set_flag<=1'b1;????????? //按鍵脈沖控制set_flag置位 elseset_flag<=set_flag; end |
模塊端口如下:
moduleDS1340Z_driver ( inputclk,rst_n,//系統(tǒng)時鐘和復(fù)位 inputkey_set, //按動脈沖輸入 input[7:0]adj_hour,adj_min,adj_sec,//時分秒調(diào)整輸入 input[7:0]adj_year,adj_mon,adj_day,adj_week,//年份調(diào)整輸入 outputi2c_scl,//I2C總線SCL inout i2c_sda,//I2C總線SDA output[7:0]rtc_hour,rtc_min,rtc_sec,//實時時鐘輸出 output[7:0]rtc_year,rtc_mon,rtc_day,rtc_week //實時年份輸出 ); |
到這里就完成了萬年歷中DS1340Z模塊的驅(qū)動設(shè)計,宏觀上講,該模塊的功能可以這樣描述:
正常模式下循環(huán)讀取時間信息,并把時間數(shù)據(jù)輸出
由旋轉(zhuǎn)編碼器按動脈沖信號key_set觸發(fā)進行一次寫操作,用于調(diào)節(jié)時間
每次寫操作調(diào)節(jié)時間的時間數(shù)據(jù)由其他模塊提供
2.旋轉(zhuǎn)編碼器驅(qū)動設(shè)計
機械增量式旋轉(zhuǎn)編碼器的原理示意圖,中間圓形齒輪連接到旋轉(zhuǎn)編碼器的公共端4管腳,A、B兩個觸點連接到旋轉(zhuǎn)編碼器的A、B相輸出端3、5管腳,當進行旋轉(zhuǎn)操作時,A、B觸點會先后接觸和錯開圓形齒輪,從而導(dǎo)致A、B相輸出信號產(chǎn)生相位不同的脈沖信號:
順時針旋轉(zhuǎn)時,A觸點超前于B觸點接觸和錯開圓形齒輪,A信號脈沖相位超前
逆時針旋轉(zhuǎn)時,B觸點超前于A觸點接觸和錯開圓形齒輪,B信號脈沖相位超前
對A信號采樣程序?qū)崿F(xiàn)如下(對B信號一樣):
regkey_a_r,key_a_r1,key_a_r2; //消除亞穩(wěn)態(tài) always@(posedgeclk_500us)begin key_a_r<=? ?key_a; key_a_r1<=? ?key_a_r; key_a_r2<=? ?key_a_r1; end |
然后簡單去抖處理程序?qū)崿F(xiàn)如下(對B信號一樣):
regA_state; //簡單去抖動處理 always@(key_a_r1orkey_a_r2)begin case({key_a_r1,key_a_r2}) 2'b11: A_state<=1'b1; 2'b00: A_state<=1'b0; default:A_state<=A_state; endcase end |
檢測A信號的邊沿程序?qū)崿F(xiàn)如下:
regA_state_r,A_state_r1; //對A_state信號進行邊沿檢測 always@(posedgeclk)begin A_state_r<=A_state; A_state_r1<=A_state_r; end wire A_pos=(!A_state_r1)&&A_state_r; wire A_neg=A_state_r1&&(!A_state_r); |
最后根據(jù)A信號邊沿與B信號的狀態(tài)組合判定旋轉(zhuǎn)的信息。
旋轉(zhuǎn)脈沖輸出程序?qū)崿F(xiàn)如下:
//當A的上升沿伴隨B的高電平或當A的下降沿伴隨B的低電平為向左旋轉(zhuǎn) always@(posedgeclkornegedgerst_n)begin if(!rst_n)L_pulse<=1'b0; elseif((A_pos&&B_state)||(A_neg&&(!B_state)))L_pulse<=1'b1; elseL_pulse<=1'b0; end //當A的上升沿伴隨B的低電平或當A的下降沿伴隨B的高電平為向右旋轉(zhuǎn) always@(posedgeclkornegedgerst_n)begin if(!rst_n)R_pulse<=1'b0; elseif((A_pos&&(!B_state))||(A_neg&&B_state))R_pulse<=1'b1; elseR_pulse<=1'b0; end |
通過上面程序最終實現(xiàn)了左旋右旋的脈沖輸出,脈沖的脈寬等于系統(tǒng)時鐘的周期。
3.模式控制:
本設(shè)計要實現(xiàn)8個模式(常態(tài)、調(diào)年、調(diào)月、調(diào)日、調(diào)周、調(diào)時、調(diào)分、調(diào)秒),對8個狀態(tài)編碼,常態(tài)—0、調(diào)秒—1、調(diào)分—2、調(diào)時—3、調(diào)周—4、調(diào)日—5、調(diào)月—6、調(diào)年—7,通過按動旋轉(zhuǎn)編碼器切換,按照常識調(diào)時間從大到小調(diào)節(jié),先調(diào)節(jié)年份最后調(diào)秒鐘,所以我們這8個狀態(tài)的狀態(tài)機跳轉(zhuǎn)順序是固定的(0→7→6→5→4→3→2→1→0),依次循環(huán)跳轉(zhuǎn),程序?qū)崿F(xiàn)如下:
//時鐘運行狀態(tài)控制 always@(posedgeclkornegedgerst_n) if(!rst_n)state<=3'd0; elseif(O_pulse)//按鍵脈沖控制時鐘運行狀態(tài)的跳變, if(state)state<=state-3'd1; elsestate<=3'd7; elsestate<=state; |
4.調(diào)時控制:
調(diào)時控制在不同的調(diào)節(jié)模式對不同時間進行調(diào)整,我們分別以常態(tài)模式和調(diào)秒模式為例進行分析。時間調(diào)節(jié)要以當時的時間為基礎(chǔ),常態(tài)模式下不需要調(diào)整任何時間,但是可以將實時時鐘讀出的時間數(shù)據(jù)賦給調(diào)節(jié)變量,這樣等跳轉(zhuǎn)到調(diào)節(jié)模式時對調(diào)節(jié)變量的控制就是以當時的時間為基礎(chǔ)了,程序?qū)崿F(xiàn)如下:
3'd0: //正常模式 begin if(O_pulse)begin //在常態(tài)下按動編碼器將當前實時時間賦值給調(diào)節(jié)寄存器 adj_sec<=rtc_sec; adj_min<=rtc_min; adj_hour<=rtc_hour; adj_week<=rtc_week; adj_day<=rtc_day; adj_mon<=rtc_mon; adj_year<=rtc_year; end end |
調(diào)秒模式與其他調(diào)節(jié)模式操作一樣,不同的是調(diào)節(jié)的規(guī)則不同,例如秒和分的調(diào)節(jié)范圍為0~59,小時調(diào)節(jié)范圍0~11或0~23,日期調(diào)節(jié)范圍需要考慮年和月的值(1、3、5、7、8、10、12月范圍1~31,4、6、9、11月范圍1~30,2月平年范圍1~28,2月閏年范圍1~29),周調(diào)節(jié)范圍1~7,月調(diào)節(jié)范圍1~12,年調(diào)節(jié)范圍0~99。對秒鐘數(shù)據(jù)進行調(diào)節(jié),程序?qū)崿F(xiàn)如下:
3'd1: //調(diào)秒模式 begin if(L_pulse)begin//逆時針轉(zhuǎn) if(adj_sec[3:0])adj_sec<=adj_sec-1'h1; elseif(adj_sec[7:4])adj_sec<={adj_sec[7:4]-1'h1,4'h9}; elseadj_sec<=8'h59; endelseif(R_pulse)begin//順時針轉(zhuǎn) if(adj_sec[3:0]!=4'h9)adj_sec<=adj_sec+1'h1; elseif(adj_sec[7:4]!=4'h5)adj_sec<={adj_sec[7:4]+1'h1,4'h0}; elseadj_sec<=8'h00; endelseadj_sec<=adj_sec; end |
5.顯示控制:
首先使用8位數(shù)碼管分兩頁顯示時鐘數(shù)據(jù),第一頁顯示年月日周,第二頁顯示時分秒。任何一項時間選項都由兩位數(shù)碼管顯示,每頁最多顯示4個時間選項,我們可以使用4位的變量disp_en[3:0]控制4個時間選項的點亮或熄滅,disp_en[3]控制最左側(cè)兩個數(shù)碼管,disp_en[0]控制最右側(cè)兩個數(shù)碼管,我們分別以常態(tài)模式和調(diào)秒模式為例進行顯示使能控制的分析。常態(tài)模式下,轉(zhuǎn)動編碼器控制顯示頁碼,兩個頁碼對應(yīng)的顯示控制,程序?qū)崿F(xiàn)如下:
3'd0: //正常模式 if(L_pulse)disp_en<=4'b1111;?? ?//逆時針轉(zhuǎn)顯示第一頁,數(shù)碼管全亮 elseif(R_pulse)disp_en<=4'b0111;//順時針轉(zhuǎn)顯示第二頁,時分秒亮 elsedisp_en<=disp_en; |
調(diào)秒模式下,小時和分鐘數(shù)碼管點亮,秒鐘閃爍顯示,轉(zhuǎn)動編碼器時秒鐘強制顯示,最后按動旋轉(zhuǎn)編碼器切到常態(tài)模式時,時分秒數(shù)碼管都回復(fù)顯示,程序?qū)崿F(xiàn)如下:
3'd1:begin //調(diào)秒模式 disp_en[3:1]<=3'b011;//時和分顯示 if(L_pulse|R_pulse)disp_en[0]<=1'b1;//轉(zhuǎn)動時強制顯示 elseif(sec_pulse)disp_en[0]<=~disp_en[0];//秒鐘閃爍顯示 elseif(O_pulse)disp_en<=4'b0111;//返回常態(tài)時顯示時分秒 elsedisp_en[0]<=disp_en[0]; end |
數(shù)碼管與時間選項是對應(yīng)關(guān)系,每個選項對應(yīng)兩位數(shù)碼管,程序?qū)崿F(xiàn)如下:
wire[7:0]data_en=//數(shù)碼管位選控制 {{2{disp_en[3]}},{2{disp_en[2]}},{2{disp_en[1]}},{2{disp_en[0]}}}; wire[7:0]dot_en=//數(shù)碼管小數(shù)點顯示控制 {1'b0,disp_en[3],1'b0,disp_en[2],1'b0,disp_en[1],1'b0,disp_en[0]}; |
時鐘顯示分兩頁實現(xiàn),我們以最右側(cè)兩個數(shù)碼管顯示內(nèi)容為例,這兩位數(shù)碼管在第一頁中顯示周數(shù)據(jù),在第二頁中顯示秒數(shù)據(jù),那么我們怎么控制顯示內(nèi)容呢?分析,萬年歷8中模式,
常態(tài)模式下,顯示讀取的實時時鐘數(shù)據(jù),具體顯示周還是秒再次細化
? disp_en等于4'b1111的時候,對應(yīng)第一頁,顯示周數(shù)據(jù)
? disp_en等于4'b0111的時候,對應(yīng)第二頁,顯示秒數(shù)據(jù)
常態(tài)模式下,根據(jù)disp_en選擇顯示周數(shù)據(jù)還是秒數(shù)據(jù),程序?qū)崿F(xiàn)如下:
//常態(tài)下數(shù)碼管顯示數(shù)據(jù) wire[7:0]data_rtc0=disp_en[3]?rtc_week:rtc_sec; |
調(diào)節(jié)模式下,顯示寫入的調(diào)節(jié)時鐘數(shù)據(jù),具體顯示周還是秒再次細化
? 調(diào)年、調(diào)月、調(diào)日、調(diào)周 狀態(tài)下(state>=3),對應(yīng)第一頁,顯示周數(shù)據(jù)
? 調(diào)時、調(diào)分、調(diào)秒 狀態(tài)下(state<3),對應(yīng)第二頁,顯示秒數(shù)據(jù)
調(diào)節(jié)模式下,根據(jù)state選擇顯示周數(shù)據(jù)還是秒數(shù)據(jù),程序?qū)崿F(xiàn)如下:
//調(diào)節(jié)狀態(tài)下數(shù)碼管顯示數(shù)據(jù) wire[7:0]data_adj0=state[2]?adj_week:adj_sec; |
最后根據(jù)常態(tài)模式還是調(diào)節(jié)模式控制數(shù)碼管顯示實時時鐘數(shù)據(jù)還是調(diào)節(jié)時鐘數(shù)據(jù)
根據(jù)state選擇顯示實時時鐘數(shù)據(jù)還是調(diào)節(jié)時鐘數(shù)據(jù),程序?qū)崿F(xiàn)如下:
//根據(jù)狀態(tài)選擇顯示常態(tài)數(shù)據(jù)還是調(diào)節(jié)狀態(tài)數(shù)據(jù) assign{data_7,data_8}=state?data_adj0:data_rtc3; |
到這里為止,就完成了數(shù)字時鐘的設(shè)計,當然在本設(shè)計中,可以通過簡化只留下時分秒及調(diào)時功能,實現(xiàn)24小時計時,省去對于頁的判斷。同時對于旋轉(zhuǎn)編碼器有難度的,可以改為按鍵調(diào)時。有興趣的小伙伴可以動手試試哦~
-
FPGA
+關(guān)注
關(guān)注
1643文章
21956瀏覽量
614013 -
編碼器
+關(guān)注
關(guān)注
45文章
3773瀏覽量
137118 -
數(shù)字時鐘
+關(guān)注
關(guān)注
2文章
153瀏覽量
20744
原文標題:FPGA畢設(shè)系列 | 1.數(shù)字時鐘
文章出處:【微信號:xiaojiaoyafpga,微信公眾號:電子森林】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
[原創(chuàng)]基于FPGA萬年歷12864顯示—年-生肖-月-日-時-分-秒-星.....
12時制數(shù)字顯示電子鐘,MCU為LPC2103/2106
89c51時鐘萬年歷
如何去實現(xiàn)一種基于51單片機的電子時鐘萬年歷設(shè)計
基于FPGA設(shè)計實現(xiàn)一個多功能數(shù)字鐘相關(guān)資料分享
基于Multisim數(shù)字時鐘的設(shè)計與仿真

鋯石FPGA A4_Nano開發(fā)板視頻:數(shù)字時鐘的項目工程講解

設(shè)計數(shù)字時鐘的設(shè)計報告

評論