文章目录
前言
在之前的文章中,我已经分别做过一次RS232的数据接收和数据发送了,本次用更容易理解的方式重新编写两种代码以及进行数据回环实验。以及学习使用In-System Sources & Probes Editor工具和串口调试工具进行调试。
一、串口接收模块
注:此图片摘自《FPGA Verilog开发指南》
依照波形图写代码会使设计事半功倍。
1.波形分析
- rx_reg1、rx_reg2:消除亚稳态,同步时钟。
- rx_reg3:与rx_reg2配合,产生下降沿信号判断起始位。
- start_nedge:依照波形图可以看出起始位下降沿的判断位置。
- work_en:使能信号
- baud_cnt:波特率计数器,在使能信号有效时开始计数。
- bit_flag:接收1bit数据标志信号,在波特率计数达到中点时刻时产生一个脉冲信号,达到控制数据接收的目的。
- bit_cnt:接收数据计数器,一帧数据有八个数据位,计时从0-8。
- rx_data:将接收的数据经过移位由串转并。
- rx_flag:移位完成标志。
2.RTL代码
module uart_rx
#(
parameter BAUD_CNT_MAX=13'd5207 //9600bps
)
(
input wire clk,
input wire rst_n,
input wire rx,
output reg [7:0]po_data,
output reg po_flag
);
reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg start_nedge;
reg work_en;
reg [12:0]baud_cnt;
reg bit_flag;
reg [3:0]bit_cnt;
reg [7:0]rx_data;
reg rx_flag;
//输入同步,消除亚稳态,并为产生下降沿信号做延迟
always @(posedge clk or negedge rst_n)
if(!rst_n)
rx_reg1<=1'b0;
else
rx_reg1<=rx;
always @(posedge clk or negedge rst_n)
if(!rst_n)
rx_reg2<=1'b0;
else
rx_reg2<=rx_reg1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
rx_reg3<=1'b0;
else
rx_reg3<=rx_reg2;
//检测到起始位低电平信号
always @(posedge clk or negedge rst_n)
if(!rst_n)
start_nedge<=1'b0;
else if(~rx_reg2 && rx_reg3)
start_nedge<=1'b1;
else
start_nedge<=1'b0;
//接收使能信号,检测到起始位后开始置1,检测到接收八位信号后结束,置0
always @(posedge clk or negedge rst_n)
if(!rst_n)
work_en<=1'b0;
else if(start_nedge)
work_en<=1'b1;
else if((bit_cnt==4'd8) && bit_flag)
work_en<=1'b0;
//波特率计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
baud_cnt<=1'b0;
else if((baud_cnt==BAUD_CNT_MAX-1) || (!work_en))
baud_cnt<=1'b0;
else if(work_en)
baud_cnt<=baud_cnt+1'b1;
//接收标志信号
always @(posedge clk or negedge rst_n)
if(!rst_n)
bit_flag<=1'b0;
else if(baud_cnt==BAUD_CNT_MAX/2-1)
bit_flag<=1'b1;
else
bit_flag<=1'b0;
//接收数据计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
bit_cnt<=1'b0;
else if((bit_cnt==4'd8) && bit_flag)
bit_cnt<=1'b0;
else if(bit_flag)
bit_cnt<=bit_cnt+1'b1;
//串行数据转并行数据,移位操作
always @(posedge clk or negedge rst_n)
if(!rst_n)
rx_data<=1'b0;
else if(bit_flag && (bit_cnt>=4'd1) && (bit_cnt<=4'd8))
rx_data<={rx_reg3,rx_data[7:1]};
//移位完成标志
always @(posedge clk or negedge rst_n)
if(!rst_n)
rx_flag<=1'b0;
else if((bit_cnt==4'd8) && bit_flag)
rx_flag<=1'b1;
else
rx_flag<=1'b0;
//rx_data不能直接输出,通过寄存器之后由po_data输出
always @(posedge clk or negedge rst_n)
if(!rst_n)
po_data<=1'b0;
else if(rx_flag)
po_data<=rx_data;
//rx_flag不能直接输出,通过寄存器之后由po_flag输出
always @(posedge clk or negedge rst_n)
if(!rst_n)
po_flag<=1'b0;
else
po_flag<=rx_flag;
endmodule
3.仿真测试模块
`timescale 1ns/1ns
`define clk_period 20
module uart_rx_tb;
reg clk;
reg rst_n;
reg rx;
wire [7:0]po_data;
wire po_flag;
uart_rx
#(
.BAUD_CNT_MAX(13'd100)
)
uart_rx0(
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.po_data(po_data),
.po_flag(po_flag)
);
initial clk=1'b1;
always #(`clk_period/2) clk=~clk;
initial begin
rst_n=1'b0;
rx=1'b0;
#(`clk_period*2)
rst_n=1'b1;
end
initial begin
#200
rx_bit(8'd0);
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd3);
rx_bit(8'd4);
rx_bit(8'd5);
rx_bit(8'd6);
rx_bit(8'd7);
end
task rx_bit(
input [7:0]data
);
integer i;
for(i=0;i<10;i=i+1)begin
case(i)
0:rx<=1'b0;
1:rx<=data[0];
2:rx<=data[1];
3:rx<=data[2];
4:rx<=data[3];
5:rx<=data[4];
6:rx<=data[5];
7:rx<=data[6];
8:rx<=data[7];
9:rx<=1'b1;
endcase
#(101*20);
end
endtask
endmodule
仿真波形图:
总体波形图^
单bit数据接收^
3.使用In-System Sources & Probes IP核进行上板验证
在IP核中找到In-System Sources & Probes,在配置页面只需改变如下两格即可。
引入IP核代码:
module uart_rx_top(clk,rst_n,rx);
input clk;
input rst_n;
input rx;
wire [7:0]po_data;
wire po_flag;
uart_rx uart_rx(
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.po_data(po_data),
.po_flag(po_flag)
);
issp issp(
.probe(po_data)
);
endmodule
自行配置引脚,进行全编译(注意设置顶层文件),下载到板子上,打开串口调试工具和In-System Sources & Probes Editor
打开In-System Sources & Probes Editor后,调整配置,进入接收功能
在串口调式工具中发送十六进制数据,可以看到In-System Sources & Probes Editor中接收到了数据。
二、串口发送模块
注:此图片摘自《FPGA Verilog开发指南》
依照波形图写代码会使设计事半功倍。
1.波形分析
- pi_data是输入端口输入的并行数据,串口发送模块需要把并行数据变为串行数据发送出去
- bit_flag:发送数据标志信号,在波特率计数器的任意时刻都可以,但不能设置成0和终止位置,这样会在数据跳变时产生不稳定信号
- bit_cnt:需要发送的数据共十位,故计数从0-9
- tx:用case语句进行发送,在第一位和第十位分别发送0和1
2.RTL代码:
module uart_tx
#(
parameter BAUD_CNT_MAX=13'd5207 //9600bps
)
(
input wire clk,
input wire rst_n,
input wire [7:0]pi_data,
input wire pi_flag,
output reg tx
);
reg [12:0]baud_cnt;
reg work_en;
reg bit_flag;
reg [3:0]bit_cnt;
//工作使能信号,数据可用信号pi_flag为有效时,使能有效,当发送计数器记满9并且发送标志信号有效时清零
always @(posedge clk or negedge rst_n)
if(!rst_n)
work_en<=1'b0;
else if(pi_flag)
work_en<=1'b1;
else if((bit_cnt==4'd9) && bit_flag)
work_en<=1'b0;
//波特率计数器,记满或使能信号无效时清零
always @(posedge clk or negedge rst_n)
if(!rst_n)
baud_cnt<=1'b0;
else if(baud_cnt==BAUD_CNT_MAX-1 || (!work_en))
baud_cnt<=1'b0;
else if(work_en)
baud_cnt<=baud_cnt+1'b1;
//发送标志信号,当波特率计数器开始计数时,就可以开始发送1bit数据,但不要使用0和最后一个计数时刻
always @(posedge clk or negedge rst_n)
if(!rst_n)
bit_flag<=1'b0;
else if(baud_cnt==13'd1)
bit_flag<=1'b1;
else
bit_flag<=1'b0;
//发送数据计时器,
always @(posedge clk or negedge rst_n)
if(!rst_n)
bit_cnt<=1'b0;
else if(bit_cnt==4'd9 && bit_flag)
bit_cnt<=1'b0;
else if(bit_flag && work_en)
bit_cnt<=bit_cnt+1'b1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
tx<=1'b0;
else if(bit_flag)
case(bit_cnt)
0:tx<=1'b0;
1:tx<=pi_data[0];
2:tx<=pi_data[1];
3:tx<=pi_data[2];
4:tx<=pi_data[3];
5:tx<=pi_data[4];
6:tx<=pi_data[5];
7:tx<=pi_data[6];
8:tx<=pi_data[7];
9:tx<=1'b1;
default:tx<=1'b1;
endcase
endmodule
3.仿真测试模块
`timescale 1ns/1ns
`define clk_period 20
module uart_tx_tb;
reg clk;
reg rst_n;
reg [7:0]pi_data;
reg pi_flag;
wire tx;
uart_tx
#(
.BAUD_CNT_MAX(13'd5207) //9600bps
)
uart_tx0(
.clk(clk),
.rst_n(rst_n),
.pi_data(pi_data),
.pi_flag(pi_flag),
.tx(tx)
);
initial clk=1'b1;
always #(`clk_period/2) clk=~clk;
initial begin
rst_n=1'b0;
pi_flag=1'b0;
#(`clk_period*2+1);
rst_n=1'b1;
#20
pi_flag=1'b1;
pi_data=8'haa;
#20;
pi_flag=1'b0;
#(`clk_period*5208*10);
pi_flag=1'b1;
pi_data=8'hbb;
#20;
pi_flag=1'b0;
#(`clk_period*5208*10);
pi_flag=1'b1;
pi_data=8'hef;
#20;
pi_flag=1'b0;
#(`clk_period*5208*10);
pi_flag=1'b1;
pi_data=8'h55;
#20;
pi_flag=1'b0;
#(`clk_period*5208*10);
end
endmodule
仿真波形图:
总体仿真波形图^
单bit数据仿真波形图^
3.使用In-System Sources & Probes IP核进行上板验证
在IP核中找到In-System Sources & Probes,在配置页面只需改变如下两格即可。
引入IP核代码:
module uart_tx_top(clk,rst_n,tx);
input clk;
input rst_n;
output tx;
wire [7:0]pi_data;
uart_tx uart_tx0(
.clk(clk),
.rst_n(rst_n),
.pi_data(pi_data),
.pi_flag(1'b1),
.tx(tx)
);
issp_tx issp_tx0(
.source(pi_data) // sources.source
);
endmodule
自行配置引脚,进行全编译(注意设置顶层文件),下载到板子上,打开串口调试工具和In-System Sources & Probes Editor
打开In-System Sources & Probes Editor后,将进制调整为十六进制,在DATA数据栏输入1bit数据(两个十六进制数),就可以在串口调试工具中观察到发送到的数据
可以看到如果输入一个数据,就会一直不断发送,这在我们实际运用中是不希望见到的,那么为何会出现这种情况呢?
是因为我们在引入IP核的代码中,将发送数据模块中的pi_flag置为了1,方便我们观察,同时一直为1会使数据一直发送,在实际应用中,合理使用pi_flag可以避免这种情况。
三、数据回环测试
1.RTL代码
module rs232_top(
input wire clk,
input wire rst_n,
input wire rx,
output wire tx
);
wire [7:0]po_data;
wire po_flag;
uart_rx uart_rx0(
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.po_data(po_data),
.po_flag(po_flag)
);
uart_tx uart_tx0(
.clk(clk),
.rst_n(rst_n),
.pi_data(po_data),
.pi_flag(po_flag),
.tx(tx)
);
endmodule
2.RTL视图
3.仿真测试模块
`timescale 1ns/1ns
`define clk_period 20
module rs232_top_tb;
reg clk;
reg rst_n;
reg rx;
wire tx;
rs232_top rs232_top0(
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.tx(tx)
);
initial clk=1'b1;
always #(`clk_period/2) clk=~clk;
initial begin
rst_n=1'b0;
rx=1'b0;
#(`clk_period*2)
rst_n=1'b1;
end
initial begin
#200;
rx_byte();
end
task rx_byte();
integer j;
for(j=0;j<8;j=j+1)
rx_bit(j);
endtask
task rx_bit(
input [7:0]data
);
integer i;
for(i=0;i<10;i=i+1)begin
case(i)
0:rx<=1'b0;
1:rx<=data[0];
2:rx<=data[1];
3:rx<=data[2];
4:rx<=data[3];
5:rx<=data[4];
6:rx<=data[5];
7:rx<=data[6];
8:rx<=data[7];
9:rx<=1'b1;
endcase
#(5208*20);
end
endtask
endmodule
仿真波形图:
4.上板测试
打开串口调试助手,选择循环多条发送,可以看到由串口发送模块发送的数据都被串口接收模块接收到了,实验成功。
总结
本篇文章使用的设计方式相比于之前的设计方式更加清晰易懂,串行数据改并行数据的方法更简单。同时也是第一次使用In-System Sources & Probes工具,是一个很使用且方便的调试工具。