目录
[RS232 通信协议](#RS232 通信协议)
串口介绍
通用异步收发传输器 (Universal Asynchronous Receiver/Transmitter),通常称作 UART。UART 是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。

- 单工,数据只能沿一个方向传输,不能实现反向传输。
- 半双工,数据可以沿两个方向传输,但需要分时进行。
- 全双工,数据可以同时进行两个方向传输。
UART 是一种通用的数据通信协议,也是异步串行通信口 (串口)的总称 ,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。它包括了RS232、RS499、RS423、RS422和RS485等接口标准规范和总线标准规范。
串口作为常用的三大低速总线(UART、SPI、IIC)之一,常用于通信接口和调试。但UART和SPI、IIC不同的是,它是异步通信接口(接收方和发送方使用的时钟不同) ,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位 的帮助下实现信息同步的 。而 SPI、IIC 是同步通信接口 ,同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。
UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫rx(Receiver)。UART可以实现全双工,即可以同时进行发送数据和接收数据。

RS232信号线
在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称 "猫")之间的通讯,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由)和数据通讯设备 DCE(调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。 在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口)。

其中接线口以针式 引出信号线的称为公头 ,以孔式 引出信号线的称为母头 。在计算机中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起来。通讯时,串口线中传输的信号使用 RS-232 标准调制。在各种应用场合下, DB9 接口中的公头及母头的各个引脚的标准信号线接法见图。

DB9 信号线说明:

RS232 通信协议
1、RS232是UART的一种,没有时钟线,只有两根数据线,分别是rx和tx,这两根线都是1bit位宽的。其中rx是接收数据的线,tx是发送数据的线。
2、rx 位宽为1bit,PC机通过串口调试助手往FPGA发8bit数据时,FPGA通过串口线 rx 一位一位地接收,从最低位到最高位依次接收,最后在FPGA里面位拼接成8比特数据。
3、tx 位宽为1bit,FPGA通过串口往PC机发8bit数据时,FPGA把8bit数据通过tx线一位一位的传给PC机,从最低位到最高位依次发送,最后上位机通过串口助手按照 RS232协议把这一位一位的数据位拼接成8bit数据。
4、串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除 了中间包含8bit有效数据外,还在每一帧的开头都必须有一个起始位,且固定为0;在每一帧的结束时也必须有一个停止位,且固定为1,即最基本的帧结构(不包括校验等) 有 10bit。在不发送或者不接收数据的情况下,rx和tx处于空闲状态,此时rx和tx线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是8bit的数据位,接着有1bit 的停止位,然后rx和tx继续进入空闲状态,然后等待下一次的数据传输。如图所示 为一个最基本的RS232帧结构。

5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是1bit进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号"Baud"表示,其单位为"波特每秒(Bps)"。串 口常见的波特率有4800、9600、115200等,我们选用9600的波特率。
6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为 "每秒比特数(bps)"。比特率可由波特率计算得出,**公式为:比特率=波特率 * 单个调制状态对应的二进制位数。**如果使用的是9600的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。
7、由计算 得串口发送或者接收1bit数据的时间为一个波特,即1/9600秒,如果用 50MHz(周期为20ns)的系统时钟来计数,需要计数的个数为cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个bit数据之间的间隔要在50MHz的时钟频率下计数5208次。
8、上位机通过串口发8bit数据时,会自动在发8位有效数据前发一个波特时间的起始位,也会自动在发完8位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完8bit的数据后,再接收一个波特时间的停止位。
RS232电路分析
如图所示,MAX3232为RS232收发器芯片 。由于 RS-232 电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个"电平转换芯片"转换成控制器能识别的 "TTL"的电平信号,才能实现通讯。

根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准。TTL 电平标准与RS232电平标准如下图:


由于 FPGA 串口输入输出引脚为 TTL电平, 用 3.3V代表逻辑"1", 0V代表逻辑 "0";所以常常会使用 MA3232 芯片对 TTL 及 RS-232电平的信号进行互相转换。
同时开发板中还搭载了USB转串口的芯片CH340,可使用 USB线进行串口调试。

串口发送模块UART_TX
绘制模块框图及波形图
第一种使用计数器的方法。

第二种使用状态机的方法。

