通信协议篇——SPI通信
程序员文章站
2022-07-09 12:53:47
...
通信协议篇——SPI通信
1.简介
SPI(Serial Peripheral Interface)是一种高速、同步、全双工串行通信总线,采用主从机通信模式,主要应用在EEPROM,FLASH,实时时钟,AD转换器等。
2.原理
通信方式
SPI通信属于串行通信,利用芯片选择/使能线CS、串行时钟线SCLK、数据输入线DATAIN、数据输出线DATAOUT四线实现同步全双工通信。
通信模式
SIP通信有四种模式,由时钟极性和时钟相位设置不同模式;
CPOL:时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平;
CPHA:时钟相位选择,为0时在SCLK第一个跳变沿采样,为1时在SCLK第二个跳变沿采样;
Mode0: CPOL=0,CPHA=0;SPI总线空闲状态为低电平,SCLK第一个跳变沿是上升沿,所以在时钟上升沿对数据采样,在时钟下降沿发送数据;
Mode1: CPOL=0,CPHA=1;SPI总线空闲状态为低电平,SCLK第一个跳变沿是上升沿,所以在时钟上升沿发送数据,在时钟下降沿对数据采样;
Mode2: CPOL=1,CPHA=0;SPI总线空闲状态为高电平,SCLK第一个跳变沿是下降沿,所以在时钟上升沿发送数据,在时钟下降沿对数据采样;
Mode3: CPOL=1,CPHA=1;SPI总线空闲状态为高电平,SCLK第一个跳变沿是下降沿,所以在时钟上升沿对数据采样,在时钟下降沿发送数据;
数据格式
SPI通信并没有固定的数据格式,可以根据不同的应用进行灵活地运用。数据内容大致可以分为三类——指令、地址、数据。以FLASH中的SPI为例,指令长度为8位,地址长度为24位,数据长度以字节为单位,数据格式主要有指令、指令+地址、指令+数据、指令+地址+数据。
操作时序
以FLASH的操作控制为例,展示几种SPI读写时序(采用Mode0或Mode3):
- 写使能:发送命令字0X06
- Sector擦除:发送命令字0X20,再发送24位地址
- 数据读:发送命令字0X03,再发送24位地址,然后接收数据
标准接口
name | description | direction | length |
---|---|---|---|
clk | 系统时钟 | input | 1 |
rst | 复位信号 | input | 1 |
spi_cs | 从机选择信号 | output | 1 |
spi_clk | SPI时钟信号 | output | 1 |
spi_mosi | SPI数据输出 | output | 1 |
spi_miso | SPI数据输入 | input | 1 |
3.程序实现
RTL视图
spi模块
`timescale 1ns/1ps
////////////////////////////////////////////
//Module Name : spi
//Description : spi communication module
//Editor : Yongxiang
//Time : 2020-02-03
////////////////////////////////////////////
module spi
(
output wire flash_clk,
output reg flash_cs,
output reg flash_datain,
input wire flash_dataout,
input wire clock25M,
input wire flash_rstn,
input wire[3:0] cmd_type,
output reg Done_Sig,
input wire[7:0] flash_cmd,
input wire[23:0] flash_addr,
output reg[7:0] mydata_o,
output wire myvalid_o
);
assign myvalid_o = myvalid;
assign flash_clk = spi_clk_en ? clock25M : 1'b0;
reg myvalid;
reg[7:0] mydata;
reg spi_clk_en = 1'b0;
reg data_come;
parameter idle = 3'b000;
parameter cmd_send = 3'b001;
parameter address_send = 3'b010;
parameter read_wait = 3'b011;
parameter write_data = 3'b101;
parameter finish_done = 3'b110;
reg[2:0] spi_state;
reg[7:0] cmd_reg;
reg[23:0] address_reg;
reg[7:0] cnta;
reg[8:0] write_cnt;
reg[7:0] cntb;
reg[8:0] read_cnt;
reg[8:0] read_num;
reg read_finish;
//发送读flash命令
always @(negedge clock25M)
begin
if(!flash_rstn)begin
flash_cs <= 1'b1;
spi_state <= idle;
cmd_reg <= 8'd0;
address_reg <= 24'd0;
spi_clk_en <= 1'b0; //SPI clock输出不使能
cnta <= 8'd0;
write_cnt <= 9'd0;
read_num <= 9'd0;
Done_Sig <= 1'b0;
end
else begin
case(spi_state)
idle:begin //idle 状态
spi_clk_en <= 1'b0;
flash_cs <= 1'b1;
flash_datain <= 1'b1;
cmd_reg <= flash_cmd;
address_reg <= flash_addr;
Done_Sig <= 1'b0;
if(cmd_type[3] == 1'b1)begin //bit3为命令请求,高表示操作命令请求
spi_state <= cmd_send;
cnta <= 8'd7;
write_cnt <= 9'd0;
read_num <= 9'd0;
end
end
cmd_send:begin //发送命令状态
spi_clk_en <= 1'b1; //flash的SPI clock输出
flash_cs <= 1'b0; //cs拉低
if(cnta > 8'd0)begin //如果cmd_reg还没有发送完
flash_datain <= cmd_reg[cnta]; //发送bit7~bit1位
cnta <= cnta - 8'd1;
end
else begin //发送bit0
flash_datain <= cmd_reg[0];
if((cmd_type[2:0] == 3'b001) | (cmd_type[2:0] == 3'b100))begin //如果是Write Enable/disable instruction
spi_state <= finish_done;
end
else if(cmd_type[2:0] == 3'b011)begin //如果是read register1
spi_state <= read_wait;
cnta <= 8'd7;
read_num <= 9'd1; //接收一个数据
end
else begin //如果是sector erase, page program, read data,read device ID
spi_state <= address_send;
cnta <= 8'd23;
end
end
end
address_send:begin //发送flash address
if(cnta > 8'd0)begin //如果cmd_reg还没有发送完
flash_datain <= address_reg[cnta]; //发送bit23~bit1位
cnta <= cnta - 8'd1;
end
else begin //发送bit0
flash_datain <= address_reg[0];
if(cmd_type[2:0] == 3'b010)begin //如果是 sector erase
spi_state <= finish_done;
end
else if(cmd_type[2:0] == 3'b101)begin //如果是page program
spi_state <= write_data;
cnta <= 8'd7;
end
else if(cmd_type[2:0] == 3'b000)begin //如果是读Device ID
spi_state <= read_wait;
read_num <= 9'd2; //接收2个数据的Device ID
end
else begin
spi_state <= read_wait;
read_num <= 9'd256; //如果是block读命令,接收256个数据
end
end
end
read_wait:begin //等待flash数据读完成
if(read_finish)begin
spi_state <= finish_done;
data_come <= 1'b0;
end
else begin
data_come <= 1'b1;
end
end
write_data:begin //写flash block数据
if(write_cnt < 9'd256)begin // program 256 byte to flash
if(cnta > 8'd0)begin //如果data还没有发送完
flash_datain <= write_cnt[cnta]; //发送bit7~bit1位
cnta <= cnta - 8'd1;
end
else begin
flash_datain <= write_cnt[0]; //发送bit0
cnta <= 8'd7;
write_cnt <= write_cnt + 9'd1;
end
end
else begin
spi_state <= finish_done;
spi_clk_en <= 1'b0;
end
end
finish_done:begin //flash操作完成
flash_cs <= 1'b1;
flash_datain <= 1'b1;
spi_clk_en <= 1'b0;
Done_Sig <= 1'b1;
spi_state <= idle;
end
default:begin
spi_state <= idle;
end
endcase;
end
end
//接收flash数据
always @(posedge clock25M)
begin
if(!flash_rstn)begin
read_cnt <= 9'd0;
cntb <= 8'd0;
read_finish <= 1'b0;
myvalid <= 1'b0;
mydata <= 8'd0;
mydata_o <= 8'd0;
end
else begin
if(data_come)begin
if(read_cnt < read_num)begin //接收数据
if(cntb < 8'd7)begin //接收一个byte的bit0~bit6
myvalid <= 1'b0;
mydata <= {mydata[6:0], flash_dataout};
cntb <= cntb + 8'd1;
end
else begin
myvalid <= 1'b1; //一个byte数据有效
mydata_o <= {mydata[6:0], flash_dataout}; //接收bit7
cntb <= 8'd0;
read_cnt <= read_cnt + 9'd1;
end
end
else begin
read_cnt <= 9'd0;
read_finish <= 1'b1;
myvalid <= 1'b0;
end
end
else begin
read_cnt <= 9'd0;
cntb <= 8'd0;
read_finish <= 1'b0;
myvalid <= 1'b0;
mydata <= 8'd0;
end
end
end
endmodule
flash_control模块
`timescale 1ns/1ps
////////////////////////////////////////////
//Module Name : flash_control
//Description : flash read and write control
//Editor : Yongxiang
//Time : 2020-02-03
////////////////////////////////////////////
module flash_control
(
input wire CLK,
input wire RSTn,
output reg clock25M,
output reg[3:0] cmd_type,
input wire Done_Sig,
output reg[7:0] flash_cmd,
output reg[23:0] flash_addr,
input wire[7:0] mydata_o,
input wire myvalid_o
);
reg[3:0] i;
reg[7:0] time_delay;
//FLASH 擦除,Page Program,读取程序
always @(posedge clock25M)
begin
if(!RSTn)begin
i <= 4'd0;
flash_addr <= 24'd0;
flash_cmd <= 8'd0;
cmd_type <= 4'b0000;
time_delay <= 8'd0;
end
else begin
case(i)
4'd0:begin //读Device ID
if( Done_Sig )begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h90;
flash_addr <= 24'd0;
cmd_type <= 4'b1000;
end
end
4'd1:begin //写Write Enable instruction
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h06;
cmd_type <= 4'b1001;
end
end
4'd2:begin //Sector擦除
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type<=4'b0000;
end
else begin
flash_cmd <= 8'h20;
flash_addr <= 24'd0;
cmd_type <= 4'b1010;
end
end
4'd3:begin //waitting 100 clock
if(time_delay < 8'd100)begin
flash_cmd <= 8'h00;
time_delay <= time_delay + 8'd1;
cmd_type <= 4'b0000;
end
else begin
i <= i + 4'd1;
time_delay <= 8'd0;
end
end
4'd4:begin //读状态寄存器1, 等待idle
if(Done_Sig)begin
if(mydata_o[0] == 1'b0)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
4'd5:begin //写Write disable instruction
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h04;
cmd_type <= 4'b1100;
end
end
4'd6:begin //读状态寄存器1, 等待idle
if(Done_Sig)begin
if(mydata_o[0] == 1'b0)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
4'd7:begin //写Write Enable instruction
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h06;
cmd_type <= 4'b1001;
end
end
4'd8:begin //waitting 100 clock
if(time_delay < 8'd100)begin
flash_cmd <= 8'h00;
time_delay <= time_delay + 8'd1;
cmd_type <= 4'b0000;
end
else begin
i <= i + 4'd1;
time_delay <= 8'd0;
end
end
4'd9:begin //page program: write 0~255 to flash
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h02;
flash_addr <= 24'd0;
cmd_type <= 4'b1101;
end
end
4'd10:begin //waitting
if(time_delay < 8'd100)begin
flash_cmd <= 8'h00;
time_delay <= time_delay + 8'd1;
cmd_type <= 4'b0000;
end
else begin
i <= i + 4'd1;
time_delay <= 8'd0;
end
end
4'd11:begin //读状态寄存器1, 等待idle
if(Done_Sig)begin
if(mydata_o[0] == 1'b0)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
4'd12:begin //写Write disable instruction
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h04;
cmd_type <= 4'b1100;
end
end
4'd13:begin //读状态寄存器1, 等待idle
if(Done_Sig)begin
if(mydata_o[0] == 1'b0)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
else begin
flash_cmd <= 8'h05;
cmd_type <= 4'b1011;
end
end
4'd14:begin //read 256byte
if(Done_Sig)begin
flash_cmd <= 8'h00;
i <= i + 4'd1;
cmd_type <= 4'b0000;
end
else begin
flash_cmd <= 8'h03;
flash_addr <= 24'd0;
cmd_type <= 4'b1110;
end
end
4'd15:begin //idle
i <= 4'd15;
end
endcase
end
end
//产生25Mhz的SPI Clock
always @(posedge CLK)
begin
if(!RSTn)begin
clock25M <= 1'b0;
end
else begin
clock25M <= ~clock25M;
end
end
endmodule
顶层模块
`timescale 1ns/1ps
////////////////////////////////////////////
//Module Name : flash
//Description : top_file
//Editor : Yongxiang
//Time : 2020-02-03
////////////////////////////////////////////
module flash
(
input wire CLK,
input wire RSTn,
output wire flash_clk, //spi flash clock
output wire flash_cs, //spi flash cs
output wire flash_datain, //spi flash data input
input wire flash_dataout //spi flash data output
);
wire[7:0] flash_cmd;
wire[23:0] flash_addr;
wire clock25M;
wire[3:0] cmd_type;
wire Done_Sig;
wire[7:0] mydata_o;
wire myvalid_o;
//spi通信
spi spi_inst
(
.flash_clk(flash_clk),
.flash_cs(flash_cs),
.flash_datain(flash_datain),
.flash_dataout(flash_dataout),
.clock25M(clock25M), //input clock
.flash_rstn(RSTn), //input reset
.cmd_type(cmd_type), // flash command type
.Done_Sig(Done_Sig), //output done signal
.flash_cmd(flash_cmd), // input flash command
.flash_addr(flash_addr),// input flash address
.mydata_o(mydata_o), // output flash data
.myvalid_o(myvalid_o) // output flash data valid
);
//flash控制
flash_control flash_control_inst
(
.CLK(CLK),
.RSTn(RSTn),
.clock25M(clock25M),
.cmd_type(cmd_type),
.Done_Sig(Done_Sig),
.flash_cmd(flash_cmd),
.flash_addr(flash_addr),
.mydata_o(mydata_o),
.myvalid_o(myvalid_o)
);
endmodule