基于FPGA的串口指令帧接收与解析的verilog代码
网上的verilog串口指令帧接收与解析源码很多,但大多数都说不到点子上,对初学者来说很不友好,今天分享一个自己调通的小工程。
串口指令帧格式如下:
文章图片
串口接收模块直接用的正点原子的源码,个人感觉正点原子的代码虽然写得冗杂,但严谨性还行,数据在波特率计数周期的中间点采集,源码如下:
module uart_recv(
inputsys_clk,//系统时钟
inputsys_rst_n,//系统复位,低电平有效
(*mark_debug ="true"*) inputuart_rxd,//UART接收端口
(*mark_debug ="true"*) outputreguart_done,//接收一帧数据完成标志信号
(*mark_debug ="true"*) outputreg [7:0] uart_data//接收的数据
);
//parameter define
parameterCLK_FREQ = 100_000_000;
//系统时钟频率
parameterUART_BPS = 230400;
//串口波特率
localparam BPS_CNT= CLK_FREQ/UART_BPS;
//为得到指定波特率,
//需要对系统时钟计数BPS_CNT次
//reg define
reguart_rxd_d0;
reguart_rxd_d1;
reg [15:0] clk_cnt;
//系统时钟计数器
reg [ 3:0] rx_cnt;
//接收数据计数器
regrx_flag;
//接收过程标志信号
reg [ 7:0] rxdata;
//接收数据寄存器//wire define
wirestart_flag;
//*****************************************************
//**main code
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assignstart_flag = uart_rxd_d1 & (~uart_rxd_d0);
//对UART接收端口的数据延迟两个时钟周期
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//当脉冲信号start_flag到达时,进入接收过程
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_flag <= 1'b0;
else begin
if(start_flag)//检测到起始位
rx_flag <= 1'b1;
//进入接收过程,标志位rx_flag拉高
else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
rx_flag <= 1'b0;
//计数到停止位中间时,停止接收过程
else
rx_flag <= rx_flag;
end
end//进入接收过程后,启动系统时钟计数器与接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
clk_cnt <= 16'd0;
rx_cnt<= 4'd0;
end
else if ( rx_flag ) begin//处于接收过程
if (clk_cnt < BPS_CNT - 1) begin
clk_cnt <= clk_cnt + 1'b1;
rx_cnt<= rx_cnt;
end
else begin
clk_cnt <= 16'd0;
//对系统时钟计数达一个波特率周期后清零
rx_cnt<= rx_cnt + 1'b1;
//此时接收数据计数器加1
end
end
else begin//接收过程结束,计数器清零
clk_cnt <= 16'd0;
rx_cnt<= 4'd0;
end
end//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if ( !sys_rst_n)
rxdata <= 8'd0;
else if(rx_flag)//系统处于接收过程
if (clk_cnt == BPS_CNT/2) begin//判断系统时钟计数器计数到数据位中间
case ( rx_cnt )
4'd1 : rxdata[0] <= uart_rxd_d1;
//寄存数据位最低位
4'd2 : rxdata[1] <= uart_rxd_d1;
4'd3 : rxdata[2] <= uart_rxd_d1;
4'd4 : rxdata[3] <= uart_rxd_d1;
4'd5 : rxdata[4] <= uart_rxd_d1;
4'd6 : rxdata[5] <= uart_rxd_d1;
4'd7 : rxdata[6] <= uart_rxd_d1;
4'd8 : rxdata[7] <= uart_rxd_d1;
//寄存数据位最高位
default:;
endcase
end
else
rxdata <= rxdata;
else
rxdata <= 8'd0;
end//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_data <= 8'd0;
uart_done <= 1'b0;
end
else if(rx_cnt == 4'd9) begin//接收数据计数器计数到停止位时
uart_data <= rxdata;
//寄存输出接收到的数据
uart_done <= 1'b1;
//并将接收完成标志位拉高
end
else begin
//uart_data <= 8'd0;
uart_data <= uart_data;
uart_done <= 1'b0;
end
endendmodule
每当接收到一个字节的数据,uart_done就会拉高一个时钟周期,同时输出一个字节的数据;
串口数据帧解析可分为6个状态:
接收到AA–>状态1;
接收到BB–>状态2;
接收完4个字节的数据–>状态3;
接收到和校验–>状态4;
接收到CC–>状态5;
接收到DD–>状态6;
设置一个计数器rx_done_cnt,初始值0,当uart_done 拉高时计数一次,由此可知:
当收到AA时,rx_done_cnt=1;
当收到BB时,rx_done_cnt=2;
当收到DATA1时,rx_done_cnt=3;
当收到DATA2时,rx_done_cnt=4;
当收到DATA3时,rx_done_cnt=5;
当收到DATA4时,rx_done_cnt=6;
当收到和校验时,rx_done_cnt=7;
当收到CC时,rx_done_cnt=8;
当收到DD时,rx_done_cnt=9;
当收到rx_done_cnt=9时,rx_done_cnt清零,准备下一帧数据接收;
那么,状态机status,初始值status=0,的逻辑也就清晰了,如下:
当status=0&&rx_done_cnt=1&&uart_data=https://www.it610.com/article/AA时,status=1;–>收到了AA;
当status=1&&rx_done_cnt=2&&uart_data=https://www.it610.com/article/BB时,status=2;–>收到了BB;
当status=2&&uart_data=https://www.it610.com/article/AA时,status=3;–>收到了4个字节的数据;
当status=3&&data_sum_ok=1时,status=4;–>和校验正确;
当status=4&&rx_done_cnt=8&&uart_data=https://www.it610.com/article/CC时,status=5;–>收到了BB;
当status=5&&rx_done_cnt=9&&uart_data=https://www.it610.com/article/DD时,status=6;–>收到了BB;
如此设计,每个状态的值和跳转条件都与上一个状态密切相关,这样就可以保证,只要一帧数据错一个字节甚至是一个bit,状态机就会失效,接收就会失败,最大限度的保证了接收的正确性;我的该部分源码如下:
/status/
always @(posedge clk_100m) begin
if(!rst_n) status<=8'd0;
else if(status==8'd0&&rx_done_cnt==8'd1 && r_rx_data=https://www.it610.com/article/=8'haa) status<=8'd1;
else if(status==8'd1&&rx_done_cnt==8'd2 && r_rx_data=https://www.it610.com/article/=8'hbb) status<=8'd2;
//进入接收有效数据阶段
else if(status==8'd2&&rx_done_cnt==8'd7) status<=8'd3;
//进入和校验阶段
else if(status==8'd3&&data_sum_ok==1'd1) status<=8'd4;
//校验和正确
else if(status==8'd4&&rx_done_cnt==8'd8 && r_rx_data=https://www.it610.com/article/=8'hcc) status<=8'd5;
else if(status==8'd5&&rx_done_cnt==8'd9 && r_rx_data=https://www.it610.com/article/=8'hdd) status<=8'd6;
else if(status==8'd6) status<=8'd0;
end
/status/
当状态机status=6时,输出有效数据和有效脉冲,设计如下:
/valid_data_out/
always @(posedge clk_100m) begin
if(!rst_n) o_x_data<='d0;
else if(status==8'd6) o_x_data<={r_data1,r_data2,r_data3,r_data4};
end
/valid_data_out//valid_data_out_req/
always @(posedge clk_100m) begin
if(!rst_n) o_req<='d0;
else if(status==8'd6) o_req<=1'd1;
else o_req<='d0;
end
/valid_data_out_req/
【基于FPGA的串口指令帧接收与解析的verilog代码】最后:整个源代码并未给出,目的是给兄弟们一个练手的机会,因为这个很简单,加之我的讲解,应该问题不大,如果你在自身尝试的基础上,实在搞不出来,可以加我VX要源码:hllsq22 我也可以给你适当的技术支持,教你怎么写。
推荐阅读
- FPGA|【FPGA】UART串口通信---基于FIFO
- FPGA学习笔记|FPGA学习笔记——知识点总结
- 前端|逻辑门图解—与门、或门、非门、与非门、或非门、异或门、同或门
- 利用SPI协议配置AD9361寄存器
- 服务器|【SOC】经典输出hello world
- pango logos 双启动
- Verilog|if-else(if判断语句和执行语句之间相差1个时钟周期吗(并行执行吗?有优先级吗?))
- FPGA|Xilinx FPGA 使用Microblaze实现串口命令行
- #|【数字IC】深入浅出理解AXI协议