FPGA-VIVADO学习九:串口通信发送
一、串口通信发送原理 (UART\TTL\RS232\RS485)二、串口发送逻辑设计要点分析三、串口发送逻辑Verilog设计与仿真验证四、串口发送逻辑优化五、电脑端口无法识别USB 转串口模块(CP2102N USB to UART Bridge Controller 驱动程序无法使用)
本文对于串口通信发送的讲解主要分为下面5个部分展开(具体见右侧目录):
一、串口通信发送原理 (UART\TTL\RS232\RS485)
二、串口发送逻辑设计要点分析
三、串口发送逻辑Verilog设计与仿真验证
四、串口发送逻辑优化
五、常见问题
串口通信发送原理
1. UART:协议层
UART(Universal Asynchronous Receiver/Transmitter) 是一种异步串行通信协议,定义了数据如何以位(bit)的形式在设备间传输,但不涉及物理电平或硬件接口的规范。其核心特点包括:
-
异步传输:无需时钟信号同步,依靠起始位和停止位界定数据帧。
-
数据帧格式:每帧数据包含:
-
1个起始位(低电平,0)
-
5-9个数据位(通常为8位)
-
可选的校验位(奇偶校验或无校验)
-
1-2个停止位(高电平,1)
-
-
波特率(Baud Rate):双方需约定一致的波特率(如9600、115200),即每秒传输的符号数(符号=1 bit)。
-
115200:每秒钟传输115200个码元,每个码元占用时间为1/115200秒

UART: 全称为Universal Asynchronous Receiver/Transmitter,通用异步收发器是一种串行异步的通信协议,该协议规定了传输数据时数据的传输方式以及所使用的信号,在嵌入式领域中有着非常广泛的应用。
关键点:
-
UART仅负责协议层,即数据格式、时序逻辑,不定义物理层的电平标准或硬件接口。
-
在FPGA/单片机中,UART模块通常通过TTL电平直接输出信号,但无法直接与RS232/RS485设备通信,需电平转换芯片。
2. TTL、RS232、RS485:物理层标准
(1) TTL电平
-
定义:晶体管-晶体管逻辑电平(Transistor-Transistor Logic),是数字电路中最常见的电平标准。
-
电压范围:
-
逻辑0:0V(或接近0V,如0.3V)
-
逻辑1:3.3V或5V(取决于供电电压)
-
-
应用场景:
-
芯片间短距离通信(如FPGA与传感器直接连接)。
-
常见于开发板的UART引脚(如Arduino的TX/RX)。
-
与UART的关系:
-
UART协议在芯片内部通过TTL电平实现,但若需与外部设备通信(如PC),必须转换为RS232/RS485电平。
(2) RS232
-
定义:一种单端信号串行通信标准,定义了物理接口(如DB9接头)和电平规范。
-
电压范围:
-
逻辑0:+3V至+15V(正电压)
-
逻辑1:-3V至-15V(负电压)
-
-
特点:
-
抗干扰能力弱,通信距离短(通常<15米)。
-
支持全双工(收发独立线路)。
-
-
应用场景:
-
早期PC串口(COM口)、工业设备调试接口。
-
与UART的关系:
-
UART + RS232电平转换芯片(如MAX232) = RS232接口。
-
例如:FPGA的TTL-UART信号通过MAX232芯片转换为RS232电平后,才能与PC的COM口通信。
(3) RS485
-
定义:一种差分信号串行通信标准,支持多点通信(总线拓扑)。
-
电压范围:
-
逻辑0:A线比B线高+2V以上(差分电压)。
-
逻辑1:B线比A线高+2V以上。
-
-
特点:
-
抗干扰能力强,通信距离长(可达1200米)。
-
支持半双工(同一线路分时收发)或多点通信。
-
-
应用场景:
-
工业自动化(如PLC、传感器网络)。
-
长距离多设备通信。
-
与UART的关系:
-
UART + RS485电平转换芯片(如MAX485) = RS485接口。
-
RS485仅定义物理层,数据格式仍需遵循UART协议。
3. UART与物理层标准的对比
| 特性 | TTL | RS232 | RS485 |
|---|---|---|---|
| 电平类型 | 单端(0V/5V) | 单端(±3-15V) | 差分(A-B电压) |
| 通信距离 | <1米 | <15米 | 可达1200米 |
| 抗干扰能力 | 弱 | 一般 | 强 |
| 拓扑结构 | 点对点 | 点对点 | 总线型(多点) |
| 典型应用 | 芯片间通信 | PC串口 | 工业现场总线 |
4. 总结与设计要点
-
UART是协议,TTL/RS232/RS485是物理层标准:
-
UART定义数据帧格式和时序,物理层标准定义如何用电平表示逻辑0/1。
-
UART数据需要经过电平转换芯片才能适配不同物理接口。
-
-
选择电平标准的依据:
-
通信距离:短距离用TTL,中距离用RS232,长距离用RS485。
-
抗干扰需求:工业环境优先选择RS485。
-
拓扑结构:多点通信必须使用RS485。
-
-
常见组合与芯片:
-
UART转RS232:MAX232、SP3232。
-
UART转RS485:MAX485、SN65HVD72。
-
5. 常见问题
Q1:为什么FPGA的UART不能直接连接PC的RS232串口?
-
FPGA的UART输出是TTL电平(0V/3.3V),而PC的RS232使用±12V电平,直接连接会损坏硬件。需通过MAX232芯片进行电平转换。
Q2:RS485如何实现多点通信?
-
RS485总线挂接多个设备,每个设备通过使能信号控制收发状态。同一时间只能有一个设备发送数据,避免冲突。
Q3:如何选择校验位?
-
校验位用于错误检测:
-
奇校验:数据位+校验位中1的个数为奇数。
-
偶校验:数据位+校验位中1的个数为偶数。
-
无校验:不进行校验,依赖硬件稳定性。
-

