FPGA开发基础篇之一(接口篇)UART串口

写在前面

从本文开始,将连载fpga开发基础知识,将这几年浅显的fpga开发经验整理出来,一是梳理一下这几年给别人做fpga的经历,同时也是分享给大家,也希望大牛批评指正。

一、UART串口通信基本概念

串口通信是非常基本且应用十分广泛的低速通信接口,无论是在dsp、单片机、arm还是在fpga中,编写uart串口通信程序是必备的基础。

首先要先了解UART串口通信的基本概念,UART串口通信是全双工的,支持发送和接收通信同时进行。硬件上UART串口只需要两条线tx和rx,分别进行发送和接收。UART串口通信没有同步时钟线,这就需要引入一个概念波特率来区分两位数据实现串行通信,波特率是指每秒传输的位数。常见的有9600bps、115200bps、460800bps等等。在fpga实现中,只需要理解每一位的时间长度是1/波特率,计数长度是时钟频率/波特率就可以了。

UART串口通信基本协议,一次UART串口发送和接收一般包含10bit或11bit,包含校验位是11bit,不包含则是10bit。有效数据位一般是8bit。依次为起始位、数据位、校验位(可选)、停止位。

起始位:首先发送的第一位,必须为0,提示接收方开始接收有效数据。

数据位:一般为8位,有效数据位。

校验位:可选位,一般为奇校验、偶校验、固定校验。校验位由数据位中1的个数来决定

停止位:最后发送的一位,必须为1,提示接收方结束接收。

包含以上所有位为一帧数据,经过一个间隔后发送或接收第二帧。由以上的内容我们可以知道如何发送和接收一帧数据了。接下来就是用fpga实现。

二、UART串口通信FPGA实现

首先编写发送模块,先将发送数据缓存,状态机由空闲状态转为发送状态,空闲状态指示信号拉低,通知上层模块发送正在进行不要更新待发送字节。根据波特率进行计数,依次发送一帧数据的所有位,发送完成后状态机转入空闲拉高空闲状态,一次发送就完成了。

//
//                                                                              //
//                                                                              //
// This source file is part of uart module.                                        //
// module features: 1.config bits number of one byte                              //
//                      2.config bitrate                                            //
// module function: transmit uart bits                                            // 
// copyright of this source file belongs to mayidianzi in CSDN blog                //
// contact the author by e-mail [email protected]                                //
//                                                                              //
//

//================================================================================
//  Revision History:
//  Date              By                Revision        Change Description
//--------------------------------------------------------------------------------
//    2023/2/26       mayidianzi      1.0              Original
//*******************************************************************************/
module uart_tx
#(
    parameter                                 CLK_FRE            =    50,              //module clock frequency(MHz)
    parameter                                 BAUD_RATE         =    115200,            //serial baud rate
    parameter                                 TRANSMIT_BITS      =    9                //bits number per byte
)
(
    input                                    clk,                  //clock input
    input                                    rst_n,                //asynchronous reset input, low active 
    input[TRANSMIT_BITS-1:0]                tx_data,              //data to send
    input                                    tx_data_valid,        //data to be sent is valid
    output reg                               tx_data_ready,        //send ready
    output                                   tx_pin                //serial data output
);

/ parameter config \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//calculates the clock cycle for baud rate 
localparam                       CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine code
localparam                       C_IDLE       = 1;//wait for transmit
localparam                       C_START      = 2;//start bit
localparam                       C_SEND_BYTE  = 3;//data bits
localparam                       C_STOP       = 4;//stop bit
localparam                       C_WAIT       = 5;//blank between two bytes transmit

// register define \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

reg[2:0]                         state;
reg[15:0]                        cycle_cnt; //baud counter
reg[3:0]                         bit_cnt;//bit counter
reg[TRANSMIT_BITS-1:0]           tx_data_latch; //latch data to send
reg                              tx_reg; //serial data output
assign tx_pin = tx_reg;

