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日
    浏览(37)
  • 【FPGA Verilog开发实战指南】初识Verilog HDL-基础语法

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    2024年04月13日
    浏览(33)
  • FPGA以状态机实现串口通信

    UART通信只有两根信号线,一-根是发送数据端口线叫tx ,一 根是接收数据端口线叫rx ,对于上位机来说它的tx要和对于FPGA来说的rx连接,同样上位机的rx要和FPGA的tx连接,如果是两个tx或者两个rx连接那数据就不能正常被发送出去和接收到。UART可以实现全双工,即可以同时进行

    2024年02月03日
    浏览(31)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包