当前位置:   article > 正文

【FPGA学习】实例一、Cyclone IV串口通信(RS232)_cyclone iv fpga

cyclone iv fpga

目录

目录

前言

一、RS232协议

二、模块框图

 三、代码编写

1.串口接收

2.串口发送

3.顶层模块

四、仿真验证

总结


前言

刚开始学习FPGA开发,项目中用到了串口RS232协议进行通信,记录一下设计思路和设计过程,开发板是野火的征途pro开发板,软件采用Quartus II 13.0。(参考资料:《FPGA实战开发指南》)

一、RS232协议

RS232协议是UART的一种,只有两根数据线,分别是rx和tx,分别用来接收数据和发送数据,数据收发基于帧结构,每次按照8bit的大小来接收和发送数据。数据线空闲状态下为高电平,开始发送数据后将电平拉低一帧作为起始位,随后8帧的数据为数据内容,发送完毕后将数据线电平拉高一帧作为停止位,然后一直拉高回到空闲状态。

二、模块框图

 本次实例将编写串口接收模块以及串口发送模块,并用顶层模块实例化调用这两个模块,组成串口回环,来进行功能验证,接下来分模块介绍这几个模块的代码编写。

 三、代码编写

本次实例串口波特率采用常见的9600 bit/s,开发板系统时钟为50MHz,所以每一帧数据所需的周期为1/9600 s,因此所需开发板计数到1/9600/(1/50_000_000) ≈ 5208,采用计数和清零的方式确定每一帧的开始和结束。

1.串口接收

采用寄存器放置读取的异步串口数据后将数据打两拍,减小亚稳态的可能性。随后在起始位的下降沿标记一个标志位start_flag产生一个周期的高电平,通过该信号控制读取数据的使能信号打开,波特率计数器baud_cnt在使能有效时进行计数,0-5207周期循环,当计数器计数到最大值5207的一半时拉高数据采集标志位bit_flag一个时钟周期,在该标志信号下降沿进行bit_cnt计数,在计数器1-8之间并且采集标志位bit_flag拉高时,将串口数据串联到rx_data的最高位,形成一个移位寄存器,移位八次后生成一个周期的高电平,将其打一拍后与数据同步输出到下一级模块。

