文章摘要:
本文主要描述了DS1302实时时钟(RTC,Real-Time Clock)芯片的应用方法;


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


重点内容:
SPI(Master)控制时序;
SPI接口芯片读写时序;
双向IO(inout)的处理(特别重要,以后还要用到);


时序图:

DS1302时序图
CE信号:高电平有效;
写操作:低电平准备,上升沿送出;
读操作:设备在下降沿时送出数据(在上升沿或者下一个下降沿之前读取);
SPI模式: CPOL = 0, CPHA = 0

时钟极性CPOL: 即SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)
时钟相位CPHA: 即SPI在SCLK第几个边沿开始采样(0:第一个边沿开始; 1:第二个边沿开始)


注意事项:

读寄存器操作只有15个时钟,发送字地址结束后的下降沿,DS1302就开始将第一个数据送出,主机应该在下一个下降沿之前读取数据即可;之前因为时序图看的不仔细,这里折腾了很久;


SPI模块:

/*
 *  功能描述:简单的SPI通信模块
 */
module spi_master(
    clk,    // 模块时钟
    rst_n,  // 模块复位
    sclk,   // SPI_CLK
    mosi,   // 主发从收
    miso,   // Master In Slave Out
    
    wdata,  // 待发的数据
    rdata,  // 收到的数据
    wr_req, // SPI发送
    rd_req, // SPI接收
    done,   // SPI操作完成(高电平有效)
    busy    // 繁忙标志 1 - 忙, 0 - 空闲
);

input  clk;
input  rst_n;
output sclk;
input  miso;
output mosi;

input[7:0]  wdata;
output[7:0] rdata;
input wr_req;
input rd_req;
output reg done;
output busy;

reg[7:0] wdata_r;   // 保存待写入的data值
reg[7:0] rdata_r;   // 接收数据寄存器
reg sclk_r;

reg mosi_r;
//--------------------------------------------------

//--------------------------------------------------
// 状态机
parameter S_SPI_IDLE = 4'd0;
parameter S_SPI_SEND = 4'd1;
parameter S_SPI_RECV = 4'd2;

// 500KHz = 2us
parameter SPI_BAUD_CNT = 8'd49;

reg[7:0] mstate;    // 主状态机
reg[7:0] nstate;    // 次状态机

reg[7:0] spi_cnt;   // SPI波特率计时器
reg[4:0] bits;      // 数据位计数
//--------------------------------------------------
assign rdata = rdata_r; 
assign sclk  = sclk_r;
assign mosi  = mosi_r;
assign busy  = (mstate == S_SPI_IDLE) ? 1'b0 : 1'b1;
//--------------------------------------------------
always @(posedge clk or negedge rst_n)
begin
    if(~rst_n) begin
        spi_cnt <= 0;   // 复位  
    end
    
    else if(spi_cnt == SPI_BAUD_CNT) begin    
        spi_cnt <= 0;   // 达到最大值,从新计数
    end
    
    else if(mstate != S_SPI_IDLE) begin    
        spi_cnt <= spi_cnt + 1'b1;  // 非空闲状态才计数   
    end
    
    // 空闲状态,清0计数
    else begin    
        spi_cnt <= 0;       
    end
