0


FPGA入门 —— FPGA UART 串口通信

FPGA入门 —— FPGA UART 串口通信

串口简介

UART 通用异步收发传输器( Universal Asynchronous Receiver/Transmitter) ,通常称作 UART。 UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 它包括了ch340、 RS232、 RS499、 RS423、 RS422 和 RS485 等接口标准规范和总线标准规范

串口作为常用的三大低速总线(UART、 SPI、 IIC)之一,在设计众多通信接口和调试时占有重要地位。但 UART 和 SPI、 IIC 不同的是,它是异步通信接口,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位的帮助下实现信息同步的。而 SPI、 IIC 是同步通信接口,同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的

UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),对于 PC 来说它的 tx 要和对于 FPGA 来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到,所以不要弄混,记住 rx 和 tx 都是相对自身主体来讲的。UART 可以实现全双工,即可以同时进行发送数据和接收数据

串口回环模块如下图所示,使用电脑上位机串口模块完成对 FPGA 串口模块的环回实验

串口时序

下面我们来看一下 RS232 协议:

  1. RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线, tx 是发送数据的线
  2. rx 位宽为 1bit, PC 机通过串口调试助手往 FPGA 发 8bit 数据时, FPGA 通过串口线rx 一位一位地接收,从最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特数据,也就是我们常说的串转并
  3. tx 位宽为 1bit, FPGA 通过串口往 PC 机发 8bit 数据时, FPGA 把 8bit 数据通过 tx线一位一位的传给 PC 机,从最低位到最高位依次发送,最后上位机通过串口助手按照RS232 协议把这一位一位的数据位拼接成 8bit 数据,并行数据转换成串行数据进行发送
  4. 串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下, rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输

rs232时序图入下图所示:

  1. 波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数), 每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、 115200 等
  2. 比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 115200 的波特率,其串口的比特率为: 115200Bps *1bit= 115200bps
  3. 由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用 50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns /115200bit)ns / 20ns ≈434 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟频率下计数 434 次

串口模块设计

我们先绘制一下串口回环的整个模块图:

由整个回环模块图,我们可以看出在整个回环实验中,我们需要再设计两个模块,一个接收模块和一个发送模块。

接收模块

先看一下接收模块的波形图:

输入信号为系统时钟、复位信号以及接收信号;为了消除跨时钟域所带来的的亚稳态现象,需要对 rx 信号进行打 3 拍操作,跨时钟域处理推荐是 3 拍,当然有些人觉得 2 拍足矣,只要验证没问题,这个你随意。

进行完打拍操作后,需要取第三拍的下降沿操作,也就是 ~rx_reg2 & rx_reg3 ,至于为何这样,可以参考边沿检测部分。

得到 start_flag 信号之后,就可以进行一个 rx 的接收了,本次接收模块使用的波特率为 115200,系统时钟为 50M,所以波特率为 50_000_000/115200=434,当然这里波特率也可以根据需要进行修改

为了每位数据的接收的稳定,所以可以数据的标志信号可以在波特率信号的中间位置进行拉高。然后将每 bit 的数据都拼接到我们接收数据的最高位,以此类推,接收到的第8位数据,就拼接在了 rx_data 的最高位,最先接收的数据就存储在了 rx_data 的最低位

接收完8位数据之后,我们的接收标志信号拉高,表示已经完成一次8位数据的接收。在接收完成标志位拉高后,将我们寄存的串转并的数据赋值 po_data,同样 po_flag 拉高一个周期

根据波形图与文字分析,可以编写代码如下:

