当前位置:   article > 正文

FPGA-串口通信_fpga串口通信

fpga串口通信

串口通信概念

UART通信原理

UART (universal asynchronous receiver-transmitter)是一种采用异步串行通信方式的通用异步收发传输器;它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。UART串口通信需要两根信号线来实现,一根用于发送,另外一根接收(表明是异步全双工通信)。

①协议层:通信协议(包括数据格式、传输速率等)。

②物理层:接口类型、电平标准等。

协议层:数据格式,一帧数据由4部分组成(用代码设计串口用到的就是协议层):

·起始位( 1bit)

·数据位(6/7/8bit)

·奇偶校验位(1bit)

·停止位(1bit/1.5bit/2bit)

起始位,数据位,停止位是必要的。

数据位的数据高位在后,低位在前,从左只有从低到高,即bit[0]至bit[n].

协议层:传输速率

串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,

单位是bit/s (位/秒),简称bps;

常用的波特率有9600、19200、 38400、 57606以 及115200等。

物理层:接口类型、电平标准串口电平标准:

.TTL电平的串口(3.3V)

·RS232电平的串口(+5~+12V为低电平,-12~-5V为高电平)

串口按电气标准分包括:

·RS-232-C:TXD/RXD/GND、15米/9600bps

·RS-422:TX+/TX-/RX+/RX-/GND

.RS485:A/B/G 、1200米/9600bps

数据传输(串口回环)整体框架:

数据传输时,串行数据经过接收端进行串转并的处理变为并行数据,再经过发送端并转串处理传输串行数据。

起始位检测

首先找到起始位才能开始数据接收(检测下降沿,进行同步打拍;检测下降沿时为r0低电平,r1高电平)

ps:为什么要做同步?因为传输过程中是没有时钟信号的,首先需要将其与时钟边沿同步,然后打拍检测边沿