end
//--------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        mstate  <= S_SPI_IDLE;    
        done    <= 0;
        sclk_r  <= 0;
    end
    
    else begin
        if(mstate == S_SPI_IDLE) begin
            // 只有空闲时刻才能切换模式
            if(wr_req)
               mstate <= S_SPI_SEND;   
            else if(rd_req)
               mstate <= S_SPI_RECV;
            else
               mstate <= S_SPI_IDLE; 
               
            nstate <= 0;     // 子状态机复位
            done   <= 0;
        end        
        
        else if(mstate == S_SPI_SEND) begin      
                       
            case(nstate)
            0: begin
                nstate <= nstate + 1'b1;     
                bits <= 0;
            end           
            
            1,3,5,7,9,11,13,15: begin
                if(spi_cnt == SPI_BAUD_CNT) begin
                    sclk_r <= 1'b1;               // 上跳发送数据
                    nstate <= nstate + 1'b1;      // 转向下步
                    bits <= bits + 1'b1;
                end
                else begin
                    sclk_r <= 0;  // 保持低电平准备数据
                    mosi_r <= wdata[bits]; // LSB                    
                end                
            end
            
            2,4,6,8,10,12,14,16:begin
                if(spi_cnt == SPI_BAUD_CNT) begin
                    sclk_r <= 1'b0;             // 低电平为下步做准备
                    nstate <= nstate + 1'b1;    // 转向下步
                end
                else begin
                    sclk_r <= 1;  // 保持高电平
                end  
            end
            
            17: begin      
                done   <= 1'b1;
                mstate <= S_SPI_IDLE;   // 操作结束,转向空闲    
            end
            
            default: begin
                mstate <= S_SPI_IDLE;   // 未知状态转向空闲
            end            
            endcase            
        end
        
        // 注意事项:读取时只有15个时钟信号,
        // 发送信号结束后的后边沿(下降沿),时钟芯片已经输出
        else if(mstate == S_SPI_RECV)begin
            case(nstate)
            0:  begin
                nstate <= nstate + 1'b1;
                bits   <= 0;
            end
            1,3,5,7,9,11,13,15: begin
                if(spi_cnt == SPI_BAUD_CNT) begin                    
                    rdata_r[bits] <= miso;  // 上升沿之前读出   
                    bits   <= bits + 1'b1;
                    sclk_r <= 1'b1;                          
                    nstate <=  nstate + 1'b1;                    
                end
                else begin
                    sclk_r <= 1'b0;    // 保持低电平
                end                
            end
            2,4,6,8,10,12,14,16: begin
                if(spi_cnt == SPI_BAUD_CNT) begin
                    nstate <=  nstate + 1'b1;                    
                    sclk_r <= 1'b0;     // 下降沿返回数据 
                end
                else begin
                    sclk_r <= 1'b1;     // 高电平准备数据   
                end
            end                      
            17: begin
                done   <= 1'b1;
                mstate <= S_SPI_IDLE;
            end
            default: begin
                mstate <= S_SPI_IDLE;
            end            
            endcase        
        end
        
        else begin
            mstate <= S_SPI_IDLE;    // 不可识别的命令
        end    
    end
end
//--------------------------------------------------
endmodule

DS1302模块

/*
 *  功能描述: ds1302读写模块
 */
module ds1302(
    clk,    // 模块时钟
    rst_n,  // 模块复位        
    sclk,   // SPI_CLK
    sio,    // SPI_SIO  输出输出复用
    ce,     // 芯片使能脚:高电平有效        
       
    addr,   // 字地址
    wdata,  // 输入的数据  
    rdata,  // 读到的数据    
    reg_wr, // 寄存器写
    reg_rd, // 寄存器读  
    done    // 操作完成标识
);

input  clk;
input  rst_n;
output sclk;
inout  sio;
output ce;
input[7:0]  addr;
input [7:0] wdata;
output[7:0] rdata;
input reg_wr;
input reg_rd;

reg ce_r;
reg spi_rd_req; // SPI读请求
reg spi_wr_req; // SPI写请求
reg [7:0]spi_wdata;

wire [7:0]spi_rdata;
wire spi_done;
//output 
wire busy;
//input en; 
output reg done;

wire mosi;
wire miso;
reg is_out;

reg[7:0] rdata_r;
//--------------------------------------------
assign ce   = ce_r;
assign sio  = is_out? mosi:1'bz; 
assign miso = sio;    
assign rdata = rdata_r;
//--------------------------------------------
reg[31:0] timer;    
//--------------------------------------------
parameter S_IDLE      = 4'd0;
parameter S_READ_CE   = 4'd1;
parameter S_READ      = 4'd2;
parameter S_WRITE_CE  = 4'd5;
parameter S_WRITE     = 4'd3;
parameter S_DONE      = 4'd4;
parameter S_DONE2     = 4'd8;

reg[3:0] state;

