当前位置:   article > 正文

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

fpga实现串口协议

一、串口通讯简介

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

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

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

串口通信具有以下特点:

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

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

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

2.1 uart发送部分代码

  1. `timescale 1ns / 1ps
  2. module uart_tx #
  3. (parameter
  4. CLK_FREQ = 50_000_000,
  5. BAUD_RATE = 9600
  6. )
  7. (
  8. input clk,
  9. input rst_n,
  10. input wire [7:0] pi_data,
  11. input wire pi_flag,
  12. output reg tx
  13. );
  14. localparam BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE;
  15. reg [12:0] baud_cnt;
  16. reg [3:0] bit_cnt;
  17. reg work_en;
  18. always @(posedge clk or negedge rst_n)
  19. if(rst_n == 1'b0)
  20. baud_cnt <= 13'b0;
  21. else if ((baud_cnt == BAUD_CNT_MAX-1) || (work_en == 1'b0))
  22. baud_cnt <= 13'd0;
  23. else if(work_en == 1'b1)
  24. baud_cnt <= baud_cnt + 13'd1;
  25. else
  26. baud_cnt <= 13'd0;
  27. always @(posedge clk or negedge rst_n)
  28. if(rst_n == 1'b0)
  29. bit_cnt <= 4'b0;
  30. else if ((baud_cnt == BAUD_CNT_MAX/2-1) && (work_en == 1'b1))
  31. bit_cnt <= bit_cnt + 4'd1;
  32. else if ((bit_cnt == 4'd9)&& (baud_cnt == 13'd1))
  33. bit_cnt <= 4'd0;
  34. else
  35. bit_cnt <= bit_cnt;
  36. always @(posedge clk or negedge rst_n)
  37. if(rst_n == 1'b0)
  38. work_en <= 1'b0;
  39. else if (pi_flag == 1'b1)
  40. work_en <= 1'b1;
  41. else if ((bit_cnt == 4'd9) && (baud_cnt == 13'd1))
  42. work_en <= 1'b0;
  43. else
  44. work_en <= work_en;
  45. always @(posedge clk) //并转串发送
  46. if(work_en == 1'b1)begin
  47. case(bit_cnt)
  48. 1: tx <= 1'b0;
  49. 2: tx <= pi_data[0];
  50. 3: tx <= pi_data[1];
  51. 4: tx <= pi_data[2];
  52. 5: tx <= pi_data[3];
  53. 6: tx <= pi_data[4];
  54. 7: tx <= pi_data[5];
  55. 8: tx <= pi_data[6];
  56. 9: tx <= pi_data[7];
  57. default
  58. tx <= 1'b1;
  59. endcase
  60. end
  61. else
  62. tx <= 1'b1; //空闲
  63. //调用ila模块IP核进行debug
  64. ila_0 uart_tx (
  65. .clk(clk), // input wire clk
  66. .probe0(clk ), // input wire [0:0] probe0
  67. .probe1(rst_n ), // input wire [0:0] probe1
  68. .probe2(pi_data), // input wire [7:0] probe2
  69. .probe3(pi_flag), // input wire [0:0] probe3
  70. .probe4(tx ) // input wire [0:0] probe4
  71. );
  72. endmodule

2.2 uart发送部分的仿真程序

  1. `timescale 1ns / 1ps
  2. module uart_tx_tb();
  3. reg sys_clk,rst_n;
  4. reg pi_flag;
  5. reg [7:0] pi_data;
  6. wire tx;
  7. initial
  8. begin
  9. sys_clk <= 1'b1;
  10. rst_n <= 1'b0;
  11. #20
  12. rst_n <= 1'b1;
  13. end
  14. //模拟发送7次数据,值为1~7
  15. initial
  16. begin
  17. pi_data <= 8'd0;
  18. pi_flag <= 1'd0;
  19. #200
  20. //发送数据1
  21. pi_data <= 8'd1;
  22. pi_flag <= 1'b1;
  23. #20
  24. pi_flag <= 1'b0;
  25. #(5208*20*10) //每发送 1bit 数据需要 5208 个时钟周期,一帧数据为 10bit
  26. //发送数据2
  27. pi_data <= 8'd2;
  28. pi_flag <= 1'b1;
  29. #20
  30. pi_flag <= 1'b0;
  31. #(5208*20*10)
  32. //发送数据3
  33. pi_data <= 8'd3;
  34. pi_flag <= 1'b1;
  35. #20
  36. pi_flag <= 1'b0;
  37. #(5208*20*10)
  38. //发送数据4
  39. pi_data <= 8'd4;
  40. pi_flag <= 1'b1;
  41. #20
  42. pi_flag <= 1'b0;
  43. #(5208*20*10)
  44. //发送数据5
  45. pi_data <= 8'd5;
  46. pi_flag <= 1'b1;
  47. #20
  48. pi_flag <= 1'b0;
  49. #(5208*20*10)
  50. //发送数据6
  51. pi_data <= 8'd6;
  52. pi_flag <= 1'b1;
  53. #20
  54. pi_flag <= 1'b0;
  55. #(5208*20*10)
  56. //发送数据7
  57. pi_data <= 8'd7;
  58. pi_flag <= 1'b1;
  59. #20
  60. pi_flag <= 1'b0;
  61. end
  62. always #10 sys_clk = ~sys_clk;
  63. uart_tx #
  64. (
  65. .CLK_FREQ (50_000_000),
  66. .BAUD_RATE( 9600 )
  67. )
  68. uart_tx_tb(
  69. .clk (sys_clk),
  70. .rst_n (rst_n),
  71. .pi_data (pi_data),
  72. .pi_flag (pi_flag),
  73. .tx (tx)
  74. );
  75. endmodule

