FPGA用verilog HDL实现串口通讯协议

这篇具有很好参考价值的文章主要介绍了FPGA用verilog HDL实现串口通讯协议。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、串口通讯简介

串口通信是一种通过串行传输数据的通信方式。它使用单个数据线将数据位逐个传输,而不是同时传输多个数据位。串口通信常用于连接计算机与外部设备,如打印机、调制解调器、传感器等。

串口通信一般使用的是异步传输方式,即发送方和接收方的时钟不同步。数据传输时,发送方将数据位、起始位、停止位和校验位按照一定的规则组合成数据帧,然后逐位地通过数据线发送。接收方在接收到起始位后开始接收数据位,并在接收到停止位后完成接收。校验位用于检测数据传输的错误。

串口通信有多种标准,常见的包括RS-232、RS-485、UART等。RS-232是一种常见的串口通信标准,它定义了电气特性、信号级别和连接器类型等。RS-485是一种多点通信标准,可以连接多个设备进行通信。UART是一种通用异步收发传输器,用于实现串口通信。

串口通信具有以下特点:

  1. 简单:串口通信只需要一根数据线和几根控制线,连接简单。
  2. 长距离传输:串口通信可以在较长距离上进行数据传输,例如RS-485标准支持最长达1200米的传输距离。
  3. 可靠性高:串口通信使用校验位进行数据的错误检测,可以提高数据传输的可靠性。
  4. 低速传输:串口通信的传输速率相对较低,通常在几十到几百kbps之间,不适用于高速数据传输。

串口通信在许多应用中广泛使用,特别是在嵌入式系统、工业自动化、通信设备等领域。它可以实现设备之间的数据交换和控制,提供了一种简单可靠的通信方式。

二、用verilog编写串口通讯协议

2.1 uart发送部分代码

`timescale 1ns / 1ps



 module uart_tx #
(parameter
 CLK_FREQ = 50_000_000,
 BAUD_RATE = 9600
)
(
input             clk,
input             rst_n,
input wire [7:0]  pi_data,
input wire        pi_flag,
output reg        tx
    );
localparam BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE;
reg [12:0] baud_cnt;
reg [3:0] bit_cnt;
reg work_en;


always @(posedge clk or negedge rst_n)
     if(rst_n == 1'b0) 
      baud_cnt <= 13'b0;
     else if ((baud_cnt == BAUD_CNT_MAX-1) || (work_en == 1'b0))
      baud_cnt <= 13'd0;
     else if(work_en == 1'b1)
      baud_cnt <= baud_cnt + 13'd1;
     else
      baud_cnt <= 13'd0;

always @(posedge clk or negedge rst_n)
     if(rst_n == 1'b0) 
      bit_cnt <= 4'b0;
     else if ((baud_cnt == BAUD_CNT_MAX/2-1) && (work_en == 1'b1))
      bit_cnt <= bit_cnt + 4'd1;
     else if ((bit_cnt == 4'd9)&& (baud_cnt == 13'd1))
      bit_cnt <= 4'd0;
     else
      bit_cnt <= bit_cnt;
      
always @(posedge clk or negedge rst_n)
     if(rst_n == 1'b0) 
      work_en <= 1'b0;
     else if (pi_flag == 1'b1)
      work_en <= 1'b1;
     else if ((bit_cnt == 4'd9) && (baud_cnt == 13'd1))
      work_en <= 1'b0;
     else
      work_en <= work_en;
     
always @(posedge clk) //并转串发送
     if(work_en == 1'b1)begin
     case(bit_cnt)
      1: tx <= 1'b0;
      2: tx <= pi_data[0];
      3: tx <= pi_data[1];
      4: tx <= pi_data[2];
      5: tx <= pi_data[3];
      6: tx <= pi_data[4];
      7: tx <= pi_data[5];
      8: tx <= pi_data[6];
      9: tx <= pi_data[7];
      default
      tx <= 1'b1;
     endcase 
     end
     
     else
     tx <= 1'b1; //空闲

//调用ila模块IP核进行debug            
ila_0 uart_tx (   
	.clk(clk), // input wire clk


	.probe0(clk   ), // input wire [0:0]  probe0  
	.probe1(rst_n ), // input wire [0:0]  probe1 
	.probe2(pi_data), // input wire [7:0]  probe2 
	.probe3(pi_flag), // input wire [0:0]  probe3 
	.probe4(tx     ) // input wire [0:0]  probe4
);     
     
endmodule

2.2 uart发送部分的仿真程序

`timescale 1ns / 1ps



module uart_tx_tb();

