基于FPGA的串口(UART)发送实验
1.串口通信模块设计的目的是用来发送数据的,因此需要一个数据输入端口。
2.串口通信,支持不同的波特率,所以需要有一个波特率设置端口。
3.串口通信的本质就是将8位的并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出。
4.串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束。(Send en)
5.控制信号,控制并转串模块什么时候开始工作,什么时候一个数据发送完成?须有一个发送开始信号,以及一个 发送完成信号。(Tx Done
Bout set 是用来控制波特率,满足不停环境下的应用
##最初版本
源代码:
module uart_byte_tx(
Clk,
Reset_n,
Send_en,
uart_tx,
Baud_set,
Data,
Tx_done
);
input Clk;
input Reset_n;
input Send_en;
input [7:0] Data;
input [2:0] Baud_set; // 8钟不同的波特率
output reg uart_tx;
output reg Tx_done;
//Baud_set = 0 让波特率 = 9600;
//Baud_set = 1 让波特率 = 19200;
//Baud_set = 2 让波特率 = 38400;
//Baud_set =3 让波特率 = 57600;
//Baud_set = 4 让波特率 = 115200;
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;
endcase
reg [17:0] div_cnt; //分频得到基本时钟。 因为波特率范围为 300~115200 最高为1000000000/300/20 18位
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; // 考虑到有 8位Bit流位 和 开始 结束 共十段 ,需要有一个计数器 计数11次
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_cnt <= 0;
else if (bps_cnt == bps_DR - 1)begin
if(bps_cnt == 11)
bps_cnt <= 0 ;
else
bps_cnt <= bps_cnt + 1'b1;
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) begin
uart_tx <= 1'b0;
Tx_done <= 0;
end
else begin
case(bps_cnt)
0: begin uart_tx <= 0; Tx_done <=1'b1; 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 <= Data[8];
10: uart_tx <= 1; //此时的结束信号 需要保持一个位的时间,即在11的时候 才是真正的结束。
11:begin uart_tx <= 1; Tx_done <=1'b1; end //判定在此时才为发送完。 Tx_Done为0时再给拉低。
default : uart_tx <= 1;
endcase
end
endmodule
TB文件:
`timescale 1ns / 1ns
module uart_byte_tx_tb();
reg Clk;
reg Reset_n;
reg Send_en;
reg [7:0] Data;
reg [2:0] Baud_set;
wire Tx_done;
wire uart_tx;
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;
#100;
Data = 8'h57;
Send_en = 1;
#20;
@(posedge Tx_done); // 阻塞语句,意思是: 一直在这里等待Tx_done信号到来才执行下面的语句,否则一直循环。
Send_en =0;
#20000;
Data = 8'h75;
Send_en = 1;
#20;
@(posedge Tx_done);
#20000;
Send_en =0;
$stop;
end
endmodule
仿真结果出现问题:
Reset_n 一直没有拉高,所有修改TB文件:
修改成这样后 再仿真
Send_en拉高之后,要过一位 tx才为0;
检查代码逻辑发现
本来dps_cnt应该为0的时候却不是0,没有及时清零。
bps_cnt也应该受到 Send_en的控制。
修改后再次运行发现。
按照前面的图,tx信号在空闲时应该为高电平 此时却为低电平了。
发现是在此时 bps_cnt变为0时,把tx拉低了。
思考后发现:在空闲时长时间保持为零,而在dps_cnt为0时又刚好命中代码中case语句中的 0
修改 使得dps_cnt从1开始,
再次仿真发现:
还是tx信号还是滞后一个周期。
分析发现
在Send en 为1开始后,
必须要等 div_cnt记满之后 dps_cnt才能加1.
修改为从 div_cnt=1 开始计数。
最终代码:
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;
//Baud_set = 0 就让波特率 = 9600;
//Baud_set = 1 就让波特率 = 19200
//Baud_set = 2 就让波特率 = 38400;
//Baud_set = 3 就让波特率 = 57600;
//Baud_set = 4 就让波特率 = 115200;
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;
endcase
wire bps_clk;
assign bps_clk = (div_cnt == 1);
reg [17:0]div_cnt;
always@(posedge Clk or negedge Reset_n)
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;
reg [3:0]bps_cnt;
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 begin
case(bps_cnt)
1:begin uart_tx <= 0;Tx_done <= 1'b0;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;
11:begin uart_tx <= 1;Tx_done <= 1'b1;end
default:uart_tx <= 1;
endcase
end
endmodule
##串口发送数据任务
使用上面设计的串口发送模块,设计一个数据发送器,每10ms以115200的波特率发送一个数据,每次发送的数据比前一个数据大一(计数器)。
直接例化上面的模块,
创建一个test文件:
module uart_tx_test(
Clk,
Reset_n,
uart_tx,
);
input Clk;
input Reset_n;
output uart_tx;
reg Send_Go;
reg [7:0]Data;
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Reset_n(Reset_n),
.Data(Data),
.Send_Go(Send_Go),
.Baud_set(3'd4),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
reg [24:0]counter;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == 4999999)
counter <= 0;
else
counter <= counter + 1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Send_Go <= 0;
else if(counter == 1)
Send_Go <= 1;
else
Send_Go <= 0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Data <= 0;
else if(Tx_done)
Data <= Data + 1'b1;
endmodule
发现问题:Send_en只有一个高电平
分析代码发现,由于TX_done一直都是高电平,使得Send_en后续一直被拉低。
先修改成:
再次仿真:
Data在此时连续加了三次 直接变成03
因为Tx_done信号持续时间太长,这是因为bps_cnt11 这个信号保持了三个时钟周期,要想办法让他只有一个时钟周期。
分析代码逻辑:
1.当div_cnt1时,产生bps_clk1的脉冲
2.当bps_cnt1时,产生bps_cnt11
3.当bps_cnt11时,产生Tx_done1的高电平信号
4.当Tx_done1时,Send_en被拉低,要等下一个时钟周期才生效。此时Data 为01;
5.而div_cnt往前加1,bps_cnt还是保持在11,则Tx_done也还是保持在高电平。
6.直到下一个时钟周期,检测到Send_en=0;,则把bps_cnt清零,此时Tx_done变为0
修改为:
思考:优化设计
要点 :在DATA——done信号为高出发期间,DATA信号要保持不变。
最终设计:
uart_tx_test:
module uart_tx_test(
Clk,
Reset_n,
uart_tx,
);
input Clk;
input Reset_n;
output uart_tx;
reg Send_Go;
reg [7:0]Data;
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Reset_n(Reset_n),
.Data(Data),
.Send_Go(Send_Go),
.Baud_set(3'd4),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
reg [24:0]counter;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == 4999999)
counter <= 0;
else
counter <= counter + 1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Send_Go <= 0;
else if(counter == 1)
Send_Go <= 1;
else
Send_Go <= 0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Data <= 0;
else if(Tx_done)
Data <= Data + 1'b1;
endmodule
uart_byte_tx:
module uart_byte_tx(
Clk,
Reset_n,
Data,
Send_Go,
Baud_set,
uart_tx,
Tx_done
);
input Clk;
input Reset_n;
input [7:0]Data;
input Send_Go;
input [2:0]Baud_set;
output reg uart_tx;
output reg Tx_done;
//Baud_set = 0 就让波特率 = 9600;
//Baud_set = 1 就让波特率 = 19200
//Baud_set = 2 就让波特率 = 38400;
//Baud_set = 3 就让波特率 = 57600;
//Baud_set = 4 就让波特率 = 115200;
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;
endcase
reg Send_en;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Send_en <= 0;
else if(Send_Go)
Send_en <= 1;
else if(Tx_done)
Send_en <= 0;
reg [7:0]r_Data;
always@(posedge Clk)
if(Send_Go)
r_Data <= Data;
else
r_Data <= r_Data;
wire bps_clk;
assign bps_clk = (div_cnt == 1);
reg [17:0]div_cnt;
always@(posedge Clk or negedge Reset_n)
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;
reg [3:0]bps_cnt;
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)
uart_tx <= 1'b1;
else begin
case(bps_cnt)
1:uart_tx <= 0;
2:uart_tx <= r_Data[0];
3:uart_tx <= r_Data[1];
4:uart_tx <= r_Data[2];
5:uart_tx <= r_Data[3];
6:uart_tx <= r_Data[4];
7:uart_tx <= r_Data[5];
8:uart_tx <= r_Data[6];
9:uart_tx <= r_Data[7];
10:uart_tx <= 1;
11:begin uart_tx <= 1;end
default:uart_tx <= 1;
endcase
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Tx_done <= 0;
else if((bps_clk == 1) && (bps_cnt == 10))
Tx_done <= 1;
else
Tx_done <= 0;
endmodule
TB:
`timescale 1ns / 1ps
module uart_tx_test_tb();
reg Clk;
reg Reset_n;
wire uart_tx;
uart_tx_test uart_tx_test(
.Clk(Clk),
.Reset_n(Reset_n),
.uart_tx(uart_tx)
);
initial Clk = 1;
always#10 Clk = ~Clk;
initial begin
Reset_n = 0;
#201;
Reset_n = 1;
#50000000;
end
endmodule