当前位置:   article > 正文

FPGA自学笔记--串口通信实现(vivado&verilog版)_串口协议verilog

串口协议verilog

最近做了两种通信协议的实现的练习(uart,spi),此文介绍uart串口协议(串口发送)的verilog实现和testbench的编写,考虑到还有部分同学使用vhdl,vhdl版本会随后发布。在以后的系列里,还会有介绍spi协议的文章,把我自己学习中遇到的困难和正确的解决方案记录下来,仿真环境为vivado 2018.3.

一、串口通信协议(uart)

        串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
此文目前只是实现串口发送过程,即将并行数据转化成串行数据发送给其他设备。

作为新手,我觉得串口通信主要要注意以下几点:

1.什么叫异步?异步,即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。而我们后面讲的SPI协议就是同步方式,是通过一个sclk信号来协同主从设备。 

2.物理层,如下图所示。我们其实只用关心RXD、TXD、GND端口,其他其实不用知道太多,有兴趣的同学可以自行百度。

3.看懂uart发送的时序图。(非常重要!!!!)

二、串口发送的时序图

1.首先要了解 串口发送的数据格式,因为是异步方式,所以数据格式就显得尤为重要,就是要知道通信的起始标志和终止标志,即通信是什么时候开始,是什么时候结束。

        UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如上图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

        起始位:起始位标志。低有效。

        数据位:一帧数据中的有效数据。注意,串口协议规定了数据位长度只能位 6 7 8 位。要想发多位数据,只能将其切分为多个过程发送。

        奇偶校验位:是用来验证数据的正确性。奇偶校验一般不使用,如果 使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。其实就是发的时候如果奇校验位为1,表面数据中1的个数为奇数个,再看接受端的奇校验位是否依旧为1。如果是的话,校验成功。在一定概率上保证了传输的正确性。当然,只是概率上的传输正确。此次试验不同奇偶校验。

        停止位:停止位标志着一帧数据的结束,高有效。

        空闲时,传输 1 。

        波特率设置 : 由于是异步通信,所以没有一个统一的时钟,需要双方约定传输传输速率。常见的波特率有:300, 1200, 2400, 9600, 19200, 115200 等.即一秒发送多少位数据。

        所以我们需要实现下面的时序图。当使能信号到来时,发送起始位,数据位,停止位,一帧数据发送结束。

 三、FPGA实现。

 任务: uart_tx模块由单脉冲信号send_go使能,将data[7:0]读入uart_tx模块,发送完成后,输出单脉冲tx_done。

   模块框图:

