FPGA利用UART串口实现一次接收多个数据并进行解析协议帧
·
基于野火的编写的UART接收模块,在FPGA上实现一次接收多个数据并对数据进行解析。
一、串口接收模块
野火UART串口接收模块源码
`timescale 1ns/1ns
////////////////////////////////////////////////////////////////////////
// Author : EmbedFire
//Create Date : 2019/06/12
// Module Name : uart_rx
// Project Name : rs485
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description :
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
////////////////////////////////////////////////////////////////////////
module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据
output reg [7:0] po_data , //串转并后的8bit数据
output reg po_flag //串转并后的数据有效标志信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//reg define
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_nedge ;
reg work_en ;
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(start_nedge == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;
//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {rx_reg3, rx_data[7:1]};
//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule
二、数据接收解析
串口数据帧包括:帧头aabb(2个字节)+数据1(1个字节)+数据2(4个字节)+数据3(4个字节)+校验和(1个字节)+帧尾ccdd (2个字节)。接收采用状态机实现,虽然有8个状态但是实际只用了7个状态。
DLE (空闲状态)等待接收,接收到aa时,转移到状态HEADER_1。
HEADER_1(等待bb状态)接收到bb时,转移到状态RX_DATA 进行数据接收,接收到的数据错误转移回DLE 。
HEADER_2(暂未用到) RX_DATA (数据接收状态)接收完数据时,转移到状态SUM_CHECK进行校验和。
SUM_CHECK(校验和)校验和正确转移到END_1,校验和不对转移回DLE 。
END_1(等待cc)接收到cc时,转移到状态END_2,接收到的数据错误转移回DLE 。
END_2(等待dd)接收到dd时,转移到状态RX_DONE ,接收到的数据错误转移回DLE 。
RX_DONE (接收完成)接收完成并进行数据拼接
`timescale 1ns/1ns
module data_analysis
(
input wire sys_clk , //系统时钟,50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire rx , //串口接收数据
output reg [7:0] wave ,
output reg [31:0] freq ,
output reg [31:0] phase ,
output reg o_req //串转并后的数据有效标志信号
);
parameter pkh1 = 8'haa,
pkh2 = 8'hbb,
pkb1 = 8'hcc,
pkb2 = 8'hdd;
localparam DLE = 8'b00000001;
localparam HEADER_1 = 8'b00000010;
localparam HEADER_2 = 8'b00000100;
localparam RX_DATA = 8'b00001000;
localparam SUM_CHECK = 8'b00010000;
localparam END_1 = 8'b00100000;
localparam END_2 = 8'b01000000;
localparam RX_DONE = 8'b10000000;
wire po_flag;
reg data_sum_ok;
reg data_sum_err;
reg rx_done;
reg [7:0] status;
wire [7:0] r_rx_data;
reg [7:0] rx_done_cnt;
reg [31:0] rx_sum;
reg [7:0] r_data;
reg [7:0] r_data0;
reg [7:0] r_data1;
reg [7:0] r_data2;
reg [7:0] r_data3;
reg [7:0] r_data4;
reg [7:0] r_data5;
reg [7:0] r_data6;
reg [7:0] r_data7;
reg [7:0] r_data8;
always @(posedge sys_clk)begin
if (!sys_rst_n)
status <= DLE;
else
case(status)
DLE:
begin
if(r_rx_data == pkh1)
status <= HEADER_1;
else
status <= DLE;
end
HEADER_1:
begin
if(rx_done_cnt == 8'd2 && r_rx_data == pkh2)
status <= RX_DATA;
else if(rx_done_cnt == 8'd2 && r_rx_data != pkh2)
status <= DLE;
else
status <= HEADER_1;
end
HEADER_2:
begin
if(rx_done_cnt == 8'd3)
status <= RX_DATA;
else
status <= HEADER_2;
end
RX_DATA:
begin
if(rx_done_cnt == 8'd12)
status <= SUM_CHECK;
else
status <= RX_DATA;
end
SUM_CHECK:
begin
if(data_sum_ok == 1'd1)
begin
if(data_sum_err == 1'd1)
status <= DLE;
else
status <= END_1;
end
else
status <= SUM_CHECK;
end
END_1:
begin
if(rx_done_cnt == 8'd13 && r_rx_data == pkb1)
status <= END_2;
else if(rx_done_cnt == 8'd13&&r_rx_data != pkb1)
status <= DLE;
else
status <= END_1;
end
END_2:
begin
if(rx_done_cnt == 8'd14 && r_rx_data == pkb2)
status <= RX_DONE;
else if(rx_done_cnt == 8'd14&&r_rx_data != pkb2)
status <= DLE;
else
status <= END_2;
end
RX_DONE:
status <= DLE;
default:
status <= DLE;
endcase
end
// 有数据到来,计算接收到的数据个数
always @(posedge sys_clk) begin
if(!sys_rst_n)
rx_done_cnt <= 'd0;
else if(po_flag==8'd1)
rx_done_cnt <= rx_done_cnt + 1'd1;
else if(status == RX_DONE||status == DLE)
rx_done_cnt <= 'd0;
else
rx_done_cnt <= rx_done_cnt;
end
// 数据接收阶段
always @(posedge sys_clk) begin
if(!sys_rst_n)
begin
r_data0 <= 8'd0;
r_data1 <= 8'd0;
r_data2 <= 8'd0;
r_data3 <= 8'd0;
r_data4 <= 8'd0;
r_data5 <= 8'd0;
r_data6 <= 8'd0;
r_data7 <= 8'd0;
r_data8 <= 8'd0;
end
else if(status == RX_DATA && rx_done==8'd1)
case(rx_done_cnt)
8'd3:
r_data0 <= r_rx_data;
8'd4:
r_data1 <= r_rx_data;
8'd5:
r_data2 <= r_rx_data;
8'd6:
r_data3 <= r_rx_data;
8'd7:
r_data4 <= r_rx_data;
8'd8:
r_data5 <= r_rx_data;
8'd9:
r_data6 <= r_rx_data;
8'd10:
r_data7 <= r_rx_data;
8'd11:
r_data8 <= r_rx_data;
default:
r_data <= r_rx_data;
endcase
end
// 数据和校验
always @(posedge sys_clk) begin
if(!sys_rst_n)
begin
data_sum_ok <= 1'd0;
rx_sum <= 'd0;
end
else if(status == SUM_CHECK)
begin
rx_sum <= r_data0+r_data1+r_data2+r_data3+r_data4+r_data5+r_data6+r_data7+r_data8;
data_sum_ok <= 1'd1;
end
else
begin
data_sum_ok <= 1'd0;
rx_sum <= 'd0;
end
end
always @(posedge sys_clk) begin
if(!sys_rst_n)
data_sum_err <= 1'd0;
else if(data_sum_ok == 1'd1)
begin
if (rx_sum[7:0]==r_rx_data)
data_sum_err <= 1'd0;
else
data_sum_err <= 1'd1;
end
else
data_sum_err <= 1'd0;
end
// 完成此次接收
always @(posedge sys_clk) begin
if(!sys_rst_n) o_req <= 'd0;
else if(status == RX_DONE)
o_req <= 1'd1;
else o_req <= 'd0;
end
// 延迟的接收信号
always @(posedge sys_clk) begin
if(!sys_rst_n)
rx_done <= 1'd0;
else if(po_flag==8'd1)
rx_done <= 1'd1;
else
rx_done <= 1'd0;
end
// 接收完成数据封装
always @(posedge sys_clk) begin
if(!sys_rst_n)
begin
wave <= 'd0;
freq <= 'd0;
phase <= 'd0;
end
else if(status == RX_DONE)
begin
wave <= r_data0;
freq <= {r_data1,r_data2,r_data3,r_data4};
phase <= {r_data5,r_data6,r_data7,r_data8};
end
end
uart_rx uart_rx_init
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (rx),
.po_data (r_rx_data), //串转并后的8bit数据
.po_flag (po_flag) //串转并后的数据有效标志信号
);
endmodule
三、仿真文件
仿真波形用了三组数据,分别是校验和正确的数据、校验和错误的数据和校验和正确的数据。仿真波形及源码如下:

`timescale 1ns/1ns
module tb_data_analysis();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg sys_clk;
reg sys_rst_n;
reg rx;
wire[7:0] wave;
wire[31:0] freq;
wire[31:0] phase;
wire o_req;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end
//模拟发送8次数据,分别为0~7
initial begin
#200
rx_bit(8'haa); //任务的调用,任务名+括号中要传递进任务的参数
rx_bit(8'hbb);
rx_bit(8'd0);
rx_bit(8'd1);
rx_bit(8'd1);
rx_bit(8'd1);
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'h0c);
rx_bit(8'hcc);
rx_bit(8'hdd);
#200
rx_bit(8'haa); //任务的调用,任务名+括号中要传递进任务的参数
rx_bit(8'hbb);
rx_bit(8'd0);
rx_bit(8'd11);
rx_bit(8'd12);
rx_bit(8'd13);
rx_bit(8'd14);
rx_bit(8'd25);
rx_bit(8'd28);
rx_bit(8'd27);
rx_bit(8'd24);
rx_bit(8'd35);
rx_bit(8'hbb);
rx_bit(8'hdd);
#200
rx_bit(8'haa); //任务的调用,任务名+括号中要传递进任务的参数
rx_bit(8'hbb);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'd2);
rx_bit(8'd1);
rx_bit(8'd1);
rx_bit(8'd1);
rx_bit(8'd1);
rx_bit(8'h0e);
rx_bit(8'hcc);
rx_bit(8'hdd);
end
//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;
//定义一个名为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;
endcase
#(5208*20); //每发送1位数据延时5208个时钟周期
end
endtask //任务以endtask结束
data_analysis dataAnalysis
(
.sys_clk (sys_clk), //系统时钟,50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.rx (rx), //串口接收数据
.wave (wave), //波形
.freq (freq), //频率
.phase (phase), //相位
.o_req (o_req) //串转并后的数据有效标志信号
);
endmodule
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)