1:串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口
2:串口通信,支持不同的波特率,所以需要一个波特率设置端口
3:串口通信的本质就是将8位并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出
4:串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,在1位的高电平标志传输的结束
5:控制信号,控制并转串模块什么时候开始工作;什么时候一个数据发送完成?须有一个发送开始信号,以及一个发送完成信号
注:图中少了一个波特率设置端口
1:波特率为300起对应的计算公式为1000 000 000/300=3 333 333ns
3 333 333ns/20=166 666次 对应的是18位,所以一般有关波特率设置的端口设置的最大值为18位计数
源代码
module uart_byte_tx(
input [7:0]Data,
input Send_en,
input Clk,
input Reset_n,
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@(*)begin
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
end
wire bps_clk;
assign bps_clk = (div_cnt == 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(Send_en)begin
if(bps_clk)begin
if(bps_cnt==11)
bps_cnt<=0;
else
bps_cnt<=bps_cnt+1;
end
end
else
bps_cnt<=1'b1;
end
//并串转换
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)begin
uart_tx<=1'b0;
Tx_done<=1'b0;
end
else begin
case(bps_cnt)
1:begin uart_tx<=1'b0;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'b1;
11:begin uart_tx<=1'b1;Tx_done=1'b1; end
default: uart_tx<=1'b1;
endcase
end
end
endmodule
测试文件
`timescale 1ns / 1ns
module uart_byte_tx_tb(
);
reg [7:0]Data;
reg Send_en;
reg Clk;
reg Reset_n;
reg [2:0]Baud_set;
wire uart_tx;
wire Tx_done;
uart_byte_tx uart_byte_tx(
.Data(Data),
.Send_en(Send_en),
.Clk(Clk),
.Reset_n(Reset_n),
.Baud_set(Baud_set),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
initial Clk=0;
always#10 Clk=!Clk;
initial begin
Reset_n=0;
Send_en=0;
Data=0;
Baud_set=4;
#201;
Reset_n=1;
Data=8'h57;
Send_en=1;
#20;
//下面的意思是一直在等待Tx_done变量,没有等到的话就不往下进行
@(posedge Tx_done);
Send_en=0;
#20000;
Data=8'h75;
Send_en=1;
#20;
@(posedge Tx_done);
Send_en=0;
#20000;
$stop;
end
endmodule
收获:
1:在有多级if else的时候要记得加上begin end 否则可能造成识别不出来,导致结果错误~
2:在主代码中为了模拟实际电路中的赋值情况,可以设置#10代表延时时间,上面记得加`timescale 1ns/1ns,因为在下载到电路板上以及vivado识别的时候会自动略去这里的延时,但是这么写对实际电路仿真的时候却又非常有用
题目:使用上面的串口发送模块,设计一个数据发送器,每10ms以115200的波特率发送一个数据,每次发送的数据比前一个数据大一(计数器)
uart_byte_tx代码
module uart_byte_tx(
input [7:0]Data,
input Send_Go,
input Clk,
input Reset_n,
input [2:0]Baud_set,
output reg uart_tx,
output reg Tx_done
);
reg Send_en;
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Send_en<=0;
else if(Send_Go)
Send_en<=1;
else if(Tx_done)
Send_en <= 0;
end
reg [7:0]r_Data;
always@(posedge Clk)begin
if(Send_Go)
r_Data<=Data;
else
r_Data<=r_Data;
end
//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@(*)begin
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
end
wire bps_clk;
assign bps_clk = (div_cnt == 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(Send_en)begin
if(bps_clk)begin
if(bps_cnt==11)
bps_cnt<=0;
else
bps_cnt<=bps_cnt+1;
end
end
else
bps_cnt<=1'b0;
end
//并串转换
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)begin
uart_tx<=1'b0;
end
else begin
case(bps_cnt)
1:uart_tx<=1'b0;
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'b1;
11: uart_tx<=1'b1;
default: uart_tx<=1'b1;
endcase
end
end
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Tx_done<=1'b0;
else if((bps_cnt==10)&&(bps_clk==1))
Tx_done<=1'b1;
else
Tx_done<=1'b0;
end
endmodule
uart_tx_test代码顶层模块
module uart_tx_test(
input Clk,
input Reset_n,
output uart_tx
);
reg [7:0]Data;
reg Send_Go;
wire Tx_done;
uart_byte_tx uart_byte_tx(
.Data(Data),
.Send_Go(Send_Go),
.Clk(Clk),
.Reset_n(Reset_n),
.Baud_set(3'd4),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
//首先计时10ms
reg [18:0] cnt;
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
cnt<=0;
else if(cnt==499999)
cnt<=0;
else
cnt<=cnt+1'b1;
end
//接着设计何时Send_Go开始发送数据
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Send_Go<=0;
else if(cnt==1)
Send_Go<=1;
//这里需要注意本来的Send_en是一段时间的信号,比较长,但是Send_Go属于脉冲信号就一下
else
Send_Go<=0;
end
//接着设计数据变化
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)
Data<=0;
else if(Tx_done)
Data<=Data+1;
end
endmodule
uart_tx_test_tb测试文件
`timescale 1ns / 1ns
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=0;
always #10 Clk=!Clk;
initial begin
Reset_n=0;
#201;
Reset_n=1;
#50000000;
$stop;
end
endmodule
收获
1:对于一些很重要的控制信号最好单独拿出来写,参杂在一些其他功能中很可能会产生其他的影响,例如Tx_done
2:Go一般是单脉冲信号,en是电平信号
有些人会了8位的串口传送数据但是总会有一些问题例如,
1.ADC,采样的结果是12位的,怎么使用串口发送
2.16位的数据,怎样通过串口发送
3.有多个字节的数据通过串口发送
不能直接将8位改成12位、16位,因为UART规定了,发送的数据位只能有6、7、8位所以应该把大于8位的字节分成多个字节进行发送
三种情况:
1:没有开始发送(上一次的已经发送完成,新的40位数据的发送请求没有出现)
2:来了发送40位数据的请求信号
3:依次发送数据的状态
第一个状态:第一种情况的时候,咱干什么事情?等待传输请求(Trans_Go)的到来,Data40[7:0]给到uart_byte_tx的Data,并同时产生Send_Go信号,启动第一个字节的发送
接着应该等待,等待Tx_Done信号的到来
40位数据是否发完了?发完了,回到第一个状态继续等Trans_Go,没发完,启动下一个8位数据的发送
源代码:
//该模块完成的功能是采用状态机实现多字节数据发送
module uart_tx_data(
input Clk,
input Reset_n,
input [39:0]Data40,
input Trans_Go,//表示合适开始发送数据,即传输请求
output reg Trans_Down,//表示一次40位的数据传输完成
output uart_tx
);
reg [7:0]Data;
reg Send_Go;
wire Tx_done;
uart_byte_tx uart_byte_tx(
.Data(Data),
.Send_Go(Send_Go),
.Clk(Clk),
.Reset_n(Reset_n),
.Baud_set(3'h4),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
reg [2:0]state;
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)begin
state<=0;
Data<=0;
Send_Go<=0;
Trans_Down<=0;
end
//下面部分也可以用case语句实现,记得加default~
else if(state==0)begin
Trans_Down<=0;
if(Trans_Go)begin
Data<=Data40[7:0];
state<=1;
Send_Go<=1;
end
else begin
Data<=Data;
state<=0;
Send_Go<=0;
end
end
else if(state==1)begin
if(Tx_done)begin
Data<=Data40[15:8];
state<=2;
Send_Go<=1;
end
else begin
Data<=Data;
state<=1;
Send_Go<=0;
end
end
else if(state==2)begin
if(Tx_done)begin
Data<=Data40[23:16];
state<=3;
Send_Go<=1;
end
else begin
Data<=Data;
state<=2;
Send_Go<=0;
end
end
else if(state==3)begin
if(Tx_done)begin
Data<=Data40[31:24];
state<=4;
Send_Go<=1;
end
else begin
Data<=Data;
state<=3;
Send_Go<=0;
end
end
else if(state==4)begin
if(Tx_done)begin
Data<=Data40[39:32];
state<=5;
Send_Go<=1;
end
else begin
Data<=Data;
state<=4;
Send_Go<=0;
end
end
else if(state==5)
begin
if(Tx_done)begin
state<=0;
Trans_Down<=1;
Send_Go<=0;
end
else begin
Data<=Data;
state<=5;
Send_Go<=0;
end
end
end
endmodule
测试文件:
`timescale 1ns / 1ns
module uart_tx_data_tb();
reg Clk;
reg Reset_n;
reg [39:0]Data40;
reg Trans_Go;
wire Trans_Down;
wire uart_tx;
uart_tx_data uart_tx_data(
Clk,
Reset_n,
Data40,
Trans_Go,//表示合适开始发送数据,即传输请求
Trans_Down,//表示一次40位的数据传输完成
uart_tx
);
initial Clk=0;
always #10 Clk=!Clk;
initial begin
Reset_n=0;
Data40=0;
Trans_Go=0;
#201
Reset_n=1;
#201
Data40=40'h123456789a;
Trans_Go=1;
@(posedge Trans_Down);
Trans_Go=0;
#201
Data40=40'ha987654321;
Trans_Go=1;
@(posedge Trans_Down);
#200000
$stop;
end
endmodule
任务:
1:优化状态机,实现只要2个或3个状态实现发送的功能,并易于修改为发送任意个字节的数据(加个计数器进行二级验证~)
2:思考不使用状态机实现的方法
任务一代码(已进行仿真)
//该模块完成的功能是采用状态机实现多字节数据发送
module uart_tx_data2(
input Clk,
input Reset_n,
input [39:0]Data40,
input Trans_Go,//表示合适开始发送数据,即传输请求
output reg Trans_Down,//表示一次40位的数据传输完成
output uart_tx
);
reg [7:0]Data;
reg Send_Go;
wire Tx_done;
uart_byte_tx uart_byte_tx(
.Data(Data),
.Send_Go(Send_Go),
.Clk(Clk),
.Reset_n(Reset_n),
.Baud_set(3'h4),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
reg [2:0]state;
//用来记录当前发送到第几个字节了
reg [2:0]cnt;
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)begin
cnt<=0;
end
else if(Tx_done) begin
if(cnt==4)
cnt<=0;
else
cnt<=cnt+1;
end
end
always@(posedge Clk or negedge Reset_n)begin
if(!Reset_n)begin
state<=0;
Data<=0;
Send_Go<=0;
Trans_Down<=0;
end
//下面部分也可以用case语句实现,记得加default~
else if(state==0)begin
Trans_Down<=0;
if(Trans_Go)begin
Trans_Down<=0;
if(Tx_done)begin
case(cnt)
0:begin Data<=Data40[7:0]; state<=0;end
1:begin Data<=Data40[15:8];state<=0;end
2:begin Data<=Data40[23:16];state<=0;end
3:begin Data<=Data40[31:24];state<=0;end
4:begin Data<=Data40[39:32];state<=1;end
default:Data<=Data;
endcase
Send_Go<=1;
end
else begin
Data<=Data;
state<=0;
Send_Go<=0;
end
end
end
else if(state==1)
begin
if(Tx_done)begin
state<=0;
Trans_Down<=1;
Send_Go<=0;
end
else begin
Data<=Data;
state<=1;
Send_Go<=0;
end
end
end
endmodule