蓝桥杯FPGA-ds1302驱动

发布于:2025-03-14 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. 驱动的作用

调用SPI底层驱动,实现DS1302的驱动

2. 关键程序代码说明

1. 独热编码设置状态机的状态

使用独热编码会使系统更加高效稳定

localparam IDLE     = 8'b0000_0001;
localparam CE_HIGH  = 8'b0000_0010;
localparam CE_LOW   = 8'b0000_0100;
localparam WR_ADDR  = 8'b0000_1000;
localparam WR_DATA  = 8'b0001_0000;
localparam RD_ADDR  = 8'b0010_0000;
localparam RD_DATA  = 8'b0100_0000;
localparam ACK      = 8'b1000_0000;
2. 状态跳转条件的设置
wire IDLE_CE_HIGH    = (cstate == IDLE)         && (cmd_read || cmd_write)  ;
wire CE_HIGH_WR_ADDR = (cstate == CE_HIGH)      && end_cnt_num   && cmd_write           ;
wire WR_ADDR_WR_DATA = (cstate == WR_ADDR)      && wr_ack                   ;
wire WR_DATA_CE_LOW  = (cstate == WR_DATA)      && wr_ack                   ;
wire CE_LOW_ACK      = (cstate == CE_LOW)       && end_cnt_num              ;
wire ACK_IDLE        = (cstate == ACK)          && end_cnt_num              ;

wire CE_HIGH_RD_ADDR = (cstate == CE_HIGH)      && end_cnt_num   &&  cmd_read          ;
wire RD_ADDR_RD_DATA = (cstate == RD_ADDR)      && wr_ack                   ;
wire RD_DATA_CE_LOW  = (cstate == RD_DATA)      && wr_ack                   ;
3. end_cnt_num的使用说明

end_cnt_numcnt_clk开始计数并且计数到最大值的时候拉高一个clk节拍,此时由于end_cnt_num的拉高的一个节拍会使cnt_num的值加一(整个过程比较复杂,这里一笔带过,需要仔细了解的朋友可以拿代码去仿真)

4. 二段式状态机

两段式状态机比较推荐,拥有更高的执行效率和稳定性,推荐使用,特别注意一点,组合逻辑使用阻塞赋值,所有的else条件必须写,否则可能会引起latch

always @(posedge clk or posedge rst) begin
    if(rst)begin
        cstate <= IDLE;
    end else begin
        cstate <= nstate;
    end
end

always @(*) begin
    case (cstate)
        IDLE     :
            if(IDLE_CE_HIGH)
                nstate = CE_HIGH;
            else
                nstate = cstate;
        CE_HIGH  :
            if(CE_HIGH_RD_ADDR)
                nstate = RD_ADDR;
            else if(CE_HIGH_WR_ADDR)
                nstate = WR_ADDR;
            else
                nstate = cstate;
        CE_LOW   :
            if(CE_LOW_ACK)
                nstate = ACK;
            else
                nstate = cstate;
        WR_ADDR  :
            if(WR_ADDR_WR_DATA)
                nstate = WR_DATA;
            else
                nstate = cstate;
        WR_DATA  :
            if(WR_DATA_CE_LOW)
                nstate = CE_LOW;
            else
                nstate = cstate;
        RD_ADDR  :
            if(RD_ADDR_RD_DATA)
                nstate = RD_DATA;
            else
                nstate = cstate;
        RD_DATA  :
            if(RD_DATA_CE_LOW)
                nstate = CE_LOW;
            else
                nstate = cstate;
        ACK      :
            if(ACK_IDLE)
                nstate = IDLE;
            else
                nstate = cstate;
        default  :
                nstate = IDLE;
    endcase
end
5. 数据的收发部分

注意这里面有数据字节的对应问题,SPI收发数据都是从低位开始,也就是LSB,需要把用户的传入的数据或者用户需要读出的数据进行位置变换,具体实现如下

always @(posedge clk or posedge rst) begin
    if(rst)begin
        send_data <= 8'd0;
    end else if(cstate == WR_ADDR)begin
        send_data <= {1'b0, write_addr[1],write_addr[2],write_addr[3],write_addr[4],write_addr[5],write_addr[6],1'b1};
    end else if(cstate == RD_ADDR)begin
        send_data <= {1'b1, read_addr[1],read_addr[2],read_addr[3],read_addr[4],read_addr[5],read_addr[6],1'b1};
    end else if(cstate == WR_DATA)begin
        send_data <= {write_data[0],write_data[1],write_data[2],write_data[3],write_data[4],write_data[5],write_data[6],write_data[7]};
    end
