基于RS-485接口的芯片的FPGA驱动程序
1.简介
ADM3485E 是一款 3.3V 低功耗数据收发器,具有 ±15kV 的 ESD(静电放电) 保护,专为多点总线传输线上的半双工通信设计。它支持平衡数据传输,符合 TIA/EIA 标准 RS-485 和 RS-422 的要求。作为一款半双工收发器,ADM3485E 采用共享差分线路设计,提供独立的驱动器和接收器启用输入,确保高效的数据传输与接收控制。这款收发器在高噪声环境下具有出色的抗干扰性能,非常适合工业自动化、通信设备及其他需要可靠数据传输的应用场景。其框架如下所示:

根据这个架构,可以推测出 ADM3485E 的工作原理。其由接收器(R)和发射器(D)两部分组成,A 和 B 是差分信号。在接收信号时,差分信号(A、B)首先进入差分放大器,放大器通过比较 A 和 B 的电压差,判断信号是高电平还是低电平。在此过程中,RE_n 是接收端差分放大器的使能信号,用于控制接收功能的启用与禁用。发射器部分的工作原理类似,数据输入到差分驱动器后,驱动器根据控制信号生成差分信号 A 和 B,从而实现信号的发送。在此过程中,DE是发送端差分放大器的使能信号,用于控制发送功能的启用与禁用。常见器件连接方式如下:
这里说明一下,FPGA的普通IO引脚可以直接用于UART通信的TX、RX信号。但是RS-485的不行,需要转换电平,也就是使用类似ADM3485E 这样的转换芯片,原因如下:FPGA通常采用单端TTL信号,而RS-485通过差分电压差值传递逻辑信号,因此必须进行信号转换。可能有人会考虑使用FPGA的LVDS(低电压差分信号),但实际上并不适用。虽然LVDS和RS-485都属于差分信号,但它们的电气特性存在显著差异:
(1)电压差范围不同:LVDS的典型电压差为350-1200mV,而RS-485要求200mV-2V。虽然范围有部分重叠,但LVDS的电压差偏小,无法完全满足RS-485标准。
(2)应用场景差异:LVDS专为短距离高速传输设计,具有低电压差和低功耗特性;而RS-485则针对远距离通信优化,较大的电压差使其具备更强的抗干扰能力和更长的传输距离。
在长距离传输时,LVDS较小的电压差容易导致信号衰减,影响通信可靠性。因此,两种标准具有不同的应用场景,不能直接替代使用。
2.引脚说明
ADM3485引脚如图2所示。

下面是引脚说明:
IO | 类型 | 功能 |
RO | 输出-数字引脚 | 接收器输出端,根据差分信号A,B来输入高低电平 |
RE_n | 输出-数字引脚 | 接收器输出使能,低电平有效 |
DE | 输出-数字引脚 | 发送器输出使能,高电平有效 |
DI | 输入-数字引脚 | 发送器的数据输入端 |
GND | 模拟引脚 | 地 |
A | 模拟引脚 | 差分传输线+(正向) |
B | 模拟引脚 | 差分传输线-(反向) |
VCC | 模拟引脚 | 供电电源 |
在电路设计中,通常会将RE_n,DE这两个使能信号短接作为一个引脚RT,低电平代表接收使能,高电平是发送使能,如图3所示。这样既可以实现控制通信的功能,也可以减少IO引脚的使用。

3.RS485协议
由于RS485协议的时序较为常见,因此ADM3485E器件手册中并未专门列出时序图。起初,我以为RS485会有独特的时序,但在查阅相关资料后,我发现RS485的时序与UART协议的时序实际上是相同的。这是因为UART协议本身并不定义具体的电气特性,它仅规定了数据传输的时序格式(如起始位、数据位、校验位和停止位)。换句话说,UART负责将并行数据转换为串行数据,而信号的实际传输则是由外部驱动电路来实现。
电信号的传输过程遵循不同的电平标准和接口规范。对于异步串行通信,常见的接口标准包括RS232、RS422和RS485等,它们定义了各自的电气特性。例如,RS232是单端信号传输,而RS422/485则采用差分信号传输。
总结来说,UART是一种协议标准,主要负责数据的串并转换;而RS485则是一种物理接口标准,专注于信号的传输方式和电气特性。虽然RS485和UART的时序相同,但它们分别代表了不同层次的协议:UART作为数据格式的定义,RS485作为接口规范。
UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如图4所示。

