前面一篇博客实现已经分析并实现串行接口的接收模块。其中,串口的波特率对串口来说是一个比较重要的概念,因为其决定了接收或者发送一位数据所用的时间。由于FPGA所用的时钟通常远比串口的波特率快,所以在使用FPGA的时钟发送或者接收数据时,都需要一个串口波特率定时模块来产生定时脉冲,以此确保每位数据只被接收或者发送一次。
串口发送过程如图1所示,由图可知,其基本原理跟串口的接收时序一致,唯一区别就是串口发送模块不需要开始标志(串口接收数据时需要开始标志,其起始位必须为0);每次发送数据也是11位,包含1位起始位、8位数据位、1位校验位、1位停止位。
图1中,sampling_signal是由波特率定时模块产生的,对应TX_Pin_Out的每一位数据,都有一个sampling_signal,表明每个数据在这个波特率的时间内只被发送一次。
一、串口发送数据流程
串口发送数据流程如图2所示, 在s0状态,计数器电路加载0,如果cnt_en串口发送数据信号使能,进入s1状态。在s1状态,加载待发送的数据,如果采样信号有效,则计数器使能,计数器开始计数;若计数器计数到count=1,进入s2状态,在s2状态,主要通过一位寄存器将并行数据转为串行发送,若采样信号有效,则计数器、移位寄存器信号使能,此时移位寄存器开始一位一位的发送出去,同时计数器计数发送数据的个数;若计数到count=11,进入到s3数据发送完成状态,在s3状态,如果采样信号有效,发送完成标志位拉高,计数器和移位寄存器信号使能,此时串口发送数据完毕。若计数器count=0,则进入到s0状态,等待下一次数据的发送。
二、数据路径
由图2可知,串口发送模块需要的电路的基本模块包括计数器、并行转串行移位寄存器,波特率定时(计数器)。数据路径如图3所示:
三、控制信号
由数据路径可知,右移寄存器的控制信号包括使能信号en_a和数据加载信号load_a,计数器的控制信号包括计数器使能信号en_b和计数器加载信号(加载0)load_b,波特率定时计数器包含波特率定时计数器使能信号。可以在图2中的各个状态中得出这些信号何时有效。
由图4可知,在s0状态,计数器加载信号load_b有效,此时计数器加载0;在s1状态右移寄存器记载信号load_a使能,此时移位寄存器加载要发送的数据,在该状态,如果波特率定时采样信号simpling_signal有效,计数器使能信号en_b有效,计数发已经送数据的个数;在s2状态如果simpling_signal有效,移位寄存器使能信号en_a和计数器使能信号en_b有效;在s3状态,如果simpling_signal有效,en_a和n_b有效,发送最后一位数据,同时发送完成信号标志tx_done信号拉高。
四、verilog描述
用verilog代码来描述图3所示的数据路径,代码如下:
1、代码顶层部分:
module TRANSMIT_MODULE(input clk_in,
input rst,
input [10:0]tx_data,
input cnt_en,发送数据使能信号
output reg tx_done,
output tx_pin_data,
output clk_50m
);
// Instantiate the module
clk_ip clk_ip (
.CLKIN_IN(clk_in),
.CLKFX_OUT(clk_50m),
.CLKIN_IBUFG_OUT(CLKIN_IBUFG_OUT),
.CLK0_OUT(CLK0_OUT)
);
wire [3:0]count;
wire simpling_signal;
reg load_b;
reg load_a;
reg en_b;
reg en_a;
parameter [3:0]s0 = 'b0001;
parameter [3:0]s1 = 'b0010;
parameter [3:0]s2 = 'b0100;
parameter [3:0]s3 = 'b1000;
reg [3:0]current_state = 'd0;
reg [3:0]next_state = 'd0;
//
always @(posedge clk_50m)
if(!rst)
current_state <= s0;
else
current_state <= next_state;
///
always @(*)
case(current_state)
s0: begin
if(cnt_en)
next_state = s1;
else
next_state = s0;
end
s1: begin
if(count == 'd1)
next_state = s2;
else
next_state = s1;
end
s2: begin
if(count == 'd11)
next_state = s3;
else
next_state = s2;
end
s3: begin
if(count == 'd0)
next_state = s0;
else
next_state = s3;
end
default: next_state = s0;
endcase
/
always @(*)
case(current_state)
s0: begin
load_b = 'd1;
load_a = 'd0;
en_b = 'd0;
en_a = 'd0;
tx_done = 'd0;
end
s1: begin
load_b = 'd0;
tx_done = 'd0;
en_a = 'd0;
load_a = 'd1;
if(simpling_signal) begin
// load_a = 'd1;
en_b = 'd1;
end
else begin
// load_a = 'd0;
en_b = 'd0;
end
end
s2: begin
load_b = 'd0;
load_a = 'd0;
tx_done = 'd0;
if(simpling_signal) begin
en_a = 'd1;
en_b = 'd1;
end
else begin
en_a = 'd0;
en_b = 'd0;
end
end
s3: begin
load_b = 'd0;
load_a = 'd0;
if(simpling_signal) begin
tx_done = 'd1;
en_b = 'd1;
en_a = 'd1;
end
else begin
tx_done = 'd0;
en_b = 'd0;
en_a = 'd0;
end
end
default: begin
load_b = 'd1;
load_a = 'd0;
en_b = 'd0;
en_a = 'd0;
tx_done = 'd0;
end
endcase
// Instantiate the module
BPS_TIMER BPS_TIMER (
.clk_50m(clk_50m),
.cnt_en(cnt_en),
.simpling_signal(simpling_signal)
);
// Instantiate the module
COUNT_NUMBER COUNT_NUMBER (
.clk_50m(clk_50m),
.load_b(load_b),
.en_b(en_b),
.count(count)
);
// Instantiate the module
right_shifter right_shifter (
.clk_50m(clk_50m),
.load_a(load_a),
.en_a(en_a),
.tx_data(tx_data),
.tx_pin_data(tx_pin_data)
);
endmodule
2、波特率定时模块:
由于使用的时钟是50mhz的,而串口的波特率是9600bps,即串口发送数据的时钟是9600hz,因此需要使用50mhz的时钟产生个计数器,
///使其每1/9600s产生一个允许采样脉冲。
//计数器大小设置:500*10^3/96 = 5208,\,因此计数器需要计数5208个数,由于在数据中间在中间时刻更稳定,因此,在5208/2=2604时对数据进行采样更准确,
//由于计数是从零开始,因此在2603时对数据进行采样,数据计数到5207清零。
//
module BPS_TIMER(input clk_50m,
input cnt_en,
output simpling_signal
);
reg [12:0] cnt = 'd0;
always @(posedge clk_50m)
if(cnt_en) begin
if(cnt == 'd5207)
cnt <= 'd0;
else
cnt <= cnt + 'd1;
end
else
cnt <= 'd0;
assign simpling_signal = (cnt == 'd2603)?'b1:'b0;
endmodule
3、计数器模块:
module COUNT_NUMBER(input clk_50m,
input load_b,
input en_b,
output reg[3:0] count
);
always @(posedge clk_50m)
if(load_b)
count <= 'd0;
else if(en_b) begin
if(count == 'd11)
count <= 'd0;
else
count <= count + 'd1;
end
else
count <= count;
endmodule
4、移位寄存器模块:
module right_shifter(input clk_50m,
input load_a,
input en_a,
input [10:0]tx_data,
output tx_pin_data
);
reg [10:0]data;
always @(posedge clk_50m)
if(load_a)
data <= tx_data;
else if(en_a)
data <= {1'b0,data[10:1]};
else
data <= data;
assign tx_pin_data = data[0];
endmodule
5、仿真激励文件:
module test;
// Inputs
reg clk_in;
reg rst;
reg [10:0] tx_data;
reg cnt_en;
// Outputs
wire tx_done;
wire tx_pin_data;
wire clk_50m;
// Instantiate the Unit Under Test (UUT)
TRANSMIT_MODULE uut (
.clk_in(clk_in),
.rst(rst),
.tx_data(tx_data),
.cnt_en(cnt_en),
.tx_done(tx_done),
.tx_pin_data(tx_pin_data),
.clk_50m(clk_50m)
);
initial begin
// Initialize Inputs
clk_in = 0;
rst = 0;
tx_data = 0;
cnt_en = 0;
// Wait 100 ns for global reset to finish
#100;
// Add stimulus here
end
always #5 clk_in = !clk_in;
reg [2:0] cnt = 'd0;
always @(posedge clk_50m)
if(cnt == 'd5)
cnt <= 'd5;
else
cnt <= cnt + 'd1;
always @(posedge clk_50m)
if(cnt<=3)
rst <= 'd0;
else
rst <= 'd1;
reg[20:0]count = 'd0;
always @(posedge clk_50m)
if(count == 'd10000)
count <= 'd0;
else
count <= count + 'd1;
always @(posedge clk_50m)
if(tx_done) begin
cnt_en <= 'd0;
tx_data <= 'd0;
end
else if(count == 'd10000) begin
cnt_en <= 'd1;
tx_data <= 'b10101010101;
end
else begin
cnt_en <= cnt_en;
tx_data <= tx_data;
end
endmodule
6、Isim仿真结果如下:发送的数据 tx_data <= ‘b10101010101;
由仿真结果可知,在一个发送数据周期,en_a有效11次。待所有数据发送完毕tx_done信号拉高。