//    main code     \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//state machine
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            state        <=    C_IDLE;
            cycle_cnt    <=    0;
            bit_cnt        <=    0;
        end
    else
        case(state)
            C_IDLE:
                if(tx_data_valid == 1'b1)
                    state <= C_START;
                else
                    state <= C_IDLE;
            C_START:
                if(cycle_cnt == CYCLE - 1)
                    begin
                        cycle_cnt    <=    0;
                        state         <=     C_SEND_BYTE;
                    end
                else 
                    cycle_cnt    <=    cycle_cnt + 1;
            C_SEND_BYTE:
                if(cycle_cnt == CYCLE - 1 && bit_cnt == TRANSMIT_BITS-1)
                    begin
                        state         <=     C_STOP;
                        cycle_cnt    <=    0;
                        bit_cnt        <=    0;
                    end
                else if(cycle_cnt == CYCLE - 1)
                    begin
                        cycle_cnt    <=    0;
                        bit_cnt        <=    bit_cnt + 1;
                    end
                else
                    cycle_cnt    <=    cycle_cnt + 1;
            C_STOP:
                if(cycle_cnt == CYCLE - 1)
                    begin
                        state         <= C_WAIT;
                        cycle_cnt    <=    0;
                    end
                else
                    cycle_cnt    <=    cycle_cnt + 1;
            C_WAIT:
                if(cycle_cnt == 10*CYCLE - 1)
                    begin
                        state         <= C_IDLE;
                        cycle_cnt    <=    0;
                    end
                else
                    cycle_cnt    <=    cycle_cnt + 1;
            default:
                state <= C_IDLE;
    endcase
end

//data output
always@(posedge clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
        tx_data_ready <= 1'b0;
    else if(state == C_IDLE)
        if(tx_data_valid == 1'b1)
            tx_data_ready <= 1'b0;
        else
            tx_data_ready <= 1'b1;
    else if(state == C_WAIT && cycle_cnt == 10*CYCLE - 1)
            tx_data_ready <= 1'b1;
end


always@(posedge clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
        begin
            tx_data_latch <= 0;
        end
    else if(state == C_IDLE && tx_data_valid == 1'b1)
            tx_data_latch <= tx_data;
        
end

always@(posedge clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
        tx_reg <= 1'b1;
    else
        case(state)
            C_IDLE,C_STOP:
                tx_reg <= 1'b1; 
            C_START:
                tx_reg <= 1'b0; 
            C_SEND_BYTE:
                tx_reg <= tx_data_latch[bit_cnt];
            default:
                tx_reg <= 1'b1; 
        endcase
end

endmodule 

然后再来编写接收模块,接收模块状态机要检测起始位,发现信号拉低后进入接收状态,按照波特率进行计数依次接收所有位,之后转入空闲状态等待下一个起始位,同时拉高数据接收完毕信号提示上层锁存接收数据。

//
//                                                                              //
//                                                                              //
// This source file is part of uart module.                                        //
// module features: 1.config bits number of one byte                              //
//                      2.config bitrate                                            //
// module function: receive uart bits                                            //
// copyright of this source file belongs to mayidianzi in CSDN blog                //
// contact the author by e-mail [email protected]                                //
//                                                                              //
//

//================================================================================
//  Revision History:
//  Date              By                Revision        Change Description
//--------------------------------------------------------------------------------
//    2023/2/26       mayidianzi      1.0              Original
//*******************************************************************************/
module uart_rx
#(
    parameter                                 CLK_FRE            =    50,              //module clock frequency(MHz)
    parameter                                 BAUD_RATE        =    115200,         //serial baud rate
    parameter                                 RECEIVE_BITS      =    9                //bits number per byte
)
(
    input                                    clk,                  //clock input
    input                                    rst_n,                //asynchronous reset input, low active 
    output reg[RECEIVE_BITS-1:0]            rx_data,              //received serial data
    output reg                               rx_data_valid,        //received serial data is valid
    input                                    rx_pin                //serial data input
);

/ parameter config \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//calculates the clock cycle for baud rate 
localparam                       CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine
localparam                       C_WAIT      = 0; //blank between two bytes
localparam                       C_IDLE      = 1; //wait for start bit
localparam                       C_START     = 2; //start bit
localparam                       C_REC_BYTE  = 3; //data bits
localparam                       C_STOP      = 4; //stop bit
localparam                       C_DATA      = 5; //data pack

// register define \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

reg[2:0]                         state;
reg[RECEIVE_BITS-1:0]            rx_bits;          //temporary storage of received data
reg[15:0]                        cycle_cnt;        //baud counter
reg[3:0]                         bit_cnt;          //bit counter

//    main code     \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

//state machine
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            state        <=    C_WAIT;
            cycle_cnt    <=    0;
            bit_cnt        <=    0;
        end
    else
        case(state)
            C_WAIT:                                            //wait for blank between two bytes
                if(cycle_cnt > CYCLE & rx_pin == 1)
                    begin
                        cycle_cnt    <=    0;
                        state        <=    C_IDLE;
                        bit_cnt        <=    0;
                    end
                else if(rx_pin == 1)
                    cycle_cnt    <=    cycle_cnt + 1;
                else
                    cycle_cnt    <=    0;
            C_IDLE:                                            //wait for first start bit
                if(rx_pin == 0)
                    state <= C_START;
                else
                    state <= C_IDLE;
            C_START:                                        //start bit
                if(cycle_cnt == CYCLE - 1)                    //one data cycle 
                    begin
                        state         <= C_REC_BYTE;
                        cycle_cnt    <=    0;
                    end
                else
                    cycle_cnt    <=    cycle_cnt + 1;
            C_REC_BYTE:
                if(cycle_cnt == CYCLE - 1 && bit_cnt == RECEIVE_BITS-1)  //receive bits data
                    begin
                        state         <=     C_STOP;
                        bit_cnt        <=    0;
                        cycle_cnt    <=    0;
                    end
                else if(cycle_cnt == CYCLE - 1)
                    begin
                        cycle_cnt    <=    0;
                        bit_cnt        <=    bit_cnt + 1;
                    end
                else
                    cycle_cnt    <=    cycle_cnt + 1;
            C_STOP:
                if(cycle_cnt == CYCLE - 1)
                    begin
                        state         <=     C_DATA;
                        cycle_cnt    <=    0;
                    end
                else
                    cycle_cnt    <=    cycle_cnt + 1;
            C_DATA:                                                        //data receive complete 
                state <= C_IDLE;
            default:
                state <= C_WAIT;
    endcase
end

//data ready signal
always@(posedge clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
        rx_data_valid <= 1'b0;
    else if(state == C_STOP)
        rx_data_valid <= 1'b1;
    else if(state == C_DATA)
        rx_data_valid <= 1'b0;
end

//data output
always@(posedge clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
        rx_data <= 8'd0;
    else if(state == C_STOP)
        rx_data <= rx_bits;//latch received data
end

//receive serial data bit data
always@(posedge clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
        rx_bits <= 8'd0;
    else if(state == C_REC_BYTE && cycle_cnt == CYCLE/2 - 1)
        rx_bits[bit_cnt] <= rx_pin;
    else
        rx_bits <= rx_bits; 
end
endmodule 

三、后记

以上所有内容均记载在我的串口通用模块内,可以直接下载测试工程了解,或根据自己的工程需要进行改编。如果不想在串口通信方面耽误时间,集中力量开发高级功能,可直接调用我编写的通用串口模块,可直接进行例化实现串口通信功能。通用串口模块可分别自定义收发波特率、收发校验种类,具备串口通信常用的全部功能,可根据实际需求设置,减少您的开发周期,本通用串口模块经过仿真模拟和硬件测试,附有测试报告和开发说明,方便使用,传送门:

通用串口模块

你可能感兴趣的:(FPGA开发基础篇,接口篇,fpga开发)