首页 » 互联网 » 「每周FPGA案例」 断电重加载时钟工程_时钟_数据

「每周FPGA案例」 断电重加载时钟工程_时钟_数据

落叶飘零 2025-01-01 00:42:09 0

扫一扫用手机浏览

文章目录 [+]

1.1 总体设计1.1.1 概述

在微机的发展初期,BIOS都存放在ROM(只读存储器)中。
ROM内部的资料是在ROM的制造工序中,在工厂里用分外的方法烧录进去的,个中的内容只能读不能改,一旦烧录进去,用户只能验证写入的资料是否精确,不能再做任何修正。
如果创造资料有任何缺点,则只有舍弃不用,重新订做一份。
ROM是在生产线上生产的,由于本钱高,一样平常只用在大批量运用的场合。

「每周FPGA案例」 断电重加载时钟工程_时钟_数据 「每周FPGA案例」 断电重加载时钟工程_时钟_数据 互联网

由于ROM制造和升级的不便,后来人们发明了PROM(Programmable ROM,可编程ROM)。
最初从工厂中制作完成的PROM内部并没有资料,用户可以用专用的编程器将自己的资料写入,但是这种机会只有一次,一旦写入后也无法修正,若是出了缺点,已写入的芯片只能报废。
PROM的特性和ROM相同,但是其本钱比ROM高,而且写入资料的速率比ROM的量产速率要慢,一样平常只适用于少量需求的场合或是ROM量产前的验证。

「每周FPGA案例」 断电重加载时钟工程_时钟_数据 「每周FPGA案例」 断电重加载时钟工程_时钟_数据 互联网
(图片来自网络侵删)

EPROM(Erasable Programmable ROM,可擦除可编程ROM)芯片可重复擦除和写入,办理了PROM芯片只能写入一次的弊端。
EPROM芯片有一个很明显的特色,在其正面的陶瓷封装上,开有一个玻璃窗口,透过该窗口,可以看到其内部的集成电路,紫外线透过该孔照射内部芯片就可以擦除其内的数据,完成芯片擦除的操作要用到EPROM擦除器。
EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12~24V,随不同的芯片型号而定)。
EPROM的型号因此27开头的,如27C020(8256K)是一片2M Bits容量的EPROM芯片。
EPROM芯片在写入资料后,还要以不透光的贴纸或胶布把窗口封住,以免受到周围的紫外线照射而使资料受损。

由于EPROM操作的不便,后来出的主板上BIOS ROM芯片大部分都采取EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)。
EEPROM的擦除不须要借助于其它设备,它因此电子旗子暗记来修正其内容的,而且因此Byte为最小修正单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。
EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商供应的专用刷新程序就可以轻而易举地改写内容,以是,它属于双电压芯片。
借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至“on”的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时利用时,则把跳线开关打至“off”的位置,防止CIH类的病毒对BIOS芯片的造孽修正。
以是,仍有不少主板采取EEPROM作为BIOS芯片并作为自己主板的一大特色。

1.1.2 设计目标

全体工程由FPGA、矩阵键盘/按键、数码管和AT93C46组成,实现一个上电后能重新加载,接着上次计数的数字时钟,详细功能如下。

1、 数码管显示时钟值,共利用了6个数码管,分别表示时十位、时个位、分十位、分个位、秒十位和秒个位。

2、 矩阵键盘或者按键可以对数字时钟进行时分秒的设置。

A、 上电后,时钟默认处于计时状态,当按键1按下,跳到韶光设置状态,当按键1再次按下,回到计时状态。

B、 当处于韶光设置状态时,默认此刻设置的是秒个位,当按键2按下,此刻设置秒十位,以此类推,一次设置为分个位、分十位、时个位和时十位。
再按下按键2,则重新设置秒个位。

C、 当处于韶光设置状态时,按下按键3,则设置位的值加1,如果溢出,则变成0。
例如当目前小时显示05时,设置时十位,按下按键3,变成15,再按下按键3,则变成05.当目前小时显示为03时,设置时十位,按一下按键3,变成13,再按一下按键3,则变成23,再按则为03。

3、 AT93C46则用于保存时钟值,其具有断电保护功能,断电数据不丢失。

A、 AT93C46一共可以保存128字节的数据。
工程将AT93C46分成空间1和空间2。
空间1占用的地址为0~3,空间2占用的地址为4~7。

B、 每隔1秒,保存当前时钟值。
第一次保存到空间1,第二次保存到空间2,第三次保存带空间1,依此类推。
(如果只有一个空间,则可能涌现写数据过程中断电,从而得不到完全数据情形)

