目录

一、SPI通信协议

1.1 SPI物理层 

1.2 SPI协议层

二、实战

2.1 SPI控制FLASH实现全擦除代码编写

2.2 上板验证


一、SPI通信协议

1.1 SPI物理层 

SPI通信模式为主-从模式 ,分为一主一从、一主多从:

 

 

片选线CS用于主机选择对应的从机进行通信,片选线置低电平为通信开始信号,被拉高则为开始信号。

1.2 SPI协议层

SPI协议有四种通信模式(通过CPOL和CPHA控制),

其中CPOL控制当没有数据传输的时候SCK的电平状态;CPHA控制数据采样位置,为0的时候位置为奇数边沿,为1的时候为偶数边沿。

 

下图传输为高位在前,先传输高位,每次传输数据的个数没有限制。

 

二、实战

2.1 SPI控制FLASH实现全擦除代码编写

程序的固化流程:

生成jic文件:

 选择生成好的jic文件烧录:

固化后的程序即使断电也不会丢失,如果想去除flash中的固化程序,可以进行全擦除操作。

第一种方法可以到下载器中进行全擦除:

第二种方法就是我们要讲的,自己编写程序对flash芯片进行全擦除。

首先打开flash芯片的手册:

找到全擦除对应的指令BE(Bulk Erase): 

然后找到BE指令的介绍:

从介绍里面可以知道:

  • BE指令会把flash芯片中的所有位设置为1。
  • 在执行全擦除指令之前,需要先执行一个写使能指令WREN。
  • 在BE指令发送的时候需要将片选信号\overline{S}拉低(如上图),指令发送完毕后需要把\overline{S}拉高,拉高片选信号后才会开始执行BE指令。

然后查看一下写使能指令的信息:

 还有一个要注意的地方就是输入时序的问题,tSLCH、tCHSH、tSHSL这三个时间要注意:

tSLCH为从片选信号\overline{S}拉低到第一个bit到来的时间间隔,最小值为5ns(如下图)。

tCHSH为从最后一个bit写入完成到片选信号\overline{S}拉高的时间间隔,最小值为5ns。

tSHSL为两个指令之间需要等待的时间,最小值为100ns。

基于上面的内容我们可以得到我们实验的时序图: 

实验顶层模块框图:

 内部结构:

 按键消抖子模块(为了简单这个按键消抖模块我就不做了,直接使用key_in控制):

全擦除控制子模块:

全擦除控制模块波形:

flash_be_ctrl模块verilog代码:

module flash_be_ctrl(
    input   wire    sys_clk     ,
    input   wire    sys_rst_n   ,
    input   wire    key_in      , //按键信号
    output  reg     cs_n        , //片选信号,低电平有效
    output  reg     sck         , //spi通信时钟信号,采用的是12.5Hz
    output  reg     mosi          //主机输出的数据
);
parameter WREN_INS = 8'b0000_0110;
parameter BE_INS = 8'b1100_0111;
 
reg           key_en;
reg           key_flag;
reg   [2:0]   state; //状态机,0为闲置状态
reg   [4:0]   cnt_clk;
reg   [2:0]   cnt_byte;
reg   [1:0]   cnt_sck;
reg   [2:0]   cnt_bit;
 
//检测按键是否按下,生成key_flag信号,并且只检测一次
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        key_en <= 1'b1;
        key_flag <= 1'b0;
    end
    else if(key_in == 1'b0 && key_en == 1'b1) begin
        key_en <= 1'b0;
        key_flag <= 1'b1;
    end
    else begin
        key_en <= key_en;
        key_flag <= 1'b0;
    end