end

always @(posedge clk or posedge rst) begin
    if(rst)begin
        read_data <= 8'd0;
    end else if(cstate == RD_DATA_CE_LOW)begin
        read_data <= {data_rec[0],data_rec[1],data_rec[2],data_rec[3],data_rec[4],data_rec[5],data_rec[6],data_rec[7]};
    end else begin
        read_data <= 8'd0;
    end
end

3. 仿真验证

1. 写过程

在这里插入图片描述

2. 读过程

在这里插入图片描述

4. 完整代码

module ds1302_io1(
    input                               clk                        ,
    input                               rst                        ,
    output                              ds1302_ce                  ,// 1
    output                              ds1302_sclk                ,// 1
    inout                               ds1302_io                  ,
    input                               cmd_read                   ,
    input                               cmd_write                  ,
    output                              cmd_write_ack              ,// 1
    output                              cmd_read_ack               ,// 1 
    input              [   7:0]         write_addr                 ,
    input              [   7:0]         read_addr                  ,
    input              [   7:0]         write_data                 ,
    output reg         [   7:0]         read_data                   //1
);

localparam IDLE     = 8'b0000_0001;
localparam CE_HIGH  = 8'b0000_0010;
localparam CE_LOW   = 8'b0000_0100;
localparam WR_ADDR  = 8'b0000_1000;
localparam WR_DATA  = 8'b0001_0000;
localparam RD_ADDR  = 8'b0010_0000;
localparam RD_DATA  = 8'b0100_0000;
localparam ACK      = 8'b1000_0000;

wire [25:0]MAX_CNT = 256;
reg [7:0]cstate;
reg [7:0]nstate;
reg [3:0]num;
reg [8:0]cnt_clk;
reg [3:0]cnt_num;
reg CS_reg;
wire add_cnt_clk = cstate != IDLE;
wire end_cnt_clk = add_cnt_clk && cnt_clk == MAX_CNT - 1;
wire add_cnt_num = end_cnt_clk;
wire end_cnt_num = add_cnt_num && cnt_num == num - 1;
wire wr_ack;


reg [7:0]send_data ;
wire [7:0]data_rec  ;
wire IDLE_CE_HIGH    = (cstate == IDLE)         && (cmd_read || cmd_write)  ;
wire CE_HIGH_WR_ADDR = (cstate == CE_HIGH)      && end_cnt_num   && cmd_write           ;
wire WR_ADDR_WR_DATA = (cstate == WR_ADDR)      && wr_ack                   ;
wire WR_DATA_CE_LOW  = (cstate == WR_DATA)      && wr_ack                   ;
wire CE_LOW_ACK      = (cstate == CE_LOW)       && end_cnt_num              ;
wire ACK_IDLE        = (cstate == ACK)          && end_cnt_num              ;

wire CE_HIGH_RD_ADDR = (cstate == CE_HIGH)      && end_cnt_num   &&  cmd_read          ;
wire RD_ADDR_RD_DATA = (cstate == RD_ADDR)      && wr_ack                   ;
wire RD_DATA_CE_LOW  = (cstate == RD_DATA)      && wr_ack                   ;
assign cmd_write_ack  = WR_DATA_CE_LOW;
assign cmd_read_ack   = RD_DATA_CE_LOW;
// wire CS_reg = cstate == ;//
always @(posedge clk or posedge rst) begin
    if(rst)begin
        CS_reg <= 1'b0;
    end else if(IDLE_CE_HIGH)begin
        CS_reg <= 1'b1;
    end else if(WR_DATA_CE_LOW || RD_DATA_CE_LOW)begin
        CS_reg <= 1'b1;   
    end
end


reg [8*20-1:0]state_name;
always @(*) begin
    case (cstate)
IDLE     :begin num = 1;state_name = "IDLE   "; end
CE_HIGH  :begin num = 1;state_name = "CE_HIGH"; end
CE_LOW   :begin num = 1;state_name = "CE_LOW "; end
WR_ADDR  :begin num = 1;state_name = "WR_ADDR"; end
WR_DATA  :begin num = 1;state_name = "WR_DATA"; end
RD_ADDR  :begin num = 1;state_name = "RD_ADDR"; end
RD_DATA  :begin num = 1;state_name = "RD_DATA"; end
ACK      :begin num = 1;state_name = "ACK    "; end
default  :begin num = 1;state_name = "IDLE   "; end  
    endcase