C、 支持8位的CRC,天生多项式

,初始值为全1。

D、 每次保存的值,时十位、时个位、分十位、分个位、秒十位和秒个位各占4bit,共3个字节,加上1个字节的CRC,一共4个字节。

E、 上电后,FPGA将读取两个空间的数值,并作CRC考验。
如果两组数据的CRC考验均失落败,则不重新加载;如果有一组数据CRC考验失落败,则加载精确的一组数据;如果两组数据CRC考验均精确,则加载数值较大的一组数据。

1.1.3 系统构造框图

系统构造框图如下所示:

图一

1.1.4模块功能键盘(按键)扫描模块实现功能

1、将外来异步旗子暗记打两拍处理,将异步旗子暗记同步化。

2、实现20ms按键消抖功能。

3、实现矩阵键盘或者普通案件的检测功能,并输出有效按键旗子暗记。

时钟数据产生模块实现功能

卖力产生数字时钟须要的时钟信息。
包括:

1、 按数字时钟办法进行计数

2、 设置数字时钟的值

3、 将时钟数据输出给外部模块利用

4、 从数据处理模块得到时钟数据,并重新加载

数码管显示模块实现功能

1、 对时钟数据进行译码,然后发到数码管显示

2、 逐一显示时分秒的值数据处理模块实现功能

卖力写到AT93C46的数据,或者从AT93C46读到数据后的处理,包括:

1、 上电后,发送EWEN命令,打开AT93C46的写保护。

2、 发送EWEN命令后,开始读存储在AT93C46的两组时钟数据;对数据进行考验,然后选择适宜的数据给时钟数据产生模块加载

3、 每隔1秒从时钟数据产生模块获取时分秒的值,并产生CRC值,末了写到AT93C46上CRC处理模块实现功能

卖力CRC算法的模块,在数据处理模块内部利用

AT93C46模块实现功能

根据上游模块的EWEN、WRITE和READ命令,产生AT93C46的相应时序,从而写数据或者读到数据。
至于数据是什么、有什么用,不关心,只关心AT93C46的时序。

1.1.5顶层旗子暗记

1.1.6参考代码

`define KEY_SCANmodule at93c46_top_scan( clk , rst_n , key_col, mo , cs , mi , sk , key_row, seg_sel, seg_data ); parameter TIME_1S = 50_000_000; input clk ; input rst_n ; input [3:0] key_col ; input mo ; output cs ; output mi ; output sk ; output[3:0] key_row ; output[5:0] seg_sel ; output[7:0] seg_data ; wire rdy ; wire rdata_vld ; wire[3:0] key_en ; wire[23:0] data_load ; wire data_load_vld; wire[23:0] clk_data_out ; wire[6:0] addr ; wire[1:0] mode ; wire start ; wire[7:0] wdata ; wire[7:0] rdata ; `ifdef KEY_SCAN key_scanu_key_scan( .clk (clk ), .rst_n (rst_n), .key_col (key_col), .key_row (key_row), .key_en(key_en ) ); `else key_moduleu_key_module( .clk (clk ), .rst_n (rst_n ), .key_in(~key_col), .key_vld (key_en) ); `endif clock_data#(.TIME_1S(TIME_1S)) u_clock_data( .clk (clk ), .rst_n (rst_n ), .data_load (data_load ), .data_load_vld (data_load_vld ), .key_en (key_en ), .data_out (clk_data_out) ); seg_disp#(.SEG_NUM(6)) u_seg_disp( .rst_n (rst_n ), .clk (clk ), .din (clk_data_out), .din_vld ({6{1'b1}} ), .seg_sel (seg_sel ), .segment (seg_data ) ); data_processor#(.TIME_1S(TIME_1S)) u_data_pro( .clk (clk ), .rst_n (rst_n ), .din (clk_data_out), .start (start ), .mode (mode ), .addr (addr ), .wdata (wdata ), .rdata (rdata ), .rdata_vld(rdata_vld ), .rdy (rdy ), .dout (data_load ), .dout_vld (data_load_vld ) ); at93c46_mix u_at93c46_mix( .clk (clk ), .rst_n (rst_n ), .start (start ), .mode (mode ), .addr (addr ), .wdata (wdata ), .rdata (rdata ), .rdata_vld(rdata_vld), .rdy (rdy ), .do (mo ), .di (mi ), .cs (cs ), .sk (sk ) );endmodule

