设计目标:设计一个可调数字时钟
1、利用OLED屏幕显示时、分、秒;
2、可以通过FPGA核心板上按键调节时分(24小时制);
3、利用蜂鸣器音乐实现整点报时功能。
实施方案:
一、引言
1、数字逻辑
可调时钟是一个最基本的嵌入式系统,也是新手用来练习的最简单的项目之一。正因如此,在诸如8051、STM32、FPGA等多个平台都有关于时钟或者万年历的编程项目。其实现流程大体上是一个较复杂的状态机:将时分秒的状态和关键时刻进位判断一一分开,这样来实现一个顺次进位的时钟功能。可是,我接触FPGA接触VHDL和Verilog的时间不算长,写过的项目也不算多,一下子去完成这么大的一个状态判断有些困难,一时间我的思维也停住了。FPGA是一个硬件模块,Verilog说到底也是硬件描述语言,通过行为级代码来生成一个硬件,无论代码什么样,都要对应一个硬件。想到这里,我想到了以前还没有CPU的时候,国外的很多大神利用数字逻辑芯片就可以做出游戏硬件就可以制作一款能在游戏机上面玩的游戏。以前的游戏也只不过是在很多复杂状态下运行的大状态机,既然数字逻辑硬件可以组成游戏,那我也可以用数字逻辑硬件来做时钟。于是,一个由60进制计数器,24进制计数器,BCD码转换器,比较器......等许多小模块级联成的数字时钟就诞生了。
2、外设驱动(OLED显示、蜂鸣器)
通过10天短时间的学习,我对Verilog语言有了更熟练的掌握,对显示方面,我移植了课程的OLED代码如下:
module OLED12832
(
input clk, //12MHz系统时钟
input rst_n, //系统复位,低有效
input [3:0] seg1, //
input [3:0] seg2,
input [3:0] seg3,
input [3:0] seg4,
input [3:0] seg5,
input [3:0] seg6,
output reg oled_csn, //OLCD液晶屏使能
output reg oled_rst, //OLCD液晶屏复位
output reg oled_dcn, //OLCD数据指令控制
output reg oled_clk, //OLCD时钟信号
output reg oled_dat //OLCD数据信号
);
localparam INIT_DEPTH = 16'd25; //LCD初始化的命令的数量
localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
localparam HIGH = 1'b1, LOW = 1'b0;
localparam DATA = 1'b1, CMD = 1'b0;
reg [7:0] cmd [24:0];
reg [39:0] mem [122:0];
reg [7:0] y_p, x_ph, x_pl;
reg [(8*16-1):0] char;
reg [7:0] num, char_reg; //
reg [4:0] cnt_main, cnt_init, cnt_scan, cnt_write;
reg [15:0] num_delay, cnt_delay, cnt;
reg [5:0] state, state_back;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
MAIN:begin
if(cnt_main >= 5'd12) cnt_main <= 5'd5;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
5'd0: begin state <= INIT; end
5'd1: begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " " ;state <= SCAN; end
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " " ;state <= SCAN; end
5'd3: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " " ;state <= SCAN; end
5'd4: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " " ;state <= SCAN; end
5'd5: begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd 1; char <= seg1 ;state <= SCAN; end
5'd6: begin y_p <= 8'hb0; x_ph <= 8'h11; x_pl <= 8'h00; num <= 5'd 1; char <= seg2 ;state <= SCAN; end
5'd7: begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= ":" ;state <= SCAN; end
5'd8: begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= seg3 ;state <= SCAN; end
5'd9: begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= seg4 ; state <= SCAN; end
5'd10: begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= ":" ; state <= SCAN; end
5'd11: begin y_p <= 8'hb0; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd 1; char <= seg5 ; state <= SCAN; end
5'd12: begin y_p <= 8'hb0; x_ph <= 8'h17; x_pl <= 8'h00; num <= 5'd 1; char <= seg6 ; state <= SCAN; end
default: state <= IDLE;
endcase
end
INIT:begin //初始化状态
case(cnt_init)
5'd0: begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end //复位有效
5'd1: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于3us
5'd2: begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end //复位恢复
5'd3: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于220us
5'd4: begin
if(cnt>=INIT_DEPTH) begin //当25条指令及数据发出后,配置完成
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num_delay <= 16'd5;
oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
end
end
5'd5: begin cnt_init <= 1'b0; state <= MAIN; end //初始化完成,返回MAIN状态
default: state <= IDLE;
endcase
end
SCAN:begin //刷屏状态,从RAM中读取数据刷屏
if(cnt_scan == 5'd11) begin
if(num) cnt_scan <= 5'd3;
else cnt_scan <= cnt_scan + 1'b1;
end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd 0: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end //定位列页地址
5'd 1: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end //定位行地址低位
5'd 2: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end //定位行地址高位
5'd 3: begin num <= num - 1'b1;end
5'd 4: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 5: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 6: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 7: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
5'd 8: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
5'd 9: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd12: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin //WRITE状态,将数据按照SPI时序发送给屏幕
if(cnt_write >= 5'd17) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
5'd 0: begin oled_csn <= LOW; end //9位数据最高位为命令数据控制位
5'd 1: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end //先发高位数据
5'd 2: begin oled_clk <= HIGH; end
5'd 3: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd 4: begin oled_clk <= HIGH; end
5'd 5: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd 6: begin oled_clk <= HIGH; end
5'd 7: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd 8: begin oled_clk <= HIGH; end
5'd 9: begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
5'd10: begin oled_clk <= HIGH; end
5'd11: begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
5'd12: begin oled_clk <= HIGH; end
5'd13: begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
5'd14: begin oled_clk <= HIGH; end
5'd15: begin oled_clk <= LOW; oled_dat <= char_reg[0]; end //后发低位数据
5'd16: begin oled_clk <= HIGH; end
5'd17: begin oled_csn <= HIGH; state <= DELAY; end //
default: state <= IDLE;
endcase
end
DELAY:begin //延时状态
if(cnt_delay >= num_delay) begin
cnt_delay <= 16'd0; state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
//OLED配置指令数据
always@(posedge rst_n)
begin
cmd[ 0] = {8'hae};
cmd[ 1] = {8'h00};
cmd[ 2] = {8'h10};
cmd[ 3] = {8'h00};
cmd[ 4] = {8'hb0};
cmd[ 5] = {8'h81};
cmd[ 6] = {8'hff};
cmd[ 7] = {8'ha1};
cmd[ 8] = {8'ha6};
cmd[ 9] = {8'ha8};
cmd[10] = {8'h1f};
cmd[11] = {8'hc8};
cmd[12] = {8'hd3};
cmd[13] = {8'h00};
cmd[14] = {8'hd5};
cmd[15] = {8'h80};
cmd[16] = {8'hd9};
cmd[17] = {8'h1f};
cmd[18] = {8'hda};
cmd[19] = {8'h00};
cmd[20] = {8'hdb};
cmd[21] = {8'h40};
cmd[22] = {8'h8d};
cmd[23] = {8'h14};
cmd[24] = {8'haf};
end
//5*8点阵字库数据
always@(posedge rst_n)
begin
mem[ 0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00}; // 32 sp
mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00}; // 33 !
mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00}; // 34
mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14}; // 35 #
mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12}; // 36 $
mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23}; // 37 %
mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50}; // 38 &
mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00}; // 39 '
mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00}; // 40 (
mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00}; // 41 )
mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14}; // 42 *
mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08}; // 43 +
mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00}; // 44 ,
mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08}; // 45 -
mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00}; // 46 .
mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02}; // 47 /
mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00}; // 58 :
mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00}; // 59 ;
mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00}; // 60 <
mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14}; // 61 =
mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08}; // 62 >
mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06}; // 63 ?
mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E}; // 64 @
mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A}; // 71 G
mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F}; // 72 H
mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00}; // 73 I
mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01}; // 74 J
mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41}; // 75 K
mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40}; // 76 L
mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F}; // 77 M
mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F}; // 78 N
mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E}; // 79 O
mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06}; // 80 P
mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E}; // 81 Q
mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46}; // 82 R
mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31}; // 83 S
mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01}; // 84 T
mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F}; // 85 U
mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F}; // 86 V
mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F}; // 87 W
mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63}; // 88 X
mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07}; // 89 Y
mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43}; // 90 Z
mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00}; // 91 [
mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55}; // 92 .
mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00}; // 93 ]
mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04}; // 94 ^
mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40}; // 95 _
mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00}; // 96 '
mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78}; // 97 a
mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38}; // 98 b
mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20}; // 99 c
mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F}; // 100 d
mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18}; // 101 e
mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02}; // 102 f
mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C}; // 103 g
mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78}; // 104 h
mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00}; // 105 i
mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00}; // 106 j
mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00}; // 107 k
mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00}; // 108 l
mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78}; // 109 m
mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78}; // 110 n
mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38}; // 111 o
mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18}; // 112 p
mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC}; // 113 q
mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08}; // 114 r
mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20}; // 115 s
mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20}; // 116 t
mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C}; // 117 u
mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C}; // 118 v
mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C}; // 119 w
mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44}; // 120 x
mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C}; // 121 y
mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44}; // 122 z
end
endmodule
值得注意的是,MAIN进程中,初始化之后一定要写零清屏,不然就会出现花屏问题,如下:
蜂鸣器我是用一个6000Hz方波驱动发声,以后会考虑加入音乐。如图:
利用方波作为驱动信号,利用单稳态电路作为开关,当比较器信号输入时蜂鸣器就会发声。
二、数字逻辑实现
1、基础60进制计数器(秒钟、按键调时源)
代码如下:此计数器只有基本计数和溢出进位信号功能
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY CNT60 IS
PORT (CLK,RST_N,EN:STD_LOGIC;
QH:BUFFER STD_LOGIC_VECTOR(3 DOWNTO 0);
QL:BUFFER STD_LOGIC_VECTOR(3 DOWNTO 0);
COUT: OUT STD_LOGIC);
END CNT60;
ARCHITECTURE BEHAVE OF CNT60 IS
BEGIN
PROCESS(CLK,RST_N)
BEGIN
IF RST_N='0' THEN
QH<="0000";
QL<="0000";
ELSIF RISING_EDGE(CLK) THEN
IF(EN='1')THEN
IF(QL="1001")THEN
QL<="0000";
IF(QH="0101")THEN
QH<="0000";
IF(QL="1001" AND QH="0101" )THEN
QH<="0000";
QL<="0000";
COUT<='1';
END IF;
ELSE
QH<=QH+1;
END IF;
ELSE
QL<=QL+1;
COUT<='0';
END IF;
END IF;
END IF;
END PROCESS;
END BEHAVE;
2、1Hz分频器
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY clk_1hz IS
PORT(CLK,RST_N:STD_LOGIC;
CLK_OUT:OUT STD_LOGIC);
END clk_1hz;
ARCHITECTURE BEHAVE OF clk_1hz IS
SIGNAL CNT : STD_LOGIC_VECTOR(25 DOWNTO 0);
SIGNAL rclk : STD_LOGIC;
BEGIN
CLK_OUT <= rclk;
PROCESS(CLK,RST_N)
BEGIN
IF RST_N ='0' THEN
CNT <= "00000000000000000000000000";
rclk <= '0';
ELSIF RISING_EDGE(CLK) THEN
IF CNT = "00101101110001101100000000" THEN
CNT <= "00000000000000000000000000";
rclk <= NOT(rclk);
ELSE
CNT <= CNT + 1;
END IF;
END IF;
END PROCESS;
END BEHAVE;
工程中还有多个分频器模块,仅以此代码为例,其余只是CNT的参数差别。
3、时分系统与进位逻辑
1)分钟系统
分钟系统同样采用一个60进制计数器进行计时与按键输入,值得一提的是,所有计数器的使能位被一个翻转JK触发器控制,他的RTL图和元件代码如下(60进制可置数计数器、置数通道)
60进制可置数计数器
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY LCNT60 IS
PORT (CLK,RST_N,EN:STD_LOGIC;
DI:IN STD_LOGIC_VECTOR(7 DOWNTO 0);
QH:BUFFER STD_LOGIC_VECTOR(3 DOWNTO 0);
QL:BUFFER STD_LOGIC_VECTOR(3 DOWNTO 0);
COUT: OUT STD_LOGIC);
END LCNT60;
ARCHITECTURE BEHAVE OF LCNT60 IS
BEGIN
PROCESS(CLK,RST_N)
BEGIN
IF RST_N='0' THEN
QH<="0000";
QL<="0000";
ELSIF RISING_EDGE(CLK) THEN
IF(EN='1')THEN
IF(QL="1001")THEN
QL<="0000";
IF(QH="0101")THEN
QH<="0000";
IF(QL="1001" AND QH="0101" )THEN
QH<="0000";
QL<="0000";
COUT<='1';
IF(QL="0000" AND QH="0000" )THEN
COUT<='0';
END IF;
END IF;
ELSE
QH<=QH+1;
END IF;
ELSE
QL<=QL+1;
COUT<='0';
END IF;
ELSIF(EN='0')THEN
QH<=DI(7 DOWNTO 4);
QL<=DI(3 DOWNTO 0);
COUT<='0';
END IF;
END IF;
END PROCESS;
END BEHAVE;
置数通道
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY L60 IS
PORT( RST_N:IN STD_LOGIC;
LOAD:IN STD_LOGIC;
DI:IN STD_LOGIC_VECTOR(7 DOWNTO 0);
CLK:IN STD_LOGIC;
DO:OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
);
END L60;
ARCHITECTURE ART OF L60 IS
BEGIN
PROCESS(CLK, RST_N)
BEGIN
IF(RST_N='0')THEN
DO<="00000000";
ELSIF (CLK'EVENT AND CLK='1')THEN
IF(LOAD='1')THEN
DO<=DI;
END IF;
END IF;
END PROCESS;
END ART;
如上图,可以看出在JK触发器反转的时候正好是置数模式,按键将可调数据输入以后JK触发器再度翻转即可正常工作。但是,这里我还是写的有些麻烦了,受到了当时需要什么功能写什么的局限性,这里面的功能完全可以简化为一个模块:由一个时钟信号做总时钟,通过检测进位输入和使能在一个模块里面同时实现这两种功能,而现在我又多使用了一个选择器来给暂停时的置数器提供时钟,多消耗了一些资源。
2)时钟系统
时钟系统在原理上和分钟系统大同小异,但是因为小时是24进制,而24不能直接通过像60进制计数器那样分出BCD码,我在计数器输出端加了一个BCD码转换器,如下:
24进制可置数计数器
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY LCNT24 IS
PORT (CLK,RST_N,EN:STD_LOGIC;
DI:IN STD_LOGIC_VECTOR(4 DOWNTO 0);
Q:BUFFER STD_LOGIC_VECTOR(4 DOWNTO 0);
COUT: OUT STD_LOGIC);
END LCNT24;
ARCHITECTURE BEHAVE OF LCNT24 IS
BEGIN
PROCESS(CLK,RST_N)
BEGIN
IF RST_N='0' THEN
Q<="00000";
ELSIF RISING_EDGE(CLK) THEN
IF(EN='1')THEN
IF(Q="10111")THEN
Q<="00000";
COUT<='1';
ELSE
Q<=Q+1;
COUT<='0';
END IF;
ELSIF(EN='0')THEN
Q<=DI;
COUT<='0';
END IF;
END IF;
END PROCESS;
END BEHAVE;
BCD码转换器
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY Bin_BCD IS
PORT (
BCDIN:IN STD_LOGIC_VECTOR(4 DOWNTO 0);
BCDOUT:OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
);
END Bin_BCD;
ARCHITECTURE BEHAVE OF Bin_BCD IS
BEGIN
BCDOUT(7 DOWNTO 0)<="00000000" WHEN BCDIN="00000" ELSE
"00000001" WHEN BCDIN="00001" ELSE
"00000010" WHEN BCDIN="00010" ELSE
"00000011" WHEN BCDIN="00011" ELSE
"00000100" WHEN BCDIN="00100" ELSE
"00000101" WHEN BCDIN="00101" ELSE
"00000110" WHEN BCDIN="00110" ELSE
"00000111" WHEN BCDIN="00111" ELSE
"00001000" WHEN BCDIN="01000" ELSE
"00001001" WHEN BCDIN="01001" ELSE
"00010000" WHEN BCDIN="01010" ELSE
"00010001" WHEN BCDIN="01011" ELSE
"00010010" WHEN BCDIN="01100" ELSE
"00010011" WHEN BCDIN="01101" ELSE
"00010100" WHEN BCDIN="01110" ELSE
"00010101" WHEN BCDIN="01111" ELSE
"00010110" WHEN BCDIN="10000" ELSE
"00010111" WHEN BCDIN="10001" ELSE
"00011000" WHEN BCDIN="10010" ELSE
"00011001" WHEN BCDIN="10011" ELSE
"00100000" WHEN BCDIN="10100" ELSE
"00100001" WHEN BCDIN="10101" ELSE
"00100010" WHEN BCDIN="10110" ELSE
"00100011" WHEN BCDIN="10111" ELSE
"ZZZZZZZZ";
END BEHAVE;
3)进位逻辑
本次设计是用计数器逐次级联而成,时钟的进位信号是秒和分相与给时,一个简单的与门即可完成。
四、总体RTL图
如下
总结:
一、困难与不足与解决办法
1、用小规模数字逻辑电路组成类状态机电路,难免会有漏状态的问题,比如:可能在整点区间会存在异常进位状态(分秒“59:59”进位后“00:59”再次进位)
2、在临近进位的几秒,系统会有硬件延时累计,最高可累计200ms左右,延时累计会让时钟越来越慢
3、按键置数的结果总是要延迟一小段时间才在屏幕上显示,这让使用者体验大打折扣。
解决办法:
造成以上问题根本原因可能是数字模块比较多,应该再简化电路组成,多练习复杂状态机设计能力,状态机整体运行延时应该会得到缓解。
二、未来展望
1、添加蜂鸣器音乐代替刺耳单调的声音。
2、进一步精简电路,争取写出复杂的状态机代替。
3、优化OLED显示字库,在原有的驱动基础上增加全面清屏功能,避免忘记手动清屏而花屏。
三、FPGA资源占用报告
如下:
我的汇报完毕,感谢大家观看!!!