一、前置知识

串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART 是一种采用异步串行通信方式的通用异步收发传输器(Universal Asynchronous Receiver Transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
image.png
image.png
协议层数据格式:
image.png
物理层接口标准:
image.png
使用DB9接口标准。
image.png
实验11:UART(uart_loopback) - 图6
USB接口类型:
image.png

二、原理分析

数据环回实验:
image.png
image.png
image.png
约定通信规则:数据位为 8 位,停止位为 1 位,无校验位,波特率为 115200bps
需要分别编写三个模块:数据接收模块、数据发送模块、环回控制模块。
E9CC465FC962DBE6DF3F55AD5C6F7BE5.png
串口接收:
无标题.png

  • sys_clk:系统时钟。
  • clk_cnt:系统时钟计数器,记录434个时钟(50_000_000/115200),表示在115200波特率下传输一位数据所需要的时间。
  • rx_cnt:每当clk_cnt计数到434,表示传输一位数据,rx_cnt加一。
  • uart_rxd:PC端上位机传输到FPGA的串行数据,FPGA接收。
  • start_flag:串口接收开始标志位,判断接收到的数据是否为起始标志。采用两时钟延迟采集uart_rxd下降沿,uart_rxd传输开始后产生一个上升沿。
  • rx_flag:串口接收标志位,拉高表示正在进行串口接收。以start_flag开始,以rx_cnt==9为止,即接收到的第9位停止位不需要,接收到第8位有效位就完成一轮数据接收。
  • uart_done:串口数据接收完成标志。
  • uart_data:串口接收到的数据,传给数据环回模块处理。

    三、代码编写

    3.1 数据接收模块

    ```verilog module uart_recv( input sys_clk, input sys_rst_n, input uart_rxd, output reg [7:0] uart_data, output reg uart_done );

// 判断uart_rxd起始位的下降沿 wire start_flag; reg uart_rxd_d0; // 当前状态 reg uart_rxd_d1; // 上一个状态 reg rx_flag; reg [3:0] rx_cnt; reg [8:0] clk_cnt; reg [7:0] rx_data;

parameter SYS_FREQ = 50_000_000; parameter UART_BPS = 115200; localparam UART_CNT = SYS_FREQ/UART_BPS;

assign start_flag = ~uart_rxd_d0 & uart_rxd_d1;

// 判断uart_rxd起始位的下降沿 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

// 处理接收标志位rx_flag always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) rx_flag <= 1’b0; else if(start_flag==1’b1) rx_flag <= 1’b1; else if(rx_cnt==4’d9) rx_flag <= 1’b0; else rx_flag <= rx_flag; end

// 处理时钟计数器clk_cnt always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) clk_cnt <= 9’d0; else if(rx_flag) begin if(clk_cnt < UART_CNT-1) clk_cnt <= clk_cnt + 1’b1; else clk_cnt <= 9’d0; end else clk_cnt <= 9’d0; end

// 处理接收数据寄存器rx_cnt always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) rx_cnt <= 4’d0; else if(rx_flag) begin if(clk_cnt == UART_CNT-1) rx_cnt <= rx_cnt + 1’b1; else rx_cnt <= rx_cnt; end else rx_cnt <= 4’d0; end

// 处理数据寄存器rx_data always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) rx_data <= 8’d0; else if(rx_flag) begin if(clk_cnt == UART_CNT/2) begin case (rx_cnt) 4’h1: rx_data[0] <= uart_rxd_d1; 4’h2: rx_data[1] <= uart_rxd_d1; 4’h3: rx_data[2] <= uart_rxd_d1; 4’h4: rx_data[3] <= uart_rxd_d1; 4’h5: rx_data[4] <= uart_rxd_d1; 4’h6: rx_data[5] <= uart_rxd_d1; 4’h7: rx_data[6] <= uart_rxd_d1; 4’h8: rx_data[7] <= uart_rxd_d1; default: ; endcase end else rx_data <= rx_data; end else rx_data <= 8’d0; end

// 处理输出的数据uart_data和标志位uart_done 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 <= rx_data; uart_done <= 1’b1; end else begin uart_data <= 8’d0; uart_done <= 1’b0; end end

endmodule

  1. <a name="Zrt4V"></a>
  2. ### 3.2 数据发送模块
  3. ```verilog
  4. module uart_send(
  5. input sys_clk,
  6. input sys_rst_n,
  7. input uart_en,
  8. input [7:0] uart_din,
  9. output reg uart_txd,
  10. output uart_tx_busy
  11. );
  12. wire en_flag;
  13. reg uart_en_d0;
  14. reg uart_en_d1;
  15. reg tx_flag;
  16. reg [7:0] tx_data;
  17. reg [3:0] tx_cnt;
  18. reg [8:0] clk_cnt;
  19. parameter SYS_FREQ = 50_000_000;
  20. parameter UART_BPS = 115200;
  21. localparam UART_CNT = SYS_FREQ/UART_BPS;
  22. assign uart_tx_busy = tx_flag;
  23. assign en_flag = uart_en_d0 & ~uart_en_d1; // 抓取uart_en上升沿
  24. // 判断uart_rxd起始位的下降沿
  25. always @(posedge sys_clk or negedge sys_rst_n) begin
  26. if(!sys_rst_n) begin
  27. uart_en_d0 <= 1'b0;
  28. uart_en_d1 <= 1'b0;
  29. end
  30. else begin
  31. uart_en_d0 <= uart_en;
  32. uart_en_d1 <= uart_en_d0;
  33. end
  34. end
  35. // 处理发送标志位tx_flag和发送数据寄存器tx_data
  36. always @(posedge sys_clk or negedge sys_rst_n) begin
  37. if(!sys_rst_n) begin
  38. tx_flag <= 1'b0;
  39. tx_data <= 8'd0;
  40. end
  41. else if (en_flag) begin
  42. tx_flag <= 1'b1;
  43. tx_data <= tx_data;
  44. end
  45. else if ((tx_cnt==4'd9) && (clk_cnt==UART_CNT-UART_CNT/16)) begin
  46. tx_flag <= 1'b0;
  47. tx_data <= 8'd0;
  48. end
  49. else begin
  50. tx_flag <= tx_flag;
  51. tx_data <= tx_data;
  52. end
  53. end
  54. // 处理时钟计数器clk_cnt
  55. always @(posedge sys_clk or negedge sys_rst_n) begin
  56. if(!sys_rst_n)
  57. clk_cnt <= 9'd0;
  58. else if(tx_flag) begin
  59. if(clk_cnt < UART_CNT-1)
  60. clk_cnt <= clk_cnt + 1'b1;
  61. else
  62. clk_cnt <= 9'd0;
  63. end
  64. else
  65. clk_cnt <= 9'd0;
  66. end
  67. // 处理发送数据寄存器tx_cnt
  68. always @(posedge sys_clk or negedge sys_rst_n) begin
  69. if(!sys_rst_n)
  70. tx_cnt <= 4'd0;
  71. else if(tx_flag) begin
  72. if(clk_cnt == UART_CNT-1)
  73. tx_cnt <= tx_cnt + 1'b1;
  74. else
  75. tx_cnt <= tx_cnt;
  76. end
  77. else
  78. tx_cnt <= 4'd0;
  79. end
  80. // 数据并转串
  81. always @(posedge sys_clk or negedge sys_rst_n) begin
  82. if(!sys_rst_n)
  83. uart_txd <= 1'b1; // 低电平数据开始传输
  84. else if(tx_flag) begin
  85. case(tx_cnt)
  86. 4'd0: uart_txd <= 1'b0;
  87. 4'd1: uart_txd <= tx_data[0];
  88. 4'd2: uart_txd <= tx_data[1];
  89. 4'd3: uart_txd <= tx_data[2];
  90. 4'd4: uart_txd <= tx_data[3];
  91. 4'd5: uart_txd <= tx_data[4];
  92. 4'd6: uart_txd <= tx_data[5];
  93. 4'd7: uart_txd <= tx_data[6];
  94. 4'd8: uart_txd <= tx_data[7];
  95. 4'd9: uart_txd <= 1'b1;
  96. default:;
  97. endcase
  98. end
  99. else
  100. uart_txd <= 1'b1;
  101. end
  102. endmodule

3.3 数据环回模块

  1. module uart_loop(
  2. input sys_clk,
  3. input sys_rst_n,
  4. input recv_done,
  5. input [7:0] recv_data,
  6. input send_busy,
  7. output reg send_en,
  8. output reg [7:0] send_data
  9. );
  10. wire recv_done_flag;
  11. reg recv_done_d0;
  12. reg recv_done_d1;
  13. reg send_ready;
  14. assign recv_done_flag = recv_done_d0 & ~recv_done_d1; // 抓取 recv_done 上升沿
  15. always @(posedge sys_clk or negedge sys_rst_n) begin
  16. if(!sys_rst_n) begin
  17. recv_done_d0 <= 1'b0;
  18. recv_done_d1 <= 1'b0;
  19. end
  20. else begin
  21. recv_done_d0 <= recv_done;
  22. recv_done_d1 <= recv_done_d0;
  23. end
  24. end
  25. always @(posedge sys_clk or negedge sys_rst_n) begin
  26. if(!sys_rst_n) begin
  27. send_en <= 1'b0;
  28. send_data <= 8'd0;
  29. send_ready <= 1'b0;
  30. end
  31. else begin
  32. if(recv_done_flag) begin
  33. send_en <= 1'b0;
  34. send_data <= recv_data;
  35. send_ready <= 1'b1;
  36. end
  37. else if(send_ready && ~send_busy) begin
  38. send_en <= 1'b1;
  39. send_ready <= 1'b0;
  40. end
  41. end
  42. end
  43. endmodule

3.4 顶层模块

  1. module uart_loopback_top(
  2. input sys_clk,
  3. input sys_rst_n,
  4. input uart_rxd,
  5. output uart_txd
  6. );
  7. wire [7:0] uart_recv_data;
  8. wire [7:0] uart_send_data;
  9. wire uart_done;
  10. wire uart_en;
  11. wire uart_tx_busy;
  12. uart_recv u_uart_recv(
  13. .sys_clk (sys_clk ),
  14. .sys_rst_n (sys_rst_n ),
  15. .uart_rxd (uart_rxd ),
  16. .uart_data (uart_recv_data),
  17. .uart_done (uart_done )
  18. );
  19. uart_send u_uart_send(
  20. .sys_clk (sys_clk ),
  21. .sys_rst_n (sys_rst_n ),
  22. .uart_en (uart_en ),
  23. .uart_din (uart_send_data),
  24. .uart_txd (uart_txd ),
  25. .uart_tx_busy (uart_tx_busy )
  26. );
  27. uart_loop u_uart_loop(
  28. .sys_clk (sys_clk ),
  29. .sys_rst_n (sys_rst_n ),
  30. .recv_done (uart_done ),
  31. .recv_data (uart_recv_data),
  32. .send_busy (uart_tx_busy ),
  33. .send_en (uart_en ),
  34. .send_data (uart_send_data)
  35. );
  36. endmodule

3.5 引脚

  1. package require ::quartus::project
  2. #system clock:50Mhz
  3. set_location_assignment PIN_M2 -to sys_clk
  4. #system reset
  5. set_location_assignment PIN_M1 -to sys_rst_n
  6. #USB UART
  7. set_location_assignment PIN_N5 -to uart_rxd
  8. set_location_assignment PIN_M7 -to uart_txd
  9. #RS232 UART
  10. #set_location_assignment PIN_B8 -to uart_rxd
  11. #set_location_assignment PIN_C3 -to uart_txd
  12. #RS485
  13. #set_location_assignment PIN_B8 -to rs485_uart_rxd
  14. #set_location_assignment PIN_C3 -to rs485_uart_txd

四、测试

能接收到数据,但接收到的数据都是空字符。。
image.png 2022-05-07 09-12-13.mp4 (16.06MB)检查一下代码,在数据发送模块里,没有将环回模块传过来的数据进行处理,一直在获取空数据0,所以会在串口调试助手显示空白符(commit:aeee75b):
image.png
修改如下:

  1. // 处理发送标志位tx_flag和发送数据寄存器tx_data
  2. always @(posedge sys_clk or negedge sys_rst_n) begin
  3. if(!sys_rst_n) begin
  4. tx_flag <= 1'b0;
  5. tx_data <= 8'd0;
  6. end
  7. else if (en_flag) begin
  8. tx_flag <= 1'b1;
  9. tx_data <= uart_din; // 准备发送数据,寄存器存储待发送的数据
  10. end
  11. else if ((tx_cnt==4'd9) && (clk_cnt==UART_CNT-UART_CNT/16)) begin
  12. tx_flag <= 1'b0;
  13. tx_data <= 8'd0;
  14. end
  15. else begin
  16. tx_flag <= tx_flag;
  17. tx_data <= tx_data;
  18. end
  19. end

重新编译烧录: 2022-05-07 09-34-36.mp4 (19.25MB)