首先时完成以后的效果如图:
观看了小梅哥的讲解和设计思路以后我决定自己重新写一下串口发送模块;
串口发送模块的原理比较简单,只需要考虑波特率和发送的数据长度就可以了。
如上图所示,一般的串口模块就是以8bit一个字节的长度为单位发送数据的,每个数据的开头都由低电平为开始,高电平为结束。那么开始结束的两个bit加上数据的8bit,每发送一次数据至少处理10bit信号。
从整体上先设计逻辑电路,首先需要一个基本的分频计数器,来产生9600Hz的数据脉冲,其次需要一个计数器,计数频率9600Hz,计数每满10就从新计数。
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag5207 <= 0;
else if(conter == 13'd5207)
flag5207 <= 1'b1;
else
flag5207 <= 1'b0;
end
为最基本的计数器分频,产生9600Hz的信号提供给其他部分
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
conter <= 0;
else if(conter == 13'd5207)
conter <= 0;
else
conter <= conter +1'b1;
end
为了后面分频给后面的逻辑电路,让5207计数器每次循环计数到1时就产生一个高电平的使能信号给下面的电路。
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
con <= 0;
else if(flag5207 == 1'b1)
begin
if(con == 4'd11)
con <= 0;
else
con <= con +1'b1;
end
else
con <= con;
end
为了发送一个字节数据需要循环发送起始信号、数据段、结束信号,加在一起一共需要10bit,最后1个bit需要保持高电平防止信号不稳定
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
TX <= 1'b0;
else
begin
case(con)
0:TX <= 1'b0;
1:TX <= 1'b1; //bit0
2:TX <= 1'b0; //bit1
3:TX <= 1'b1; //bit2
4:TX <= 1'b0; //bit3
5:TX <= 1'b1; //bit4
6:TX <= 1'b0; //bit5
7:TX <= 1'b1; //bit6
8:TX <= 1'b0; //bit7
default: TX <= 1'b1;
endcase
end
end
来产生最后的输出信号,需要接收11进制计数器的数据,选择发送从0到11的数据。分别代表了数据起始位和数据位以及数结束位。
这里我实现了基础功能,下载到开发板里以后只能一直循环发送数据“0x55”,转换成ASIC就是“u”。也只以9600这一种波特率发送。
module main(
//input
clk,//模块的时钟 50MHz
rst_n, //系统复位信号
//output
TX, //串口输出信号
conter, //内部计数器
con, //内部计数器
);
input clk;
input rst_n;
output TX;
output [13:0] conter;
output [4:0]con;
wire clk;
wire rst_n;
reg [13:0] conter;
reg [4:0]con;
reg TX;
reg flag5207;
//为最基本的计数器分频,产生9600Hz的信号提供给其他部分
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag5207 <= 0;
else if(conter == 13'd5207)
flag5207 <= 1'b1;
else
flag5207 <= 1'b0;
end
//为了后面分频给后面的逻辑电路,让5207计数器每次循环计数到1时就产生一个高电平的使能信号给下面的电路。
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
conter <= 0;
else if(conter == 13'd5207)
conter <= 0;
else
conter <= conter +1'b1;
end
//11进制计数器为了发送一个字节数据需要循环发送起始信号、数据段、结束信号,
//加在一起一共需要10bit,最后1个bit需要保持高电平防止信号不稳定
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
con <= 0;
else if(flag5207 == 1'b1)
begin
if(con == 4'd13)
con <= 0;
else
con <= con +1'b1;
end
else
con <= con;
end
//来产生最后的输出信号,需要接收11进制计数器的数据,
//选择发送从0到11的数据。分别代表了数据起始位和数据位以及数结束位
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
TX <= 1'b0;
else
begin
case(con)
0:TX <= 1'b0;
1:TX <= 1'b1; //bit0
2:TX <= 1'b0; //bit1
3:TX <= 1'b1; //bit2
4:TX <= 1'b0; //bit3
5:TX <= 1'b1; //bit4
6:TX <= 1'b0; //bit5
7:TX <= 1'b1; //bit6
8:TX <= 1'b0; //bit7
default: TX <= 1'b1;
endcase
end
end
endmodule
`timescale 1ns/1ns
module main_testbench;
reg clk;
reg rst_n;
wire TX;
wire [13:0] conter;
wire [4:0] con;
main main(
.clk(clk),
.rst_n(rst_n),
.TX(TX),
.conter(conter),
.con(con)
);
always #10 clk =~ clk;
initial
begin
clk = 1;
rst_n = 1'b0;
#100
rst_n = 1'b1;
end
endmodule
在原来的基础上增加了可以选择的波特率、模块的控制使能信号、发送一个字节的完成信号。
下面是我改进以后的代码,可以在115200波特率的情况下向串口发送“hello word!”
main.v
module main(
clk,
rst_n,
TX
);
input clk;
input rst_n;
output TX;
wire clk;
wire rst_n;
wire TX;
wire tx_down;
reg [8:0]data;
reg [3:0]flag;
urat_tx urat_tx(
//input
.clk(clk),//模块的时钟 50MHz
.rst_n(rst_n), //系统复位信号
.set_Baud_rate(4'd4),//波特率大小设置
.data(data), //要发送的一个字节数据
.en(1'b1),
//output
.TX(TX), //串口输出信号
.tx_down(tx_down) //发送完成标志,每完成一次发送就产生一段时间高电平
);
always@(posedge tx_down or negedge rst_n)
begin
if (!rst_n)
flag <=0;
else
flag <= flag + 1'd1;
end
always@(posedge tx_down or negedge rst_n)
begin
if(!rst_n)
data <= 0;
else
case(flag)
0: data <= 8'h68; //h
1: data <= 8'h65; //e
2: data <= 8'h6c; //l
3: data <= 8'h6c; //l
4: data <= 8'h6f; //o
5: data <= 8'h20; //空格
6: data <= 8'h77; //w
7: data <= 8'h6f; //o
8: data <= 8'h72; //r
9: data <= 8'h64; //d
10: data <= 8'h21; //!
11: data <= 8'h0d; //回车 \r
12: data <= 8'h0a; //换行 \n
default : data <= 8'h00;
endcase
end
endmodule
urat_tx.v
module urat_tx(
//input
clk,//模块的时钟 50MHz
rst_n, //系统复位信号
set_Baud_rate,//波特率大小设置
data, //要发送的一个字节数据
en, //模块工作的使能信号
//output
TX, //串口输出信号
tx_down //发送完成标志,每完成一次发送就产生一段高脉冲
);
input clk; //模块的时钟 50MHz
input rst_n; //系统复位信号
input [8:0] data; //要发送的一个字节数据
input [3:0] set_Baud_rate; //波特率大小设置
input en; //模块工作的使能信号
output TX; //串口输出信号
output tx_down; //发送完成标志,每完成一次发送就产生一段高脉冲
wire clk;
wire rst_n;
wire en;
reg TX;
reg tx_down;
//模块内部的寄存器
reg flag5207;
reg [8:0] rev_data;
reg [13:0] conter;
reg [4:0]con;
reg [13:0] Baud_rate; //波特率
//localparam Baud_rate=433;//设置波特率
//接收设置的波特率大小
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Baud_rate <= 5207;
else
case (set_Baud_rate)
0: Baud_rate <= 5207; //9600
1: Baud_rate <= 2603;
2: Baud_rate <= 1301;
3: Baud_rate <= 867;
4: Baud_rate <= 433; //115200
default: Baud_rate <= 5207;
endcase
end
//接收外来的要发送的数据;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rev_data <= 8'd0;
else
rev_data <= data;
end
//为最基本的计数器分频,产生例如9600Hz的信号提供给其他部分
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag5207 <= 0;
else if(en)//增加使能信号控制
begin
if(conter == Baud_rate)
flag5207 <= 1'b1;
else
flag5207 <= 1'b0;
end
else
flag5207 <= 1'b0;//如果不使能相当于给复位了 使能:en=1;
end
//为了后面分频给后面的逻辑电路,让5207计数器每次循环计数到1时就产生一个高电平的使能信号给下面的电路。
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
conter <= 0;
else if(conter == Baud_rate)
conter <= 0;
else
conter <= conter +1'b1;
end
//11进制计数器为了发送一个字节数据需要循环发送起始信号、数据段、结束信号,
//加在一起一共需要10bit,最后1个bit需要保持高电平防止信号不稳定
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
con <= 0;
else if(flag5207 == 1'b1)
begin
if(con == 4'd13)
con <= 0;
else
con <= con +1'b1;
end
else
con <= con;
end
//来产生最后的输出信号,需要接收11进制计数器的数据,
//选择发送从0到11的数据。分别代表了数据起始位和数据位以及数结束位
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
TX <= 1'b0;
else
begin
case(con)
0:TX <= 1'b0;
1:TX <= rev_data[0]; //bit0
2:TX <= rev_data[1]; //bit1
3:TX <= rev_data[2]; //bit2
4:TX <= rev_data[3]; //bit3
5:TX <= rev_data[4]; //bit4
6:TX <= rev_data[5]; //bit5
7:TX <= rev_data[6]; //bit6
8:TX <= rev_data[7]; //bit7
default: TX <= 1'b1;
endcase
end
end
//发送一个字节完成的标志位,高电平
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
tx_down <=0;
else if(con == 4'd13)
tx_down <= 1'b1;
else
tx_down <= 1'b0;
end
endmodule
串口要发送换行符就必须发送\r\n两个数据,并不是直接发送这四个字符就可以实现换行的目的,而是需要根据 ASCII码 的值来发送,这里我主要参考了这两位大神的教程:
回车与换行的区别
ZZ 回车\r和换行\n的区别–ASCII码表(含二进制 十进制 十六进制 )