UART_recv.v

  1. module UART_recv
  2. #(
  3. parameter CLK = 26'd50000000 , // 时钟频率
  4. parameter BAUD = 17'd115200 // 波特率
  5. )
  6. (
  7. input wire clk ,
  8. input wire rstn ,
  9. input wire UART_rx ,
  10. output reg flag_out , // 数据接收完成标志位,既发送开始标志位
  11. output reg [7 : 0] data_out // 接收的数据
  12. );
  13. localparam Baud_Clk = CLK/BAUD ; // 传输每个 Baud 需要的时钟数
  14. reg rx_en ; // 接收使能
  15. reg start_flag ; // 开始接收标志
  16. reg flag_rx ; // 接收标志位,半个时钟周期为 1 ,用于判断数据已经全部接收完成
  17. reg flag_bit ; // 比特标志位,采用下降沿发送
  18. reg rx_reg1 ; // 接收寄存器1,同步打拍(打一拍延时一个时钟周期)
  19. reg rx_reg2 ; // 接收寄存器2
  20. reg rx_reg3 ; // 接收寄存器3,取第三拍下降沿进行边缘检测
  21. reg [8 : 0] cnt_baud ; // 波特率计数器
  22. reg [7 : 0] data_rx ; // 接收数据寄存器
  23. reg [3 : 0] cnt_bit ; // 比特计数器
  24. // 打三拍
  25. always @(posedge clk or negedge rstn) begin
  26. if(!rstn) begin
  27. rx_reg1 <= 1'b1;
  28. rx_reg2 <= 1'b1;
  29. rx_reg3 <= 1'b1;
  30. end
  31. else begin
  32. rx_reg1 <= UART_rx;
  33. rx_reg2 <= rx_reg1;
  34. rx_reg3 <= rx_reg2;
  35. end
  36. end
  37. // 检测第三拍下降沿,用作数据接收信号
  38. always @(posedge clk or negedge rstn) begin
  39. if(!rstn) begin
  40. start_flag <= 1'b0;
  41. end
  42. // 判断第三拍下降沿
  43. else if(rx_reg3 && ~rx_reg2) begin
  44. start_flag <= 1'b1;
  45. end
  46. else begin
  47. start_flag <= 1'b0;
  48. end
  49. end
  50. // 接收使能
  51. always @(posedge clk or negedge rstn) begin
  52. if(!rstn) begin
  53. rx_en <= 1'b0;
  54. end
  55. // 下降沿接收
  56. else if(start_flag == 1'b1) begin
  57. rx_en <= 1'b1;
  58. end
  59. // 接收完成,输入只需要判断到数据位最后一位,输出则需要判断完整输出
  60. else if(cnt_bit == 4'd8 && flag_bit == 1'b1) begin
  61. rx_en <= 1'b0;
  62. end
  63. end
  64. // 波特计数器
  65. always @(posedge clk or negedge rstn) begin
  66. if(!rstn) begin
  67. cnt_baud <= 9'd0;
  68. end
  69. // 传输完成所有波特或者使能失效,表示接收结束
  70. else if(cnt_baud == Baud_Clk - 1'b1 || rx_en == 1'b0) begin
  71. cnt_baud <= 9'd0;
  72. end
  73. // 只有输入使能才能计数
  74. else if(rx_en == 1'b1) begin
  75. cnt_baud <= cnt_baud + 9'd1;
  76. end
  77. end
  78. // 比特标志位
  79. always @(posedge clk or negedge rstn) begin
  80. if(!rstn) begin
  81. flag_bit <= 1'b0;
  82. end
  83. // 半个周期反转一次,输入一个bit需要两个时钟周期,输出需要三个
  84. else if(cnt_baud == Baud_Clk/2 - 1'b1) begin
  85. flag_bit <= 1'b1;
  86. end
  87. else begin
  88. flag_bit <= 1'b0;
  89. end
  90. end
  91. // 比特计数器
  92. always @(posedge clk or negedge rstn) begin
  93. if(!rstn) begin
  94. cnt_bit <= 4'd0;
  95. end
  96. // 输入判断完成
  97. else if(cnt_bit == 4'd8 && flag_bit == 1'b1) begin
  98. cnt_bit <= 4'd0;
  99. end
  100. // 前面判断了输入使能失效,无法进行波特计数
  101. else if(flag_bit == 1'b1) begin
  102. cnt_bit <= cnt_bit + 4'd1;
  103. end
  104. end
  105. // 接收数据
  106. always @(posedge clk or negedge rstn) begin
  107. if(!rstn) begin
  108. data_rx <= 8'd0;
  109. end
  110. // 只要开始接收,就开始存储数据,1-8为数据位,解析输入数据,从最低位向最高位输入
  111. else if(cnt_bit >= 4'd1 && cnt_bit <= 4'd8 && flag_bit == 1'b1) begin
  112. data_rx <= {rx_reg3,data_rx[7:1]};
  113. end
  114. end
  115. // 接收标志位
  116. always @(posedge clk or negedge rstn) begin
  117. if(!rstn) begin
  118. flag_rx <= 1'b0;
  119. end
  120. // 数据接收完成,输出半个时钟周期的 1 用于数据转存,将收到的数据再次发送出去
  121. else if(cnt_bit == 4'd8 && flag_bit == 1'b1) begin
  122. flag_rx <= 1'b1;
  123. end
  124. else begin
  125. flag_rx <= 1'b0;
  126. end
  127. end
  128. // 接收的串转并数据
  129. always @(posedge clk or negedge rstn) begin
  130. if(!rstn) begin
  131. data_out <= 8'd0;
  132. end
  133. // 判断数据已经全部接收完成
  134. else if(flag_rx == 1'b1) begin
  135. data_out <= data_rx;
  136. end
  137. end
  138. always @(posedge clk or negedge rstn) begin
  139. if(!rstn) begin
  140. flag_out <= 1'b0;
  141. end
  142. else begin
  143. flag_out <= flag_rx;
  144. end
  145. end
  146. endmodule //UART_recv

编写接收模块仿真代码:

tb_uart_rx.v:

  1. `timescale 1ns / 1ns
  2. module tb_uart_rx();
  3. reg sys_clk ;
  4. reg sys_rst_n ;
  5. reg rx ;
  6. wire [7:0] po_data ;
  7. wire po_flag ;
  8. initial begin
  9. sys_clk = 1'b1;
  10. sys_rst_n <= 1'b0;
  11. #201
  12. sys_rst_n <= 1'b1;
  13. end
  14. always #10 sys_clk = ~sys_clk;
  15. //模拟发送8次数据,分别为0~7
  16. initial begin
  17. #200
  18. rx_bit(8'd8); //任务的调用,任务名+括号中要传递进任务的参数
  19. rx_bit(8'd1);
  20. rx_bit(8'd2);
  21. rx_bit(8'd3);
  22. rx_bit(8'd4);
  23. rx_bit(8'd5);
  24. rx_bit(8'd6);
  25. rx_bit(8'd7);
  26. end
  27. //定义一个名为rx_bit的任务
  28. //任务以task开头,后面紧跟的是任务名,调用时使用
  29. task rx_bit(
  30. //传递到任务中的参数,调用任务的适合从外部传进来一个8位的值
  31. input [7:0] data
  32. );
  33. integer i; //定义一个常量
  34. for(i=0; i<10; i=i+1) begin
  35. case(i)
  36. 0: rx <= 1'b0;
  37. 1: rx <= data[0];
  38. 2: rx <= data[1];
  39. 3: rx <= data[2];
  40. 4: rx <= data[3];
  41. 5: rx <= data[4];
  42. 6: rx <= data[5];
  43. 7: rx <= data[6];
  44. 8: rx <= data[7];
  45. 9: rx <= 1'b1;
  46. endcase
  47. #(434*20); //每发送1位数据时434个时钟周期
  48. end
  49. endtask
  50. uart_rx
  51. #(
  52. .BAUD_MAX ('d115_200 ),
  53. .CLK_MAX ('d50_000_000 )
  54. )
  55. uart_rx_inst
  56. (
  57. .sys_clk (sys_clk ),
  58. .sys_rst_n (sys_rst_n),
  59. .rx (rx ),
  60. .po_data (po_data ),
  61. .po_flag (po_flag )
  62. );
  63. endmodule

打拍取沿及接收使能部分波形:

波特计数器以及比特标志位(434/2拉高)波形:

数据接收端以及数据接收完成标志位波形:

发送模块

同理按照时序图绘制发送模块的波形图:

因为设计一个串口回环,发送和接收模块的波特率是相同的,所以并没有队 pi_flag 信号进行打 3 拍处理。在接收信号拉高之后, tx_en 拉高,进入发送模式。

同样波特计数器为 434,bit_flag 每次拉高,bit_cnt 计一个数,第 0 位为起始位,拉低,然后进行数据的传送,当计数到第九位时,使 tx 信号拉高,表示停止位。

同样根据波形图可以编写出发送模块的代码:

UART_send.v:

  1. module UART_send
  2. #(
  3. parameter CLK = 26'd50000000 , // 时钟频率
  4. parameter BAUD = 17'd115200 // 波特率
  5. )
  6. (
  7. input wire clk ,
  8. input wire rstn ,
  9. input wire [7 : 0] data_in , // 需要发送的数据
  10. input wire flag_in , // 数据接收标志位,既发送标志位
  11. output reg UART_tx // 串口输出位
  12. );
  13. localparam Baud_Clk = CLK/BAUD ; // 传输每个 Baud 需要的时钟数
  14. reg tx_en ; // 发送使能
  15. reg flag_bit ; // 比特标志位,采用下降沿发送
  16. reg [8 : 0] cnt_baud ; // 波特率计数器
  17. reg [3 : 0] cnt_bit ; // 比特计数器
  18. // 发送使能
  19. always @(posedge clk or negedge rstn) begin
  20. if(!rstn) begin
  21. tx_en <= 1'b0;
  22. end
  23. // 已经发送了十位 bit 并且到达下一个下降沿,输入只需要判断到数据位最后一位,输出则需要判断完整输出
  24. else if(cnt_bit == 4'd9 && flag_bit == 1'b1) begin
  25. tx_en <= 1'b0;
  26. end
  27. else if(flag_in == 1'b1) begin
  28. tx_en <= 1'b1;
  29. end
  30. end
  31. // 波特计数器
  32. always @(posedge clk or negedge rstn) begin
  33. if(!rstn) begin
  34. cnt_baud <= 9'd0;
  35. end
  36. // 传输完成所有波特或者使能失效,表示发送结束
  37. else if(cnt_baud == Baud_Clk - 1'b1 || tx_en == 1'b0) begin
  38. cnt_baud <= 9'd0;
  39. end
  40. else begin
  41. cnt_baud <= cnt_baud + 9'd1;
  42. end
  43. end
  44. always @(posedge clk or negedge rstn) begin
  45. if(!rstn) begin
  46. flag_bit <= 1'b0;
  47. end
  48. // 只有刚开始发送的一瞬间会产生一个时钟周期上升沿和下降沿
  49. else if(cnt_baud == 9'd1) begin
  50. flag_bit <= 1'b1;
  51. end
  52. else begin
  53. flag_bit <= 1'b0;
  54. end
  55. end
  56. // 计数10分有效数据位
  57. always @(posedge clk or negedge rstn) begin
  58. if(!rstn) begin
  59. cnt_bit <= 4'd0;
  60. end
  61. // 已经发送了十位 bit 并且到达下一个下降沿
  62. else if(cnt_bit == 4'd9 && flag_bit == 1'b1) begin
  63. cnt_bit <= 4'd0;
  64. end
  65. // 使能有效,下降沿发送数据
  66. else if(flag_bit == 1'b1 && tx_en == 1'b1) begin
  67. cnt_bit <= cnt_bit + 4'd1;
  68. end
  69. else begin
  70. cnt_bit <= cnt_bit;
  71. end
  72. end
  73. // 满足 RS232 协议 起始位为 0,停止位为 1,并按位输出
  74. always @(posedge clk or negedge rstn) begin
  75. if(!rstn) begin
  76. UART_tx <= 1'd1;
  77. end
  78. // 下降沿发送数据
  79. else if(flag_bit == 1'b1) begin
  80. case (cnt_bit)
  81. 0: UART_tx <= 1'd0 ;
  82. 1: UART_tx <= data_in[0] ;
  83. 2: UART_tx <= data_in[1] ;
  84. 3: UART_tx <= data_in[2] ;
  85. 4: UART_tx <= data_in[3] ;
  86. 5: UART_tx <= data_in[4] ;
  87. 6: UART_tx <= data_in[5] ;
  88. 7: UART_tx <= data_in[6] ;
  89. 8: UART_tx <= data_in[7] ;
  90. 9: UART_tx <= 1'd1 ;
  91. default: UART_tx <= 1'd1 ;
  92. endcase
  93. end
  94. end
  95. endmodule //UART_send

同样进行发送代码的仿真编写:

tb_uart_tx.v:

  1. `timescale 1ns / 1ns
  2. module tb_uart_tx();
  3. reg sys_clk ;
  4. reg sys_rst_n ;
  5. reg [7:0] pi_data ;
  6. reg pi_flag ;
  7. wire tx ;
  8. initial begin
  9. sys_clk = 1'b0;
  10. sys_rst_n <= 1'b0;
  11. #201
  12. sys_rst_n <= 1'b1;
  13. end
  14. always #10 sys_clk = ~sys_clk;
  15. initial begin
  16. pi_flag <= 1'b0;
  17. pi_data <= 8'd0;
  18. #401
  19. pi_data <= 8'd0;
  20. pi_flag <= 1'd1;
  21. #20
  22. pi_flag <= 1'd0;
  23. #(434*10*20)
  24. pi_data <= 8'd15;
  25. pi_flag <= 1'd1;
  26. #20
  27. pi_flag <= 1'd0;
  28. #(434*10*20)
  29. pi_data <= 8'd2;
  30. pi_flag <= 1'd1;
  31. #20
  32. pi_flag <= 1'd0;
  33. #(434*10*20)
  34. pi_data <= 8'd3;
  35. pi_flag <= 1'd1;
  36. #20
  37. pi_flag <= 1'd0;
  38. #(434*10*20)
  39. pi_data <= 8'd4;
  40. pi_flag <= 1'd1;
  41. #20
  42. pi_flag <= 1'd0;
  43. #(434*10*20)
  44. pi_data <= 8'd5;
  45. pi_flag <= 1'd1;
  46. #20
  47. pi_flag <= 1'd0;
  48. #(434*10*20)
  49. pi_data <= 8'd6;
  50. pi_flag <= 1'd1;
  51. #20
  52. pi_flag <= 1'd0;
  53. #(434*10*20)
  54. pi_data <= 8'd7;
  55. pi_flag <= 1'd1;
  56. #20
  57. pi_flag <= 1'd0;
  58. end
  59. uart_tx
  60. #(
  61. .BAUD_MAX('d115_200 ) ,
  62. .CLK_MAX ('d50_000_000 )
  63. )uart_tx_inst
  64. (
  65. .sys_clk (sys_clk ),
  66. .sys_rst_n (sys_rst_n),
  67. .pi_data (pi_data ),
  68. .pi_flag (pi_flag ),
  69. .tx (tx )
  70. );
  71. endmodule

先看一下发送开始标志位波形:

延迟一个时钟周期,发送使能拉高:

波特计数器等于 1 是,比特标志位拉高,比特位数加 1:

如接收到的数据为 7,发送端从最低位 1110_0000 发送数据 7,注意这里的顺序是先发送最低位,最后一位为最高位

例化回环模块

最后将我们的两个模块例化到一起:

UART.v:

  1. module UART (
  2. input wire clk ,
  3. input wire rstn ,
  4. input wire UART_rx ,
  5. output wire UART_tx
  6. );
  7. localparam CLK_50MHz = 26'd50000000 ; // 时钟频率
  8. localparam BAUD = 17'd115200 ; // 波特率
  9. wire [7:0] data ;
  10. wire flag ;
  11. UART_send
  12. #(
  13. .CLK (CLK_50MHz ),// 时钟频率
  14. .BAUD (BAUD ) // 波特率
  15. )
  16. UART_send_init(
  17. .clk (clk ),
  18. .rstn (rstn ),
  19. .data_in (data ), // 需要发送的数据
  20. .flag_in (flag ), // 数据接收标志位,既发送标志位
  21. .UART_tx (UART_tx ) // 串口输出位
  22. );
  23. UART_recv
  24. #(
  25. .CLK (CLK_50MHz ), // 时钟频率
  26. .BAUD (BAUD ) // 波特率
  27. )
  28. UART_recv_init(
  29. .clk (clk ),
  30. .rstn (rstn ),
  31. .UART_rx (UART_rx ),
  32. .flag_out (flag ), // 数据接收完成标志位,既发送开始标志位,半个时钟周期为 1 ,用于判断数据已经全部接收完成
  33. .data_out (data ) // 接收的数据
  34. );
  35. endmodule //UART

烧录效果演示

串口发送多字节数据

这里我们采用发送多字节的方式进行发送,前面我们已经介绍了 FPGA 如何使用超声波并显示在数码管上,这里讲超声波距离数据通过串口发送到上位机,这里超声波距离保留三位小数,格式如下:xxx.xxxcm

这里我们通过数据处理后,转为 ASCII,然后我们每次发送一位,按照我们上面给定的格式,整个数据共有 9 位,有由于我们需要每分钟发送一次数据,所以我们需要每分钟发送 10 个数据

有些朋友可能会问,我们只有 9 位数据,为什么要发送 10 位?

这是因为我们由于要使用 python 程序接收数据,所以我们需要在数据最后发送换行表示一次数据发送完成

数据处理代码如下:

UART_driver.v:

  1. module UART_driver (
  2. input wire clk ,
  3. input wire rstn ,
  4. input wire [18:0] data_in ,
  5. input wire UART_rx ,
  6. output wire UART_tx
  7. );
  8. localparam CLK_50MHz = 26'd50000000 ; // 时钟频率
  9. localparam BAUD = 17'd115200 ; // 波特率
  10. reg [7:0] data ;
  11. wire flag ;
  12. wire tx_done ;
  13. wire flag_0 ; // 未启动超声波
  14. reg [25:0] cnt_clk ;
  15. reg [3:0] xcnt ;
  16. reg [71: 0] data_out ; // 最终发送的数据
  17. reg [3:0] cm_hund ;//100cm
  18. reg [3:0] cm_ten ;//10cm
  19. reg [3:0] cm_unit ;//1cm
  20. reg [3:0] point_1 ;//1mm
  21. reg [3:0] point_2 ;//0.1mm
  22. reg [3:0] point_3 ;//0.01mm
  23. localparam
  24. byte0 = "0",
  25. byte1 = "1",
  26. byte2 = "2",
  27. byte3 = "3",
  28. byte4 = "4",
  29. byte5 = "5",
  30. byte6 = "6",
  31. byte7 = "7",
  32. byte8 = "8",
  33. byte9 = "9",
  34. byte10 = "\n";
  35. always@(posedge clk or negedge rstn) begin
  36. if(!rstn)
  37. xcnt <= 0;
  38. else if(tx_done)
  39. xcnt <= xcnt + 1'd1;
  40. else if(xcnt == 10)
  41. xcnt <= 0;
  42. end
  43. always @(posedge clk or negedge rstn) begin
  44. if(!rstn) begin
  45. cnt_clk <= 0;
  46. end
  47. else if(flag) begin
  48. cnt_clk <= 0;
  49. end
  50. else begin
  51. cnt_clk = cnt_clk + 1;
  52. end
  53. end
  54. assign flag = cnt_clk == CLK_50MHz/11 - 1; // 一秒钟发送所有数据
  55. assign flag_0 = cm_hund == 0 && cm_ten == 0 && cm_unit == 0 && point_1 == 0 && point_2 == 0 && point_3 == 0;
  56. always @(posedge clk or negedge rstn)begin
  57. if(!rstn)begin
  58. cm_hund <= 'd0;
  59. cm_ten <= 'd0;
  60. cm_unit <= 'd0;
  61. point_1 <= 'd0;
  62. point_2 <= 'd0;
  63. point_3 <= 'd0;
  64. end
  65. else begin
  66. cm_hund <= data_in % 10;
  67. cm_ten <= data_in / 10 ** 1 % 10;
  68. cm_unit <= data_in / 10 ** 2 % 10;
  69. point_1 <= data_in / 10 ** 3 % 10;
  70. point_2 <= data_in / 10 ** 4 % 10;
  71. point_3 <= data_in / 10 ** 5 % 10;
  72. end
  73. end
  74. always @(*) begin
  75. case (xcnt)
  76. 0 : data = hex_data(point_3);
  77. 1 : data = hex_data(point_2);
  78. 2 : data = hex_data(point_1);
  79. 3 : data = "." ;
  80. 4 : data = hex_data(cm_unit);
  81. 5 : data = hex_data(cm_ten) ;
  82. 6 : data = hex_data(cm_hund);
  83. 7 : data = "c" ;
  84. 8 : data = "m" ;
  85. 9 : data = "\n" ;
  86. default: data = 6'h30;
  87. endcase
  88. end
  89. // 函数,4位输入,7位输出,判断要输出的数字
  90. function [7:0] hex_data; //函数不含时序逻辑相关
  91. input [03:00] data_i;//至少一个输入
  92. begin
  93. case(data_i)
  94. 4'd0:hex_data = 6'h30;
  95. 4'd1:hex_data = 6'h31;
  96. 4'd2:hex_data = 6'h32;
  97. 4'd3:hex_data = 6'h33;
  98. 4'd4:hex_data = 6'h34;
  99. 4'd5:hex_data = 6'h35;
  100. 4'd6:hex_data = 6'h36;
  101. 4'd7:hex_data = 6'h37;
  102. 4'd8:hex_data = 6'h38;
  103. 4'd9:hex_data = 6'h39;
  104. default:hex_data = 6'h30;
  105. endcase
  106. end
  107. endfunction
  108. UART_send
  109. #(
  110. .CLK (CLK_50MHz ),// 时钟频率
  111. .BAUD (BAUD ) // 波特率
  112. )
  113. UART_send_init(
  114. .clk (clk ),
  115. .rstn (rstn ),
  116. .data_in (data ), // 需要发送的数据
  117. .flag_in (flag ), // 数据接收标志位,既发送标志位
  118. .UART_tx (UART_tx ), // 串口输出位
  119. .tx_done (tx_done )
  120. );
  121. endmodule //UART

python 接收串口多字节数据

这里我们选择 python 语言作为上位机数据处理语言

开始的时候,我们运行数据处理程序,但是并不知道板子上已经发送到哪一位数据,所以我们需要循环过滤掉第一次接收到的数据:

  1. while True:
  2. if ser.in_waiting:
  3. data = ser.read(ser.in_waiting)
  4. if str(data,encoding="utf-8") == '\n':
  5. break

然后我们就可以循环接收串口数据:

  1. while True:
  2. if ser.in_waiting: # 如果串口接收到了数据
  3. data = ser.read(ser.in_waiting) # 读取所有可用的数据
  4. num_chars = len(data) # 获取收到的字符个数
  5. num_all = num_all + num_chars
  6. if num_all <= 7:
  7. char = char + str(data , encoding = "utf-8")
  8. elif num_all == 10:
  9. # distance = float(char)
  10. if char != "No Data":
  11. distance = float(char)
  12. # print(char[0:7])
  13. print("距离:",distance,"cm")
  14. char = ""
  15. num_all = 0
  16. # print(f"收到 {num_chars} 个字符:{data}")
  17. # print(data)
  18. ser.close() # 关闭串口

完整 python 代码如下:

  1. import serial
  2. ser = serial.Serial('COM22', 115200) # 假设您的串口是 COM1,波特率为 9600
  3. ser.flushInput() # 清空输入缓冲区
  4. num_all = 0
  5. char = ""
  6. while True:
  7. if ser.in_waiting:
  8. data = ser.read(ser.in_waiting)
  9. if str(data,encoding="utf-8") == '\n':
  10. break
  11. while True:
  12. if ser.in_waiting: # 如果串口接收到了数据
  13. data = ser.read(ser.in_waiting) # 读取所有可用的数据
  14. num_chars = len(data) # 获取收到的字符个数
  15. num_all = num_all + num_chars
  16. if num_all <= 7:
  17. char = char + str(data , encoding = "utf-8")
  18. elif num_all == 10:
  19. # distance = float(char)
  20. if char != "No Data":
  21. distance = float(char)
  22. # print(char[0:7])
  23. print("距离:",distance,"cm")
  24. char = ""
  25. num_all = 0
  26. # print(f"收到 {num_chars} 个字符:{data}")
  27. # print(data)
  28. ser.close() # 关闭串口

烧录效果演示

标签: fpga开发

本文转载自: https://blog.csdn.net/m0_59161987/article/details/131079669
版权归原作者 ppqppl 所有, 如有侵权,请联系我们删除。

“FPGA入门 —— FPGA UART 串口通信”的评论:

还没有评论