设计文件:

  1. `timescale 1ns / 1ps
  2. //
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2022/04/28 09:19:27
  7. // Design Name:
  8. // Module Name: uart_tx
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //
  21. module uart_byte_tx (
  22. clk,
  23. reset,
  24. send_go, // 改成单脉冲启动信号 启动一个内部 send_en 信号
  25. data,
  26. baud_set,
  27. uart_tx,
  28. tx_done
  29. );
  30. input clk;
  31. input reset;
  32. input send_go;
  33. input [7:0]data;
  34. input [2:0]baud_set;
  35. output reg uart_tx;
  36. output reg tx_done;
  37. // every data remain time
  38. // baud_set = 0 bps = 9600 bps_max = 1000 000 000 / 9600 / 20
  39. reg [17:0]bps_max;
  40. always@(*)
  41. case(baud_set)
  42. 0:bps_max = 1000000000/9600/20; // clk = 50MHZ
  43. 0:bps_max = 1000000000/19200/20;
  44. 0:bps_max = 1000000000/38400/20;
  45. 0:bps_max = 1000000000/57600/20;
  46. 0:bps_max = 1000000000/115200/20;
  47. default:bps_max = 1000000000/9600/20;
  48. endcase
  49. reg send_en;
  50. always@(posedge clk or negedge reset)
  51. if (!reset)
  52. send_en <= 0;
  53. else if(send_go)
  54. send_en <= 1;
  55. else if(tx_done)
  56. send_en <= 0;
  57. // 一旦开始发送,send_go 有效,就将外部的data 锁存起来,,这样防止外部数据变化而采错data【i】
  58. reg [7:0]r_data;
  59. always@(posedge clk or negedge reset)
  60. if (send_go)
  61. r_data <= data;
  62. else
  63. r_data <= r_data;
  64. wire bps_clk;
  65. assign bps_clk = (div_cnt == 1);
  66. reg [17:0]div_cnt;
  67. always @(posedge clk or negedge reset) begin
  68. if(!reset)
  69. div_cnt <= 0;
  70. else if(send_en)
  71. begin
  72. if(div_cnt == bps_max - 1)
  73. div_cnt <= 0;
  74. else
  75. div_cnt <= div_cnt + 1'b1;
  76. end
  77. else
  78. div_cnt <= 0;
  79. end
  80. reg [3:0]bps_cnt; // 11
  81. always @(posedge clk or negedge reset) begin
  82. if(!reset)
  83. bps_cnt <= 0;
  84. else if(send_en)begin
  85. if(bps_clk)begin
  86. //if(div_cnt == 1)begin // 当send_en有效事 bps_cnt 才开始加 bps_cnt 不要等到最大再加一 这样会滞后
  87. if(bps_cnt == 12)
  88. bps_cnt <= 0;
  89. else
  90. bps_cnt <= bps_cnt + 1'b1;
  91. end
  92. end
  93. else
  94. bps_cnt <= 0;
  95. end
  96. // send
  97. always @(posedge clk or negedge reset)
  98. if(!reset)begin
  99. uart_tx <= 1'b1;
  100. tx_done <= 1'b0;
  101. end
  102. else begin
  103. case(bps_cnt)
  104. // 0:begin uart_tx <= 1'b0;tx_done <= 1'b0;end 这样写,bps_cnt 上面是 send_en 为零的时候就 为00 不科学
  105. // 1:uart_tx <= data[0];
  106. // 2:uart_tx <= data[1];
  107. // 3:uart_tx <= data[2];
  108. // 4:uart_tx <= data[3];
  109. // 5:uart_tx <= data[4];
  110. // 6:uart_tx <= data[5];
  111. // 7:uart_tx <= data[6];
  112. // 8:uart_tx <= data[7];
  113. // 9:uart_tx <= 1'b1;
  114. // 10:begin uart_tx <= 1'b1;tx_done <= 1'b1;end
  115. // default:uart_tx <= 1'b1;
  116. 0:tx_done<=1'b0;
  117. 1:uart_tx <= 1'b0;
  118. 2:uart_tx <= r_data[0];
  119. 3:uart_tx <= r_data[1];
  120. 4:uart_tx <= r_data[2];
  121. 5:uart_tx <= r_data[3];
  122. 6:uart_tx <= r_data[4];
  123. 7:uart_tx <= r_data[5];
  124. 8:uart_tx <= r_data[6];
  125. 9:uart_tx <= r_data[7];
  126. 10:uart_tx <= 1'b1;
  127. 11:begin uart_tx <= 1'b1;end
  128. default:uart_tx <= 1'b1;
  129. endcase
  130. end
  131. // 为了保证 tx_done 只持续一个时钟周 单独写一个process
  132. always @(posedge clk or negedge reset)
  133. if(!reset)
  134. tx_done <= 1'b0;
  135. else if ((bps_clk == 1) && (bps_cnt == 10))
  136. tx_done <= 1'b1;
  137. else
  138. tx_done <= 1'b0;
  139. endmodule

testbench文件:

  1. `timescale 1ns / 1ps
  2. //
  3. // Company:
  4. // Engineer:
  5. //
  6. // Create Date: 2022/05/01 14:23:59
  7. // Design Name:
  8. // Module Name: uart_byte_tx_tb
  9. // Project Name:
  10. // Target Devices:
  11. // Tool Versions:
  12. // Description:
  13. //
  14. // Dependencies:
  15. //
  16. // Revision:
  17. // Revision 0.01 - File Created
  18. // Additional Comments:
  19. //
  20. //
  21. module uart_byte_tx_tb(
  22. );
  23. reg clk;
  24. reg reset;
  25. reg send_go;
  26. reg [7:0]data;
  27. wire uart_tx;
  28. wire tx_done;
  29. uart_byte_tx uart_byte_tx (
  30. .clk(clk),
  31. .reset(reset),
  32. .send_go(send_go), // 改成单脉冲启动信号 启动一个内部 send_en 信号
  33. .data(data),
  34. .baud_set(3'd4),
  35. .uart_tx(uart_tx),
  36. .tx_done(tx_done)
  37. );
  38. initial clk = 1;
  39. always#10 clk = ~clk;
  40. initial begin
  41. reset = 0;
  42. #201;
  43. reset = 1;
  44. send_go = 1;
  45. data = 8'h57;
  46. #20
  47. send_go = 0;
  48. @(posedge tx_done);
  49. #201;
  50. reset = 1;
  51. send_go = 1;
  52. data = 8'h75;
  53. #20
  54. send_go = 0;
  55. @(posedge tx_done);
  56. #20000;
  57. $stop;
  58. end
  59. endmodule

仿真结果: 

由上图结果我们可以看到: 当send_go信号来临时(单脉冲),至于为什么设计成单脉冲,因为单脉冲更符合模块与模块之间的握手习惯。这个主要参考B站上小梅哥的视频。现在我们继续看这个仿真结果,首先发送01010111这个数据,串口规定从低位到高位,所以加上起始标志和结束标志,就是0111010101十位数据,证明时序的正确性。每发送完一个数据,产生一个tx_done单脉冲。工程文件可以直接下载我的资源(0积分)串口通信实现(verilog带testbench文件)-嵌入式文档类资源-CSDN文库

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

闽ICP备14008679号

        
cppcmd=keepalive&