## 串行接口实验
### 1. 实验内容
本实验要求熟悉串行接口通信的基本原理,实现利用串行接口与计算机的通信,通过键盘输入的数据通过PC上的串行接口发送给FPGA,FPGA收到数据以后再将该数据通过串行接口发送给计算机,并通过超级终端显示出来,如图所示。
{{ :图14-1.png |图14-1 串口通信实验要求}}
**图14-1 串口通信实验要求**
\\
\\
### 2. 实验原理
串行接口(简称串口)通信是目前最常用的一种低速短距离通信方式,常用于对各种设备进行操作的控制台,是UART(Universal Asynchronous Receiver/Transmitter,通用异步接收/发送装置)的一种具体实现方式,最常用的就是计算机上具备的RS232接口(图14-1)。本实验的采用的就是RS232接口,RS232接口由9针,其各引脚定义如表14-1所示。通常只使用其中的2、3、5三个管脚。
{{ :图14-2.jpg |图14-2 RS232接口}}
**图14-2 RS232接口**
**表14-1 RS232管脚定义**
|1|载波检测 DCD|
|2|接收数据 RXD|
|3|发送数据 TXD|
|4|数据终端准备好 DTR|
|5|信号地 SG|
|6|数据准备好 DSR|
|7|请求发送 RTS|
|8|清除发送 CTS|
|9|振铃提示 RI|
串口是异步通信方式,通信的发送方和接收方各自有独立的时钟,传输的速率由双方约定。UART的通信协议十分简单,以低电平作为起始位,高电平作为停止位,中间可传输5~8比特数据和1比特奇偶校验位,奇偶校验位的有无和数据比特的长度由通信双方约定。一帧数据传输完毕后可以继续传输下一帧数据,也可以继续保持为高电平,两帧之间保持高电平,持续时间可以任意长。本实验采用不添加校验位的方法,以提高数据传输效率。发送端发送数据时先发一低电平,然后发送8比特数据,之后马上把信号拉高,从而完成一帧数据传送。接收端接收到低电平时开始计数,然后接收8比特信息位后如果检测到高电平即认为已接收完一帧数据,继续等待下一帧起始信号低电平的到来,若接收完8比特数据后没有检测到高电平则认为这不是一帧有效数据,将其丢弃,继续等待起始信号。时序关系如图14-3所示,收发可同时进行,互不干扰。本实验的波特率设为9600。
{{ :图14-3.png |图14-3 RS-232接口的工作时序}}
**图14-3 RS-232接口的工作时序**
\\
\\
### 3. 程序设计
#### 3.1 总体架构
本实验的要求时在计算机串口控制台上显示输入的按键,即程序在收到从计算机发来的用户按键后将其原封不动地发回给计算机,计算机收到后将显示其收到的数据。因此,本程序的核心模块就是串口的收发模块,程序架构如图14-4所示。顶层模块为UART(UART.v),其中只包含一个模块miniUART,miniUART的功能是实现数据在串口上的收发。UART模块只需将输入数据作为输出数据送给miniUART。
{{ :图14-4.png |图14-4 程序总体架构}}
**图14-4 程序总体架构**
UART模块的对外接口非常简单,只有4根信号:
input clk; //异步复位信号
input reset; //时钟信号,50MHZ
input Rxd; //UART串行数据输入端口
output Txd; //UART串行数据输出端口
#### 3.2 miniUART模块(miniUART.v)
miniUART是程序的核心模块,完成串口上数据的接收和发送,其由3个子模块组成(图14 5):\\
1. 波特率发生器clkUnit,波特率发生器产生发送和接收所需要的时钟信号。\\
2. 接收模块Rx_Unit负责串口上数据的接口。\\
3. 发送模块Tx_Unit负责串口上数据的接收。
{{ :图14-5.png |图14-5 miniUART模块结构}}
**图14-5 miniUART模块结构**
miniUART的输入输出接口如下:
input clk; //时钟信号,50MHZ
input reset; //异步复位信号
input Rxd; //接收模块串行数据输入端口
output Txd; //发送模块串行数据输出端口
input [7:0]DataIn; //串行模块接收到的数据
output rx_data_rdy //接收数据使能信号,为高表示DataIn有效。
output [7:0]DataOut; //串行模块将要发送的数据
output tx_data_en; //发送数据使能信号,为高表示DataOut有效。
#### 3.3 ClkUnit模块(clkUnit.v)
clkUnit模块产生用于传输数据和接收数据的时钟,频率分别是9.6KHz和9.6×8KHz,对应的输出是clk_Tx和clk_Rx,正脉冲宽度为1个clk(50MHz)时钟周期。
#### 3.4 Rx_Unit模块(UART_Rx_Unit.v)
Rx_Unit完成串行数据的接口,其接口定义如下:
input clk; //时钟信号,50MHz。
input rst; //异步复位端
input serial_in; //串口输入。
input sample_clk; //串口接收时钟,9.6K×8Hz。
output [7:0]data_out; //从串口接收到的数据通过data_out输出给其他模块。
output data_rdy_out; //dataout使能信号,为高时表示dataout数据有效,宽度为1个clk时钟周期。
接收模块由以下几个模块组成(图14 6):metastable_state_eliminator消除亚稳定,由于串口是异步输出,通过多级触发器消除亚稳态;start_trigger该模块检测串口线上是否有输入;bit_Rx该模块负责接收单比特数据;data_rx该模块负责接收整个字节的数据;rx_data_en_regulation模块将脉宽为一个sample_clk的数据输出使能信号整形为一个clk时钟周期。
接收模块中除了rx_data_en_regulation模块外,其它都是以sample_clk作为工作时钟。
{{ :图14-6.png |图14-6 接收模块内部框图}}
**图14-6 接收模块内部框图**
1.metastable_state_eliminator模块\\
metastable_state_eliminator模块通过两级D触发器来消除亚稳态,同时输出两路串行输出serial_2_delay和serial_3_delay,分别对serial_in延时了两个时钟周期和3个时钟周期。
reg serial_1_delay, serial_2_delay,serial_3_delay;
always @ ( posedge sample_clk or negedge rst ) begin : metastable_state_eliminator
if( !rst ) begin
serial_1_delay <= 0;
serial_2_delay <= 0;
end
else begin
serial_1_delay <= serial_in; //第一级延迟。
serial_2_delay <= serial_1_delay; //第二级延迟。
serial_3_delay <= serial_2_delay; //第三级延迟。
end
end
2. start_trigger模块\\
start_trigger模块判断输入的serial_2_delay是否为低电平。如果serial_2_delay为低电平,同时判断data_rx模块的状态Recv_state是否处于空闲状态,如果两者皆满足,则有可能有新的数据到来,发出新数据到来的使能信号new_data_in给data_rx模块,通知data_rx开始接收数据。
reg new_data_in;
always @ ( posedge sample_clk or negedge rst ) begin : start_trigger
if( !rst ) begin
new_data_in <= 0;
end
else begin
new_data_in <= 0; //确保new_data_in为一个sample_clk周期。
//当recv_state在空闲状态,且serial_2_delay为0时,
//置new_data_in为高。
if( Recv_state == idle && ! serial_2_delay ) begin
new_data_in <= 1;
end
end
end
3. data_rx模块\\
data_rx模块由new_data_in触发工作,data_rx的由状态机Recv_state控制工作,状态机如图14 7所示。缺省情况下,data_rx处于idle状态,当有数据到来时new_data_in变高,触发data_rx进入startx状态。在starting状态,data_rx接收数据,recv_counter是接收比特计数,当接收到9个比特的数据时,data_rx回到idle状态。Data_rx模块需要产生get_bit_en信号通知bit_rx接收一个比特的数据。当收到bit_rx模块发来的接收错误指示error_in时,也立刻反馈idle状态。
{{ :图14-7.png |图14-7 Data_rx模块状态机}}
**图14-7 Data_rx模块状态机图**
reg Recv_state;
reg [3:0] Recv_counter;
always @ ( posedge sample_clk or negedge rst ) begin: data_rx
if( !rst ) begin
RCV_shftreg <= 8'hff;
Recv_state <= 0;
Recv_counter <= 0;
Data_rdy <= 0;
get_bit_en <= 0;
Data_out <= 0;
end
else begin
Data_rdy <= 0;
get_bit_en <= 0; //使得使能脉冲为一个sample周期。
if( error_in ) begin //如果bit_tx接收数据发现错误,则进入ilde状态。
RCV_shftreg <= 8'hff;
Recv_state <= 0; //idle。
Recv_counter <= 0;
Data_rdy <= 0;
end
else begin
case(Recv_state)
3'd0: begin //idle
if( new_data_in ) begin //有新数据到来,进入starting状态。
get_bit_en <= 1; //通知bit_rx接收数据。
Recv_state <= 1; //starting。
end
end
3'd1: begin //starting。
if( bit_in_en ) begin //如果有数据接收完毕。
if( Recv_counter == 4'd9 ) begin //如果接收数据完毕。
//如果停止位为1,则将接收到的数据RCV_shftreg
//赋给Data_out,并置使能信号Data_rdy。
if( bit_in ) begin
Data_out <= RCV_shftreg;
Data_rdy <= 1;
End
//回到idle状态。
RCV_shftreg <= 8'hff;
Recv_state <= 0;
Recv_counter <= 0;
end
else begin
//将接收到的比特赋给RCV_shitreg的最高位,然后
//通知bit_tx接收下一比特数据。
RCV_shftreg <= {bit_in, RCV_shftreg[Datasize-1:1]};
Recv_counter <= Recv_counter + 1;
get_bit_en <= 1;
end
end
end
endcase
end
end
end
4. bit_rx模块\\
bit_rx接收单比特数据,由get_bit_en触发工作,由状态机bit_rx_state控制工作(图14 8)。初始时状态机处于waiting状态。当收到data_rx发来的get_bit_en信号时,状态机进入confirming的状态。在confirming状态,bit_rx对收到的数据进行确认,即连续4个时钟周期收到的数据都相同时,确认一个比特的数据接收正确。在停留到第6个时钟周期时,返回waiting状态,并产生bit_in_enable信号通知data_rx一个比特数据接收完成。。如果发现连续4个时钟周期收到的数据有不相同时,则认为数据接收错误,立即反馈waiting状态,并产生error_in信号。
{{ :图14-8.png |图14-8 Bit_rx模块状态机}}
**图14-8 Bit_rx模块状态机**
reg bit_in_old,bit_in, bit_in_en;
reg [3:0] checkcounter;
reg bit_Rx_state, get_bit_en, error_in;
always @ ( posedge sample_clk or negedge rst ) begin : bit_Rx
if( !rst ) begin
bit_Rx_state <= waiting;
bit_in_old <= 0;
bit_in <= 0; //接收到的1比特数据。
checkcounter <= 0; //表示对数据确认次数,4次接收的数据全一致则认为数据正确。
error_in <= 0; //指示数据接收错误。
bit_in_en <= 0; //1比特数据接收完成。
end
else begin
error_in <= 0;
bit_in_en <= 0;
case( bit_Rx_state )
waiting: begin
//如果data_rx模块通知接收数据,则进入confirming状态。
if( get_bit_en ) begin
bit_Rx_state <= confirming;
bit_in_old <= serial_3_delay;
checkcounter <= 1;
end
end
confirming: begin
//如果在4次以内接收的数据有不一致的地方则认为数据接收错误,返回waiting
//状态并将error_in置位。
if( checkcounter <= 4'd4 && bit_in_old != serial_3_delay ) begin
bit_Rx_state <= waiting;
bit_in_old <= 0;
checkcounter <= 1;
error_in <= 1;
end
else begin
//当checkcounter计数到6时,输出数据给data_rx。
if( checkcounter == 4'd6 ) begin
checkcounter <= 0;
bit_in_en <= 1;
bit_Rx_state <= waiting;
bit_in_old <= 0;
end
else begin
//如果4次数据确认都正确,则将收到数据赋给bit_in。
if( checkcounter == 4'd4 ) begin
bit_in <= bit_in_old;
end
checkcounter <= checkcounter + 1;
end
end
end
endcase
end
end
#### 3.5 Rx_Unit模块(UART_Rx_Unit.v)
Rx_Unit模块完成串口数据的发送,其接口定义如下:
input clk; //时钟信号,50MHz。
input rst; //异步复位信号。
input clk_tx; //9.6KHz的时钟脉冲信号。
input [7:0] data_in; //要发送的数据。
input data_en; //发送数据使能,为高表示data_in有效。
Out serial_out //串行输出。
Rx_Unit模块由状态机TX_state控制(图14 9),Tx_State有四个状态:\\
1) Idle状态:在初始时,状态机处于idle状态。当有数据从串口输出,即data_en为高时,进入starting状态。\\
2) Starting状态: 在Starting状态,发送起始位0,之后进入sending_data状态。\\
3) Sending_data状态:在sending_data状态,发送8比特数据。当8比特数据发送完成后,进入stopping状态。\\
4) 在stopping状态,发送停止位,而后回到idle状态。
{{ :图14-9.png |图14-9 发送模块状态机}}
**图14-9 发送模块状态机**
reg [1:0] TX_state;
reg [7:0] data_sent;
always @ ( posedge clk or negedge rst ) begin
if( !rst ) begin
TX_state <= idle;
serial_out <= 1;
data_sent <= 0; //要发送的数据。
bitnumber <= 0;
end
else begin
case(TX_state )
idle: begin
if( data_en ) begin //将data_en时,进入starting状态。
TX_state <= starting;
data_sent <= data_in;
end
end
starting: begin
if( clk_Tx ) begin //clk_tx为9.6KHz时钟,有clk_tx触发发送。
serial_out <= 0; //发送起始位0。
TX_state <= sending_data; //进入sending_data状态。
bitnumber <= 0; //发送比特计数0。
end
end
sending_data : begin
if( clk_Tx ) begin
serial_out <= data_sent[0]; //发送1比特数据。
//data_sent右移一位,下一发送比特赋给data_sent[0]。
data_sent <= {1'b1, data_sent[7:1]};
bitnumber <= bitnumber + 1;
//当8比特发送完成后,进入stopping状态。
if( bitnumber == 4'd7 ) begin
TX_state <= stopping;
end
end
end
stopping : begin
//在stopping状态,发送停止位,进入idle状态。
if( clk_Tx) begin
TX_state <= idle;
serial_out <= 1;
data_sent <= 0;
bitnumber <= 0;
end
end
endcase
end
end
#### 3.6 程序使用及设置要求
1. 设备要求:核心板、串口线(图14 11),连接串口线时注意黑线接地。
{{ :图14-10.png |图14-10 串口线}}
**图14-10 串口线**
2. 使用步骤:把串口线连接好,把程序下载到实验板上以后,打开windows系统自带的串口调试工具超级终端,建立新连接,从键盘输入信息,实现计算机和FPGA之间的通信。具体如下:
“开始”—〉“所有程序”—〉“附件”—〉“通讯”—〉“超级终端”,出现如所图14-11图14-11示画面。
{{ :图14-11.png |图14-11 建立串口连接}}
**图14-11 建立串口连接**
这里为新建连接起名字为“UART”,然后选择COM1,有的计算机有多于1个的串口,这时要注意自己连接的是哪一个串口(图14-12)。
{{ :图14-12.png |图14-12 选择串口}}
**图14-12 选择串口**
设置串口属性时,如果不知道该设置什么样的值,可以直接按“还原默认值”,系统会自动设置最保守的符合要求的值(图14-13)。
{{ :图14-13.png |图14-13 串口设置}}
**图14-13 串口设置**
设置完成后,下载程序,可以看到超级终端中会显示用户的按键(图14-14)。
{{ :图14-14.png |图14-14 运行结果}}
**图14-14 运行结果**
\\
\\
### 4. 演示程序文件说明
|文件名|功能|
|UART.v|UART模块。|
|ClkUnit.v|产生发送数据和接收数据所需的时钟;在本实验中配置的时钟是9600波特,可以通过修改该模块里面的分频数值来设置波特率。|
|Rx_Unit.v|接收数据,把串行输入数据转换成并行数据后输出;|
|Tx_Unit.v|发送数据,把并行输入数据转换成串行数据后输出|
|miniUART.v|miniUART模块。|
\\
\\
### 5. 注意事项
由于本实验中使用了两个时钟:clk和clk_rx,因此需要在Quartus II中设置双时钟。具体设置如下:
Assignment→Setting→Timing Analysis Setting→Classic Timing Analyzer Settings→Individual Clocks,在里面分别添加时钟clk和clk_rx,频率分别设为50MHz和1MHz。