代码如下:

  1. module uart_rx
  2. #(
  3.     parameter       UART_BPS    =   'd9600      ,//串口波特率
  4.     parameter       CLK_FREQ    =   'd50_000_000//时钟频率
  5. )
  6. (
  7.     input   wire            sys_clk     ,
  8.     input   wire            sys_rst_n   ,
  9.     input   wire            rx          ,//输入数据
  10.     output  reg   [7:0]    out_data    ,//输出数据
  11.     output  reg            out_flag    //输出标志位
  12. );
  13. parameter   BAUD_CNT_MAX    =   CLK_FREQ / UART_BPS;
  14. //中间变量声明
  15. reg          rx_reg1         ;//打一拍把异步串口信号同步读取
  16. reg          rx_reg2         ;
  17. reg          rx_reg3         ;//打两拍减小亚稳态影响
  18. reg          start_flag      ;//开始标志位
  19. reg          work_en         ;//读取数据的使能信号
  20. reg  [15:0]  baud_cnt        ;//波特率计数器
  21. reg          bit_flag        ;//数据读取标志位
  22. reg  [3:0]   bit_cnt         ;//数据读取计数器
  23. reg  [7:0]   rx_data         ;//并行已读数据
  24. reg          rx_flag         ;//并行已读数据标志位
  25. //同步读取信号
  26. always@(posedge sys_clk or negedge sys_rst_n)
  27.     if(sys_rst_n == 1'b0)
  28.         rx_reg1 <= 1'b1;
  29.     else 
  30.         rx_reg1 <= rx;
  31.         
  32. //打两拍减小亚稳态影响
  33. always@(posedge sys_clk or negedge sys_rst_n)
  34.     if(sys_rst_n == 1'b0)
  35.         rx_reg2 <= 1'b1;
  36.     else
  37.         rx_reg2 <= rx_reg1;
  38. always@(posedge sys_clk or negedge sys_rst_n)
  39.     if(sys_rst_n == 1'b0)
  40.         rx_reg3 <= 1'b1;
  41.     else 
  42.         rx_reg3 <= rx_reg2;
  43. //第三个寄存器下降沿开始,起始标志位一个clk的高电平
  44. always@(posedge sys_clk or negedge sys_rst_n)
  45.     if(sys_rst_n == 1'b0)
  46.         start_flag <= 1'b0;//增加使能信号判据防止数据变化误判
  47.     else if((rx_reg3 == 1'b1)&&(rx_reg2 == 1'b0)&&(work_en == 1'b0))
  48.         start_flag <= 1'b1;
  49.     else
  50.         start_flag <= 1'b0;
  51. //提取数据使能信号
  52. always@(posedge sys_clk or negedge sys_rst_n)
  53.     if(sys_rst_n == 1'b0)
  54.         work_en <= 1'b0;
  55.     else if(start_flag == 1'b1)
  56.         work_en <= 1'b1;//增加标志位信号严格条件
  57.     else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
  58.         work_en <= 1'b0;
  59.     else
  60.         work_en <= work_en;
  61. //波特率计数器
  62. always@(posedge sys_clk or negedge sys_rst_n)
  63.     if((sys_rst_n == 1'b0))
  64.         baud_cnt <= 16'd0; //计数满或使能信号无的时候清零
  65.     else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))
  66.         baud_cnt <= 16'd0; 
  67.     else
  68.         baud_cnt <= baud_cnt + 1'b1;
  69. //数据读取标志位
  70. always@(posedge sys_clk or negedge sys_rst_n)
  71.     if(sys_rst_n == 1'b0)
  72.         bit_flag <= 1'b0
  73.     else if(baud_cnt == BAUD_CNT_MAX / 2 - 1)
  74.         bit_flag <= 1'b1;
  75.     else
  76.         bit_flag <= 1'b0;
  77. //数据读取计数器
  78. always@(posedge sys_clk or negedge sys_rst_n)
  79.     if(sys_rst_n == 1'b0)
  80.         bit_cnt <= 4'd0; //计数满或使能信号无的时候清零
  81.     else if((bit_flag == 1'b1)&&(bit_cnt == 4'd8))
  82.         bit_cnt <= 4'd0;
  83.     else if(bit_flag == 1'b1)
  84.         bit_cnt <= bit_cnt + 1'b1;
  85. //并行已读数据标志位
  86. always@(posedge sys_clk or negedge sys_rst_n)
  87.     if(sys_rst_n == 1'b0)
  88.         rx_flag <= 1'b0; 
  89.     else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
  90.         rx_flag <= 1'b1;
  91.     else
  92.         rx_flag <= 1'b0; 
  93. //并行已读数据
  94. always@(posedge sys_clk or negedge sys_rst_n)
  95.     if((sys_rst_n == 1'b0))
  96.         rx_data <= 8'b0; 
  97.     else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
  98.         rx_data <= {rx_reg3,rx_data[7:1]};//在第八个数据到来时仍需要移位
  99. //输出数据
  100. always@(posedge sys_clk or negedge sys_rst_n)
  101.     if((sys_rst_n == 1'b0))
  102.         out_data <= 8'b0;
  103.     else if(rx_flag == 1'b1)
  104.         out_data <= rx_data;
  105. //输出标志位
  106. always@(posedge sys_clk or negedge sys_rst_n)
  107.     if((sys_rst_n == 1'b0))
  108.         out_flag <= 1'b0; 
  109.     else
  110.         out_flag <= rx_flag;//与输入标志位信号同步,实际刚好打一拍
  111. endmodule

2.串口发送

同样的,将需要发送的数据放置在寄存器in_data中,in_flag为与数据同步的标志信号,使能信号和计数器作用与串口接收模块类似,此处不再赘述。使能有效后每当bit_cnt计数到1时bit_flag拉高一个周期,并在其下降沿对数据进行按位输出(一共10位宽,包含起始低电平和停止位高电平)并同时按位计数,完成10位宽输出后拉高电平。

 代码如下:

  1. module  uart_tx
  2. #(
  3.     parameter       UART_BPS    =   'd9600      ,//串口波特率
  4.     parameter       CLK_FREQ    =   'd50_000_000//时钟频率
  5. )
  6. (
  7. input        wire          sys_clk     ,
  8. input        wire          sys_rst_n   ,
  9. input        wire   [7:0]  in_data     ,    //wire型数据,大小在wire后面
  10. input        wire          in_flag     ,
  11. output       reg          tx                //always中reg型数据输出
  12. );
  13. parameter   BAUD_CNT_MAX    =   CLK_FREQ / UART_BPS;
  14. //中间参数定义
  15. reg             work_en     ;
  16. reg   [16:0]    baud_cnt    ;
  17. reg   [3:0]     bit_cnt     ;
  18. reg             bit_flag    ;
  19. //使能信号,读到输入标志位后拉高,数据读取9次后拉低
  20. always@(posedge sys_clk or  negedge sys_rst_n)
  21.     if(sys_rst_n == 1'b0)
  22.         work_en <= 1'b0;
  23.     else if(in_flag == 1'b1)
  24.         work_en <= 1'b1;
  25.     else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1))
  26.         work_en <= 1'b0;
  27.     else
  28.         work_en <= work_en;
  29. //波特率计数器
  30. always@(posedge sys_clk or  negedge sys_rst_n)
  31.     if(sys_rst_n == 1'b0)
  32.         baud_cnt <= 16'd0;
  33.     else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))
  34.         baud_cnt <= 16'd0;
  35.     else if(work_en == 1'b1)//时序逻辑不需要列出所有,不会产生多余latch
  36.         baud_cnt <= baud_cnt + 1'b1;
  37. //数据提取标志位
  38. always@(posedge sys_clk or  negedge sys_rst_n)
  39.     if(sys_rst_n == 1'b0)
  40.         bit_flag <= 1'b0;
  41.     else if(baud_cnt == 16'd1)  //此处因为每个计数周期“1”只出现一次
  42.         bit_flag <= 1'b1;
  43.     else
  44.         bit_flag <= 1'b0;
  45. //提取数据计数器
  46. always@(posedge sys_clk or  negedge sys_rst_n)
  47.     if(sys_rst_n == 1'b0)
  48.         bit_cnt <= 4'd0;
  49.     else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1))
  50.         bit_cnt <= 4'd0;
  51.     else if((bit_flag == 1'b1)&&(work_en == 1'b1))
  52.         bit_cnt <= bit_cnt + 1'b1;
  53.     else
  54.         bit_cnt <= bit_cnt;
  55. //输出数据
  56. always@(posedge sys_clk or  negedge sys_rst_n)
  57.     if(sys_rst_n == 1'b0)
  58.         tx <= 1'b1;      //空闲状态为高电平
  59.     else if(bit_flag == 1'b1)
  60.         begin
  61.             case(bit_cnt)
  62.                 0:  tx <= 1'b0;         //起始位
  63.                 1:  tx <= in_data[0];
  64.                 2:  tx <= in_data[1];
  65.                 3:  tx <= in_data[2];
  66.                 4:  tx <= in_data[3];
  67.                 5:  tx <= in_data[4];
  68.                 6:  tx <= in_data[5];
  69.                 7:  tx <= in_data[6];
  70.                 8:  tx <= in_data[7];
  71.                 9:  tx <= 1'b1;         //停止位
  72.                 default: tx = 1'b1;
  73.             endcase
  74.         end
  75. endmodule

3.顶层模块

实例化两个模块即可:

  1. module RS232
  2. (
  3. input wire sys_clk ,
  4. input wire sys_rst_n ,
  5. input wire rx ,
  6. output wire tx
  7. );
  8. wire [7:0] rx_data ;
  9. wire rx_flag ;
  10. uart_rx
  11. #(
  12. .UART_BPS (9600 ),//串口波特率
  13. .CLK_FREQ (50_000_000)//时钟频率
  14. )
  15. uart_rx_inst
  16. (
  17. .sys_clk (sys_clk) ,
  18. .sys_rst_n (sys_rst_n) ,
  19. .rx (rx) ,//输入数据
  20. .out_data (rx_data) ,//输出数据
  21. .out_flag (rx_flag) //输出标志位
  22. );
  23. uart_tx
  24. #(
  25. .UART_BPS (9600 ),//串口波特率
  26. .CLK_FREQ (50_000_000)//时钟频率
  27. )
  28. uart_tx_inst
  29. (
  30. .sys_clk (sys_clk) ,
  31. .sys_rst_n (sys_rst_n) ,
  32. .in_data (rx_data) , //wire型数据,大小在wire后面
  33. .in_flag (rx_flag) ,
  34. .tx (tx) //always中reg型数据输出
  35. );
  36. endmodule

四、仿真验证

通过串口发送数据并返回可以看到功能无误,模块代码编写正确。


总结

本次实例采用波形图法,编写了常见的低速通信协议UART中的RS232协议并验证正确,这样的话一个简单的串口通信就完成了。

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号