文章摘要:
串口应该是计算机最古老的通信接口之一,早期的应用无处不在,虽然目前有一点被USB、以太网以及无线接口等这些后浪拍死在沙滩上的苗头,但由于其协议的简单、简单、简单(重要的事说三遍)及可靠,所以在某些领域仍具有相当广泛的应用。

当然这都不重要,本文的重点是通过描述串口发送模块的方法,来简单说明一下基于FPGA的通信协议的实现,并对后续的其他接口通信达到抛砖引玉、触类旁通的效果;

知识重点: 状态机,边沿检测,串并转换;


硬件平台: EP4CE6F17C8
开发环境: Quartus II 13.1


时序图:

串口时序

对于一个合格的老司机来说,串口时序图应该早已烂熟于心了,不过,子又曾经曰过:“有图有真相”,所以为了对照代码,看图说话,还是放上来充数罢;


/* 
 * 模块说明:串口发送模块(9600-N-8-1)
 */
module uart_send(
    input clk,          // 时钟
    input rst_n,        // 复位,低电平有效
    output reg txd,     // 发送引脚         
    input [7:0]data,    // 发送数据
    input  en,          // 发送使能,高电平有效
    output busy         // 发送忙标志(高电平表示忙)
);

// 发送时序状态机
parameter S_IDLE    = 4'd0;
parameter S_START   = 4'd1;
parameter S_BIT0    = 4'd2;
parameter S_BIT1    = 4'd3;
parameter S_BIT2    = 4'd4;
parameter S_BIT3    = 4'd5;
parameter S_BIT4    = 4'd6;
parameter S_BIT5    = 4'd7;
parameter S_BIT6    = 4'd8;
parameter S_BIT7    = 4'd9;
parameter S_STOP    = 4'd10;

reg [3:0] status;       // 当前状态

// 波特率计算 9600bps即每bit时间为1/9600 S
// 50MHz时钟下,计数值为50_000_000/9600 = 5208
parameter BAUD_CNT = 32'd5208;

reg [31:0]timer;        // 波特率定时器
reg [7:0]rdata;         // 数据寄存器,防止发送过程中修改了数据端口

// 发送忙标志位
assign busy = (status == S_IDLE)?1'b0:1'b1;

always @(posedge clk or negedge rst_n)
begin
    if(~rst_n)
    begin
        status <= S_IDLE;   // 复位时设置为空闲状态
        txd <= 1'b1;        // 复位时发送引脚为高电平
        timer <= 0;
    end
    
    else
    begin       
        timer <= timer + 1'b1;  // 计时       
        
        // 发送状态机
        case(status)
        S_IDLE:
        begin       
            if(en)  // 只有空闲状态时,收到使能信号,才启动发送
            begin
                timer  <= 0;
                rdata  <= data;     // 保存待发数据
                status <= S_START;  // 启动发送
            end
            else
                status <= S_IDLE;       // 这里主要是设计风格
        end     
        S_START:
        begin
            if(timer == BAUD_CNT)   // 1bit起始位时间
            begin
                timer <= 0;
                status = S_BIT0;
            end
            else    
                txd <= 1'b0;    // 发送起始位    
        end
        S_BIT0:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT1;
            end
            else
                txd <= rdata[0];
        end
        S_BIT1:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT2;
            end
            else
                txd <= rdata[1];
        end
        S_BIT2:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT3;
            end
            else
                txd <= rdata[2];
        end
        S_BIT3:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT4;
            end
            else
                txd <= rdata[3];
        end
        S_BIT4:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT5;
            end
            else
                txd <= rdata[4];
        end
        S_BIT5:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT6;
            end
            else
                txd <= rdata[5];
        end
        S_BIT6:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT7;
            end
            else
                txd <= rdata[6];
        end
        S_BIT7:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_STOP;
            end         
            else
                txd <= rdata[7];
        end
        S_STOP:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_IDLE;
            end 
            else
                txd <= 1'b1;    // 发送停止位
        end
        default:
            status <= S_IDLE;
        endcase
    end
end

endmodule