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_num
在cnt_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