(Serial Peripheral interface)
是由摩托罗拉公司定义的一种串行外围设备接口, 是一种全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于 PCB 的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了 SPI 通信协议,如 FLASH、AD 转换器等。SPI接口一般使用四条信号线通信:
【SDI:(数据输入);SDO:(数据输出);SCK:(时钟);CS:(片选)】
SPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。
SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。【收发同步】
CPOL配置SPI总线的极性
CPHA配置SPI总线的相位
SPI总线极性【CPOL】
CPOL = 1:表示空闲时是高电平;发起通信后的第一个时钟沿为下降沿
CPOL = 0:表示空闲时是低电平;发起通信后的第一个时钟沿为上升沿
数据传输往往是从跳变沿开始的,也就表示开始传输数据的时候,是下降沿还是上升沿。
SPI总线相位【CPHA】
CPHA = 0:【表示从第一个跳变沿开始采样】
CPHA = 1:【表示从第二个跳变沿开始采样】
模式0 (CPOL=0; CPHA=0)
CPOL = 0:空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 0:数据在第1个跳变沿(上升沿)【前沿】采样
CPOL = 0:空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 1:数据在第2个跳变沿(下降沿)采样
CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 0:数据在第1个跳变沿(下降沿)采样
CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 1:数据在第2个跳变沿(上升沿)采样
模式 | CPOL【极性】 | CPHA【相位】 | 描述 |
---|---|---|---|
0 | 0 | 0 | 空闲时为低电平,时钟前沿采样【上升沿或第一个跳变沿】 |
1 | 0 | 1 | 空闲时为低电平,时钟后沿采样【下降沿或第二个跳变沿】 |
2 | 1 | 0 | 空闲时为高电平,时钟前沿采样【下降沿或第一个跳变沿】 |
3 | 1 | 1 | 空闲时为高电平,时钟后沿采样【上升沿或第二个跳变沿】 |
优点
缺点
信号名称 | 英文 | 描述 |
---|---|---|
C | Serial Clock | 串行时钟 |
D | Serial Data Input | 串行数据输入 |
Q | Serial Data Output | 串行数据输出 |
S_N | Chip Select | 片选 |
W_N | Write Protect | 写保护 |
HOLD_N | Hold | 保持 |
VCC | Supply Voltage | 电源电压 |
VSS | Ground | 接地 |
信号作用:
写保护 (W) 和保持 (HOLD) 信号应被驱动为适当的高或低。
读取标识(RDID)指令序列和数据输出序列
写入禁用(WRDI)指令顺序
写入启用(WREN)指令顺序
读取状态寄存器(RDSR)指令序列和数据输出序列
写入状态寄存器(WRSR)指令顺序
读取数据字节(READ)指令序列和数据输出序列
快速读指令序列和数据输出序列
页编程指令
扇区擦除指令【SE】
批量擦除(BE)指令顺序
深度掉电模式【DP】
释放深度掉电模式【RES】
①读器件id操作;②读数据操作;③写数据操作。
写操作包含 4 种操作:读状态寄存器,写使能、扇区擦除,页编程;每个操作又包括了指令、地址或数据组合,并且在写使能、扇区擦除和页编程操作之后还需要延时。
读状态寄存器 读状态寄存器指令 接收数据
写使能 写使能指令 延时
扇区擦除 扇区擦除指令 地址高字节 地址中字节 地址低字节
延时
页编程 页编程指令 地址高字节 地址中字节 地址低字节发送数据 延时
//spi采样发送数据状态参数定义【同步通信 模式三】
localparam IDLE = 3'b001,//初始状态 高电平
REDY = 3'b010,//采样缓冲准备状态
TRAN = 3'b100;//传输状态
localparam IDLE = 4'b0001,//初始状态
RD_CMD = 4'b0010,//发读命令
RD_ADDR = 4'b0100,//读地址
RD_DATA = 4'b1000;//读数据
划分为主从状态机两个部分。主状态机进行操作【使能、扇区擦除、页编程】,从状态机给指令【指令、地址、数据】。这里就不补状态转移图了。
状态参数定义
//状态机状态参数定义 【Master 主 Slave 从】
localparam M_IDLE = 6'b000_001,//初始状态
M_WREN0 = 6'b000_010,//扇区擦除之前使能
M_WRSE = 6'b000_100,//扇区擦除
M_WAIT = 6'b001_000,//等待延时
M_WREN1 = 6'b010_000,//页编程之前使能
M_WRPP = 6'b100_000;//页编程
localparam S_IDLE = 4'b0001,//初始状态
S_WR_CMD = 4'b0010,//写命令
S_WR_ADDR = 4'b0100,//写地址
S_WR_DATA = 4'b1000;//写数据
状态转移
wire m_idle2m_wren0 ;
wire m_wren02m_wrse ;
wire m_wrse2m_wait ;
wire m_wait2m_wren1 ;
wire m_wren12m_wrpp ;
wire m_wrpp2m_idle ;
wire s_idle2s_wr_cmd ;
wire s_wr_cmd2s_idle ;
wire s_wr_cmd2s_wr_addr ;
wire s_wr_addr2s_idle ;
wire s_wr_addr2s_wr_data;
wire s_wr_data2s_idle ;
产生串行时钟sclk,收发数据
//计数器设计 cnt_bit cnt_sclk
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_sclk <= 0;
end
else if(add_cnt_sclk)begin
if(end_cnt_sclk)begin
cnt_sclk <= 0;
end
else begin
cnt_sclk <= cnt_sclk + 1;
end
end
end
assign add_cnt_sclk = state_c == TRAN;//传输状态 开始计时
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == SCLK_CYCLE-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = end_cnt_sclk;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 8-1;
//sclk_s
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sclk_s <= 1'b1;//模式三 初始状态为高电平
end
else if(add_cnt_sclk && cnt_sclk == SCLK_NEDGE-1)begin
sclk_s <= 1'b0;//串行时钟下降沿
end
else if(add_cnt_sclk && cnt_sclk == SCLK_PEDGE-1)begin
sclk_s <= 1'b1;//串行时钟上升沿
end
end
assign sclk = sclk_s;
//采样数据 pick_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
pick_data <= 0;
end
else if(cnt_sclk == SCLK_PEDGE)begin
pick_data[7-cnt_bit]<= miso;//采样数据寄存输入
end
end
assign dout = pick_data;
//数据发送 send_data
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
send_data <= 0;
end
else if(state_c == REDY)begin
send_data <= din;//发送输入数据 等到串行时钟上升沿进行采样输出
end
end
assign mosi = send_data[7-cnt_bit];//MSB 高位在前 循环左移
//cs_n 片选
assign cs_n = !req;
//done 数据传输完成标志
assign done = tran2idle;
module spi_flash #(parameter KEY_W = 2)(
input clk ,//系统时钟 50Mhz
input rst_n ,//系统复位 低电平有效
input [KEY_W-1:0] key_in ,//按键输入 模拟写请求
//SPI接口
output mosi ,//主入从出
output sclk ,//串行时钟
output cs_n ,//片选
input miso ,//主出从入
//数码管
output [1:0] sel ,//数码管位选
output [7:0] dig //数码管段选
);
//中间信号定义
wire [KEY_W-1:0] key_out ;//按键检测输出
wire [7:0] disp_data ;
wire disp_data_vld ;
wire [7:0] trans_data ;
wire [7:0] rd_data ;
wire req ;
wire done ;
//模块例化
//按键消抖
key_debounce #(.KEY_W(2)) u_key_debounce
(
.clk ( clk ),
.rst_n ( rst_n ),
.key_in ( key_in ),
.key_out ( key_out )
);
//数码管驱动
seg_driver u_seg_driver
(
.clk ( clk ),
.rst_n ( rst_n ),
.din ( disp_data ),
.din_vld ( disp_data_vld ),
.sel ( sel ),
.dig ( dig )
);
//flash 控制模块
flash_ctrl u_flash_ctrl
(
.clk ( clk ),
.rst_n ( rst_n ),
.key ( key_out ),
.done ( done ),
.din ( rd_data ),
.dout ( trans_data ),
.req ( req ),
.disp_data ( disp_data ),
.disp_data_vld ( disp_data_vld )
);
//spi接口
spi_interface u_spi_interface
(
.clk ( clk ),
.rst_n ( rst_n ),
.din ( trans_data ),
.req ( req ),
.done ( done ),
.dout ( rd_data ),
.miso ( miso ),
.cs_n ( cs_n ),
.mosi ( mosi ),
.sclk ( sclk )
);
endmodule
//flash控制模块 控制flash读写
//例化读写模块 控制读写
module flash_ctrl(
input clk ,//系统时钟
input rst_n ,//复位
//按键
input [1:0] key ,//按键 控制flash读写
//spi_interface
input done ,//传输完成标志
input [7:0] din ,//data in
output [7:0] dout ,//数据输出
output req ,//输出请求信号
//seg数码管显示数据
output [7:0] disp_data ,//数码管显示数据
output disp_data_vld //显示数据有效
);
//中间信号定义
wire [7:0] rd_dout ;
wire rd_done ;
wire [7:0] wr_dout ;
wire wr_done ;
wire rd_req ;
wire wr_req ;
wire [7:0] wr_din ;
//模块例化
//flash读模块 flash_read
flash_read u_flash_read
(
.clk ( clk ),
.rst_n ( rst_n ),
.din ( din ),
.done ( rd_done ),
.rd_flag ( key[0] ),
.dout ( rd_dout ),
.disp_data ( disp_data ),
.disp_data_vld ( disp_data_vld ),
.rd_req ( rd_req )
);
//flash写模块 flash_write
flash_write u_flash_write
(
.clk ( clk ),
.rst_n ( rst_n ),
.done ( wr_done ),
.wr_flag ( key[1] ),
.dout ( wr_dout ),
.wr_req ( wr_req )
);
assign req = rd_req | wr_req;
assign dout = rd_req?rd_dout:wr_dout;
assign wr_done = done;
assign rd_done = done;
endmodule
//计数器 cnt_byte
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
end
assign add_cnt_byte = (state_c != IDLE) && (done);//非初始状态且字节传输完成 开始计数
assign end_cnt_byte = add_cnt_byte && cnt_byte == X-1;
//字节寄存 组合逻辑
always @(*) begin
if(state_c == RD_CMD)begin
X = 1;
end
else if(state_c == RD_ADDR)begin
X = 3;
end
else if(state_c == RD_DATA)begin
X = 1;
end
else begin
X = 0;
end
end
//读请求寄存 rd_req_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_req_r <= 1'b0;
end
else if(idle2rd_cmd)begin//开始发送数据命令 拉高请求信号
rd_req_r <= 1'b1;
end
else if(rd_data2idle)begin//读取数据完成 拉低请求信号
rd_req_r <= 1'b0;
end
end
assign rd_req = rd_req_r;
//dout_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout_r <= 0;
end
else if(idle2rd_cmd)begin
dout_r <= 8'h03;//发送读数据命令
end
else if(rd_cmd2rd_addr)begin
dout_r <= 0;
end
end
assign dout = dout_r;
//din_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
din_r <= 0;
end
else if(rd_data2idle)begin
din_r <= din;
end
end
//disp_data disp_data_vld
assign disp_data = din;
assign disp_data_vld = rd_data2idle;
//计数器设计 cnt_wait cnt_100ns cnt_key cnt_byte
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_key <= 0;
end
else if(add_cnt_key)begin
if(end_cnt_key)begin
cnt_key <= 0;
end
else begin
cnt_key <= cnt_key + 1;
end
end
end
assign add_cnt_key = wr_flag;
assign end_cnt_key = add_cnt_key && cnt_key == 256-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_wait <= 0;
end
else if(add_cnt_wait)begin
if(end_cnt_wait)begin
cnt_wait <= 0;
end
else begin
cnt_wait <= cnt_wait + 1;
end
end
end
assign add_cnt_wait = (m_state_c == M_WAIT);//等待状态下 开始计数
assign end_cnt_wait = add_cnt_wait && cnt_wait == TIME_3S-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_100ns <= 0;
end
else if(add_100ns)begin
if(end_100ns)begin
cnt_100ns <= 0;
end
else begin
cnt_100ns <= cnt_100ns + 1;
end
end
end
assign add_100ns = delay_flag;
assign end_100ns = add_100ns && cnt_100ns == 5-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
end
assign add_cnt_byte = (s_state_c != S_IDLE) && done;//处于发送指令状态且发送完成
assign end_cnt_byte = add_cnt_byte && cnt_byte == XX-1;
//字节数 XX
always @(*) begin
if(s_state_c == S_WR_CMD)begin
XX = 1;
end
else if(s_state_c == S_WR_ADDR)begin
XX = 3;
end
else if(s_state_c == S_WR_DATA)begin
XX = 1;
end
else begin
XX = 0;
end
end
reg wr_flag_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_flag_r <= 1'b0;
end
else begin
wr_flag_r <= wr_flag;
end
end
//取消片选延时 delay_flag
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
delay_flag <= 1'b1;
end
else if(s_wr_cmd2s_idle | s_wr_addr2s_idle | s_wr_data2s_idle)begin
delay_flag <= 1'b1;
end
else if(s_idle2s_wr_cmd)begin
delay_flag <= 1'b0;
end
end
//wr_req_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
wr_req_r <= 1'b0;
end
else if(s_idle2s_wr_cmd)begin
wr_req_r <= 1'b1;
end
else if(s_wr_cmd2s_idle | s_wr_addr2s_idle | s_wr_data2s_idle)begin
wr_req_r <= 1'b0;
end
end
assign wr_req = wr_req_r;
//dout_r
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dout_r <= 0;
end
else if((m_state_c == M_WREN0) | (m_state_c == M_WREN1))begin//发送使能命令
dout_r <= 8'h06;
end
else if(m_state_c == M_WRSE)begin
if(s_state_c == S_WR_CMD)begin//擦除
dout_r <= 8'hd8;
end
else if(s_state_c == S_WR_ADDR)begin
dout_r <= 0;
end
end
else if(m_state_c == M_WRPP)begin
if(s_state_c == S_WR_CMD)begin//页编程
dout_r <= 8'h02;
end
else if(s_state_c == S_WR_ADDR)begin
dout_r <= 0;
end
else if(s_state_c == S_WR_DATA)begin
dout_r <= cnt_key;
end
end
end
assign dout = dout_r;
//数码管驱动
`include "number.v"
module seg_driver(
input clk ,//系统时钟
input rst_n ,//复位
input [7:0] din ,//data in
input din_vld ,
output reg[1:0] sel ,//数码管位选
output reg[7:0] dig //数码管段选
);
//数码管刷新参数定义
parameter TIME_REFRE = 25_000;
//信号定义
reg [19:0] cnt_refre ;//数码管刷新计数器
wire add_cnt_refre ;
wire end_cnt_refre ;
reg [3:0] data ;//数码管位选数字显示
reg [7:0] disp_num ;//显示数字
//计数器设计 cnt_refre
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_refre <= 0;
end
else if(add_cnt_refre)begin
if(end_cnt_refre)begin
cnt_refre <= 0;
end
else begin
cnt_refre <= cnt_refre + 1;
end
end
end
assign add_cnt_refre = 1'b1;
assign end_cnt_refre = add_cnt_refre && cnt_refre == TIME_REFRE-1;
//disp_num
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
disp_num <= 0;
end
else if(din_vld)begin
disp_num <= din;
end
end
//sel
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sel <= 2'b10;
end
else if(end_cnt_refre)begin
sel <= ~sel;
end
end
//data
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data <= 0;
end
else begin
case(sel)
2'b01:data <= disp_num[3:0];
2'b10:data <= disp_num[7:4];
default:data <= disp_num[3:0];
endcase
end
end
//dig
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dig <= 8'hff;
end
else begin
case(data)
4'h0:dig <= `ZERO ;
4'h1:dig <= `ONE ;
4'h2:dig <= `TWO ;
4'h3:dig <= `THREE ;
4'h4:dig <= `FOUR ;
4'h5:dig <= `FIVE ;
4'h6:dig <= `SIX ;
4'h7:dig <= `SEVEN ;
4'h8:dig <= `EIGHT ;
4'h9:dig <= `NINE ;
4'ha:dig <= `A ;
4'hb:dig <= `B ;
4'hc:dig <= `C ;
4'hd:dig <= `D ;
4'he:dig <= `E ;
4'hf:dig <= `F ;
default: dig <= 8'hff;
endcase
end
end
endmodule
flash_read
flash_write
spi_interface
SPI总线工作模式
SPI总线传输的4种模式