reg sys_clk,rst_n;
reg pi_flag;
reg [7:0] pi_data;
wire  tx;
initial
begin
 sys_clk <= 1'b1;
 rst_n <= 1'b0;
 #20
 rst_n <= 1'b1;
end
//模拟发送7次数据,值为1~7
initial
 begin
  pi_data <= 8'd0;
  pi_flag <= 1'd0;
  #200
  //发送数据1
  pi_data <= 8'd1;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  #(5208*20*10) //每发送 1bit 数据需要 5208 个时钟周期,一帧数据为 10bit
  //发送数据2
  pi_data <= 8'd2;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  #(5208*20*10)
   //发送数据3
  pi_data <= 8'd3;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  #(5208*20*10)
  //发送数据4
  pi_data <= 8'd4;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  #(5208*20*10)
   //发送数据5
  pi_data <= 8'd5;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  #(5208*20*10)
  //发送数据6
  pi_data <= 8'd6;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  #(5208*20*10)
   //发送数据7
  pi_data <= 8'd7;
  pi_flag <= 1'b1;
  #20
  pi_flag <= 1'b0;
  
 end

always #10 sys_clk = ~sys_clk;

uart_tx #
(
 .CLK_FREQ (50_000_000),
 .BAUD_RATE( 9600     )
)
uart_tx_tb(
.clk     (sys_clk),
.rst_n   (rst_n),
.pi_data (pi_data),
.pi_flag (pi_flag),
.tx      (tx)
    );
endmodule

仿真结果分析

fpga通信协议,通讯协议,fpga开发,网络

仿真运行10ms后的结果,可以看到在每次位传输计数bit_cnt=9时,输出tx产生高电平,表示空闲状态,之后在bit_cnt计数1~9的区间内发送9个数据(第1位为低电平,数据开始信号),以输入pi_data=3为例,分别在bit_cnt为2、3时拉高,即对应输出数据的第1、2位,即8'b0000 0011(8'd3)。在整个仿真时间内发送了数据1~7,仿真结果正确。

 2.3 uart接收部分的代码

`timescale 1ns / 1ps



module uart_rx 
#(parameter
CLK_FREQ = 50_000_000, //50MHz的时钟频率,每秒产生50,000,000个周期
BAUD_RATE = 9_600   //9600的波特率,每秒发送9600个码元
 )
(
input clk,
input rst_n,
input rx,
output reg [7:0] po_data,
output reg po_flag
    );
localparam BAUD_cnt_max = CLK_FREQ/BAUD_RATE;  //每5208个周期发送一个码元
   
   reg [2:0] rx_reg3;
   reg work_en;
   reg [12:0] baud_cnt;
   reg [3:0] bit_cnt;
   reg [7:0] rx_data;
   reg rx_end_flag;
   

always @ (posedge clk or negedge rst_n)//拼接在右,每时钟取值后右移一位
     if (rst_n == 1'b0)
       rx_reg3 <= 3'b111;
     else
       rx_reg3 <= {rx,rx_reg3[2:1]}; //跨时钟域传输,打两拍,rx_reg3保存了原始信号、第1、2拍,共三位数据
       
always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
       work_en <= 1'b0;
     else if (rx_reg3[1:0] == 2'b01)//打两拍后第1拍为低、第2拍为高时
       work_en <= 1'b1;
     else if ((baud_cnt == BAUD_cnt_max/2-1) && (bit_cnt == 4'd8)) //最后一个码元计数周期的中间位置处拉低,与bit_cnt一致
       work_en <= 1'b0;
     else
       work_en <= work_en;
       
always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
       baud_cnt <= 13'd0;
     else if((baud_cnt == BAUD_cnt_max-1) || (work_en == 1'b0))
       baud_cnt <= 13'd0;
     else if (work_en == 1'b1)
       baud_cnt <= baud_cnt + 13'd1;
     else
       baud_cnt <= 13'd0;
    
always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
       bit_cnt <= 4'd0;
     else if((bit_cnt == 4'd8) && (baud_cnt == BAUD_cnt_max/2-1))
       bit_cnt <= 4'd0;
     else if(baud_cnt ==  BAUD_cnt_max/2-1)  //每次在计数周期的中间位置采数据最稳定
       bit_cnt <= bit_cnt + 4'd1;
     else
      bit_cnt <= bit_cnt; 
     
always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
       rx_data <= 8'b0;
     else if ((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(baud_cnt ==  BAUD_cnt_max/2-1))   //拼接在右,每时钟取值后右移一位
       rx_data <= {rx_reg3[0],rx_data[7:1]}; //rx_reg3中的第1位为打两拍后的数据
     else 
       ;
    
always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
       rx_end_flag <= 1'b0;
     else if((bit_cnt == 4'd8) && (baud_cnt == BAUD_cnt_max/2-1))
       rx_end_flag <= 1'b1;
     else
       rx_end_flag <= 1'b0; 
       
always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
      po_data<= 8'h00; 
     else if (rx_end_flag == 1'b1)
      po_data<= rx_data;
     else
      ;

always @ (posedge clk or negedge rst_n)
     if (rst_n == 1'b0)
      po_flag<= 1'b0; 
     else 
      po_flag<= rx_end_flag;
 
 ila_0 uart_tx (
	.clk(clk), // input wire clk


	.probe0(clk   ), // input wire [0:0]  probe0  
	.probe1(rst_n ), // input wire [0:0]  probe1 
	.probe2(po_data), // input wire [7:0]  probe2 
	.probe3(po_flag), // input wire [0:0]  probe3 
	.probe4(rx     ) // input wire [0:0]  probe4
);    
endmodule

 发送部分的代码相对复杂,主要原因是使用了打2拍的方法消除跨时钟域传输数据的亚稳态情况。

2.4 uart接收部分仿真程序

`timescale 1ns / 1ps