end
//cs_n片选信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cs_n <= 1'b1;
    end
    else if(key_flag == 1'b1 || (cnt_clk == 5'd31 && cnt_byte == 3'd3)) begin
        cs_n <= 1'b0; //拉低
    end
    else if((cnt_clk == 5'd31 && cnt_byte == 3'd2) || (cnt_clk == 5'd31 && cnt_byte == 3'd6)) begin
        cs_n <= 1'b1; //拉高
    end
    else begin
        cs_n <= cs_n;
    end
end
//cnt_clk计数信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cnt_clk <= 5'd0;
    end
    else if(cnt_clk == 5'd31 || state == 3'd0) begin
        cnt_clk <= 5'd0;
    end
    else begin
        cnt_clk <= cnt_clk + 1'b1;
    end
end
 
//cnt_byte计数信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cnt_byte <= 3'd0;
    end
    else if(cnt_byte == 3'd6 && cnt_clk == 5'd31) begin
        cnt_byte <= 3'd0;
    end
    else if(cnt_clk == 5'd31) begin
        cnt_byte <= cnt_byte + 1'b1;
    end
    else begin
        cnt_byte <= cnt_byte;
    end
end
 
//cnt_sck计数信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cnt_sck <= 2'd0;
    end
    else if(cnt_byte == 3'd1 || cnt_byte == 3'd5) begin
        cnt_sck <= cnt_sck + 1'b1;
    end
    else begin
        cnt_sck <= 2'd0;
    end
end
//cnt_bit计数信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cnt_bit <= 3'd0;
    end
    else if(cnt_byte != 3'd1 && cnt_byte != 3'd5) begin
        cnt_bit <= 3'd0;
    end
    else if(cnt_sck == 2'd2) begin
        cnt_bit <= cnt_bit + 1'b1;
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end
//state状态机
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        state <= 3'd0; //IDLE
    end
    else if(key_flag == 1'd1) begin
        state <= 3'd1; //WREN
    end 
    else if(cnt_clk == 5'd31 && cnt_byte == 3'd2) begin
        state <= 3'd2; //DELAY
    end
    else if(cnt_clk == 5'd31 && cnt_byte == 3'd3) begin
        state <= 3'd3; //BE
    end
    else if(cnt_clk == 5'd31 && cnt_byte == 3'd6) begin
        state <= 3'd0; //IDLE
    end
    else begin
        state <= state;
    end
end
//输出sck时钟信号
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        sck <= 1'b0;
    end
    else if(cnt_sck == 2'd2 || (cnt_sck == 2'd0 && sck == 1'b1)) begin
        sck <= ~sck;
    end
    else begin
        sck <= sck;
    end
end
//输出mosi数据
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        mosi <= 1'b0;
    end
    else if(cnt_byte != 3'd1 && cnt_byte != 3'd5) begin
        mosi <= 1'b0;
    end
    else if(cnt_byte == 3'd1 && cnt_sck == 2'd0) begin
        //WREN信号
        case(cnt_bit)
            3'd0: mosi <= WREN_INS[7];
            3'd1: mosi <= WREN_INS[6];
            3'd2: mosi <= WREN_INS[5];
            3'd3: mosi <= WREN_INS[4];
            3'd4: mosi <= WREN_INS[3];
            3'd5: mosi <= WREN_INS[2];
            3'd6: mosi <= WREN_INS[1];
            3'd7: mosi <= WREN_INS[0];
        endcase
    end
    else if(cnt_byte == 3'd5 && cnt_sck == 2'd0) begin
        //BE信号
        case(cnt_bit)
            3'd0: mosi <= BE_INS[7];
            3'd1: mosi <= BE_INS[6];
            3'd2: mosi <= BE_INS[5];
            3'd3: mosi <= BE_INS[4];
            3'd4: mosi <= BE_INS[3];
            3'd5: mosi <= BE_INS[2];
            3'd6: mosi <= BE_INS[1];
            3'd7: mosi <= BE_INS[0];
        endcase
    end
    else begin
        mosi <= mosi;
    end
end
endmodule
 

testbench代码:

module tb_flash_be_ctrl();
 
reg sys_clk;
reg sys_rst_n;
reg key_in;
wire cs_n;
wire sck;
wire mosi;
 
always #10 sys_clk = ~sys_clk;
 
initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    key_in <= 1'b1;
    #20
    sys_rst_n <= 1'b1;
end
 
initial begin
    #200
    key_in <= 1'b0;
    #100
    key_in <= 1'b1;
end
 
flash_be_ctrl flash_be_ctrl_inst(
    .sys_clk(sys_clk)     ,
    .sys_rst_n(sys_rst_n)   ,
    .key_in(key_in)      , //按键信号
    .cs_n(cs_n)       , //片选信号,低电平有效
    .sck(sck)         , //spi通信时钟信号,采用的是12.5Hz
    .mosi(mosi)          //主机输出的数据
);
 
endmodule


仿真波形:

如果想额外添加flash仿真文件可以在如下窗口添加:

代码编写完成后我们就可以上板验证了。

2.2 上板验证

各个端口绑定的引脚如下: 

下面介绍一下怎么找到对应的引脚。首先找到flash的原理图:

FLASH_NCE为片选信号,对应的端口为D2: 

key_in信号对应M1端口:

并且按下时为低电平: 

 

 EPCS_ASDO为flash芯片的输入mosi,对应引脚C1:

 

 EPCS_CLK为传入flash芯片的同步时钟信号sck,对应引脚为H1:

其余两个是系统时钟和系统复位信号,这个我就不作说明了。

配置好引脚后,重新编译,发现报错信息:

 这些要修改io口的配置:

改成如下内容:

三个位置对应三个错误,改完后重新编译通过。

然后就是上板测试,首先要烧录一个测试程序到板子上(要进行固化),然后再烧录我们的spi全擦除程序到板子上,烧录完成后按下key1,等待一段时间后再重新上电,重新上电后就可以看到之前固化的程序消失了,全擦除实现成功!