數(shù)字萬年歷設(shè)計
實驗任務(wù)
實驗?zāi)康?/h3>前面的章節(jié)中我們學(xué)習(xí)了掃描式數(shù)碼管模塊和旋轉(zhuǎn)編碼器模塊的工作原理及驅(qū)動方法,也對I2C總線協(xié)議及相關(guān)知識,本實驗主要對I2C總線驅(qū)動方法加以練習(xí),同時完成數(shù)字萬年歷時間調(diào)節(jié)和顯示控制的邏輯,最終完成數(shù)字萬年歷總體設(shè)計。
本文引用地址:http://m.butianyuan.cn/article/202312/453964.htm設(shè)計框圖
根據(jù)前面的實驗解析我們可以得知,該設(shè)計可以拆分成幾個功能模塊實現(xiàn),
實驗原理
DS1340Z模塊介紹
從DS1340Z芯片手冊可以得到如下信息,DS1340Z芯片典型電路連接如下:
DS1340Z芯片管腳功能描述如下(SO-8封裝):
DS1340Z芯片內(nèi)部結(jié)構(gòu)圖如下:
DS1340Z模塊連接
STEP BaseBoard V3.0底板上的實時時鐘芯片DS1340Z模塊電路圖如下(上拉電阻未顯示):
我們的實時時鐘芯片為DS1340Z-33,模塊電路中有電池座,電池電壓范圍為1.3V~5.5V,當(dāng)安裝電池后底板掉電不影響實時時鐘芯片的運(yùn)行,重新上電后讀取實時時鐘數(shù)據(jù)。
實時時鐘芯片DS1340Z需要外置32.768KHz的晶體,芯片內(nèi)部集成起振電阻電容等電路,晶體直接連接即可。
DS1340Z驅(qū)動設(shè)計
前面實驗中我們已經(jīng)講述學(xué)習(xí)過I2C總線驅(qū)動的設(shè)計,本實驗可以上原來的基礎(chǔ)上調(diào)整,首先來了解DS1340Z時序中的參數(shù)要點。
通過DS1340Z時序參數(shù)了解,DS1340Z支持I2C通信400KHz快速模式同時兼容100KHz的標(biāo)準(zhǔn)模式,還有兩種模式下時序中的各種時間參數(shù),所以通信速度不需要調(diào)整。
I2C時序基本單元(啟動、停止、發(fā)送、接收、發(fā)應(yīng)答、讀應(yīng)答)協(xié)議里統(tǒng)一的,所以所以基本單元狀態(tài)的設(shè)計也是不需要調(diào)整的。
DS1340Z芯片有很多寄存器,用于存儲實時時鐘的時間信息,例如地址為00H的寄存器中,bit7為晶體使能控制位,低有效,默認(rèn)使能,bit6~bit0為秒鐘數(shù)據(jù),且是BCD碼的格式(bit6~bit4代表秒鐘的十位,bit3~bit0代表秒鐘的個位),當(dāng)需要調(diào)整秒鐘時間時,對00H寄存器寫操作,當(dāng)讀取秒鐘時間時, 對00H寄存器讀操作。其他寄存器也是一樣,詳細(xì)請參考每個寄存器的功能說明。
本實驗涉及DS1340Z的寫寄存器和讀寄存器操作,查看手冊給出的操作時序流程。
芯片支持連續(xù)寫寄存器操作(寄存器地址自加1),時序流程如下:
根據(jù)連續(xù)寫寄存器時序流程,其設(shè)計程序?qū)崿F(xiàn)如下:
MAIN:begin
if(cnt_main >= 6'd11) //對MAIN中的子狀態(tài)執(zhí)行控制cnt_main
cnt_main <= 6'd0; //
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
6'd0: begin state <= START; end //I2C通信時序中的START
6'd1: begin data_wr <= 8'hd0; state <= WRITE; end //寫地址為8'hd0
6'd2: begin data_wr <= 8'h00; state <= WRITE; end //8'h00,起始寄存器
6'd3: begin data_wr <= adj_sec; state <= WRITE; end //00寄存器地址,寫秒
6'd4: begin data_wr <= adj_min; state <= WRITE; end //01寄存器地址,寫分
6'd5: begin data_wr <= adj_hour; state <= WRITE; end //02寄存器地址,寫時
6'd6: begin data_wr <= adj_week; state <= WRITE; end //03寄存器地址,寫周
6'd7: begin data_wr <= adj_day; state <= WRITE; end //04寄存器地址,寫日
6'd8: begin data_wr <= adj_mon; state <= WRITE; end //05寄存器地址,寫月
6'd9: begin data_wr <= adj_year; state <= WRITE; end //06寄存器地址,寫年
6'd10: begin data_wr <= 8'h40; state <= WRITE; end //07寄存器地址,8'h40
6'd11: begin state <= STOP; end //I2C通信時序中的STOP
default: state <= IDLE; //如果程序失控,進(jìn)入IDLE自復(fù)位狀態(tài)
endcase
end
芯片支持連續(xù)讀寄存器操作(寄存器地址自加1),時序流程如下:
根據(jù)連續(xù)寫寄存器時序流程,其設(shè)計程序?qū)崿F(xiàn)如下:
MAIN:begin
if(cnt_main >= 6'd32) //對MAIN中的子狀態(tài)執(zhí)行控制cnt_main
cnt_main <= 6'd12; //否則只執(zhí)行時間讀取操作
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
6'd12: begin state <= START; end //I2C通信時序中的START
6'd13: begin data_wr <= 8'hd0; state <= WRITE; end //寫地址為8'hd0
6'd14: begin data_wr <= 8'h00; state <= WRITE; end //8'h00,寄存器初始地址
6'd15: begin state <= START; end //I2C通信時序中的START
6'd16: begin data_wr <= 8'hd1; state <= WRITE; end //讀地址為8'hd1
6'd17: begin ack <= ACK; state <= READ; end //讀秒
6'd18: begin rtc_sec <= rtc_data_r; end
6'd19: begin ack <= ACK; state <= READ; end //讀分
6'd20: begin rtc_min <= rtc_data_r; end
6'd21: begin ack <= ACK; state <= READ; end //讀時
6'd22: begin rtc_hour <= rtc_data_r; end
6'd23: begin ack <= ACK; state <= READ; end //讀周
6'd24: begin rtc_week <= rtc_data_r; end
6'd25: begin ack <= ACK; state <= READ; end //讀日
6'd26: begin rtc_day <= rtc_data_r; end
6'd27: begin ack <= ACK; state <= READ; end //讀月
6'd28: begin rtc_mon <= rtc_data_r; end
6'd29: begin ack <= ACK; state <= READ; end //讀年
6'd30: begin rtc_year <= rtc_data_r; end
6'd31: begin ack <= NACK; state <= READ; end //控制
6'd32: begin state <= STOP; end //I2C通信時序中的STOP,讀取完成標(biāo)志
default: state <= IDLE; //如果程序失控,進(jìn)入IDLE自復(fù)位狀態(tài)
endcase
end
上面兩段程序就是對于DS1340Z芯片的兩種操作,調(diào)時間和讀時間,對于萬年歷來說因為有電池供電,實時時鐘一直都處于工作狀態(tài),當(dāng)給FPGA上電時只需要讀時間即可,只有遇到時間不對的時候才需要調(diào)時間,所以DS1340Z驅(qū)動模塊平時都在循環(huán)讀取時間,所以如果將調(diào)時間和讀時間的時序操作融合到同一個狀態(tài)下時,對于cntmain要加以控制,cntmain初值為12,且運(yùn)行軌跡在12~32之間,控制程序調(diào)整如下:
if(cnt_main >= 6'd32) //對MAIN中的子狀態(tài)執(zhí)行控制cnt_main
if(set_flag)cnt_main <= 6'd0; //當(dāng)set_flag被置位時才會執(zhí)行時間寫入操作
else cnt_main <= 6'd12; //否則只執(zhí)行時間讀取操作
else cnt_main <= cnt_main + 1'b1;
上面setflag為時間調(diào)整標(biāo)志位,只有按動編碼器在調(diào)時間模式時需要用到寫時間數(shù)據(jù)的操作流程,可以根據(jù)按鍵脈沖置位setflag并自鎖,每次完成寫入操作后再將set_flag復(fù)位。程序?qū)崿F(xiàn)如下:
reg set_flag;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) set_flag <= 1'b0;
else if(cnt_main==5'd11) set_flag <= 1'b0; //完成寫入時間操作復(fù)位set_flag
else if(key_set) set_flag <= 1'b1; //按鍵脈沖控制set_flag置位
else set_flag <= set_flag;
end
模塊端口如下:
module DS1340Z_driver
(
input clk, rst_n, //系統(tǒng)時鐘和復(fù)位
input key_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)整輸入
output i2c_scl, //I2C總線SCLinout 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è)計,宏觀上講,該模塊的功能可以這樣描述:
萬年歷控制模塊實現(xiàn)
控制模塊包含多個功能的設(shè)計:模式控制、調(diào)時控制、顯示控制,可以細(xì)化成多個模塊實現(xiàn),本實驗例程中就寫在了一個模塊下,我們會針對這三個功能分別講解其實現(xiàn)方法及原理。
模式控制
項目要求設(shè)計成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)機(jī)跳轉(zhuǎn)順序是固定的(0→7→6→5→4→3→2→1→0),依次循環(huán)跳轉(zhuǎn),程序?qū)崿F(xiàn)如下:
//時鐘運(yùn)行狀態(tài)控制
always@(posedge clk or negedge rst_n )
if(!rst_n) state <= 3'd0;
else if(O_pulse) //按鍵脈沖控制時鐘運(yùn)行狀態(tài)的跳變,
if(state) state <= state - 3'd1;
else state <= 3'd7;
else state <= state;
調(diào)時控制
調(diào)時控制在不同的調(diào)節(jié)模式對不同時間進(jìn)行調(diào)整,我們分別以常態(tài)模式和調(diào)秒模式為例進(jìn)行分析。
萬年歷時間調(diào)節(jié)要以當(dāng)時的時間為基礎(chǔ),常態(tài)模式下不需要調(diào)整任何時間,但是可以將實時時鐘讀出的時間數(shù)據(jù)賦給調(diào)節(jié)變量,這樣等跳轉(zhuǎn)到調(diào)節(jié)模式時對調(diào)節(jié)變量的控制就是以當(dāng)時的時間為基礎(chǔ)了,程序?qū)崿F(xiàn)如下:
3'd0: //正常模式
begin
if(O_pulse)begin //在常態(tài)下按動編碼器將當(dāng)前實時時間賦值給調(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ù)進(jìn)行調(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;
else if(adj_sec[7:4]) adj_sec <= {adj_sec[7:4]-1'h1,4'h9};
else adj_sec <= 8'h59;
end else if(R_pulse) begin //順時針轉(zhuǎn)
if(adj_sec[3:0]!=4'h9) adj_sec <= adj_sec + 1'h1;
else if(adj_sec[7:4]!=4'h5) adj_sec <= {adj_sec[7:4]+1'h1,4'h0};
else adj_sec <= 8'h00;
end else adj_sec <= adj_sec;
end
顯示控制
首先來看看一下數(shù)碼管要顯示的效果,8位數(shù)碼管分兩頁顯示萬年歷數(shù)據(jù),第一頁顯示年月日周,第二頁顯示時分秒。
我們看到任何一項時間選項都由兩位數(shù)碼管顯示,每頁最多顯示4個時間選項,我們可以使用4位的變量dispen[3:0]控制4個時間選項的點亮或熄滅,dispen[3]控制最左側(cè)兩個數(shù)碼管,disp_en[0]控制最右側(cè)兩個數(shù)碼管,我們分別以常態(tài)模式和調(diào)秒模式為例進(jìn)行顯示使能控制的分析。
常態(tài)模式下,轉(zhuǎn)動編碼器控制顯示頁碼,兩個頁碼對應(yīng)的顯示控制,程序?qū)崿F(xiàn)如下:
3'd0: //正常模式
if(L_pulse) disp_en <= 4'b1111; //逆時針轉(zhuǎn)顯示第一頁,數(shù)碼管全亮
else if(R_pulse) disp_en <= 4'b0111; //順時針轉(zhuǎn)顯示第二頁,時分秒亮
else disp_en <= disp_en;
調(diào)秒模式下,小時和分鐘數(shù)碼管點亮,秒鐘閃爍顯示,轉(zhuǎn)動編碼器時秒鐘強(qiáng)制顯示,最后按動旋轉(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)動時強(qiáng)制顯示
else if(sec_pulse) disp_en[0] <= ~disp_en[0]; //秒鐘閃爍顯示
else if(O_pulse) disp_en <= 4'b0111; //返回常態(tài)時顯示時分秒
else disp_en[0] <= disp_en[0];
end
系統(tǒng)總體實現(xiàn)
前面分析了顯示控制,主要對時間選項的點亮還是熄滅做控制,對應(yīng)到數(shù)碼管上就轉(zhuǎn)化成數(shù)碼管位的點亮和熄滅控制。另外還包含顯示數(shù)據(jù)的控制,而這部分設(shè)計我們放到頂層模塊中實現(xiàn)了,我們來分析一下。
數(shù)碼管點亮控制
數(shù)碼管與時間選項是對應(yīng)關(guān)系,每個選項對應(yīng)兩位數(shù)碼管,程序?qū)崿F(xiàn)如下:
wire [7:0] data_en = {{2{disp_en[3]}},{2{disp_en[2]}},{2{disp_en[1]}},{2{disp_en[0]}}}; //數(shù)碼管位選控制
wire [7:0] dot_en = {1'b0,disp_en[3],1'b0,disp_en[2],1'b0,disp_en[1],1'b0,disp_en[0]}; //數(shù)碼管小數(shù)點顯示控制
數(shù)碼管內(nèi)容控制
萬年歷的顯示分兩頁實現(xiàn),我們以最右側(cè)兩個數(shù)碼管顯示內(nèi)容為例,這兩位數(shù)碼管在第一頁中顯示周數(shù)據(jù),在第二頁中顯示秒數(shù)據(jù),那么我們怎么控制顯示內(nèi)容呢?分析,萬年歷8中模式,
1.常態(tài)模式下,顯示讀取的實時時鐘數(shù)據(jù),具體顯示周還是秒再次細(xì)化
常態(tài)模式下,根據(jù)disp_en選擇顯示周數(shù)據(jù)還是秒數(shù)據(jù),程序?qū)崿F(xiàn)如下:
wire [7:0] data_rtc0 = disp_en[3]? rtc_week:rtc_sec; //常態(tài)下數(shù)碼管顯示數(shù)據(jù)
2.調(diào)節(jié)模式下,顯示寫入的調(diào)節(jié)時鐘數(shù)據(jù),具體顯示周還是秒再次細(xì)化
調(diào)節(jié)模式下,根據(jù)state選擇顯示周數(shù)據(jù)還是秒數(shù)據(jù),程序?qū)崿F(xiàn)如下:
wire [7:0] data_adj0 = state[2]? adj_week:adj_sec; //調(diào)節(jié)狀態(tài)下數(shù)碼管顯示數(shù)據(jù)
3.最后根據(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)如下:
assign {data_7,data_8} = state? data_adj0:data_rtc3; //根據(jù)狀態(tài)選擇顯示常態(tài)數(shù)據(jù)還是調(diào)節(jié)狀態(tài)數(shù)據(jù)
綜合后的設(shè)計框圖如下:
實驗步驟
實驗現(xiàn)象
將程序下載到FPGA中,按照設(shè)計要求的功能操作調(diào)節(jié)萬年歷的時間,觀察數(shù)碼管萬年歷顯示,如圖時間為18年6月27日,周三,19點15分14秒。
前面的章節(jié)中我們學(xué)習(xí)了掃描式數(shù)碼管模塊和旋轉(zhuǎn)編碼器模塊的工作原理及驅(qū)動方法,也對I2C總線協(xié)議及相關(guān)知識,本實驗主要對I2C總線驅(qū)動方法加以練習(xí),同時完成數(shù)字萬年歷時間調(diào)節(jié)和顯示控制的邏輯,最終完成數(shù)字萬年歷總體設(shè)計。
本文引用地址:http://m.butianyuan.cn/article/202312/453964.htm根據(jù)前面的實驗解析我們可以得知,該設(shè)計可以拆分成幾個功能模塊實現(xiàn),
從DS1340Z芯片手冊可以得到如下信息,DS1340Z芯片典型電路連接如下:
DS1340Z芯片管腳功能描述如下(SO-8封裝):
DS1340Z芯片內(nèi)部結(jié)構(gòu)圖如下:
STEP BaseBoard V3.0底板上的實時時鐘芯片DS1340Z模塊電路圖如下(上拉電阻未顯示):
我們的實時時鐘芯片為DS1340Z-33,模塊電路中有電池座,電池電壓范圍為1.3V~5.5V,當(dāng)安裝電池后底板掉電不影響實時時鐘芯片的運(yùn)行,重新上電后讀取實時時鐘數(shù)據(jù)。
實時時鐘芯片DS1340Z需要外置32.768KHz的晶體,芯片內(nèi)部集成起振電阻電容等電路,晶體直接連接即可。
前面實驗中我們已經(jīng)講述學(xué)習(xí)過I2C總線驅(qū)動的設(shè)計,本實驗可以上原來的基礎(chǔ)上調(diào)整,首先來了解DS1340Z時序中的參數(shù)要點。
通過DS1340Z時序參數(shù)了解,DS1340Z支持I2C通信400KHz快速模式同時兼容100KHz的標(biāo)準(zhǔn)模式,還有兩種模式下時序中的各種時間參數(shù),所以通信速度不需要調(diào)整。
I2C時序基本單元(啟動、停止、發(fā)送、接收、發(fā)應(yīng)答、讀應(yīng)答)協(xié)議里統(tǒng)一的,所以所以基本單元狀態(tài)的設(shè)計也是不需要調(diào)整的。
DS1340Z芯片有很多寄存器,用于存儲實時時鐘的時間信息,例如地址為00H的寄存器中,bit7為晶體使能控制位,低有效,默認(rèn)使能,bit6~bit0為秒鐘數(shù)據(jù),且是BCD碼的格式(bit6~bit4代表秒鐘的十位,bit3~bit0代表秒鐘的個位),當(dāng)需要調(diào)整秒鐘時間時,對00H寄存器寫操作,當(dāng)讀取秒鐘時間時, 對00H寄存器讀操作。其他寄存器也是一樣,詳細(xì)請參考每個寄存器的功能說明。
本實驗涉及DS1340Z的寫寄存器和讀寄存器操作,查看手冊給出的操作時序流程。
芯片支持連續(xù)寫寄存器操作(寄存器地址自加1),時序流程如下:
根據(jù)連續(xù)寫寄存器時序流程,其設(shè)計程序?qū)崿F(xiàn)如下:
MAIN:begin if(cnt_main >= 6'd11) //對MAIN中的子狀態(tài)執(zhí)行控制cnt_main cnt_main <= 6'd0; // else cnt_main <= cnt_main + 1'b1; case(cnt_main) 6'd0: begin state <= START; end //I2C通信時序中的START 6'd1: begin data_wr <= 8'hd0; state <= WRITE; end //寫地址為8'hd0 6'd2: begin data_wr <= 8'h00; state <= WRITE; end //8'h00,起始寄存器 6'd3: begin data_wr <= adj_sec; state <= WRITE; end //00寄存器地址,寫秒 6'd4: begin data_wr <= adj_min; state <= WRITE; end //01寄存器地址,寫分 6'd5: begin data_wr <= adj_hour; state <= WRITE; end //02寄存器地址,寫時 6'd6: begin data_wr <= adj_week; state <= WRITE; end //03寄存器地址,寫周 6'd7: begin data_wr <= adj_day; state <= WRITE; end //04寄存器地址,寫日 6'd8: begin data_wr <= adj_mon; state <= WRITE; end //05寄存器地址,寫月 6'd9: begin data_wr <= adj_year; state <= WRITE; end //06寄存器地址,寫年 6'd10: begin data_wr <= 8'h40; state <= WRITE; end //07寄存器地址,8'h40 6'd11: begin state <= STOP; end //I2C通信時序中的STOP default: state <= IDLE; //如果程序失控,進(jìn)入IDLE自復(fù)位狀態(tài) endcase end
芯片支持連續(xù)讀寄存器操作(寄存器地址自加1),時序流程如下:
根據(jù)連續(xù)寫寄存器時序流程,其設(shè)計程序?qū)崿F(xiàn)如下:
MAIN:begin if(cnt_main >= 6'd32) //對MAIN中的子狀態(tài)執(zhí)行控制cnt_main cnt_main <= 6'd12; //否則只執(zhí)行時間讀取操作 else cnt_main <= cnt_main + 1'b1; case(cnt_main) 6'd12: begin state <= START; end //I2C通信時序中的START 6'd13: begin data_wr <= 8'hd0; state <= WRITE; end //寫地址為8'hd0 6'd14: begin data_wr <= 8'h00; state <= WRITE; end //8'h00,寄存器初始地址 6'd15: begin state <= START; end //I2C通信時序中的START 6'd16: begin data_wr <= 8'hd1; state <= WRITE; end //讀地址為8'hd1 6'd17: begin ack <= ACK; state <= READ; end //讀秒 6'd18: begin rtc_sec <= rtc_data_r; end 6'd19: begin ack <= ACK; state <= READ; end //讀分 6'd20: begin rtc_min <= rtc_data_r; end 6'd21: begin ack <= ACK; state <= READ; end //讀時 6'd22: begin rtc_hour <= rtc_data_r; end 6'd23: begin ack <= ACK; state <= READ; end //讀周 6'd24: begin rtc_week <= rtc_data_r; end 6'd25: begin ack <= ACK; state <= READ; end //讀日 6'd26: begin rtc_day <= rtc_data_r; end 6'd27: begin ack <= ACK; state <= READ; end //讀月 6'd28: begin rtc_mon <= rtc_data_r; end 6'd29: begin ack <= ACK; state <= READ; end //讀年 6'd30: begin rtc_year <= rtc_data_r; end 6'd31: begin ack <= NACK; state <= READ; end //控制 6'd32: begin state <= STOP; end //I2C通信時序中的STOP,讀取完成標(biāo)志 default: state <= IDLE; //如果程序失控,進(jìn)入IDLE自復(fù)位狀態(tài) endcase end
上面兩段程序就是對于DS1340Z芯片的兩種操作,調(diào)時間和讀時間,對于萬年歷來說因為有電池供電,實時時鐘一直都處于工作狀態(tài),當(dāng)給FPGA上電時只需要讀時間即可,只有遇到時間不對的時候才需要調(diào)時間,所以DS1340Z驅(qū)動模塊平時都在循環(huán)讀取時間,所以如果將調(diào)時間和讀時間的時序操作融合到同一個狀態(tài)下時,對于cntmain要加以控制,cntmain初值為12,且運(yùn)行軌跡在12~32之間,控制程序調(diào)整如下:
if(cnt_main >= 6'd32) //對MAIN中的子狀態(tài)執(zhí)行控制cnt_main if(set_flag)cnt_main <= 6'd0; //當(dāng)set_flag被置位時才會執(zhí)行時間寫入操作 else cnt_main <= 6'd12; //否則只執(zhí)行時間讀取操作 else cnt_main <= cnt_main + 1'b1;
上面setflag為時間調(diào)整標(biāo)志位,只有按動編碼器在調(diào)時間模式時需要用到寫時間數(shù)據(jù)的操作流程,可以根據(jù)按鍵脈沖置位setflag并自鎖,每次完成寫入操作后再將set_flag復(fù)位。程序?qū)崿F(xiàn)如下:
reg set_flag; always@(posedge clk or negedge rst_n) begin if(!rst_n) set_flag <= 1'b0; else if(cnt_main==5'd11) set_flag <= 1'b0; //完成寫入時間操作復(fù)位set_flag else if(key_set) set_flag <= 1'b1; //按鍵脈沖控制set_flag置位 else set_flag <= set_flag; end
模塊端口如下:
module DS1340Z_driver ( input clk, rst_n, //系統(tǒng)時鐘和復(fù)位 input key_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)整輸入 output i2c_scl, //I2C總線SCLinout 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è)計,宏觀上講,該模塊的功能可以這樣描述:
控制模塊包含多個功能的設(shè)計:模式控制、調(diào)時控制、顯示控制,可以細(xì)化成多個模塊實現(xiàn),本實驗例程中就寫在了一個模塊下,我們會針對這三個功能分別講解其實現(xiàn)方法及原理。
模式控制
項目要求設(shè)計成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)機(jī)跳轉(zhuǎn)順序是固定的(0→7→6→5→4→3→2→1→0),依次循環(huán)跳轉(zhuǎn),程序?qū)崿F(xiàn)如下:
//時鐘運(yùn)行狀態(tài)控制 always@(posedge clk or negedge rst_n ) if(!rst_n) state <= 3'd0; else if(O_pulse) //按鍵脈沖控制時鐘運(yùn)行狀態(tài)的跳變, if(state) state <= state - 3'd1; else state <= 3'd7; else state <= state;
調(diào)時控制
調(diào)時控制在不同的調(diào)節(jié)模式對不同時間進(jìn)行調(diào)整,我們分別以常態(tài)模式和調(diào)秒模式為例進(jìn)行分析。
萬年歷時間調(diào)節(jié)要以當(dāng)時的時間為基礎(chǔ),常態(tài)模式下不需要調(diào)整任何時間,但是可以將實時時鐘讀出的時間數(shù)據(jù)賦給調(diào)節(jié)變量,這樣等跳轉(zhuǎn)到調(diào)節(jié)模式時對調(diào)節(jié)變量的控制就是以當(dāng)時的時間為基礎(chǔ)了,程序?qū)崿F(xiàn)如下:
3'd0: //正常模式 begin if(O_pulse)begin //在常態(tài)下按動編碼器將當(dāng)前實時時間賦值給調(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ù)進(jìn)行調(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; else if(adj_sec[7:4]) adj_sec <= {adj_sec[7:4]-1'h1,4'h9}; else adj_sec <= 8'h59; end else if(R_pulse) begin //順時針轉(zhuǎn) if(adj_sec[3:0]!=4'h9) adj_sec <= adj_sec + 1'h1; else if(adj_sec[7:4]!=4'h5) adj_sec <= {adj_sec[7:4]+1'h1,4'h0}; else adj_sec <= 8'h00; end else adj_sec <= adj_sec; end
顯示控制
首先來看看一下數(shù)碼管要顯示的效果,8位數(shù)碼管分兩頁顯示萬年歷數(shù)據(jù),第一頁顯示年月日周,第二頁顯示時分秒。
我們看到任何一項時間選項都由兩位數(shù)碼管顯示,每頁最多顯示4個時間選項,我們可以使用4位的變量dispen[3:0]控制4個時間選項的點亮或熄滅,dispen[3]控制最左側(cè)兩個數(shù)碼管,disp_en[0]控制最右側(cè)兩個數(shù)碼管,我們分別以常態(tài)模式和調(diào)秒模式為例進(jìn)行顯示使能控制的分析。
常態(tài)模式下,轉(zhuǎn)動編碼器控制顯示頁碼,兩個頁碼對應(yīng)的顯示控制,程序?qū)崿F(xiàn)如下:
3'd0: //正常模式 if(L_pulse) disp_en <= 4'b1111; //逆時針轉(zhuǎn)顯示第一頁,數(shù)碼管全亮 else if(R_pulse) disp_en <= 4'b0111; //順時針轉(zhuǎn)顯示第二頁,時分秒亮 else disp_en <= disp_en;
調(diào)秒模式下,小時和分鐘數(shù)碼管點亮,秒鐘閃爍顯示,轉(zhuǎn)動編碼器時秒鐘強(qiáng)制顯示,最后按動旋轉(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)動時強(qiáng)制顯示 else if(sec_pulse) disp_en[0] <= ~disp_en[0]; //秒鐘閃爍顯示 else if(O_pulse) disp_en <= 4'b0111; //返回常態(tài)時顯示時分秒 else disp_en[0] <= disp_en[0]; end
前面分析了顯示控制,主要對時間選項的點亮還是熄滅做控制,對應(yīng)到數(shù)碼管上就轉(zhuǎn)化成數(shù)碼管位的點亮和熄滅控制。另外還包含顯示數(shù)據(jù)的控制,而這部分設(shè)計我們放到頂層模塊中實現(xiàn)了,我們來分析一下。
數(shù)碼管點亮控制
數(shù)碼管與時間選項是對應(yīng)關(guān)系,每個選項對應(yīng)兩位數(shù)碼管,程序?qū)崿F(xiàn)如下:
wire [7:0] data_en = {{2{disp_en[3]}},{2{disp_en[2]}},{2{disp_en[1]}},{2{disp_en[0]}}}; //數(shù)碼管位選控制 wire [7:0] dot_en = {1'b0,disp_en[3],1'b0,disp_en[2],1'b0,disp_en[1],1'b0,disp_en[0]}; //數(shù)碼管小數(shù)點顯示控制
數(shù)碼管內(nèi)容控制
萬年歷的顯示分兩頁實現(xiàn),我們以最右側(cè)兩個數(shù)碼管顯示內(nèi)容為例,這兩位數(shù)碼管在第一頁中顯示周數(shù)據(jù),在第二頁中顯示秒數(shù)據(jù),那么我們怎么控制顯示內(nèi)容呢?分析,萬年歷8中模式,
1.常態(tài)模式下,顯示讀取的實時時鐘數(shù)據(jù),具體顯示周還是秒再次細(xì)化
常態(tài)模式下,根據(jù)disp_en選擇顯示周數(shù)據(jù)還是秒數(shù)據(jù),程序?qū)崿F(xiàn)如下:
wire [7:0] data_rtc0 = disp_en[3]? rtc_week:rtc_sec; //常態(tài)下數(shù)碼管顯示數(shù)據(jù)
2.調(diào)節(jié)模式下,顯示寫入的調(diào)節(jié)時鐘數(shù)據(jù),具體顯示周還是秒再次細(xì)化
調(diào)節(jié)模式下,根據(jù)state選擇顯示周數(shù)據(jù)還是秒數(shù)據(jù),程序?qū)崿F(xiàn)如下:
wire [7:0] data_adj0 = state[2]? adj_week:adj_sec; //調(diào)節(jié)狀態(tài)下數(shù)碼管顯示數(shù)據(jù)
3.最后根據(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)如下:
assign {data_7,data_8} = state? data_adj0:data_rtc3; //根據(jù)狀態(tài)選擇顯示常態(tài)數(shù)據(jù)還是調(diào)節(jié)狀態(tài)數(shù)據(jù)
綜合后的設(shè)計框圖如下:
將程序下載到FPGA中,按照設(shè)計要求的功能操作調(diào)節(jié)萬年歷的時間,觀察數(shù)碼管萬年歷顯示,如圖時間為18年6月27日,周三,19點15分14秒。
評論