当前位置:   article > 正文

基于FPGA的UART接口设计_基于fpga的uart接口的设计

基于fpga的uart接口的设计

 一、顶层设计思路:

        UART即通用异步收发传输接口(Universal Asynchronous Receiver/Transmitter),简称串口,是一种常用的通信接口,其协议原理就不赘述了,不了解的可以自己查阅资料。(不赘述不代表不重要,相反,对于每一个FPGA设计,充分理解原理是基础和前提,而FPGA和Verilog只是工具。)用FPGA来实现UART,关键就是要将UART收发数据时的时序用Verilog描述出来。

        根据UART协议的原理,可以将整个UART分为两个模块:串口接收模块“UART_RX”和串口发送模块“UART_TX”,前者将接收到的1位串行数据“uart_rxd”转化为8位并行数据“data[7:0]”,后者又将8位并行数据“data[7:0]”转化回1位串行数据“uart_txd”输出,最终实现串行数据的收发。UART顶层功能框图如图1所示:

图1 

二、串口接收模块设计思路:

        该模块实现将接收到的1位串行数据转化为8位并行数据,而只有当有数据输入时,该模块才去工作,所以需要一个使能信号“rxd_en”来控制该模块,当“rxd_en”有效时,该模块才工作。根据UART协议原理,接收到的1位串行数据最开始的一位为起始位(“0”),而“uart_rxd”在空闲时为“1”,故刚开始接收到串行数据时,“uart_rxd”必定会产生一个下降沿,所以可以检测这个下降沿,每当下降沿到来的同时将“rxd_en”置为有效,从而使该模块开始工作;每当所有串行数据都被接收完毕时,再把“rxd_en”置为无效,同时发出接收完成标志“rx_done”,从而关闭该模块。(关于在Verilog里怎么实现实现边沿检测,可以看我写的关于DDS信号发生器的那篇博文。)

        在数据接收的过程中,速率以串口波特率为准,常用的串口波特率有:1200、2400、4800、9600、14400、57600、115200等等。所以需要一个波特率计数器“baud_cnt”在特定的波特率下对主时钟进行计数,每当这个波特率计数器计满时,接收一位数据。例如,主时钟为50MHz,当波特率为9600时,波特率计数器的最大值应该为:50000000/9600-1=5207,此时,每当波特率计数器计到5207时就清零,同时接收一位串行数据。

        在本次设计中,每个数据的数据位共有10位(1位起始位、8位数据位、1位停止位),故在接收过程中,还需要一个位计数器“bit_cnt”来对每个串行数据的数据位进行计数,具体操作为:每当波特率计数器计满时,位计数器就自增1,直到位计数器的值为9时清零。

        在接收过程中,为了接收到稳定的串行数据,本设计在每一位串行数据的中间对其进行采样和接收,具体操作为:每当波特率计数器计到最大值的一半时,就对当前的串行数据进行采样,然后根据位计数器的值,将采样后的值赋给相应的并行数据位。此外,为了消除亚稳态,待接收的串行数据应该先通过一个两位的寄存器进行缓冲后再进行边沿检测。

        根据以上分析,做出串口接收模块的时序图如图2所示(“uart_rxd_r”是“uart_rxd”消除亚稳态后再通过边沿检测寄存器后的信号):

