一、串口基本概念
串行接口(Serial port),主要用于串行式逐位数据传输。常见的有一般计算机应用的RS-232(使用 25 针或 9 针连接器)和工业计算机应用的半双工RS-485与全双工RS-422。----------维基百科
按照电气标准划分,串口可以分为RS-232-C、RS-422、RS-485。
RS-232-C:也称标准接口,是目前最常用的一种串行通讯接口。台式计算机一般有两个串行口:COM1和COM2,均为9针D形接口通常在计算机后面能看到,一般笔记本上有一个串口,超薄本除外。RS-232-C电气标准接口传输速率低,传输距离近。
RS-422:为改进RS-232-C传输距离近,传输速率低的特点,RS-422定义了一种平衡通信接口,传输速率提高到10Mb/s,并允许在一条接收总线上挂多个接收器。
RS-485:为扩展应用范围,EIA又于1983年在RS-422基础上制定了RS-485 标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为 TIA/EIA-485-A标准。
下图是串口公口实物图,在台式机和笔记本上一般都能找到。维基百科
2、串口传输时序-------参考【FPGA黑金开发板】Verilog HDL那些事儿--串口模块(十一)
图2是串口传输数据的时序图,串口总线默认是高电平,一帧数据开始必须先拉低电平,对应图中起始位。串口传输数据是以数据包为单位的,一个数据包有11位,其中先传输起始位,紧接着传输8个数据位,数据位是按照由低到高的顺序进行传输的,接着传输校验位,最后传输停止位。
串口还有一个重要的参数就是波特率,一般用波特率来形容串口传输数据的速度。常用的波特率有9600bps和115200bps,本文主要介绍9600bps。bps:bit per second,就是每秒钟传输多少位;9600bps就是每秒钟传输9600位,对应的传输一位所用的时钟周期就是9600Hz。接下来本文参考《verilog那些事儿——建模篇》中串口模块的划分来实现串口的接收部分verilog代码。
3、串口接收分析
由于这里是串口接收模块的介绍,为简单考虑从串口外部输入的信号只考虑RX_data_in。由于串口接收数据有明显的先后顺序,因此可以使用状态机来描述串口接收数据过程。使用算法状态机描述串口接收过程如下图所示:
图3中s0状态实现数据的初始化,start信号对应串口发送的起始位数据,使用沿检测电路实现;s2状态是接收8位有效数据状态,由于串口是串行将数据接收进来,然后转成并行数据,因此s1部分的电路可以用右移的移位寄存器实现(串口先发送低位数据,所以选用右移移位寄存器);图中虽然画出了s3状态执行校验功能,但是本代码不考虑检验问题,因此s3状态相当于是空状态;s4状态是串口接收数据完毕发出的标志信号。根据算法状态机可以看出,实现串口的接收所需要的基本电路有移位寄存器电路、计数器电路和触发器电路。
4、数据路径
数据路径如图4所示,波特率定时信号的作用分析如下:
由于FPGA的工作时钟较快,串口发送数据较慢。如图5所示,串口发送一位数据可能占用多个FPGA时钟周期,因此需要增加采样信号sampling_signal,采样信号有效时占一个时钟周期,在采样信号有效时,FPGA才接收数据,保证每位数据只接收一次。波特率定时计数器就是用来产生采样信号的,在波特率定时计数器计数到一定数据后,采样信号有效,否则,采样信号无效,这样就可以产生如图5所示的采样信号。
5、控制信号
图6给出了控制信号在哪个状态有效。在s0状态,移位寄存器和计数器加载0,D触发器使能信号en_b有效,检测串口发送的数据rx_pin_in的起始信号,待检测到起始信号后,start信号有效,进入s1状态,在s1状态使能波特率采样计时信号cnt_en,判断串口采样信号sinpling_signal是否有效,若有效,设置计数器使能信号有效,反之,设置计数器使能信号无效;在计数器cnt=1时,进入s2状态,此状态开始接收串口发送的8位数据位,首先使能波特率采样计时信号cnt_en,判断sinpling_signal是否有效,若有效,计数器使能信号和移位寄存器使能信号均设置为有效,反之,设置为无效;待8位数据位接收完毕后,进入s3状态,此时仍然使能波特率采样计时信号cnt_en,判断采样信号sinpling_signal是否有效,若有效,设置计数器使能信号有效,反之,设置无效。待计数器记到10时,进入s3状态,串口发送的11位信号接收完毕标志信号done拉高,此时仍然使能波特率采样计时信号cnt_en,判断sinpling_signal是否有效,若有效设置计数器使能信号有效,反之,设置无效。然后进入s0空闲状态,等待下一次数据接收。
6、verilog描述
接下来就可以使用verilog语言描述上述电路:
module RECEIVE_MODULE(input clk_in,
input rst,
input rx_pin_in,
output [7:0] rx_data,
output reg done,
output clk_50m
);
//wire clk_50m;
clk_1 clk_1 (
.CLKIN_IN(clk_in),
.CLKFX_OUT(clk_50m),
.CLKIN_IBUFG_OUT(CLKIN_IBUFG_OUT),
.CLK0_OUT(CLK0_OUT)
);
parameter s0 = 'b00001;
parameter s1 = 'b00010;
parameter s2 = 'b00100;
parameter s3 = 'b01000;
parameter s4 = 'b10000;
reg[4:0]current_state = 'd0;
reg[4:0]next_state = 'd0;
//////////////////////////
wire start;
reg en_b;
reg cnt_en;
wire simpling_signal;
reg load_c;
reg en_c;
reg en_a;
reg load_a;
wire [3:0]count;
/////////////////////////////////////////
always @(posedge clk_50m or negedge rst)
if(!rst)
current_state <= s0;
else
current_state <= next_state;
/////////////////////////////////
always @(*)
case(current_state)
s0: begin
if(start)
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 == 'd9)
next_state = s3;
else
next_state = s2;
end
s3: begin
if(count == 'd10)
next_state = s4;
else
next_state = s3;
end
s4: begin
if(count == 'd0)
next_state = s0;
else
next_state = s4;
end
default:next_state = s0;
endcase
/////////////////////
always @(*)
case(current_state)
s0: begin
en_b = 'd1;
load_a = 'd1;
load_c = 'd1;
cnt_en = 'd0;
en_a = 'd0;
en_c = 'd0;
done = 'd0;
end
s1: begin
en_b = 'd1;
load_a = 'd0;
load_c = 'd1;
cnt_en = 'd1;
en_c = 'd0;
done = 'd0;
if(simpling_signal) begin
en_a = 'd1;
end
else begin
en_a = 'd0;
end
end
s2: begin
en_b = 'd0;
load_a = 'd0;
load_c = 'd0;
cnt_en = 'd1;
done = 'd0;
if(simpling_signal) begin
en_c = 'd1;
en_a = 'd1;
end
else begin
en_c = 'd0;
en_a = 'd0;
end
end
s3: begin
en_b = 'd0;
load_a = 'd0;
load_c = 'd0;
cnt_en = 'd1;
en_c = 'd0;
done = 'd0;
if(simpling_signal)
en_a = 'd1;
else
en_a = 'd0;
end
s4: begin
en_b = 'd0;
load_a = 'd0;
load_c = 'd0;
cnt_en = 'd1;
en_c = 'd0;
done = 'd1;
if(simpling_signal)
en_a = 'd1;
else
en_a = 'd0;
end
default: begin
en_b = 'd0;
load_a = 'd0;
load_c = 'd0;
cnt_en = 'd0;
en_a = 'd0;
en_c = 'd0;
done = 'd0;
end
endcase
// Instantiate the module
edge_detection edge_detection (
.clk_50m(clk_50m),
.en_b(en_b),
.rx_pin_in(rx_pin_in),
.start(start)
);
// Instantiate the module
BPS_TIMER BPS_TIMER (
.clk_50m(clk_50m),
.cnt_en(cnt_en),
.simpling_signal(simpling_signal)
);
// Instantiate the module
right_shifter right_shifter (
.clk_50m(clk_50m),
.load_c(load_c),
.en_c(en_c),
.rx_pin_in(rx_pin_in),
.rx_data(rx_data)
);
// Instantiate the module
COUNT_NUM COUNT_NUM (
.clk_50m(clk_50m),
.load_a(load_a),
.en_a(en_a),
.count(count)
);
endmodule
边沿检测模块代码:
module edge_detection(input clk_50m,
input en_b,
input rx_pin_in,
output start
);
reg rx_pin_in_1 = 'd0;
always @(posedge clk_50m)
if(en_b)
rx_pin_in_1 <= rx_pin_in;
else
rx_pin_in_1 <= rx_pin_in_1;
assign start = (!rx_pin_in) & rx_pin_in_1;
endmodule
波特率定时模块代码:
////////由于使用的时钟是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
右移寄存器模块代码:
//此模块用于将串行发送的数据转成并行接收
//////////////////////////////////////////////////////////////////////////////////
module right_shifter(input clk_50m,
input load_c,
input en_c,
input rx_pin_in,
output reg[7:0]rx_data
);
always @(posedge clk_50m)
if(load_c)
rx_data <= 'd0;
else if(en_c)
rx_data <= {rx_pin_in,rx_data[7:1]};
else
rx_data <= rx_data;
endmodule
计数器模块代码:
module COUNT_NUM(input clk_50m,
input load_a,
input en_a,
output reg [3:0]count
);
always @(posedge clk_50m)
if(load_a)
count <= 'd0;
else if(en_a) begin
if(count == 'd10)
count <= 'd0;
else
count <= count + 'd1;
end
else
count <= count;
endmodule
仿真激励文件代码:
module test;
// Inputs
reg clk_in;
reg rst;
reg rx_pin_in = 'd1;
// Outputs
wire [7:0] rx_data;
wire done;
wire clk_50m;
// Instantiate the Unit Under Test (UUT)
RECEIVE_MODULE uut (
.clk_in(clk_in),
.rst(rst),
.rx_pin_in(rx_pin_in),
.rx_data(rx_data),
.done(done),
.clk_50m(clk_50m)
);
initial begin
// Initialize Inputs
clk_in = 0;
rst = 0;
rx_pin_in = 1;
// Wait 100 ns for global reset to finish
#100;
// Add stimulus here
end
reg clk1= 'd1;
always #5 clk_in = ~clk_in;
reg [15:0]cnt = 'd0;
always @(posedge clk_50m)
if(cnt>0)
rst <= 'd1;
else
rst <= rst;
always @(posedge clk_50m)
if(cnt == 'd60000)/////////'d57287
cnt <= 'd0;
else
cnt <= cnt + 'd1;
always @(posedge clk_50m)
if(cnt>'d10&&cnt<='d5208)
rx_pin_in <= 'd0;//////////////////////1
else if(cnt>='d5208&&cnt<='d10415)
rx_pin_in <= 'd1;//////////////////////2
else if(cnt>='d10416&&cnt<='d15623)
rx_pin_in <= 'd0;////////////////////3
else if(cnt>='d15624&&cnt<='d20831)
rx_pin_in <= 'd1;///////////////////4
else if(cnt>='d20832&&cnt<='d26039)
rx_pin_in <= 'd0;////////////////////5
else if(cnt>='d26040&&cnt<='d31247)
rx_pin_in <= 'd1;///////////////////6
else if(cnt>='d31248&&cnt<='d36455)
rx_pin_in <= 'd0;///////////////////7
else if(cnt>='d36456&&cnt<='d41663)
rx_pin_in <= 'd1;///////////////////8
else if(cnt>='d41664&&cnt<='d46871)
rx_pin_in <= 'd1;///////////////////9
else if(cnt>='d46872&&cnt<='d52079)
rx_pin_in <= 'd1;///////////////////10
else if(cnt>='d52080&&cnt<='d57287)
rx_pin_in <= 'd1;///////////////////11
endmodule
Isim行为仿真结果:
从仿真波形中可以看出,当串行发送信号 01010101111时,在done信号拉高时接收数据完毕,显示接收的数据是11010101。因此,从仿真上看串口的接收可以正常工作。