[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-11 UART串口接收驱动设计

文档创建者:FPGA课程
浏览次数:281
最后更新:2024-08-24
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-8-24 11:10 编辑

​软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

1概述
UART串口通信是应用非常广泛的一种串行异步通信方式,常用的异步串口包括RS232\RS482\RS485。
RS232的逻辑1的电平为-3~-15V,逻辑0的电平为+3~+15V,下图是老式的DB9串口线。
2ea35f09c2444918987304571938bd4f.jpg
标准的DB9接口定义如下:
                        
序号
                        
                        
DB9公头
                        
                        
DB9母头
                        
                        
1
                        
                        
CD
                        
                        
载波检测
                        
                        
CD
                        
                        
载波检测
                        
                        
2
                        
                        
RXD
                        
                        
接收端接收
                        
                        
TXD
                        
                        
发送端发送数据
                        
                        
3
                        
                        
TXD
                        
                        
发送数据
                        
                        
RXD
                        
                        
接收端接收数据
                        
                        
4
                        
                        
DTR
                        
                        
数据终端就绪
                        
                        
DTR
                        
                        
数据终端就绪
                        
                        
5
                        
                        
GND
                        
                        
系统接地
                        
                        
GND
                        
                        
系统接地
                        
                        
6
                        
                        
DSR
                        
                        
数据准备就绪
                        
                        
DSR
                        
                        
数据准备就绪
                        
                        
7
                        
                        
RTS
                        
                        
发送请求
                        
                        
CTS
                        
                        
清除请求
                        
                        
8
                        
                        
CTS
                        
                        
清除发送
                        
                        
RTS
                        
                        
发送请求
                        
                        
9
                        
                        
RI
                        
                        
振铃指示器
                        
                        
RI
                        
                        
振铃指示器
                        
实际上一般RS232只使用到TXD RXD两个信号。
8d95a322f8a5408499e8071a7b24538e.jpg
RS485/422采用差分信号负逻辑,逻辑“1”以两线间的电压差为-(2~6)V表示;逻辑“0”以两线间的电压差为+(2~6)V表示,由于采用差分方式可以具有更高的速度和更强的抗干扰能力,具有更远的传输距离。由于RS485/RS422更多出现在工业场合,因此一般采用接线端子方式接线。
老式DB9串口基本上已经消失了,但是UART串口更加广泛地使用,比如我们常见的USB串口,就是通过USB接口芯片,实现了以TTL电平方式的UART串口通信。数据通过USB接口进行传输,通过UART串口芯片完成USB协议到UART串口协议的相互转换。
实验目的:
1:实现UART串口接收控制器的设计
2:完成仿真验证,仿真模拟发送三组数据接收成功
2UART接收驱动设计2.1系统框图
如下图所示,米联客设计的UART发送控制器包含4个主要模块:波特率发生器、抗干扰过采样模块、起始位检测模块,移位模块。
5090469aaff645e3af749376e54ba4b5.jpg
2.2UART接收时序
下图中,UART串口通信数据格式包括1bit起始位、8bits数据位、1bit停止位,不包含奇偶校验位。
14a0c514eada4f0f918a04b1448ae8a9.jpg
上图中,对接收数据进行过采样,对一个波特率位的数据以8倍波特率采样,通过判断低电平和高电平采样的次数,决定最终采集的是高电平还是低电平。多次采样可以提高总线的抗干扰能力。
在开始编写串口接收驱动前,我们需要了解以下概念:
波特率UART采用异步通信方式,数据收发双方只有在同一波特率才能正常通信。波特率代表了UART完成1个时间单位数据位或者控制位的时间。通常,我们需要对系统时钟进行分频来产生正确的波特率,所以计算分频系数尤为重要,比如系统时钟是100_000_000HZ,波特率是115200,那么分频系数为=100_000_000/115200-1
起始位UART数据总线由高电平变低电平,并且持续1个波特率时间代表数据的起始。
数据位每个数据位占用1个波特率时间,本文实验发送1BYTE字节需要占用8个波特率时间。
停止位如果没有奇偶校验位,数据位结束后,保持1/1.5/2个波特率的高电平代表了停止位。
奇偶校验用于校对数据,对于UART通信,可以根据实际情况选择是否需要支持奇偶校验。
2.3驱动接口时序图
米联客设计了一种通用简洁的驱动接口,包含以下信号:
xxx_rdata:接收的数据
xxx_rvalid:接收的有效数据
这里xxx代表了uart
92eddedd853345d2b5a472955449a7f8.jpg
2.4驱动源码
代码如下:
  1. /*******************************UART接收驱动*********************
  2. --以下是米联客设计的UART接收驱动器
  3. --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨
  4. --2.uart_rvalid_o有效,代表数据uart_rdata_o有效
  5. --3.抗干扰设计,对每个bit采用8倍采样,如果采样到低电平的次数大于高电平,则为0,否则为1
  6. *********************************************************************/

  7. `timescale 1ns / 1ns//仿真时间刻度/精度

  8. module uiuart_rx#
  9. (
  10. parameter integer  BAUD_DIV     = 10416  //波特率分频参数,BAUD_DIV=系统时钟频率/波特率-1 比如100M系统时钟,波特率115200 BAUD_DIV= 100_000_000/115200-1
  11. )
  12. (
  13. input I_clk, //系统时钟输入
  14. input I_uart_rstn,//系统复位输入
  15. input I_uart_rx,//uart rx 总线信号输入
  16. output [7:0] O_uart_rdata,//uart rx接收到的数据输出
  17. output O_uart_rvalid// uart rx 接收数据有效信号,当为1的时候O_uart_rdata数据有效
  18. );

  19. localparam  BAUD_DIV_SAMP = (BAUD_DIV/8)-1;                            //多次采样,按照波特率系数的八分之一进行采样

  20. wire bps_en       ; //波特率使能信号
  21. wire samp_en      ; //采样使能信号
  22. wire bit_cap_done ; //uart rx总线信号采样有效数据完成
  23. wire uart_rx_done ; //uart 1byte 接收完成
  24. wire bit_data     ; //接收的1bit数据
  25. wire I_uart_rxnt  ; //I_uart_rxnt的启动信号检测,当变为低电平,代表可能存在起始位(UART 起始位为低电平)

  26. reg [13:0]  baud_div = 14'd0;//波特率分频计数器
  27. reg [13:0]  samp_cnt = 14'd0;//采样计数器
  28. reg [4 :0]  I_uart_rx_r = 5'd0;//异步采集多次寄存
  29. reg [3 :0]  bit_cnt=4'd0;//bit 计数器
  30. reg [3 :0]  cap_cnt=4'd0;//cap 计数器
  31. reg [4 :0]  rx_bit_tmp = 5'd0;//rx_bit_tmp用于多次采样,通过计算采样到高电平次数和低电平次数,判断本次采样是高电平还是低电平
  32. reg [7 :0]  rx_data = 8'd0;//数据接收寄存器

  33. reg bps_start_en_r = 1'b0;
  34. reg bit_cap_done_r = 1'b0;
  35. reg bps_start_en,start_check_done,start_check_failed;

  36. assign bps_en       =   (baud_div == (BAUD_DIV - 1'b1));                     //完成一次波特率传输信号
  37. assign samp_en      =   (samp_cnt == (BAUD_DIV_SAMP - 1'b1 ));               //完成一次波特率采样信号
  38. assign bit_cap_done =   (cap_cnt  == 3'd7);//采样计数
  39. assign uart_rx_done =   (bit_cnt  == 9)&&(baud_div == BAUD_DIV >> 1);//当停止位开始,提前半停止位,发送uart_rx_done信号,以便提前准备进入下一个数据的接收

  40. assign bit_data     =   (rx_bit_tmp < 5'd15) ? 0 : 1; //rx_bit_tmp用于多次采样,通过计算采样到高电平次数和低电平次数,判断本次采样是高电平还是低电平,提高抗干扰能力
  41. //连续5次信号拉低,判断开始传输
  42. assign I_uart_rxnt  =   I_uart_rx_r[4] | I_uart_rx_r[3] | I_uart_rx_r[2] | I_uart_rx_r[1] | I_uart_rx_r[0];
  43. assign O_uart_rdata   =   rx_data;
  44. assign O_uart_rvalid  =   uart_rx_done;   

  45. //波特率计数器
  46. always@(posedge I_clk)begin
  47.     if(bps_start_en && baud_div < BAUD_DIV)                 //baud_div计数,目标值BAUD_DIV
  48.         baud_div <= baud_div + 1'b1;
  49.     else
  50.         baud_div <= 14'd0;
  51. end

  52. //8bit采样使能,8倍波特率采样,也就是这个计数器,用于产生8倍过采样
  53. always@(posedge I_clk)begin
  54.     if(bps_start_en && samp_cnt < BAUD_DIV_SAMP)             //bps_start_en高电平有效,开始对bit进行采样,samp_cnt以8倍于波特率速度对每个bit采样
  55.         samp_cnt <= samp_cnt + 1'b1;                         //samp_cnt计数+1      
  56.     else
  57.         samp_cnt <= 14'd0;                                   //samp_cnt计数清零
  58. end

  59. //uart rx bus asynchronous to Synchronous
  60. always@(posedge I_clk)begin
  61.     I_uart_rx_r <= {I_uart_rx_r[3:0],I_uart_rx};             //I_uart_rx的数据存入I_uart_rx_r进行缓存
  62. end

  63. //uart接收启动检查
  64. always@(posedge I_clk)begin
  65.     if(I_uart_rstn == 1'b0 || uart_rx_done || start_check_failed) //bps_start_en拉低的三种情况,复位、接收完成、校验失败
  66.         bps_start_en    <= 1'b0;                                               //接收结束
  67.     else if((I_uart_rxnt == 1'b0)&(bps_start_en==1'b0))//当判断到I_uart_rxnt == 1'b0,并且总线之前空闲(bps_start_en==1'b0,代表总线空闲)
  68.         bps_start_en    <= 1'b1;//使能波特率计数器使能
  69. end

  70. //uart接收启动使能
  71. always@(posedge I_clk)begin
  72.         bps_start_en_r    <= bps_start_en;                              //bps_start_en信号打一拍,方便后续上升沿捕捉
  73. end

  74. always@(posedge I_clk)begin
  75.     if(I_uart_rstn == 1'b0 || start_check_failed)begin//当系统复位,或者start_check_failed,重置start_check_done和start_check_failed
  76.         start_check_done    <= 1'b0;
  77.         start_check_failed  <= 1'b0;
  78.     end   
  79.     else if(bps_start_en == 1'b1&&bps_start_en_r == 1'b0) begin//当检测到start信号,也重置start_check_done和start_check_failed
  80.         start_check_done    <= 1'b0;
  81.         start_check_failed  <= 1'b0;
  82.     end
  83.     else if((bit_cap_done&&bit_cap_done_r==1'b0)&&(start_check_done == 1'b0))begin//第一个波特率采样,用于判断是否一个有效的起始位,如果不是有效的,start_check_failed设置为1
  84.         start_check_failed <= bit_data ? 1'b1 : 1'b0;
  85.         start_check_done   <= 1'b1;//不管是否start_check_failed==1,都会设置start_check_done=1,但是start_check_failed==1,会下一个系统时钟重置start_check_done=0
  86.     end     
  87. end

  88. //bits 计数器
  89. always@(posedge I_clk)begin
  90.     if(I_uart_rstn == 1'b0 || uart_rx_done || bps_start_en == 1'b0)//复位、接收完成、或者总线空闲(bps_start_en == 1'b0),重置bit_cnt
  91.         bit_cnt   <= 4'd0;                                                   
  92.     else if(bps_en)//每一个bps_en有效,加1
  93.         bit_cnt <= bit_cnt + 1'b1;  // bit_cnt计数器用于计算当前采样了第几个bit
  94. end

  95. //8次过采样,提高抗干扰
  96. always@(posedge I_clk)begin
  97.     if(I_uart_rstn == 1'b0 || bps_en == 1'b1 || bps_start_en == 1'b0) begin //当I_uart_rstn=0或者bps_en=1或者bps_start_en==0,重置cap_cnt和rx_bit_tmp
  98.         cap_cnt     <= 4'd0;
  99.         rx_bit_tmp  <= 5'd15;
  100.     end
  101.     else if(samp_en)begin//bit采样使能
  102.         cap_cnt     <= cap_cnt + 1'b1;//cap_cnt用于记录了当前是第几次过采样,1个bit采样8次
  103.         rx_bit_tmp  <= I_uart_rx_r[4] ? rx_bit_tmp + 1'b1 :  rx_bit_tmp - 1'b1;   //多次采样,如果是高电平+1,如果是低电平-1,最终看本次bit采样结束rx_bit_tmp如果小于15代表是低电平
  104.     end                                                                                   
  105. end

  106. //寄存一次bit_cap_done,用于产生高电平触发脉冲下面用到
  107. always@(posedge I_clk)
  108.     bit_cap_done_r <= bit_cap_done;

  109. always@(posedge I_clk)begin
  110.     if(I_uart_rstn == 1'b0 || bps_start_en == 1'b0)//当复位或者总线空闲,重置rx_data
  111.         rx_data  <= 8'd0;  
  112.     else if(start_check_done&&(bit_cap_done&&bit_cap_done_r==1'b0)&&bit_cnt < 9)//当start_check_done有效,并且bit_cnt<9,每次bit_cap_done有效,完成一次移位寄存
  113.         rx_data  <= {bit_data,rx_data[7:1]};                                         //串并转换,将数据存入rx_data 中,共8位
  114. end

  115. endmodule
复制代码

3 FPGA工程
fpga工程的创建过程不再重复,如有不清楚的请看前面实验
4d50c62ef9b0483f9c12d7104d011ebb.jpg
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
7b68d36a148f44c38c45537665e90686.jpg
4 仿真测试4.1添加仿真测试源码
仿真测试文件存放在工程目录uisrc\02_sim中,源码如下:
  1. `timescale 1ns / 1ns//仿真时间刻度/精度
  2. module uiuart_rx#
  3. (
  4. parameter integer  BAUD_DIV     = 10416  //波特率分频参数,BAUD_DIV=系统时钟频率/波特率-1 比如100M系统时钟,波特率115200 BAUD_DIV= 100_000_000/115200-1
  5. )
  6. (
  7. input I_clk, //系统时钟输入
  8. input I_uart_rstn,//系统复位输入
  9. input I_uart_rx,//uart rx 总线信号输入
  10. output [7:0] O_uart_rdata,//uart rx接收到的数据输出
  11. output O_uart_rvalid// uart rx 接收数据有效信号,当为1的时候O_uart_rdata数据有效
  12. );

  13. localparam  BAUD_DIV_SAMP = (BAUD_DIV/8)-1;          //多次采样,按照波特率系数的八分之一进行采样

  14. wire bps_en       ; //波特率使能信号
  15. wire samp_en      ; //采样使能信号
  16. wire bit_cap_done ; //uart rx总线信号采样有效数据完成
  17. wire uart_rx_done ; //uart 1byte 接收完成
  18. wire bit_data     ; //接收的1bit数据
  19. wire I_uart_rxnt  ; //I_uart_rxnt的启动信号检测,当变为低电平,代表可能存在起始位(UART 起始位为低电平)

  20. reg [13:0]  baud_div = 14'd0;//波特率分频计数器
  21. reg [13:0]  samp_cnt = 14'd0;//采样计数器
  22. reg [4 :0]  I_uart_rx_r = 5'd0;//异步采集多次寄存
  23. reg [3 :0]  bit_cnt=4'd0;//bit 计数器
  24. reg [3 :0]  cap_cnt=4'd0;//cap 计数器
  25. reg [4 :0]  rx_bit_tmp = 5'd0;//rx_bit_tmp用于多次采样,通过计算采样到高电平次数和低电平次数,判断本次采样是高电平还是低电平
  26. reg [7 :0]  rx_data = 8'd0;//数据接收寄存器

  27. reg bps_start_en_r = 1'b0;
  28. reg bit_cap_done_r = 1'b0;
  29. reg bps_start_en,start_check_done,start_check_failed;

  30. assign bps_en       =   (baud_div == (BAUD_DIV - 1'b1));                     //完成一次波特率传输信号
  31. assign samp_en      =   (samp_cnt == (BAUD_DIV_SAMP - 1'b1 ));               //完成一次波特率采样信号
  32. assign bit_cap_done =   (cap_cnt  == 3'd7);//采样计数
  33. assign uart_rx_done =   (bit_cnt  == 9)&&(baud_div == BAUD_DIV >> 1);//当停止位开始,提前半停止位,发送uart_rx_done信号,以便提前准备进入下一个数据的接收

  34. assign bit_data     =   (rx_bit_tmp < 5'd15) ? 0 : 1; //rx_bit_tmp用于多次采样,通过计算采样到高电平次数和低电平次数,判断本次采样是高电平还是低电平,提高抗干扰能力
  35. //连续5次信号拉低,判断开始传输
  36. assign I_uart_rxnt  =   I_uart_rx_r[4] | I_uart_rx_r[3] | I_uart_rx_r[2] | I_uart_rx_r[1] | I_uart_rx_r[0];
  37. assign O_uart_rdata   =   rx_data;
  38. assign O_uart_rvalid  =   uart_rx_done;  

  39. //波特率计数器
  40. always@(posedge I_clk)begin
  41.     if(bps_start_en && baud_div < BAUD_DIV)                 //baud_div计数,目标值BAUD_DIV
  42.         baud_div <= baud_div + 1'b1;
  43.     else
  44.         baud_div <= 14'd0;
  45. end

  46. //8bit采样使能,8倍波特率采样,也就是这个计数器,用于产生8倍过采样
  47. always@(posedge I_clk)begin
  48.     if(bps_start_en && samp_cnt < BAUD_DIV_SAMP)             //bps_start_en高电平有效,开始对bit进行采样,samp_cnt以8倍于波特率速度对每个bit采样
  49.         samp_cnt <= samp_cnt + 1'b1;                         //samp_cnt计数+1      
  50.     else
  51.         samp_cnt <= 14'd0;                                   //samp_cnt计数清零
  52. end

  53. //uart rx bus asynchronous to Synchronous
  54. always@(posedge I_clk)begin
  55.     I_uart_rx_r <= {I_uart_rx_r[3:0],I_uart_rx};             //I_uart_rx的数据存入I_uart_rx_r进行缓存
  56. end

  57. //uart接收启动检查
  58. always@(posedge I_clk)begin
  59.     if(I_uart_rstn == 1'b0 || uart_rx_done || start_check_failed) //bps_start_en拉低的三种情况,复位、接收完成、校验失败
  60.         bps_start_en    <= 1'b0;                                               //接收结束
  61.     else if((I_uart_rxnt == 1'b0)&(bps_start_en==1'b0))//当判断到I_uart_rxnt == 1'b0,并且总线之前空闲(bps_start_en==1'b0,代表总线空闲)
  62.         bps_start_en    <= 1'b1;//使能波特率计数器使能
  63. end

  64. //uart接收启动使能
  65. always@(posedge I_clk)begin
  66.         bps_start_en_r    <= bps_start_en;               //bps_start_en信号打一拍,方便后续上升沿捕捉
  67. end

  68. always@(posedge I_clk)begin
  69.     if(I_uart_rstn == 1'b0 || start_check_failed)begin//当系统复位,或者start_check_failed,重置start_check_done和start_check_failed
  70.         start_check_done    <= 1'b0;
  71.         start_check_failed  <= 1'b0;
  72.     end   
  73.     else if(bps_start_en == 1'b1&&bps_start_en_r == 1'b0) begin//当检测到start信号,也重置start_check_done和start_check_failed
  74.         start_check_done    <= 1'b0;
  75.         start_check_failed  <= 1'b0;
  76.     end
  77.     else if((bit_cap_done&&bit_cap_done_r==1'b0)&&(start_check_done == 1'b0))begin//第一个波特率采样,用于判断是否一个有效的起始位,如果不是有效的,start_check_failed设置为1
  78.         start_check_failed <= bit_data ? 1'b1 : 1'b0;
  79.         start_check_done   <= 1'b1;//不管是否start_check_failed==1,都会设置start_check_done=1,但是start_check_failed==1,会下一个系统时钟重置start_check_done=0
  80.     end   
  81. end

  82. //bits 计数器
  83. always@(posedge I_clk)begin
  84.     if(I_uart_rstn == 1'b0 || uart_rx_done || bps_start_en == 1'b0)//复位、接收完成、或者总线空闲(bps_start_en == 1'b0),重置bit_cnt
  85.         bit_cnt   <= 4'd0;                                                   
  86.     else if(bps_en)//每一个bps_en有效,加1
  87.         bit_cnt <= bit_cnt + 1'b1;  // bit_cnt计数器用于计算当前采样了第几个bit
  88. end

  89. //8次过采样,提高抗干扰
  90. always@(posedge I_clk)begin
  91.     if(I_uart_rstn == 1'b0 || bps_en == 1'b1 || bps_start_en == 1'b0) begin //当I_uart_rstn=0或者bps_en=1或者bps_start_en==0,重置cap_cnt和rx_bit_tmp
  92.         cap_cnt     <= 4'd0;
  93.         rx_bit_tmp  <= 5'd15;
  94.     end
  95.     else if(samp_en)begin//bit采样使能
  96.         cap_cnt     <= cap_cnt + 1'b1;//cap_cnt用于记录了当前是第几次过采样,1个bit采样8次
  97.         rx_bit_tmp  <= I_uart_rx_r[4] ? rx_bit_tmp + 1'b1 :  rx_bit_tmp - 1'b1;   //多次采样,如果是高电平+1,如果是低电平-1,最终看本次bit采样结束rx_bit_tmp如果小于15代表是低电平
  98.     end                                                                                 
  99. end

  100. //寄存一次bit_cap_done,用于产生高电平触发脉冲下面用到
  101. always@(posedge I_clk)
  102.     bit_cap_done_r <= bit_cap_done;

  103. always@(posedge I_clk)begin
  104.     if(I_uart_rstn == 1'b0 || bps_start_en == 1'b0)//当复位或者总线空闲,重置rx_data
  105.         rx_data  <= 8'd0;  
  106.     else if(start_check_done&&(bit_cap_done&&bit_cap_done_r==1'b0)&&bit_cnt < 9)//当start_check_done有效,并且bit_cnt<9,每次bit_cap_done有效,完成一次移位寄存
  107.         rx_data  <= {bit_data,rx_data[7:1]};                                         //串并转换,将数据存入rx_data 中,共8位
  108. end

  109. endmodule
复制代码

4.3开始仿真 5fcbd96d70784a318d7d37fb138ed307.jpg
选择我们主程序信号
07c9237ddd2344d98d2f813966bc345e.jpg

添加进观察窗
a644e23e14bd4f049acadfe504afe5e4.jpg
删除多余的信号
5ce492a5bcf646b89da9b088d96cfc1f.jpg




点击run按键,开始仿真
d75b07c0d53147a08813fa0efca4988e.jpg
观察发送程序的仿真结果准确无误
8f752f01d66247ef976632e08808de22.jpg
本实验只完成仿真演示,在下一个实验中完成uart收发环路实验。


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则