parameter BYTE_TIMEOUT = 32'd25_000; // 500us
parameter T1SEC = 32'd50_000_000;    // 1s
parameter T10US = 32'd50_000;        // 1ms
//--------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        state  <= S_IDLE;
        timer  <= 0;
        ce_r   <= 1'b0;
        done   <= 1'b0;
        is_out <= 0;
    end
        
    else begin
        case(state)
        S_IDLE: begin           
            done  <= 1'b0;     
            if(reg_wr) begin
                state <= S_WRITE_CE;       
                ce_r  <= 1'b1;
                is_out<= 1'b1;
                timer <= 0;                
            end    
            else if(reg_rd) begin
                state <= S_READ_CE;       
                ce_r  <= 1'b1;     
                is_out<= 1'b1;
                timer <= 0;
            end
            else begin
                state <= S_IDLE; 
                timer <= 0;   
                ce_r  <= 0;  
                is_out<= 1'b0;
            end
        end
        
        S_READ_CE: begin
            // CE必须保持4us以上
            if(timer == 32'd400) begin            
                spi_wdata <= addr;  // 设置字地址     
                spi_wr_req  <= 1'b1;  // 写使能  
                state     <= S_READ;
                timer     <= 0;
            end
            else 
                timer <= timer + 1'b1;
        end
        
        // 发送字地址
        S_READ: begin
            if(timer == BYTE_TIMEOUT) begin // 超时     
                state <= S_IDLE;  
                timer <= 0;
                is_out<= 1'b0;
            end  
            else if(spi_done) begin  // 发送地址完成    
                is_out    <= 1'b0;   // 设置端口方向为输入
                spi_rd_req  <= 1'b1;   // 启动读使能  
                state     <= S_DONE;                     
                timer     <= 0;  
                               
            end
            else begin
                spi_wr_req  <= 1'b0;
                timer <= timer + 1'b1; 
            end  
        end    
     
        S_WRITE_CE: begin
            // CE必须保持4us以上
            if(timer == 32'd400) begin            
                spi_wdata <= addr;  // 设置字地址     
                spi_wr_req  <= 1'b1;  // 写使能  
                state     <= S_WRITE;
                timer     <= 0;
            end
            else 
                timer <= timer + 1'b1;
        end
        S_WRITE: begin        
            if(timer == BYTE_TIMEOUT) begin   
                state <= S_IDLE;  
                timer <= 0; 
            end              
            else if(spi_done) begin                                     
                spi_wdata <= wdata;  // 设置数据     
                spi_wr_req  <= 1'b1;   // 写使能 
              
                state     <= S_DONE; // 还要进行收尾工作   
                timer     <= 0; 
            end   
            else begin                
                timer   <= timer + 1'b1; 
            end
        end
        
        S_DONE: begin
            if(timer == BYTE_TIMEOUT) begin // 超时     
                state   <= S_IDLE;  
                timer   <= 0;
            end  
            else if(spi_done) begin 
                rdata_r <= spi_rdata;
                state <= S_DONE2; 
                timer <= 0;      
                is_out<= 1'b0;    
            end            
            else begin                
                spi_rd_req  <= 1'b0;  // 撤销读写信号
                spi_wr_req  <= 1'b0;
                timer     <= timer + 1'b1; 
            end    
        end
        
        S_DONE2: begin
            if(timer == 32'd400) begin // 延时8us   
                state   <= S_IDLE;  
                timer   <= 0;
                done    <= 1'b1;       // 操作完成
            end   
            else begin
                ce_r  <= 0;
                timer <= timer + 1'b1;
            end
        end
        
        default: begin
            state <= S_IDLE;
            timer <=0; 
        end    
        endcase
    end

end
//--------------------------------------------
// 例化SPI接口
spi_master spi0(
    .clk(clk),          // 模块时钟
    .rst_n(rst_n),      // 模块复位
    .sclk(sclk),        // SPI_CLK
    .mosi(mosi),        // SPI_SIO    
    .miso(miso),         // 主收
    .wdata(spi_wdata),  // 待发的数据
    .rdata(spi_rdata),  // 收到的数据
    .wr_req(spi_wr_req),
    .rd_req(spi_rd_req),
    .done(spi_done),
    .busy(busy)     // 繁忙标志 1 - 忙, 0 - 空闲
);

endmodule

DS1302模块