提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
串口回环工程:使用RS232进行上位机和FPGA之间的串口通信。
个人学习记录,仅供参考
一、实验目的
上位机(电脑)发送一帧数据(1位起始位,8位数据位,1位校验位,1位停止位)给FPGA,FPGA对数据进行校验,如果正确,将该帧数据保持不变发送给上位机;如果错误,发送带有校验位的00给上位机。
二、通信协议
物理层
信号线:1、TXD:发送端。 2、RXD:接收端
通信的方式和方向:异步通信全双工
RS232、RS422、RS485区别
图1、三种串口数据线对比
协议层
数据格式:
图2、串口的数据格式
传输速率:
波特率:9600、19200、38400、57600、115200。
单位:bit/s
三、项目框架
参数模块:
`define CLK_FREQ 50_000_000
`define TIME_zhen 10
`define BAUD_9600 9600
// `define BAUD_19200 19200
// `define BAUD_38400 38400
// `define BAUD_57600 57600
// `define BAUD_115200 115200
`ifdef BAUD_9600
`define BAUD_NUM `CLK_FREQ / `BAUD_9600
`elsif BAUD_19200
`define BAUD_NUM `CLK_FREQ / `BAUD_19200
`elsif BAUD_38400
`define BAUD_NUM `CLK_FREQ / `BAUD_38400
`elsif BAUD_57600
`define BAUD_NUM `CLK_FREQ / `BAUD_57600
`elsif BAUD_115200
`define BAUD_NUM `CLK_FREQ / `BAUD_115200
`endif
顶层模块
module top(
input clk ,
input rst_n ,
input rxd ,
output txd
);
wire [7:0] rx_data ;
wire rx_data_vld ;
wire tx_buzy ;
wire [7:0] ctrl_data ;
wire ctrl_data_vld ;
// 实例化
RX RX_inst(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input */.rxd (rxd ),
/* output [7:0] */.rx_data (rx_data ),
/* output */.rx_data_vld (rx_data_vld )
);
Ctrl Ctrl_inst(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [7:0] */.rx_data (rx_data ),
/* input */.rx_data_vld (rx_data_vld ),
/* input */.tx_buzy (tx_buzy ),
/* output [7:0] */.ctrl_data (ctrl_data ),
/* output */.ctrl_data_vld (ctrl_data_vld )
);
TX TX_inst(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [7:0] */.ctrl_data (ctrl_data ),
/* input */.ctrl_data_vld (ctrl_data_vld ),
/* output */.tx_buzy (tx_buzy ),
/* output */.txd (txd )
);
endmodule
2.1、RX模块
实现目标:
UART的接收模块————将串行数据转为并行数据后,传给Ctrl模块
逻辑步骤:
1、寻找串行数据开始的地方
-----(寄存打拍)
2、在接收时间内寄存收到的数据
-----(两个计数器,一个计数波特率,一个计数数据长度)
3、接受完1帧数据后,进行奇校验,校验正确后将数据传给Ctrl模块,校验错误发送00给Ctrl模块
-----(当最后一位数据接收完毕,输出“数据合理”信号,表示一帧数据已经完整接收)
代码:
`include "param.v"
module RX(
input clk ,
input rst_n ,
input rxd ,
output [7:0] rx_data ,
output rx_data_vld
);
//内部连线
// 1
reg rxd_r0 ;
reg rxd_r1 ;
wire nedge ;
reg rx_flag ; // 接收flag信号
// 2
reg [12:0] cnt_Baud ;
wire add_cnt_Baud ;
wire end_cnt_Baud ;
reg [3:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [9:0] rx_data_r ; // 输出寄存器
// 3
reg rx_data_vld_r ;
//内部逻辑
// 1、寻找串行数据开始的地方(寄存打拍)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rxd_r0 <= 1'd0 ;
rxd_r1 <= 1'd0 ;
end
else begin
rxd_r0 <= rxd ;
rxd_r1 <= rxd_r0 ;
end
end
assign nedge = (~rxd_r0) & rxd_r1 ;
// 2、在接收时间内寄存收到的数据(两个计数器,一个计数波特率,一个计数数据长度)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_flag <= 1'd0 ;
end
else if(nedge)begin
rx_flag <= 1'b1 ;
end
else if(rx_data_vld_r)begin
rx_flag <= 1'b0 ;
end
end
// 计数波特率
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_Baud <= 13'd0;
end
else if(add_cnt_Baud )begin
if(end_cnt_Baud )begin
cnt_Baud <= 13'd0;
end
else begin
cnt_Baud <= cnt_Baud + 1'b1;
end
end
end
assign add_cnt_Baud = rx_flag ;
assign end_cnt_Baud = add_cnt_Baud && cnt_Baud == `BAUD_NUM-1'b1;
// 计数数据长度
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 4'd0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 4'd0 ;
end
else begin
cnt_byte <= cnt_byte + 1'b1 ;
end
end
end
assign add_cnt_byte = end_cnt_Baud ;
assign end_cnt_byte = add_cnt_byte && cnt_byte == `TIME_zhen-1'b1 ;
// 将每一位的数据传给rx_data_r寄存器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data_r <= 10'h3ff ;
end
else if(cnt_Baud == (`BAUD_NUM >> 1))begin
rx_data_r[cnt_byte] <= rxd_r0 ;
end
end
// 还可以将数据进行移位寄存
/* always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data_r <= 11'h7ff ;
end
else if(cnt_Baud == (`BAUD_NUM >> 1))begin
rx_data_r <= {rxd_r0,rx_data_r[10:1]} ;
end
end */
assign rx_data = rx_data_r[8:1] ;
/* // 进行奇校验
assign check_bit = !(^(rx_data_r[8:1])) ;
assign rx_data = (check_bit == rx_data_r[9]) ? (rx_data_r[8:1]) : (8'h00) ;
// ^rx_data_r[8:1]为1表示数据位中有奇数个1 */
// 3、接受完1帧数据后,将数据传给Ctrl模块(当最后一位数据接收完毕,输出“数据合理”信号,表示一帧数据已经完整接收)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data_vld_r <= 1'd0 ;
end
else if(end_cnt_byte)begin
rx_data_vld_r <= 1'd1 ;
end
else begin
rx_data_vld_r <= 1'b0 ;
end
end
assign rx_data_vld = rx_data_vld_r ;
endmodule
2.2、ctrl模块
将接收到的一字节数据(8bit)传递给TX模块,用了一个FIFO,FIFO设置为前显模式。
module Ctrl(
input clk ,
input rst_n ,
input [7:0] rx_data ,
input rx_data_vld ,
input tx_buzy ,
output [7:0] ctrl_data ,
output ctrl_data_vld
);
wire [7:0] fifo_data ;
wire fifo_rdreq ;
wire fifo_wrreq ;
wire fifo_empty ;
wire fifo_full ;
wire [7:0] fifo_q ;
wire [7:0] fifo_usedw ;
fifo_8_256 fifo_8_256_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( fifo_data ),
.rdreq ( fifo_rdreq ),
.wrreq ( fifo_wrreq ),
.empty ( fifo_empty ),
.full ( fifo_full ),
.q ( fifo_q ),
.usedw ( fifo_usedw )
);
assign fifo_data = rx_data ;
assign fifo_rdreq = (!fifo_empty) && (!tx_buzy) ;
assign fifo_wrreq = (!fifo_full) && rx_data_vld ;
assign ctrl_data = fifo_q ;
assign ctrl_data_vld = fifo_rdreq ;
endmodule
2.3、TX模块
实现目标: UART的发送模块(并转串)————什么时候传、传什么、怎么传、什么时候结束传输
逻辑步骤: 1、当vld有效时,添加起始位和停止位,拼接成完整的一帧数据。
2、传输数据(一个计数波特率,一个计数数据位),按照协议发送数据
3、传输结束时,将buzy拉低
`include "param.v"
module TX(
input clk ,
input rst_n ,
input [7:0] tx_data ,
input tx_data_vld ,
output tx_buzy ,
output txd
);
// 内部连线
reg [9:0] tx_data_r ;
reg [12:0] cnt_Baud ;
wire add_cnt_Baud ;
wire end_cnt_Baud ;
reg [3:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg tx_flag ; // 发送flag信号
reg txd_r ;
wire check ;
// 内部逻辑
/* // 0、添加校验位
assign check = ^tx_data ; // ^tx_data :8位数据1的个数为奇数个时为1,为偶数个为0
// --------------------------奇校验--数据位和校验位中1的个数为奇数个---------------------
// 当8位数据位的1的个数为奇数个时(check为1),校验位为0(~check)可满足奇数个1
// 当8位数据位的1的个数为偶数个时(check为0),校验位为1(~check)可满足奇数个1
// --------------------------偶校验--数据位和校验位中1的个数为偶数个---------------------
// 当8位数据位的1的个数为奇数个时(check为1),校验位为1(check)可满足偶数个1
// 当8位数据位的1的个数为偶数个时(check为0),校验位为0(check)可满足偶数个1 */
// 1、当vld有效时,开始传输数据 (输入寄存与拼接)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_r <= 11'd0 ;
end
else if(tx_data_vld )begin // 奇校验
tx_data_r <= {1'b1,tx_data,1'b0};
end
end
// 2、传输数据(一个计数波特率,一个计数数据位),按照协议发送数据
// 计数有效信号——tx_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_flag <= 1'd0 ;
end
else if(tx_data_vld )begin
tx_flag <= 1'b1 ;
end
else if (end_cnt_byte)begin
tx_flag <= 1'b0 ;
end
end
// 波特计数器——cnt_Baud
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_Baud <= 13'd0 ;
end
else if(add_cnt_Baud)begin
if(end_cnt_Baud)begin
cnt_Baud <= 13'd0 ;
end
else begin
cnt_Baud <= cnt_Baud + 1'b1 ;
end
end
end
assign add_cnt_Baud = tx_flag;
assign end_cnt_Baud = add_cnt_Baud && (cnt_Baud == (`BAUD_NUM-1'b1)) ;
// 数据位计数器——cnt_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 3'd0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 3'd0 ;
end
else begin
cnt_byte <= cnt_byte + 1'b1 ;
end
end
end
assign add_cnt_byte = end_cnt_Baud;
assign end_cnt_byte = add_cnt_byte && (cnt_byte == (`TIME_zhen-1'b1)) ;
// 按位传输数据——txd_r(在tx_flag有效期间内按位传)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
txd_r <= 1'b1 ;
end
else if(tx_flag)begin
txd_r <= tx_data_r[cnt_byte] ;
end
end
assign txd = txd_r ;
// txd_buzy_r信号——信号波形和tx_flag一样,不用在单独定义寄存器输出
assign tx_buzy = tx_flag ;
endmodule
面试遇到的一些问题
1、讲一下UART协议
物理层:
UART为异步全双工通信,物理层有两根数据线(RX和TX),这两根数据线空闲为高电平。
协议层:
数据是LSB传输的,首先是1位的低电平表示数据传输开始,然后是5~8位的数据位,之后有一个奇偶校验位,最后是1位的高电平表示数据传输结束。