起始位传输占据1bit,求波特率为9600传输1bit需要的时钟周期,即起始位所占用的时间,计算方法为:链接(https://blog.csdn.net/weixin_45388202/article/details/116465712),需要计数大约5208次。

数据位传输

传输一帧数据的数据位如图所示:

从低位到高位传输,所以数据位数据为11111001.

接收端RX

rx_data接收数据进行中间采样(中间信号稳定),如果接收的时候检测到了下降沿但采样的起始位为1(接收数据包含了起始位),说明rxd可能产生了抖动过后又回到高电平,因此需要进行判断其是否为起始位,采样到1说明产生了抖动,此时将bit计数器清零,rx_data不是有效数据不能作为输出。

输入串行数据rxd,进入FPGA后,一帧数据里包含停止位和起始位,但只有数据位这8位是需要的有效数据。

接收端设计文件:

  1. 输入串行数据rxd,输出8位数据rx_data,加入标志信号rx_data_vld判断输出的rx_data是否为8位数据位,是则有效。

  1. 对rxd进行同步打拍,边沿检测下降沿(起始位为一帧数据的开始)

  1. 定义参数及信号,flag为接收一帧数据的标志信号,作为开启bps计数器的标志,即在一帧数据到来时,起始位检测下降沿bps计数器开启计数flag拉高,记完1bit后end_cnt_bps拉高结束这一比特计时,此处flag则拉低。除开end_cnt_bps的其他时刻均保持flag拉高,知道最后1bit数据传输完毕。

  1. 对于end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]),它必须满足计数完第十个bit或者起始位data_in[0]为1,此时才是bit结束计数标志,两钟情况满足任一即可。

  1. 进行数据采样时最好是选择之间的稳定信号,为什么加入data_in信号?我的理解是此信号是经过串行信号通过FPGA进行并行处理,但我们只需要数据位的8位有效信号,因此需要对有效信号进行提取做准备

  1. 将有效数据位数据赋予rx_data,并进行是否是有效数据的判断,判断条件结束bit计数和存在起始位必须同时满足,这样才能有一帧完整的10bit数据,然后去除的data_in[8:1]中间的8位数据才是数据位信息。

  1. module uart_rx (
  2. input clk ,
  3. input rst_n ,
  4. input rxd ,//输入串行数据
  5. output reg [7:0] rx_data ,
  6. output reg rx_data_vld//判断标志信号,判断rx_data的数据是否有效
  7. );
  8. //参数定义
  9. parameter BPS_9600 = 5208 ;
  10. //信号定义
  11. reg rx_r0 ;
  12. reg rx_r1 ;
  13. wire nedge ;//检测起始位
  14. reg [12:0] cnt_bps ;//计数1bit时钟周期
  15. wire add_cnt_bps ;
  16. wire end_cnt_bps ;
  17. reg flag ;//接收标志
  18. reg [3:0] cnt_bit ;//计数bit数
  19. wire add_cnt_bit ;
  20. wire end_cnt_bit ;
  21. reg [9:0] data_in ;//一帧数据,起始位(1bit),数据位(8bit),停止位(1bit)
  22. //进行串并转换
  23. //同步,打拍,边沿检测
  24. always @(posedge clk or negedge rst_n) begin
  25. if (!rst_n) begin
  26. rx_r0 <= 1'b1 ;
  27. rx_r1 <= 1'b1 ;
  28. end
  29. else begin
  30. rx_r0 <= rxd ;//同步数据
  31. rx_r1 <= rx_r0 ;//打拍
  32. end
  33. end
  34. assign nedge = rx_r1 & ~rx_r0 ;//下降沿检测
  35. //flag
  36. always @(posedge clk or negedge rst_n) begin
  37. if (!rst_n) begin
  38. flag <= 1'b0 ;
  39. end
  40. else if(nedge) begin
  41. flag <= 1'b1 ;
  42. end
  43. else if(end_cnt_bit) begin
  44. flag <= 1'b0;
  45. end
  46. end
  47. //cnt_bps
  48. always @(posedge clk or negedge rst_n) begin
  49. if (!rst_n) begin
  50. cnt_bps <= 'd0;
  51. end
  52. else if (add_cnt_bps) begin
  53. if (end_cnt_bps) begin
  54. cnt_bps <= 'd0 ;
  55. end
  56. else begin
  57. cnt_bps <= cnt_bps + 1'b1 ;
  58. end
  59. end
  60. end
  61. assign add_cnt_bps = flag ;
  62. assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;
  63. //cnt_bit
  64. always @(posedge clk or negedge rst_n) begin
  65. if (!rst_n) begin
  66. cnt_bit <= 'd0;
  67. end
  68. else if (add_cnt_bit) begin
  69. if (end_cnt_bit) begin
  70. cnt_bit <= 'd0 ;
  71. end
  72. else begin
  73. cnt_bit <= cnt_bit + 1'b1 ;
  74. end
  75. end
  76. end
  77. assign add_cnt_bit = end_cnt_bps ;
  78. assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9 || data_in[0]) ;//data_in[0]是存的起始位
  79. //data_in采样数据(包含起始位和停止位)
  80. always @(posedge clk or negedge rst_n) begin
  81. if (!rst_n) begin
  82. data_in <= 0;
  83. end
  84. else if (cnt_bps == BPS_9600 >> 1) begin//为了稳定采样中间的值
  85. data_in[cnt_bit] <= rx_r1 ;
  86. end
  87. end
  88. //取数据位rx_data
  89. always @(posedge clk or negedge rst_n) begin
  90. if (!rst_n) begin
  91. rx_data <= 0 ;
  92. end
  93. else if (end_cnt_bit) begin
  94. rx_data <= data_in[8:1] ;//只需要给数据位
  95. end
  96. else begin
  97. rx_data <= 0 ;
  98. end
  99. end
  100. //判断rx_data是否有效
  101. always @(posedge clk or negedge rst_n) begin
  102. if (!rst_n) begin
  103. rx_data_vld <= 0 ;
  104. end
  105. else begin
  106. rx_data_vld <= end_cnt_bit && (data_in[0] == 0);
  107. end
  108. end
  109. endmodule

接收端测试文件:

定义系统时钟周期,例化模块,产生时钟,上电复位并赋予初值,然后分配起始位,数据位,停止位并进行延时处理。

  1. `timescale 1ns/1ps
  2. module tb_uart_rx ();
  3. reg clk ;
  4. reg rst_n ;
  5. reg rxd ;
  6. wire [7:0] rx_data ;
  7. wire rx_data_vld ;
  8. //参数定义
  9. parameter CYCLE = 20 ;
  10. //例化
  11. uart_rx u_uart_rx(
  12. .clk (clk ),
  13. .rst_n (rst_n ),
  14. .rxd (rxd ),
  15. .rx_data (rx_data ),
  16. .rx_data_vld (rx_data_vld)
  17. );
  18. //时钟
  19. initial begin
  20. clk = 1'b1 ;
  21. forever begin
  22. #(CYCLE/2);
  23. clk = ~clk ;
  24. end
  25. end
  26. integer i;//数据个数
  27. initial begin
  28. rst_n = 1'b1 ;
  29. #(CYCLE);
  30. rst_n = 1'b0 ;
  31. rxd = 1'b1 ;
  32. #22;
  33. rst_n = 1'b1 ;
  34. #(CYCLE*200);
  35. //模拟一帧数据的格式
  36. rxd = 1'b0 ;//起始位
  37. #(CYCLE*5208);
  38. //数据位
  39. for (i=0;i<8;i=i+1) begin
  40. case (i)//赋值rxd = 1111_1001
  41. 1: rxd = 1'b0 ;
  42. 2: rxd = 1'b0 ;
  43. default: rxd = 1'b1 ;//只有第一位和第二位为0
  44. endcase
  45. #(CYCLE*5208);
  46. end
  47. rxd = 1'b1 ;//停止位
  48. #(CYCLE*5208);
  49. #(CYCLE*200);
  50. $stop;
  51. end
  52. endmodule

仿真

test.do文件:

  1. vlib work
  2. vmap work work
  3. #编译testbench文件
  4. vlog tb_uart_rx.v
  5. #编译 设计文件
  6. vlog ../rtl/uart_rx.v
  7. #vlog altera_mf.v
  8. #指定仿真顶层
  9. vsim -novopt work.tb_uart_rx
  10. #添加信号到波形窗
  11. add wave -position insertpoint sim:/tb_uart_rx//*
  12. run -all

观测到最后8位数据位为1111_1001符合要求。

疑问

此处没有理解透彻5802为啥右移一位就变成中间部分的数据了?将打拍的数据赋予data_in内部为什么应bit计数器表示?

答:右移一一位相当于除以2,符合在中间值采样原则

发送端TX

发送端设计文件:

和发送端类似,由上设计框图

  1. 输入并行信号tx_data,将rx_data的数据传输给它,tx_data_vld为传输8位数据位有效信号标志,输出串行数据txd

  1. 必须保持波特率一致,定义参数

  1. flag在这里作为发送数据标志,同时也是计时器bps的开启条件,其在数据有效时拉高进行发送,所有bit的数据发送完后拉低

  1. 同样用data_out寄存一帧10bit的数据(并行数据),通过发送端转化为串行数据,在数据发送有效时,添加起始位和停止位赋值于它进行寄存

  1. 输出串行数据txd,仅在发送数据标志flag拉高时发送数据,数据来自于寄存于data_out的10bit并行数据。

  1. module uart_tx (
  2. input clk ,
  3. input rst_n ,
  4. input [7:0] tx_data ,
  5. input tx_data_vld ,//发送有效信号标志
  6. output reg txd
  7. );
  8. //参数定义
  9. parameter BPS_9600 = 5208 ;
  10. //信号定义
  11. reg [12:0] cnt_bps ;//计数1bit时钟周期
  12. wire add_cnt_bps ;
  13. wire end_cnt_bps ;
  14. reg flag ;//发送数据标志,同时也是bps计数的开启条件
  15. reg [3:0] cnt_bit ;//计数bit数
  16. wire add_cnt_bit ;
  17. wire end_cnt_bit ;
  18. reg [9:0] data_out ;
  19. //flag
  20. always @(posedge clk or negedge rst_n) begin
  21. if (!rst_n) begin
  22. flag <= 1'b0 ;
  23. end
  24. else if(tx_data_vld) begin //在发送有效信号时,flag作为标志拉高
  25. flag <= 1'b1 ;
  26. end
  27. else if(end_cnt_bit) begin//bit计数结束时,flag拉低
  28. flag <= 1'b0;
  29. end
  30. end
  31. //cnt_bps
  32. always @(posedge clk or negedge rst_n) begin
  33. if (!rst_n) begin
  34. cnt_bps <= 'd0;
  35. end
  36. else if (add_cnt_bps) begin
  37. if (end_cnt_bps) begin
  38. cnt_bps <= 'd0 ;
  39. end
  40. else begin
  41. cnt_bps <= cnt_bps + 1'b1 ;
  42. end
  43. end
  44. end
  45. assign add_cnt_bps = flag ;
  46. assign end_cnt_bps = add_cnt_bps && (cnt_bps == BPS_9600 - 1) ;
  47. //cnt_bit
  48. always @(posedge clk or negedge rst_n) begin
  49. if (!rst_n) begin
  50. cnt_bit <= 'd0;
  51. end
  52. else if (add_cnt_bit) begin
  53. if (end_cnt_bit) begin
  54. cnt_bit <= 'd0 ;
  55. end
  56. else begin
  57. cnt_bit <= cnt_bit + 1'b1 ;
  58. end
  59. end
  60. end
  61. assign add_cnt_bit = end_cnt_bps ;
  62. assign end_cnt_bit = add_cnt_bit && (cnt_bit == 9) ;
  63. //寄存数据至data_out(包含起始位和停止位)
  64. always @(posedge clk or negedge rst_n) begin
  65. if (!rst_n) begin
  66. data_out <= 0;
  67. end
  68. else if (tx_data_vld) begin
  69. data_out <= {1'b1,tx_data,1'b0} ;
  70. end
  71. end
  72. //输出txd
  73. always @(posedge clk or negedge rst_n) begin
  74. if (!rst_n) begin
  75. txd <= 1'b1 ;
  76. end
  77. else if (flag) begin
  78. txd <= data_out[cnt_bit] ;
  79. end
  80. end
  81. endmodule

发送端测试文件:

只需要加上接收端的例化模块即可,中间信号端口互通,在最后再增加10bit的周期延迟。

  1. `timescale 1ns/1ps
  2. module tb_uart_rx ();
  3. reg clk ;
  4. reg rst_n ;
  5. reg rxd ;
  6. wire [7:0] rx_data ;
  7. wire rx_data_vld ;
  8. //参数定义
  9. parameter CYCLE = 20 ;
  10. //例化
  11. uart_rx u_uart_rx(
  12. .clk (clk ),
  13. .rst_n (rst_n ),
  14. .rxd (rxd ),
  15. .rx_data (rx_data ),
  16. .rx_data_vld (rx_data_vld)
  17. );
  18. uart_tx u_uart_tx(
  19. .clk (clk ) ,
  20. .rst_n (rst_n ) ,
  21. .tx_data (rx_data ) ,
  22. .tx_data_vld (rx_data_vld) ,
  23. .txd (txd )
  24. );
  25. //时钟
  26. initial begin
  27. clk = 1'b1 ;
  28. forever begin
  29. #(CYCLE/2);
  30. clk = ~clk ;
  31. end
  32. end
  33. integer i;//数据个数
  34. initial begin
  35. rst_n = 1'b1 ;
  36. #(CYCLE);
  37. rst_n = 1'b0 ;
  38. rxd = 1'b1 ;
  39. #22;
  40. rst_n = 1'b1 ;
  41. #(CYCLE*200);//增加间隔
  42. //模拟一帧数据的格式
  43. rxd = 1'b0 ;//起始位
  44. #(CYCLE*5208);
  45. //数据位
  46. for (i=0;i<8;i=i+1) begin
  47. case (i)//赋值rxd = 1111_1001
  48. 1: rxd = 1'b0 ;
  49. 2: rxd = 1'b0 ;
  50. default: rxd = 1'b1 ;//只有第一位和第二位为0
  51. endcase
  52. #(CYCLE*5208);
  53. end
  54. rxd = 1'b1 ;//停止位
  55. #(CYCLE*5208);
  56. #(CYCLE*200);//增加间隔
  57. #(CYCLE*10*5208);//10bit时钟周期延迟
  58. $stop;
  59. end
  60. endmodule

