[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-30 RS485串口程序收发环路设计

文档创建者:FPGA课程
浏览次数:284
最后更新:2024-09-05
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
​软件版本: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串口程序的设计,在工业场合为了提高串口的抗干扰能力,以及传输距离,RS485/RS422会更加广泛应用。在本文的实验中我们通过调用前面已经编写好的UART收发IP模块,以及FIFO,实现RS485的单工通信测试。其中重点是关于RS485芯片在单工通信方式下的信号控制逻辑设计。
2RS485硬件电路设计
2.1 RS485驱动芯片介绍
SP3485 器件是一款 3.3V 低功耗半双工收发器,符合 RS-485 和 RS-422 串行协议的规范。 SP3485 可以满足 RS-485 和 RS-422 串行协议的电气规范,负载下最高 10Mbps。
a298030ea75f402790922b6b157e1ef5.jpg
                        
PIN
                        
                        
功能
                        
                        
功能描述
                        
                        
1
                        
                        
RO
                        
                        
数据接收输出
                        
                        
2
                        
                        
RE
                        
                        
接收输出使能,低电平有效
                        
                        
3
                        
                        
DE
                        
                        
输出使能,高电平有效
                        
                        
4
                        
                        
DI
                        
                        
数据发送输入
                        
                        
5
                        
                        
GND
                        
                        
                        
                        
6
                        
                        
A
                        
                        
同相驱动器输出/接收器输入
                        
                        
7
                        
                        
B
                        
                        
反相驱动器输出/接收器输入
                        
                        
8
                        
                        
VCC
                        
                        
供电
                        
2.2 RS485的应用电路设计
实际设计的时候,把RE和DE接到一起,可以减少一个控制管脚,入下图

3aea50472c9545f1952b439b8df6158f.jpg
发送真值表:
                        
INPUTS
                        
                        
OUTPUTS
                        
                        
RE
                        
                        
DE
                        
                        
DI
                        
                        
B
                        
                        
A
                        
                        
X
                        
                        
1
                        
                        
1
                        
                        
0
                        
                        
1
                        
                        
X
                        
                        
1
                        
                        
0
                        
                        
1
                        
                        
0
                        
                        
0
                        
                        
0
                        
                        
X
                        
                        
High-Z
                        
                        
1
                        
                        
0
                        
                        
X
                        
                        
关闭
                        
可以看到,结合原理图设计:
当DE是高电平,RE是任何值,DI决定了A B的输出电平;
当DE和RE都是0,AB 输出为高阻,这个时候,RO可以作为输入
接收真值表:
                        
INPUTS
                        
                        
OUTPUTS
                        
                        
RE
                        
                        
DE
                        
                        
Va~Vb
                        
                        
RO
                        
                        
0
                        
                        
X
                        
                        
-50mv
                        
                        
1
                        
                        
0
                        
                        
X
                        
                        
-200mv
                        
                        
0
                        
                        
X
                        
                        
X
                        
                        
Open/Shorted
                        
                        
1
                        
                        
1
                        
                        
1
                        
                        
X
                        
                        
High-Z
                        
                        
1
                        
                        
0
                        
                        
X
                        
                        
关闭
                        
当DE是0,DE为任意值,RO输入有效:
当Va~Vb的差模电压>-50mv RO输出1;
当Va~Vb的差模电压<-200mv RO输出0
2.3 瞬态抑制器的使用
1a176d7b79da47608da7735f8c5bbdc9.jpg
对于工业场合的工作环境,SMBJ6.0瞬态抑制器,可以抑制RS485总线的浪涌,过压,对RS485芯片起到保护作用。
7b44ac1293704df6aab2b0edbf4b8fa2.jpg
3RS485的收发环路
3.1系统框图
RS485采用的通信协议依然是UART串口协议,因此收发模块还是使用前面已经写好的UART收发IP模块。为了实现单工模式的环路测试功能,这里增加了1个FIFO以及1个收发模块控制器。默认情况下,RS485芯片工作于接收数据状态,当FIFO有数据,并且一段时间内没有新数据到达,控制器切换RS485芯片到发送状态,从FIFO中取出缓存的数据发出。
ec7cc00f4d2546c790d9fd395ecd2bfe.jpg
3.2顶层模块源码
以下代码中核心部分为如何实现RS485数据方向的切换,以及数据到FIFO的缓存。
  1. /*************RS485 半双工测试*************
  2. --使用到了UART 发送和接收驱动
  3. --通过FIFO缓存数据,并且检测到接收总线一段时间没有时间达到,进入发送模式,发送之前接收到的数据。
  4. *********************************************************************/
  5. `timescale 1ns / 1ns //仿真时间刻度/精度

  6. module rs485_top(
  7. input       I_sysclk_p,
  8. input       I_sysclk_n,//系统时钟输入
  9. output      O_rs485_de, // 485 de控制,控制输入 输出方向,0 输入 1输出
  10. input       I_rs485_rx, // 485 RX输入总线
  11. output      O_rs485_tx  // 485 TX输出总线
  12. );

  13. localparam  SYSCLKHZ = 100_000_000;

  14. wire I_clk;
  15. IBUFGDS CLK_U(
  16. .I(I_sysclk_p),
  17. .IB(I_sysclk_n),
  18. .O(I_clk)
  19. );
  20. wire        reset_n;  //内部上电延迟复位
  21. wire        rs485_rx; //内部RX 接收
  22. wire        rs485_fifo_empty;  //FIFO空
  23. wire        rs485_fifo_rd_en;  //FIFO 读使能
  24. wire        rs485_rvalid;      //读数据有效
  25. wire [7:0]  rs485_rdata;       //串口读数据(接收)
  26. wire [7:0]  rs485_wdata;       //串口写数据(发送)
  27. wire        rs485_wbusy;       //写忙
  28. reg         rs485_de;          //485芯片的DE控制0 输入 1输出
  29. reg         rs485_wreq = 1'b0; //串口写请求
  30. wire        rs485_tx_start;    //发送启动
  31. reg         rs485_tx_start_r = 1'b0; //发送启动寄存
  32. reg [17:0]  T_dcnt; //延迟计数器
  33. reg [11:0]  rstn_cnt = 12'd0; //复位计数器

  34. //当rs485_de=0,设置rs485内部逻辑准备接收总线数据
  35. assign rs485_rx         = (rs485_de == 1'b0) ? I_rs485_rx : 1'b1;
  36. //当FIFO中有数据,并且串口发送不忙,启动发送
  37. assign rs485_tx_start   = rs485_de&&(rs485_wbusy == 1'b0)&&(!rs485_fifo_empty);
  38. //读FIFO使能,用rs485_tx_start的上升沿触发读FIFO
  39. assign rs485_fifo_rd_en = (rs485_tx_start_r == 1'b0 & rs485_tx_start == 1'b1);//read fifo enable
  40. //当rs485_de=0,设置rs485内部逻辑准备接收总线数据,否则,可以发送数据
  41. assign O_rs485_de       = rs485_de;

  42. assign reset_n = rstn_cnt[11];//上电延迟复位

  43. //复位计数器
  44. always @(posedge I_clk)begin
  45.     if(rstn_cnt[11] == 1'b0)
  46.         rstn_cnt <= rstn_cnt + 1'b1;
  47.     else
  48.         rstn_cnt <= rstn_cnt;
  49. end

  50. //T_dcnt用于一段时间内,判断接收总线是否空闲
  51. always @(posedge I_clk or negedge reset_n)begin
  52.    if(reset_n == 1'b0)
  53.        T_dcnt <= 17'd0;
  54.    else if(rs485_rx && (!rs485_fifo_empty))//如果RX总线是高电平,并且FIFO非空(有数据)
  55.        T_dcnt <= (T_dcnt[17] == 1'b1) ? T_dcnt : T_dcnt + 1'b1; //计数器累加
  56.    else
  57.        T_dcnt <= 0; //否则重置归零

  58. end

  59. //当rs485_de=0,设置rs485内部逻辑准备接收总线数据,否则,可以发送数据
  60. always @(posedge I_clk )begin
  61.     if(reset_n ==1'b0)//if(rs485_de | reset_n ==1'b0)
  62.        rs485_de <= 1'b0;//重置rs485_de,切换到接收
  63.     else if((T_dcnt[17]==1'b1)|rs485_wreq|rs485_wbusy)//当T_dcnt延迟计数器到达计数值,或者串口发送控制器正在发送(rs485_wreq,rs485_wbusy有效)
  64.        rs485_de <= 1'b1;//设置rs485_de
  65.      else
  66.      rs485_de <= 1'b0;//设置rs485_de
  67. end

  68. //打拍寄存一次
  69. always @(posedge I_clk) rs485_tx_start_r <= rs485_tx_start;

  70. //rs485_wreq,延迟于rs485_fifo_rd_en,1个时钟,用于数据同步
  71. always @(posedge I_clk) rs485_wreq <= rs485_fifo_rd_en;

  72. //例化FIFO IP,FIFO设置标准模式,用户缓存接收到的帧
  73. fifo_generator_0 inst_fifo (
  74.   .wr_clk(I_clk),          //写时钟输入
  75.   .wr_rst(reset_n == 1'b0),   //写复位
  76.   .rd_clk(I_clk),          //读时钟输入
  77.   .rd_rst(reset_n == 1'b0),   //读复位
  78.   .din(rs485_rdata),          //RX接收到的数据
  79.   .wr_en(rs485_rvalid&(rs485_de==1'b0)), // 写FIFO 使能,当rs485_rvalid有效,写入数据
  80.   .rd_en(rs485_fifo_rd_en),   //FIFO读使能
  81.   .dout(rs485_wdata),         //写数据
  82.   .empty(rs485_fifo_empty)    //FIFO 空
  83. );
  84. //例化串口发送模块
  85. uiuart_tx#
  86. (
  87. .BAUD_DIV(SYSCLKHZ/115200 -1)  //设置波特率  
  88. )
  89. uiuart_tx_u
  90. (
  91. .I_clk(I_clk),           //系统时钟
  92. .I_uart_rstn(reset_n),      //系统复位
  93. .I_uart_wreq(rs485_wreq),   //串口发送驱动器发送请求
  94. .I_uart_wdata(rs485_wdata), //串口发送,数据
  95. .O_uart_wbusy(rs485_wbusy), //串口发送总线忙
  96. .O_uart_tx(O_rs485_tx)      //串口发送总线
  97. );



  98. //例化串口接收模块
  99. uiuart_rx#
  100. (
  101. .BAUD_DIV(SYSCLKHZ/115200 -1) //设置波特率      
  102. )
  103. uiuart_rx_u
  104. (
  105. .I_clk(I_clk),           //系统时钟
  106. .I_uart_rstn(reset_n),   //系统复位
  107. .I_uart_rx(rs485_rx),       //串口接收总线
  108. .O_uart_rdata(rs485_rdata), //串口接收数据
  109. .O_uart_rvalid(rs485_rvalid)//串口接收数据有效
  110. );   

  111.    
  112. endmodule
复制代码

4FPGA工程
fpga工程的创建过程不再重复
3e750c0c840d4fe3bb6d91f25ded08d5.jpg
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
925ca5ba1749439582061a77083b166c.jpg
5 RTL仿真
5.1仿真测试文件
  1. `timescale 1ns/1ns  //定义仿真时间刻度/精度
  2. module uart_top_tb();

  3. localparam      BPS          = 'd115200 ;             //波特率
  4. localparam      CLK_FRE    = 'd100_000_000  ;     //系统频率
  5. localparam    CLK_TIME   = 'd1000_000_000 /CLK_FRE;//计算系统时钟周期,以ns为单位
  6. localparam      BIT_TIME   = 'd1000_000_000/ BPS ;  //计算出传输每个bit所需要的时间以ns为单位
  7. localparam    NUM_BYTES  = 3;               //需要发送的BYTES

  8. reg               I_sysclk_p;
  9. reg               I_sysclk_n;               //系统时钟
  10. reg         bsp_clk ;     //波特率时钟
  11. reg               uart_tx;      //uart 数据发送,该信号接入到,FPGA的uart 接收
  12. wire        uart_rx;      //uart 数据接收,该信号接入到,FPGA的uart 发送
  13. reg [8*NUM_BYTES-1:0] uart_send_data;   //需要发送的数据
  14. reg [7:0]                 uart_send_data_r; //寄存每次需要发送的BYTE

  15. integer i,j;

  16. //例化顶层模块
  17. rs485_top rs485_top
  18. (
  19. .I_sysclk_p     (I_sysclk_p),
  20. .I_sysclk_n     (I_sysclk_n),
  21. .O_rs485_de   (O_rs485_de),
  22. .I_rs485_rx   (uart_tx),
  23. .O_rs485_tx   (uart_rx)
  24. );
  25. //仿真初始化
  26. initial begin
  27. //初始化REG寄存器
  28. I_sysclk_p =0;
  29. I_sysclk_n =1;
  30. bsp_clk  = 0;  
  31. uart_tx  = 1;
  32. i=0;
  33. j=0;

  34. uart_send_data   =0;
  35. uart_send_data_r =0;

  36. #40000;//延迟40000ns,等待uart测试代码中的复位延迟
  37. uart_send_data[(0*8) +: 8] = 8'b1001_0101;//初始化需要发送的第1个BYTE
  38. uart_send_data[(1*8) +: 8] = 8'b0000_0101;//初始化需要发送的第2个BYTE
  39. uart_send_data[(2*8) +: 8] = 8'b1000_0100;//初始化需要发送的第3个BYTE
  40. //uart tx 发送数据
  41.   for(i=0; i<NUM_BYTES;i=i+1)
  42.   Begin

  43.       uart_send_data_r = uart_send_data[(i*8) +: 8];//寄存需要发送的数据到寄存器
  44.       $display("uart_send_data : 0x%h",uart_send_data_r);//打印准备发送的数据

  45.       @(posedge bsp_clk);  //发送起始位1bit
  46.       uart_tx = 1'b0;

  47.       for(j=0;j<8;j=j+1)begin//发送数据8bits
  48.       @(posedge bsp_clk);  //发送
  49.       uart_tx = uart_send_data_r[j];
  50.       end

  51.        @(posedge bsp_clk);//发送停止位1bit
  52.        uart_tx = 1'b1;  
  53.   end
  54.        @(posedge bsp_clk);
  55.        #2000000 $finish;            
  56. end

  57. always #(CLK_TIME/2) I_sysclk_p = ~I_sysclk_p;
  58. always #(CLK_TIME/2) I_sysclk_n = ~I_sysclk_n;    //产生主时钟
  59. always #(BIT_TIME/2) bsp_clk  = ~bsp_clk;       //产生波特率时钟

  60. endmodule
复制代码

5.2仿真结果
320b5c48ecbf46a2b876f556cbe213e5.jpg
6下载演示
(硬件不支持,本课不展示下载演示结果)



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

本版积分规则