我是靠谱客的博主 故意月亮,这篇文章主要介绍09B 串口通信发送的verilog设计与调试,现在分享给大家,希望可以做个参考。

1、题目

设计一个串口模块,以一个字节为单位发送数据

2、代码实现

复制代码
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
`timescale 1ns / 1ns module uart_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; //串口模块8bit发送完毕 再发送一个发送完成信号通知对方 // baud_set=0 就让波特率=9600 //baud_set=1 就让波特率=19200 //baud_set=2 就让波特率=38400 //baud_set=3 就让波特率=115200 5 6 7 位 先留着 //波特率设置 reg [17:0]bps_DR;//一个时钟周期在某波特率下计时器应该计的数 //9600对应5208 最少需要13位 这里用了18位是因为要和div_cnt作比较 always@(*)begin if(!reset_n) bps_DR <= 1000000000/9600/20; else begin case(baud_set)//fpga保留整数 无需考虑除不尽的情况 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; //baud_set其他的情况还是为9600的波特率 endcase end end //每一个时钟周期计数 send_en 为控制信号 为1则开始计数 reg [17:0]div_cnt;//每一个时钟周期计数器 always@(posedge clk or negedge reset_n)begin if (!reset_n) div_cnt <= 0; else if(send_en)begin if(div_cnt == bps_DR-1) div_cnt <= 0; else div_cnt <= div_cnt + 1'b1; end else div_cnt <= 0; end //数据输出模块设计 reg [3:0]bps_cnt;// 时钟信号个数计数器 always@(posedge clk or negedge reset_n)begin if(!reset_n) bps_cnt <= 0; else if (div_cnt == bps_DR-1)begin//当到达一个时钟信号 bps_cnt+1 if(bps_cnt == 11) bps_cnt <= 0; else bps_cnt <= bps_cnt + 1'b1; end else bps_cnt <= 0; end //数据传输模块设计 always@(posedge clk or negedge reset_n) if(!reset_n)begin uart_tx <= 1'b1;//空闲时刻为高电平 tx_done <= 0; end else begin case(bps_cnt) 0:begin uart_tx <= 0;tx_done <= 1'b0;end//发送开始信号并让tx_done为0 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;//stop信号 10: begin uart_tx <= 1; tx_done <= 1'b1;end//发送结束信号并让tx_done为1 default: uart_tx <= 1; endcase end endmodule

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
`timescale 1ns / 1ns module uart_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_tx uart_tx_inst0( .clk(clk), .reset_n(reset_n), .data(data), .send_en(send_en), .baud_set(baud_set), .uart_tx(uart_tx), .tx_done(tx_done) ); //产生周期为20ns的时钟 initial clk=1; always #10 clk =~clk; //对输入进行赋值 initial begin reset_n = 0; data = 0;//刚开始要发的数据也让它等于0 send_en = 0; baud_set = 4; //初始化波特率为115200 速度快11个时钟脉冲下来时间短 #201; reset_n = 1; #100 send_en = 1; data = 8'h57; //0101 0111 #20; @(posedge tx_done);//死循环 一直等待tx_done上升沿的到来 来了就跳出循环 没有来就持续等待 send_en = 0; #2000; $stop; end endmodule

仿真结果如下:

 send_en还是低电平的时候,串口发送的信号已经变成了低电平,不合常理 

发送的第二个信号在send_en为1之后要先以1为发送对象,等待div_cnt到一个新的时钟脉冲,bps_cnt变成11,在bps_cnt为11时,发送对象还是1,所以继续以1为发送对象,等到div_cnt到一个新的时钟脉冲,bps_cnt从1变成0,才开始发送低电平起始位。所以一共用了两个周期才走到低电平的位置。

所以提出解决方法:让bps_cnt也受send_en的控制

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
reg [3:0]bps_cnt;// 时钟信号个数计数器 always@(posedge clk or negedge reset_n)begin if(!reset_n) bps_cnt <= 0; else if (send_en) begin if(div_cnt == bps_DR-1) begin //当到达一个时钟信号 bps_cnt+1 if(bps_cnt == 11) bps_cnt <= 0; else bps_cnt <= bps_cnt + 1'b1; end end else bps_cnt <= 0; end

在stop信号传输中,div_cnt计数到了新的时钟脉冲,div_cnt = 0,bps_cnt=10,于是开始传输高电平同时让tx_done变成高电平。刚一变成高电平,测试平台感应到就立马把send_en赋值为0,send_en=0了,div_cnt还是为0(因为这些操作都是一瞬间的事情,姑且认为div_cnt还没来得及开始计数)且bps_cnt由10变为0,所以在下面的仿真图中可以看见,uart_tx和tx_done立马就变为低电平了,随后就以低电平为传输对象一直传,直到20ns后send_en再次被赋值为1,div_cnt立马重新开始计时,到一个新的时钟脉冲时(div_cnt从0开始到这个新的时钟脉冲这段时间依旧是以0为传输对象,所以这段时间就相当于开始信号),bps_cnt由零变为1,随后就开始传输data[0]

 还是有问题:比如20ns这段空闲状态tx是以低电平为传输对象的,不符合常理

原因是bps_cnt被置零,直接就对应发送start信号,所以把代码做如下修改:(记得把11个脉冲那里改为12个)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
always@(posedge clk or negedge reset_n) if(!reset_n)begin uart_tx <= 1'b1;//空闲时刻为高电平 tx_done <= 0; end else begin case(bps_cnt) 1:begin uart_tx <= 0;tx_done <= 1'b0;end//发送开始信号并让tx_done为0 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;//stop信号 11: begin uart_tx <= 1; tx_done <= 1'b1;end//发送结束信号并让tx_done为1 default: uart_tx <= 1;//其他就为1 高电平 endcase end

但是又出现了之前的问题:要多一个周期后才会开始传输start信号 

 

观察代码可知,计满才加一次(才让bps_cnt变为1)才开始发送start信号,浪费了一位的时间

div_cnt计数到bps_dr需要很长的时间,但是计数到1的时间是可以忽略的(这里的意思是说:原来代码里的div_cnt需要计数一个完整的周期才能将bps_cnt加一,而每一段计数过程都会经过1这个数,所以直接让它跑到1的时候就让bps——cnt加1,节约了很多时间。可能你会想,那这样的话每一个位的时间不就改变了吗?其实并没有,因为div_cnt是每次到bps_dr-1才归零的这个条件没有变。只是之前把div_cnt到了bps_dr-1时当作bps_cnt加1的条件,现在把div_cnt到1时当作bps_cnt加1的条件,同样是一段div_cnt的计数周期仅对应一个数值1.代码改变如下:

仿真结果如下

 接下来是波特率验证,从上图仿真结果来看,发一位需要8.68us 符合115200的波特率

另外:将程序修改为bps_clk来控制的思路

 仿真结果里有一个bps_clk 11个脉冲把tx分为10位

结束!!

后记:没有天生的适合和不适合 事在人为 只要你想 

 

 

最后

以上就是故意月亮最近收集整理的关于09B 串口通信发送的verilog设计与调试的全部内容,更多相关09B内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部