image.png

一、前置知识

上一篇已经了解了数码管静态显示的过程,即位选部分全部选中即可。数码管动态显示是利用人眼的视觉暂留效应以及数码管中二极管的余晖效应实现的显示效果,可以控制一组数码管显示不同的数字。
当每位数码管的点亮时间为 1~2ms 时,显示效果能满足使用需要。数码管的动态驱动实际上就是分时轮流控制不同数码管的显示。

二、代码流程分析

要实现数码管的动态显示,实现实验预期效果,需要以下模块:

  1. 数码管动态显示模块:用来控制数码管动态显示。
  2. 数据计数控制模块:用来产生数码管显示的具体数据,实验中要求是从0增加到999999。

image.png
以上是正点原子给出的模块及信号连接图,各信号作用如下:

信号名 功能
sys_clk / clk 系统时钟
sys_rst_n / rst_n 系统复位
data[19:0] 用来表示具体显示的数值0~999999,占20位
point[5:0] 控制六位数码管各位的小数点dp位
sign 控制数码管显示负号
en 数码管显示使能控制

三、代码编写

其中point和sign这两个端口在本次实验中没有用到,所以我先不考虑这两个端口的使用,直接让他们保持输出0。
首先编写数据计数控制模块counter.v,该模块的功能就是每隔100ms改变一次数据data,使data加1

  1. // 产生供数码管显示的数据
  2. module counter (
  3. input sys_clk ,
  4. input sys_rst_n ,
  5. output reg [19:0] data , // 0~999999
  6. output reg [ 5:0] point ,
  7. output reg sign ,
  8. output reg en
  9. );
  10. parameter DATA_CNT = 23'd5_000_000; // 100ms
  11. // 每隔100ms数据data加1
  12. reg [22:0] data_cnt ;
  13. reg data_flag ;
  14. // 计数器在0~DATA_CNT自增,增满后产生data_flag脉冲信号
  15. always @(posedge sys_clk or negedge sys_rst_n) begin
  16. if(!sys_rst_n) begin
  17. data_cnt <= 23'd0;
  18. data_flag <= 1'b0;
  19. end
  20. else if(data_cnt < DATA_CNT - 1'b1) begin
  21. data_cnt <= data_cnt + 1'b1;
  22. data_flag <= 1'b0;
  23. end
  24. else begin
  25. data_cnt <= 23'b0;
  26. data_flag <= 1'b1;
  27. end
  28. end
  29. // 当data_flag脉冲到来时,data从0~999999自增;point和sign始终为0
  30. always @(posedge sys_clk or negedge sys_rst_n) begin
  31. if(!sys_rst_n) begin
  32. data <= 20'd0;
  33. point <= 6'd0 ;
  34. sign <= 1'b0 ;
  35. en <= 1'b0 ;
  36. end
  37. else begin
  38. en <= 1'b1;
  39. if(data_flag) begin
  40. if(data == 20'd999_999)
  41. data <= 1'b0;
  42. else
  43. data <= data + 1'b1;
  44. end
  45. else
  46. data <= data;
  47. end
  48. end
  49. endmodule //counter

以上产生数据分过程还是比较简单的,接下来编写数码管动态显示模块,用来处理数据并输出段选位选信号。
数码管显示模块编写思路:

  1. 首先把data数据有效位解析出来,第0到第6位,十进制格式。
  2. 然后可以用一个分频操作,把降频后的时钟作为数码管的显示时钟。
  3. 然后把从data中解析出来的数据寄存到一个大数组num中,此时num中保存的是显示数据的十进制格式、是否有负号、以及是否有小数点,合成一个4*6位的num信号。
  4. 处理这个num信号,把相应的6段译码成数码管的段码,也就是输出段选信号。
  5. 用分频后的时钟计时1ms,每隔1ms切换一次位选信号,需要刷新6段,相当于每6ms完整刷新一次数码管。 ```verilog module seg_led_synamic ( input sys_clk , input sys_rst_n , // Data interface input [19:0] data , input [ 5:0] point , input sign , input en , // User interface output reg [ 5:0] seg_scl , output reg [ 7:0] seg_deg
    );

parameter CLK_DIVICE = 4’d10; // 分频系数 parameter DATA_EMPTY = 4’d10; // 数据空占位 parameter DATA_MINUS = 4’d11; // 负号位占位 parameter DATA_ERROR = 24’haaaaaa; // 数据有误 parameter SEL_CNT = 13’d5000; // 位选倒计时

reg [ 2:0] divice_cnt ; // 分频计数器 reg clk_div ; // 分频时钟信号 reg [23:0] num_temp ; // 寄存十进制的显示数据 reg [12:0] scl_cnt ; // 位选倒计时0~5000 reg scl_flag ; // 位选切换允许标志 reg [ 2:0] scl_select ; // 位选切换的值 reg [ 3:0] seg_deg_temp; // 寄存num_temp相应的位段 reg point_temp ; // 寄存小数点的值

// 将data数据有效位提取出来,6位,每位0~9 wire [ 3:0] data0; // 十进制数据最低位 wire [ 3:0] data1; wire [ 3:0] data2; wire [ 3:0] data3; wire [ 3:0] data4; wire [ 3:0] data5; // 十进制数据最高位

assign data0 = data % 4’d10; assign data1 = data % 7’d100 / 4’d10; assign data2 = data % 10’d1000 / 7’d100; assign data3 = data / 10’d1000 % 4’d10; assign data4 = data / 14’d10000 % 4’d10; assign data5 = data / 17’d100000;

// 进行分频操作,频率 always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin divice_cnt <= 3’d0; clk_div <= 1’b0; end else if(sys_clk) begin if(divice_cnt == CLK_DIVICE/2’d2 - 1’d1) begin clk_div <= ~clk_div; divice_cnt <= 3’d0; end else begin divice_cnt <= divice_cnt + 1’b1; divice_cnt <= divice_cnt; end end end

// 处理数据 always @(posedge clk_div or negedge sys_rst_n) begin if(!sys_rst_n) begin num_temp <= 24’d0; end else if(en) begin if(data5 || point[5]) begin num_temp <= {data5, data4, data3, data2, data1, data0}; end else if(data4 || point[4]) begin if(sign) num_temp <= {DATA_MINUS, data4, data3, data2, data1, data0}; else num_temp <= {DATA_EMPTY, data4, data3, data2, data1, data0}; end else if(data3 || point[3]) begin if(sign) num_temp <= {DATA_EMPTY, DATA_MINUS, data3, data2, data1, data0}; else num_temp <= {{2{DATA_EMPTY}}, data3, data2, data1, data0}; end else if(data2 || point[2]) begin if(sign) num_temp <= {{2{DATA_EMPTY}}, DATA_MINUS, data2, data1, data0}; else num_temp <= {{3{DATA_EMPTY}}, data2, data1, data0}; end else if(data1 || point[1]) begin if(sign) num_temp <= {{3{DATA_EMPTY}}, DATA_MINUS, data1, data0}; else num_temp <= {{4{DATA_EMPTY}}, data1, data0}; end else if(data0 || point[0]) begin if(sign) num_temp <= {{4{DATA_EMPTY}}, DATA_MINUS, data0}; else num_temp <= {{5{DATA_EMPTY}}, data0}; end else begin num_temp <= DATA_ERROR; end end else begin num_temp <= num_temp; end end

// 先进行位选控制 always @(posedge clk_div or negedge sys_rst_n) begin if(!sys_rst_n) begin scl_cnt <= 13’d0; scl_flag <= 1’b0; end else if(scl_cnt < SEL_CNT - 1’b1) begin scl_cnt <= scl_cnt + 1’b1; scl_flag <= 1’b0; end else begin scl_cnt <= 13’d0; scl_flag <= 1’b1; end end

// 位选切换 always @(posedge clk_div or negedge sys_rst_n) begin if(!sys_rst_n) begin scl_select <= 3’d0; end else begin if(scl_flag) begin if(scl_select == 5) begin scl_select <= 3’d0; end else begin scl_select <= scl_select + 1’b1; end end else scl_select <= scl_select; end end

// 控制位选变量映射成位选值,seg_deg_temp寄存十进制数据 always @(posedge clk_div or negedge sys_rst_n) begin if(!sys_rst_n) begin seg_scl <= 6’b111_111; seg_deg_temp <= 4’ha; point_temp <= 1’b1; end else begin case(scl_select) // seg_deg_temp寄存num_temp指定位置十进制的数据 3’d0: begin seg_scl <= 6’b111110; seg_deg_temp <= num_temp[ 3: 0]; point_temp = ~point[0]; end 3’d1: begin seg_scl <= 6’b111101; seg_deg_temp <= num_temp[ 7: 4]; point_temp = ~point[1]; end 3’d2: begin seg_scl <= 6’b111011; seg_deg_temp <= num_temp[11: 8]; point_temp = ~point[2]; end 3’d3: begin seg_scl <= 6’b110111; seg_deg_temp <= num_temp[15:12]; point_temp = ~point[3]; end 3’d4: begin seg_scl <= 6’b101111; seg_deg_temp <= num_temp[19:16]; point_temp = ~point[4]; end 3’d5: begin seg_scl <= 6’b011111; seg_deg_temp <= num_temp[23:20]; point_temp = ~point[5]; end default: ; endcase end end

// seg_deg_temp译码,段选 always @(posedge clk_div or negedge sys_rst_n) begin if(!sys_rst_n) begin seg_deg <= 8’b1111_1111; end else begin case(seg_deg_temp) 4’d0 : seg_deg <= {point_temp, 7’b1_000_000}; 4’d1 : seg_deg <= {point_temp, 7’b1_111_001}; 4’d2 : seg_deg <= {point_temp, 7’b0_100_100}; 4’d3 : seg_deg <= {point_temp, 7’b0_110_000}; 4’d4 : seg_deg <= {point_temp, 7’b0_011_001}; 4’d5 : seg_deg <= {point_temp, 7’b0_010_010}; 4’d6 : seg_deg <= {point_temp, 7’b0_000_010}; 4’d7 : seg_deg <= {point_temp, 7’b1_111_000}; 4’d8 : seg_deg <= {point_temp, 7’b0_000_000}; 4’d9 : seg_deg <= {point_temp, 7’b0_010_000}; 4’ha : seg_deg <= 8’b00000110; // 数据有误,显示E DATA_EMPTY : seg_deg <= 8’b1111_1111; // 不显示 DATA_MINUS : seg_deg <= 8’b1011_1111; // 负号 default: seg_deg <= 8’b1111_1111; endcase end end

endmodule //seg_led_synamic

  1. 最后的顶层文件只需要例化两个模块。
  2. ```verilog
  3. module seg_led_synamic_top (
  4. input sys_clk ,
  5. input sys_rst_n ,
  6. // User interface
  7. output [ 5:0] seg_scl ,
  8. output [ 7:0] seg_deg
  9. );
  10. wire [19:0] data ;
  11. wire [ 5:0] point;
  12. wire sign ;
  13. wire en ;
  14. counter u_counter (
  15. .sys_clk (sys_clk ),
  16. .sys_rst_n (sys_rst_n),
  17. .data (data ),
  18. .point (point ),
  19. .sign (sign ),
  20. .en (en )
  21. );
  22. seg_led_synamic u_seg_led_synamic (
  23. .sys_clk (sys_clk ),
  24. .sys_rst_n (sys_rst_n),
  25. .data (data ),
  26. .point (point ),
  27. .sign (sign ),
  28. .en (en ),
  29. .seg_scl (seg_scl ),
  30. .seg_deg (seg_deg )
  31. );
  32. endmodule //seg_led_synamic_top

TCL文件与静态显示一致:

  1. package require ::quartus::project
  2. #system clock:50Mhz
  3. set_location_assignment PIN_M2 -to sys_clk
  4. #system reset
  5. set_location_assignment PIN_M1 -to sys_rst_n
  6. #digit display
  7. set_location_assignment PIN_N16 -to seg_scl[0]
  8. set_location_assignment PIN_N15 -to seg_scl[1]
  9. set_location_assignment PIN_P16 -to seg_scl[2]
  10. set_location_assignment PIN_P15 -to seg_scl[3]
  11. set_location_assignment PIN_R16 -to seg_scl[4]
  12. set_location_assignment PIN_T15 -to seg_scl[5]
  13. set_location_assignment PIN_M11 -to seg_deg[0]
  14. set_location_assignment PIN_N12 -to seg_deg[1]
  15. set_location_assignment PIN_C9 -to seg_deg[2]
  16. set_location_assignment PIN_N13 -to seg_deg[3]
  17. set_location_assignment PIN_M10 -to seg_deg[4]
  18. set_location_assignment PIN_N11 -to seg_deg[5]
  19. set_location_assignment PIN_P11 -to seg_deg[6]
  20. set_location_assignment PIN_D9 -to seg_deg[7]

四、测试

烧录了一下,发现数码管什么都不显示。
检查了一下代码,发现是在数码管动态显示模块里,分频那部分代码有问题,应该是让分频时钟信号保持的。
image.png
修改一下重新编译烧录: 07_deg_led_dynamic_error.mp4 (6.56MB)前面本应该显示空字符的数码管却显示了数字E,再检查一下代码:
在处理数据的always块里,当没有数据的时候,会默认给num_temp一个ERROR的赋值,译码出来之后就是显示E字符。这里我把最后一个else if(data0)的情况直接用else处理,不再用ERROR赋值了。改完之后再试一下,发信啊不是这里的问题,应该把复位之后seg_deg_temp的初始值改一下,我改成了空值:
image.png
修改之后再次运行代码: 07_deg_led_dynamic.mp4 (10.5MB)从0加到999999,每100ms变一次数据,需要 27:46:39.89 (时分秒.毫秒),很长时间。

五、代码

GitHub:https://github.com/ZHJ0125/FPGA_Learn/tree/main/Projects/07_seg_led_dynamic