各组成成分的详细信息如下表所示。
字段 | 描述 | 大小 | 备注 |
起始位 (Start Bit) | 用于标识数据帧的开始。 | 1位 | 一般为逻辑低电平 (0),表示数据的开始。因为空闲时是高电平,所以有低电平时可以识别出来。 |
数据位 (Data Bits) | 传输的有效数据。 | 5-9位(常见为8位) | 数据位数量可配置,常用的是8位数据。 |
校验位 (Parity Bit) | 用于错误检测,可以选择偶校验、奇校验或无校验。 | 1位(可选) | 校验位帮助检测数据传输中的错误。 |
停止位 (Stop Bit) | 标识数据帧的结束。 | 1位、1.5位或2位 | 停止位可选,常见的是1位或2位。 |
空闲位 (Idle Bit) | 在没有数据传输时,线路处于的状态。 | 无 | 当UART线路空闲时,它处于逻辑高电平 (1)。 |
4.verilog代码
根据ADM3485E工作的原理可知,要想实现对ADM3485E的通信控制,主要是实现UART协议帧的发送和接收。 通过根据UART协议帧的结构,并配合接收和发送使能信号,即可实现对ADM3485E的通信控制。需要发送的时候开启发送使能,不发送的时候关闭发送使能。所以先实现uart发送和接收,再在外层加个控制逻辑就可以了。
uart_tx的verilog代码:
/* * file : uart_tx.v* author : yuluo_lhw* date : 2025-09-01* version : v1.0* description : uart tx :8bit data 无校验位 1位停止位*/
module uart_tx(
input wire clk, //采样时钟
input wire rst_n,
input wire [7:0] data,
input wire tx_en, //上升沿有效output reg tx,
output reg tx_done);
parameter CLK_FREQ = 100_000_000; //时钟频率
parameter BAUDRATE = 115200 ;
localparam BAUD_CNT = CLK_FREQ/BAUDRATE; //为得到指定波特率,对系统时钟计数 BPS_CNT 次,也就是BPS_CNT个CLK发送一个bit. 868.0wire tx_en_pos ;
reg tx_en_do ;
reg tx_en_d1 ;
reg [9:0] baud_cnt ;
reg [3:0] bit_cnt ;
reg tx_busy ; //1:进入发送bit阶段 0:空闲
//tx_en
assign tx_en_pos = ~tx_en_d1& tx_en_do ;
always@(posedge clk or negedge rst_n)beginif(~rst_n)begintx_en_do <= 0 ;tx_en_d1 <= 0 ;endelse begintx_en_do <= tx_en ;tx_en_d1 <= tx_en_do ;end
end//tx_busy
always@(posedge clk or negedge rst_n)beginif(~rst_n)begintx_busy <= 0 ;endelse beginif(tx_en_pos) begin tx_busy <= 1 ;endelse if(bit_cnt>=9 && (baud_cnt>=BAUD_CNT-1) ) tx_busy <= 0 ; //发送玩停止位结束else tx_busy <= tx_busy ;end
end//baud_cnt
always@(posedge clk or negedge rst_n)beginif(~rst_n)beginbaud_cnt <= 0 ;endelse beginif(tx_busy) begin if(baud_cnt>=BAUD_CNT-1) baud_cnt <= 0 ;else baud_cnt <= baud_cnt + 1 ;endelse baud_cnt <= 0 ;end
end//bit_cnt
always@(posedge clk or negedge rst_n)beginif(~rst_n)beginbit_cnt <= 0 ;endelse beginif(tx_busy) begin if(baud_cnt==BAUD_CNT-1) bit_cnt <= bit_cnt + 1 ;else bit_cnt <= bit_cnt ;endelse bit_cnt <= 0 ;end
end//tx
always@(posedge clk or negedge rst_n)beginif(~rst_n)begintx <= 1 ;endelse beginif(tx_busy) begin case(bit_cnt)4'd0: tx <= 0 ; //停止位4'd1: tx <= data[0] ; //小端传送4'd2: tx <= data[1] ; 4'd3: tx <= data[2] ; 4'd4: tx <= data[3] ; 4'd5: tx <= data[4] ; 4'd6: tx <= data[5] ; 4'd7: tx <= data[6] ; 4'd8: tx <= data[7] ; 4'd9: tx <= 1 ; //停止位default : tx <= 1 ;endcaseend else tx <= 1 ;end
end
//tx_done
always@(posedge clk or negedge rst_n)beginif(~rst_n)begintx_done <= 1 ;endelse beginif(tx_busy) begin tx_done <= 0 ;endelse tx_done <= 1 ;end
end
endmodule
仿真代码:
`timescale 1ns / 1psmodule tb_uart_tx;reg clk;
reg rst_n;
reg [7:0] data;
reg tx_en;wire tx;
wire tx_done;uart_tx #(
.CLK_FREQ (100_000_000),
.BAUDRATE (115200 )
) u_uart_tx(.clk(clk),.rst_n(rst_n),.data(data),.tx_en(tx_en),.tx(tx),.tx_done(tx_done)
);always #5 clk = ~clk ;initial beginclk = 0 ;rst_n = 0;tx_en = 0;//data1data = 8'b01010101; #20 rst_n = 1; #10 tx_en = 1;#10 tx_en = 0;@(posedge tx_done); //等待上升沿//wait(tx_done == 1);//data2#10 data = 8'b11000011; #10 tx_en = 1;#10 tx_en = 0;@(posedge tx_done); //等待上升沿#50;$finish;
end// Monitor signals
initial begin$monitor("At time %t, tx = %b, tx_done = %b, data = %b", $time, tx, tx_done, data);
endendmodule
从以下两图数据可以看出,每个比特的传输需要 868 个时钟周期。因此,在 100MHz 时钟频率下,每秒可传输的数据量为 100,000,000/868 ≈ 115,200 比特,该结果与预期相符。
如下图所示,数据帧的发送格式符合预期:起始位为0,随后是8位数据位(采用小端传输,先发送低位),接着是1位停止位1,最后恢复为空闲状态。
接收端的代码也按照UART协议帧来写就可以了,只要检测到rx的下降沿就开始接收数据,不过rx最好进行打拍,避免出现亚稳态。这里就不粘贴了。
5.参考资料
ADI-ADM3485E.pdf
以上就是本次学习的记录。欢迎加我为好友(QQ:235840795),一起交流与学习!