FPGA设计中RS232串口的Verilog实现(TX控制器)

RS232串口是一种简单的异步串行通讯方式,虽然传输速率不太高,但因为通讯协议简单,实现起来非常容易,所以在对数据带宽要求不太高的场合得到了非常广泛的应用。今天我们在这里讨论一下RS232串口通讯的Verilog实现。

一.硬件电路:

下面是一个典型的计算机与串口设备的连接示意图。RS232采用DB9或DB25的接口。最简单的连接方法只需要TXD和RXD两根信号线分别传输和接收数据。

FPGA设计中RS232串口的Verilog实现(TX控制器)_第1张图片

现在常用的FPGA的IO电平一般都在1.8/2.5/3.3V,不满足RS232的电平要求,所以我们要通过一个RS232的串口电平转换芯片,将FPGA的IO输出电平转换为RS232的电平。这里我们采用一片MAX3232实现两路串口的电平转换。UART_*直接与FPGA的IO连接,EXT_*与DB9/DB25的RS232接口连接。

FPGA设计中RS232串口的Verilog实现(TX控制器)_第2张图片

要注意的一点是,RS232的电平。逻辑“0”的电平是+5~+15V,逻辑“1”的电平是-5~-15V,是和TTL电平的定义相反的。

二.TXD(发送数据)方的Verilog实现

串口通讯是以字节为单位的。每个字节的传输包括一个起始位,8bit数据位,一到两个的结束位,以及可选的校验位等。下图是一次传输的逻辑波形的定义。在传输中,每一位的宽度相同的,由传输的波特率决定。

FPGA设计中RS232串口的Verilog实现(TX控制器)_第3张图片

对于串口TXD的方向,控制器要根据所要传输的一个字节的数据,生成上面格式的逻辑波形并发送出去。

每次传输除了数据不同外,整个传输的基本格式是一样的。在verilog的实现中,用一个包含有4个状态的状态机来实现一个字节数据的传输。四个状态分别是UART_IDLE,UART_START,UART_DATA和UART_STOP。

当没有数据传输时,串口处于IDLE状态,TXD为高电平。当有一个字节的数据需要传输时,进入UART_START状态,同时生成一个bit的起始位(低电平),然后进入UART_DATA状态,将要传输的一个字节的数据按照从bit0到bit7的顺序,依次放到TXD上,然后进入结束位(这里用一个bit的结束位),当该次传输结束以后,串口重新进入IDLE状态。如果有多个字节的数据需要传输,那么当前一个字节的数据传输结束时(UART_STOP结束),直接进入UART_START状态,开始下一个字节的传输。

下面是状态机的verilog实现(其中FIFO_EMPTY用于表示是否有数据需要传输,UART_DATA_CNT用来选择当前需要传输的数据位)。UART_CLK是串口的时钟,由串口波特率决定。

wire FIFO_EMPTY;

reg [2:0] UART_DATA_CNT;

parameter UART_IDLE = 4'b0001,

UART_START = 4'b0010,

UART_DATA = 4'b0100,

UART_STOP = 4'b1000;

reg [3:0] UARTSM, UARTSMNXT;

wire PHASE_IDLE = UARTSM[0];

wire PHASE_START = UARTSM[1];

wire PHASE_DATA = UARTSM[2];

wire PHASE_STOP = UARTSM[3];

always @(posedge UART_CLK or negedge RESET)

begin

if(~RESET)

UARTSM <= UART_IDLE;

else

UARTSM <= UARTSMNXT;

end

always @( UARTSM or FIFO_EMPTY or UART_DATA_CNT) begin

UARTSMNXT = UARTSM;

case (UARTSM)

UART_IDLE:

if(~FIFO_EMPTY)

UARTSMNXT = UART_START;

else

UARTSMNXT = UART_IDLE;

UART_START:

UARTSMNXT = UART_DATA;

UART_DATA:

if(UART_DATA_CNT == 3'b111)

UARTSMNXT = UART_STOP;

else

UARTSMNXT = UART_DATA;

UART_STOP:

if(~FIFO_EMPTY)

UARTSMNXT = UART_START;

else

UARTSMNXT = UART_IDLE;

default:

UARTSMNXT = UART_IDLE;

endcase

end

UART_DATA状态需要持续8个bit的宽度,在这个状态下,需要将8bit的数据(TX_DATA)一次放到TXD上。可以通过下面的逻辑来实现:

assign TX_DATA_EN = PHASE_START;

always @(posedge UART_CLK or negedge RESET)

begin

if(~RESET)

UART_DATA_CNT <= 3'b0;

else if(PHASE_DATA)

UART_DATA_CNT <= UART_DATA_CNT + 3'b1;

else

UART_DATA_CNT <= 3'b0;

end

reg UART_DATA_BIT;

always @( UART_DATA_CNT or TX_DATA)

begin

case(UART_DATA_CNT)

3'b000:

UART_DATA_BIT = TX_DATA[0];

3'b001:

UART_DATA_BIT = TX_DATA[1];

3'b010:

UART_DATA_BIT = TX_DATA[2];

3'b011:

UART_DATA_BIT = TX_DATA[3];

3'b100:

UART_DATA_BIT = TX_DATA[4];

3'b101:

UART_DATA_BIT = TX_DATA[5];

3'b110:

UART_DATA_BIT = TX_DATA[6];

3'b111:

UART_DATA_BIT = TX_DATA[7];

default:

UART_DATA_BIT = 1'b0;

endcase

end

wire TXD_PRE;

assign TXD_PRE = PHASE_START ? 1'b0 : (PHASE_DATA & UART_DATA_BIT) | PHASE_STOP | PHASE_IDLE;

为了简化时序设计,用下面的逻辑将TXD用register out的方式送到FPGA的IO上。

reg TXD;

always @(posedge UART_CLK or negedge RESET)

begin

if(~RESET)

TXD <= 1'b1;

else

TXD <= TXD_PRE;

End

一般情况下,串口作为一个通讯模块,都是要为其他功能模块服务的,功能模块产生数据,送给串口,串口将数据发送给上位机或者其他设备。那么在设计的过程中,就需要注意功能模块与串口之间的数据传输的问题。一般情况下,功能模块和串口的工作时钟都不会相同,所以两个模块之间的数据可能要以异步方式传输。这里我们在串口中直接实现一个8bit位宽的异步FIFO。功能模块可以直接将数据写入FIFO,当FIFO不为空的情况下,串口就直接从FIFO中读取数据并发送出去。

由于本文主要是介绍串口的实现的,对于异步FIFO的实现,就不做过多的讨论,直接采用ALTERA的异步FIFO的IP核生成。

asyn_fifo uart_tx_fifo (

.aclr ( FIFO_CLR ), //FIFO Clear,可以由RESET生成

.data ( DATA_IN ), //数据输入,由功能模块生成,8bit位宽

.rdclk ( UART_CLK ), //串口时钟,与波特率相关

.rdreq ( TX_DATA_EN ), //数据输出使能信号,从FIFO中输出数据

.wrclk ( CLK ), //数据输入时钟,与功能模块的时钟相关

.wrreq ( DATA_EN ), //数据输入使能信号

.q ( FIFO_DATA_OUT ), //FIFO数据输出,送串口

.rdempty ( FIFO_EMPTY ), //FIFO empty信号,非空时,串口即要启动

.wrfull ( FIFO_FULL ) //FIFO full信号,如果出现该种情况,则说明波特率太低,串口无法满足数据传输要求。

);

下图是从示波器上抓取的串口单个字节的传输波形。波特率为115200,白色为FPGA IO的3.3V TTL输出,红色为MAX232的RS232电平输出。

FPGA设计中RS232串口的Verilog实现(TX控制器)_第4张图片

你可能感兴趣的:(fpga)