图2

        根据时序图,就可以进行串口接收模块的RTL描述,编写的Verilog代码如下:

  1. `timescale 1ns / 1ps
  2. module UART_RX(
  3. input clk, //主时钟,50MHz
  4. input rst, //复位,高电平有效
  5. input uart_tx_data, //发送给串口的串行数据
  6. output reg [7:0] uart_rx_data, //串口接收后的并行数据
  7. output reg rx_done //接收完成标志
  8. );
  9. parameter CLK_F = 50000000; //主时钟频率
  10. parameter UART_B = 9600; //串口波特率
  11. parameter B_CNT = CLK_F / UART_B; //波特率计数器的最大值
  12. reg [1:0] uart_tx_data_r1; //用于消除输入串行数据的亚稳态
  13. reg [1:0] uart_tx_data_r2; //输入串行数据边沿检测寄存器
  14. reg rxd_en; //接收使能信号
  15. reg [15:0] baud_cnt = 16'd0; //115200波特率计数器
  16. reg [3:0] bit_cnt = 4'd0; //位计数器
  17. reg [7:0] rx_data; //接收数据寄存器
  18. /***************消除输入串行数据亚稳态*************/
  19. always @ (posedge clk)
  20. begin
  21. uart_tx_data_r1 <= {uart_tx_data_r1[0] , uart_tx_data};
  22. end
  23. /***************输入串行数据边沿检测*************/
  24. always @ (posedge clk)
  25. begin
  26. uart_tx_data_r2 <= {uart_tx_data_r2[0] , uart_tx_data_r1[1]};
  27. end
  28. /***************接收使能信号控制*************/
  29. always @ (posedge clk or negedge rst)
  30. begin
  31. if (rst)
  32. rxd_en <= 1'd0;
  33. else
  34. begin
  35. if (uart_tx_data_r2 == 2'b10)
  36. rxd_en <= 1'd1;
  37. else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))
  38. rxd_en <= 1'd0;
  39. else
  40. rxd_en <= rxd_en;
  41. end
  42. end
  43. /***************波特率计数器控制*************/
  44. always @ (posedge clk or negedge rst)
  45. begin
  46. if (rst)
  47. baud_cnt <= 16'd0;
  48. else if (rxd_en)
  49. begin
  50. if (baud_cnt == B_CNT - 1)
  51. baud_cnt <= 16'd0;
  52. else
  53. baud_cnt <= baud_cnt + 1'b1;
  54. end
  55. else
  56. baud_cnt <= 16'd0;
  57. end
  58. /***************位计数器控制*************/
  59. always @ (posedge clk or negedge rst)
  60. begin
  61. if (rst)
  62. bit_cnt <= 4'd0;
  63. else if (rxd_en)
  64. begin
  65. if (baud_cnt == B_CNT - 1)
  66. bit_cnt <= bit_cnt + 1'b1;
  67. else
  68. bit_cnt <= bit_cnt;
  69. end
  70. else
  71. bit_cnt <= 4'd0;
  72. end
  73. /***************接收缓存*************/
  74. always @ (posedge clk or negedge rst)
  75. begin
  76. if (rst)
  77. rx_data <= 8'd0;
  78. else if (rxd_en)
  79. begin
  80. if (baud_cnt == B_CNT / 2)
  81. begin
  82. case (bit_cnt)
  83. 4'd1:rx_data[0] <= uart_tx_data_r2[1];
  84. 4'd2:rx_data[1] <= uart_tx_data_r2[1];
  85. 4'd3:rx_data[2] <= uart_tx_data_r2[1];
  86. 4'd4:rx_data[3] <= uart_tx_data_r2[1];
  87. 4'd5:rx_data[4] <= uart_tx_data_r2[1];
  88. 4'd6:rx_data[5] <= uart_tx_data_r2[1];
  89. 4'd7:rx_data[6] <= uart_tx_data_r2[1];
  90. 4'd8:rx_data[7] <= uart_tx_data_r2[1];
  91. default:;
  92. endcase
  93. end
  94. else
  95. rx_data <= rx_data;
  96. end
  97. else
  98. rx_data <= 8'd0;
  99. end
  100. /***************接收*************/
  101. always @ (posedge clk or negedge rst)
  102. begin
  103. if (rst)
  104. uart_rx_data <= 8'd0;
  105. else if (bit_cnt == 4'd9)
  106. uart_rx_data <= rx_data;
  107. else
  108. uart_rx_data <= 8'd0;
  109. end
  110. /***************接收完成标志控制*************/
  111. always @ (posedge clk or negedge rst)
  112. begin
  113. if (rst)
  114. rx_done <= 1'd0;
  115. else if (bit_cnt == 4'd9)
  116. rx_done <= 1'd1;
  117. else
  118. rx_done <= 1'd0;
  119. end
  120. endmodule

        编写的testbeach如下:

  1. `timescale 1ns / 1ps
  2. module tb_uart_rx();
  3. reg clk; //主时钟,50MHz
  4. reg rst; //复位,低电平有效
  5. reg uart_tx_data; //发送给串口的串行数据
  6. wire [7:0] uart_rx_data; //串口接收后的并行数据
  7. wire rx_done; //接收完成标志
  8. /***************模块例化*************/
  9. UART_RX tb_uart_rx(
  10. .clk(clk),
  11. .rst(rst),
  12. .uart_tx_data(uart_tx_data),
  13. .uart_rx_data(uart_rx_data),
  14. .rx_done(rx_done)
  15. );
  16. /***************产生主时钟*************/
  17. always #10 clk = ~clk;
  18. /***************初始化*************/
  19. initial
  20. begin
  21. clk = 1'd0;
  22. rst = 1'd0;
  23. uart_tx_data = 1'b1;
  24. #1000000
  25. uart_tx_data = 1'b0;
  26. #200000
  27. uart_tx_data = 1'b1;
  28. #200000
  29. uart_tx_data = 1'b0;
  30. #200000
  31. uart_tx_data = 1'b1;
  32. end
  33. endmodule

         仿真结果如图3所示,可以看到接收功能已实现:

图3 

三、串口发送模块设计思路:

        该模块实现将8位并行数据转化回1位串行数据输出,与接收模块一样,串口发送模块也需要一个使能信号“txd_en”来控制,当“txd_en”有效时,模块才工作。根据UART原理,只有接收模块接收完成后,发送模块才能开始工作,故接收模块里的接收完成标志即是发送模块的发送开始标志“txd_start”,所以可以通过检测发送开始标志的上升沿来使能“txd_en”,从而使能发送模块。

        根据以上分析,做出串口发送模块的时序图如图4所示:

 图4

        根据时序图,编写的Verilog代码如下:

  1. `timescale 1ns / 1ps
  2. module UART_TX(
  3. input clk, //主时钟,50MHz
  4. input rst, //复位,高电平有效
  5. input txd_start, //发送开始标志
  6. input [7:0] uart_rx_data, //串口接收到的并行数据
  7. output reg uart_tx_data //串口发送的串行数据
  8. );
  9. parameter CLK_F = 50000000; //主时钟频率
  10. parameter UART_B = 9600; //串口波特率
  11. parameter B_CNT = CLK_F / UART_B; //波特率计数器的最大值
  12. reg [1:0] txd_start_r; //发送开始标志边沿检测寄存器
  13. reg txd_en; //发送使能信号
  14. reg [15:0] baud_cnt = 16'd0; //115200波特率计数器
  15. reg [3:0] bit_cnt = 4'd0; //位计数器
  16. reg [7:0] tx_data; //发送数据寄存器
  17. /***************发送开始标志边沿检测*************/
  18. always @ (posedge clk)
  19. begin
  20. txd_start_r <= {txd_start_r[0] , txd_start};
  21. end
  22. /***************发送使能信号控制*************/
  23. always @ (posedge clk or negedge rst)
  24. begin
  25. if (rst)
  26. txd_en <= 1'd0;
  27. else
  28. begin
  29. if (txd_start_r == 2'b01)
  30. txd_en <= 1'd1;
  31. else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))
  32. txd_en <= 1'd0;
  33. else
  34. txd_en <= txd_en;
  35. end
  36. end
  37. /***************发送缓存*************/
  38. always @ (posedge clk or negedge rst)
  39. begin
  40. if (rst)
  41. tx_data <= 8'd0;
  42. else
  43. begin
  44. if (txd_start_r == 2'b01)
  45. tx_data <= uart_rx_data;
  46. else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))
  47. tx_data <= 8'd0;
  48. else
  49. tx_data <= tx_data;
  50. end
  51. end
  52. /***************波特率计数器控制*************/
  53. always @ (posedge clk or negedge rst)
  54. begin
  55. if (rst)
  56. baud_cnt <= 16'd0;
  57. else if (txd_en)
  58. begin
  59. if (baud_cnt == B_CNT - 1)
  60. baud_cnt <= 16'd0;
  61. else
  62. baud_cnt <= baud_cnt + 1'b1;
  63. end
  64. else
  65. baud_cnt <= 16'd0;
  66. end
  67. /***************位计数器控制*************/
  68. always @ (posedge clk or negedge rst)
  69. begin
  70. if (rst)
  71. bit_cnt <= 4'd0;
  72. else if (txd_en)
  73. begin
  74. if (baud_cnt == B_CNT - 1)
  75. bit_cnt <= bit_cnt + 1'b1;
  76. else
  77. bit_cnt <= bit_cnt;
  78. end
  79. else
  80. bit_cnt <= 4'd0;
  81. end
  82. /***************发送*************/
  83. always @ (posedge clk or negedge rst)
  84. begin
  85. if (rst)
  86. uart_tx_data <= 1'd1;
  87. else if (txd_en)
  88. begin
  89. case (bit_cnt)
  90. 4'd0:uart_tx_data <= 1'd0;
  91. 4'd1:uart_tx_data <= tx_data[0];
  92. 4'd2:uart_tx_data <= tx_data[1];
  93. 4'd3:uart_tx_data <= tx_data[2];
  94. 4'd4:uart_tx_data <= tx_data[3];
  95. 4'd5:uart_tx_data <= tx_data[4];
  96. 4'd6:uart_tx_data <= tx_data[5];
  97. 4'd7:uart_tx_data <= tx_data[6];
  98. 4'd8:uart_tx_data <= tx_data[7];
  99. 4'd9:uart_tx_data <= 1'd1;
  100. default:;
  101. endcase
  102. end
  103. else
  104. uart_tx_data <= 1'd1;
  105. end
  106. endmodule

        编写的testbeach如下:

  1. `timescale 1ns / 1ps
  2. module tb_uart_tx();
  3. reg clk; //主时钟,50MHz
  4. reg rst; //复位,低电平有效
  5. reg txd_start; //发送开始标志
  6. reg [7:0] uart_rx_data; //串口接收到的并行数据
  7. wire uart_tx_data; //串口发送的串行数据
  8. /***************模块例化*************/
  9. UART_TX tb_uart_tx(
  10. .clk(clk),
  11. .rst(rst),
  12. .txd_start(txd_start),
  13. .uart_rx_data(uart_rx_data),
  14. .uart_tx_data(uart_tx_data)
  15. );
  16. /***************产生主时钟*************/
  17. always #10 clk = ~clk;
  18. /***************初始化*************/
  19. initial
  20. begin
  21. clk = 1'd0;
  22. rst = 1'd0;
  23. txd_start = 1'd0;
  24. uart_rx_data = 8'h5a;
  25. #20
  26. txd_start = 1'd1;
  27. #20
  28. txd_start = 1'd0;
  29. #100000
  30. uart_rx_data = 8'h3f;
  31. #100000
  32. uart_rx_data = 8'he6;
  33. end
  34. endmodule

          仿真结果如图5所示,可以看到发送功能已实现:

图5

四、顶层代码及其上板调试:

        两个子模块设计完成后,按照顶层功能框图可以编写顶层的RTL描述,编写的Verilog代码如下(由于我的开发板上的时钟是差分时钟,故需要调用一个差分信号转单端信号的设计原语“IBUFDS”,该原语的使用很简单,在这里就不专门介绍了,不了解的可以自己查阅资料):

  1. `timescale 1ns / 1ps
  2. module UART_TOP(
  3. input clk_p, //差分主时钟正端,50MHz
  4. input clk_n, //差分主时钟负端,50MHz
  5. input rst, //复位,高电平有效
  6. input uart_rxd, //接收端
  7. output uart_txd //发送端
  8. );
  9. parameter CLK_FREQ = 50000000; //主时钟频率
  10. parameter UART_BPS = 460800; //串口波特率
  11. wire clk; //主时钟,50MHz
  12. wire [7:0] uart_rx_data_w; //串口里的并行数据线
  13. wire rx_done_w; //接收完成信号线
  14. /***************差分时钟转单端时钟*************/
  15. IBUFDS #(
  16. .DIFF_TERM("FALSE"),
  17. .IBUF_LOW_PWR("TRUE"),
  18. .IOSTANDARD("DEFAULT")
  19. ) IBUFDS_inst (
  20. .O(clk),
  21. .I(clk_p),
  22. .IB(clk_n)
  23. );
  24. /***************调用串口接收模块*************/
  25. UART_RX #(
  26. .CLK_F(CLK_FREQ),
  27. .UART_B(UART_BPS)
  28. )
  29. uut_rxd(
  30. .clk(clk),
  31. .rst(rst),
  32. .uart_tx_data(uart_rxd),
  33. .uart_rx_data(uart_rx_data_w),
  34. .rx_done(rx_done_w)
  35. );
  36. /***************调用串口发送模块*************/
  37. UART_TX #(
  38. .CLK_F(CLK_FREQ),
  39. .UART_B(UART_BPS)
  40. )
  41. uut_txd(
  42. .clk(clk),
  43. .rst(rst),
  44. .txd_start(rx_done_w),
  45. .uart_rx_data(uart_rx_data_w),
  46. .uart_tx_data(uart_txd)
  47. );
  48. endmodule

        综合、实现后,进行上板调试,为了简单起见,本设计采用回环的方式来调试验证,即PC发送数据到FPGA上,FPGA通过串口接收数据后再通过串口发送回PC。调试结果如图6所示,可以看到,所设计的串口工作正常:

图6

        至此,本设计完成。 

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

闽ICP备14008679号