串口-时钟系列知识分享:
(1)串口通信实现-串口接收
(2)FPGA 驱动数码管动态显示
(3)使用串口发送实现ACX720开发板时钟显示
前言
本篇使用串口发送实现ACX720开发板时钟显示(Verilog&Vivado)。
1、 digital_clock顶层模块控制
用于用于实现整个模块控制,分为两个子模块1.hex_top(用于实现数码管数字显示) 2.uart_byte_rx(用于实现串口发送)3.自身用于实现时分秒的控制
digital_clock顶层模块控制输入、输出草图如下所示:
顶层控制模块代码如下:
`timescale 1ns / 1ps
module digital_clock(
clk,
reset_n,
sh_cp,
st_cp,
rx,
ds
);
input clk;
input reset_n;
input rx;
output sh_cp;
output st_cp;
output ds;
reg [31:0] display_data;
parameter second_cnt = 26'd50000000;
hex_top hex_top(
.clk(clk),
.reset_n(reset_n),
.sh_cp(sh_cp),
.st_cp(st_cp),
.ds(ds),
.disp_data(display_data)
);
wire [7:0] data;
wire rx_done;
uart_byte_rx uart_byte_rx_demo(
.clk(clk),
.reset(reset_n),
.baud_set(3'd4),
.uart_rx(rx),
.data_byte(data),
.rx_done(rx_done)
);
reg [1:0] set;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
set <= 0;
else if(set == 2'd3)
set<= 1'd0;
else if(rx_done)
set <= set+ 1'd1;
end
//-- counter == second_cnt - 1'b1计时1s钟------------------------------------------------------------------
reg [25:0] counter;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
counter <= 0;
else if(counter == second_cnt - 1'b1)
counter <= 0;
else
counter <= counter + 1'b1;
end
//-------------------------------------------------------------------------------------------------------
//--产生秒脉冲sec_flag------------------------------------------------------------------------------------------------
reg sec_flag;
always@(posedge clk or negedge reset_n)begin//产生秒脉冲
if(!reset_n)
sec_flag <= 1'b0;
else if(counter == second_cnt - 1'b1)
sec_flag <= 1'b1;
else
sec_flag <= 1'b0;
end
//----------------------------------------------------------------------------------------------------------
// --秒计数需要两个数码管故分为 second[7:4] 与 second[3:0]对两个数码管分别赋值
reg [7:0]second;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
second <= 8'd0;
else if((rx_done )&&(set== 2'd2))
second <= data;
else if( sec_flag)begin
if((second[7:4] == 4'd5)&&(second[3:0] == 4'd9))begin
second <=8'd0;
end
else if(second[3:0] == 4'd9)begin
second[7:4] <= second[7:4] + 1;
second[3:0] <= 4'd0;
end
else
second[3:0] <= second[3:0] + 1'b1;
end
end
//-------------------------------------------------------------------------------------------------------
reg [7:0]minute;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
minute <= 8'd0;
else if((rx_done )&&(set== 2'd1))
minute <= data;
else if(sec_flag)begin
if((second[7:4] == 4'd5)&&(second[3:0] == 4'd9))begin
if((minute[7:4]== 4'd5)&&(minute[3:0]== 4'd9))
minute <= 8'd0;
else if(minute[3:0] == 4'd9)begin
minute[7:4] <= minute[7:4] + 1'b1;
minute[3:0] <= 4'd0;
end
else
minute[3:0] <= minute[3:0] + 1'b1;
end
end
end
reg [7:0]hour;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
hour <= 8'd0;
else if((rx_done )&&(set== 2'd0))
hour <= data;
else if(sec_flag)begin
if((second[7:4] == 4'd5)&&(second[3:0] == 4'd9)&&(minute[7:4] == 4'd5)&&(minute[3:0] == 4'd9))begin
if((hour[7:4]==4'd2)&&(hour[3:0] == 4'd3))
hour <= 8'd0;
else if(hour[3:0] == 4'd9)begin
hour[3:0] <= 4'd0;
hour[7:4] <= hour[7:4] + 1'b1;
end
else
hour[3:0] <= hour[3:0] + 1'b1;
end
end
end
//--依次赋给display_data
always@(posedge clk or negedge reset_n)
if(!reset_n)
display_data <= 32'd0;
else if(sec_flag)
//display_data <= {8'h0,hour[7:4],hour[3:0],minute[7:4],minute[3:0],second[7:4],second[3:0]};
display_data <= {hour,4'hf,minute,4'hf,second};//4'f点亮为-,为了间隔开时分秒
endmodule
2、 串口发送模块
1.串口发送输入、输出草图如下:
串口发送模块代码如下:
`timescale 1ns / 1ps
module uart_byte_rx(
clk,
reset,
baud_set,
uart_rx,
data_byte,
rx_done
);
input clk; //模块全局时钟输入,50M
input reset; //复位信号输入,低有效
input [2:0]baud_set; //波特率设置
input uart_rx; //串口输入信号
output [7:0]data_byte; //串口接收的1byte数据
output rx_done; //1byte数据接收完成标志
reg [7:0]data_byte;
reg rx_done;
reg uart_rx_sync1; //同步寄存器
reg uart_rx_sync2; //同步寄存器
reg uart_rx_reg1; //数据寄存器
reg uart_rx_reg2; //数据寄存器
reg [15:0]bps_DR; //分频计数最大值
reg [15:0]div_cnt; //分频计数器
reg bps_clk; //波特率时钟
reg [7:0] bps_cnt; //波特率时钟计数器
reg uart_EN;//接收数据状态
wire uart_rx_nedge; //下降沿检测
reg [2:0]START_BIT;
reg [2:0]STOP_BIT;
reg [2:0]data_byte_pre [7:0];
//同步串行输入信号,消除亚稳态
always@(posedge clk or posedge reset)
if(!reset)begin
uart_rx_sync1 <= 1'b0;
uart_rx_sync2 <= 1'b0;
end
else begin
uart_rx_sync1 <= uart_rx;
uart_rx_sync2 <= uart_rx_sync1;
end
//数据寄存器
always@(posedge clk or posedge reset)
if(!reset)begin
uart_rx_reg1 <= 1'b0;
uart_rx_reg2 <= 1'b0;
end
else begin
uart_rx_reg1 <= uart_rx_sync2;
uart_rx_reg2 <= uart_rx_reg1;
end
//下降沿检测
assign uart_rx_nedge = !uart_rx_reg1 & uart_rx_reg2;
always@(posedge clk or posedge reset)
if(!reset)
bps_DR <= 16'd324;
else begin
case(baud_set)
0:bps_DR <= 16'd324;
1:bps_DR <= 16'd162;
2:bps_DR <= 16'd80;
3:bps_DR <= 16'd53;
4:bps_DR <= 16'd26;
default:bps_DR <= 16'd324;
endcase
end
always@(posedge clk or posedge reset)
if(!reset)
uart_EN <= 1'b0;
else if(uart_rx_nedge)
uart_EN <= 1'b1;
else if(rx_done || (bps_cnt == 8'd12 && (START_BIT > 2)) || (bps_cnt == 8'd155 && (STOP_BIT < 3)))
uart_EN <= 1'b0;
else
uart_EN <= uart_EN;
always@(posedge clk or posedge reset)
if(!reset)
div_cnt <= 16'd0;
else if(uart_EN)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
always@(posedge clk or posedge reset)
if(!reset)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
always@(posedge clk or posedge reset)
if(!reset)
bps_cnt <= 8'd0;
else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2)))
bps_cnt <= 8'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
always@(posedge clk or posedge reset)
if(!reset)
rx_done <= 1'b0;
else if(bps_cnt == 8'd159)
rx_done <= 1'b1;
else
rx_done <= 1'b0;
always@(posedge clk or posedge reset)
if(!reset)begin
START_BIT <= 3'd0;
data_byte_pre[0] <= 3'd0;
data_byte_pre[1] <= 3'd0;
data_byte_pre[2] <= 3'd0;
data_byte_pre[3] <= 3'd0;
data_byte_pre[4] <= 3'd0;
data_byte_pre[5] <= 3'd0;
data_byte_pre[6] <= 3'd0;
data_byte_pre[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
else if(bps_clk)begin
case(bps_cnt)
0:begin
START_BIT <= 3'd0;
data_byte_pre[0] <= 3'd0;
data_byte_pre[1] <= 3'd0;
data_byte_pre[2] <= 3'd0;
data_byte_pre[3] <= 3'd0;
data_byte_pre[4] <= 3'd0;
data_byte_pre[5] <= 3'd0;
data_byte_pre[6] <= 3'd0;
data_byte_pre[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
6 ,7 ,8 ,9 ,10,11:START_BIT <= START_BIT + uart_rx_sync2;
22,23,24,25,26,27:data_byte_pre[0] <= data_byte_pre[0] + uart_rx_sync2;
38,39,40,41,42,43:data_byte_pre[1] <= data_byte_pre[1] + uart_rx_sync2;
54,55,56,57,58,59:data_byte_pre[2] <= data_byte_pre[2] + uart_rx_sync2;
70,71,72,73,74,75:data_byte_pre[3] <= data_byte_pre[3] + uart_rx_sync2;
86,87,88,89,90,91:data_byte_pre[4] <= data_byte_pre[4] + uart_rx_sync2;
102,103,104,105,106,107:data_byte_pre[5] <= data_byte_pre[5] + uart_rx_sync2;
118,119,120,121,122,123:data_byte_pre[6] <= data_byte_pre[6] + uart_rx_sync2;
134,135,136,137,138,139:data_byte_pre[7] <= data_byte_pre[7] + uart_rx_sync2;
150,151,152,153,154,155:STOP_BIT <= STOP_BIT + uart_rx_sync2;
default:
begin
START_BIT <= START_BIT;
data_byte_pre[0] <= data_byte_pre[0];
data_byte_pre[1] <= data_byte_pre[1];
data_byte_pre[2] <= data_byte_pre[2];
data_byte_pre[3] <= data_byte_pre[3];
data_byte_pre[4] <= data_byte_pre[4];
data_byte_pre[5] <= data_byte_pre[5];
data_byte_pre[6] <= data_byte_pre[6];
data_byte_pre[7] <= data_byte_pre[7];
STOP_BIT <= STOP_BIT;
end
endcase
end
always@(posedge clk or posedge reset)
if(!reset)
data_byte <= 8'd0;
else if(bps_cnt == 8'd159)begin
data_byte[0] <= data_byte_pre[0][2];
data_byte[1] <= data_byte_pre[1][2];
data_byte[2] <= data_byte_pre[2][2];
data_byte[3] <= data_byte_pre[3][2];
data_byte[4] <= data_byte_pre[4][2];
data_byte[5] <= data_byte_pre[5][2];
data_byte[6] <= data_byte_pre[6][2];
data_byte[7] <= data_byte_pre[7][2];
end
endmodule
3、 hex_top
用于实现数码管显示其包含两个子模块1.hex_display数码管显示2.hex_hc595数码管引脚转换
其输入、输出草图如下:
其代码展示如下:
`timescale 1ns / 1ps
module hex_top(
clk,
reset_n,
sh_cp,
st_cp,
ds,
disp_data
);
input clk;
input reset_n;
input [31:0]disp_data;
output sh_cp;
output st_cp;
output ds;
wire[7:0] sel;
wire[7:0] seg;
wire [15:0] data;
assign data = {seg,sel};//先传段选再传位选
hex_display hex_display(
.clk(clk),
.reset_n(reset_n),
.disp_data(disp_data),
.sel(sel),
.seg(seg)
);
hex_hc595 hc595(
.clk(clk),
.reset_n(reset_n),
.data(data),
.sh_cp(sh_cp),
.st_cp(st_cp),
.en(1'b1),
.ds(ds)
);
endmodule
4、 hex_display模块
用于实现数码管显示其输入、输出草图如下:
代码展示如下:
`timescale 1ns / 1ps
module hex_display(
clk,
reset_n,
disp_data,
sel,
seg
);
input clk;
input reset_n;
input [31:0] disp_data;//要现实的数据一共对应八位数码管的八位
output reg [7:0] sel;//位选输出
output reg [7:0] seg;//译码输出 a-h : seg[0]-seg[7]
reg clk_div; //计时1ms时间产生EN信号
reg [14:0]div_cnt; //计时1ms时间
reg [2:0]num_cnt; //产生8个数据进行赋值操作
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
div_cnt <= 15'd0;
else if(div_cnt == 24999)
div_cnt <= 15'd0;
else
div_cnt <= div_cnt + 1'd1;
end
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
clk_div <= 1;
else if(div_cnt == 24999)
clk_div <= 1'b1;
else
clk_div <= 0;
end
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
num_cnt <= 0;
else if(clk_div)
num_cnt <= num_cnt + 1;
end
//--每隔1ms进行赋值操作 sel控制8个数码管的开关-----------------------------------------------------------
always@(*)begin
case(num_cnt)
0: sel <= 8'b00000001;
1: sel <= 8'b00000010;
2: sel <= 8'b00000100;
3: sel <= 8'b00001000;
4: sel <= 8'b00010000;
5: sel <= 8'b00100000;
6: sel <= 8'b01000000;
7: sel <= 8'b10000000;
endcase
end
//--------------------------------------------------------------------------------------------------------
//--每个数码管想要输出的数值------------------------------------------------------------------------------
reg [3:0]disp_temp;
always@(*)begin
case(num_cnt)
0: disp_temp <= disp_data[3:0];
1: disp_temp <= disp_data[7:4];
2: disp_temp <= disp_data[11:8];
3: disp_temp <= disp_data[15:12];
4: disp_temp <= disp_data[19:16];
5: disp_temp <= disp_data[23:20];
6: disp_temp <= disp_data[27:24];
7: disp_temp <= disp_data[31:28];
endcase
end
//--------------------------------------------------------------------------------------------------------
//--查找表控制数码管输出-----------------------------------------------------------------------------------
//--ACX720FPGA板为共阳极
//--当disp_data[31:28]==0时数码管a、b、c、d、e、f为低电平 g、dp为高电平
//--seg为8’b11000000
always@(*)begin
case(disp_temp)
4'h0: seg = 8'hc0;
4'h1: seg = 8'hf9;
4'h2: seg = 8'ha4;
4'h3: seg = 8'hb0;
4'h4: seg = 8'h99;
4'h5: seg = 8'h92;
4'h6: seg = 8'h82;
4'h7: seg = 8'hf8;
4'h8: seg = 8'h80;
4'h9: seg = 8'h90;
4'hf : seg = 8'hbf; //此处显示不是f而是-,为了上板区分时分秒
default:seg = 8'hc0;
endcase
end
//----------------------------------------------------------------------------------------------------------
endmodule
5、 hex_hc595模块
ACX720开发板使用HC595芯片作为驱动,故无法分配给sel、seg引脚于是使用hex_hc595模块转换
其中[15:0]data = {[seg,sel]}其输入输出草图如下:
hex_hc595模块输出波形图如下:
hex_hc595模块代码展示如下:
`timescale 1ns / 1ps
module hex_hc595(
clk,
reset_n,
data,
sh_cp,
st_cp,
en,
ds
);
input clk;
input reset_n;
input [15:0] data;
input en;
output reg sh_cp; //HC595输出波形图中的时钟信号12.5MHZ(3.3V)
output reg st_cp;
output reg ds;
reg [5:0] shcp_edge_cnt;
reg [15:0] r_data;
always@(posedge clk)begin
if(en)
r_data <= data;
end
reg[2:0] div_cnt;
//---产生时钟分频25MHz--------------------------------------------------------------------------------------------------------------------------------------
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
div_cnt <= 0;
else if(div_cnt == 1'b1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'b1;
end
wire sck;
assign sck = (div_cnt == 1'b1);
//------------------------------------------------------------------------------------------------------------------------------------------------------------
//--以div_cnt == 1'b1为节点产生32个shcp_edge_cnt 用于赋值------------------------------------------------------------------------------------------------------
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
shcp_edge_cnt <= 0;
else if(sck)begin
if(shcp_edge_cnt == 6'd32)
shcp_edge_cnt <= 0;
else
shcp_edge_cnt <= shcp_edge_cnt + 1'b1;
end
else
shcp_edge_cnt <= shcp_edge_cnt;
end
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
//按HC595芯片输出波形图----------------------------------------------------------------------------------------------------------------------------------------------------------
always@(posedge clk or negedge reset_n)begin
if(!reset_n)begin
sh_cp <= 1'b0;
st_cp <= 1'b0;
ds <= 1'b0;
end
else begin
case(shcp_edge_cnt)
0:begin sh_cp <= 1'd0; ds <= r_data[15];end
1:begin sh_cp <= 1'd1;st_cp <= 1'd0;end
2:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[14];end
3:sh_cp <= 1'd1;
4:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[13];end
5:sh_cp <= 1'd1;
6:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[12];end
7:sh_cp <= 1'd1;
8:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[11];end
9:sh_cp <= 1'd1;
10:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[10];end
11:sh_cp <= 1'd1;
12:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[9];end
13:sh_cp <= 1'd1;
14:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[8];end
15:sh_cp <= 1'd1;
16:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[7];end
17:sh_cp <= 1'd1;
18:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[6];end
19:sh_cp <= 1'd1;
20:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[5];end
21:sh_cp <= 1'd1;
22:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[4];end
23:sh_cp <= 1'd1;
24:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[3];end
25:sh_cp <= 1'd1;
26:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[2];end
27:sh_cp <= 1'd1;
28:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[1];end
29:sh_cp <= 1'd1;
30:begin sh_cp <= 1'd0; st_cp <= 1'd0;ds <= r_data[0];end
31:sh_cp <= 1'd1;
32:st_cp <= 1'd1;
default : begin sh_cp <= 1'b0;st_cp <= 1'b0;ds <= 1'b0;end
endcase
end
end
//-------------------------------------------------------------------------------------------------------------------------------------------------------------
endmodule
6、 TB文件展示
`timescale 1ns / 1ps
module time_clock_tb( );
reg clk;
reg reset_n;
wire sh_cp;
wire st_cp;
reg rx;
wire ds;
digital_clock digital_clock(
.clk(clk ),
.reset_n(reset_n),
.sh_cp(sh_cp),
.st_cp(st_cp),
.rx(rx),
.ds(ds)
);
initial clk = 1;
always#10 clk = ~clk;
initial begin
reset_n = 0;
rx = 1;
#201;
reset_n= 1;
#200;
uart_tx_byte(8'h01); //调用task
#90000;
uart_tx_byte(8'h30);
#90000;
uart_tx_byte(8'h59);
#90000;
$stop;
end
task uart_tx_byte; //创建任务
input [7:0]tx_data;
begin
rx= 1;
#20;
rx = 0;
#8680; //一位的发送时间
rx = tx_data[0];
#8680;
rx = tx_data[1];
#8680;
rx = tx_data[2];
#8680;
rx = tx_data[3];
#8680;
rx = tx_data[4];
#8680;
rx = tx_data[5];
#8680;
rx = tx_data[6];
#8680;
rx = tx_data[7];
#8680;
rx = 1;
#8680;
end
endtask
endmodule
【附件:】链接:https://pan.baidu.com/s/1gK-0TX7tbdopJGFE4XbNAA?pwd=vgax
提取码:vgax