FPGA开发。串口通信(串口发送)。
串口通信
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)是一种异步
收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的
串行数据转换成并行数据,可以实现全双工传输和接收。它包括了 RS232、RS449、RS423、
RS422 和 RS485 等接口标准规范和总线标准规范。换句话说,UART 是异步串行通信的总
称。而 RS232、RS449、RS423、RS422 和 RS485 等,是对应各种异步串行通信口的接口标
准和总线标准,它们规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内
容。
UART 关键参数及时序图
UART 通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶
校验类型和停止位数。
1.数据位(Data bits):该参数定义单个 UART 数据传输在开始到停止期间发送的数据位
数。可选择为:5、6、7 或者 8(默认)。
2.波特率(Baud):是指从一设备发到另一设备的波特率,即每秒钟可以通信的数据比特
个数。典型的波特率有 300, 1200, 2400, 9600, 19200, 115200 等。一般通信两端设备都要设为
相同的波特率,但有些设备也可设置为自动检测波特率。
3.奇偶校验类型(Parity Type):是用来验证数据的正确性。奇偶校验一般不使用,如果
使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。在偶校验中,因为奇偶校验位
会被相应的置 1 或 0(一般是最高位或最低位),所以数据会被改变以使得所有传送的数位
(含字符的各数位和校验位)中“1”的个数为偶数;在奇校验中,所有传送的数位(含字符
的各数位和校验位)中“1”的个数为奇数。奇偶校验可以用于接受方检查传输是否发送生错
误,如果某一字节中“1”的个数发生了错误,那么这个字节在传输中一定有错误发生。如果
奇偶校验是正确的,那么要么没有发生错误,要么发生了偶数个的错误。如果用户选择数据
长度为 8 位,则因为没有多余的比特可被用来作为奇偶校验位,因此就叫做“无奇偶校验
(Non)”。
4.停止位(Stop bits):在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据
传输完成,同时用来帮助接受信号方硬件重同步。可选择为:1(默认)、1.5 或者 2 位。
在 RS-232 标准中,最常用的配置是 8N1(即八个数据位、无奇偶校验、一个停止位),其
发送一个字节时序图如下图所示:
按照一个完整的字节包括一位起始位、8 位数据位、一位停止位即总共十位数据来算,
要想完整的实现这十位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传
输的起始,第 11 个脉冲标记一次传输的结束,如下所示:
bps_clk 信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的 2 到 9
个上升沿,发送 8 个数据位,第 10 个上升沿到第 11 个上升沿为停止位的发送。
串口发送实验
1.串口通信模块设计的目的是用来发生数据的,因此需要有一个数据输入端口;
2.串口通信,支持不同的波特率,所以需要有一个波特率设置端口;
3.串口通信的本质是将八位的并行数据通过一根信号线,在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据传出去;
4.串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成后,再以1位的高电平标志传输的结束;
5.控制信号,控制并转串模块什么开始工作,什么时候一个数据发送完成?需有一个发送开始信号以及一个发送完成信号。
代码
分解
定义端口
时钟与复位;数据输入端口;比特率设置端口;使能;数据输出及完成标志。(使能信号到达,数据以设置好的波特率输入并传输,传输完成后标志位置1)
module utral_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;
reg[17:0]bps_DR;
波特率设置
本模块的设计是为了保证模块的复用性。当需要不同的波特率时,只需设置不同的波
特率时钟计数器的计数值。使用查找表即可实现,下面的设计代码中只包含了针对 5 个波
特率的设置,如需要其他波特率可根据实际使用情况具体修改。
波特率计算方法:1s=1000000000ns 发送一个字节所需要的时间1s/波特率 发送一个字节需要的时间100000000/9600 再除以20(50Hz)得到最终的时钟周期。
//Baud_set = 0 波特率 = 9600 1000000000/9600/20 1s/波特率/20
//Baud_set = 1 波特率 = 19200
//Baud_set = 2 波特率 = 38400
//Baud_set = 3 波特率 = 57600
//Baud_set = 4 波特率 = 115200
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_DR <= 16'd5207;
else begin
case(Baud_set)
0:bps_DR <= 16'd5207;
1:bps_DR <= 16'd2603;
2:bps_DR <= 16'd1301;
3:bps_DR <= 16'd867;
4:bps_DR <= 16'd433;
default:bps_DR <= 16'd5207;
endcase
end
波特率生成
利用一个计数器生成一个波特率时钟
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;
所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信
号。例如,我们使用波特率为 115200bps,则我们需要产生一个频率为 115200Hz 的时钟信号。
那么如何产生这样一个 115200Hz 的时钟信号呢?这里,我们首先将 115200Hz 时钟信号的周期
计算出来,1 秒钟为 1000_000_000ns,因此波特率时钟的周期 Tb= 1000000000/115200 =8680.6ns,
即 115200 信号的一个周期为 8680.6ns,那么,我们只需要设定我们的定时器定时时间为 8680.6ns,
每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。系统时钟频率为 50MHz,即
周期为 20ns,那么,我们只需要计数 8680/20 个系统时钟,就可获得 8680ns 的定时,
bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此相同
为了能够通过外部控制波特率,设计中使用了一个 3 位的波特率选择端口:baud_set。通过
给此端口不同的值,就能选择不同的波特率,此端口控制不同波特率的原理很简单,就是一个多
路选择器,多路选择器通过选择不同的定时器计数最大值来设置不同的比特率时钟频率。
数据输出模块设计
通过对波特率时钟进行计数,来确定数据发送的循环状态。
每一个波特率开始计数,状态+1,因此div _ cnt == 1时状态+1,这里并没有再div_cnt == bps_DR - 1时状态+1,这是因为如果设为改值,那么在状态转变时,需要等待计数到bps _ DR - 1才会出执行相应的命令。
reg[3:0]bps_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_cnt <= 0 ;
else if(bps_cnt ==11)
bps_cnt <= 0 ;
else if(div_cnt ==1)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
同样为了使得模块可以对其他模块进行控制或者调用,这里产生一个 byte 传送结束的
信号。一个数据位传输结束后 tx_done 信号输出一个时钟的高电平。
always@(posedge Clk or posedge Reset_n)
if(!Reset_n)
Tx_done <= 1'b0;
else if(bps_cnt == 11)
Tx_done <= 1'b1;
else
Tx_done <= 1'b0;
根据不同状态输出值
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 <= 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:uart_tx <= 1;
default: uart_tx <= 1;
endcase
end
=========================================================================================
时序仿真:
`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;
utral_tx utral_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;
Reset_n = 1;
#100;
Data = 8'h57;
Send_en = 1;
#20;
@(posedge Tx_done);
Send_en = 0;
#20000;
Data = 8'h75;
Send_en = 1;
#20;
@(posedge Tx_done);
Send_en = 0;
#20000;
$stop;
end
endmodule
串口发送的应用
使用上述的串口发送实现一个每10ms输出一个+1的数字
子程序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;
reg[17:0]bps_DR;
//Baud_set = 0 波特率 = 9600 1000000000/9600/20 1s/波特率/20
//Baud_set = 1 波特率 = 19200
//Baud_set = 2 波特率 = 38400
//Baud_set = 3 波特率 = 57600
//Baud_set = 4 波特率 = 115200
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_DR <= 16'd5207;
else begin
case(Baud_set)
0:bps_DR <= 16'd5207;
1:bps_DR <= 16'd2603;
2:bps_DR <= 16'd1301;
3:bps_DR <= 16'd867;
4:bps_DR <= 16'd433;
default:bps_DR <= 16'd5207;
endcase
end
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(bps_cnt ==11)
bps_cnt <= 0 ;
else if(div_cnt ==1)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
always@(posedge Clk or posedge Reset_n)
if(!Reset_n)
Tx_done <= 1'b0;
else if(bps_cnt == 11)
Tx_done <= 1'b1;
else
Tx_done <= 1'b0;
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 <= 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:uart_tx <= 1;
default: uart_tx <= 1;
endcase
end
endmodule
主程序
module uart_tx_2(
Clk,
Reset_n,
uart_tx
);
input Clk;
input Reset_n;
output uart_tx;
reg Send_en;
reg [7:0] Data;
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Reset_n(Reset_n),
.Data(Data),
.Send_en(Send_en),
.Baud_set(3'd4),
.uart_tx(uart_tx),
.Tx_done(Tx_done)
);
reg [18:0]cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
cnt <= 0;
else if(cnt == 499999)
cnt <= 0;
else
cnt <= cnt + 1;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Send_en <= 0;
else if(cnt == 1)
Send_en <= 1;
else if(Tx_done)
Send_en <= 0; //可以把这段放在子程序中,
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Data <= 0;
else if(Tx_done)
Data <= Data + 1'b1;
endmodule
时序仿真
`timescale 1ns / 1ns
module uart_tx_tb(
);
reg Clk;
reg Reset_n;
wire uart_tx;
uart_tx_2 uart_tx_2(
.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;
$stop;
end
endmodule
还可以添加一个寄存器寄存数据:
reg [7:0]data_byte_reg
always@(posedge Clk or posedge Reset_n)
if(!Reset_n)
data_byte_reg <= 8'd0;
else if(send_en)
data_byte_reg <= data_byte;
else
data_byte_reg <= data_byte_reg;