uisrc 发表于 2023-12-29 19:18:22

2-3-30 基于FPGA实现RS485串口程序收发环路设计

软件版本:VIVADO2021.1操作系统:WIN10 64bit硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA登录米联客(MiLianKe)FPGA社区-www.uisrc.com观看免费视频课程、在线答疑解惑!1 概述在前面的课程中,我们已经学习了UART串口程序的设计,在工业场合为了提高串口的抗干扰能力,以及传输距离,RS485/RS422会更加广泛应用。在本文的实验中我们通过调用前面已经编写好的UART收发IP模块,以及FIFO,实现RS485的单工通信测试。其中重点是关于RS485芯片在单工通信方式下的信号控制逻辑设计。2 RS485硬件电路设计2.1 RS485驱动芯片介绍SP3485 器件是一款 3.3V 低功耗半双工收发器,符合 RS-485 和 RS-422 串行协议的规范。 SP3485 可以满足 RS-485 和 RS-422 串行协议的电气规范,负载下最高 10Mbps。
PIN功能功能描述
1RO数据接收输出
2RE接收输出使能,低电平有效
3DE输出使能,高电平有效
4DI数据发送输入
5GND地
6A同相驱动器输出/接收器输入
7B反相驱动器输出/接收器输入
8VCC供电


2.2 RS485的应用电路设计实际设计的时候,把RE和DE接到一起,可以减少一个控制管脚,入下图
发送真值表:
INPUTSOUTPUTS
REDEDIBA
X1101
X1010
00XHigh-Z
10X关闭


可以看到,结合原理图设计:当DE是高电平,RE是任何值,DI决定了A B的输出电平;当DE和RE都是0,AB 输出为高阻,这个时候,RO可以作为输入接收真值表:
INPUTSOUTPUTS
REDEVa~VbRO
0X-50mv1
0X-200mv0
XXOpen/Shorted1
11XHigh-Z
10X关闭


