电子说
01
SPI基础知识
SPI(Serial Perripheral Interface, 串行外围设备接口) 是 Motorola 公司推出的一种同步串行接口技术。SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信。
**SPI **主要应用在 EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC), 数字信号处理器(DSP) 以及数字信号解码器之间。它在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间。正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术。
SPI主要具有以下特点:
1.1 采用主-从模式(Master-Slave) 的控制方式SPI
规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作.
1.2 采用同步方式(Synchronous)传输数据
采用同步方式(Synchronous)传输数据Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.
1.3 数据交换(Data Exchanges)
SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access). 所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.
在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效. 因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的.
02
SPI传输协议
2.1 SPI协议层相关组件和接口
时钟相关概念:
CPOL(clock polarity) : 时钟极性, 表示 SPI 在空闲时, 时钟信号是高电平还是低电平. 若 CPOL 被设为 1, 那么该设备在空闲时 SCK 管脚下的时钟信号为高电平. 当 CPOL 被设为 0 时则正好相反;
CPHA(clock phase) : 时钟相位, 表示 SPI 设备是在 SCK 管脚上的时钟信号变为上升沿时触发数据采样, 还是在时钟信号变为下降沿时触发数据采样. 若 CPHA 被设置为 1, 则 SPI 设备在时钟信号变为下降沿时触发数据采样, 在上升沿时发送数据. 当 CPHA 被设为 0 时也正好相反.
SSPBUF(Synchronous Serial Port Buffer) : 泛指 SPI 设备里面的内部缓冲区, 一般在物理上是以 FIFO 的形式, 保存传输过程中的临时数据;
SSPSR(Synchronous Serial Port Register) : 泛指 SPI 设备里面的移位寄存器(Shift Regitser), 它的作用是根据设置好的数据位宽(bit-width) 把数据移入或者移出 SSPBUF;
Controller : 泛指 SPI 设备里面的控制寄存器, 可以通过配置它们来设置 SPI 总线的传输模式.
通常情况下, 我们只需要对上图所描述的四个管脚(pin) 进行控制即可实现整个 SPI 设备之间的数据通信:
SCK(Serial Clock) : 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;
SS/CS(Slave Select/Chip Select) : 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;
SDO/MOSI(Serial Data Output/Master Out Slave In) : 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;
SDI/MISO(Serial Data Input/Master In Slave Out) : 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据。
2.2 SPI协议层传输规则
当SPI进行数据传输时,片选信号cs_n置低,数据位按照SPI同步时钟发送。主设备发给从设备的数据沿mosi数据线发送,从设备同步发给主设备的数据沿miso数据线发送,主从设备接收的数据依次分别存入主从设备内的FIFO中。
2.3 说明
03
SPI从机配置方式
3.1 常规多片选模式
上图所示系统中,主机需要为每一个从机提供单独的片选信号,一旦主机使能(拉低)片选信号,MOSI/MISO线上的时钟和数据便可用于所选的从机。如果使能多个片选信号,则MISO线上的数据会被破坏,因为主机无法识别哪个从机正在传输数据。
但是,随着从机数量的增加,来自主机的片选线的数量也增加。这会快速增加主机需要提供的输入和输出数量,并限制可以使用的从机数量。可以使用其他技术来增加常规模式下的从机数量,例如使用多路复用器产生片选信号。
3.2 菊花链模式
在菊花链模式下,所有从机的片选信号连接在一起,数据从一个从机传播到下一个从机。在此配置中,所有从机同时接收同一SPI时钟。来自主机的数据直接送到第一个从机,该从机将数据提供给下一个从机,依此类推。
使用该方法时,由于数据是从一个从机传播到下一个从机,所以传输数据所需的时钟周期数与菊花链中的从机位置成比例。例如,在上图所示的8位系统中,为使第3个从机能够获得数据,需要24个时钟脉冲,而常规SPI模式下只需8个时钟脉冲。
时钟周期和通过菊花链的数据传播如下图所示:
04
SPI的Verilog实现代码
4.1 SPI目标实现功能
设计4线SPI master 模块和slave 模块,要求如下:
module spi_master(
input clk_40k, //时钟信号,40kHz
input rst_n, //复位信号,低有效
input [7:0] data_in, //主机准备要输出给从机的数据,8位宽
input send_start, //通信使能信号,高有效,宽度为1个时钟周期(40kHz),收到该信号后开始一次主从设备通信
output [7:0] data_out, //主机从从机接收到的数据,8位宽
output data_out_vld, //输出数据有效标志,高电平有效,宽度为1个时钟周期(40kHz)
output cs_n, //从设备片选使能信号,低有效,低电平时选中从设备与主设备进行通信,处于通信状态时维持低电平
output sclk, //同步时钟,1kHz,空闲时置低电平
input miso, //主机当前从从机收到的串行数据
output mosi //主机当前发送给从机的串行数据
);
module spi_slave(
input rst_n, //复位信号,低有效
input cs_n, //从设备片选使能信号
input sclk, //SPI时钟,1kHz空闲时置低电平
input mosi, //从机从主机接收到的串行数据
output miso, //从机要发送给主机的串行数据
output [7:0] reg0_out, //内部寄存器0的值
output [7:0] reg1_out, //内部寄存器1的值
output [7:0] reg2_out, //内部寄存器2的值
output [7:0] reg3_out //内部寄存器3的值
);
Slave模块中有四个八位内部寄存器(reg0、reg1、reg2、reg3),地址分别为 0~3,master模块通过SPI总线配置slave模块中四个寄存器的值,slave寄存器的值直接通过其端口输出。Master模块收到send_start信号之后,将数据data_in通过spi总线发送到slave模块的reg0,然后将data_in循环右移两位后发送到slave模块的reg1,然后再将data_in循环右移两位发送到reg2,最后将data_in再循环右移两位发送到reg3。至此,master完成对slave中所有寄存器的配置。然后master再通过spi总线将slave中reg2的数据读出来,通过data_out输出,并同时给出一个周期宽度的data_out_vld。
SPI每帧数据包含16位,最先发送的第0位为读写控制位,该位为0代表master向slave写数据,为1则代表master从slave读数据;随后发送的第1-7位为地址位,先发高位地址再发低位地址,9-16位为数据位,高位数据先发。所有数据均在sclk的上升沿产生,下降沿采样。
SPI写数据格式如图所示:
SPI读数据格式如图所示:
4.2 Verilog代码
1. 主模块(SPI_master):
module spi_master(
input clk_40k,
input rst_n,
input [7:0] data_in,
input send_start,
output [7:0] data_out,
output data_out_vld,
output cs_n,
output sclk,
input miso,
output mosi
);
reg cs_n_r;
reg sclk_r;
reg mosi_r;
reg data_out_vld_r;
reg [7:0] data_out_r;
reg wr_rd;
reg [1:0] reg_num;
reg [6:0] clk_cnt;
reg [3:0] bit_cnt;
reg [7:0] rx_data;
reg [15:0] tx_data;
parameter reg0 = 2'd0;
parameter reg1 = 2'd1;
parameter reg2 = 2'd2;
parameter reg3 = 2'd3;
parameter reg0_address = 7'b0000000; //address of reg0
parameter reg1_address = 7'b0000001; //address of reg1
parameter reg2_address = 7'b0000010; //address of reg2
parameter reg3_address = 7'b0000011; //address of reg3
//cs_n
assign cs_n = cs_n_r;
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
cs_n_r <= 1'b1;
else if(send_start)
cs_n_r <= 1'b0;
else if(data_out_vld_r)
cs_n_r <= 1'b1;
end
//clk_cnt
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
clk_cnt <= 7'b0;
else if(cs_n_r)
clk_cnt <= 7'b0;
else if(clk_cnt == 7'd39)
clk_cnt <= 7'b0;
else
clk_cnt <= clk_cnt + 1'b1;
end
//bit_cnt
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
bit_cnt <= 4'b0;
else if(clk_cnt == 7'd39)
bit_cnt <= bit_cnt + 1'b1;
end
//sclk
assign sclk = sclk_r;
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
sclk_r <= 1'b0;
else if(clk_cnt == 7'd19 || clk_cnt == 7'd39)
sclk_r <= ~sclk_r;
end
//wr_rd
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
wr_rd <= 1'b0;
else if(cs_n_r)
wr_rd <= 1'b0;
else if(reg_num == reg3 && bit_cnt == 4'd15 && clk_cnt == 7'd39)
wr_rd <= 1'b1;
end
//send
//reg_num
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
reg_num <= reg0;
else if(cs_n_r)
reg_num <= reg0;
else if(bit_cnt == 4'd15 && clk_cnt == 7'd39)
case(reg_num)
reg0:reg_num <= reg1;
reg1:reg_num <= reg2;
reg2:reg_num <= reg3;
reg3:reg_num <= reg0;
endcase
end
//tx_data
always @ (posedge sclk_r or negedge rst_n)
begin
if(~rst_n)
tx_data <= 16'b0;
else if(wr_rd && bit_cnt == 4'd0)
tx_data <= {1'b1,reg2_address,8'b0};
else if(~wr_rd && bit_cnt == 4'd0)
case(reg_num)
reg0:tx_data <= {1'b0,reg0_address,data_in};
reg1:tx_data <= {1'b0,reg1_address,data_in[1:0],data_in[7:2]};
reg2:tx_data <= {1'b0,reg2_address,data_in[3:0],data_in[7:4]};
reg3:tx_data <= {1'b0,reg3_address,data_in[5:0],data_in[7:6]};
endcase
else
tx_data <= {tx_data[14:0],tx_data[15]};
end
//mosi
assign mosi = tx_data[15];
//recieve
//rx_data
always @ (posedge sclk_r or negedge rst_n)
begin
if(~rst_n)
rx_data <= 8'b0;
else if(wr_rd)
rx_data <= {rx_data[6:0],miso};
end
//data_out_vld
assign data_out_vld = data_out_vld_r;
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
data_out_vld_r <= 1'b0;
else if(wr_rd && bit_cnt == 4'd15 && clk_cnt == 7'd39)
data_out_vld_r <= 1'b1;
else
data_out_vld_r <= 1'b0;
end
//data_out
assign data_out = data_out_r;
always @ (posedge clk_40k or negedge rst_n)
begin
if(~rst_n)
data_out_r <= 8'b0;
else if(wr_rd && bit_cnt == 4'd15 && clk_cnt == 7'd39)
data_out_r <= rx_data;
end
endmodule
全部0条评论
快来发表一下你的评论吧 !