第48讲:基于SPI协议的Flash驱动控制
0. 理论部分
SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输
应用:EEPROM、Flash、RTC、ADC、DSP等
优缺点:全双工通信,通讯方式较为简单,相对数据传输速率较快;没有应答机制确认数据是否接收,在数据可靠性上有一定缺陷(与I2C相比)
1. Flash全擦除实验
key_filter
`timescale 1ns/1ns module key_filter #( parameter CNT_MAX = 20'd999_999 //计数器计数最大值 ) ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 input wire key_in , //按键输入信号 output reg key_flag //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //reg define reg [19:0] cnt_20ms ; //计数器 //cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_20ms <= 20'b0; else if(key_in == 1'b1) cnt_20ms <= 20'b0; else if(cnt_20ms == CNT_MAX && key_in == 1'b0) cnt_20ms <= cnt_20ms; else cnt_20ms <= cnt_20ms + 1'b1; //key_flag:当计数满20ms后产生按键有效标志位 //且key_flag在999_999时拉高,维持一个时钟的高电平 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) key_flag <= 1'b0; else if(cnt_20ms == CNT_MAX - 1'b1) key_flag <= 1'b1; else key_flag <= 1'b0; endmodule
flash_be_ctrl
`timescale 1ns/1ns module flash_be_ctrl ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire key , //按键输入信号 output reg cs_n , //片选信号 output reg sck , //串行时钟 output reg mosi //主输出从输入数据 ); //parameter define parameter IDLE = 4'b0001 , //初始状态 WR_EN = 4'b0010 , //写状态 DELAY = 4'b0100 , //等待状态 BE = 4'b1000 ; //全擦除状态 parameter WR_EN_INST = 8'b0000_0110, //写使能指令 BE_INST = 8'b1100_0111; //全擦除指令 //reg define reg [2:0] cnt_byte; //字节计数器 reg [3:0] state ; //状态机状态 reg [4:0] cnt_clk ; //系统时钟计数器 reg [1:0] cnt_sck ; //串行时钟计数器 reg [2:0] cnt_bit ; //比特计数器 //cnt_clk:系统时钟计数器,用以记录单个字节 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_clk <= 5'd0; else if(state != IDLE) cnt_clk <= cnt_clk + 1'b1; //cnt_byte:记录输出字节个数和等待时间 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_byte <= 3'd0; else if((cnt_clk == 5'd31) && (cnt_byte == 3'd6)) cnt_byte <= 3'd0; else if(cnt_clk == 31) cnt_byte <= cnt_byte + 1'b1; //cnt_sck:串行时钟计数器,用以生成串行时钟 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_sck <= 2'd0; else if((state == WR_EN) && (cnt_byte == 1'b1)) cnt_sck <= cnt_sck + 1'b1; else if((state == BE) && (cnt_byte == 3'd5)) cnt_sck <= cnt_sck + 1'b1; //cs_n:片选信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cs_n <= 1'b1; else if(key == 1'b1) cs_n <= 1'b0; else if((cnt_byte == 3'd2) && (cnt_clk == 5'd31) && (state == WR_EN)) cs_n <= 1'b1; else if((cnt_byte == 3'd3) && (cnt_clk == 5'd31) && (state == DELAY)) cs_n <= 1'b0; else if((cnt_byte == 3'd6) && (cnt_clk == 5'd31) && (state == BE)) cs_n <= 1'b1; //sck:输出串行时钟 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) sck <= 1'b0; else if(cnt_sck == 2'd0) sck <= 1'b0; else if(cnt_sck == 2'd2) sck <= 1'b1; //cnt_bit:高低位对调,控制mosi输出 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_bit <= 3'd0; else if(cnt_sck == 2'd2) cnt_bit <= cnt_bit + 1'b1; //state:两段式状态机第一段,状态跳转 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) state <= IDLE; else case(state) IDLE: if(key == 1'b1) state <= WR_EN; WR_EN: if((cnt_byte == 3'd2) && (cnt_clk == 5'd31)) state <= DELAY; DELAY: if((cnt_byte == 3'd3) && (cnt_clk == 5'd31)) state <= BE; BE: if((cnt_byte == 3'd6) && (cnt_clk == 5'd31)) state <= IDLE; default: state <= IDLE; endcase //mosi:两段式状态机第二段,逻辑输出 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) mosi <= 1'b0; else if((state == WR_EN) && (cnt_byte == 3'd2)) mosi <= 1'b0; else if((state == BE) && (cnt_byte == 3'd6)) mosi <= 1'b0; else if((state == WR_EN) && (cnt_byte == 3'd1) && (cnt_sck == 5'd0)) mosi <= WR_EN_INST[7 - cnt_bit]; //写使能指令 else if((state == BE) && (cnt_byte == 3'd5) && (cnt_sck == 5'd0)) mosi <= BE_INST[7 - cnt_bit]; //全擦除指令 endmodule
spi_flash_be
`timescale 1ns/1ns module spi_flash_be ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire pi_key , //按键输入信号 output wire cs_n , //片选信号 output wire sck , //串行时钟 output wire mosi //主输出从输入数据 ); //parameter define parameter CNT_MAX = 20'd999_999; //计数器计数最大值 //wire define wire po_key ; //------------- key_filter_inst ------------- key_filter #( .CNT_MAX (CNT_MAX ) //计数器计数最大值 ) key_filter_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低电平有效 .key_in (pi_key ), //按键输入信号 .key_flag (po_key ) //消抖后信号 ); //------------- flash_be_ctrl_inst ------------- flash_be_ctrl flash_be_ctrl_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低电平有效 .key (po_key ), //按键输入信号 .sck (sck ), //片选信号 .cs_n (cs_n ), //串行时钟 .mosi (mosi ) //主输出从输入数据 ); endmodule
tb_flash_be_ctrl
`timescale 1ns/1ns module tb_flash_be_ctrl(); //wire define wire cs_n ; //Flash片选信号 wire sck ; //Flash串行时钟 wire mosi ; //Flash主输出从输入信号 //reg define reg sys_clk ; //模拟时钟信号 reg sys_rst_n ; //模拟复位信号 reg key ; //模拟全擦除触发信号 //时钟、复位信号、模拟按键信号 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; key <= 1'b0; #100 sys_rst_n <= 1'b1; #1000 key <= 1'b1; #20 key <= 1'b0; end always #10 sys_clk <= ~sys_clk; //模拟时钟,频率50MHz //写入Flash仿真模型初始值(全F) defparam memory.mem_access.initfile = "initmemory.txt"; //------------- flash_be_ctrl_inst ------------- flash_be_ctrl flash_be_ctrl_inst ( .sys_clk (sys_clk ), //输入系统时钟,频率50MHz,1bit .sys_rst_n (sys_rst_n ), //输入复位信号,低电平有效,1bit .key (key ), //按键输入信号,1bit .sck (sck ), //输出串行时钟,1bit .cs_n (cs_n ), //输出片选信号,1bit .mosi (mosi ) //输出主输出从输入数据,1bit ); //------------- memory ------------- m25p16 memory ( .c (sck ), //输入串行时钟,频率12.5Mhz,1bit .data_in (mosi ), //输入串行指令或数据,1bit .s (cs_n ), //输入片选信号,1bit .w (1'b1 ), //输入写保护信号,低有效,1bit .hold (1'b1 ), //输入hold信号,低有效,1bit .data_out ( ) //输出串行数据 ); endmodule
tb_spi_flash_be
`timescale 1ns/1ns module tb_spi_flash_be(); //wire define wire cs_n; wire sck ; wire mosi ; //reg define reg clk ; reg rst_n ; reg key ; //时钟、复位信号、模拟按键信号 initial begin clk = 0; rst_n <= 0; key <= 0; #100 rst_n <= 1; #1000 key <= 1; #20 key <= 0; end always #10 clk <= ~clk; defparam memory.mem_access.initfile = "initmemory.txt"; //-------------spi_flash_erase------------- spi_flash_be spi_flash_be_inst ( .sys_clk (clk ), //系统时钟,频率50MHz .sys_rst_n (rst_n ), //复位信号,低电平有效 .pi_key (key ), //按键输入信号 .sck (sck ), //串行时钟 .cs_n (cs_n ), //片选信号 .mosi (mosi ) //主输出从输入数据 ); m25p16 memory ( .c (sck ), //输入串行时钟,频率12.5Mhz,1bit .data_in (mosi ), //输入串行指令或数据,1bit .s (cs_n ), //输入片选信号,1bit .w (1'b1 ), //输入写保护信号,低有效,1bit .hold (1'b1 ), //输入hold信号,低有效,1bit .data_out ( ) //输出串行数据 ); endmodule
2. Flash扇区擦除实验
key_filter
`timescale 1ns/1ns module key_filter #( parameter CNT_MAX = 20'd999_999 //计数器计数最大值 ) ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 input wire key_in , //按键输入信号 output reg key_flag //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //reg define reg [19:0] cnt_20ms ; //计数器 //cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_20ms <= 20'b0; else if(key_in == 1'b1) cnt_20ms <= 20'b0; else if(cnt_20ms == CNT_MAX && key_in == 1'b0) cnt_20ms <= cnt_20ms; else cnt_20ms <= cnt_20ms + 1'b1; //key_flag:当计数满20ms后产生按键有效标志位 //且key_flag在999_999时拉高,维持一个时钟的高电平 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) key_flag <= 1'b0; else if(cnt_20ms == CNT_MAX - 1'b1) key_flag <= 1'b1; else key_flag <= 1'b0; endmodule
flash_se_ctrl
`timescale 1ns/1ns module flash_se_ctrl ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire key , //按键输入信号 output reg cs_n , //片选信号 output reg sck , //串行时钟 output reg mosi //主输出从输入数据 ); //parameter define parameter IDLE = 4'b0001 , //初始状态 WR_EN = 4'b0010 , //写状态 DELAY = 4'b0100 , //等待状态 SE = 4'b1000 ; //扇区擦除状态 parameter WR_EN_INST = 8'b0000_0110, //写使能指令 SE_INST = 8'b1101_1000; //扇区擦除指令 parameter SECTOR_ADDR = 8'b0000_0000, //扇区地址 PAGE_ADDR = 8'b0000_0100, //页地址 BYTE_ADDR = 8'b0010_0101; //字节地址 //reg define reg [3:0] cnt_byte; //字节计数器 reg [3:0] state ; //状态机状态 reg [4:0] cnt_clk ; //系统时钟计数器 reg [1:0] cnt_sck ; //串行时钟计数器 reg [2:0] cnt_bit ; //比特计数器 //cnt_clk:系统时钟计数器,用以记录单个字节 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_clk <= 5'd0; else if(state != IDLE) cnt_clk <= cnt_clk + 1'b1; //cnt_byte:记录输出字节个数和等待时间 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_byte <= 4'd0; else if((cnt_clk == 5'd31) && (cnt_byte == 4'd9)) cnt_byte <= 4'd0; else if(cnt_clk == 31) cnt_byte <= cnt_byte + 1'b1; //cnt_sck:串行时钟计数器,用以生成串行时钟 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_sck <= 2'd0; else if((state == WR_EN) && (cnt_byte == 1'b1)) cnt_sck <= cnt_sck + 1'b1; else if((state == SE) && (cnt_byte >= 4'd5) && (cnt_byte <= 4'd8)) cnt_sck <= cnt_sck + 1'b1; //cs_n:片选信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cs_n <= 1'b1; else if(key == 1'b1) cs_n <= 1'b0; else if((cnt_byte == 4'd2) && (cnt_clk == 5'd31) && (state == WR_EN)) cs_n <= 1'b1; else if((cnt_byte == 4'd3) && (cnt_clk == 5'd31) && (state == DELAY)) cs_n <= 1'b0; else if((cnt_byte == 4'd9) && (cnt_clk == 5'd31) && (state == SE)) cs_n <= 1'b1; //sck:输出串行时钟 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) sck <= 1'b0; else if(cnt_sck == 2'd0) sck <= 1'b0; else if(cnt_sck == 2'd2) sck <= 1'b1; //cnt_bit:高低位对调,控制mosi输出 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_bit <= 3'd0; else if(cnt_sck == 2'd2) cnt_bit <= cnt_bit + 1'b1; //state:两段式状态机第一段,状态跳转 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) state <= IDLE; else case(state) IDLE: if(key == 1'b1) state <= WR_EN; WR_EN: if((cnt_byte == 4'd2) && (cnt_clk == 5'd31)) state <= DELAY; DELAY: if((cnt_byte == 4'd3) && (cnt_clk == 5'd31)) state <= SE; SE: if((cnt_byte == 4'd9) && (cnt_clk == 5'd31)) state <= IDLE; default: state <= IDLE; endcase //mosi:两段式状态机第二段,逻辑输出 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) mosi <= 1'b0; else if((state == WR_EN) && (cnt_byte == 4'd2)) mosi <= 1'b0; else if((state == SE) && (cnt_byte == 4'd9)) mosi <= 1'b0; else if((state == WR_EN) && (cnt_byte == 4'd1) && (cnt_sck == 5'd0)) mosi <= WR_EN_INST[7 - cnt_bit]; //写使能指令 else if((state == SE) && (cnt_byte == 4'd5) && (cnt_sck == 5'd0)) mosi <= SE_INST[7 - cnt_bit]; //扇区擦除指令 else if((state == SE) && (cnt_byte == 4'd6) && (cnt_sck == 5'd0)) mosi <= SECTOR_ADDR[7 - cnt_bit]; //扇区地址 else if((state == SE) && (cnt_byte == 4'd7) && (cnt_sck == 5'd0)) mosi <= PAGE_ADDR[7 - cnt_bit]; //页地址 else if((state == SE) && (cnt_byte == 4'd8) && (cnt_sck == 5'd0)) mosi <= BYTE_ADDR[7 - cnt_bit]; //字节地址 endmodule
spi_flash_se
`timescale 1ns/1ns module spi_flash_se ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire pi_key , //按键输入信号 output wire cs_n , //片选信号 output wire sck , //串行时钟 output wire mosi //主输出从输入数据 ); //parameter define parameter CNT_MAX = 20'd999_999; //计数器计数最大值 //wire define wire po_key ; //------------- key_filter_inst ------------- key_filter #( .CNT_MAX (CNT_MAX ) //计数器计数最大值 ) key_filter_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低电平有效 .key_in (pi_key ), //按键输入信号 .key_flag (po_key ) //消抖后信号 ); //------------- flash_se_ctrl_inst ------------- flash_se_ctrl flash_se_ctrl_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低电平有效 .key (po_key ), //按键输入信号 .sck (sck ), //片选信号 .cs_n (cs_n ), //串行时钟 .mosi (mosi ) //主输出从输入数据 ); endmodule
FPGA进阶(1):基于SPI协议的Flash驱动控制(二)+https://developer.aliyun.com/article/1556613