当前位置:   article > 正文

UART协议——FPGA代码篇

UART协议——FPGA代码篇

一.串口(UART)协议简介

        UART 串口通信有几个重要的参数,分别是波特率、起始位、数据位、停止位和奇偶检验位,对于两个使用UART 串口通信的端口,这些参数必须匹配,否则通

图片

  • 起始位:表示数据传输的开始,电平逻辑为“0” 。

  • 数据位:可能值有5、6、7、8、9,表示传输这几个bit 位数据。一般取值为8,因为一个ASCII 字符值为8 位。

  • 奇偶校验位:用于接收方对接收到的数据进行校验,校验“1” 的位数为偶数(偶校验) 或奇数(奇校验),以此来校验数据传送的正确性,使用时不需要此位也可以。

  • 停止位:表示一帧数据的结束。电平逻辑为“1”。

  • 波特率:串口通信时的速率,它用单位时间内传输的二进制代码的有效位(bit) 数来表示,其单位为每秒比特数bit/s(bps)。常见的波特率值有4800、9600、14400、38400、115200 等,数值越大数据传输的越快,波特率为115200 表示每秒钟传输115200 位数据。

 

二.串口发送端设计 

  (1)流程设计 

         当使能信号有效后拉高发送标志信号,标志模块进入发送过程;当发送完10个bit后,拉低发送标志信号,标志发送过程结束。使能信号有效时将要发送的数据寄存。

  (2)verilog代码

  1. // ** 功能 : 1、基于FPGA的串口发送驱动模块;
  2. // 2、可设置波特率BPS、主时钟CLK_FRE;
  3. // 3、起始位1bit,数据位8bit,停止位1bit,无奇偶校验;
  4. // 4、每发送1个字节后拉高uart_tx_done一个周期,可用于后续发送多字节模块。
  5. // *******************************************************************************************************
  6. module uart_tx
  7. #(
  8. parameter integer BPS = 9_600 , //发送波特率
  9. parameter integer CLK_FRE = 50_000_000 //主时钟频率
  10. )
  11. (
  12. //系统接口
  13. input sys_clk , //系统时钟
  14. input sys_rst_n , //系统复位,低电平有效
  15. //用户接口
  16. input [7:0] uart_tx_data , //需要通过UART发送的数据,在uart_tx_en为高电平时有效
  17. input uart_tx_en , //发送有效,当其为高电平时,代表此时需要发送的数据有效
  18. //UART发送
  19. output reg uart_tx_done , //成功发送1BYTE数据后拉高一个周期
  20. output reg uart_txd //UART发送数据线tx
  21. );
  22. //当发送使能信号到达时,寄存待发送的数据以免后续变化、丢失
  23. always @(posedge sys_clk or negedge sys_rst_n)begin
  24. if(!sys_rst_n)
  25. uart_tx_data_reg <=8'd0;
  26. else if(uart_tx_en) //要发送有效的数据
  27. uart_tx_data_reg <= uart_tx_data; //寄存需要发送的数据
  28. else
  29. uart_tx_data_reg <= uart_tx_data_reg;
  30. end
  31. //当发送使能信号到达时,进入发送过程
  32. always @(posedge sys_clk or negedge sys_rst_n)begin
  33. if(!sys_rst_n)
  34. tx_state <=1'b0;
  35. else if(uart_tx_en)
  36. tx_state <= 1'b1; //发送信号有效则进入发送过程
  37. //发送完了最后一个数据则退出发送过程
  38. else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1))
  39. tx_state <= 1'b0;
  40. else
  41. tx_state <= tx_state;
  42. end
  43. //发送数据完毕后拉高发送完毕信号一个周期,指示一个字节发送完毕
  44. always @(posedge sys_clk or negedge sys_rst_n)begin
  45. if(!sys_rst_n)
  46. uart_tx_done <=1'b0;
  47. //发送数据完毕后拉高发送完毕信号一个周期
  48. else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1))
  49. uart_tx_done <=1'b1;
  50. else
  51. uart_tx_done <=1'b0;
  52. end
  53. //进入发送过程后,启动时钟计数器与发送个数bit计数器
  54. always @(posedge sys_clk or negedge sys_rst_n)begin
  55. if(!sys_rst_n)begin
  56. clk_cnt <= 32'd0;
  57. bit_cnt <= 4'd0;
  58. end
  59. else if(tx_state) begin //在发送状态
  60. if(clk_cnt < BPS_CNT - 1'd1)begin //一个bit数据没有发送完
  61. clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1
  62. bit_cnt <= bit_cnt; //bit计数器不变
  63. end
  64. else begin //一个bit数据发送完了
  65. clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时
  66. bit_cnt <= bit_cnt+1'b1; //bit计数器+1,表示发送完了一个bit的数据
  67. end
  68. end
  69. else begin //不在发送状态
  70. clk_cnt <= 32'd0; //清零
  71. bit_cnt <= 4'd0; //清零
  72. end
  73. end
  74. endmodule

   (3)前仿真代码

  1. `timescale 1ns/1ns //定义时间刻度
  2. module tb_uart_tx();
  3. reg sys_clk ;
  4. reg sys_rst_n ;
  5. reg [7:0] uart_tx_data ;
  6. reg uart_tx_en ;
  7. wire uart_txd ;
  8. parameter integer BPS = 'd230400 ; //波特率
  9. parameter integer CLK_FRE = 'd50_000_000 ; //系统频率50M
  10. localparam integer BIT_TIME = 'd1000_000_000 / BPS ; //计算出传输每个bit所需要的时间
  11. initial begin
  12. sys_clk <=1'b0;
  13. sys_rst_n <=1'b0;
  14. uart_tx_en <=1'b0;
  15. uart_tx_data <=8'd0;
  16. #80 //系统开始工作
  17. sys_rst_n <=1'b1;
  18. #200
  19. @(posedge sys_clk);
  20. uart_tx_en <=1'b1;
  21. uart_tx_data <= ({$random} % 256); //发送8位随机数据
  22. #20
  23. uart_tx_en <=1'b0;
  24. #(BIT_TIME * 10) //发送1个BYTE需要10个bit
  25. #200 $finish; //结束仿真
  26. end
  27. always #10 sys_clk=~sys_clk; //定义主时钟,周期20ns,频率50M
  28. //例化发送驱动模块
  29. uart_tx #(
  30. .BPS (BPS ),
  31. .CLK_FRE (CLK_FRE )
  32. )
  33. uart_tx_inst(
  34. .sys_clk (sys_clk ),
  35. .sys_rst_n (sys_rst_n ),
  36. .uart_tx_data (uart_tx_data ),
  37. .uart_tx_en (uart_tx_en ),
  38. .uart_tx_done (uart_tx_done ),
  39. .uart_txd (uart_txd )
  40. );
  41. endmodule

   (3)结果

         

三.接收端设计

  (1)流程设计 

        串口的传输是以起始位开始的,而起始位是将数据线拉低 ,所以我们需要捕捉数据线的下降沿,将接收数据线打拍3次,捕捉其下降沿。当捕捉到接收数据线的下降沿,拉高接收标志信号,标志模块进入接收过程;当接收完10个bit后,拉低接收标志信号,标志接收过程结束。

  (2)verilog代码

  1. module uart_rx
  2. #(
  3. parameter integer BPS = 9_600 , //发送波特率
  4. parameter integer CLK_FRE = 50_000_000 //输入时钟频率
  5. )
  6. (
  7. //系统接口
  8. input sys_clk , //50M系统时钟
  9. input sys_rst_n , //系统复位
  10. //UART接收线
  11. input uart_rxd , //接收数据线
  12. //用户接口
  13. output reg uart_rx_done , //数据接收完成标志,当其为高电平时,代表接收数据有效
  14. output reg [7:0] uart_rx_data //接收到的数据,在uart_rx_done为高电平时有效
  15. );
  16. assign neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2); //捕获数据线的下降沿,用来标志数据传输开始
  17. //将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
  18. always@(posedge sys_clk or negedge sys_rst_n)begin
  19. if(!sys_rst_n)begin
  20. uart_rx_d1 <= 1'b0;
  21. uart_rx_d2 <= 1'b0;
  22. uart_rx_d3 <= 1'b0;
  23. end
  24. else begin
  25. uart_rx_d1 <= uart_rxd;
  26. uart_rx_d2 <= uart_rx_d1;
  27. uart_rx_d3 <= uart_rx_d2;
  28. end
  29. end
  30. //捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
  31. always@(posedge sys_clk or negedge sys_rst_n)begin
  32. if(!sys_rst_n)
  33. rx_en <= 1'b0;
  34. else begin
  35. if(neg_uart_rxd )
  36. rx_en <= 1'b1;
  37. //接收完第9个数据(终止位)将传输开始标志位拉低,标志传输结束,判断高电平
  38. else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'b1) && (uart_rx_d3 == 1'b1) )
  39. rx_en <= 1'b0;
  40. else
  41. rx_en <= rx_en;
  42. end
  43. end
  44. //当数据传输到终止位时,拉高传输完成标志位,并将数据输出
  45. always@(posedge sys_clk or negedge sys_rst_n)begin
  46. if(!sys_rst_n)begin
  47. uart_rx_done <= 1'b0;
  48. uart_rx_data <= 8'd0;
  49. end
  50. //结束接收后,将接收到的数据输出
  51. else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'd1) && (uart_rx_d3 == 1'b1))begin
  52. uart_rx_done <= 1'b1; //仅仅拉高一个时钟周期
  53. uart_rx_data <= uart_rx_data_reg;
  54. end
  55. else begin
  56. uart_rx_done <= 1'b0; //仅仅拉高一个时钟周期
  57. uart_rx_data <= uart_rx_data;
  58. end
  59. end
  60. //时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
  61. always@(posedge sys_clk or negedge sys_rst_n)begin
  62. if(!sys_rst_n)begin
  63. bit_cnt <= 4'd0;
  64. clk_cnt <= 32'd0;
  65. end
  66. else if(rx_en)begin //在接收状态
  67. if(clk_cnt < BPS_CNT - 1'b1)begin //一个bit数据没有接收完
  68. clk_cnt <= clk_cnt + 1'b1; //时钟计数器+1
  69. bit_cnt <= bit_cnt; //bit计数器不变
  70. end
  71. else begin //一个bit数据接收完了
  72. clk_cnt <= 32'd0; //清空时钟计数器,重新开始计时
  73. bit_cnt <= bit_cnt + 1'b1; //bit计数器+1,表示接收完了一个bit的数据
  74. end
  75. end
  76. else begin //不在接收状态
  77. bit_cnt <= 4'd0; //清零
  78. clk_cnt <= 32'd0; //清零
  79. end
  80. end
  81. endmodule

   (3)前仿真代码

  1. // ** 功能 : 1、对基于FPGA的串口接收驱动模块的测试testbench
  2. // 2、通过构建一个task来模拟上位机时序发送数据给串口接收驱动,观察该模块能否成功接收数据。
  3. // 3、依次发送4个随机的8bit数据
  4. // *******************************************************************************************************
  5. `timescale 1ns/1ns //定义时间刻度
  6. //模块、接口定义
  7. module tb_uart_rx();
  8. reg sys_clk ;
  9. reg sys_rst_n ;
  10. reg uart_rxd ;
  11. wire uart_rx_done ;
  12. wire [7:0] uart_rx_data ;
  13. localparam integer BPS = 'd230400 ; //波特率
  14. localparam integer CLK_FRE = 'd50_000_000 ; //系统频率50M
  15. localparam integer CNT = 1000_000_000 / BPS ; //计算出传输每个bit所需要的时间,单位:ns
  16. //初始时刻定义
  17. initial begin
  18. $timeformat(-9, 0, " ns", 10); //定义时间显示格式
  19. sys_clk =1'b0;
  20. sys_rst_n <=1'b0;
  21. uart_rxd <=1'b1;
  22. #20 //系统开始工作
  23. sys_rst_n <=1'b1;
  24. #3000
  25. rx_byte({$random} % 256); //生成8位随机数1
  26. rx_byte({$random} % 256); //生成8位随机数2
  27. rx_byte({$random} % 256); //生成8位随机数3
  28. rx_byte({$random} % 256); //生成8位随机数4
  29. #60 $finish();
  30. end
  31. //每当成功接收一个BYTE的数据,就在测试端窗口打印出来
  32. always @(posedge sys_clk)begin
  33. if(uart_rx_done)begin
  34. $display("@time%t", $time);
  35. $display("rx : 0x%h",uart_rx_data);
  36. end
  37. end
  38. //定义任务,每次发送的数据10 位(起始位1+数据位8+停止位1)
  39. task rx_byte(
  40. input [7:0] data
  41. );
  42. integer i; //定义一个常量
  43. //用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
  44. for(i=0; i<10; i=i+1) begin
  45. case(i)
  46. 0: uart_rxd <= 1'b0; //起始位
  47. 1: uart_rxd <= data[0]; //LSB
  48. 2: uart_rxd <= data[1];
  49. 3: uart_rxd <= data[2];
  50. 4: uart_rxd <= data[3];
  51. 5: uart_rxd <= data[4];
  52. 6: uart_rxd <= data[5];
  53. 7: uart_rxd <= data[6];
  54. 8: uart_rxd <= data[7]; //MSB
  55. 9: uart_rxd <= 1'b1; //停止位
  56. endcase
  57. #CNT; //每发送 1 位数据延时
  58. end
  59. endtask //任务结束
  60. //设置主时钟
  61. always #10 sys_clk <= ~sys_clk; //时钟20ns,50M
  62. //例化被测试的串口接收驱动
  63. uart_rx
  64. #(
  65. .BPS (BPS ),
  66. .CLK_FRE (CLK_FRE )
  67. )
  68. uart_rx_inst(
  69. .sys_clk (sys_clk ),
  70. .sys_rst_n (sys_rst_n ),
  71. .uart_rxd (uart_rxd ),
  72. .uart_rx_done (uart_rx_done ),
  73. .uart_rx_data (uart_rx_data )
  74. );
  75. endmodule

  (3)结果

 

 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号