2025.9.14知识笔记

Sep 14 2025 每日笔记

1、串口基本知识

按照一个完整的字节包括一位起始位、8位数据位、一位停止位即总共十位数据来算,要想完整的实现这十
位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传输的起始,第 11 个脉冲标记一次传输的结束。

bps_clk 信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的 2 到 9 个上升沿,发送 8 个数据位,第 10 个上升沿到第 11 个上升沿为停止位的发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UART 发送一个字节时序图- 精细模拟
+-----------------------------------------------------------------------------+
| |
| 波特率时钟 (bps_clk): |
| __ __ __ __ __ __ __ __ __ __ __ |
| clk: | |__| |__| |__| |__| | | |__| |__| |__| |__| |__| |__ |
| l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11 |
| |
| 发送数据线 (uart_tx): |
| |
| | START | BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7| STOP |
| tx: |_______|______|______|______|______|______|______|______|_____|______|
| |<----------------------- 10 位数据 ----------------------->| |
| |
| 时钟动作: |
| ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ |
| 开始 位01234567 停止 结束 |
| |
+-----------------------------------------------------------------------------+

串口输出的时候,并转串,由并行数据,转化为串行数据一位一位发出去

1
2
3
4
5
6
7
8
9
10
输入 (8位)
0 ───────────────┐
1 ───────────────┤
1 ───────────────┤
0 ───────────────┤
0 ───────────────┤ ┌────────────┐
0 ───────────────┼───┤ 处理单元 ├─── 输出波形
1 ───────────────┤ └────────────┘ ▁▁▁▂▃▅▆▇▇▆▅▃▂▁
0 ───────────────┤
0 ───────────────┘

2、串口的设计思想

1、串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口

2、串口通信,支持不同的波特率,所以需要有一个波特率设置端口

3、串口通信的本质就是将8位的并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出

4、串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束。

5、控制信号,控制并转串模块什么开始工作。什么时候一个数据发送完成?须有一个发送开始信号,以及一个发送完成信号。

3、波特率与时钟的关系

1、这些计算好的分频系数值会写入到模块内部的 bps_DR寄存器中。模块中的分频计数器会一直数到 bps_DR的值,然后归零并产生一个短暂的脉冲,这个脉冲就是​​波特率时钟​​。每个波特率时钟脉冲到来时,模块就向外发送一个比特的数据。

2、波特率计算​​:在您设计的串口发送模块中,通过 baud_set[2:0]输入信号选择不同的分频系数。该系数由公式 ​​分频值 = (50MHz系统时钟 / 目标波特率) - 1​​ 计算得出并预设在模块中。例如,选择9600波特率时,分频系数为5207,模块内部计数器每数5207个系统时钟,就产生一个控制时钟脉冲来发送1比特数据。

3、与串口发送的关系​​:​​波特率直接决定了串口发送数据的“节奏”​​。每个波特率时钟脉冲到来时,发送模块就向 uart_tx线输出一个比特。波特率越高,脉冲间隔越短,发送速度越快。通信双方必须设置相同的波特率,接收方才能以同样的节奏采样,否则会导致数据错乱,通信失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 波特率分频系数设置模块
// 功能:根据系统复位状态和波特率选择信号,设置合适的分频系数值

always@(posedge clk or posedge reset) // 敏感列表:时钟上升沿或复位信号上升沿触发
if(reset) // 如果复位信号有效(高电平)
bps_DR <= 16'd5207; // 将分频系数重置为默认值5207(对应9600波特率)
else begin // 复位无效时
case(baud_set) // 根据波特率选择信号baud_set的值进行分支处理
0: bps_DR <= 16'd5207; // 设置波特率为9600bps(50MHz时钟下的分频系数)
1: bps_DR <= 16'd2603; // 设置波特率为19200bps
2: bps_DR <= 16'd1301; // 设置波特率为38400bps
3: bps_DR <= 16'd867; // 设置波特率为57600bps
4: bps_DR <= 16'd433; // 设置波特率为115200bps
default: bps_DR <= 16'd5207;// 默认设置为9600bps(防止未定义状态)
endcase
end
baud_set 波特率 波特率周期 波特率分频计数值 50M系统时钟计数值
0 9600 104167ns 104167 / System_clk_period 5208 - 1
1 19200 52083ns 52083 / System_clk_period 2604 - 1
2 38400 26041ns 26041 / System_clk_period 1302 - 1
3 57600 17361ns 17361 / System_clk_period 868 - 1
4 115200 8680ns 8680 / System_clk_period 434 - 1

4、总体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 模块定义开始,声明所有输入输出端口
module uart_byte_tx(
clk, // 系统时钟输入,50MHz
reset_n, // 低电平有效的异步复位信号

data_byte, // 待发送的8位并行数据输入
send_en, // 发送使能信号,高电平脉冲启动一次发送
baud_set, // 波特率选择信号,3位宽,支持8种波特率选择

uart_tx, // 串行数据输出线
tx_done, // 发送完成标志,一帧数据发送完成后产生高脉冲
uart_state // 发送状态指示,高电平表示正在发送中
);