end



always @(posedge clk or posedge rst) begin
    if(rst)begin
        cnt_clk <= 9'd0;
    end else if(add_cnt_clk)begin
        if(end_cnt_clk)
            cnt_clk <= 9'd0;
        else
            cnt_clk <= cnt_clk + 9'd1;
    end
end
always @(posedge clk or posedge rst) begin
    if(rst)begin
        cnt_num <= 4'd0;
    end else if(add_cnt_num)begin
        if(end_cnt_num)
            cnt_num <= 4'd0;
        else
            cnt_num <= cnt_num + 4'd1;
    end
end





always @(posedge clk or posedge rst) begin
    if(rst)begin
        cstate <= IDLE;
    end else begin
        cstate <= nstate;
    end
end

always @(*) begin
    case (cstate)
        IDLE     :
            if(IDLE_CE_HIGH)
                nstate = CE_HIGH;
            else
                nstate = cstate;
        CE_HIGH  :
            if(CE_HIGH_RD_ADDR)
                nstate = RD_ADDR;
            else if(CE_HIGH_WR_ADDR)
                nstate = WR_ADDR;
            else
                nstate = cstate;
        CE_LOW   :
            if(CE_LOW_ACK)
                nstate = ACK;
            else
                nstate = cstate;
        WR_ADDR  :
            if(WR_ADDR_WR_DATA)
                nstate = WR_DATA;
            else
                nstate = cstate;
        WR_DATA  :
            if(WR_DATA_CE_LOW)
                nstate = CE_LOW;
            else
                nstate = cstate;
        RD_ADDR  :
            if(RD_ADDR_RD_DATA)
                nstate = RD_DATA;
            else
                nstate = cstate;
        RD_DATA  :
            if(RD_DATA_CE_LOW)
                nstate = CE_LOW;
            else
                nstate = cstate;
        ACK      :
            if(ACK_IDLE)
                nstate = IDLE;
            else
                nstate = cstate;
        default  :
                nstate = IDLE;
    endcase
end

assign wr_req = cstate == RD_ADDR || cstate == WR_ADDR
           || cstate == WR_DATA || cstate == RD_DATA;//读数据也要写请求吗   是的  代表启动SPI总线


always @(posedge clk or posedge rst) begin
    if(rst)begin
        send_data <= 8'd0;
    end else if(cstate == WR_ADDR)begin
        send_data <= {1'b0, write_addr[1],write_addr[2],write_addr[3],write_addr[4],write_addr[5],write_addr[6],1'b1};
    end else if(cstate == RD_ADDR)begin
        send_data <= {1'b1, read_addr[1],read_addr[2],read_addr[3],read_addr[4],read_addr[5],read_addr[6],1'b1};
    end else if(cstate == WR_DATA)begin
        send_data <= {write_data[0],write_data[1],write_data[2],write_data[3],write_data[4],write_data[5],write_data[6],write_data[7]};
    end
end

always @(posedge clk or posedge rst) begin
    if(rst)begin
        read_data <= 8'd0;
    end else if(cstate == RD_DATA_CE_LOW)begin
        read_data <= {data_rec[0],data_rec[1],data_rec[2],data_rec[3],data_rec[4],data_rec[5],data_rec[6],data_rec[7]};
    end else begin
        read_data <= 8'd0;
    end
end



spi_master spi_master_m0(
    .clk                               (clk                       ),
    .rst                               (rst                       ),
    .nCS                               (ds1302_ce                 ),
    .DCLK                              (ds1302_sclk               ),//
    .MOSI                              (MOSI                      ),
    .MISO                              (MISO                      ),
    .CPOL                              (1'b0                      ),
    .CPHA                              (1'b0                      ),
    .nCS_ctrl                          (CS_reg                    ),
    .clk_div                           (16'd50                    ),
    .wr_req                            (wr_req                    ),
    .wr_ack                            (wr_ack                    ),
    .data_in                           (send_data                 ),
    .data_out                          (data_rec                  ) 
);

endmodule

网站公告

今日签到

点亮在社区的每一天
去签到