当DE是0,DE为任意值,RO输入有效:当Va~Vb的差模电压>-50mv RO输出1;当Va~Vb的差模电压<-200mv RO输出02.3 瞬态抑制器的使用对于工业场合的工作环境,SMBJ6.0瞬态抑制器,可以抑制RS485总线的浪涌,过压,对RS485芯片起到保护作用。3 RS485的收发环路3.1 系统框图RS485采用的通信协议依然是UART串口协议,因此收发模块还是使用前面已经写好的UART收发IP模块。为了实现单工模式的环路测试功能,这里增加了1个FIFO以及1个收发模块控制器。默认情况下,RS485芯片工作于接收数据状态,当FIFO有数据,并且一段时间内没有新数据到达,控制器切换RS485芯片到发送状态,从FIFO中取出缓存的数据发出。3.2 顶层模块源码以下代码中核心部分为如何实现RS485数据方向的切换,以及数据到FIFO的缓存。
/*************RS485 半双工测试*************--使用到了UART 发送和接收驱动--通过FIFO缓存数据,并且检测到接收总线一段时间没有时间达到,进入发送模式,发送之前接收到的数据。*********************************************************************/`timescale 1ns / 1ns //仿真时间刻度/精度
module rs485_top(input       I_sysclk,//系统时钟输入output      O_rs485_de, // 485 de控制,控制输入 输出方向,0 输入 1输出input       I_rs485_rx, // 485 RX输入总线output      O_rs485_tx// 485 TX输出总线);
localparamSYSCLKHZ = 50_000_000;
wire      reset_n;//内部上电延迟复位wire      rs485_rx; //内部RX 接收wire      rs485_fifo_empty;//FIFO空wire      rs485_fifo_rd_en;//FIFO 读使能wire      rs485_rvalid;      //读数据有效wire rs485_rdata;       //串口读数据(接收)wire rs485_wdata;       //串口写数据(发送)wire      rs485_wbusy;       //写忙reg         rs485_de;          //485芯片的DE控制0 输入 1输出reg         rs485_wreq = 1'b0; //串口写请求wire      rs485_tx_start;    //发送启动reg         rs485_tx_start_r = 1'b0; //发送启动寄存reg T_dcnt; //延迟计数器reg rstn_cnt = 12'd0; //复位计数器
//当rs485_de=0,设置rs485内部逻辑准备接收总线数据assign rs485_rx         = (rs485_de == 1'b0) ? I_rs485_rx : 1'b1;//当FIFO中有数据,并且串口发送不忙,启动发送assign rs485_tx_start   = rs485_de&&(rs485_wbusy == 1'b0)&&(!rs485_fifo_empty);//读FIFO使能,用rs485_tx_start的上升沿触发读FIFOassign rs485_fifo_rd_en = (rs485_tx_start_r == 1'b0 & rs485_tx_start == 1'b1);//read fifo enable//当rs485_de=0,设置rs485内部逻辑准备接收总线数据,否则,可以发送数据assign O_rs485_de       = rs485_de;
assign reset_n = rstn_cnt;//上电延迟复位
//复位计数器always @(posedge I_sysclk)begin    if(rstn_cnt == 1'b0)      rstn_cnt <= rstn_cnt + 1'b1;    else      rstn_cnt <= rstn_cnt;End
//T_dcnt用于一段时间内,判断接收总线是否空闲always @(posedge I_sysclk or negedge reset_n)begin   if(reset_n == 1'b0)       T_dcnt <= 17'd0;   else if(rs485_rx && (!rs485_fifo_empty))//如果RX总线是高电平,并且FIFO非空(有数据)       T_dcnt <= (T_dcnt == 1'b1) ? T_dcnt : T_dcnt + 1'b1; //计数器累加   else       T_dcnt <= 0; //否则重置归零End
//当rs485_de=0,设置rs485内部逻辑准备接收总线数据,否则,可以发送数据always @(posedge I_sysclk )begin    if(reset_n ==1'b0)//if(rs485_de | reset_n ==1'b0)       rs485_de <= 1'b0;//重置rs485_de,切换到接收    else if((T_dcnt==1'b1)|rs485_wreq|rs485_wbusy)//当T_dcnt延迟计数器到达计数值,或者串口发送控制器正在发送(rs485_wreq,rs485_wbusy有效)       rs485_de <= 1'b1;//设置rs485_de   else   rs485_de <= 1'b0;//设置rs485_deEnd
//打拍寄存一次always @(posedge I_sysclk) rs485_tx_start_r <= rs485_tx_start;
//rs485_wreq,延迟于rs485_fifo_rd_en,1个时钟,用于数据同步always @(posedge I_sysclk) rs485_wreq <= rs485_fifo_rd_en;
//例化FIFO IP,FIFO设置标准模式,用户缓存接收到的帧fifo_generator_0 inst_fifo (.wr_clk(I_sysclk),          //写时钟输入.wr_rst(reset_n == 1'b0),   //写复位.rd_clk(I_sysclk),          //读时钟输入.rd_rst(reset_n == 1'b0),   //读复位.din(rs485_rdata),          //RX接收到的数据.wr_en(rs485_rvalid&(rs485_de==1'b0)), // 写FIFO 使能,当rs485_rvalid有效,写入数据.rd_en(rs485_fifo_rd_en),   //FIFO读使能.dout(rs485_wdata),         //写数据.empty(rs485_fifo_empty)    //FIFO 空);
//例化串口发送模块uiuart_tx#(.BAUD_DIV(SYSCLKHZ/115200 -1)//设置波特率)uiuart_tx_u(.I_clk(I_sysclk),         //系统时钟.I_uart_rstn(reset_n),      //系统复位.I_uart_wreq(rs485_wreq),   //串口发送驱动器发送请求.I_uart_wdata(rs485_wdata), //串口发送,数据.O_uart_wbusy(rs485_wbusy), //串口发送总线忙.O_uart_tx(O_rs485_tx)      //串口发送总线);
//例化串口接收模块uiuart_rx#(.BAUD_DIV(SYSCLKHZ/115200 -1) //设置波特率      )uiuart_rx_u(.I_clk(I_sysclk),         //系统时钟.I_uart_rstn(reset_n),   //系统复位.I_uart_rx(rs485_rx),       //串口接收总线.O_uart_rdata(rs485_rdata), //串口接收数据.O_uart_rvalid(rs485_rvalid)//串口接收数据有效);

endmodule


4 FPGA工程fpga工程的创建过程不再重复米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹01_rtl:放用户编写的rtl代码02_sim:仿真文件或者工程03_ip:放使用到的ip文件04_pin:放fpga的pin脚约束文件或者时序约束文件05_boot:放编译好的bit或者bin文件(一般为空)06_doc:放本一些相关文档(一般为空)5 RTL仿真5.1 仿真测试文件
`timescale 1ns/1ns//定义仿真时间刻度/精度
module uart_top_tb();
localparam      BPS          = 'd115200 ;             //波特率localparam      CLK_FRE    = 'd50_000_000   ;   //系统频率localparam    CLK_TIME   = 'd500_000_000 /CLK_FRE;//计算系统时钟周期,以ns为单位localparam      BIT_TIME   = 'd500_000_000/ BPS ;   //计算出传输每个bit所需要的时间以ns为单位localparam    NUM_BYTES= 3;               //需要发送的BYTES
reg               I_sysclk;         //系统时钟reg         bsp_clk ;   //波特率时钟reg               O_uart_tx;      //uart 数据发送,该信号接入到,FPGA的uart 接收wire      I_uart_rx;      //uart 数据接收,该信号接入到,FPGA的uart 发送reg uart_send_data;   //需要发送的数据reg                uart_send_data_r; //寄存每次需要发送的BYTE
integer i,j;
//例化顶层模块rs485_top rs485_top(.I_sysclk   (I_sysclk),.O_rs485_de   (O_rs485_de),.I_rs485_rx   (O_uart_tx),.O_rs485_tx   (I_uart_rx));//仿真初始化initial begin
//初始化REG寄存器I_sysclk =0;bsp_clk= 0;O_uart_tx= 1;i=0;j=0;
uart_send_data   =0;uart_send_data_r =0;
#40000;//延迟40000ns,等待uart测试代码中的复位延迟
uart_send_data[(0*8) +: 8] = 8'b1001_0101;//初始化需要发送的第1个BYTEuart_send_data[(1*8) +: 8] = 8'b0000_0101;//初始化需要发送的第2个BYTEuart_send_data[(2*8) +: 8] = 8'b1000_0100;//初始化需要发送的第3个BYTE
//uart tx 发送数据for(i=0; i<NUM_BYTES;i=i+1)begin
      uart_send_data_r = uart_send_data[(i*8) +: 8];//寄存需要发送的数据到寄存器      $display("uart_send_data : 0x%h",uart_send_data_r);//打印准备发送的数据
      @(posedge bsp_clk);//发送起始位1bit      O_uart_tx = 1'b0;
      for(j=0;j<8;j=j+1)begin//发送数据8bits      @(posedge bsp_clk);//发送      O_uart_tx = uart_send_data_r;      end
       @(posedge bsp_clk);//发送停止位1bit       O_uart_tx = 1'b1;
end       @(posedge bsp_clk);       #2000000 $finish;            end
always #(CLK_TIME/2) I_sysclk = ~I_sysclk;    //产生主时钟always #(BIT_TIME/2) bsp_clk= ~bsp_clk;       //产生波特率时钟
endmodule


5.2 仿真结果6 下载演示下载程序前,先确保FPGA工程已经编译。6.1硬件连接请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)我们使用的是板卡自带的RS485接口的RS485_0接口,为了方便我们测试,我们使用了RS485转USB的转接线与我们的电脑端相连接,方便我们的通信6.2运行结果
页: [1]
查看完整版本: 2-3-30 基于FPGA实现RS485串口程序收发环路设计