本工程会运用于不同的开拓板,紧张差异在于利用普通按键还是矩阵键盘,顶层代码中针对这一点进行了设计,如何开拓板利用的是矩阵键盘,则顶层代码不须要改,如果利用的是普通按键,只须要将顶层代码最上面的一行删除或者注释掉就可以了。

1.2 键盘(按键)扫描模块设计

1.2.1接口旗子暗记

下面为利用矩阵键盘时的接口旗子暗记:

下面是利用普通按键时的接口旗子暗记:

1.2.2 设计思路

在前面的按键掌握数字时钟的案例中已经有先容,以是这里不在过多先容,详细先容请看下方链接:

http://fpgabbs.com/forum.php?mod=viewthread&tid=310

1.2.3参考代码

modulekey_scan( clk , rst_n, key_col, key_row, key_en ); parameter KEY_W = 4 ; parameter CHK_COL= 0 ; parameter CHK_ROW= 1 ; parameter DELAY = 2 ; parameter WAIT_END = 3 ; parameter COL_CNT= 16 ; parameter TIME_20MS= 1000000; input clk ; input rst_n ; input [3:0] key_col ; output[3:0] key_en ; output[KEY_W-1:0] key_row ; reg [3:0] key_out ; reg [KEY_W-1:0] key_row ; reg key_vld ; reg [3:0] key_col_ff0 ; reg [3:0] key_col_ff1 ; reg [1:0] key_col_get ; reg [3:0] key_en ; wire end_shake_cnt ; reg end_shake_cnt_ff0; reg [3:0] state_c ; reg [19:0] shake_cnt ; reg [3:0] state_n ; reg [1:0] row_index ; reg [15:0] row_cnt ; wire col2row_start ; wire row2del_start ; wire del2wait_start ; wire wait2col_start ; wire add_row_cnt ; wire end_row_cnt ; wire add_shake_cnt ; wire add_row_index ; wire end_row_index ;always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_col_ff0 <= 4'b1111; key_col_ff1 <= 4'b1111; end else begin key_col_ff0 <= key_col ; key_col_ff1 <= key_col_ff0; endendalways @(posedge clk or negedge rst_n) begin if (rst_n==0) begin shake_cnt <= 0; end else if(add_shake_cnt) begin if(end_shake_cnt) shake_cnt <= 0; else shake_cnt <= shake_cnt+1 ; endendassign add_shake_cnt = key_col_ff1!=4'hf;assign end_shake_cnt = add_shake_cnt&& shake_cnt == TIME_20MS-1 ;always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin state_c <= CHK_COL; end else begin state_c <= state_n; endendalways@()begin case(state_c) CHK_COL: begin if(col2row_start )begin state_n = CHK_ROW; end else begin state_n = CHK_COL; end end CHK_ROW: begin if(row2del_start)begin state_n = DELAY; end else begin state_n = CHK_ROW; end end DELAY :begin if(del2wait_start)begin state_n = WAIT_END; end else begin state_n = DELAY; end end WAIT_END: begin if(wait2col_start)begin state_n = CHK_COL; end else begin state_n = WAIT_END; end end default: state_n = CHK_COL; endcaseendassign col2row_start = state_c==CHK_COL&& end_shake_cnt;assign row2del_start = state_c==CHK_ROW&& row_index==3 && end_row_cnt;assign del2wait_start= state_c==DELAY && end_row_cnt;assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_row <= 4'b0; end else if(state_c==CHK_ROW)begin key_row <= ~(1'b1 << row_index); end else begin key_row <= 4'b0; endendalways @(posedge clk or negedge rst_n) begin if (rst_n==0) begin row_index <= 0; end else if(add_row_index) begin if(end_row_index) row_index <= 0; else row_index <= row_index+1 ; end else if(state_c!=CHK_ROW)begin row_index <= 0; endendassign add_row_index = state_c==CHK_ROW && end_row_cnt;assign end_row_index = add_row_index&& row_index == 4-1 ;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin row_cnt <= 0; end else if(add_row_cnt) begin if(end_row_cnt) row_cnt <= 0; else row_cnt <= row_cnt+1 ; endendassign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;assign end_row_cnt = add_row_cnt&& row_cnt == 16-1 ;always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_col_get <= 0; end else if(state_c==CHK_COL && end_shake_cnt ) begin if(key_col_ff1==4'b1110) key_col_get <= 0; else if(key_col_ff1==4'b1101) key_col_get <= 1; else if(key_col_ff1==4'b1011) key_col_get <= 2; else key_col_get <= 3; endendalways@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_out <= 0; end else if(state_c==CHK_ROW && end_row_cnt)begin key_out <= {row_index,key_col_get}; end else begin key_out <= 0; endendalways@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin key_vld <= 1'b0; end else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin key_vld <= 1'b1; end else begin key_vld <= 1'b0; endendalways@()begin if(rst_n==1'b0)begin key_en = 0; end else if(key_vld && key_out==0)begin key_en = 4'b0001; end else if(key_vld && key_out==1)begin key_en = 4'b0010; end else if(key_vld && key_out==2)begin key_en = 4'b0100; end else begin key_en = 0; endend

1.3 韶光数据产生模块设计1.3.1接口旗子暗记

1.3.2设计思路

本模块相对付前面的按键掌握数字时钟案例中的韶光数据产生模块来说,总体的设计思路是相同的,只是增加了一个重载的时钟旗子暗记,对付此旗子暗记的设计也比较大略,只须要在时分秒的个位和十位计数器中增加一句:在重载的时钟数据有效的时候,使计数器输出重载的时钟对应的数据即可,比如秒个位计数器该当输出重载时钟数据的第0到第3位数据,秒十位计数器该当输出重载时钟数据的第4到第7位数据,以此类推。

其他详细的设计思路可以看一下往期按键掌握数字时钟的文章:

1.3.3参考代码

module clock_data( clk , rst_n , data_load , data_load_vld, key_en , data_out ); parameter TIME_1S = 50_000_000 ; input clk ; input rst_n ; input data_load_vld; input [23:0] data_load ; input [ 3:0] key_en ; output[23:0] data_out ; wire[23:0] data_out ; reg [25:0] cnt_1s ; reg [3:0] miao_ge ; reg [3:0] miao_shi ; reg [3:0] fen_ge ; reg [3:0] fen_shi ; reg [3:0] shi_ge ; reg [3:0] shi_shi ; reg [2:0] set_sel ; reg set_flag ; wire add_set_sel; wire add_cnt_1s ; wire add_miao_ge; wire add_miao_shi ; wire add_fen_ge ; wire add_fen_shi; wire add_shi_ge ; wire add_shi_shi; wire end_cnt_1s ; wire end_set_sel; wire end_miao_ge; wire end_miao_shi ; wire end_fen_ge ; wire end_fen_shi; wire end_shi_ge ; wire end_shi_shi; wire set_miao_ge; wire set_miao_shi ; wire set_fen_ge ; wire set_fen_shi; wire set_shi_ge ; wire set_shi_shi; reg[ 3:0] x ; reg[ 2:0] y ; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin set_flag <= 1'b0; end else if(key_en[0]) begin set_flag <= ~ set_flag; end end always @(posedge clk or negedge rst_n)begin if(!rst_n)begin set_sel <= 0; end else if(add_set_sel)begin if(end_set_sel) set_sel <= 0; else set_sel <= set_sel + 1; end else if(set_flag==0)begin set_sel <= 0; end end assign add_set_sel = set_flag && key_en[1]; assign end_set_sel = add_set_sel && set_sel==6-1 ; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_1s <= 0; end else if(add_cnt_1s)begin if(end_cnt_1s) cnt_1s <= 0; else cnt_1s <= cnt_1s + 1; end end assign add_cnt_1s = set_flag==0 ; assign end_cnt_1s = add_cnt_1s && cnt_1s==TIME_1S-1 ; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin miao_ge <= 0; end else if(add_miao_ge)begin if(end_miao_ge) miao_ge <= 0; else miao_ge <= miao_ge + 1; end else if(data_load_vld)begin miao_ge <=data_load[3:0]; end end assign add_miao_ge = (end_cnt_1s || set_miao_ge) ; assign end_miao_ge = add_miao_ge && miao_ge==10-1 ; assign set_miao_ge = set_flag && set_sel==0 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin miao_shi <= 0; end else if(add_miao_shi)begin if(end_miao_shi) miao_shi <= 0; else miao_shi <= miao_shi + 1; end else if(data_load_vld)begin miao_shi <= data_load[7:4]; end end assign add_miao_shi = (end_miao_ge || set_miao_shi); assign end_miao_shi = add_miao_shi && miao_shi==6-1; assign set_miao_shi = set_flag && set_sel==1 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin fen_ge <= 0; end else if(add_fen_ge)begin if(end_fen_ge) fen_ge <= 0; else fen_ge <= fen_ge + 1; end else if(data_load_vld)begin fen_ge <= data_load[11:8]; end end assign add_fen_ge = (end_miao_shi || set_fen_ge); assign end_fen_ge = add_fen_ge && fen_ge==10-1; assign set_fen_ge = set_flag && set_sel==2 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin fen_shi <= 0; end else if(add_fen_shi)begin if(end_fen_shi) fen_shi <= 0; else fen_shi <= fen_shi + 1; end else if(data_load_vld)begin fen_shi <= data_load[15:12]; end end assign add_fen_shi = (end_fen_ge || set_fen_shi); assign end_fen_shi = add_fen_shi && fen_shi==6-1; assign set_fen_shi = set_flag && set_sel==3 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin shi_ge <= 0; end else if(add_shi_ge)begin if(end_shi_ge) shi_ge <= 0; else shi_ge <= shi_ge + 1; end else if(data_load_vld)begin shi_ge <= data_load[19:16]; end end assign add_shi_ge = (end_fen_shi || set_shi_ge); assign end_shi_ge = add_shi_ge && shi_ge==x-1; assign set_shi_ge = set_flag && set_sel==4 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin shi_shi <= 0; end else if(add_shi_shi)begin if(end_shi_shi) shi_shi <= 0; else shi_shi <= shi_shi + 1; end else if(data_load_vld)begin shi_shi <= data_load[23:20]; end end assign add_shi_shi = (end_shi_ge || set_shi_shi); assign end_shi_shi = add_shi_shi && shi_shi==y-1; assign set_shi_shi = set_flag && set_sel==5 && key_en[2]; always@()begin if(shi_shi<2) x =10; else x = 4; end always@()begin if(set_flag && set_sel==5 && shi_ge>=4) y = 2; else y = 3; end assign data_out = {shi_shi,shi_ge,fen_shi,fen_ge,miao_shi,miao_ge};endmodule

1.4 数码管显示模块设计1.4.1接口旗子暗记

1.4.2设计思路

数码管显示在前面的案例文章已经有讲述,这里不再进行先容,想理解的可以看一下往期文章:

1.4.3参考代码

always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_2ms <= 0; end else if(add_cnt_2ms)begin if(end_cnt_2ms) cnt_2ms <= 0; else cnt_2ms <= cnt_2ms + 1; endendassign add_cnt_2ms = 1; assign end_cnt_2ms = add_cnt_2ms && cnt_2ms==TIME_2MS-1 ; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_sel <= 0; end else if(add_cnt_sel)begin if(end_cnt_sel) cnt_sel <= 0; else cnt_sel <= cnt_sel + 1; endendassign add_cnt_sel = end_cnt_2ms; assign end_cnt_sel = add_cnt_sel && cnt_sel== SEG_NUM-1; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel <= {SEG_NUM{1'b1}}; end else begin seg_sel <= ~(1'b1 << cnt_sel); endendalways@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_ff0 <= 0; end else begin for(ii=0;ii<SEG_NUM;ii=ii+1)begin if(din_vld[ii]==1'b1)begin din_ff0[(ii+1)4-1 -:4] <= din[(ii+1)4-1 -:4]; end else begin din_ff0[(ii+1)4-1 -:4] <= din_ff0[(ii+1)4-1 -:4]; end end endendalways@()begin seg_tmp = din_ff0[(cnt_sel+1)4-1 -:4]; endalways@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin segment<=NUM_0; end else begin case(seg_tmp) 4'd0:segment <= NUM_0; 4'd1:segment <= NUM_1; 4'd2:segment <= NUM_2; 4'd3:segment <= NUM_3; 4'd4:segment <= NUM_4; 4'd5:segment <= NUM_5; 4'd6:segment <= NUM_6; 4'd7:segment <= NUM_7; 4'd8:segment <= NUM_8; 4'd9:segment <= NUM_9; default:begin segment <= NUM_ERR; end endcase endendendmodul

由于篇幅限定,请连续看(二)。

标签:

相关文章

编程语言中的情话,代码之恋

在浩瀚的编程世界里,每一门语言都如同一位独具魅力的情人,以它们独特的语言风格,向开发者诉说着缠绵悱恣的爱意。从C语言的严谨,到Py...

互联网 2025-01-02 阅读0 评论0

编程语言优势,构建未来世界的基石

随着科技的飞速发展,编程语言已经成为我们构建未来世界的基石。从人工智能、大数据到物联网,编程语言在各个领域都发挥着至关重要的作用。...

互联网 2025-01-02 阅读0 评论0

编程语言千千万,哪一种最适合你

随着互联网技术的飞速发展,编程语言已经成为了现代社会不可或缺的一部分。从网页开发到大数据处理,从人工智能到物联网,编程语言的应用领...

互联网 2025-01-02 阅读0 评论0