一、串口通信理论知识.
1、 硬件电路
板子上已经用PL2303芯片实现了串口转USB,所以我们可以直接使用TXD、和RXD来进行操作。下图为USB与PL2303连接图,TXD、RXD接到FPGA的tx和rx端,其中TXD是向主机(一般视为PC端,串口调试助手)写出数据;RXD是FPGA中实现的读取数据模块来读取主机的数据,用以后续使用。
附件中有PL2303驱动以及资料。
! I1 Q3 R# s' J7 f7 ^* G/ _2、 串口通信协议
首先对于串口通信原理,现在基本板子上都实现了串口与USB的转换,所以只需要知道
如何使用对应的接口就行了。
串口通信主要在于串口协议上面:
上图是串口传输的时序图,一帧数据为11位。
1 b, B1 ~2 u. |; j. B! p
位 ' y7 S: P/ C* G4 j
| 作用 | 数据采集时 |
0 | 起始位 | 忽略 |
1~8 | 数据位 | 采集 |
9 | 校验位 | 忽略 |
10 | 停止位 | 忽略 |
在串口的总线上“高电平”是默认的状态,当一帧数据的开始传输必须先拉低电平,这
就是第0 位的作用。第0 位过后就是8 个数据位,这八个数据位才是一帧数据中最有意
义的东西。最后的两位是校验位和停止位,作用如同命名般一样,基本上是没有重要意
义。
' v z* Q" [5 O0 x! b% I. b
3、 波特率
“波特率”BPS(bit persecond),从字面上理解就是每秒钟传输数据的位数。宏观上一般认为是来标定串口的传输速度。而在微观上,“波特率”就是串口传输中“一个位的周期”,换句话说就是“一个位所逗留的时间”。
以波特率为9600bps为例,每秒传输9600位数据,则有:
一个位的周期 = 1/bps
= 1/9600
= 1.0416666666666666666666666666667e-4
即一位数据占用时间为0.001041667s。这个数据就是下面在进行数据采样时,根据板子晶振来设定采样个数的依据,也即设定波特率。
; F0 S! l% M5 w8 v
+ V1 D/ K3 V1 g3 B二、FPGA模块化电路设计
1、串口接收模块:首先给出框图,有一个整体理解。
框图解读:
a. 电平检测模块:由于串口总线上默认是高电平,串口数据开始位是高电平向低电平转化,所以电平检测模块就是来检测数据高电平转化为低电平时,发送出H2L_Sig(高电平)信号,通知就收控制模块可以进行数据的采集了。下面为检测模块(请看注释解释):
module detect_module( CLK,RSTn,RX_Pin_In,H2L_Sig ); 2 H z3 f% v. n/ B
input CLK; input RSTn; input RX_Pin_In; output H2L_Sig;
" @& S6 o, S3 Z4 G/ l2 F* s /*********************************************/ reg H2L_F1;//存放此刻接收到的数据的电平高低 reg H2L_F2;//存放上一时刻数据的电平高低
) i' [8 Q' X, V always @(posedge CLK or negedge RSTn) begin if(!RSTn) begin H2L_F1 <= 1'b1; H2L_F2 <= 1'b1; end else begin H2L_F1 <= RX_Pin_In;//接收此刻输入数据信号 H2L_F2 <= H2L_F1;//将原信号赋值H2L_F2 end end ; t2 @# I1 h1 C7 I6 G# W' ^& |" i# ]
/*********************************************/ assign H2L_Sig = !H2L_F1 & H2L_F2;//大家可以尝试画一下,当PX_Pin_In 由高到 //时H2L_Sig的电平情况
8 m' w: N- q; w( B /*********************************************/ endmodule |
2 D6 T7 E: c. p7 lb. 波特率定时模块:上文已经介绍过波特率了,这里就举例说明如何设定波特率。# l6 e4 |$ K8 Q+ X# A2 a
波特率定时体现在电路中就是采样率,而采样率又与板子上FPGA的时钟有关,所以第一步你要知道板子上FPGA的晶振多大。比如:48M的晶振,波特率为9600,由于一位数据占用的时间为1/9600=0.001041667s,所以定时需要:N=0.001041667*48M=5000,即5000个时钟的时间就是以为数据占用的时间。
这里还要考虑的一个问题是数据的采集,一般而言,数据采集在“每位数据的中间”进行时比较稳定,如上图所示。所以波特率定时模块产生的定时实在每个位的中间,下面会结合代码进行解释。
module rx_bps_module( CLK,RSTn, Count_Sig, BPS_CLK ); 8 C9 X& p0 D, K: A# Q" V; W# P
input CLK; input RSTn; input Count_Sig;//计时控制标志,高电平启动计时 output BPS_CLK;//波特率时钟,即数据采集控制
9 B3 @5 I" W+ K& n2 u8 N- H /*********************************************/ reg [12:0] Count_BPS;
" ^& ]* x1 M, A2 h# d0 K" J& Q always @(posedge CLK or negedge RSTn) begin if(!RSTn) Count_BPS <= 13'd1; else if(Count_BPS == 13'd5000) Count_BPS <= 13'd1; else if(Count_Sig) Count_BPS <= Count_BPS + 1'b1; else Count_BPS <= 13'd1; end 2 I5 ~- p8 I9 |$ F" C
/*********************************************/ assign BPS_CLK = (Count_BPS == 13'd2500)?1'b1:1'b0;//定时器的一半进行数据采集 " Q$ B9 L2 v/ u2 L
endmodule |
: s* I- Y3 e, O- v4 c6 `: [) b
c. 接收控制模块:主要完成控制信号的产生和数据的采集。这里不多说了,直接上代码。
module rx_control_module( CLK,RSTn, H2L_Sig,RX_Pin_In,BPS_CLK,RX_En_Sig, Count_Sig,RX_Data,RX_Done_Sig ); , ~& B& E7 V* [. G+ C
input CLK; input RSTn; input H2L_Sig;//开始采集标志 input RX_Pin_In;//输入的数据 input BPS_CLK;//数据采集标志 input RX_En_Sig;//数据采集就绪,等待数据,高电平有效 output Count_Sig; output [7:0] RX_Data; output RX_Done_Sig;
+ T& e: j* v" q; D n+ b& S+ T /*********************************************/ reg [3:0] i; reg [7:0] rData; reg isCount; reg isDone; 2 o f6 P$ g5 F
always @(posedge CLK or negedge RSTn) begin if(!RSTn) begin i <= 4'd0; rData <= 8'd0; isCount <= 1'b0; isDone <= 1'b0; end else if(RX_En_Sig) case(i) 4'd0: if(H2L_Sig)//起始位开始标志 begin i <= i + 1'b1; isCount <= 1'b1; end
5 G! g/ i1 _' x: T8 A 4'd1: if( BPS_CLK ) //起始位,无数据,采取忽略策略 begin i <= i + 1'b1; end ! m; Z0 [2 T' V- _+ P. r
4'd2,4'd3,4'd4,4'd5,4'd6,4'd7,4'd8,4'd9://八位数据位的读取 if(BPS_CLK) begin i <= i + 1'b1; rData[i-2] <= RX_Pin_In; end
/ [/ Q0 S9 O V6 Z4 q 4'd10: if( BPS_CLK )//校验位,忽略 begin i <= i + 1'b1; end , b# @5 }7 g# k; J9 H$ Y
4'd11 : if( BPS_CLK ) //结束位,忽略 begin i <= i + 1'b1; end & C o# D; C! w) ?
4'd12://结束位之后,一帧数据完成标志isDone置为高电平,关闭计数器 begin i <= i + 1'b1; isDone <= 1'b1; isCount <= 1'b0; end ) x8 S7 W! c6 }; A. |/ i
4'd13://isDone置为低电平,等待下一帧数据 begin i <= 1'b0; isDone <= 1'b0; end
9 z7 C& I. Z& l endcase end 8 x6 L% V4 t2 ?) S( h; s( \$ ?
/*********************************************/ assign Count_Sig = isCount; assign RX_Data = rData; assign RX_Done_Sig = isDone;
) V; l# m- X! v /*********************************************/ endmodule |
# E; }- M. C# F2 x) ?小结:以上就是串口读取数据的模块,关于如何在中间采集数据,大家可以画一个图,一看就清楚了。可以将三个模块进行一下组合,如下:
关于波特率:我的理解就是发送和接收端的数据统一,能够使得发送的数据在接收端能够准确的在以为数据的中间就行采集到。
1 j/ Q$ F- h `$ \9 o9 g- @ B) D: [6 K3 w
三、串口接收实验
1、下图是通过串口来控制LED的实验框图,其中串口接收模块是上面三个模块的组合模块,而控制模块主要是来产生RX_En_Sig信号和接收数据并进行相应处理。这里的数据来源于PC端,由串口调试助手来完成。
2、控制模块
module control_module( CLK, RSTn, RX_Done_Sig, RX_Data, RX_En_Sig, Number_Data );
/ X% Q0 s+ @# q7 X8 ] input CLK; input RSTn; input RX_Done_Sig; input [7:0] RX_Data; output RX_En_Sig; output [7:0]Number_Data; 2 Q' s5 w y, ?* o6 I
/*********************************************/ reg [7:0]rData; reg isEn; a5 Y( n- K, ~( N' |9 }
always @ ( posedge CLK or negedge RSTn ) if( !RSTn ) rData <= 8'd0; else if( RX_Done_Sig ) begin rData <= RX_Data; // 数据接收完成后, isEn <= 1'b0; end else isEn <= 1'b1; 9 ?* y% R4 p9 X1 w3 l) @5 ^
/*********************************/ assign Number_Data = rData; assign RX_En_Sig = isEn;
1 \ X; ~$ m7 e# _3 H/ V /*********************************/ endmodule |
+ G/ [4 h( _, {# n6 ]: t6 U
将该控制模块与上面三个模块的组合模块再进行组合就得到实验电路图:
, m# U5 O d3 A分配好引脚,PX_Pin_In接到FPGA的rx引脚,下载到板子上,打开串口调试助手,以十六进制发送OF,则控制的LED后四位熄灭。
% p8 g0 D, e1 G' @# n2 E+ {说明:该程序我通过自己写的发送模块向该接受模块进行传输数据,LED能够实现预定的功能,但是通过串口调试助手进行发送数据,则不能实现预定功能。大家可以在自己的板子上试试,有什么问题可以指正。谢谢!