數碼管的基本原理
關於數碼管,一個單個的數碼管可以看做是多個led燈的集合,如下圖所示
其中的8和。都是LED組成的,通過引腳上電即可點亮不同的LED然后組成不同的數字,這個過程在數碼管的設計中叫做段選。
在多個數碼管的情況下,需要選擇哪個數碼管點亮,這個在數碼管設計中稱作位選,多個數碼管可以通過位選和段選完成電子時鍾設計等功能。
下面通過項目對於多個數碼管進行點亮,讓其在開發板上顯示不同的數據。
預計實驗現象:
在quartus的in system source and probes editor 工具,輸入需要顯示在數碼管上的數據,則數碼管顯示對應數據。
相關知識點:
數碼管動態掃描的實現、in system source and probes editor調試工具的使用。
注意:這里重點講的是有位選和段選的連接方式的,友晶采用的是並口的連接方式的,不要看這里,直接看后面
設計過程:
1、數碼管動態掃描實現。
2、In system sources and probes edit (ISSP)調試工具的使用
3、4輸入查找表,6位輸出。
4、分頻模塊,從系統時鍾分頻得到1KHz的掃描時鍾
5、6選一多路選擇器,選擇為當前數碼管的位置。
驅動模塊邏輯電路圖:
下面就是照着邏輯電路圖來編寫程序了。
創建工程,添加文件
module segment(disp_data,rst_n,clk,en ,sel,seg); input clk;//50M input rst_n; input en; input [23:0]disp_data; output [5:0]sel;//位選(控制哪個數碼管亮) output reg [6:0]seg;//段選(控制數碼管顯示什么數據) //分頻器的代碼,這里為了完整,不做多個文件來寫模塊了 reg[14:0] diviter_cnt; //25000-1 reg clk_1k; reg [5:0]sel_r; reg [3:0]data_temp;//待顯示數據緩存 //生成一個分頻計數器計數 always@(posedge clk or negedge rst_n) if (!rst_n) diviter_cnt<=15'd0; else if (!en) diviter_cnt<=15'd0; else if (diviter_cnt==24999) diviter_cnt<=15'd0; else diviter_cnt<=diviter_cnt+1'b1; //1k掃描時鍾生成 always@(posedge clk or negedge rst_n) if (!rst_n) clk_1k<=1'b0; else if (diviter_cnt==24999) clk_1k<=~clk_1k; //大型設計中,這種產生分頻器的方法是不可以的 //位選移位寄存器 always@(posedge clk_1k or negedge rst_n) if (!rst_n) sel_r<=6'b000_001; else if(sel_r==6'b100_000) sel_r<=6'b000_001; else sel_r<=sel_r<<1; //設計一個6選一多路器 always@(*) case(sel_r) 6'b000_001:data_temp=disp_data[3:0]; 6'b000_010:data_temp = disp_data[7:4]; 6'b000_100:data_temp=disp_data[11:8]; 6'b001_000:data_temp=disp_data[15:12]; 6'b010_000:data_temp=disp_data[19:16]; 6'b100_000:data_temp=disp_data[23:20]; default data_temp<=4'b0000; endcase //譯碼器 always@(*) case (data_temp) 4'h0:seg=7'b1000000;//這里按數碼管碼表來 4'h1:seg=7'b1111001; 4'h2:seg=7'b0100100; 4'h3:seg=7'b0110000; 4'h4:seg=7'b0011001; 4'h5:seg=7'b0010010; 4'h6:seg=7'b0000010; 4'h7:seg=7'b1111000; 4'h8:seg=7'b0000000; 4'h9:seg=7'b0010000; 4'ha:seg=7'b0001000; 4'hb:seg=7'b0000011; 4'hc:seg=7'b1000110; 4'hd:seg=7'b0100001; 4'he:seg=7'b0000110; 4'hf:seg=7'b0001110; endcase //二選一多路器 assign sel=(en)?sel_r:6'b000_000; endmodule
編寫testbench文件來進行仿真
`timescale 1ns/1ns `define clk_period 20 module HXE_tb; reg Clk; //50M reg Rst_n; reg En; //數碼管顯示使能,1使能,0關閉 reg [31:0]disp_data; wire [7:0] sel;//數碼管位選(選擇當前要顯示的數碼管) wire [6:0] seg;//數碼管段選(當前要顯示的內容) HXE8 HXE8( .Clk(Clk), .Rst_n(Rst_n), .En(En), .disp_data(disp_data), .sel(sel), .seg(seg) ); initial Clk = 1; always#(`clk_period/2) Clk = ~Clk; initial begin Rst_n = 1'b0; En = 1; disp_data = 32'h12345678; #(`clk_period*20); Rst_n = 1; #(`clk_period*20); #20000000; disp_data = 32'h87654321; #20000000; disp_data = 32'h89abcdef; #20000000; $stop; end endmodule
點擊仿真運行,可以看到sel和seg的輸出與我們期望的是一樣的,即位選進行移位操作,段選顯示123456和abcdef。
一般都需要進行后仿,才能得到實際的工作時的數據波形,這里由於使用的是Cyclone V系列的芯片,而quartus 取消了對該系列的門級仿真,故而此次設計的后仿就不做了。但是我們這里發現,友晶的開發板的連接模式與下面這種常規的位選段選接法不同,其連接方式為並行接法,每個數碼管連接一個IO管腳,通過IO管腳的設置來決定數據的顯示,這里兩者的區別是位選的有無。
這里開始講解了兩者之間的區別,並結合友晶的開發板進行的設計
重新編寫segment_2程序
module segment_2(disp_data,rst_n,clk,en ,data_out); input clk;//50M input rst_n; input en; input [23:0]disp_data; output reg [41:0]data_out; reg[24:0]cnt;//定義計數寄存器 reg [3:0]data_temp;//待顯示數據緩存 reg[6:0] seg,seg1,seg2,seg3,seg4,seg5;//段選(控制數碼管顯示什么數據) reg [2:0]num; initial begin num<=3'b000; end //設定延時閃爍時間 always@(posedge clk or negedge rst_n) //設置500ms的延時 if(rst_n==1'b0) cnt<=25'd0; else if (cnt==25'd24_999_999)//(500_000_000/20) -1的結果。 cnt<=25'd0; else cnt<=cnt+1'b1; //運用狀態機進行disp_data的數據抽取 always@(posedge clk or negedge rst_n) if (rst_n==1'b0) begin num=0; data_temp=4'b1000; end else if (cnt==25'd24_999_999)//等待500ms后切換下一個數據 case (num) //按次數轉換 3'b000: begin data_temp=disp_data[3:0];num=num+1; end 3'b001:begin data_temp=disp_data[7:4] ; num=num+1;end 3'b010:begin data_temp=disp_data[11:8] ;num=num+1;end 3'b011:begin data_temp=disp_data[15:12] ; num=num+1;end 3'b100:begin data_temp=disp_data[19:16] ; num=num+1;end 3'b101:begin data_temp=disp_data[23:20] ; num=num+1;end default num=0; endcase //對抽取的數據進行譯碼 always@(*) begin case (data_temp) 4'h0:seg=7'b1000000;//這里按數碼管碼表來 4'h1:seg=7'b1111001; 4'h2:seg=7'b0100100; 4'h3:seg=7'b0110000; 4'h4:seg=7'b0011001; 4'h5:seg=7'b0010010; 4'h6:seg=7'b0000010; 4'h7:seg=7'b1111000; 4'h8:seg=7'b0000000; 4'h9:seg=7'b0010000; 4'ha:seg=7'b0001000; 4'hb:seg=7'b0000011; 4'hc:seg=7'b1000110; 4'hd:seg=7'b0100001; 4'he:seg=7'b0000110; 4'hf:seg=7'b0001110; endcase //完成顯示 data_out=({seg,seg,seg,seg,seg,seg,seg}); end endmodule
編寫testbench文件並設定路徑
`timescale 1ns/1ns `define clk_period 20 module segment_2_tb; reg clk;//50M reg rst_n; reg en; reg [23:0]disp_data; wire [41:0]data_out;//段選(控制數碼管顯示什么數據) segment_2 segment0(.disp_data(disp_data), .rst_n(rst_n), .clk(clk), .en (en), .data_out(data_out) ); initial clk = 1; always#(`clk_period/2) clk = ~clk; initial begin rst_n=1'b0; en=1; disp_data=24'h123456; #(`clk_period*20) rst_n=1; #(`clk_period*20) #20_000_000; disp_data<=24'habcdef; #20_000_000; $stop; end endmodule
仿真波形如圖
到了這一步就需要一個工具,即之前說到的In sysytem sources and probes editor (ISSP)
打開一個IP核
之后一直next知道finish,ctrl+o將產生的文件添加到工程中。將端口添加到頂層文件中
新建一個segment_top文件,將文件端口都鏈接進來
module segment_top(clk,rst_n,data_out); input clk;//50M input rst_n; wire [23:0]disp_data; output [41:0]data_out; segment_data segment_data0( .probe(), .source(disp_data)); segment_2 segment0(.disp_data(disp_data), .rst_n(rst_n), .clk(clk), .en (1'b1), .data_out(data_out)); endmodule
至此,工程修改完了,之后就是分配引腳
將.sof文件下載到開發板中
下載之后,開發板上的數碼管一直顯示00_00_00,這是因為沒有給它需要顯示的值。
現在在電腦上調用in system programer 工具
將數據格式改為16進制
在Data框中輸入希望顯示的數據,會發現數碼管上的數據會隨着輸入的數據的變化而變化,按下復位按鍵KEY0時,數碼管顯示全為8,松手后重頭開始計數。
到這里,數碼管的點亮的試驗的基本目標就已經完成了,顯示的基本功能與板上程序相似。