仿真结果分析

仿真运行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接收部分的代码

  1. `timescale 1ns / 1ps
  2. module uart_rx
  3. #(parameter
  4. CLK_FREQ = 50_000_000, //50MHz的时钟频率,每秒产生50,000,000个周期
  5. BAUD_RATE = 9_600 //9600的波特率,每秒发送9600个码元
  6. )
  7. (
  8. input clk,
  9. input rst_n,
  10. input rx,
  11. output reg [7:0] po_data,
  12. output reg po_flag
  13. );
  14. localparam BAUD_cnt_max = CLK_FREQ/BAUD_RATE; //每5208个周期发送一个码元
  15. reg [2:0] rx_reg3;
  16. reg work_en;
  17. reg [12:0] baud_cnt;
  18. reg [3:0] bit_cnt;
  19. reg [7:0] rx_data;
  20. reg rx_end_flag;
  21. always @ (posedge clk or negedge rst_n)//拼接在右,每时钟取值后右移一位
  22. if (rst_n == 1'b0)
  23. rx_reg3 <= 3'b111;
  24. else
  25. rx_reg3 <= {rx,rx_reg3[2:1]}; //跨时钟域传输,打两拍,rx_reg3保存了原始信号、第1、2拍,共三位数据
  26. always @ (posedge clk or negedge rst_n)
  27. if (rst_n == 1'b0)
  28. work_en <= 1'b0;
  29. else if (rx_reg3[1:0] == 2'b01)//打两拍后第1拍为低、第2拍为高时
  30. work_en <= 1'b1;
  31. else if ((baud_cnt == BAUD_cnt_max/2-1) && (bit_cnt == 4'd8)) //最后一个码元计数周期的中间位置处拉低,与bit_cnt一致
  32. work_en <= 1'b0;
  33. else
  34. work_en <= work_en;
  35. always @ (posedge clk or negedge rst_n)
  36. if (rst_n == 1'b0)
  37. baud_cnt <= 13'd0;
  38. else if((baud_cnt == BAUD_cnt_max-1) || (work_en == 1'b0))
  39. baud_cnt <= 13'd0;
  40. else if (work_en == 1'b1)
  41. baud_cnt <= baud_cnt + 13'd1;
  42. else
  43. baud_cnt <= 13'd0;
  44. always @ (posedge clk or negedge rst_n)
  45. if (rst_n == 1'b0)
  46. bit_cnt <= 4'd0;
  47. else if((bit_cnt == 4'd8) && (baud_cnt == BAUD_cnt_max/2-1))
  48. bit_cnt <= 4'd0;
  49. else if(baud_cnt == BAUD_cnt_max/2-1) //每次在计数周期的中间位置采数据最稳定
  50. bit_cnt <= bit_cnt + 4'd1;
  51. else
  52. bit_cnt <= bit_cnt;
  53. always @ (posedge clk or negedge rst_n)
  54. if (rst_n == 1'b0)
  55. rx_data <= 8'b0;
  56. else if ((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(baud_cnt == BAUD_cnt_max/2-1)) //拼接在右,每时钟取值后右移一位
  57. rx_data <= {rx_reg3[0],rx_data[7:1]}; //rx_reg3中的第1位为打两拍后的数据
  58. else
  59. ;
  60. always @ (posedge clk or negedge rst_n)
  61. if (rst_n == 1'b0)
  62. rx_end_flag <= 1'b0;
  63. else if((bit_cnt == 4'd8) && (baud_cnt == BAUD_cnt_max/2-1))
  64. rx_end_flag <= 1'b1;
  65. else
  66. rx_end_flag <= 1'b0;
  67. always @ (posedge clk or negedge rst_n)
  68. if (rst_n == 1'b0)
  69. po_data<= 8'h00;
  70. else if (rx_end_flag == 1'b1)
  71. po_data<= rx_data;
  72. else
  73. ;
  74. always @ (posedge clk or negedge rst_n)
  75. if (rst_n == 1'b0)
  76. po_flag<= 1'b0;
  77. else
  78. po_flag<= rx_end_flag;
  79. ila_0 uart_tx (
  80. .clk(clk), // input wire clk
  81. .probe0(clk ), // input wire [0:0] probe0
  82. .probe1(rst_n ), // input wire [0:0] probe1
  83. .probe2(po_data), // input wire [7:0] probe2
  84. .probe3(po_flag), // input wire [0:0] probe3
  85. .probe4(rx ) // input wire [0:0] probe4
  86. );
  87. endmodule

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

2.4 uart接收部分仿真程序

  1. `timescale 1ns / 1ps
  2. module uart_rx_tb( );
  3. reg sys_clk;
  4. reg rst_n;
  5. reg rx;
  6. wire po_data;
  7. wire po_flag;
  8. initial
  9. begin
  10. sys_clk <= 1'b1;
  11. rst_n <= 1'b0;
  12. rx <= 1'b1;
  13. #20
  14. rst_n <= 1'b1;
  15. end
  16. always #10 sys_clk = ~sys_clk;
  17. //定义一个名为 rx_bit 的任务,每次发送的数据有 10 位
  18. task rx_bit(
  19. input [7:0] data
  20. );
  21. integer i; //定义一个常量
  22. for(i=0; i<10; i=i+1)begin //不可以写成C语言 i=i++的形式
  23. case(i)
  24. 0: rx <= 1'b0;
  25. 1: rx <= data[0];
  26. 2: rx <= data[1];
  27. 3: rx <= data[2];
  28. 4: rx <= data[3];
  29. 5: rx <= data[4];
  30. 6: rx <= data[5];
  31. 7: rx <= data[6];
  32. 8: rx <= data[7];
  33. 9: rx <= 1'b1;
  34. default
  35. ;
  36. endcase
  37. #(5208*20); //每发送 1 位数据延时 5208 个时钟周期
  38. end
  39. endtask
  40. initial
  41. begin
  42. #400
  43. rx_bit(8'd0); //发送0000 0000
  44. rx_bit(8'd1); //发送0000 0001
  45. rx_bit(8'd2);
  46. rx_bit(8'd3);
  47. rx_bit(8'd4);
  48. rx_bit(8'd5);
  49. rx_bit(8'd6);
  50. rx_bit(8'd7); //发送0000 0111
  51. rx_bit(8'd8);
  52. rx_bit(8'd9);
  53. end
  54. uart_rx
  55. #(
  56. .CLK_FREQ (50_000_000),
  57. .BAUD_RATE (9600 )
  58. )
  59. uart_rx_tb(
  60. .clk (sys_clk),
  61. .rst_n (rst_n),
  62. .rx (rx),
  63. .po_data (po_data),
  64. .po_flag (po_flag)
  65. );
  66. endmodule

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

仿真运行结果:

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

2.5 上板验证

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

  1. `timescale 1ns / 1ps
  2. module uart_top(
  3. input sys_clk,
  4. input rst_n,
  5. input wire rx,
  6. output wire tx
  7. );
  8. wire [7:0] pi_data;
  9. wire pi_flag;
  10. parameter
  11. CLK_FREQ = 26'd50_000_000,
  12. BAUD_RATE = 14'd9_600 ;
  13. uart_rx
  14. #(
  15. .CLK_FREQ (CLK_FREQ), //50MHz的时钟频率,每秒产生50,000,000个周期
  16. .BAUD_RATE ( BAUD_RATE ) //9600的波特率,每秒发送9600个码元
  17. )
  18. uart_rx_top(
  19. .clk (sys_clk),
  20. .rst_n (rst_n),
  21. .rx (rx), //将rx引脚接收的数据作为输入赋给rx模块
  22. .po_data(pi_data), //串转并后输出给8位pi_data寄存器
  23. .po_flag(pi_flag)
  24. );
  25. uart_tx #
  26. (
  27. .CLK_FREQ (CLK_FREQ ),
  28. .BAUD_RATE (BAUD_RATE)
  29. )
  30. uart_tx_top(
  31. .clk (sys_clk),
  32. .rst_n (rst_n),
  33. .pi_data (pi_data), //将8位的pi_data寄存器的值再作为输入给tx模块
  34. .pi_flag (pi_flag),
  35. .tx (tx) //并转串后输出给上位机
  36. );
  37. endmodule

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

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

 观察串口接收端数据。

 观察串口发送端数据。

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

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

  1. module uart(
  2. input clk,
  3. input reset,
  4. input rx,
  5. output tx
  6. );
  7. reg [7:0] data;
  8. reg [2:0] state;
  9. reg [3:0] count;
  10. reg start_bit;
  11. reg [7:0] tx_data;
  12. reg tx_busy;
  13. parameter IDLE = 0;
  14. parameter START_BIT = 1;
  15. parameter DATA_BITS = 2;
  16. parameter STOP_BIT = 3;
  17. always @(posedge clk or posedge reset) begin
  18. if (reset) begin
  19. state <= IDLE;
  20. count <= 0;
  21. start_bit <= 0;
  22. tx_busy <= 0;
  23. end
  24. else begin
  25. case (state)
  26. IDLE: if (rx == 0) begin
  27. state <= START_BIT;
  28. count <= 0;
  29. start_bit <= 1;
  30. end
  31. START_BIT: if (count == 7) begin
  32. state <= DATA_BITS;
  33. count <= 0;
  34. end
  35. else begin
  36. count <= count + 1;
  37. end
  38. DATA_BITS: if (count == 7) begin
  39. state <= STOP_BIT;
  40. count <= 0;
  41. end
  42. else begin
  43. count <= count + 1;
  44. end
  45. STOP_BIT: if (count == 3) begin
  46. state <= IDLE;
  47. count <= 0;
  48. start_bit <= 0;
  49. end
  50. else begin
  51. count <= count + 1;
  52. end
  53. endcase
  54. end
  55. end
  56. always @(posedge clk) begin
  57. if (state == DATA_BITS) begin
  58. data <= rx;
  59. end
  60. end
  61. always @(posedge clk) begin
  62. if (tx_busy) begin
  63. if (count == 0) begin
  64. tx <= start_bit;
  65. end
  66. else if (count >= 1 && count <= 8) begin
  67. tx <= tx_data[count - 1];
  68. end
  69. else if (count == 9) begin
  70. tx <= 1;
  71. end
  72. else if (count == 10) begin
  73. tx <= 1;
  74. tx_busy <= 0;
  75. end
  76. end
  77. end
  78. always @(posedge clk) begin
  79. if (state == START_BIT && count == 7) begin
  80. tx_data <= data;
  81. tx_busy <= 1;
  82. end
  83. end
  84. 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,表示开始发送数据。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/467786
推荐阅读
相关标签
  

闽ICP备14008679号