module uart_rx_tb(   );
reg sys_clk;
reg rst_n;
reg rx;
wire po_data;
wire po_flag;

initial
  begin
  sys_clk <= 1'b1;
  rst_n <= 1'b0;
  rx <= 1'b1;
  #20
  rst_n <= 1'b1;
  end
  
always #10 sys_clk = ~sys_clk;

//定义一个名为 rx_bit 的任务,每次发送的数据有 10 位
task rx_bit(
     input [7:0] data
);
     integer i; //定义一个常量
     for(i=0; i<10; i=i+1)begin //不可以写成C语言 i=i++的形式
      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;
      default
       ;
      endcase
     #(5208*20); //每发送 1 位数据延时 5208 个时钟周期
    end
 endtask

initial
  begin
   #400
   rx_bit(8'd0); //发送0000 0000
   rx_bit(8'd1); //发送0000 0001
   rx_bit(8'd2); 
   rx_bit(8'd3);
   rx_bit(8'd4);
   rx_bit(8'd5);
   rx_bit(8'd6);
   rx_bit(8'd7); //发送0000 0111
   rx_bit(8'd8);
   rx_bit(8'd9);
  end
  
uart_rx 
#(
.CLK_FREQ   (50_000_000),
.BAUD_RATE  (9600      )
 )
uart_rx_tb(
.clk      (sys_clk),
.rst_n    (rst_n),
.rx       (rx),
.po_data  (po_data),
.po_flag  (po_flag)
    );
    
    
endmodule

在仿真程序中,我们给输入信号赋0~9共十个数据。

仿真运行结果:

fpga通信协议,通讯协议,fpga开发,网络

fpga通信协议,通讯协议,fpga开发,网络

fpga通信协议,通讯协议,fpga开发,网络

po_data成功接收了0~9的数据,仿真结果正确。

2.5 上板验证

写一个顶层模块将uart发送和接收模块引用,从而实现FPGA的rx端接收到上位机的数据后马上“串转并”,传给FPGA的发送模块进行“并转串”通过tx端一位一位发送给上位机,并在上位机上显示。以上过程使用串口助手完成。

`timescale 1ns / 1ps


module uart_top(
input sys_clk,
input rst_n,

input wire rx,
output wire tx
    );
wire [7:0] pi_data;    
wire  pi_flag;
parameter
CLK_FREQ  = 26'd50_000_000,
BAUD_RATE = 14'd9_600   ;

uart_rx 
#(
.CLK_FREQ  (CLK_FREQ),        //50MHz的时钟频率,每秒产生50,000,000个周期
.BAUD_RATE ( BAUD_RATE   )    //9600的波特率,每秒发送9600个码元
 )
uart_rx_top(
.clk    (sys_clk),
.rst_n  (rst_n),
.rx     (rx),                 //将rx引脚接收的数据作为输入赋给rx模块

.po_data(pi_data),            //串转并后输出给8位pi_data寄存器
.po_flag(pi_flag)
    );
    
uart_tx #
(
 .CLK_FREQ  (CLK_FREQ ),
 .BAUD_RATE (BAUD_RATE)
)
uart_tx_top(
.clk     (sys_clk),
.rst_n   (rst_n),
.pi_data (pi_data),        //将8位的pi_data寄存器的值再作为输入给tx模块
.pi_flag (pi_flag),

.tx      (tx)              //并转串后输出给上位机
    );
    
endmodule

使用ARTIX-7 xc7A35T fgg484芯片,用ila模块进行debug。

在串口助手里进行数据发送。

fpga通信协议,通讯协议,fpga开发,网络

 观察串口接收端数据。fpga通信协议,通讯协议,fpga开发,网络

 观察串口发送端数据。fpga通信协议,通讯协议,fpga开发,网络

 三、 串口通信协议的其他实现形式

 使用状态机来实现串口通讯协议:

module uart(
input clk, 
input reset, 
input rx, 
output tx 
);

reg [7:0] data; 
reg [2:0] state; 
reg [3:0] count; 
reg start_bit; 
reg [7:0] tx_data; 
reg tx_busy;

parameter IDLE = 0; 
parameter START_BIT = 1; 
parameter DATA_BITS = 2; 
parameter STOP_BIT = 3;

always @(posedge clk or posedge reset) begin 
   if (reset) begin 
     state <= IDLE; 
     count <= 0; 
     start_bit <= 0; 
     tx_busy <= 0; 
   end 
  else begin 
   case (state) 
   IDLE: if (rx == 0) begin 
        state <= START_BIT; 
        count <= 0; 
        start_bit <= 1; 
        end 
   START_BIT: if (count == 7) begin 
        state <= DATA_BITS; 
        count <= 0; 
        end 
             else begin 
        count <= count + 1; 
        end 
   DATA_BITS: if (count == 7) begin 
        state <= STOP_BIT; 
        count <= 0; 
        end 
              else begin 
        count <= count + 1;
        end 
   STOP_BIT: if (count == 3) begin 
        state <= IDLE; 
        count <= 0; 
        start_bit <= 0; 
        end 
             else begin 
        count <= count + 1;
        end 
   endcase 
  end 
end

always @(posedge clk) begin
    if (state == DATA_BITS) begin 
      data <= rx;  
      end 
    end

always @(posedge clk) begin 
    if (tx_busy) begin 
      if (count == 0) begin 
        tx <= start_bit; 
        end 
      else if (count >= 1 && count <= 8) begin 
        tx <= tx_data[count - 1]; 
        end 
      else if (count == 9) begin 
        tx <= 1; 
        end 
      else if (count == 10) begin 
        tx <= 1; 
        tx_busy <= 0;
        end
     end 
 end

always @(posedge clk) begin 
    if (state == START_BIT && count == 7) begin 
       tx_data <= data; 
       tx_busy <= 1; 
       end 
 end

endmodule

在上面的UART模块中,具有时钟输入、复位输入、接收输入和发送输出。在时钟上升沿触发的时候,根据当前状态和计数器的值,模块会执行相应的操作。

模块中定义了几个寄存器,用于存储数据、状态和计数器的值。还定义了一些参数,用于表示不同的状态。

在always块中,根据时钟和复位信号的边沿,根据当前状态执行相应的操作。例如,在IDLE状态下,如果接收到了起始位(rx == 0),则切换到START_BIT状态,并设置计数器和起始位的值。在START_BIT状态下,计数器递增,直到达到7,然后切换到DATA_BITS状态。在DATA_BITS状态下,将接收到的数据存储到data寄存器中。在STOP_BIT状态下,计数器递增,直到达到3,然后切换回IDLE状态,并清除起始位。

在另一个always块中,根据时钟的上升沿,根据当前状态和计数器的值,设置发送输出tx的值。如果tx_busy为1,则表示正在发送数据。在计数器为0时,发送起始位。在计数器为1到8时,发送数据位。在计数器为9时,发送停止位。在计数器为10时,停止发送,并将tx_busy设置为0。

最后一个always块用于在START_BIT状态下,将接收到的数据存储到tx_data寄存器中,并将tx_busy设置为1,表示开始发送数据。文章来源地址https://www.toymoban.com/news/detail-754440.html

到了这里,关于FPGA用verilog HDL实现串口通讯协议的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FPGA协议篇:UART通信及Verilog最易懂实现方式/通用于任何工程/带握手信号 ----UART_TX

            UART(Universal Asynchronous Receiver/Transmitter)是一种通用的 异步收发传输协议 ,用于在计算机系统和外部设备之间进行串行数据传输。UART 协议定义了数据的传输格式和通信规则,使得不同设备之间能够进行可靠的数据交换。 首先先把设计代码放到这里: UART_TX完整代

    2024年03月27日
    浏览(52)
  • 【FPGA Verilog开发实战指南】初识Verilog HDL-基础语法

    就是用代码来描述硬件结构 语言有VHDL与Verilog HDL Verilog HDL 是从C语言来的,学的快 ###例子 也叫保留字,一般是小写 module 表示模块的开始 endmodule 模块的结束 模块名 一般与.v文件的名字一致 输入信号 input 输出信号 output 既做输入也做输出 inout 需要一些变量和参数对输

    2024年02月21日
    浏览(44)
  • 【Verilog HDL】FPGA-Verilog文件的基本结构

    🎉欢迎来到FPGA专栏~Verilog文件的基本结构 ☆* o(≧▽≦)o *☆ 嗨 ~我是 小夏与酒 🍹 ✨ 博客主页: 小夏与酒的博客 🎈该系列 文章专栏: FPGA学习之旅 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏 📜 欢迎大家关注! ❤️ Verilog HDL系列博客参考书籍 《

    2024年02月04日
    浏览(82)
  • 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)

    🎉欢迎来到Python专栏~与FPGA、蓝牙模块实现串口通信 ☆* o(≧▽≦)o *☆ 嗨 ~我是 小夏与酒 🍹 ✨ 博客主页: 小夏与酒的博客 🎈该系列 文章专栏: Python学习专栏 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏 📜 欢迎大家关注! ❤️ Python与FPGA串口通信

    2024年02月15日
    浏览(45)
  • 【Verilog HDL】FPGA-testbench基础知识

    🎉欢迎来到FPGA专栏~testbench基础知识 ☆* o(≧▽≦)o *☆ 嗨 ~我是 小夏与酒 🍹 ✨ 博客主页: 小夏与酒的博客 🎈该系列 文章专栏: FPGA学习之旅 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏 📜 欢迎大家关注! ❤️ 📜在开发FPGA的过程中,需要掌握V

    2024年02月12日
    浏览(40)
  • fpga开发基于verilog HDL的四人抢答器

    鱼弦:CSDN内容合伙人、CSDN新星导师、全栈领域创作新星创作者 、51CTO(Top红人+专家博主) 、github开源爱好者(go-zero源码二次开发、游戏后端架构 https://github.com/Peakchen) 智能电子抢答器可容纳4组参赛者抢答,每组设一个抢答钮。 ③ 电路具有第一抢答信号的鉴别和锁存功能。

    2024年02月04日
    浏览(49)
  • 【FPGA】FPGA实现UART串口通信回环

    关于UART协议的基础理论部分已经在上一篇文章中讲述,不再重复介绍。 UART通信协议 本文主要介绍如何使用Verlilog编程,通过FPGA实现UART串口通信回环工程。在本工程中所使用的系统时钟为50MHz,如果选择115200的波特率进行数据传输,那么传输1bit所用的时钟周期数就是50_000_

    2024年02月05日
    浏览(65)
  • FPGA实现的多波形信号发生器,支持正弦、方波、锯齿波、三角波及调制,配备仿真和实物制作功能,使用Verilog HDL编写

    基于FPGA的DDS多波形信号发生器,可以产生正弦波,方波,锯齿波三角波,调制波形2psk.2askAM调制,可以仿真,可以制作实物,可以进行讲解! 使用可以使用Quarter9.0自带仿真软件进行仿真波形。 也可以使用quarter13.1与modesim进行联合仿真进行仿真波形! 使用verilog HDL语言进行编

    2024年04月12日
    浏览(54)
  • FPGA实验报告 Verilog HDL:7人表决器 巴克码信号发生器 FPGA数字时钟

    写在前面:本文提供以下三个任务的思路讲解和代码实现, 如需参考引脚配置说明,可以点击下方链接跳转查看完整实验报告 ;本实验使用的是Altera公司的cycloneⅢ类型的芯片。 Verilog HDL实现:7人表决器 信号发生器 多功能数字时钟 实验目标:实现7人投票表决电路,支持人

    2024年02月05日
    浏览(49)
  • 【FPGA】组合逻辑电路三种建模方式(Verilog HDL 门级建模、Verilog HDL 数据流建模、组合电路行为级建模)

    目录   Verilog HDL 门级建模 各种逻辑门的表示和使用 门级建模书写实例 Verilog HDL 数据流建模 数据流建模 数据流建模书写实例 组合电路行为级建模 always语句 条件语句 多路分支语句 循环语句 for while repeat forever 行为级建模示例   可以理解为对逻辑电路中各个门依次进行描述

    2024年04月13日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包