此篇是我在学习中做的归纳与总结,其中如果存在版权或知识错误或问题请直接联系我,欢迎留言。
PS:本着知识共享的原则,此篇博客可以转载,但请标明出处!
SPI,顾名思义就是串行外围通信接口,只需四条线就可以完成主、从与各种外围器件全双工同步通信。4根接口线分别是:串行时钟线(SCK)、主机输入/从机输出数据线(MISO)、主机输出/从机输入(MOSI),低电平有效从机选择线(CS)。
SPI系统可分为主机设备和从机设备两类设备,其中主机提供SPI时钟信号和片选信号,从机设备是接受SPI信号的任何集成电路。当SPI工作时,在移位寄存器中的数据逐位从输出引脚(MOSI)输出,同时从输入引脚(MISO)逐位接受数据。发送和接收操作都受控于SPI主机设备的时钟信号(SCK),从而保证同步,因此只能有一个主设备,但从设备可以有多个,通过不同的片选信号可以同时选择一个或多个从设备。
代码功能介绍:
1、数据用写片仪将数据写入SPI-Flash中【W25Q128BV】
2、使用A7系列FPGA控制SPI读取Flash中数据。
以下为SPI-Flash读取数据控制模块代码,实测有效:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2019/11/26 11:55:56
// Design Name:
// Module Name: spi_driver
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module spi_driver(
input I_qspi_miso , // SPI总线输入信号线,也是QSPI Flash的输出信号线
input I_rst_n , // 复位信号
input I_clk , // 25MHz时钟信号
input [4:0] I_cmd_type , // 命令类型
input [7:0] I_cmd_code , // 命令码
input [23:0] I_qspi_addr , // QSPI Flash地址
output O_qspi_clk , // SPI总线串行时钟线
output reg O_qspi_cs , // SPI总线片选信号
output reg O_qspi_mosi , // SPI总线输出信号线,也是QSPI Flash的输入信号线
output reg O_done_sig , // 指令执行结束标志
output reg [7:0] O_read_data , // 从QSPI Flash读出的数据
output reg O_read_byte_valid , // 读一个字节完成的标志
output reg [3:0] O_qspi_state // 状态机,用于在顶层调试用
);
parameter C_IDLE = 4'b0000 ; // 空闲状态
parameter C_SEND_CMD = 4'b0001 ; // 发送命令码
parameter C_SEND_ADDR = 4'b0010 ; // 发送地址码
parameter C_READ_WAIT = 4'b0011 ; // 读等待
parameter C_WRITE_DATA = 4'b0101 ; // 写数据
parameter C_FINISH_DONE = 4'b0110 ; // 一条指令执行结束
reg [7:0] R_read_data_reg ; // 从Flash中读出的数据用这个变量进行缓存,等读完了在把这个变量的值给输出
reg R_qspi_clk_en ; // 串行时钟使能信号
reg R_data_come_single ; // 单线操作读数据使能信号,当这个信号为高时
reg [7:0] R_cmd_reg ; // 命令码寄存器
reg [23:0] R_address_reg ; // 地址码寄存器
reg [7:0] R_write_bits_cnt ; // 写bit计数器,写数据之前把它初始化为7,发送一个bit就减1
reg [8:0] R_write_bytes_cnt ; // 写字节计数器,发送一个字节数据就把它加1
reg [7:0] R_read_bits_cnt ; // 写bit计数器,接收一个bit就加1
reg [8:0] R_read_bytes_cnt ; // 读字节计数器,接收一个字节数据就把它加1
reg [8:0] R_read_bytes_num ; // 要接收的数据总数
reg R_read_finish ; // 读数据结束标志位
assign O_qspi_clk = R_qspi_clk_en ? I_clk : 0 ; // 产生串行时钟信号
// 功能:用时钟的下降沿发送数据
always @(negedge I_clk) begin
if(!I_rst_n) begin
O_qspi_cs <= 1'b1 ;
O_qspi_state <= C_IDLE ;
R_cmd_reg <= 0 ;
R_address_reg <= 0 ;
R_qspi_clk_en <= 1'b0 ; //SPI clock输出不使能
R_write_bits_cnt <= 0 ;
R_write_bytes_cnt <= 0 ;
R_read_bytes_num <= 0 ;
R_address_reg <= 0 ;
O_done_sig <= 1'b0 ;
R_data_come_single <= 1'b0 ;
end
else begin
case(O_qspi_state)
C_IDLE: begin // 初始化各个寄存器,当检测到命令类型有效(命令类型的最高位位1)以后,进入发送命令码状态
R_qspi_clk_en <= 1'b0 ;
O_qspi_cs <= 1'b1 ;
O_qspi_mosi <= 1'b0 ;
R_cmd_reg <= I_cmd_code ;
R_address_reg <= I_qspi_addr ;
O_done_sig <= 1'b0 ;
if(I_cmd_type[4] == 1'b1) begin //如果flash操作命令请求
O_qspi_state <= C_SEND_CMD ;
R_write_bits_cnt <= 7 ;
R_write_bytes_cnt <= 0 ;
R_read_bytes_num <= 0 ;
end
end
C_SEND_CMD: begin// 发送8-bit命令码状态
R_qspi_clk_en <= 1'b1 ; // 打开SPI串行时钟SCLK的使能开关
O_qspi_cs <= 1'b0 ; // 拉低片选信号CS
if(R_write_bits_cnt > 0) begin //如果R_cmd_reg还没有发送完
O_qspi_mosi <= R_cmd_reg[R_write_bits_cnt] ; //发送bit7~bit1位
R_write_bits_cnt <= R_write_bits_cnt-1'b1 ;
end
else begin //发送bit0
O_qspi_mosi <= R_cmd_reg[0] ;
if ((I_cmd_type[3:0] == 4'b0001) | (I_cmd_type[3:0] == 4'b0100))
begin //如果是写使能指令(Write Enable)或者写不使能指令(Write Disable)
O_qspi_state <= C_FINISH_DONE ;
end
else if (I_cmd_type[3:0] == 4'b0011) begin //如果是读状态寄存器指令(Read Register)
O_qspi_state <= C_READ_WAIT ;
R_write_bits_cnt <= 7 ;
R_read_bytes_num <= 1 ;//读状态寄存器指令需要接收一个数据
end
else if( (I_cmd_type[3:0] == 4'b0010) || (I_cmd_type[3:0] == 4'b0101)
|| (I_cmd_type[3:0] == 4'b0111) || (I_cmd_type[3:0] == 4'b0000) )
begin // 如果是扇区擦除(Sector Erase),页编程指令(Page Program),读数据指令(Read Data),读设备ID指令(Read Device ID)
O_qspi_state <= C_SEND_ADDR ;
R_write_bits_cnt <= 23 ; // 这几条指令后面都需要跟一个24-bit的地址码
end
end
end
C_SEND_ADDR: begin// 发送地址状态
if(R_write_bits_cnt > 0) begin //如果R_cmd_reg还没有发送完
O_qspi_mosi <= R_address_reg[R_write_bits_cnt] ; //发送bit23~bit1位
R_write_bits_cnt <= R_write_bits_cnt - 1 ;
end
else begin
O_qspi_mosi <= R_address_reg[0] ; //发送bit0
if(I_cmd_type[3:0] == 4'b0010) // 扇区擦除(Sector Erase)指令
begin //扇区擦除(Sector Erase)指令发完24-bit地址码就执行结束了,所以直接跳到结束状态
O_qspi_state <= C_FINISH_DONE ;
end
else if (I_cmd_type[3:0] == 4'b0101) begin// 页编程(Page Program)指令
O_qspi_state <= C_WRITE_DATA ;
R_write_bits_cnt <= 7 ;
end
else if (I_cmd_type[3:0] == 4'b0000) begin// 读Device ID指令
O_qspi_state <= C_READ_WAIT ;
R_read_bytes_num <= 2 ; //接收2个数据的Device ID
end
else if (I_cmd_type[3:0] == 4'b0111) begin// 读数据(Read Data)指令
O_qspi_state <= C_READ_WAIT ;
R_read_bytes_num <= 6 ; //接收6个数据
end
end
end
C_READ_WAIT: begin// 读等待状态
if(R_read_finish) begin
O_qspi_state <= C_FINISH_DONE ;
R_data_come_single <= 1'b0 ;
end
else begin
R_data_come_single <= 1'b1 ; // 单线模式下读数据标志信号,此信号为高标志正在接收数据
end
end
C_WRITE_DATA: begin// 写数据状态
if(R_write_bytes_cnt < 256) begin// 往QSPI Flash中写入 256个数据
if(R_write_bits_cnt > 0) begin//如果数据还没有发送完
O_qspi_mosi <= W_rom_out[R_write_bits_cnt] ; //发送bit7~bit1位
R_write_bits_cnt <= R_write_bits_cnt - 1'b1 ;
end
else begin
O_qspi_mosi <= W_rom_out[0] ; //发送bit0
R_write_bits_cnt <= 7 ;
R_write_bytes_cnt <= R_write_bytes_cnt + 1'b1 ;
end
end
else begin
O_qspi_state <= C_FINISH_DONE ;
R_qspi_clk_en <= 1'b0 ;
end
end
C_FINISH_DONE: begin
O_qspi_cs <= 1'b1 ;
O_qspi_mosi <= 1'b0 ;
R_qspi_clk_en <= 1'b0 ;
O_done_sig <= 1'b1 ;
R_data_come_single <= 1'b0 ;
O_qspi_state <= C_IDLE ;
end
default:O_qspi_state <= C_IDLE ;
endcase
end
end
//
// 功能:接收QSPI Flash发送过来的数据
//
always @(posedge I_clk) begin
if(!I_rst_n) begin
R_read_bytes_cnt <= 0 ;
R_read_bits_cnt <= 0 ;
R_read_finish <= 1'b0 ;
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= 0 ;
O_read_data <= 0 ;
end
else if(R_data_come_single) begin // 此信号为高表示接收数据从QSPI Flash发过来的数据
if(R_read_bytes_cnt < R_read_bytes_num) begin
if(R_read_bits_cnt < 8'd7) begin //接收6Bytes
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= {R_read_data_reg[6:0],I_qspi_miso} ;
R_read_bits_cnt <= R_read_bits_cnt + 1'b1 ;
end else begin
O_read_byte_valid <= 1'b1 ; //6个byte数据有效
O_read_data <= {R_read_data_reg[6:0],I_qspi_miso} ; //接收bit47
R_read_bits_cnt <= 0 ;
R_read_bytes_cnt <= R_read_bytes_cnt + 1'b1 ;
end
end else begin
R_read_bytes_cnt <= 0 ;
R_read_finish <= 1'b1 ;
O_read_byte_valid <= 1'b0 ;
end
end else begin
R_read_bytes_cnt <= 0 ;
R_read_bits_cnt <= 0 ;
R_read_finish <= 1'b0 ;
O_read_byte_valid <= 1'b0 ;
R_read_data_reg <= 0 ;
end
end
endmodule