下面使用的是第一种方法进行编写代码。
编写模块代码
module uart_tx
#(
parameter UART_BPS = 'd9600 ,//波特率
CLK_FREQ = 'd50_000_000 ,//时钟频率
UART_DATA_BIT = 'd8 //串口发送数据位数
)
(
input wire clk ,
input wire rst_n ,
input wire [7:0] pi_data , //8bit的并行数据
input wire pi_flag , //并行数据有效标志信号(周期必须大于一帧数据的时长)
output reg tx , //并行转串行发送
output reg done //完成一帧数据发送的标志信号
);
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS , //一个码元要保持的时长
BIT_CNT_MAX = 1'b1 + UART_DATA_BIT + 1'b1 ; //一帧数据的总位数,起始位+数据位+停止位(无校验位)
reg [7:0] data_reg;//用于存储并行数据,防止并行数据没有保持发送一帧数据的时长
reg work_en ;//发送数据工作使能信号
reg [31:0] cnt_baud;//码元计数器
reg [3:0] cnt_bit ;//数据位的位数计数器
//data_reg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_reg <= 8'd0;
else if(pi_flag)
data_reg <= pi_data;
else
data_reg <= data_reg;
end
//work_en
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
work_en <= 1'b0;
else if(pi_flag)
work_en <= 1'b1;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
work_en <= 1'b0;
else
work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_baud <= 32'd0;
else if(work_en && cnt_baud == BAUD_CNT_MAX - 1'b1)
cnt_baud <= 32'd0;
else if(work_en)
cnt_baud <= cnt_baud + 1'b1;
else
cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bit <= 4'd0;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
cnt_bit <= 4'd0;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
//tx
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
tx <= 1'b1;
else if(work_en)
case(cnt_bit)
0:tx <= 1'b0 ;
1:tx <= data_reg[0] ;
2:tx <= data_reg[1] ;
3:tx <= data_reg[2] ;
4:tx <= data_reg[3] ;
5:tx <= data_reg[4] ;
6:tx <= data_reg[5] ;
7:tx <= data_reg[6] ;
8:tx <= data_reg[7] ;
9:tx <= 1'b1 ;
default:tx <= 1'b1;
endcase
else
tx <= 1'b1;
end
//done
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
done <= 1'b0;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
done <= 1'b1;
else
done <= 1'b0;
end
endmodule
编写仿真代码
`timescale 1ns/1ps
module uart_tx_tb();
reg clk ;
reg rst_n ;
reg [7:0] pi_data;
reg pi_flag;
initial
begin
clk = 1'b0 ;
rst_n = 1'b0 ;
pi_data = 8'h00 ;
pi_flag = 1'b0 ;
#123
rst_n = 1'b1 ;
#20000
//发送数据1
pi_data = 8'h01 ;
#20
pi_flag = 1'b1 ;
#20
pi_flag = 1'b0 ;
#(5208*10*20) ;
//发送数据2
pi_data = 8'h02 ;
#20
pi_flag = 1'b1 ;
#20
pi_flag = 1'b0 ;
#(5208*10*20) ;
//发送数据3
pi_data = 8'h03 ;
#20
pi_flag = 1'b1 ;
#20
pi_flag = 1'b0 ;
#(5208*10*20) ;
//发送数据4
pi_data = 8'h04 ;
#20
pi_flag = 1'b1 ;
#20
pi_flag = 1'b0 ;
#(5208*10*20) ;
#(5208*10*20) ;
//发送数据5
pi_data = 8'h05 ;
#20
pi_flag = 1'b1 ;
#20
pi_flag = 1'b0 ;
#(5208*10*20) ;
end
always #10 clk = ~clk;
uart_tx
#(
.UART_BPS ( 'd9600 ),//波特率
.CLK_FREQ ( 'd50_000_000 ),//时钟频率
.UART_DATA_BIT ( 'd8 ) //串口发送数据位数
)
uart_tx_inst
(
.clk (clk ),
.rst_n (rst_n ),
.pi_data (pi_data), //8bit的并行数据
.pi_flag (pi_flag), //并行数据有效标志信号(周期必须大于一帧数据的时长)
.tx (), //并行转串行发送
.done () //完成一帧数据发送的标志信号
);
endmodule
仿真验证

data_reg、work_en、cnt_baud、cnt_bit、tx、done开始部分没有问题。



可以发现data_reg,work_en没有问题,两个计数器也没有问题,tx发送一帧数据没有问题,done完成发送一帧的标志信号也没问题。

仿真验证通过。
串口接收模块UART_RX
绘制模块框图及波形图

rx串行数据一开始直接打了两拍,就是经过了 两级寄存器,理论上我们应该按照串口接收数据的时序要求找到rx的下降沿,然后开始接收起始位的数据,但为什么先将数据打了两拍呢?那就要先从跨时钟域会导致"亚稳态" 的问题上说起。
产生亚稳态的原因: FPGA在接收rx数据时不满足内部寄存器(D触发器)的建立时间Tsu (指触发器的时钟信 号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th (指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间),此时 FPGA 的第一级寄存器的输出端在时钟沿到来之后比较长的一段时间内都处于不确定的状态,在 0和 1之间处于振荡状态,而不是 等于串口输入的确定的rx值。产生亚稳态的波形示意图,如下图


注意:打两拍后虽然能让信号稳定到 0或者1中确定的值,但究竟是0还是1却是随机的,与打拍之前输入信号的值没有必然的关系。
注:单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。 第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为70%~80%左右,第二级寄存 器可以稳定输出的概率为 99%左右,后面再多加寄存器的级数改善效果就不明显了,所以数据进来后一般选择打两拍即可。
单比特信号从快速时钟域同步到慢速时钟域还仅仅使用打两拍的方式会漏采数据,所以往往使用脉冲同步法 或握手信号法
多比特信号跨时钟域 需要进行格雷码编码 (多比特顺序数才可以)后才能进行打两拍的处理,或者通过使用 FIFO、RAM 来处理数据与时钟同步的问题。 亚稳态振荡时间 Tmet 关系到后级寄存器的采集稳定问题,Tmet 影响因素包括:器件的生产工艺、温度、环境以及寄存器采集到亚稳态里稳定态的时刻等。甚至某些特定条件,如干扰、辐射等都会造成Tmet增长。
data_reg信号是参与移位的数据,在移位的过程中数据是变动的,不可以被后级模块所使用,而可以肯定的是在移位完成时,data_reg信号一定是移位完成的稳定的 8bit 有用数据。
编写模块代码
module uart_rx
#(
parameter UART_BPS = 'd9600 , //波特率
CLK_FREQ = 'd50_000_000 , //时钟频率
UART_DATA_BIT = 'd8 //串口接收的有效数据位数
)
(
input wire clk ,
input wire rst_n ,
input wire rx ,
output reg [7:0] po_data ,
output reg po_flag
);
localparam BAUT_CNT_MAX = CLK_FREQ / UART_BPS , //一个码元要保持的时长
BIT_CNT_MAX = 1'b1 + UART_DATA_BIT + 1'b1 ;//一帧数据的总位数,起始位+数据位+停止位(无校验位)
reg rx_r1 ;//rx打拍,将rx同步到本系统时钟下
reg rx_r2 ;
reg rx_r3 ;//用于下降沿采集,和数据提取
wire fall_flag ;//下降沿标志信号
reg work_en ;//下降沿到来,开始解析数据
reg [31:0] cnt_baud ;//码元计数器,计数一个码元要保持的时长
reg [3:0] cnt_bit ;//位数计数器
reg [7:0] data_reg ;//用于提取有效数据
//rx_r1,rx_r2,rx_r3
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rx_r1 <= 1'b1 ;
rx_r2 <= 1'b1 ;
rx_r3 <= 1'b1 ;
end
else
begin
rx_r1 <= rx ;
rx_r2 <= rx_r1 ;
rx_r3 <= rx_r2 ;
end
end
//fall_flag
assign fall_flag = (rx_r3 && !rx_r2 && !cnt_bit) ? 1'b1 : 1'b0;
//work_en
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
work_en <= 1'b0;
else if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
work_en <= 1'b0;
else if(fall_flag)
work_en <= 1'b1;
else
work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_baud <= 32'd0;
else if(work_en && cnt_baud == BAUT_CNT_MAX - 1'b1)
cnt_baud <= 32'd0;
else if(work_en)
cnt_baud <= cnt_baud + 1'b1;
else
cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bit <= 4'd0;
else if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
cnt_bit <= 4'd0;
else if(cnt_baud == BAUT_CNT_MAX - 1'b1)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
//data_reg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_reg <= 8'd0;
else if(cnt_baud == BAUT_CNT_MAX / 2
&& cnt_bit >= 1'b1
&& cnt_bit <= UART_DATA_BIT)
data_reg <= {rx_r3, data_reg[7:1]};
else
data_reg <= data_reg;
end
//po_data
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
po_data <= 8'd0;
else if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
&& cnt_bit == UART_DATA_BIT)
po_data <= data_reg;
else
po_data <= po_data;
end
//po_flag
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
po_flag <= 1'b0;
else if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
&& cnt_bit == UART_DATA_BIT)
po_flag <= 1'b1;
else
po_flag <= 1'b0;
end
endmodule
编写仿真代码
`timescale 1ns/1ps
module uart_rx_tb();
reg clk ;
reg rst_n;
reg rx ;
initial
begin
clk = 1'b0;
rst_n = 1'b0;
rx = 1'b1;
#123
rst_n = 1'b1;
#20000
//模拟8'b1010_0000
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
#20000
//模拟8'b1010_1010
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
//模拟8'b1010_0101
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b0;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
rx = 1'b1;
#(5208*20) ;
end
always #10 clk = ~clk ;
uart_rx
#(
.UART_BPS ( 'd9600 ), //波特率
.CLK_FREQ ( 'd50_000_000 ), //时钟频率
.UART_DATA_BIT ( 'd8 ) //串口接收的有效数据位数
)
uart_rx_inst
(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),
.po_data (),
.po_flag ()
);
endmodule
仿真验证

三级打拍和下降沿采集没有问题,工作使能信号和cnt_baud起始没问题。


两个计数器和工作使能信号没有问题。

两个输出信号没问题。


仿真验证通过。
RS232串口数据回显的设计
实现目标:设计并实现基于串口 RS232 的数据收、发模块,使用收、发模块,完成串口数据回环 实验。
绘制模块框图

可以看到我在uart_rx模块加了一个tx_done信号,表示一帧帧结构发送完毕,这个可以避免我们在tx还没发送完数据时,pi_flag的单脉冲信号就到了,从而会导致丢失数据或出现乱码的情况。利用tx_done信号可以实现连续的发送数据。
如果直接使用上面的已经验证超过的两个模块,那你会发现回显设计的仿真是没有问题的,但是上班验证会丢失偶数位的数据,这是因为tx发送的是完完整整的一帧数据,pc机发送的帧可能会有误差。
所以这里需要修改一下uart_rx的po_flag信号,当uart_tx忙(还没发完一帧)时,po_flag如果提前到来时,让其保持高电平;当uart_tx空闲时(发送完一帧),也就是done为高电平时,将其po_flag拉低,此时可以检测到有效的数据有效信号。
编写模块代码
顶层模块
module uart_rx_tx(
input wire clk ,
input wire rst_n ,
input wire rx ,
output wire tx
);
wire [7:0] po_data;
wire po_flag;
wire done ;
uart_rx
#(
.UART_BPS ( 'd9600 ), //波特率
.CLK_FREQ ( 'd50_000_000 ), //时钟频率
.UART_DATA_BIT ( 'd8 ) //串口接收的有效数据位数
)
uart_rx_inst1
(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),
.tx_done(done ),
.po_data(po_data),
.po_flag(po_flag)
);
uart_tx
#(
.UART_BPS ( 'd9600 ),//波特率
.CLK_FREQ ( 'd50_000_000 ),//时钟频率
.UART_DATA_BIT ( 'd8 ) //串口发送数据位数
)
uart_tx_inst1
(
.clk (clk ),
.rst_n (rst_n ),
.pi_data (po_data), //8bit的并行数据
.pi_flag (po_flag), //并行数据有效标志信号(周期必须大于一帧数据的时长)
.tx (tx ), //并行转串行发送
.done (done ) //完成一帧数据发送的标志信号
);
endmodule
功能模块uart_rx
module uart_rx
#(
parameter UART_BPS = 'd9600 , //波特率
CLK_FREQ = 'd50_000_000 , //时钟频率
UART_DATA_BIT = 'd8 //串口接收的有效数据位数
)
(
input wire clk ,
input wire rst_n ,
input wire rx ,
input wire tx_done ,
output reg [7:0] po_data ,
output reg po_flag
);
localparam BAUT_CNT_MAX = CLK_FREQ / UART_BPS , //一个码元要保持的时长
BIT_CNT_MAX = 1'b1 + UART_DATA_BIT + 1'b1 ;//一帧数据的总位数,起始位+数据位+停止位(无校验位)
reg rx_r1 ;//rx打拍,将rx同步到本系统时钟下
reg rx_r2 ;
reg rx_r3 ;//用于下降沿采集,和数据提取
wire fall_flag ;//下降沿标志信号
reg work_en ;//下降沿到来,开始解析数据
reg [31:0] cnt_baud ;//码元计数器,计数一个码元要保持的时长
reg [3:0] cnt_bit ;//位数计数器
reg [7:0] data_reg ;//用于提取有效数据
//rx_r1,rx_r2,rx_r3
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rx_r1 <= 1'b1 ;
rx_r2 <= 1'b1 ;
rx_r3 <= 1'b1 ;
end
else
begin
rx_r1 <= rx ;
rx_r2 <= rx_r1 ;
rx_r3 <= rx_r2 ;
end
end
//fall_flag
assign fall_flag = (rx_r3 && !rx_r2 && !cnt_bit) ? 1'b1 : 1'b0;
//work_en
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
work_en <= 1'b0;
else if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
work_en <= 1'b0;
else if(fall_flag)
work_en <= 1'b1;
else
work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_baud <= 32'd0;
else if(work_en && cnt_baud == BAUT_CNT_MAX - 1'b1)
cnt_baud <= 32'd0;
else if(work_en)
cnt_baud <= cnt_baud + 1'b1;
else
cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bit <= 4'd0;
else if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
cnt_bit <= 4'd0;
else if(cnt_baud == BAUT_CNT_MAX - 1'b1)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
//data_reg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_reg <= 8'd0;
else if(cnt_baud == BAUT_CNT_MAX / 2
&& cnt_bit >= 1'b1
&& cnt_bit <= UART_DATA_BIT)
data_reg <= {rx_r3, data_reg[7:1]};
else
data_reg <= data_reg;
end
//po_data
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
po_data <= 8'd0;
else if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
&& cnt_bit == UART_DATA_BIT)
po_data <= data_reg;
else
po_data <= po_data;
end
//po_flag
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
po_flag <= 1'b0;
else if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
&& cnt_bit == UART_DATA_BIT)
po_flag <= 1'b1;
else if(tx_done)
po_flag <= 1'b0;
else
po_flag <= po_flag;
end
endmodule
功能模块uart_tx
module uart_tx
#(
parameter UART_BPS = 'd9600 ,//波特率
CLK_FREQ = 'd50_000_000 ,//时钟频率
UART_DATA_BIT = 'd8 //串口发送数据位数
)
(
input wire clk ,
input wire rst_n ,
input wire [7:0] pi_data , //8bit的并行数据
input wire pi_flag , //并行数据有效标志信号(周期必须大于一帧数据的时长)
output reg tx , //并行转串行发送
output reg done //完成一帧数据发送的标志信号
);
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS , //一个码元要保持的时长
BIT_CNT_MAX = 1'b1 + UART_DATA_BIT + 1'b1 ; //一帧数据的总位数,起始位+数据位+停止位(无校验位)
reg [7:0] data_reg;//用于存储并行数据,防止并行数据没有保持发送一帧数据的时长
reg work_en ;//发送数据工作使能信号
reg [31:0] cnt_baud;//码元计数器
reg [3:0] cnt_bit ;//数据位的位数计数器
//data_reg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_reg <= 8'd0;
else if(pi_flag)
data_reg <= pi_data;
else
data_reg <= data_reg;
end
//work_en
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
work_en <= 1'b0;
else if(pi_flag)
work_en <= 1'b1;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
work_en <= 1'b0;
else
work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_baud <= 32'd0;
else if(work_en && cnt_baud == BAUD_CNT_MAX - 1'b1)
cnt_baud <= 32'd0;
else if(work_en)
cnt_baud <= cnt_baud + 1'b1;
else
cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_bit <= 4'd0;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
cnt_bit <= 4'd0;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
//tx
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
tx <= 1'b1;
else if(work_en)
case(cnt_bit)
0:tx <= 1'b0 ;
1:tx <= data_reg[0] ;
2:tx <= data_reg[1] ;
3:tx <= data_reg[2] ;
4:tx <= data_reg[3] ;
5:tx <= data_reg[4] ;
6:tx <= data_reg[5] ;
7:tx <= data_reg[6] ;
8:tx <= data_reg[7] ;
9:tx <= 1'b1 ;
default:tx <= 1'b1;
endcase
else
tx <= 1'b1;
end
//done
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
done <= 1'b0;
else if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
done <= 1'b1;
else
done <= 1'b0;
end
endmodule
编写仿真代码
`timescale 1ns/1ps
module uart_rx_tx_tb();
reg clk ;
reg rst_n;
reg rx ;
wire tx;
initial
begin
clk = 1'b0;
rst_n = 1'b0;
rx = 1'b1;
#123
rst_n = 1'b1;
end
always #10 clk = ~clk ;
//模拟发送8次数据,分别为0~7
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
//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
input [7:0] data
);
integer i; //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=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 ;
default:rx <= 1'b1 ;
endcase
#(5208*20); //每发送1位数据延时5208个时钟周期
end
endtask//任务以endtask结束
uart_rx_tx uart_rx_tx_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.rx (rx ) ,
.tx (tx )
);
endmodule
仿真验证

仿真没有问题
上板验证

验证通过。
串口连续发送8字节
功能:使用串口连续发送8字节(50Mhz,波特率9600Bps)
绘制模块框图及波形图
第二种使用状态机的方法。



编写模块代码
顶层模块uart_send_8byte
module uart_send_8byte(
input wire clk ,
input wire rst_n ,
output wire tx
);
wire send_done;
wire pi_flag ;
wire [7:0] pi_data ;
fsm fsm_inst1(
.clk (clk ) ,
.rst_n (rst_n ) ,
.send_done (send_done) ,//控制串口发送信号线
.pi_flag (pi_flag ) ,
.pi_data (pi_data )
);
uart_tx uart_tx_inst1(
.clk (clk ) ,
.rst_n (rst_n ) ,
.pi_flag (pi_flag ) ,
.pi_data (pi_data ) ,
.send_done (send_done) ,
.tx (tx )
);
endmodule
模块fsm
module fsm(
input wire clk ,
input wire rst_n ,
input wire send_done ,//控制串口发送信号线
output reg pi_flag ,
output reg [7:0] pi_data
);
//定义一个数组,存放8个字节需要发送的数据
wire [7:0] mem [0:7];
assign mem[0] = 8'h26;//年
assign mem[1] = 8'h07;//月
assign mem[2] = 8'h01;
assign mem[3] = 8'h14;
assign mem[4] = 8'h15;
assign mem[5] = 8'h16;
assign mem[6] = 8'h17;
assign mem[7] = 8'h18;
parameter idle = 8'd0;
parameter s0 = 8'd1;
parameter s1 = 8'd2;
parameter s2 = 8'd3;
parameter s3 = 8'd4;
reg [7:0] state;
//内部变量的定义
reg [31:0] delay_cnt;
reg [7:0] send_done_cnt;
reg [7:0] index;
parameter TIME_1S = 32'd50_000_000;
parameter BYTE_NUM = 32'd8 ;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
delay_cnt <= 32'd0;
send_done_cnt <= 8'd0;
index <= 8'd0;
state <= idle;
pi_flag <= 1'b0;
pi_data <= 8'd0;
end
else
begin
case(state)
idle:
begin
if(delay_cnt == TIME_1S)
begin
delay_cnt <= 32'd0;
send_done_cnt <= 8'd0;
pi_flag <= 1'b0;
pi_data <= 8'd0;
index <= 8'd0;
state <= s0 ;
end
else
begin
state <= state;
delay_cnt <= delay_cnt + 1'b1;
end
end
s0 :
begin
pi_flag <= 1'b1;
pi_data <= mem[index];
state <= s1;
end
s1 :
begin
pi_flag <= 1'b0;
if(send_done)
begin
state <= s2;
send_done_cnt <= send_done_cnt + 1'b1;
index <= index + 1'b1;
end
else
begin
state <= state;
send_done_cnt <= send_done_cnt;
index <= index;
end
end
s2 :
begin
if(send_done_cnt == BYTE_NUM)
state <= s3;
else
state <= s0;
end
s3 :
begin
delay_cnt <= 32'd0;
send_done_cnt <= 8'd0;
index <= 8'd0;
state <= state;
pi_flag <= 1'b0;
pi_data <= 8'd0;
end
default:
begin
delay_cnt <= 32'd0;
send_done_cnt <= 8'd0;
index <= 8'd0;
state <= state;
pi_flag <= 1'b0;
pi_data <= 8'd0;
end
endcase
end
end
endmodule
模块uart_tx
module uart_tx(
input wire clk ,
input wire rst_n ,
input wire pi_flag ,
input wire [7:0] pi_data ,
output reg send_done ,
output reg tx
);
parameter CLK_FREQ = 'd50_000_000 ;
parameter BPS = 'd9600 ;
localparam BAUD_TIME= CLK_FREQ / BPS ;
parameter idle = 8'd0 ;
parameter s0 = 8'd1 ;
parameter s1 = 8'd2 ;
parameter s2 = 8'd3 ;
parameter s3 = 8'd4 ;
parameter s4 = 8'd5 ;
parameter s5 = 8'd6 ;
parameter s6 = 8'd7 ;
parameter s7 = 8'd8 ;
parameter s8 = 8'd9 ;
parameter s9 = 8'd10;
parameter s10 = 8'd11;
parameter done = 8'd12;
reg [7:0] state ;
reg [15:0] delay_cnt ;
reg [9:0] send_data ;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
state <= idle;
delay_cnt <= 16'd0;
send_done <= 1'b0;
tx <= 1'b1;
end
else
case(state)
idle:
begin
if(pi_flag)
state <= s0;
else
state <= state;
delay_cnt <= 16'd0;
tx <= 1'b1;
send_done <= 1'b0;
end
s0 :
begin
send_data <= {1'b1,pi_data[7:0],1'b0};
state <= s1;
end
s1 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s2;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[0];
delay_cnt <= delay_cnt + 1'b1;
end
end
s2 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s3;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[1];
delay_cnt <= delay_cnt + 1'b1;
end
end
s3 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s4;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[2];
delay_cnt <= delay_cnt + 1'b1;
end
end
s4 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s5;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[3];
delay_cnt <= delay_cnt + 1'b1;
end
end
s5 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s6;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[4];
delay_cnt <= delay_cnt + 1'b1;
end
end
s6 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s7;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[5];
delay_cnt <= delay_cnt + 1'b1;
end
end
s7 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s8;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[6];
delay_cnt <= delay_cnt + 1'b1;
end
end
s8 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s9;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[7];
delay_cnt <= delay_cnt + 1'b1;
end
end
s9 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= s10;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[8];
delay_cnt <= delay_cnt + 1'b1;
end
end
s10 :
begin
if(delay_cnt == BAUD_TIME - 1'b1)
begin
state <= done;
delay_cnt <= 16'd0;
end
else
begin
tx <= send_data[9];
delay_cnt <= delay_cnt + 1'b1;
end
end
done:
begin
state <= idle;
send_done <= 1'b1;
end
default:
begin
state <= idle;
delay_cnt <= 16'd0;
send_done <= 1'b0;
tx <= 1'b1;
end
endcase
end
endmodule
编写仿真代码
`timescale 1ns/1ps
module uart_send_8byte_tb();
reg clk ;
reg rst_n;
wire tx;
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#123
rst_n = 1'b1;
end
always #10 clk = ~clk;
uart_send_8byte uart_send_8byte_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.tx (tx )
);
endmodule
仿真验证

上板验证
