• 基于FPGA的任意字节数的串口接收(含源码工程)


    1、概述

            在这篇文章:基于FPGA的任意字节数的串口发送(含源码工程)中实现了基于FPGA的任意字节数的串口发送,那么对应的,这一篇文章将分享给大家如何实现任意字节的FPGA接收方法。

            在这篇文章:串口(UART)的FPGA实现(含源码工程),实现了基于FPGA的串口接收驱动。利用接收驱动可以实现 起始位1bit+数据位8bit+停止位1bit 共10bit的单字节接收。

            但是在实际应用过程中有时候需要一次性接收多个字节的数据。比如,一次性通过UART接收5个字节的数据,再将其组合成一个位宽为【39:0】的数据。诚然,可以直接更改此文中的串口接收驱动,使其变成 起始位1bit+数据位40bit+停止位1bit 共42bit的多字节传输。这种方法理论上是可行的,因为UART协议并没有规定你一次要发送、接收多个少bit的数据,既然能接收8个bit,那同样能接收40个bit。

            但是很不幸,实际上基本行不通,因为通用的绝大部分上位机软件都不支持一次解析40bit的数据位(如果你自己写上位机就当我没说)。

            所以只能想点其他办法,比如:写个逻辑,多次调用8bit即单字节的串口接收驱动,那么40bit的数据就调用5次,也就是5个 起始位1bit+数据位8bit+停止位1bit共10bit的单字节 分别接收,再组合成一个40bit的数据就可以了。


    2、串口接收驱动

            请参考:串口(UART)的FPGA实现(含源码工程),在此文详细介绍了串口的接收驱动。

            以下代码可以实现 1bit+数据位8bit+停止位1bit 共10bit的单字节接收,无奇偶校验。

    1. // *******************************************************************************************************
    2. // ** 作者 : 孤独的单刀
    3. // ** 邮箱 : zachary_wu93@163.com
    4. // ** 博客 : https://blog.csdn.net/wuzhikaidetb
    5. // ** 日期 : 2022/08/05
    6. // ** 功能 : 1、基于FPGA的串口接收驱动模块;
    7. // 2、可重新设置波特率BPS、主时钟CLK_FRE;
    8. // 3、起始位1bit,数据位8bit,停止位1bit,无奇偶校验。
    9. // *******************************************************************************************************
    10. module uart_rx
    11. #(
    12. parameter integer BPS = 9_600 , //发送波特率
    13. parameter integer CLK_FRE = 50_000_000 //输入时钟频率
    14. )
    15. (
    16. //系统接口
    17. input sys_clk , //50M系统时钟
    18. input sys_rst_n , //系统复位
    19. //UART接收线
    20. input uart_rxd , //接收数据线
    21. //用户接口
    22. output reg uart_rx_done , //数据接收完成标志,当其为高电平时,代表接收数据有效
    23. output reg [7:0] uart_rx_data //接收到的数据,在uart_rx_done为高电平时有效
    24. );
    25. //param define
    26. localparam integer BPS_CNT = CLK_FRE / BPS; //根据波特率计算传输每个bit需要多个系统时钟
    27. //reg define
    28. reg uart_rx_d1 ; //寄存1拍
    29. reg uart_rx_d2 ; //寄存2拍
    30. reg uart_rx_d3 ; //寄存3拍
    31. reg [31:0] clk_cnt ; //计数器,用于计数发送一个bit数据所需要的时钟数
    32. reg [3:0] bit_cnt ; //bit计数器,标志当前发送了多少个bit
    33. reg rx_en ; //接收标志信号,拉高代表接收过程正在进行
    34. reg [7:0] uart_rx_data_reg; //接收数据寄存
    35. //wire define
    36. wire neg_uart_rxd ; //接收数据线的下降沿
    37. assign neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2); //捕获数据线的下降沿,用来标志数据传输开始
    38. //将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
    39. always@(posedge sys_clk or negedge sys_rst_n)begin
    40. if(!sys_rst_n)begin
    41. uart_rx_d1 <= 1'b0;
    42. uart_rx_d2 <= 1'b0;
    43. uart_rx_d3 <= 1'b0;
    44. end
    45. else begin
    46. uart_rx_d1 <= uart_rxd;
    47. uart_rx_d2 <= uart_rx_d1;
    48. uart_rx_d3 <= uart_rx_d2;
    49. end
    50. end
    51. //捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
    52. always@(posedge sys_clk or negedge sys_rst_n)begin
    53. if(!sys_rst_n)
    54. rx_en <= 1'b0;
    55. else begin
    56. if(neg_uart_rxd )
    57. rx_en <= 1'b1;
    58. //接收完第9个数据(终止位)将传输开始标志位拉低,标志传输结束,判断高电平
    59. else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'b1) && (uart_rx_d3 == 1'b1) )
    60. rx_en <= 1'b0;
    61. else
    62. rx_en <= rx_en;
    63. end
    64. end
    65. //当数据传输到终止位时,拉高传输完成标志位,并将数据输出
    66. always@(posedge sys_clk or negedge sys_rst_n)begin
    67. if(!sys_rst_n)begin
    68. uart_rx_done <= 1'b0;
    69. uart_rx_data <= 8'd0;
    70. end
    71. //结束接收后,将接收到的数据输出
    72. else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'd1) && (uart_rx_d3 == 1'b1))begin
    73. uart_rx_done <= 1'b1; //仅仅拉高一个时钟周期
    74. uart_rx_data <= uart_rx_data_reg;
    75. end
    76. else begin
    77. uart_rx_done <= 1'b0; //仅仅拉高一个时钟周期
    78. uart_rx_data <= uart_rx_data;
    79. end
    80. end
    81. //时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
    82. always@(posedge sys_clk or negedge sys_rst_n)begin
    83. if(!sys_rst_n)begin
    84. bit_cnt <= 4'd0;
    85. clk_cnt <= 32'd0;
    86. end
    87. else if(rx_en)begin //在接收状态
    88. if(clk_cnt < BPS_CNT - 1'b1)begin //一个bit数据没有接收完
    89. clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1
    90. bit_cnt <= bit_cnt; //bit计数器不变
    91. end
    92. else begin //一个bit数据接收完了
    93. clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时
    94. bit_cnt <= bit_cnt + 1'b1; //bit计数器+1,表示接收完了一个bit的数据
    95. end
    96. end
    97. else begin //不在接收状态
    98. bit_cnt <= 4'd0; //清零
    99. clk_cnt <= 32'd0; //清零
    100. end
    101. end
    102. //在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
    103. always@(posedge sys_clk or negedge sys_rst_n)begin
    104. if(!sys_rst_n)
    105. uart_rx_data_reg <= 8'd0; //复位无接收数据
    106. else if(rx_en) //处于接收状态
    107. if(clk_cnt == BPS_CNT >> 1'b1) begin //传输过程正中(数据比较稳定)
    108. case(bit_cnt) //根据位数决定接收的内容是什么
    109. 4'd1:uart_rx_data_reg[0] <= uart_rx_d3; //LSB最低位
    110. 4'd2:uart_rx_data_reg[1] <= uart_rx_d3; //
    111. 4'd3:uart_rx_data_reg[2] <= uart_rx_d3; //
    112. 4'd4:uart_rx_data_reg[3] <= uart_rx_d3; //
    113. 4'd5:uart_rx_data_reg[4] <= uart_rx_d3; //
    114. 4'd6:uart_rx_data_reg[5] <= uart_rx_d3; //
    115. 4'd7:uart_rx_data_reg[6] <= uart_rx_d3; //
    116. 4'd8:uart_rx_data_reg[7] <= uart_rx_d3; //MSB最高位
    117. default:; //1和9分别是起始位和终止位,不需要接收
    118. endcase
    119. end
    120. else //数据不一定稳定就不接收
    121. uart_rx_data_reg <= uart_rx_data_reg;
    122. else
    123. uart_rx_data_reg <= 8'd0; //不处于接收状态
    124. end
    125. endmodule

    3、任意字节接收的实现方法

            串口接收驱动模块的对外端口如下:

     

            其中uart_rx_done信号是关键。当一个字节的数据被按约定的串口协议接收成功后,该信号就会拉高一个时钟周期,表示一次接收结束。

            可以每次在接收到一个字节后,将接收到的数据通过移位寄存起来,直到所有字节的数据都被成功接收。如下:

     

            所以,多字节数据的接收逻辑:

    • 1、用户通过UART数据多次发送单字节数据(比如5个)
    • 2、根据uart_rx_done信号判断是否成功接收到一个信号,并用计数器记录此时接收的是第几个字节的数据,同时将其移位寄存起来
    • 3、当所有单字节数据均被接收完毕后,拉高uart_bytes_vld,表示一次多字节接收结束,且此时的数据uart_bytes_data是有效数据

             完整代码如下:

    1. // *****************************************************************************************************************************
    2. // ** 作者 : 孤独的单刀
    3. // ** 邮箱 : zachary_wu93@163.com
    4. // ** 博客 : https://blog.csdn.net/wuzhikaidetb
    5. // ** 日期 : 2022/08/05
    6. // ** 功能 : 1、基于FPGA的串口多字节接收模块;
    7. // 2、可设置一次接收的字节数、波特率BPS、主时钟CLK_FRE;
    8. // 3、UART协议设置为起始位1bit,数据位8bit,停止位1bit,无奇偶校验(不可在端口更改,只能更改发送驱动源码);
    9. // 4、每接收到1次多字节后拉高指示信号一个周期,指示一次多字节接收结束;
    10. // 5、数据接收顺序,先接收低字节、再接收高字节。如:第1次接收到8’h34,第2次接收到8’h12,则最终接收到的数据为16'h12_34。
    11. // *****************************************************************************************************************************
    12. module uart_bytes_rx
    13. #(
    14. parameter integer BYTES = 4 , //一次接收字节数,单字节8bit
    15. parameter integer BPS = 9600 , //发送波特率
    16. parameter integer CLK_FRE = 50_000_000 //输入时钟频率
    17. )
    18. (
    19. //系统接口
    20. input sys_clk , //系统时钟
    21. input sys_rst_n , //系统复位,低电平有效
    22. //用户接口
    23. output [(BYTES * 8 - 1):0] uart_bytes_data , //接收到的多字节数据,在uart_bytes_vld为高电平时有效
    24. output uart_bytes_vld , //成功发送所有字节数据后拉高1个时钟周期,代表此时接收的数据有效
    25. //UART接收
    26. input uart_rxd //UART发送数据线rx
    27. );
    28. //reg define
    29. reg [(BYTES*8-1):0] uart_bytes_data_reg; //寄存接收到的多字节数据,先接收低字节,后接收高字节
    30. reg uart_bytes_vld_reg; //高电平表示此时接收到的数据有效
    31. reg [9:0] byte_cnt; //发送的字节个数计数(因为懒直接用10bit计数,最大可以表示1024BYTE,大概率不会溢出)
    32. //wire define
    33. wire [7:0] uart_sing_data; //接收的单个字节数据
    34. wire uart_sing_done; //单个字节数据接收完毕信号
    35. //对端口赋值
    36. assign uart_bytes_data = uart_bytes_data_reg;
    37. assign uart_bytes_vld = uart_bytes_vld_reg;
    38. //分别接收各个字节的数据
    39. always @(posedge sys_clk or negedge sys_rst_n)begin
    40. if(!sys_rst_n)
    41. uart_bytes_data_reg <= 0;
    42. else if(uart_sing_done)begin //接收到一个单字节则将数据右移8bit,实现最先接收的数据在低字节
    43. if(BYTES == 1) //单字节就直接接收
    44. uart_bytes_data_reg <= uart_sing_data;
    45. else //多字节就移位接收
    46. uart_bytes_data_reg <= {uart_sing_data,uart_bytes_data_reg[(BYTES*8-1)-:(BYTES-1)*8]};
    47. end
    48. else
    49. uart_bytes_data_reg <= uart_bytes_data_reg;
    50. end
    51. //对接收的字节个数进行计数
    52. always @(posedge sys_clk or negedge sys_rst_n)begin
    53. if(!sys_rst_n)
    54. byte_cnt <= 0;
    55. else if(uart_sing_done && byte_cnt == BYTES - 1) //计数到了最大值则清零
    56. byte_cnt <= 0;
    57. else if(uart_sing_done) //发送完一个单字节则计数器+1
    58. byte_cnt <= byte_cnt + 1'b1;
    59. else
    60. byte_cnt <= byte_cnt;
    61. end
    62. //所有数据接收完毕,拉高接收多字节数据有效信号
    63. always @(posedge sys_clk or negedge sys_rst_n)begin
    64. if(!sys_rst_n)
    65. uart_bytes_vld_reg <= 1'b0;
    66. else if(uart_sing_done && byte_cnt == BYTES - 1) //所有单字节数据接收完毕
    67. uart_bytes_vld_reg <= 1'b1;
    68. else
    69. uart_bytes_vld_reg <= 1'b0;
    70. end
    71. //例化串口接收驱动模块
    72. uart_rx #(
    73. .BPS (BPS ),
    74. .CLK_FRE (CLK_FRE )
    75. )
    76. uart_rx_inst
    77. (
    78. .sys_clk (sys_clk ),
    79. .sys_rst_n (sys_rst_n ),
    80. .uart_rx_done (uart_sing_done ),
    81. .uart_rx_data (uart_sing_data ),
    82. .uart_rxd (uart_rxd )
    83. );
    84. endmodule

    4、仿真

            使用该模块接收数据3次,观测接收结果是否正常。分别使用单字节(8位)、双字节(16位)、5字节(40位)进行测试。


    4.1、单字节仿真

            模拟上位机按UART协议三次发送随机的单字节数据过来,观察能否正确接收。TB如下:

    1. `timescale 1ns/1ns //定义时间刻度
    2. module tb_uart_bytes_rx();
    3. localparam integer BYTES = 1 ; //一次接收的字节个数
    4. localparam integer BPS = 230400 ; //波特率
    5. localparam integer CLK_FRE = 50_000_000 ; //系统频率50M
    6. localparam integer CNT = 1000_000_000 / BPS ; //计算出传输每个bit所需要的时间,单位:ns
    7. reg sys_clk ; //系统时钟
    8. reg sys_rst_n ; //系统复位,低电平有效
    9. reg uart_rxd ; //UART接收数据线
    10. wire [(BYTES * 8 - 1):0] uart_bytes_data ; //接收到的多字节数据,在uart_bytes_vld为高电平时有效
    11. wire uart_bytes_vld ; //当其为高电平时,代表此时接收到的多字节数据有效
    12. initial begin
    13. sys_clk <=1'b0;
    14. sys_rst_n <=1'b0;
    15. uart_rxd <=1'b1;
    16. #20 //系统开始工作
    17. sys_rst_n <=1'b1;
    18. #3000
    19. repeat(3) begin //重复生成8位随机数
    20. rx_byte({$random} % 256); //
    21. end
    22. #60 $finish();
    23. end
    24. always #10 sys_clk=~sys_clk; //设置主时钟,20ns,50M
    25. //定义任务,每次发送的数据10 位(起始位1+数据位8+停止位1)
    26. task rx_byte(
    27. input [7:0] data
    28. );
    29. integer i; //定义一个常量
    30. //用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
    31. for(i=0; i<10; i=i+1) begin
    32. case(i)
    33. 0: uart_rxd <= 1'b0; //起始位
    34. 1: uart_rxd <= data[0]; //LSB
    35. 2: uart_rxd <= data[1];
    36. 3: uart_rxd <= data[2];
    37. 4: uart_rxd <= data[3];
    38. 5: uart_rxd <= data[4];
    39. 6: uart_rxd <= data[5];
    40. 7: uart_rxd <= data[6];
    41. 8: uart_rxd <= data[7]; //MSB
    42. 9: uart_rxd <= 1'b1; //停止位
    43. endcase
    44. #(CNT+10); //每发送1 位数据延时(加10是为了减小误差)
    45. end
    46. endtask //任务结束
    47. //例化多字节接收模块
    48. uart_bytes_rx #(
    49. .BYTES (BYTES ),
    50. .BPS (BPS ),
    51. .CLK_FRE (CLK_FRE )
    52. )
    53. uart_bytes_rx_inst(
    54. .sys_clk (sys_clk ),
    55. .sys_rst_n (sys_rst_n ),
    56. .uart_bytes_data (uart_bytes_data ),
    57. .uart_bytes_vld (uart_bytes_vld ),
    58. .uart_rxd (uart_rxd )
    59. );
    60. endmodule

           

            仿真结果如下:

      

     

    • 模拟上位机调用了3次,分别发送数据8'h24,8'h81,8'h09
    • 串口接收模块接收到了同样的3个数据8'h24,8'h81,8'h09

    4.2、双字节仿真

            TB基本不用修改,把BYTES这个参数改成2,并把repeat的次数改成6就行。仿真结果如下:

      

    • 上位机分别发送了6个数据8'h24,8'h818'h09,8'h638'h0d,8'h8d
    • 接收到了3个数据16'h812416'h630916'h8d0d
    • 根据先接收低字节后高字节的原则,将6个单字节组合成了3个双字节接收

    4.3、5字节仿真

            TB基本不用修改,把BYTES这个参数改成5,并把repeat的次数改成10就行。仿真结果如下:

      

            您照着上面的逻辑看看就行,我就不啰嗦了。


    5、实测

            下板实测,可以通过上位机发送数据到FPGA,同时使用在线逻辑仪signaltap II来观察接收的数据是否与发送一致。


    5.1、单字节实测

            上位机发送数据8'h55:  

            FPGA同样接收到数据8'h55:

            测试结果与预期一致。 


    5.2、双字节实测

            上位机发送数据8'h55、8'haa:

           

            FPGA接收到数据16'haa55(先接收低字节,后接收高字节):

            测试结果与预期一致。 


    5.3、5字节实测

            上位机发送数据8'h9a、8'h78、8'h56、8'h34、8'h12:

            

            FPGA接收到数据40'h123456789a(先接收低字节,后接收高字节): 

            测试结果与预期一致。

            源码工程点击这里下载(提取码:oc60)


    • 📣博客主页:wuzhikai.blog.csdn.net
    • 📣本文由 孤独的单刀 原创,首发于CSDN平台🐵
    • 📣您有任何问题,都可以在评论区和我交流📞!
    • 📣创作不易,您的支持是我持续更新的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!

     

  • 相关阅读:
    linux下nginx安装与配置说明
    Latex语法学习08:打通latex、mathml和word公式转换
    『MySQL快速上手』-⑥-表的约束
    Mysql优化-经验分享
    【毕业设计】基于javaEE+SSH+SqlServer的企业车辆管理系统设计与实现(毕业论文+程序源码)——车辆管理系统
    Spring教程
    使用ngrok内网穿透后,调用相关接口报ERR_NGROK_6024 异常
    Python 能力提升之这 9 个 Python 特性被严重低估了
    一文搞懂UART通信协议
    Mysql的B+树高度计算
  • 原文地址:https://blog.csdn.net/wuzhikaidetb/article/details/126229191