软件版本: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。
PIN | 功能 | 功能描述 | 1 | RO | 数据接收输出 | 2 | RE | 接收输出使能,低电平有效 | 3 | DE | 输出使能,高电平有效 | 4 | DI | 数据发送输入 | 5 | GND | 地 | 6 | A | 同相驱动器输出/接收器输入 | 7 | B | 反相驱动器输出/接收器输入 | 8 | VCC | 供电 | 2.2 RS485的应用电路设计实际设计的时候,把RE和DE接到一起,可以减少一个控制管脚,入下图
发送真值表: 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 瞬态抑制器的使用
对于工业场合的工作环境,SMBJ6.0瞬态抑制器,可以抑制RS485总线的浪涌,过压,对RS485芯片起到保护作用。
3RS485的收发环路
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_p,
- input I_sysclk_n,//系统时钟输入
- output O_rs485_de, // 485 de控制,控制输入 输出方向,0 输入 1输出
- input I_rs485_rx, // 485 RX输入总线
- output O_rs485_tx // 485 TX输出总线
- );
- localparam SYSCLKHZ = 100_000_000;
- wire I_clk;
- IBUFGDS CLK_U(
- .I(I_sysclk_p),
- .IB(I_sysclk_n),
- .O(I_clk)
- );
- wire reset_n; //内部上电延迟复位
- wire rs485_rx; //内部RX 接收
- wire rs485_fifo_empty; //FIFO空
- wire rs485_fifo_rd_en; //FIFO 读使能
- wire rs485_rvalid; //读数据有效
- wire [7:0] rs485_rdata; //串口读数据(接收)
- wire [7:0] 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 [17:0] T_dcnt; //延迟计数器
- reg [11:0] 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的上升沿触发读FIFO
- assign 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[11];//上电延迟复位
- //复位计数器
- always @(posedge I_clk)begin
- if(rstn_cnt[11] == 1'b0)
- rstn_cnt <= rstn_cnt + 1'b1;
- else
- rstn_cnt <= rstn_cnt;
- end
- //T_dcnt用于一段时间内,判断接收总线是否空闲
- always @(posedge I_clk 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[17] == 1'b1) ? T_dcnt : T_dcnt + 1'b1; //计数器累加
- else
- T_dcnt <= 0; //否则重置归零
- end
- //当rs485_de=0,设置rs485内部逻辑准备接收总线数据,否则,可以发送数据
- always @(posedge I_clk )begin
- if(reset_n ==1'b0)//if(rs485_de | reset_n ==1'b0)
- rs485_de <= 1'b0;//重置rs485_de,切换到接收
- else if((T_dcnt[17]==1'b1)|rs485_wreq|rs485_wbusy)//当T_dcnt延迟计数器到达计数值,或者串口发送控制器正在发送(rs485_wreq,rs485_wbusy有效)
- rs485_de <= 1'b1;//设置rs485_de
- else
- rs485_de <= 1'b0;//设置rs485_de
- end
- //打拍寄存一次
- always @(posedge I_clk) rs485_tx_start_r <= rs485_tx_start;
- //rs485_wreq,延迟于rs485_fifo_rd_en,1个时钟,用于数据同步
- always @(posedge I_clk) rs485_wreq <= rs485_fifo_rd_en;
- //例化FIFO IP,FIFO设置标准模式,用户缓存接收到的帧
- fifo_generator_0 inst_fifo (
- .wr_clk(I_clk), //写时钟输入
- .wr_rst(reset_n == 1'b0), //写复位
- .rd_clk(I_clk), //读时钟输入
- .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_clk), //系统时钟
- .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_clk), //系统时钟
- .I_uart_rstn(reset_n), //系统复位
- .I_uart_rx(rs485_rx), //串口接收总线
- .O_uart_rdata(rs485_rdata), //串口接收数据
- .O_uart_rvalid(rs485_rvalid)//串口接收数据有效
- );
-
- endmodule
复制代码
4FPGA工程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 = 'd100_000_000 ; //系统频率
- localparam CLK_TIME = 'd1000_000_000 /CLK_FRE;//计算系统时钟周期,以ns为单位
- localparam BIT_TIME = 'd1000_000_000/ BPS ; //计算出传输每个bit所需要的时间以ns为单位
- localparam NUM_BYTES = 3; //需要发送的BYTES
- reg I_sysclk_p;
- reg I_sysclk_n; //系统时钟
- reg bsp_clk ; //波特率时钟
- reg uart_tx; //uart 数据发送,该信号接入到,FPGA的uart 接收
- wire uart_rx; //uart 数据接收,该信号接入到,FPGA的uart 发送
- reg [8*NUM_BYTES-1:0] uart_send_data; //需要发送的数据
- reg [7:0] uart_send_data_r; //寄存每次需要发送的BYTE
- integer i,j;
- //例化顶层模块
- rs485_top rs485_top
- (
- .I_sysclk_p (I_sysclk_p),
- .I_sysclk_n (I_sysclk_n),
- .O_rs485_de (O_rs485_de),
- .I_rs485_rx (uart_tx),
- .O_rs485_tx (uart_rx)
- );
- //仿真初始化
- initial begin
- //初始化REG寄存器
- I_sysclk_p =0;
- I_sysclk_n =1;
- bsp_clk = 0;
- 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个BYTE
- uart_send_data[(1*8) +: 8] = 8'b0000_0101;//初始化需要发送的第2个BYTE
- uart_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
- uart_tx = 1'b0;
- for(j=0;j<8;j=j+1)begin//发送数据8bits
- @(posedge bsp_clk); //发送
- uart_tx = uart_send_data_r[j];
- end
- @(posedge bsp_clk);//发送停止位1bit
- uart_tx = 1'b1;
- end
- @(posedge bsp_clk);
- #2000000 $finish;
- end
-
- always #(CLK_TIME/2) I_sysclk_p = ~I_sysclk_p;
- always #(CLK_TIME/2) I_sysclk_n = ~I_sysclk_n; //产生主时钟
- always #(BIT_TIME/2) bsp_clk = ~bsp_clk; //产生波特率时钟
-
- endmodule
复制代码
5.2仿真结果
6下载演示(硬件不支持,本课不展示下载演示结果)
|