目录
?????? 通信方式一般分为串行通信和并行通信。并行通信是指多比特数据同时通过并行线进行传送。这种传输方式通信线多、成本高,故不宜进行远距离通信,通常传输距离小于 30 米。串行通信是指数据在一条数据线上,一比特接一比特地按顺序传送的方式。这种运输方式通常节省传输线,大大降低使用成本,但数据传送速度慢。综上可知,串行通信主要应用于长距离、低速率的通信场合。本次实验我们主要讲解下串行通信。
//串转并
module zdyz_rs232_rx(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低有效
input uart_rxd , //UART 接收端口
output reg uart_rx_done, //UART 接收完成信号
output reg [7:0] uart_rx_data //UART 接收到的数据
);
parameter CLK_FREQ = 5000_0000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次
reg uart_rxd_d0;
reg uart_rxd_d1;//定义两个D触发器进行异步打拍处理
reg rx_flag ; //接收过程标志信号
reg [3:0] rx_cnt ; //接收数据位计数器
reg [15:0] baud_cnt ; //波特率计数器(位宽为16,防止溢出)
reg [7:0 ] rx_data_t ; //接收数据寄存器
wire start_flag;//开始接收的标志,下降沿到来。
//打两拍:波特率时钟和系统时钟不同步,为异步信号,所以要进行打拍处理,防止产生亚稳态
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
end
end
assign start_flag = (uart_rxd_d0 == 0)&&(uart_rxd_d1 == 1);//下降沿到来的表示方法
// rx_flag接收信号的拉高与拉低
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_flag <= 1'b0;
else if(start_flag) //检测到起始位
rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高
//在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低
else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))//rx_flag 要提前拉低,防止其影响下一帧数据的接收
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
//波特率的计数器计数逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
baud_cnt <= 0;
else if(rx_flag)begin
if(baud_cnt == BAUD_CNT_MAX - 1)
baud_cnt <= 0;
else
baud_cnt <= baud_cnt + 1;
end
else
baud_cnt <= 0;
end
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_cnt <= 0;
else if(rx_flag)begin
if(baud_cnt == BAUD_CNT_MAX - 1)
rx_cnt <= rx_cnt + 1;
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end
//根据 rx_cnt 来寄存 rxd 端口的数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_data_t <= 0;
else if(rx_flag)begin //系统处于接收过程时
if(baud_cnt == BAUD_CNT_MAX/2 - 1)begin//判断 baud_cnt 是否计数到数据位的中间
case(rx_cnt)
1:rx_data_t[0] <= uart_rxd_d1; //寄存数据的最低位
2:rx_data_t[1] <= uart_rxd_d1;
3:rx_data_t[2] <= uart_rxd_d1;
4:rx_data_t[3] <= uart_rxd_d1;
5:rx_data_t[4] <= uart_rxd_d1;
6:rx_data_t[5] <= uart_rxd_d1;
7:rx_data_t[6] <= uart_rxd_d1;
8:rx_data_t[7] <= uart_rxd_d1;//寄存数据的高低位
default:rx_data_t <= rx_data_t;
endcase
end
else
rx_data_t <= rx_data_t;
end
else
rx_data_t <= 0;
end
//给接收完成信号和接收到的数据赋值
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_done <= 0;
uart_rx_data <= 0;
end
//当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时
else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))begin
uart_rx_done <= 1; //拉高接收完成信号
uart_rx_data <= rx_data_t;//并对 UART 接收到的数据进行赋值
end
else begin
uart_rx_done <= 0;
uart_rx_data <= uart_rx_data;
end
end
endmodule
module zdyz_rs232_tx(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位,低有效
input uart_tx_en , //UART 的发送使能(发送是有标志起始时间的,接收是根据下降沿到来就开始进入起始位)
input [7:0] uart_tx_data, //UART 要发送的数据
output reg uart_txd , //UART 发送端口
output reg uart_tx_busy //发送忙状态信号
);
//parameter define
parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次
reg [7:0] tx_data_t; //发送数据寄存器
reg [3:0] tx_cnt ; //发送数据位计数器
reg [15:0] baud_cnt ; //波特率计数器
//当 uart_tx_en 为高时,寄存输入的并行数据(防止发生变化影响发送),并拉高 BUSY 信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
tx_data_t <= 0;
uart_tx_busy <= 0;
end
else if(uart_tx_en)begin
tx_data_t <= uart_tx_data;
uart_tx_busy <= 1;
end
/*为了确保环回实验的成功,在程序的 36 行我们将 uart_tx_busy 提前 1/16 个停止位拉低。尽管串口发送数据只是接收数据的反过程,
理论上在传输的时间上是一致的,但是考虑到我们模块里计算波特率会有较小的偏差,并且串口对端的通信设备(如电脑等)收发数据的波特率
同样可能会出现较小的偏差,因此为了确保环回实验的成功,这里将发送模块的停止位略微提前结束。需要说明的是,较小偏差的波特率在
串口通信时是允许的,同样可以保证数据可靠稳定的传输*/
else if((tx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16))begin
uart_tx_busy <= 0;
tx_data_t <= 0;
end
else begin
uart_tx_busy <= uart_tx_busy;
tx_data_t <= tx_data_t;
end
end
//波特率的计数器计数逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
baud_cnt <= 0;
else if(uart_tx_busy)begin //当处于发送过程时,波特率计数器(baud_cnt)进行循环计数
if(baud_cnt == BAUD_CNT_MAX - 1)
baud_cnt <= 0; //计数达到一个波特率周期后清零
else
baud_cnt <= baud_cnt + 1;
end
else
baud_cnt <= 0;//发送过程结束时计数器清零
end
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
tx_cnt <= 0;
else if(uart_tx_busy)begin//处于发送过程时 tx_cnt 才进行计数
if(baud_cnt == BAUD_CNT_MAX - 1)//当波特率计数器计数到一个波特率周期时
tx_cnt <= tx_cnt + 1; //发送数据计数器加 1
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end
//根据 tx_cnt 来给 uart 发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_txd <= 1 ;//空闲态为1
else if(uart_tx_busy)begin
case(tx_cnt)
4'd0 : uart_txd <= 1'b0 ; //起始位
4'd1 : uart_txd <= tx_data_t[0]; //数据位最低位
4'd2 : uart_txd <= tx_data_t[1];
4'd3 : uart_txd <= tx_data_t[2];
4'd4 : uart_txd <= tx_data_t[3];
4'd5 : uart_txd <= tx_data_t[4];
4'd6 : uart_txd <= tx_data_t[5];
4'd7 : uart_txd <= tx_data_t[6];
4'd8 : uart_txd <= tx_data_t[7]; //数据位最高位
4'd9 : uart_txd <= 1'b1 ; //停止位
default:uart_txd <= 1'b1 ;
endcase
end
else
uart_txd <= 1 ; //空闲时发送端口为高电平
end
endmodule
module top_uart(
input sys_clk , //外部 50MHz 时钟
input sys_rst_n, //系外部复位信号,低有效
//UART 端口
input uart_rxd , //UART 接收端口(串行数据)
output uart_txd //UART 发送端口
);
parameter CLK_FREQ = 50000000; //定义系统时钟频率
parameter UART_BPS = 115200 ; //定义串口波特率
//wire define
wire uart_rx_done; //UART 接收完成信号
wire [7:0] uart_rx_data; //UART 接收数据(并行数据)
wire uart_tx_busy;
//*****************************************************
//** main code
//*****************************************************
//串口接收
zdyz_rs232_rx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)zdyz_rs232_rx(
.sys_clk(sys_clk) , //系统时钟
.sys_rst_n(sys_rst_n) , //系统复位,低有效
.uart_rxd(uart_rxd) , //UART 接收端口
.uart_rx_done(uart_rx_done), //UART 接收完成信号
.uart_rx_data(uart_rx_data)//UART 接收到的数据
);
zdyz_rs232_tx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)zdyz_rs232_tx(
.sys_clk(sys_clk) , //系统时钟
.sys_rst_n(sys_rst_n) , //系统复位,低有效
.uart_tx_en(uart_rx_done) , //UART 的发送使能
.uart_tx_data(uart_rx_data), //UART 要发送的数据
.uart_txd(uart_txd) , //UART 发送端口
.uart_tx_busy(uart_tx_busy) //发送忙状态信号
);
endmodule
`timescale 1ns/1ns //仿真的单位/仿真的精度
module tb_uart_loopback();
//parameter define
parameter CLK_PERIOD = 20;//时钟周期为 20ns
//reg define
reg sys_clk ; //时钟信号
reg sys_rst_n; //复位信号
reg uart_rxd ; //UART 接收端口
//wire define
wire uart_txd ; //UART 发送端口
//*****************************************************
//** main code
//*****************************************************
//发送 8'h55 8'b0101_0101
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
uart_rxd <= 1'b1;
#200
sys_rst_n <= 1'b1;
#1000
uart_rxd <= 1'b0; //起始位
#8680
uart_rxd <= 1'b1; //D0
#8680
uart_rxd <= 1'b0; //D1
#8680
uart_rxd <= 1'b1; //D2
#8680
uart_rxd <= 1'b0; //D3
#8680
uart_rxd <= 1'b1; //D4
#8680
uart_rxd <= 1'b0; //D5
#8680
uart_rxd <= 1'b1; //D6
#8680
uart_rxd <= 1'b0; //D7
#8680
uart_rxd <= 1'b1; //停止位
#8680
uart_rxd <= 1'b1; //空闲状态
end
//50Mhz 的时钟,周期则为 1/50Mhz=20ns,所以每 10ns,电平取反一次
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
//例化顶层模块
top_uart top_uart(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd ),
.uart_txd (uart_txd )
);
endmodule