2012年10月25日 星期四

電子鐘(I)


源自http://www.haogongju.net/art/1083947 

如何設計電子鐘(I)

作者:真 OO无双 | 出处:博客园 | 2011/12/13 11:35:14 | 阅读91
Abstract
學會計數器除頻電路後,就能以這兩個電路為基礎,設計一個電子鐘,並可自行調整目前時間。
Introduction
使用環境:Quartus II 7.2 SP3 + DE2(Cyclone II EP2C35F627C6)
(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)討論過計數器,接著在(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)討論過萬用除頻器,利用這兩個基礎,就可以設計一個簡易的電子鐘,如同電子錶一樣顯示時間。
Specification
SW17enable
SW16reset
SW15load (設定時間)
SW[3:0]設定分的個位數
SW[6:4]設定分的十位數
SW[10:7]設定時的個位數
SW[13:11]設定時的十位數
HEX[2]顯示秒的個位數
HEX[3]顯示秒的十位數
HEX[4]顯示分的個位數
HEX[5]顯示分的十位數
HEX[6]顯示時的個位數
HEX[7]顯示時的十位數

Block Diagram


 digi_clock00
digi_clock01
這是一個簡化的系統方塊圖,因為寬度的關係,我將input與output都與以省略,上圖的divn是個除頻器,負責將DE2提供50MHz clock除頻程1 Hz,seg7_lut則是負責將數字顯示在7段顯示器上。clock的細部實現為下圖,由兩個60計數器負責,24計數器負責
digi_clock.v / Verilog
1 /* 2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : digi_clock.v5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g6 Description : Demo how to write digital clock top module7 Release     : 07/27/2008 1.08 */
9 
10 module digi_clock (11   input         CLOCK_50,12   input  [17:0] SW,13   input  [3:0]  KEY,14   output [6:0]  HEX2,15   output [6:0]  HEX3,16   output [6:0]  HEX4,17   output [6:0]  HEX5,18   output [6:0]  HEX6,19   output [6:0]  HEX720 );21 
22 wire       clk_1;23 wire [3:0] w_sq0;24 wire [2:0] w_sq1;25 wire [3:0] w_mq0;26 wire [2:0] w_mq1;27 wire [3:0] w_hq0;28 wire [2:0] w_hq1;29 
30 // 1Hz clock
31 divn # (.WIDTH(26), .N(50000000)) 32 u0 (33   .clk(CLOCK_50),34   .rst_n(KEY[0]),35   .o_clk(clk_1)36 );37 
38 clock u1 (39   .clk(clk_1),40   .en(SW[17]),     // input  enable
41   .clr(SW[16]),    // input  clear
42   .load(SW[15]),   // input  load
43   .sd0(4'h0),      // input  second digit 0
44   .sd1(3'h0),      // input  second digit 1
45   .md0(SW[3:0]),   // input  minute digit 0
46   .md1(SW[6:4]),   // input  minute digit 1
47   .hd0(SW[10:7]),  // input  hour digit   0
48   .hd1(SW[13:11]), // input  hour digit   1
49   .sq0(w_sq0),     // output second digit 0
50   .sq1(w_sq1),     // output second digit 1 
51   .mq0(w_mq0),     // output minute digit 0
52   .mq1(w_mq1),     // output minute digit 1
53   .hq0(w_hq0),     // output minute digit 0
54   .hq1(w_hq1)      // output minute digit 1
55 );56 
57 // sec. dig0 to seg7
58 seg7_lut u2 (59   .i_dig(w_sq0),60   .o_seg(HEX2)61 );62 
63 // sec. dig1 to seg7
64 seg7_lut u3 (65   .i_dig({1'b0, w_sq1}),
66   .o_seg(HEX3)67 );68 
69 // min. dig0 to seg7
70 seg7_lut u4 (71   .i_dig(w_mq0),72   .o_seg(HEX4)73 );74 
75 // min. dig1 to seg7
76 seg7_lut u5 (77   .i_dig({1'b0, w_mq1}),
78   .o_seg(HEX5)79 );80 
81 // hour dig0 to seg7
82 seg7_lut u6 (83   .i_dig(w_hq0),84   .o_seg(HEX6)85 );86 
87 // hour dig1 to seg7
88 seg7_lut u7 (89   .i_dig({1'b0, w_hq1}),
90   .o_seg(HEX7)91 );92 
93 endmodule

這是整個project的top module,為了簡化pin assignment的動作,所以port的命名方式和DE2_pin_assignments.csv相同,或許你跟我一樣不喜歡port用大寫的coding style,但為了pin assignment方便,只好在top module配合一下大寫,其他module的port一樣可以用小寫。
30行
// 1Hz clock
divn # (.WIDTH(26), .N(50000000)) 
u0 (
  .clk(CLOCK_50),
  .rst_n(KEY[
0]),
  .o_clk(clk_1)
);

divn為(原創) 如何設計除頻器? (SOC) (Verilog) (MegaCore)所寫過的萬用除頻器,由於DE2提供的clock是50MHz,但電子鐘只希望每秒變化一次,所以要除頻剩下1Hz,所以要將50MHz除50M,經過計算,這樣需26位才夠,所以傳進26與50000000。
divn.v / Verilog
1 /* 2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : divn.v5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g6 Description : Demo how to write frequency divider by n7 Release     : 07/16/2008 1.08 */
9 
10 module divn    (11   input  clk,12   input  rst_n,13   output o_clk14 );15 
16 parameter WIDTH = 3;17 parameter N     = 6;18 
19 reg [WIDTH-1:0] cnt_p;20 reg [WIDTH-1:0] cnt_n;21 reg             clk_p;22 reg             clk_n;23 
24 assign o_clk = (N == 1? clk :25                (N[0])   ? (clk_p | clk_n) : (clk_p);26         27 always@(posedge clk or negedge rst_n) begin
28   if (!rst_n)29     cnt_p <= 0;30   else if (cnt_p == (N-1))31     cnt_p <= 0;32   else
33     cnt_p <= cnt_p + 1;34 end
35 
36 always@(posedge clk or negedge rst_n) begin
37   if (!rst_n) 38     clk_p <= 1;39   else if (cnt_p < (N>>1))40     clk_p = 1;41   else
42     clk_p = 0;    43 end
44 
45 always@(negedge clk or negedge rst_n) begin
46   if (!rst_n)47     cnt_n <= 0;48   else if (cnt_n == (N-1))49     cnt_n <= 0;50   else
51     cnt_n <= cnt_n + 1;52 end
53 
54 always@(negedge clk or negedge rst_n) begin
55   if (!rst_n)56     clk_n <= 1;57   else if (cnt_n < (N>>1))58     clk_n = 1;59   else
60     clk_n = 0;61 end
62 
63 endmodule
64 
seg7_lut.v / Verilog
1 /* 2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : seg7_lut.V5 Compiler    : Quartus II 7.2 SP36 Description : Demo how to use 8 bit 7 segment display decimal7 Release     : 07/20/2008 1.08 */
9 module seg7_lut (10   input      [3:0] i_dig,11   output reg [6:0] o_seg12 );13 
14 always@(i_dig) begin
15   case(i_dig)16     4'h1: o_seg = 7'b111_1001;  // ---t----
17     4'h2: o_seg = 7'b010_0100;  // |      |
18     4'h3: o_seg = 7'b011_0000;  // lt    rt
19     4'h4: o_seg = 7'b001_1001;  // |      |
20     4'h5: o_seg = 7'b001_0010;  // ---m----
21     4'h6: o_seg = 7'b000_0010;  // |      |
22     4'h7: o_seg = 7'b111_1000;  // lb    rb
23     4'h8: o_seg = 7'b000_0000;  // |      |
24     4'h9: o_seg = 7'b001_1000;  // ---b----
25     4'ha: o_seg = 7'b000_1000;26     4'hb: o_seg = 7'b000_0011;27     4'hc: o_seg = 7'b100_0110;28     4'hd: o_seg = 7'b010_0001;29     4'he: o_seg = 7'b000_0110;30     4'hf: o_seg = 7'b000_1110;31     4'h0: o_seg = 7'b100_0000;32   endcase
33 end
34 
35 endmodule

這是一個7段顯示器的lookup table,請參考(原創) 如何以16進位顯示8位數的七段顯示器? (SOC) (Verilog) (DE2)
clock.v / Verilog
  1 /*   2 (C) OOMusou 2008 http://oomusou.cnblogs.com
  3 
  4 Filename    : clock.v  5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g  6 Description : Demo how to write clock counter  7 Release     : 07/28/2008 1.0  8 */
  9 
10 module clock (11   input        clk, 12   input        en,  13   input        clr, 14   input        load,15   input  [3:0] sd0, 16   input  [2:0] sd1, 17   input  [3:0] md0, 18   input  [2:0] md1, 19   input  [3:0] hd0, 20   input  [2:0] hd1, 21   output [3:0] sq0, 22   output [2:0] sq1,  23   output [3:0] mq0, 24   output [2:0] mq1, 25   output [3:0] hq0, 26   output [1:0] hq1, 27   output       co   28 );29 
30 wire       w_clr;31 wire [3:0] w_md0; 32 wire [2:0] w_md1;33 reg  [3:0] w_hd0;34 reg  [2:0] w_hd1;35 wire       w_sco; // second carry
36 wire       w_mco; // minute carry
37 wire       w_hco; // hour carry
38 
39 counter60 sec (40   .clk(clk),41   .load(load),42   .clr(w_clr),43   .en(en),44   .d0(sd0),45   .d1(sd1),46   .q0(sq0),47   .q1(sq1),48   .co(w_sco)49 );50 
51 counter60 min (52   .clk(clk),53   .load(load),54   .clr(w_clr),55   .en(en & w_sco),56   .d0(w_md0),57   .d1(w_md1),58   .q0(mq0),59   .q1(mq1),60   .co(w_mco)61 );62 
63 counter24 hour (64   .clk(clk),65   .load(load),66   .clr(w_clr),67   .en(en & w_mco & w_sco),68   .d0(w_hd0),69   .d1(w_hd1),70   .q0(hq0),71   .q1(hq1),72   .co(w_hco)73 );74 
75 assign w_clr = clr | co;76 assign co    = w_hco & w_mco & w_sco;77 assign w_md0 = (!load) ? 0 :78                (md0 < 10? md0 : 9;79 assign w_md1 = (!load) ? 0 : 80                (md1 < 6)  ? md1 : 5;81                       82 always@(load or hd0 or hd1) begin
83   if (!load) begin
84     w_hd0 = 0;85     w_hd1 = 0;86   end
87   else begin
88     if (hd1 <= 1begin // 0 1
89       w_hd1 = hd1;90       91       if (hd0 < 10)92         w_hd0 = hd0;93       else
94         w_hd0 = 9;95     end
96     else begin // >= 2
97       w_hd1 = 2;98       99       if (hd0 < 4)100         w_hd0 = hd0;101       else
102         w_hd0 = 3;103     end
104   end
105 end                        106 
107 endmodule

clock.v事實上已經是一個完整功能的電子鐘,可以單獨用testbench測試,但為了要和DE2周邊搭配,所以才又包了一個digi_clock.v。
clock.v主要有兩個功能:
1.例化時、分、秒3個instance。
2.對輸入做防呆的動作。
75行
assign w_clr = clr | co;assign co    = w_hco & w_mco & w_sco;

co為整個電子鐘的進位功能,也就是當時、分、秒皆有進位時才進位,也就是23:59:59時才進位。
w_clr為整個電子鐘歸0的連線,除了手動clear外,當co為1,也就是23:59:59時, 整個電子鐘也會歸0。
下面都是對輸入防呆的程式,也就是的輸入的的部分,最多只能輸入到59,的部分最多只能輸入到23。
77行
assign w_md0 = (!load) ? 0 :
               (md0 
< 10? md0 : 9;assign w_md1 = (!load) ? 0 : 
               (md1 
< 6)  ? md1 : 5;

的部分比較單純,所以用 ?: 寫法即可,因為分最多只能到59分,個位數最多只能到9,超過9的部分一率只能輸入9。十位部分最多只能到5,超過5的部分一率只能輸入5。
81行
always@(load or hd0 or hd1) begin
  
if (!load) begin
    w_hd0 
= 0;
    w_hd1 
= 0;
  
end
  
else begin
    
if (hd1 <= 1begin // 0 1
      w_hd1 = hd1;
      
      
if (hd0 < 10)
        w_hd0 
= hd0;
      
else
        w_hd0 
= 9;
    
end
    
else begin // >= 2
      w_hd1 = 2;
      
      
if (hd0 < 4)
        w_hd0 
= hd0;
      
else
        w_hd0 
= 3;
    
end
  
end
end         

的部分比較複雜,所以用always block來寫。因為時最多只能到23,所以個位數能輸入的數字還要看當時的十位數而定。若十位數為0或1,則個位數做多到9;若十位數大於2,個位數最多只能到3,超過3的部分一率只能輸3。
counter60.v / Verilog
1 /* 2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : counter60.v5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g6 Description : Demo how to write 60 counter7 Release     : 07/27/2008 1.08 */
9 
10 module counter60 (11   input            clk,12   input            clr,13   input            load,14   input            en,15   input      [3:0] d0,16   input      [3:0] d1,17   output reg [3:0] q0,18   output reg [2:0] q1,19   output           co20 );21 
22 assign co = q1[2& q1[0& q0[3& q0[0]; // 101 1001 = 59 
23 
24 always@(posedge clk) begin
25   if (clr) begin
26     q0 <= 0;27     q1 <= 0;28   end
29   else if (load) begin
30     q0 <= d0;31     q1 <= d1;32   end
33   else if (en) begin
34     if (q0 == 9begin
35       q0 <= 0;36       37       if (q1 == 538         q1 <= 0;39       else
40         q1 <= q1 + 1;41     end
42     else 43       q0 <= q0 + 1;44   end 45   else begin
46     q0 <= q0;47     q1 <= q1;48   end
49 end
50 
51 endmodule

由於分和秒為60進制,所以需要一個60計數器,並且能進位,請參考(筆記) 如何設計計數器? (SOC) (Verilog) (MegaCore)
22行
assign co = q1[2& q1[0& q0[3& q0[0]; // 101 1001 = 59

進位的地方較特別,當59時要送出進位carry,若用2進位表示就是101 1001,所以只要將q1[2] & q1[0] & q0[3] & q0[0]即可,這就是2進位好用的地方。
counter24.v / Verilog
1 /* 2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3 
4 Filename    : counter24.v5 Compiler    : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g6 Description : Demo how to write 24 counter7 Release     : 07/27/2008 1.08 */
9 
10 module counter24 (11   input            clk,12   input            clr,13   input            load,14   input            en,15   input      [3:0] d0,16   input      [1:0] d1,17   output reg [3:0] q0,18   output reg [1:0] q1,19   output           co20 );21 
22 assign co = q0[1& q0[0& q1[1]; // 010   11 = 23
23 
24 always@(posedge clk) begin
25   if (clr) begin
26     q0 <= 0;27     q1 <= 0;28   end
29   else if (load) begin
30     q0 <= d0;31     q1 <= d1;32   end
33   else if (en) begin
34     if (q0 == 9begin
35       q0 <= 0;36       q1 <= q1 + 1;37     end
38     else if (q1 == 2 & q0 == 3begin // 23
39       q1 <= 0;40       q0 <= 0;41     end 42     else 43       q0 <= q0 + 1;44   end
45   else begin
46     q0 <= q0;47     q1 <= q1;48   end
49 end
50 
51 endmodule

原理和60計數器一樣,我就不在多做解釋。
完整程式碼下載
digi_clock.7z
Conclusion
原來每天帶的電子錶,就是以clock為基礎,搭配計數器做出來的,透過FPGA,我們也可以自己設計一個簡易的電子鐘。

沒有留言:

張貼留言

WOKWI DHT22 & LED , Node-Red + SQLite database

 WOKWI DHT22 & LED , Node-Red + SQLite database Node-Red程式 [{"id":"6f0240353e534bbd","type":"comment&...