欢迎来到FPGA专栏~串口接收模块设计与验证(工业环境)
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒
- ✨博客主页:小夏与酒的博客
- 该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正
- 欢迎大家关注! ❤️
效果展示:
接收展示:
PC机的串口助手发送数据,FPGA接收数据并将数据显示到数码管上(hex格式数据):
代码展示:
串口接收模块:uart_byte_rx.v
module uart_byte_rx(
input Clk,//50M
input Rst_n,
input [2:0] baud_set,
input data_rx,
output reg [7:0] data_byte,
output reg Rx_Done
);
reg s0_Rx,s1_Rx;//同步寄存器
reg tmp0_Rx,tmp1_Rx;//数据寄存器
reg [15:0]bps_DR;//分频计数器计数最大值
reg [15:0]div_cnt;//分频计数器
reg bps_clk;//波特率时钟
reg [7:0]bps_cnt;
reg uart_state;
reg [2:0] r_data_byte [7:0];
reg [2:0]START_BIT;
reg [2:0]STOP_BIT;
wire nedge;
//--------<同步寄存器处理>--------
//用于消除亚稳态
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
s0_Rx <= 1'b0;
s1_Rx <= 1'b0;
end
else begin
s0_Rx <= data_rx;
s1_Rx <= s0_Rx;
end
end
//--------<数据寄存器处理>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
tmp0_Rx <= 1'b0;
tmp1_Rx <= 1'b0;
end
else begin
tmp0_Rx <= s1_Rx;
tmp1_Rx <= tmp0_Rx;
end
end
//--------<下降沿检测>--------
assign nedge = !tmp0_Rx & tmp1_Rx;
//----------------
//得到不同计数周期的计数器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
div_cnt <= 16'd0;
else if(uart_state)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
end
//----------------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
end
//----------------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_cnt <= 8'd0;
else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2)))
bps_cnt <= 8'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
//----------------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Rx_Done <= 1'b0;
else if(bps_cnt == 8'd159)
Rx_Done <= 1'b1;
else
Rx_Done <= 1'b0;
end
//--------<波特率查找表>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_DR <= 16'd324;
else begin
case(baud_set)
0:bps_DR <= 16'd324;
1:bps_DR <= 16'd162;
2:bps_DR <= 16'd80;
3:bps_DR <= 16'd53;
4:bps_DR <= 16'd26;
default:bps_DR <= 16'd324;
endcase
end
end
//--------<采样数据接收模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
else if(bps_clk)begin
case(bps_cnt)
0:begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rx;
22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rx;
38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rx;
54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rx;
70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rx;
86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rx;
102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rx;
118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rx;
134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rx;
150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rx;
default:begin
START_BIT <= START_BIT;
r_data_byte[0] <= r_data_byte[0];
r_data_byte[1] <= r_data_byte[1];
r_data_byte[2] <= r_data_byte[2];
r_data_byte[3] <= r_data_byte[3];
r_data_byte[4] <= r_data_byte[4];
r_data_byte[5] <= r_data_byte[5];
r_data_byte[6] <= r_data_byte[6];
r_data_byte[7] <= r_data_byte[7];
STOP_BIT <= STOP_BIT;
end
endcase
end
end
//--------<数据状态判定模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
data_byte <= 8'd0;
else if(bps_cnt == 8'd159)begin
data_byte[0] <= r_data_byte[0][2];
data_byte[1] <= r_data_byte[1][2];
data_byte[2] <= r_data_byte[2][2];
data_byte[3] <= r_data_byte[3][2];
data_byte[4] <= r_data_byte[4][2];
data_byte[5] <= r_data_byte[5][2];
data_byte[6] <= r_data_byte[6][2];
data_byte[7] <= r_data_byte[7][2];
end
else
;
end
//----------------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
uart_state <= 1'b0;
else if(nedge)
uart_state <= 1'b1;
else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2)))
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
endmodule
有关于串口发送模块设计的相关文章如下:
【FPGA零基础学习之旅#13】串口发送模块设计与验证。
【FPGA零基础学习之旅#14】串口发送字符串。
在串口通信的一般应用中,采集每一位数据中间时刻的电平即认为是此位数据的电平。
但是在实际工业应用中,现场往往有非常强的电磁干扰,只采样一次就作为该数据的电平状态是不可靠的。很有可能恰好采集到被干扰的信号而导致结果出错,因此提出以下改进型的单 bit 数据接收方式示意图,使用多次采样求概率的方式进行状态判定:
将每一位数据再平均分成了 16 小段。对于 Bit_x 这一位数据,考虑到数据在刚刚发生变化和即将发生变化的这一时期,数据极有可能不稳定的(用深灰色标出的两段),在这两个时间段采集数据,很有可能得到错误的结果,因此判定这两段时间的电平无效,采集时直接忽略。而中间这一时间段(用浅灰色标出),数据本身是比较稳定的,一般都代表了正确的结果。也就是前面提到的中间测量方式,但是也不排除该段数据受强电磁干扰而出现错误的电平脉冲。因此对这一段电平,进行多次采样,并求高低电平发生的概率,6 次采集结果中,取出现次数多的电平作为采样结果。例如,采样 6 次的结果分别为 1/1/1/1/0/1/,则取电平结果为 1,若为 0/0/1/0/0/0,,则取电平结果为 0,当 6 次采样结果中 1 和 0 各占一半(各 3 次),则可判断当前通信线路环境非常恶劣,数据不具有可靠性,不进行处理。
信号名称 | 功能描述 |
---|---|
Clk | 系统时钟 50Mhz |
Rst_n | 系统复位信号 |
baud_set | 波特率选择信号 |
data_rx | 串行数据输入 |
data_byte | 并行数据输出 |
Rx_Done | 接收结束信号 |
Verilog HDL的一维数组讲解参考:verilog数组的定义、转换和加法器的实现。
在代码的编写过程中,可以参考串口发送数据时的设计框架:
其中,采样时钟的计算变为如下:
代码中较难理解的部分如下:
//--------<采样数据接收模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
else if(bps_clk)begin
case(bps_cnt)
0:begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rx;
22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rx;
38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rx;
54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rx;
70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rx;
86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rx;
102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rx;
118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rx;
134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rx;
150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rx;
default:begin
START_BIT <= START_BIT;
r_data_byte[0] <= r_data_byte[0];
r_data_byte[1] <= r_data_byte[1];
r_data_byte[2] <= r_data_byte[2];
r_data_byte[3] <= r_data_byte[3];
r_data_byte[4] <= r_data_byte[4];
r_data_byte[5] <= r_data_byte[5];
r_data_byte[6] <= r_data_byte[6];
r_data_byte[7] <= r_data_byte[7];
STOP_BIT <= STOP_BIT;
end
endcase
end
end
//--------<数据状态判定模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
data_byte <= 8'd0;
else if(bps_cnt == 8'd159)begin
data_byte[0] <= r_data_byte[0][2];
data_byte[1] <= r_data_byte[1][2];
data_byte[2] <= r_data_byte[2][2];
data_byte[3] <= r_data_byte[3][2];
data_byte[4] <= r_data_byte[4][2];
data_byte[5] <= r_data_byte[5][2];
data_byte[6] <= r_data_byte[6][2];
data_byte[7] <= r_data_byte[7][2];
end
else
;
end
上述部分的代码需要紧密结合模块设计思路和verilog一维数组的语法来理解。
特别是在进行数据状态的判定中一直选择读取一维数组中每位数据的第三位:
data_byte[0] <= r_data_byte[0][2];
data_byte[1] <= r_data_byte[1][2];
data_byte[2] <= r_data_byte[2][2];
data_byte[3] <= r_data_byte[3][2];
data_byte[4] <= r_data_byte[4][2];
data_byte[5] <= r_data_byte[5][2];
data_byte[6] <= r_data_byte[6][2];
data_byte[7] <= r_data_byte[7][2];
编写思想举例如下:
对一位数据需进行 6 次采样,然后取出现次数较多的数据作为采样结果,也就是说,6 次采样中出现次数多于 3 次的数据才能作为最终的有效数据。对此,可以用接收到数据 r_data_byte[n]结合数值比较器来判断,也可以直接令其等于当前位的最高位数据。
以下面例子说明:当 r_data_byte[n]分别为二进制的 011B/010B/100B/101B时,这几个数据十进制格式分别为 3d/2d/4d/5d,可以发现大于等 4d 的为 100B/101B。当最高位是 1 即此时的数据累加值大于等于 4d,可以说明数据真实值为 1;当最高位是 0 即此时的数据累加值小于等于 3d,可以说明数据真实值为 0,因此只需判断最高位即可。
我们将该模块与串口发送模块(串口发送模块讲解)放到一起来进行仿真测试:
uart_byte_rx_tb.v:
`timescale 1ns/1ns
`define clock_period 20
module uart_byte_rx_tb;
reg Clk;
reg Rst_n;
reg [2:0]baud_set;
reg [7:0]data_byte;
reg send_en;
wire uart_tx;
wire [7:0]data_byte_r;
wire uart_state;
wire Tx_Done;
wire Rx_Done;
uart_byte_tx uart_byte_tx0(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.Tx_Done(Tx_Done),
.uart_state(uart_state)
);
uart_byte_rx uart_byte_rx0(
.Clk(Clk),
.Rst_n(Rst_n),
.baud_set(baud_set),
.data_rx(uart_tx),
.data_byte(data_byte_r),
.Rx_Done(Rx_Done)
);
initial Clk = 1;
always#(`clock_period / 2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
data_byte = 8'd0;
send_en = 1'd0;
baud_set = 3'd4;
#(`clock_period*20 + 1 );
Rst_n = 1'b1;
#(`clock_period*50);
data_byte = 8'haa;
send_en = 1'd1;
#`clock_period;
send_en = 1'd0;
@(posedge Tx_Done)
#(`clock_period*5000);
data_byte = 8'h55;
send_en = 1'd1;
#`clock_period;
send_en = 1'd0;
@(posedge Tx_Done)
#(`clock_period*5000);
$stop;
end
endmodule
仿真结果:
板级验证:
关于issp调试工具ip核的创建和使用参考:【FPGA零基础学习之旅#11】数码管动态扫描。注意在本次项目中使用的是issp的探针(probe) 功能,而非源;
编写板级验证代码:
module uart_byte_rx_top(
input Clk,
input Rst_n,
input data_rx
);
reg [7:0]data_byte_r;
wire [7:0]data_byte;
wire Rx_Done;
uart_byte_rx uart_byte_rx0(
.Clk(Clk),
.Rst_n(Rst_n),
.baud_set(3'd0),
.data_rx(data_rx),
.data_byte(data_byte),
.Rx_Done(Rx_Done)
);
issp issp(
.probe(data_byte_r),
.source()
);
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
data_byte_r <= 8'd0;
else if(Rx_Done)
data_byte_r <= data_byte;
else
data_byte_r <= data_byte_r;
end
endmodule
其中,如下部分代码是对接收到的数据进行简单的缓存操作,避免出现数据读取出错的情况:
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
data_byte_r <= 8'd0;
else if(Rx_Done)
data_byte_r <= data_byte;
else
data_byte_r <= data_byte_r;
end
jic文件配置完成:
在issp调试工具的界面中选择连续读取数据:
FPGA通过串口通信,将接收到的数据(hex格式)显示到数码管和issp调试工具上。
电脑串口助手发送hex数据,issp接收到数据并将数据显示到数码管上:
先通过RTL视图来理清思路:
FPGA通过串口通信(uart_byte_rx),先将接收到的数据进行简单的缓存操作(Data_r),再将数据依次传递给HEX8模块和issp调试模块,其中HEX8模块将数据处理好之后再传给移位寄存器模块(m74HC595_Drive)。
关于HEX8模块的讲解:【FPGA零基础学习之旅#11】数码管动态扫描。
关于移位寄存器模块的讲解:【FPGA零基础学习之旅#12】三线制数码管驱动(74HC595)串行移位寄存器驱动。
先将数据缓存部分的代码封装为一个模块:
Data_r.v:
module Data_r(
input Clk,
input Rst_n,
input Rx_Done,
input [7:0] data_byte,
output reg [7:0] data_byte_r
);
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
data_byte_r <= 8'd0;
else if(Rx_Done)
data_byte_r <= data_byte;
else
data_byte_r <= data_byte_r;
end
endmodule
展示顶层模块:
uart_byte_rx_hex8_top.v:
module uart_byte_rx_hex8_top(
input Clk,
input Rst_n,
input data_rx,
output SH_CP, //shift clock---------------SCK
output ST_CP, //latch data clock----------RCK
output DS //shift serial data---------Data
);
wire [7:0] data_byte_r;
wire [7:0] data_byte;
wire Rx_Done;
wire [7:0] sel;//数码管位选(选择当前要显示的数码管)
wire [7:0] seg;//数码管段选(当前要显示的内容)
uart_byte_rx uart_byte_rx(
.Clk(Clk),
.Rst_n(Rst_n),
.baud_set(3'd0),
.data_rx(data_rx),
.data_byte(data_byte),
.Rx_Done(Rx_Done)
);
Data_r Data_r(
.Clk(Clk),
.Rst_n(Rst_n),
.Rx_Done(Rx_Done),
.data_byte(data_byte),
.data_byte_r(data_byte_r)
);
issp issp(
.probe(data_byte_r),
.source()
);
//由于串口目前只接收一个字节数据,只占用2个数码管,所以其余数码管显示0
HEX8 HEX8(
.Clk(Clk),
.Rst_n(Rst_n),
.En(1'b1),
.disp_data({24'h0,data_byte_r}),
.sel(sel),
.seg(seg)
);
m74HC595_Driver m74HC595_Driver(
.Clk(Clk),
.Rst_n(Rst_n),
.Data({seg,sel}),
.S_EN(1'b1),
.SH_CP(SH_CP),
.ST_CP(ST_CP),
.DS(DS)
);
endmodule
(顶层模块中其余模块的设计代码直接从上述参考文章中复制过来即可)
jic文件配置完成:
最终实现效果:
结尾
- ❤️ 感谢您的支持和鼓励!
- 您可能感兴趣的内容:
- 【FPGA零基础学习之旅#14】串口发送字符串
- 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制