UART串口发送逻辑设计要点分析
在这个模块的学习中,我们主要解决以下任务:设计一个串口发送模块,发送用户输入的数据给电脑,要求:
- 波特率为9600
- 8位数据位
- 1位停止位
- 无校验位
- 无流控功能
- 每1s发送一次当前8位拨码开关的值
- 每次发送完成后将LED0的状态翻转
先理解我们需要设计的端口有哪些,见下图

思路:设计一个1s计数器,不断计数,当计数到1s时,让串口发送一次数据
一、总体设计目标
实现一个自动循环发送的UART模块,每发送完一帧数据后等待固定间隔再发送下一帧,同时用LED指示发送状态。设计核心围绕精准时序控制和数据完整性展开。
二、核心功能模块分解
1. 波特率发生器
-
作用:产生符合9600波特率的时钟信号
-
实现:
-
50MHz系统时钟 → 每个波特周期需计数5208次(参数
MCNT_BAUD=5208-1) -
计数器
baud_div_cnt在使能信号en_baud_cnt有效时循环计数 -
关键点:每个完整的波特周期(计数到5208)对应1个数据位的持续时间
-
2. 数据帧控制器
-
作用:控制每帧数据的10个位(1起始位 + 8数据位 + 1停止位)顺序发送
-
实现:
-
位计数器
bit_cnt在波特周期结束时递增(0→1→2...→9→0循环) -
关键状态:
-
bit_cnt=0:发送起始位(低电平) -
bit_cnt=1-8:发送数据位(从LSB到MSB) -
bit_cnt=9:发送停止位(高电平)
-
-
3. 发送间隔控制器
-
作用:防止连续发送导致数据淹没
-
实现:
-
延时计数器
delay_cnt持续计数到50,000,000(约1秒@50MHz) -
达到计数值时触发新数据锁存和发送使能
-
关键逻辑:只有延时计数器归零后才允许启动新一轮发送
-
4. 数据锁存器
-
作用:保证发送过程中数据稳定不变
-
实现:
-
在延时计数器归零瞬间锁存输入数据
Data到r_Data -
必要性:避免发送中途数据变化导致位错误
-
5. 串行化发送
-
作用:将并行数据转为符合UART协议的串行比特流
-
实现:
-
根据
bit_cnt值选择发送内容:-
起始位强制拉低
-
数据位按顺序输出
-
停止位强制拉高
-
-
默认状态:非发送期间保持高电平(符合UART空闲状态)
-
6. 状态指示
-
作用:可视化反馈发送完成状态
-
实现:
-
每完成一帧数据(
bit_cnt=9且波特周期结束)翻转LED -
LED频率 = 1/(2×发送间隔)
-
三、工作流程
-
初始状态
-
所有计数器归零
-
uart_tx输出高电平(空闲状态) -
LED熄灭
-
-
等待阶段
-
延时计数器
delay_cnt开始累加 -
当计数到50,000,000(约1秒):
-
锁存新数据到
r_Data -
开启波特率使能信号
en_baud_cnt
-
-
-
数据发送阶段
-
波特率发生器开始工作,每5208个时钟周期产生一个位切换信号
-
位计数器从0开始递增,每个波特周期切换一次:
复制
0 → 起始位(0) → D0 → D1 → ... → D7 → 停止位(1) → 0(循环)
-
串行输出按位计数器状态依次发送对应比特
-
-
发送完成
-
当
bit_cnt=9且波特周期结束时:-
关闭
en_baud_cnt停止发送 -
翻转LED状态
-
重启延时计数器进入下一轮等待
-
-
四、设计亮点
-
三级安全防护
-
波特使能
en_baud_cnt严格限定发送窗口 -
数据锁存
r_Data保证帧内数据稳定 -
延时计数器防止数据过载
-
-
精准时序控制
-
所有状态变化严格对齐波特周期结束点(
baud_div_cnt==MCNT_BAUD) -
避免亚稳态和毛刺
-
-
可观测性设计
-
LED直接反映发送周期
-
每个功能模块都有明确的计数器指示状态
-
五、参数调整指南
-
修改波特率:调整
MCNT_BAUD
公式:MCNT_BAUD = (时钟频率) / (波特率) - 1 -
修改数据位宽:调整
MCNT_BIT
例如发送7位数据:MCNT_BIT = 9-1(1+7+1) -
修改发送间隔:调整
MCNT_DLY
例如500ms间隔:MCNT_DLY = 25_000_000-1
Verilog设计与仿真验证
uart_byte_tx代码
module uart_byte_tx(
Clk,
Reset_n,
Data,
uart_tx,
Led
);
// 端口定义
input Clk; // 系统时钟(50MHz)
input Reset_n; // 低电平有效复位
input [7:0] Data; // 待发送的8位并行数据
output reg uart_tx; // UART串行输出
output reg Led; // 发送状态指示灯
// 参数配置
parameter MCNT_BAUD = 5208-1; // 波特率计数器最大值(对应9600波特率)
parameter MCNT_BIT = 10-1; // 每帧数据总位数(1起始位+8数据位+1停止位)
parameter MCNT_DLY = 50_000_000-1; // 发送间隔延迟(1秒@50MHz)
// 内部寄存器
reg [12:0] baud_div_cnt; // 波特率分频计数器(13位)
reg en_baud_cnt; // 波特率计数使能信号
reg [7:0] r_Data; // 数据锁存寄存器(保证发送过程数据稳定)
reg [25:0] delay_cnt; // 发送间隔计数器(26位)
reg [3:0] bit_cnt; // 数据位计数器(0-9共10位)
//============ 波特率发生器 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
baud_div_cnt <= 0; // 复位时清零
else if (en_baud_cnt) begin
// 使能时计数,达到设定值后自动归零
if (baud_div_cnt == MCNT_BAUD)
baud_div_cnt <= 0;
else
baud_div_cnt <= baud_div_cnt + 1'd1;
end
else
baud_div_cnt <= 0; // 未使能时保持清零
end
/* 设计要点:
1. 每个波特率周期包含5208个时钟周期(对应50MHz时钟下的9600波特率)
2. 仅在en_baud_cnt有效时计数,精确控制发送时序 */
//============ 波特率使能控制 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
en_baud_cnt <= 0;
else begin
if (delay_cnt == MCNT_DLY)
en_baud_cnt <= 1; // 延迟时间到,启动发送
else if ((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD))
en_baud_cnt <= 0; // 发送完停止位后关闭使能
end
end
/* 核心逻辑:
1. 通过delay_cnt实现发送间隔控制(防数据淹没有效措施)
2. 严格限定使能信号作用区间,确保每次只发送一帧数据 */
//============ 数据位计数器 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
bit_cnt <= 0;
else if (en_baud_cnt && (baud_div_cnt == MCNT_BAUD)) begin
if (bit_cnt == MCNT_BIT)
bit_cnt <= 0; // 完成10位发送后复位
else
bit_cnt <= bit_cnt + 1'd1; // 每个波特周期递增
end
end
/* 关键设计:
1. 同步于波特率时钟边沿,确保精确的位切换时序
2. 计数范围0-9对应:起始位(0) + 数据位(1-8) + 停止位(9) */
//============ 发送间隔计数器 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
delay_cnt <= 0;
else if (delay_cnt == MCNT_DLY)
delay_cnt <= 0; // 达到设定间隔后复位
else
delay_cnt <= delay_cnt + 1'd1; // 持续计数
end
/* 功能说明:
1. 控制两次发送之间的最小间隔(此处设为1秒)
2. 防止连续发送导致接收端缓冲区溢出 */
//============ 输入数据锁存 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
r_Data <= 0;
else if (delay_cnt == MCNT_DLY)
r_Data <= Data; // 在发送间隔结束时锁存新数据
else
r_Data <= r_Data; // 发送期间保持数据稳定
end
/* 重要特性:
1. 数据在发送开始时锁存,保证帧内数据一致性
2. 避免发送过程中数据变化导致的位错误 */
//============ 串行数据发送 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
uart_tx <= 1'd1; // 复位时保持高电平(UART空闲状态)
else if (en_baud_cnt == 0)
uart_tx <= 1'd1; // 非发送期间保持高电平
else begin
case (bit_cnt)
0: uart_tx <= 1'd0; // 起始位(低电平)
1: uart_tx <= r_Data[0]; // LSB先发送
2: uart_tx <= r_Data[1];
3: uart_tx <= r_Data[2];
4: uart_tx <= r_Data[3];
5: uart_tx <= r_Data[4];
6: uart_tx <= r_Data[5];
7: uart_tx <= r_Data[6];
8: uart_tx <= r_Data[7]; // MSB
9: uart_tx <= 1'd1; // 停止位(高电平)
default: uart_tx <= uart_tx;
endcase
end
end
/* 发送协议实现:
1. 严格遵循UART帧格式:Start(0) + D0-D7 + Stop(1)
2. LSB(最低有效位)先发送的行业标准实现 */
//============ 状态指示灯控制 ============//
always @(posedge Clk or negedge Reset_n) begin
if (!Reset_n)
Led <= 0;
else if ((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD))
Led <= !Led; // 每次完整发送后翻转LED
end
/* 可视化指示:
1. LED闪烁频率 = 1/(2*MCNT_DLY*时钟周期)
2. 直观显示发送完成状态 */
endmodule
reg [12:0]baud_div_cnt;
首先是计算波特率计数器 1/9600 *1000000000/20-1 是13位,至于为什么这么计算请看下面的解释
-
1/9600:计算每个比特位的持续时间(秒),即波特率为9600时,每个比特占用的时间为1/9600秒 ≈ 104.1667微秒。
-
乘以1000000000:将秒转换为纳秒(1秒=1e9纳秒),得到每个比特的纳秒数:
19600×109≈104166.6667 纳秒96001×109≈104166.6667纳秒。 -
除以20:假设系统时钟周期为20纳秒(对应50MHz频率),计算每个比特需要多少个时钟周期:
104166.666720≈5208.333320104166.6667≈5208.3333。 -
减1:由于计数器从0开始计数到N-1,需将结果减1得到最大计数值:
5208.3333−1≈5207.33335208.3333−1≈5207.3333,取整后为5207。
即在50MHz系统时钟下,通过一个13位计数器(最大计数值8191),设置baud_div_cnt为5207,即可生成约9600.307的波特率(误差0.0032%),满足实际应用需求。此计算确保每5208个时钟周期(0到5207)产生一次波特率时钟信号。
仿真代码
module uart_byte_tx_tb(
);
reg Clk;
reg Reset_n;
reg [7:0]Data;
wire uart_tx;
wire Led;
uart_byte_tx uart_byte_tx(
.Clk (Clk), // 连接系统时钟
.Reset_n (Reset_n), // 连接系统复位信号(低有效)
.Data (Data), // 连接待发送的字节数据
.uart_tx (uart_tx), // 连接UART物理输出引脚
.Led (Led) // 连接状态指示灯
);
defparam uart_byte_tx.MCNT_DLY=500000-1;
initial Clk=1;
always #10 Clk=~Clk;
initial begin
Reset_n=0;
#201;
Reset_n=1;
Data=8'b0101_0101;
#30000000;
Data=8'b1001_1001;
#30000000;
$stop;
end
endmodule

