系列文章目录
FPGA基础 – 通信协议 — 了解UART以及电脑串口环境准备
前言
咱们上一文章学过了UART的基础,已经初步了解了协议的基础知识和时序要求,接下来就是将学到的写出东西来,本文通过上位机的串口助手发送数据,FIFO将数据在RX和TX之间进行传递,并且传递回上位机的串口助手,修改校验方式数据会发生变化。
一、思路设计
1、框架图
大体框架就如图所示,通过电脑串口助手发送数据,FPGA芯片的RX将数据接收,经过FIFO缓存传输到TX,再由TX传输回到串口助手。
二、代码
1.RX
rx模块:接收串口助手发送过来的数据。
状态机:
由上一篇文章知道,在传输数据的过程中,一共有四种总线的变化。
起始位(1bit)、数据位(8bit)、校验位(1bit)、停止位(1bit)。所以设置了五个状态来控制总线。
传输数据速率是由波特率来决定的,所以设置一个波特率计数器和bit计数器,来控制总线上数据的传输和状态机的变换。
assign idle2start = state_c == IDLE && rx == 0 ;
assign start2data = state_c == START && end_cnt_bps ;
assign data2stop = state_c == DATA && end_cnt_bit && CHECK_BIT == "None" ;
assign data2check = state_c == DATA && end_cnt_bit ;
assign check2stop = state_c == CHECK && end_cnt_bps ;
assign stop2idle = state_c == STOP && end_cnt_bps ;
上边的代码就是状态机的状态跳转条件:
由初始状态到发送起始位总线会拉低1bit的时间,所以条件就是当总线拉低时表示发送起始位。
接下来就是按照每个状态会通过总线发送几bit的数据来控制状态的跳转。其中校验位通过顶层来设置有无校验或者奇偶校验。
如果代码中的CHECK_BIT
是无校验的话,发送完8bit的数据就会直接发送停止位。有校验位就会先跳转到发送校验位的状态,再发停止位。
assign check_val = (CHECK_BIT == "Odd")? ~^rx_data : ^rx_data;
上边的代码就是发送的校验位。上一章已经提过了,奇校验和偶校验指
若是奇校验,则要保证发送的数据中“1”的bit数是奇数个。
若是偶校验,则要保证发送的数据中“1”的bit数是偶数个。
在代码中的写法就是“奇同偶异”,
奇校验就是将8bit的数据进行按位同或;
偶校验就是将8bit的数据进行按位异或;
例如我想写入一个:11010001。下面图中的红色就是数据中的每一位,黑色就是异或后得到的值。异或就是相同为0,不同为1
。最后得到的值就是0。校验位就是0。和8bit数据和在一起就是110100010。一共有4位1,符合偶校验的要求。
仔细看上边。
下面就是完整的代码。基本上注释写的差不多,有哪些不懂得发评论区或加qq:35二4291零六5
。
//---------<模块及端口声名>------------------------------------------------------
module rx(
input clk ,
input rst_n ,
input rx ,
output reg [7:0] rx_data ,
output rx_data_vld
);
//---------<参数定义>---------------------------------------------------------
parameter CHECK_BIT = "None" ;
parameter BPS = 115200 ;
parameter CLK = 50_000_000 ;
parameter BPS_MAX = CLK/BPS ;
//---------<内部信号定义>-----------------------------------------------------
localparam IDLE = 'b00001 ,//初始状态
START = 'b00010 ,//发送起始位
DATA = 'b00100 ,//发送数据
CHECK = 'b01000 ,//发送校验位
STOP = 'b10000 ;//发送停止位
reg [4:0] state_c ;//现态
reg [4:0] state_n ;//次态
wire idle2start ;
wire start2data ;
wire data2stop ;
wire data2check ;
wire check2stop ;
wire stop2idle ;
//波特率计数器
reg [12:0] cnt_bps ;
wire add_cnt_bps ;
wire end_cnt_bps ;
//bit计数器
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;
//校验位
reg rev_check ;
wire check_val ;
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*)begin
case (state_c)
IDLE : begin
if (idle2start) begin
state_n = START ;
end
else begin
state_n = state_c ;
end
end
START : begin
if (start2data) begin
state_n = DATA ;
end
else begin
state_n = state_c ;
end
end
DATA : begin
if (data2stop) begin
state_n = STOP ;
end
else if (data2check) begin
state_n = CHECK ;
end
else begin
state_n = state_c ;
end
end
CHECK : begin
if (check2stop) begin
state_n = STOP ;
end
else begin
state_n = state_c ;
end
end
STOP : begin
if (stop2idle) begin
state_n = IDLE ;
end
else begin
state_n = state_c ;
end
end
default: state_n = IDLE ;
endcase
end
//第三段:描述输出,时序逻辑或组合逻辑皆可
assign idle2start = state_c == IDLE && rx == 0 ;
assign start2data = state_c == START && end_cnt_bps ;
assign data2stop = state_c == DATA && end_cnt_bit && CHECK_BIT == "None" ;
assign data2check = state_c == DATA && end_cnt_bit ;
assign check2stop = state_c == CHECK && end_cnt_bps ;
assign stop2idle = state_c == STOP && end_cnt_bps ;
//****************************************************************
//--波特率计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 'd0 ;
end
else if(add_cnt_bps)begin
if(end_cnt_bps)begin
cnt_bps <= 'd0 ;
end
else begin
cnt_bps <= cnt_bps + 1'b1 ;
end
end
end
assign add_cnt_bps = state_c != IDLE ;
assign end_cnt_bps = add_cnt_bps && cnt_bps == BPS_MAX - 1;
//****************************************************************
//--位数
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0 ;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0 ;
end
else begin
cnt_bit <= cnt_bit + 1'b1 ;
end
end
end
assign add_cnt_bit = end_cnt_bps && state_c == DATA ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;
always @(*)begin
case (state_c)
IDLE : bit_max <= 1 ;
START : bit_max <= 1 ;
DATA : bit_max <= 8 ;
CHECK : bit_max <= 1 ;
STOP : bit_max <= 1 ;
default: bit_max <= 1 ;
endcase
end
//****************************************************************
//--接受
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0;
end
else begin
case (state_c)
IDLE : rx_data <= 0 ;
START : rx_data <= rx_data ;
DATA : if(cnt_bps == BPS_MAX / 2)
rx_data[cnt_bit] <= rx ;
CHECK : rx_data <= rx_data ;
STOP : rx_data <= rx_data ;
default: rx_data <= 0 ;
endcase
end
end
//****************************************************************
//--接收校验位
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rev_check <= 'd0;
end
else if (state_c == CHECK && cnt_bps == BPS_MAX / 2) begin
rev_check <= rx;
end
else begin
rev_check <= rev_check;
end
end
//****************************************************************
//--计算校验位
//****************************************************************
assign check_val = (CHECK_BIT == "Odd")? ~^rx_data : ^rx_data;
assign rx_data_vld = (CHECK_BIT == "None") ? (state_c == DATA && end_cnt_bit)
: ((state_c == CHECK && end_cnt_bps) && (check_val == rev_check)) ? 1
: 0;
endmodule
2.TX
TX发送模块是指由FPGA芯片将接收到的数据发送回PC端 串口助手上。
这个模块的状态机和校验位基本都一样,也没啥说的。上代码
//---------<模块及端口声名>------------------------------------------------------
module tx(
input clk ,
input rst_n ,
input [7:0] tx_data ,
input tx_data_vld ,
output ready ,
output reg tx
);
//---------<参数定义>---------------------------------------------------------
localparam IDLE = 'b00001 ,//初始状态
START = 'b00010 ,//发送起始位
DATA = 'b00100 ,//发送数据
CHECK = 'b01000 ,//发送校验位
STOP = 'b10000 ;//发送停止位
parameter CHECK_BIT = "None";
parameter BPS = 115200 ;
parameter CLK = 50_000_000 ;
parameter BPS_MAX = CLK/BPS ;
//---------<内部信号定义>-----------------------------------------------------
reg [4:0] state_c ;//现态
reg [4:0] state_n ;//次态
wire idle2start;
wire start2data;
wire data2stop ;//有校验位
wire data2check;//无校验位
wire check2stop;
wire stop2idle ;
reg [12:0] cnt_bps ;
wire add_cnt_bps ;
wire end_cnt_bps ;
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;
reg [7:0] tx_data_t ;
wire check_vld ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_t <= 'd0;
end
else if(tx_data_vld)begin
tx_data_t <= tx_data ;
end
else begin
tx_data_t <= tx_data_t ;
end
end
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*)begin
case (state_c)
IDLE : begin
if (idle2start) begin
state_n = START ;
end
else begin
state_n = state_c ;
end
end
START : begin
if (start2data) begin
state_n = DATA ;
end
else begin
state_n = state_c ;
end
end
DATA : begin
if (data2stop) begin
state_n = STOP ;
end
else if (data2check) begin
state_n = CHECK ;
end
else begin
state_n = state_c ;
end
end
CHECK : begin
if (check2stop) begin
state_n = STOP ;
end
else begin
state_n = state_c ;
end
end
STOP : begin
if (stop2idle) begin
state_n = IDLE ;
end
else begin
state_n = state_c ;
end
end
default: state_n = IDLE ;
endcase
end
//第三段:描述输出,时序逻辑或组合逻辑皆可
assign idle2start = state_c == IDLE && tx_data_vld ;
assign start2data = state_c == START && end_cnt_bps ;
assign data2stop = state_c == DATA && end_cnt_bit && CHECK_BIT == "None" ;
assign data2check = state_c == DATA && end_cnt_bit ;
assign check2stop = state_c == CHECK && end_cnt_bps ;
assign stop2idle = state_c == STOP && end_cnt_bps ;
//****************************************************************
//--波特率计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 'd0 ;
end
else if(add_cnt_bps)begin
if(end_cnt_bps)begin
cnt_bps <= 'd0 ;
end
else begin
cnt_bps <= cnt_bps + 1'b1 ;
end
end
end
assign add_cnt_bps = state_c != IDLE ;
assign end_cnt_bps = add_cnt_bps && cnt_bps == BPS_MAX - 1;
//****************************************************************
//--位数
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0 ;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0 ;
end
else begin
cnt_bit <= cnt_bit + 1'b1 ;
end
end
end
assign add_cnt_bit = end_cnt_bps && state_c == DATA ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;
always @(*)begin
case (state_c)
IDLE : bit_max <= 1 ;
START : bit_max <= 1 ;
DATA : bit_max <= 8 ;
CHECK : bit_max <= 1 ;
STOP : bit_max <= 1 ;
default: bit_max <= 1 ;
endcase
end
//****************************************************************
//--校验
//****************************************************************
assign check_vld = (CHECK_BIT == "Odd")? ~^tx_data_t : ^tx_data_t;
//****************************************************************
//--输出
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx <= 1;
end
else case (state_c)
IDLE : tx <= 1;//初始状态下总线拉高
START : tx <= 0;//起始位将总线拉低1bit
DATA : tx <= tx_data_t[cnt_bit];//低位先发
CHECK : tx <= check_vld;//有校验位的话就对校验位进行发送
STOP : tx <= 1;//停止位拉高
default: tx <= 1;
endcase
end
assign ready = state_c == IDLE ;
endmodule
3.CTRL
控制模块主要是将数据通过fifo进行缓存,没有这个模块也可以,就是在top里多些几行控制数据传输的代码就行。fifo的调用不会直接看我之前的文章。
点个传送门:fifo IP核调用
有的作者写的不加fifo串口回环也很简单,可以多找一些看一看。
//---------<模块及端口声名>------------------------------------------------------
module ctrl(
input clk ,
input rst_n ,
input ready ,
input [7:0] rx_data ,
input rx_data_vld ,
output [7:0] tx_data ,
output tx_data_vld
);
//---------<参数定义>---------------------------------------------------------
wire fifo_full ;
wire fifo_empty ;
//---------<内部信号定义>-----------------------------------------------------
fifo fifo_inst (
.aclr ( ~rst_n ),
.data ( rx_data ),
.wrclk ( clk ),
.wrreq ( rx_data_vld && ~fifo_full ),
.wrfull ( fifo_full ),
.q ( tx_data ),
.rdclk ( clk ),
.rdreq ( ready && ~fifo_empty ),
.rdempty ( fifo_empty ),
.rdusedw ( ),
.wrusedw ( )
);
assign tx_data_vld = ready && ~fifo_empty;
endmodule
4.TOP
defparam rx_inst.CHECK_BIT = "Odd";//"Odd"奇校验 "Even"偶校验 "None"无校验位
defparam tx_inst.CHECK_BIT = "Odd";//"Odd"奇校验 "Even"偶校验 "None"无校验位
这里就是控制代码使用校验位的代码。tx和rx一定要在同一种校验下进行传输,不然数据会出错。
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
input rx ,
output tx
);
//---------<参数定义>---------------------------------------------------------
wire [7:0] rx_data ;
wire rx_data_vld ;
wire [7:0] tx_data ;
wire tx_data_vld ;
wire ready ;
//---------<内部信号定义>-----------------------------------------------------
defparam rx_inst.CHECK_BIT = "Odd";//"Odd"奇校验 "Even"偶校验 "None"无校验位
defparam tx_inst.CHECK_BIT = "Odd";//"Odd"奇校验 "Even"偶校验 "None"无校验位
rx rx_inst(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.rx (rx ),
/*output [7:0] */.rx_data (rx_data ),
/*output */.rx_data_vld (rx_data_vld )
);
tx tx_inst(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [7:0] */.tx_data (tx_data ),
/*input */.tx_data_vld (tx_data_vld ),
/*output */.ready (ready ),
/*output */.tx (tx )
);
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 */.ready (ready ),
/*output [7:0] */.tx_data (tx_data ),
/*output */.tx_data_vld (tx_data_vld )
);
endmodule
5.仿真
`timescale 1ns/1ns
module top_tb();
//激励信号定义
reg clk;
reg rst_n;
reg tx_data_vld;
reg [7:0]tx_data;
//输出信号定义
wire ready;
wire tx;
wire tx_2;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
tx tx_inst(
/* input wire */.clk (clk ),
/* input wire */.rst_n (rst_n ),
/* input wire [7:0] */.tx_data (tx_data ),
/* input wire */.tx_data_vld (tx_data_vld ),
/* output wire */.ready (ready ),
/* output reg */.tx (tx )
);
top top_inst(
/* input wire */.clk (clk ),
/* input wire */.rst_n (rst_n ),
/* input wire */.rx (tx ),
/* output wire */.tx (tx_2 )
);
//产生时钟
always #(CLOCK_CYCLE / 2) clk = ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
#(CLOCK_CYCLE * 2);
rst_n = 1'b1;
end
//产生激励
initial begin
tx_data = 0;
tx_data_vld = 0;
#(CLOCK_CYCLE * 5);
repeat(16) begin
tx_data_vld = 1;
tx_data = {$random};
#20;
tx_data_vld = 0;
wait(ready == 1);
#20;
end
#(CLOCK_CYCLE * 50);
$stop;
end
endmodule
仿真代码加在这里,结果没错,反正网站上的图片也不能放大,就不放仿真图片了,可以自己去看。
温馨提示:本人不放仿真图片一是因为网站上的图片不能放大,看不清楚;二是懒。但是懂得看仿真是学会FPGA很重要的手段。如果各位看这个仿真有哪里不懂,记得加qq问,我可以解释。
总结
代码设置的奇校验,视频中可以看出来当串口助手不用奇校验时数据都是出错的。
UART串口回环
摄像头花了,有点模糊,各位在试验代码的时候如果发生错误记得说。
代码工程都放进baidu网盘了,自行提取(之前的一些baidu网盘链接失效了,我把我所有文章的链接都换了)文章来源:https://www.toymoban.com/news/detail-830069.html
链接:https://pan.baidu.com/s/1lQqqWZXfb3i6XHwkKf52zg
提取码:yang文章来源地址https://www.toymoban.com/news/detail-830069.html
到了这里,关于FPGA实战 -- UART --- 实现串口回环(加FIFO)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!