// 输入端口声明
input clk ;
input reset_n;
input [7:0]data_byte;
input send_en;
input [2:0]baud_set;

// 输出端口声明(定义为寄存器类型,便于在always块中赋值)
output reg uart_tx;
output reg tx_done;
output reg uart_state;

// 内部信号定义
wire reset=~reset_n; // 将低有效复位转换为高有效复位,简化后续逻辑
localparam START_BIT = 1'b0; // 起始位常量定义(低电平)
localparam STOP_BIT = 1'b1; // 停止位常量定义(高电平)

reg bps_clk; // 波特率时钟脉冲信号
reg [15:0]div_cnt; // 分频计数器,用于产生波特率时钟
reg [15:0]bps_DR; // 分频计数最大值,决定波特率
reg [3:0]bps_cnt; // 比特计数器,计数当前发送的是第几位(0-11)
reg [7:0]data_byte_reg; // 数据字节寄存器,用于锁存待发送数据

// 状态机控制逻辑:管理模块的发送状态
always@(posedge clk or posedge reset)
if(reset)
uart_state <= 1'b0; // 复位时处于空闲状态
else if(send_en) // 收到发送使能信号
uart_state <= 1'b1; // 进入发送状态
else if(bps_cnt == 4'd11) // 已发送完11位(起始位+8数据位+停止位)
uart_state <= 1'b0; // 返回空闲状态
else
uart_state <= uart_state; // 保持当前状态

// 数据锁存逻辑:在发送开始时锁存数据,防止发送过程中数据变化
always@(posedge clk or posedge reset)
if(reset)
data_byte_reg <= 8'd0; // 复位时清零
else if(send_en) // 在发送使能有效时
data_byte_reg <= data_byte; // 锁存输入数据
else
data_byte_reg <= data_byte_reg; // 保持数据不变

// 波特率设置逻辑:根据baud_set选择分频系数
always@(posedge clk or posedge reset)
if(reset)
bps_DR <= 16'd5207; // 复位默认9600波特率
else begin
case(baud_set) // 根据选择设置不同波特率的分频值
0:bps_DR <= 16'd5207; // 9600bps @50MHz
1:bps_DR <= 16'd2603; // 19200bps
2:bps_DR <= 16'd1301; // 38400bps
3:bps_DR <= 16'd867; // 57600bps
4:bps_DR <= 16'd433; // 115200bps
default:bps_DR <= 16'd5207; // 默认9600bps
endcase
end

// 分频计数器:对系统时钟进行分频以产生波特率时钟基准
always@(posedge clk or posedge reset)
if(reset)
div_cnt <= 16'd0; // 复位时计数器清零
else if(uart_state)begin // 仅在发送状态下计数
if(div_cnt == bps_DR) // 达到分频最大值
div_cnt <= 16'd0; // 归零重新计数
else
div_cnt <= div_cnt + 1'b1; // 计数器加1
end
else
div_cnt <= 16'd0; // 空闲状态时计数器保持为0

// 波特率时钟生成逻辑:在特定时刻产生一个时钟周期的脉冲
always@(posedge clk or posedge reset)
if(reset)
bps_clk <= 1'b0; // 复位时输出低电平
else if(div_cnt == 16'd1) // 当计数器计数到1时(避开0时刻的竞争冒险)
bps_clk <= 1'b1; // 产生一个高电平脉冲
else
bps_clk <= 1'b0; // 其他时刻为低电平

// 比特计数器:对发送的位数进行计数(0-11共12个状态)
always@(posedge clk or posedge reset)
if(reset)
bps_cnt <= 4'd0; // 复位时清零
else if(bps_cnt == 4'd11) // 已计数到11(完成一帧发送)
bps_cnt <= 4'd0; // 归零准备下一次发送
else if(bps_clk) // 当波特率时钟脉冲到来时
bps_cnt <= bps_cnt + 1'b1; // 计数器加1,准备发送下一位
else
bps_cnt <= bps_cnt; // 无脉冲时保持计数值不变

// 发送完成标志生成逻辑
always@(posedge clk or posedge reset)
if(reset)
tx_done <= 1'b0; // 复位时标志为0
else if(bps_cnt == 4'd11) // 当发送完第11位时
tx_done <= 1'b1; // 产生一个时钟周期的高脉冲
else
tx_done <= 1'b0; // 其他时刻为0

// 串行数据发送逻辑:根据当前计数状态输出相应的比特位
always@(posedge clk or posedge reset)
if(reset)
uart_tx <= 1'b1; // 复位时输出高电平(空闲状态)
else begin
case(bps_cnt) // 根据比特计数器的状态输出相应位
0:uart_tx <= 1'b1; // 空闲状态或帧间间隔
1:uart_tx <= START_BIT; // 发送起始位(低电平)
2:uart_tx <= data_byte_reg[0]; // 发送数据位0(LSB,最低有效位)
3:uart_tx <= data_byte_reg[1]; // 发送数据位1
4:uart_tx <= data_byte_reg[2]; // 发送数据位2
5:uart_tx <= data_byte_reg[3]; // 发送数据位3
6:uart_tx <= data_byte_reg[4]; // 发送数据位4
7:uart_tx <= data_byte_reg[5]; // 发送数据位5
8:uart_tx <= data_byte_reg[6]; // 发送数据位6
9:uart_tx <= data_byte_reg[7]; // 发送数据位7(MSB,最高有效位)
10:uart_tx <= STOP_BIT; // 发送停止位(高电平)
default:uart_tx <= 1'b1; // 默认输出高电平(空闲状态)
endcase
end

endmodule
// 模块定义结束

5、testbench测试文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
`timescale 1ns/1ns
`define CLK_PERIOD 20

module uart_byte_tx_tb;

reg clk;
reg reset_n;
reg [7:0]data_byte;
reg send_en;
reg [2:0]baud_set;

wire uart_tx;
wire tx_done;
wire uart_state;

uart_byte_tx uart_byte_tx(
.clk(clk),
.reset_n(reset_n),

.data_byte(data_byte),
.send_en(send_en),
.baud_set(baud_set),

.uart_tx(uart_tx),
.tx_done(tx_done),
.uart_state(uart_state)
);

initial clk = 1;
always#(`CLK_PERIOD/2)clk = ~clk;

initial begin
reset_n = 1'b0;
data_byte = 8'd0;
send_en = 1'd0;
baud_set = 3'd4;
#(`CLK_PERIOD*500 + 1 )
reset_n = 1'b1;
#(`CLK_PERIOD*50);

//send first byte
data_byte = 8'haa;
send_en = 1'd1;
#`CLK_PERIOD;
send_en = 1'd0;

@(posedge tx_done)
#(`CLK_PERIOD*5000);

//send second byte
data_byte = 8'h55;
send_en = 1'd1;
#`CLK_PERIOD;
send_en = 1'd0;

@(posedge tx_done)
#(`CLK_PERIOD*5000);
$stop;
end

endmodule

代码解析:

6、测试文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/////////////////////////////////////////////////////////////////////////////
// 模块名称:uart_tx_test
// 功能描述:UART自动测试模块,周期性地发送递增数据
// 创建日期:2023-XX-XX
// 设计者:XXX
// 版本:v1.0
/////////////////////////////////////////////////////////////////////////////

module uart_tx_test(
input clk, // 系统时钟输入,50MHz
input reset_n, // 低电平有效的异步复位信号

output uart_tx, // UART串行数据输出
output led // 发送状态指示灯(高电平表示正在发送)
);

// 参数定义
parameter MCNT = 49_999_999; // 1秒定时计数值(50MHz时钟下计数50,000,000次)

// 寄存器定义
reg [7:0] data_byte; // 待发送的8位数据
reg send_en; // 发送使能信号
reg [25:0] cnt; // 26位定时计数器(最大可计数67,108,863)

// 连线定义
wire tx_done; // 发送完成标志(来自uart_byte_tx模块)

//=============================================
// 1秒定时计数器
// 功能:实现精确的1秒定时
//=============================================
always@(posedge clk or negedge reset_n)
if(!reset_n)
cnt <= 25'd0; // 复位时计数器清零
else if(cnt == MCNT)
cnt <= 25'd0; // 计数达到1秒时归零
else
cnt <= cnt + 1'b1; // 正常计数

//=============================================
// 发送使能信号生成
// 功能:每1秒产生一个单周期的发送使能脉冲
//=============================================
always@(posedge clk or negedge reset_n)
if(!reset_n)
send_en <= 1'b0; // 复位时禁止发送
else if(cnt == MCNT)
send_en <= 1'b1; // 在1秒时刻产生发送使能
else
send_en <= 1'b0; // 其他时间保持低电平

//=============================================
// 自动递增数据生成
// 功能:每次发送完成后数据自动加1
//=============================================
always@(posedge clk or negedge reset_n)
if(!reset_n)
data_byte <= 8'b0; // 复位时数据清零
else if(tx_done) // 检测到发送完成信号
data_byte <= data_byte + 1'b1; // 数据递增
else
data_byte <= data_byte; // 其他时间保持数据不变

//=============================================
// UART发送模块实例化
// 功能:实现实际的串口数据发送
//=============================================
uart_byte_tx uart_byte_tx(
.clk(clk), // 连接系统时钟
.reset_n(reset_n), // 连接复位信号

.data_byte(data_byte), // 连接待发送数据
.send_en(send_en), // 连接发送使能
.baud_set(3'd0), // 固定9600波特率(baud_set=0)

.uart_tx(uart_tx), // 串行数据输出
.tx_done(tx_done), // 发送完成标志
.uart_state(led) // 发送状态指示(连接LED)
);

endmodule