FPGA之串口UART通信
- 1. UART发送模块(Transmitter)
- 2. UART接收模块(Receiver)
- 3. testbench
- 4. 边沿检测电路
串口(Universal Asynchronous Receiver/Transmitter,UART)是一种串行通信协议,用于异步通信。它广泛应用于计算机和嵌入式系统中,用于设备之间的数据传输。以下是UART的一些关键特性和工作原理:
- 异步通信:UART通信是异步的,这意味着发送器和接收器使用不同的时钟信号,数据传输不需要同步时钟线。
- 数据格式:UART通信可以配置不同的数据位(通常是7或8位)、停止位(1或2位)和奇偶校验位。
- 波特率:UART通信的速率以波特率(Baud Rate)来衡量,表示每秒传输的信号单元数。常见的波特率有9600、19200、38400、115200等。
- 串行数据传输:UART使用两条线进行通信:一条用于发送(TX),另一条用于接收(RX)。
- 帧结构:一个UART数据帧通常包括起始位、数据位、奇偶校验位(可选)和停止位。
- 起始位:每个数据帧开始时,发送器将信号线从高电平拉低,表示数据帧的开始。
- 数据位:紧接着起始位,发送器发送数据帧的实际数据。
- 奇偶校验位:如果启用,这是一个额外的位,用于检测数据中1的个数是奇数还是偶数。
- 停止位:数据帧的最后是停止位,可以是1个或2个,将信号线拉高,表示数据帧的结束。
- 流控制:UART可以支持硬件流控制(如RTS/CTS)或软件流控制(如XON/XOFF)。
- 中断和DMA:UART通常可以通过中断或直接内存访问(DMA)与主机处理器通信,以减少处理器负载。
- 配置和控制:UART设备通常有一组寄存器,用于配置波特率、数据格式、流控制等参数。
在嵌入式系统设计中,UART通常用于以下目的:
与计算机或其他设备进行串行通信。
调试和日志记录,通过UART发送调试信息。
控制台接口,用于输入和输出命令。
与其他串行设备(如传感器、调制解调器等)通信。
UART是一种成熟且广泛支持的通信协议,几乎所有的微控制器和计算机系统都提供UART接口。在软件层面,操作系统和编程语言通常提供UART通信的库和API,使得UART通信的实现变得简单。
常用脚位为Pin2、3、5。
注意这里需要11个时钟才能够确认到是否已经完全收完数据。
UART(Universal Asynchronous Receiver/Transmitter,通用异步接收器/发送器)是一种全双工串行通信协议,用于异步通信。UART使用一对数据线(TX和RX)实现双向通信,无需共享时钟线,数据逐位传输。并且可以通过一个波特率设置端口支持不同的波特率。其本质为,将8位的并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出。串口通信通过控制端口的信号以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束。当收到发送完成的响应信号时再开始下一个开始信号的发送。
对于串口接收电路,其实质为采样。通俗来讲,通过对一位数据进行多次采样,统计得到高电平出现的次数,次数多的就是该位的电平值。起始位检测,通过边沿检测电路。
在Verilog中实现一个简单的UART模块,我们通常需要两个主要部分:发送模块(Transmitter)和接收模块(Receiver)。以下是这两个模块的基本实现示例:
1. UART发送模块(Transmitter)
module uart_tx(
input wire clk, // 时钟信号
input wire rst_n, // 复位信号(低电平有效)
input wire [7:0] data, // 要发送的数据
input wire start, // 开始发送信号
input wire [4:0]band_set, //波特率
output reg tx, // UART发送线
output reg busy // 忙信号,表示发送是否完成
);
//
// 300:10^9/300/20=166666=EDB6
//115200:8680ns/20ns=434次计数=1B2
reg [10:0] counter; // 波特率计数器,主要用来分频
reg [3:0] bit_index; // 位索引,用于跟踪当前发送的位
reg [17:0] bps_DR;
always@(*)
case(Baud_set)
0:bps_DR=1000000000/9600/20;
1:bps_DR=1000000000/19200/20;
2:bps_DR=1000000000/38400/20;
3:bps_DR=1000000000/57600/20;
4:bps_DR=1000000000/115200/20;
default:bps_DR=1000000000/9600/20;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx <= 1; // 复位时,将发送线置为高电平
busy <= 0;
counter <= 0;
bit_index <= 0;
end
else begin
if (start && !busy) begin
busy <= 1; // 标记为忙
counter <= 0; // 重置计数器
tx <= 0; // 发送起始位
bit_index <= 0;
end
else if (busy) begin
if (counter >= (bps_DR-1)) begin
counter <= 0;
if (bit_index < 8) begin
tx <= data[bit_index]; // 发送数据位
bit_index <= bit_index + 1;
end
else begin
tx <= 1; // 发送停止位
bit_index <= 0;
if (data == 8'hFF) begin
busy <= 0; // 如果发送的数据是0xFF,表示发送完成
end
end
end
else begin
counter <= counter + 1;
end
end
end
end
endmodule
2. UART接收模块(Receiver)
module uart_rx(
input wire clk, // 时钟信号
input wire rst_n, // 复位信号(低电平有效)
input wire rx, // UART接收线
input wire [4:0]band_set, //波特率控制
output reg [7:0] data, // 接收到的数据
output reg busy, // 忙信号,表示接收是否完成
output reg start // 开始接收信号
);
reg [10:0] counter; // 波特率计数器
reg [3:0] bit_index; // 位索引,用于跟踪当前接收的位
reg [7:0] temp_data; // 临时数据寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data <= 8'b0;
busy <= 0;
start <= 0;
counter <= 0;
bit_index <= 0;
temp_data <= 8'b0;
end
else begin
if (!start && rx == 0) begin
start <= 1; // 检测到起始位
counter <= 0;
bit_index <= 0;
temp_data <= 8'b0;
end
else if (start && counter >= (bps_DR-1)) begin
counter <= 0;
if (bit_index < 8) begin
temp_data[bit_index] <= rx; // 存储数据位
bit_index <= bit_index + 1;
end else begin
data <= temp_data; // 更新接收到的数据
start <= 0;
busy <= 1; // 标记为忙
if (rx == 1) begin
busy <= 0; // 停止位检测到,接收完成
end
end
end else if (start) begin
counter <= counter + 1;
end
end
end
endmodule
请注意,这两个模块都是基于一些假设实现的,例如波特率设置用户设置。在实际应用中,你可能需要根据具体的硬件和需求来调整这些参数。此外,这些模块没有实现奇偶校验。
3. testbench
为了测试上面给出的 UART 发送模块(uart_tx)和接收模块(uart_rx),需要编写一个测试平台(testbench),用于生成激励信号并观察模块的响应。下面是一个简单的 Verilog 测试平台示例:
`timescale 1ns / 1ps //单位/精度,比如1.1
module uart_testbench;
// 测试平台的参数
parameter CLK_PERIOD = 20; // 时钟周期为20纳秒,对应频率为50MHz
// 测试平台的信号
reg clk;
reg rst_n;
reg [7:0] data_to_send;
wire tx;
wire busy_tx;
wire tx_start;
wire rx;
wire busy_rx;
wire rx_start;
wire band_set;
// 实例化 UART 发送模块
uart_tx uut_tx(
.clk(clk),
.rst_n(rst_n),
.data(data_to_send),
.start(tx_start),
.tx(tx),
.busy(busy_tx),
.band_set(band_set_tx)
);
// 实例化 UART 接收模块
uart_rx uut_rx(
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.data(rx_data),
.busy(busy_rx),
.start(rx_start),
.band_set(band_set_rx)
);
// 时钟生成
always #(CLK_PERIOD/2) clk = ~clk;
// 初始化测试平台
initial begin
// 初始化信号
clk = 0;
rst_n = 0;
data_to_send = 8'hAA; // 初始数据
tx_start = 0;
rx = 1; // 初始时,接收线为高电平(空闲状态)
band_set_tx = 4;
band_set_rx = 4;
// 等待几个时钟周期以应用复位
#100;
rst_n = 1; // 释放复位
// 等待直到发送模块准备好发送数据
@(posedge busy_tx);
#10;
tx_start = 1; // 触发发送操作
@(posedge busy_tx); // 等待发送完成
// 检查接收模块是否接收到了正确的数据
#10;
if (rx_data == data_to_send) begin
$display("Test Passed: Received data is correct.");
end else begin
$display("Test Failed: Received data is incorrect.");
end
// 结束仿真
$finish;
end
// 监视变量的变化
initial begin
$monitor("Time = %t, rst_n = %b, data_to_send = %x, tx = %b, busy_tx = %b, rx = %b, rx_data = %x, busy_rx = %b, tx_start = %b, rx_start = %b",
$time, rst_n, data_to_send, tx, busy_tx, rx, rx_data, busy_rx, tx_start, rx_start);
end
endmodule
这个测试平台(testbench)完成了以下任务:
- 定义了时钟周期和信号。
- 实例化了 UART 发送模块和接收模块。
- 生成了周期性的时钟信号。
- 初始化了所有信号,并应用了复位。
- 释放复位后,设置了要发送的数据,并触发了发送操作。
- 观察发送和接收模块的行为,并检查接收到的数据是否正确。
- 使用
$monitor
来监视信号的变化,使用$display
来打印测试结果。 - 如果测试通过,则打印通过信息;如果失败,则打印失败信息,并指出接收到的数据不正确。
- 最后,使用
$finish
结束仿真。
4. 边沿检测电路
在UART通信中,起始位是一个逻辑“0”信号,它标志着一个数据帧的开始。通常,UART的数据线在空闲时保持高电平状态,因此起始位的检测可以通过检测数据线从高电平到低电平的边沿来实现。
以下是一个简单的Verilog代码示例,实现了起始位的边沿检测电路:
module uart_start_bit_detector(
input wire clk, // 时钟信号
input wire rst_n, // 复位信号(低电平有效)
input wire rx, // UART接收线
output reg start_bit_detected // 起始位检测到的信号
);
// 用于存储上一个时钟周期的rx线状态
reg last_rx;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 同步复位
last_rx <= 1'b1; // 假设数据线在复位时是空闲的(高电平)
start_bit_detected <= 1'b0;
end else begin
// 捕获上一个周期的rx状态
last_rx <= rx;
// 检测从高到低的边沿,表示起始位的开始
if (last_rx && !rx) begin
start_bit_detected <= 1'b1; // 起始位检测到
end else if (rx) begin
start_bit_detected <= 1'b0; // 如果rx线再次变高,清除检测信号
end
end
end
endmodule
这段代码使用了一个寄存器last_rx
来存储上一个时钟周期的rx
线状态。在每个时钟上升沿,它会检查rx
线是否从高电平变为低电平,如果是,就表示检测到了起始位的开始,将start_bit_detected
信号置为高电平。如果rx
线再次变为高电平,表示起始位已经结束,将start_bit_detected
信号清除。
请注意,这个简单的边沿检测电路没有实现任何同步机制或滤波器,这在实际硬件设计中可能需要以避免误触发。此外,该电路假设rx
信号在复位时是高电平,这应与系统的其他部分一致。在实际应用中,可能还需要进一步的逻辑来处理数据帧的其他部分,例如数据位、奇偶校验位和停止位。
如果需要将1位数据分成16段,舍弃前5段和后4段,取中间7段进行采样。即原本为一个波特率下的一位为一个bpsk,则现在需要16个bpsk,即,10^9 /9600/16 为一个采样的时间,则对应一个波特率的计数次数为10 ^ 9/9600/16 /20, 20ns为一个时钟周期
数据格式:UART通信的数据帧包括起始位、数据位、奇偶校验位(可选)、停止位。数据位通常为7或8位,停止位可以是1或2位。
波特率:数据传输速率以波特率(Baud Rate)表示,定义为每秒传输的位数。常见的波特率有9600、19200、38400、115200等。
起始位:数据传输开始时,发送器将信号线从高电平拉低,表示数据帧的开始。
数据位:起始位之后,发送器发送实际数据,可以是7位或8位,通常按照最低有效位(LSB)先发送的顺序。
奇偶校验位:如果启用,该位用于校验数据中1的个数,可以是奇校验或偶校验。
停止位:数据传输结束,发送器将信号线拉高,表示数据帧的结束。停止位可以是1位或2位,用于提供数据帧之间的间隔。
空闲状态:UART线上没有数据传输时,信号线保持高电平状态。
传输方向:数据可以从高位(MSB)开始传输,也可以从低位(LSB)开始传输。
帧间隔:数据帧与帧之间的间隔,可以以位或时间计量。
抗干扰能力:UART使用共模电压进行数据传输,抗干扰能力相对较差,因此传输距离有限。
配置参数:通信双方必须约定波特率、数据位宽、奇偶校验位、停止位等配置参数,以确保正确通信。
UART广泛应用于嵌入式系统和计算机之间,以及其他设备间的通信,因其简单、低成本而被广泛使用。在实际应用中,UART可以支持硬件流控制(如RTS/CTS)或软件流控制(如XON/XOFF),以提高数据传输的可靠性。
参考:
https://www.bilibili.com/video/BV1va411c7Dz?p=17&spm_id_from=pageDriver&vd_source=179014f1a2f3078fc78ff0659a14acb9