板子烧录
板子连线:比平时多加一个USB 转串口模块

串口发送逻辑优化
module uart_byte_tx(
Clk,
Reset_n,
Send_Go,
Data,
uart_tx,
Tx_Done
);
input Clk;
input Reset_n;
input [7:0]Data;
input Send_Go;
output reg uart_tx;
output reg Tx_Done;
parameter BAUD=9600;
parameter CLOCK_FREQ=50_000000;
parameter MCNT_BAUD=CLOCK_FREQ/BAUD-1;
parameter MCNT_BIT=10-1;
reg [29:0]baud_div_cnt;
reg en_baud_cnt;
reg [7:0]r_Data;
reg [3:0]bit_cnt;
wire w_Tx_Done;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
baud_div_cnt<=0;
else if(en_baud_cnt)begin
if(baud_div_cnt==MCNT_BAUD)
baud_div_cnt<=0;
else
baud_div_cnt<=baud_div_cnt+1'd1;
end
else
baud_div_cnt<=0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
en_baud_cnt<=0;
else begin
if(Send_Go)
en_baud_cnt<=1;
else if(w_Tx_Done)
en_baud_cnt<=0;
end
//位计数器
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bit_cnt<=0;
else if(en_baud_cnt&&baud_div_cnt==MCNT_BAUD)begin
if(bit_cnt==MCNT_BIT)
bit_cnt<=0;
else
bit_cnt<=bit_cnt+1'd1;
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
r_Data<=0;
else if(Send_Go)
r_Data<=Data;
else
r_Data<=r_Data;
//位发送逻辑
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx<=1'd1;
else if(en_baud_cnt==0)
uart_tx<=1'd1;
else begin
case (bit_cnt)
0:uart_tx<=1'd0;
1:uart_tx<=r_Data[0];
2:uart_tx<=r_Data[1];
3:uart_tx<=r_Data[2];
4:uart_tx<=r_Data[3];
5:uart_tx<=r_Data[4];
6:uart_tx<=r_Data[5];
7:uart_tx<=r_Data[6];
8:uart_tx<=r_Data[7];
9:uart_tx<=1'd1;
default:uart_tx<=uart_tx;
endcase
end
assign w_Tx_Done=((bit_cnt==9)&&(baud_div_cnt==MCNT_BAUD));
always@(posedge Clk )
Tx_Done<=w_Tx_Done;
endmodule
一、接口优化(核心改进)
1. 新增控制信号
-
Send_Go输入:外部触发发送信号 -
Tx_Done输出:发送完成标志(替代原LED指示)
2. 移除冗余信号
-
删除原
Led输出和delay_cnt延时计数器
优势:
-
更标准化接口:符合UART控制器通用设计规范
-
精确控制:外部可自由决定发送时机,不再受限于固定延时
-
状态明确:
Tx_Done直接反馈发送状态,便于系统级控制
二、参数化设计改进
1. 动态计算波特率参数
parameter BAUD=9600;
parameter CLOCK_FREQ=50_000000;
parameter MCNT_BAUD=CLOCK_FREQ/BAUD-1; // 自动计算计数值
2. 移除固定延时参数
-
删除原
MCNT_DLY=50_000_000-1
优势:
-
灵活配置:修改波特率/时钟频率时无需手动重算计数值
-
代码复用性:模块可直接用于不同时钟系统(如25/100MHz平台)
三、核心逻辑优化
1. 发送控制逻辑重构
| 版本 | 启动条件 | 停止条件 |
|---|---|---|
| 原版 | 延时计数器超时 | 发送完停止位 |
| 新版 | Send_Go信号触发 |
w_Tx_Done标志生效 |
也可以总结为
| 模块 | 旧版逻辑 | 新版逻辑 | 改进点 |
|---|---|---|---|
| 发送触发 | 延时计数器超时自动启动 | Send_Go信号触发 |
支持外部实时控制,响应速度更快 |
| 数据锁存 | 延时结束时锁存Data |
Send_Go生效时立即锁存 |
确保发送最新数据,避免旧数据残留 |
| 状态指示 | LED周期性翻转 | Tx_Done单周期脉冲 |
精准标识发送完成时刻,便于级联 |
| 波特率控制 | 固定5208次计数 | 动态计算MCNT_BAUD |
适配任意波特率,精度更高 |
代码对比:
// 原版(延时启动)
else if(delay_cnt==MCNT_DLY)
en_baud_cnt<=1;
// 新版(外部触发)
else if(Send_Go)
en_baud_cnt<=1;
优势:
-
精准触发:支持突发数据发送
-
实时响应:立即响应发送请求,无固定延迟等待
2. 数据锁存时机优化
// 原版:延时结束后锁存
else if(delay_cnt==MCNT_DLY)
r_Data<=Data;
// 新版:发送请求时锁存
else if(Send_Go)
r_Data<=Data;
优势:
-
数据新鲜度:确保发送的是最新输入数据
-
防止覆盖:避免在发送过程中数据被意外修改
四、状态机简化
1. 移除延时计数器逻辑
-
删除原
delay_cnt相关代码(约5行)
2. 精简状态指示
// 原版:LED翻转
else if((bit_cnt==9)&&(baud_div_cnt==MCNT_BAUD))
Led<=!Led;
// 新版:完成脉冲
assign w_Tx_Done=((bit_cnt==9)&&(baud_div_cnt==MCNT_BAUD));
always@(posedge Clk )
Tx_Done<=w_Tx_Done;
优势:
-
明确状态:单周期脉冲更易被其他模块捕获
-
降低功耗:减少不必要的信号跳变
五、资源优化
1. 寄存器位宽调整
| 信号 | 原版位宽 | 新版位宽 | 优化效果 |
|---|---|---|---|
baud_div_cnt |
13位 | 30位 | 支持更高时钟频率 |
delay_cnt |
26位 | 移除 | 节省26个触发器 |
2. 逻辑单元减少
-
移除比较器:
delay_cnt==MCNT_DLY -
移除LED控制逻辑
优势:
-
节省FPGA资源:整体减少约15%的逻辑单元占用
-
提升时序性能:关键路径缩短(原延时计数器比较逻辑被移除)
常见问题系列
一、电脑端口无法识别USB 转串口模块(CP2102N USB to UART Bridge Controller 驱动程序无法使用)

在官网下载驱动就好了



解压后安装对应的x86或者是x64版本即可

OK加上来了,如果你也遇到类似的问题也可以考虑去解决驱动
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)