仿真

test.do文件

  1. vlib work
  2. vmap work work
  3. #编译testbench文件
  4. vlog tb_uart_rx.v
  5. #编译 设计文件
  6. vlog ../rtl/uart_rx.v
  7. vlog ../rtl/uart_tx.v
  8. #vlog altera_mf.v
  9. #指定仿真顶层
  10. vsim -novopt work.tb_uart_rx
  11. #添加信号到波形窗
  12. add wave -position insertpoint sim:/tb_uart_rx//*
  13. run -all

顶层仿真

只需要改动例化模块即可

  1. `timescale 1ns/1ps
  2. module tb_uart ();
  3. reg clk ;
  4. reg rst_n ;
  5. reg rxd ;
  6. wire txd ;
  7. //参数定义
  8. parameter CYCLE = 20 ;
  9. //例化
  10. uart u_uart(
  11. .clk (clk ),
  12. .rst_n (rst_n),
  13. .rxd (rxd ),
  14. .txd (txd )
  15. );
  16. //时钟
  17. initial begin
  18. clk = 1'b1 ;
  19. forever begin
  20. #(CYCLE/2);
  21. clk = ~clk ;
  22. end
  23. end
  24. integer i;//数据个数
  25. initial begin
  26. rst_n = 1'b1 ;
  27. #(CYCLE);
  28. rst_n = 1'b0 ;
  29. rxd = 1'b1 ;
  30. #22;
  31. rst_n = 1'b1 ;
  32. #(CYCLE*200);//增加间隔
  33. //模拟一帧数据的格式
  34. rxd = 1'b0 ;//起始位
  35. #(CYCLE*5208);
  36. //数据位
  37. for (i=0;i<8;i=i+1) begin
  38. case (i)//赋值rxd = 1111_1001
  39. 1: rxd = 1'b0 ;
  40. 2: rxd = 1'b0 ;
  41. default: rxd = 1'b1 ;//只有第一位和第二位为0
  42. endcase
  43. #(CYCLE*5208);
  44. end
  45. rxd = 1'b1 ;//停止位
  46. #(CYCLE*5208);
  47. #(CYCLE*200);//增加间隔
  48. #(CYCLE*10*5208);//10bit时钟周期延迟
  49. $stop;
  50. end
  51. endmodule

仿真图

另外一种测试方法:

用FPGA的tx端去模拟PC端的tx端发送数据,然后FPGA接收端rx进行接收

代码:

直接建立task发送三个数据

  1. `timescale 1ns/1ps
  2. module tb_uart1 ();
  3. reg clk ;
  4. reg rst_n ;
  5. reg [7:0] data ;
  6. reg data_vld ;
  7. wire dout ; //中间连线
  8. wire txd ;//tx输出
  9. //参数定义
  10. parameter CYCLE = 20 ;
  11. //例化
  12. uart_tx u_uart_tx(
  13. .clk (clk ) ,
  14. .rst_n (rst_n ) ,
  15. .tx_data (data ) ,
  16. .tx_data_vld (data_vld) ,
  17. .txd (dout )
  18. );
  19. uart u_uart(
  20. .clk (clk ),
  21. .rst_n (rst_n ),
  22. .rxd (dout ),
  23. .txd (txd )
  24. );
  25. //时钟
  26. initial begin
  27. clk = 1'b1 ;
  28. forever begin
  29. #(CYCLE/2);
  30. clk = ~clk ;
  31. end
  32. end
  33. initial begin
  34. rst_n = 1'b1 ;
  35. #(CYCLE);
  36. rst_n = 1'b0 ;
  37. data = 0 ;
  38. data_vld = 1'b0 ;
  39. #22;
  40. rst_n = 1'b1 ;
  41. #(CYCLE*200);//增加间隔
  42. //发送几个数据
  43. Send(8'hf9);
  44. Send(8'ha0);
  45. Send(8'hff);
  46. #(CYCLE*10*5208);//10bit时钟周期延迟
  47. #(CYCLE*200);
  48. $stop;
  49. end
  50. task Send;
  51. input [7:0] send_data ;
  52. begin
  53. data = send_data ;
  54. data_vld = 1'b1 ;
  55. #(CYCLE);
  56. data_vld = 1'b0 ;
  57. #(10*5208*CYCLE);
  58. end
  59. endtask
  60. endmodule

上板验证

能够在波特率为9600时进行发送和接收

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

闽ICP备14008679号