更新一篇博客吧,写一写关于这周自己对串口通信的理解。也会分享一下自己写的代码,
先简单的介绍一下一些概念:
并行通信是指数据的各个位用多条数据线同时进行传输
串行通信是将数据分成一位一位的形式在一条传输线上逐个传输
同步通信:带时钟同步信号的数据传输;发送方和接收方在同一时钟的控制下,同步传输数据。
异步通信:不带时钟同步信号的数据传输。发送方与接收方使用各自的时钟控制数据的发送和接收过程。
单工 :数据只能沿一个方向传输
半双工:数据传输可以沿两个方向,但需要分时进行
全双工:数据可以同时进行双向传输
下面是串口通信的过程:
串口通信是全双工,有Tx,Rx两条线。Tx是发送,Rx是接收。
波特率:每秒钟传送数据位的数目来表示,9600,就是1秒,可以传送9600bit个数据(0,1)
首先先介绍一下FPGA接收电脑串口助手发过来的数据过程,
首先,如果电脑还没有发数据之前,Rx这条线上一直是高电平,处于空闲状态,如果一旦电脑发数据过来, 这条线就会拉低,持续Period时间,这个Period时间就是一位数据需要的时间,这个时间计算:
例如:如果系统时钟频率clk=50000000hz,波特率baud=9600 ,Period=(50000000/9600)*20ns;
我们检测Rx如果发生下降沿,我们就开始用一个计数器进行计时,如果记到Period这个时间到了,我们进入下个状态,准备接收第1bit数据,如果电脑发过来的数据是1。1的ASCII码就是:49,49对应的二进制为:0011 0001,电脑会在Rx这条线上,先发低位数据,然后是高位数据,所以就是发过来的数据就是:1000_1100。然后FPGA接收数据时,最好在每位中间时刻去保存数据,这个时候的数据比较稳定。按照这样一共接收八位数据。最后一位就是停止位。电脑会发过来1bit的停止位,为高电平。我们一定要在1bit数据之前结束接收状态。(给一个建议,在一半period那个时刻,就结束)。
//串口助手设置:波特率9600 无奇偶校验位,8位数据位,一个停止位
//time:2019.11.13.22.41
module uart_rx(
input clk ,
input rst_n ,
input uart_rx,
output reg uart_rx_done,
output reg [7:0] data
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600; //波特率
localparam PERIOD = CLK_FREQ/UART_BPS;
//接收的数据
reg [7:0] rx_data;
reg rx1,rx2;
wire start_bit;
reg start_flag;
reg [15:0] cnt0;
wire add_cnt0;
wire end_cnt0;
reg [3:0] cnt1;
wire add_cnt1;
wire end_cnt1;
//下降沿检测
assign start_bit=(rx2)&(~rx1);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx1<=1'b0;
rx2<=1'b0;
end
else begin
rx1<=uart_rx;
rx2<=rx1;
end
end
//开始标志位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start_flag<=0;
end
else if(start_bit) begin
start_flag<=1;
end
else if(end_cnt1) begin
start_flag<=0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(end_cnt1) begin
cnt0 <= 0;
end
else if(end_cnt0) begin
cnt0 <= 0;
end
else if(add_cnt0)begin
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = start_flag;
assign end_cnt0 = add_cnt0 && cnt0==PERIOD-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(end_cnt1) begin
cnt1 <= 0;
end
else if(add_cnt1)begin
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0 ;
assign end_cnt1 = (cnt0==((PERIOD-1)/2))&& (cnt1==10-1) ;
//数据接收
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data<=8'd0;
end
else if(start_flag) begin
if(cnt0== PERIOD/2)begin
case(cnt1)
4'd1:rx_data[0]<=rx2;
4'd2:rx_data[1]<=rx2;
4'd3:rx_data[2]<=rx2;
4'd4:rx_data[3]<=rx2;
4'd5:rx_data[4]<=rx2;
4'd6:rx_data[5]<=rx2;
4'd7:rx_data[6]<=rx2;
4'd8:rx_data[7]<=rx2;
default:rx_data<=rx_data;
endcase
end
else begin
rx_data<=rx_data;
end
end
else begin
rx_data<=8'd0;
end
end
//数据接收
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data<=0;
end
else if(end_cnt1)begin
data<=rx_data;
end
end
//接收完成标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_rx_done<=0;
end
else if(end_cnt1)begin
uart_rx_done<=1;
end
else begin
uart_rx_done<=0;
end
end
endmodule
发送代码:
//串口助手设置:波特率9600 无奇偶校验位,8位数据位,一个停止位
//time:2019.11.13.22.41
module uart_tx(
input clk ,
input rst_n ,
output reg uart_tx,
input [7:0] data,
input tx_start
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600; //波特率
localparam PERIOD = CLK_FREQ/UART_BPS;
reg [7:0] tx_data; //发送的数据
reg start_tx_flag; //发送数据标志位
//记算一位数据需要多长时间PERIOD
reg [15:0] cnt0;
wire add_cnt0;
wire end_cnt0;
//发送几个数据
reg [3:0] cnt1;
wire add_cnt1;
wire end_cnt1;
//发送标志位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start_tx_flag<=0;
tx_data<=0;
end
else if(tx_start) begin
start_tx_flag<=1;
tx_data<=data; //把发送的数据存到这里来
end
else if(end_cnt1) begin
start_tx_flag<=0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(end_cnt1) begin
cnt0 <= 0;
end
else if(end_cnt0) begin
cnt0 <= 0;
end
else if(add_cnt0)begin
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = start_tx_flag;
assign end_cnt0 = add_cnt0 && cnt0==PERIOD-1; //一位时间
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(end_cnt1) begin
cnt1 <= 0;
end
else if(add_cnt1)begin
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0 ;
assign end_cnt1 = (cnt0==((PERIOD-1)/2))&& (cnt1==10-1); //发送10位,包括停止位,空闲位
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_tx<=1; //空闲状态
end
else if(start_tx_flag) begin
if(cnt0==0)begin
case(cnt1)
4'd0:uart_tx<=0; //起始位
4'd1:uart_tx<=tx_data[0];
4'd2:uart_tx<=tx_data[1];
4'd3:uart_tx<=tx_data[2];
4'd4:uart_tx<=tx_data[3];
4'd5:uart_tx<=tx_data[4];
4'd6:uart_tx<=tx_data[5];
4'd7:uart_tx<=tx_data[6];
4'd8:uart_tx<=tx_data[7];
4'd9:uart_tx<=1; //停止位
default:;
endcase
end
end
end
endmodule
顶层模块:
//注意:
//串口助手设置:波特率9600 无奇偶校验位,8位数据位,一个停止位
//time:2019.11.13.22.41
module uart(
input clk ,
input rst_n ,
input uart_rx ,
output uart_tx ,
output wire [7:0] seg_sel,
output wire [7:0] segment
);
wire [7:0] data;
wire uart_rx_done;
//数码管
seg_num seg_disp_uut(
.clk (clk),
.rst_n (rst_n),
.seg_sel (seg_sel),
.segment (segment),
.data (data)
);
//FPGA接收串口助手发来的数据
uart_rx uart_rx_uut(
.clk (clk),
.rst_n (rst_n),
.uart_rx (uart_rx),
.uart_rx_done(uart_rx_done),
.data (data)
);
//FPGA把接收的数据发送到串口助手上
uart_tx uart_tx_uut(
.clk (clk),
.rst_n (rst_n),
.uart_tx (uart_tx),
.data (data),
.tx_start(uart_rx_done)
);
endmodule