1. 串口发送字节数据——基于FPGA的串口发送数据实验
1. 通用异步收发传输器(universal asynchronous receiver/transmitter, UART)传输一个字节的数据
1.1 设计前的思考
- 首先进行单字节模块设计
- 串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口
- 串口通信,支持不同的波特率,所以需要有一个波特率设置端口
- 根据通信环境选择(容易受到干扰就慢一些,波特率小一些)
- 串口通信的本质就是将8位的并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出
- 串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束
- 控制信号,控制并转串模块什么时候开始工作。什么时候一个数据发送完成?需要有一个发送开始信号,以及一个发送完成信号
1.2 设计开始
- 首先设计Uart_Byte_Tx(单字节发送)模块
选择以下变量作为模块Uart_Byte_Tx输入
- Clk 时钟信号
- Reset_N 复位信号
- Data 数据信号
- Send_En 发送使能
- Baud_Set 波特率设置信号
- Uart_Tx 串口输出
- Tx_Done 字节发送完成信号
使用的变量 - Bps_Dr 波特率
- 时钟设计(默认9600波特率)
条目 | |||||
---|---|---|---|---|---|
Baud_set | 0 | 1 | 2 | 3 | 4 |
Bps_Dr | 9600 | 19200 | 38400 | 57600 | 115200 |
- 注
- 仿真时钟默认为50MHz。
- 先低电平,码字,再高电平。因此需要10个码字。
- 默认状态下为发送高电平
- 发送时序图如下所示
.9.22串口发送总结\图1-1_发送一个字节的时序图.png)
代码如下:
module Uart_Byte_Tx(
Clk,
Reset_N,
Data,
Send_En,
Baud_Set,
Uart_Tx,
Tx_Done
);
input Clk;
input Reset_N;
input [7:0]Data;
input Send_En;
input [2:0]Baud_Set;
output reg Uart_Tx;
output reg Tx_Done;
reg [12: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;
endcase
reg [12:0]Div_cnt;
always@(posedge Clk or negedge Reset_N)
if(!Reset_N)
Div_cnt <= 0;
else if(Send_En)
if(Div_cnt == Bps_Dr - 1)
Div_cnt <= 0;
else
Div_cnt <= Div_cnt + 1'b1;
else
Div_cnt <= 0;
reg [3:0]Bps_cnt;
always@(posedge Clk or negedge Reset_N)
if(!Reset_N)
Bps_cnt <= 0;
else if(Div_cnt == Bps_Dr - 1)begin
if(Bps_cnt == 10)
Bps_cnt <= 0;
else
Bps_cnt <= Bps_cnt + 1'b1;
end
else
Bps_cnt <= Bps_cnt;
always@(posedge Clk or negedge Reset_N)
if(!Reset_N)
Uart_Tx <= 1'b1;
else
case(Bps_cnt)
0: begin
Uart_Tx <= 0;
Tx_Done <= 0;
end
1: Uart_Tx <= Data[0];
2: Uart_Tx <= Data[1];
3: Uart_Tx <= Data[2];
4: Uart_Tx <= Data[3];
5: Uart_Tx <= Data[4];
6: Uart_Tx <= Data[5];
7: Uart_Tx <= Data[6];
8: Uart_Tx <= Data[7];
9: Uart_Tx <= 1'b1;
10: begin
Tx_Done <= 1'b1;
Uart_Tx <= 1'b1;
end
default: Uart_Tx <= 1'b1;
endcase
endmodule
仿真代码:
`timescale 1ns / 1ns
module Uart_Byte_Tx_tb;
reg Clk;
reg Reset_N;
reg [7:0]Data;
reg Send_En;
reg [2:0]Baud_Set;
wire Uart_Tx;
wire Tx_Done;
Uart_Byte_Tx Uart_Byte_Tx(
.Clk(Clk),
.Reset_N(Reset_N),
.Data(Data),
.Send_En(Send_En),
.Baud_Set(Baud_Set),
.Uart_Tx(Uart_Tx),
.Tx_Done(Tx_Done)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_N = 0;
Data = 0;
Send_En = 0;
Baud_Set = 4;
#201;
Reset_N = 1;
#100;
Send_En = 1;
Data = 8'h57;
@(posedge Tx_Done)
Send_En = 0;
#2000;
Send_En = 1;
Data = 8'h75;
@(posedge Tx_Done)
Send_En = 0;
$stop;
end
endmodule
1.3 仿真结果
-
基本上能够传输,但是遇到了一些问题:
-
问题1:在第一个码字发送前,
Send_En
还没有拉高时,Uart_Tx
已经变0。
-
问题2:在第二个码字发送前,
Send_En
已经拉高,但还是等待了一个时间间隔(序号10多持续了一个Bps_cnt
)才开始发送码字。
-
-
问题思考
- 对问题1来说,
Uart_Tx
提前变0是因为在传输片段中,命中了情况case(0)
,因此在Reset_N
拉高之后的一个电平就开始发送。 - 对问题2来说,10多持续了一个时间间隔,是因为
Send_En
在拉高时,Bps_cnt
还是10。
- 对问题1来说,
-
解决方案
- 既然命中
case(0)
,那就从1开始计数 Send_En
在拉高时,让Bps_cnt
为0。
- 既然命中
-
仿真结果
- 成功解决问题1。
- 可以看到
Bps_cnt=11
持续了很短一段时间,当Send_En
拉高时计数已经从0开始。可以认为基本解决问题2。 - 但是由于不再使用
Bps_cnt=0
的情况,导致产生了一个间隔的时间浪费。
- 成功解决问题1。
1.4 仿真后问题的思考及解决
-
不再使用
Bps_cnt=0
,那么可以修改Bps_cnt
计数的起始信号,之前是(Div_cnt == Bps_Dr - 1)
触发,修改为(Div_cnt == 1)
触发。修改之后即可解决。
-
附修改后的源码(添加Bps_Clk)
module Uart_Byte_Tx(
Clk,
Reset_N,
Data,
Send_En,
Baud_Set,
Uart_Tx,
Tx_Done
);
input Clk;
input Reset_N;
input [7:0]Data;
input Send_En;
input [2:0]Baud_Set;
output reg Uart_Tx;
output reg Tx_Done;
reg [12: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;
endcase
reg [12:0]Div_cnt;
always@(posedge Clk or negedge Reset_N)
if(!Reset_N)
Div_cnt <= 0;
else if(Send_En)
if(Div_cnt == Bps_Dr - 1)
Div_cnt <= 0;
else
Div_cnt <= Div_cnt + 1'b1;
else
Div_cnt <= 0;
reg [3:0]Bps_cnt;
wire Bps_Clk;
assign Bps_Clk = (Div_cnt == 1);
always@(posedge Clk or negedge Reset_N)
if(!Reset_N)
Bps_cnt <= 0;
else if(Send_En)begin
if(Bps_Clk)begin
if(Bps_cnt == 11)
Bps_cnt <= 0;
else
Bps_cnt <= Bps_cnt + 1'b1;
end
end
else
Bps_cnt <= 0;
always@(posedge Clk or negedge Reset_N)
if(!Reset_N)begin
Uart_Tx <= 1'b1;
Tx_Done <= 0;
end
else
case(Bps_cnt)
1: begin
Uart_Tx <= 0;
Tx_Done <= 0;
end
2: Uart_Tx <= Data[0];
3: Uart_Tx <= Data[1];
4: Uart_Tx <= Data[2];
5: Uart_Tx <= Data[3];
6: Uart_Tx <= Data[4];
7: Uart_Tx <= Data[5];
8: Uart_Tx <= Data[6];
9: Uart_Tx <= Data[7];
10: Uart_Tx <= 1'b1;
11: begin
Tx_Done <= 1'b1;
Uart_Tx <= 1'b1;
end
default: Uart_Tx <= 1'b1;
endcase
endmodule
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理