接口/总线/驱动
SPI 简介
SPI 全称为 Serial Peripheral Interface,译为串行外设接口。它是 Motorola 公司推出的一种相对高速的同步、全双工的通信总线协议。
SPI 一般有以下几个特点:
◆ 因为时钟线的存在,SPI 是同步的串行通信总线。
◆ 因为 Master 与 Slave 之间存在两根不同方向的数据线,所以 SPI 支持全双工传输。
◆ SPI 通信协议简单,数据传输速率较快,传输速率没有特殊设定,一般在 10兆波特率以下,最高可支持 30Mbps 甚至 50Mbps。
◆ SPI 采用主从机通信模式,应用广泛,多用于 EEPROM、Flash、实时时钟 (RTC)、 数模转换器 (ADC) 等模块的通信。
◆ 缺点是没有应答机制和校验机制,不能确认是否接收到数据、是否传输有错。
SPI 引脚
SPI 通信最少需要 4 根信号线,分别是 CSN、SCLK、MOSI 与 MISO。各个信号说明如下:
◆ CS/CSN: Chip Select,主设备产生的从设备片选信号。当 Slave 片选信号有效时,Slave 可被 Master 访问并进行通信。一个 Master 可能有多个片选信号,但一个 Slave 只能有一个片选信号。CS 表示片选信号为高时开始数据传输,CSN 表示片选信号为低时开始数据传输。
◆ SCLK: Master 产生的时钟信号,用于数据的同步传输。时钟频率决定了数据传输速率。
◆ MOSI: Mater Output Slave Input,主设备输出、从设备输入的数据线,用于 Master 向 Slave 进行数据传输。
◆ MISO: Mater Input Slave Output,主设备输入、从设备输输出的数据线,用于 Slave 向 Master 进行数据传输。
SPI 的通信采用主从模式,通常会有一个主设备和一个或多个从设备。
某 Master 与 多个 Slave 通过 SPI 信号线的互联示意图如下。
SPI 协议
SPI 通信有 4 种传输模式,规定了数据在不同时钟边沿的采样与发送规则,由时钟极性 (CPOL,Clock Polarity) 和时钟相位 (CPHA,Clock Phase) 两两组合来定义。其中,CPOL 参数决定了时钟信号 SCLK 空闲状态为低电平还是高电平,CPHA 参数决定了数据是在时钟 SCKL 的上升沿采样还是下降沿采样。Slave 可能在出厂时就配置为某种模式不能修改的固定模式,这就要求 SPI Master 发送的传输模式要与 Slave 一致。
SPI 的 4 种传输模式示意图如下:
SPI 的 4 种传输模式说明如下:
◆ CPOL=0,CPHA=0:空闲态时 SCLK 处于低电平,数据采样发生在 SCLK 时钟的第一个边沿时刻。即 Slave 在 SCLK 由低电平到高电平的上升沿跳变时进行数据采样,Master 在 SCLK 下降沿发送数据。
◆ CPOL=0,CPHA=1:空闲态时 SCLK 处于低电平,数据采样发生在 SCLK 时钟的第二个边沿时刻。即 Slave 在 SCLK 下降沿接收数据,Master 在 SCLK 上升沿发送数据。
◆ CPOL=1,CPHA=0:空闲态时 SCLK 处于高电平,数据采样发生在 SCLK 时钟的第一个边沿时刻。即 Slave 在 SCLK 下降沿接收数据,Master 在 SCLK 上升沿发送数据。
◆ CPOL=1,CPHA=1:空闲态时 SCLK 处于高电平,数据采样发生在 SCLK 时钟的第二个边沿时刻。即 Slave 在 SCLK 上升沿接收数据,Master 在 SCLK 下降沿发送数据。
以 CPOL=1、CPHA=1 为例,说明 SPI Master 向 Slave 传输 4bit 数据、并读取 4bit 数据的过程,示意图如下。
(1) 空闲状态,SCLK、CSN、MOSI、MISO 均为高电平;
(2) SPI Master CSN 拉低,选择对应 SPI Slave,将开始传输数据;
(3) SCLK 拉低,同时 MOSI 输出单 bit 数据 D1 ;
(4) SCLK 拉高,此时 SPI Slave 读取 MOSI 对应的数据 D1 ;
(5) 重复此过程,直至 SPI Slave 接收到 4bit
(6) 如果 Slave 接收到的 4bit 数据包含 SPI Master 读控制,则在 SPI Master 仍然会继续输出时钟 SCLK,但无需做额外驱动 MOSI ;
(7) SPI Slave 在 SCLK 下降沿输出数据至 MISO;
(8) SPI Master 在 SCLK 上升沿对 MISO 进行采集;
(9) 重复步骤 (6)~(8),直至 Slave 传输完 4bit 数据。
需要注意的是,SPI 协议中的时钟线 SCLK、片选线 CSN 和数据线 MOSI 都是由 SPI Master 控制,并不像 UART 或 IIC 协议有明显的通信起始信号、结束信号和通信周期,所以 SPI 通信时 SCLK 有效个数和 CSN 有效长度要控制得当。当没有数据交互时,CSN(CS) 信号要保持为高电平 (低电平) 状态,时钟也需要保持高电平或持低电平状态不变。
SPI 实现
假设某 SPI Slave 中存在 128 个可读可写的 8bit 位宽的寄存器。
规定某 SPI Master 与 该SPI Slave 每次通信时传输 16bit 数据,其中最高位 bit[15] 为读写控制位,次高位 bit[14:8] 为地址位,低 8bit 数据位。
下面,对 CPOL=1、CPHA=1 工作模式 (下降沿发送数据、上升沿接收数据) 的 SPI 进行 Verilog 设计与简单仿真。
◆ 参数设计
工作时钟:200 Mhz
波特率:20MHz
传输位度:16
地址位度:7
数据位宽:8
◆ SPI Master 设计
SCLK 由 SPI 模块工作时钟产生,为避免工作时钟与 SCLK 交互时的相位差关系,SPI Master 输出的 SCLK 、CSN 与 MOSI 信号均在工作时钟下产生, SCLK 只作为输出时钟驱动。
SPI Master 信号端口说明如下:
SPI Master 代码描述如下:
// spi master:
// at negedge send data
// at posedge recevie data
module spi_master
(
input rstn,
input clk,
//data sended and received
input [15:0] tx_data,
input tx_data_en,
output [7:0] rdata,
output rdata_valid,
output ready,
//spi intf
output sclk,
output csn,
output mosi,
input miso
);
//100MHz clk, 10MHz spi clk
parameter BAUD_NUM = 100/10 ;
//==========================================================
//baud clk generating by baud counter
reg [4:0] baud_cnt_r ;
//generating negedge sclk
wire baud_cnt_end = baud_cnt_r == BAUD_NUM-1;
//generating posedge sclk
wire baud_cnt_half = baud_cnt_r == BAUD_NUM/2-1;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
baud_cnt_r <= 'b0 ;
end
else if (csn) begin
baud_cnt_r <= 'b0 ;
end
else if (baud_cnt_end) begin
baud_cnt_r <= 'b0 ;
end
else begin
baud_cnt_r <= baud_cnt_r + 1'b1 ;
end
end
//==========================================================
//bit counter
reg [7:0] bit_cnt_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
bit_cnt_r <= 'b0 ;
end
else if (csn) begin
bit_cnt_r <= 'b0 ;
end
//add: at posedge sclk
else if (baud_cnt_half && bit_cnt_r != 16) begin
bit_cnt_r <= bit_cnt_r + 1'b1 ;
end
end
//(1) generate spi clk
reg sclk_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
sclk_r <= 1'b1 ;
end
else if (csn) begin
sclk_r <= 1'b1 ;
end
else if (baud_cnt_half && bit_cnt_r != 16) begin
sclk_r <= 1'b0 ;
end
else if (baud_cnt_end) begin
sclk_r <= 1'b1 ;
end
end
assign sclk = sclk_r ;
//==========================================================
//(2) generate csn
reg csn_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
csn_r <= 1'b1 ;
end
else if (tx_data_en) begin
csn_r <= 1'b0;
end
//16 data finished, delay half cycle
else if (!csn_r && bit_cnt_r == 16 && baud_cnt_half) begin
csn_r <= 1'b1 ;
end
end
assign csn = csn_r ;
//==========================================================
//tx_data buffer
reg [15:0] tx_data_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
tx_data_r <= 'b0 ;
end
else if (tx_data_en && ready) begin
tx_data_r <= tx_data ;
end
end
//(3) generate mosi
reg mosi_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
mosi_r <= 1'b1 ;
end
else if (csn) begin
mosi_r <= 1'b1 ;
end
//output tx_data
else if (baud_cnt_half && bit_cnt_r != 16 ) begin
mosi_r <= tx_data_r[15-bit_cnt_r] ;
end
end
assign mosi = mosi_r ;
//(4) receive data by miso
reg [7:0] rdata_r;
reg rdata_valid_r;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
rdata_r <= 8'b0 ;
rdata_valid_r <= 1'b0 ;
end
else if (rdata_valid_r) begin
rdata_valid_r <= 1'b0 ;
end
else if (!tx_data_r[15] && bit_cnt_r ==16 && baud_cnt_end) begin
rdata_r <= {rdata_r[6:0], miso} ;
rdata_valid_r <= 1'b1 ;
end
else if (!tx_data_r[15] && bit_cnt_r >=9 && baud_cnt_end) begin
rdata_r <= {rdata_r[6:0], miso} ;
end
end
reg ready_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
ready_r <= 1'b1 ;
end
else if (tx_data_en) begin
ready_r <= 1'b0 ;
end
else if (csn) begin
ready_r <= 1'b1 ;
end
end // always @ (negedge clk or negedge rstn)
assign ready = ready_r ;
assign rdata = rdata_r ;
assign rdata_valid = rdata_valid_r ;
endmodule
◆ SPI Slave 设计
SPI Master 模块只保留 4 个信号即可,片选信号可以当做复位信号使用,说明如下。
SPI Slave 也是在 SCLK 下降沿开始接收数据,在 SCLK 上升沿输出数据,代码描述如下:
// spi slave:
// at negedge send data
// at posedge recevie data
module spi_slave
(
input sclk,
input csn,
input mosi,
output miso);
//===============================================
//bit counter
reg [3:0] bit_cnt_r ;
always @(posedge sclk or posedge csn) begin
if (csn) begin
bit_cnt_r <= 'b0 ;
end
else begin
bit_cnt_r <= bit_cnt_r + 1'b1 ;
end
end
//===============================================
//(1) receive rw cmd
reg rw_r ;
always @(posedge sclk or posedge csn) begin
if (csn) begin
rw_r <= 1'b0 ;
end
else if (bit_cnt_r == 0) begin
rw_r <= mosi ;
end
end
//(2) receive address
reg [6:0] addr_r;
always @(posedge sclk or posedge csn) begin
if (csn) begin
addr_r <= 6'b0 ;
end
else if (bit_cnt_r >= 1 && bit_cnt_r <= 7) begin
addr_r <= {addr_r[5:0], mosi} ;
end
end
//(3) receive data
reg [7:0] data_r;
always @(posedge sclk or posedge csn) begin
if (csn) begin
data_r <= 8'b0 ;
end
else if (rw_r && bit_cnt_r >=8 && bit_cnt_r <= 15) begin
data_r <= {data_r[6:0], mosi} ;
end
end
//===============================================
//write regs
reg [7:0] reg_group_r [127:0];
always @(posedge sclk) begin
if (rw_r && bit_cnt_r == 15) begin
reg_group_r[addr_r] <= {data_r[6:0], mosi} ;
end
end
//===============================================
//rd regs and send out
reg miso_r ;
always @(negedge sclk or posedge csn) begin
if (csn) begin
miso_r <= 'b0;
end
else if (!rw_r && bit_cnt_r >= 8 && bit_cnt_r <= 15) begin
miso_r <= reg_group_r[addr_r][15-bit_cnt_r] ;
end
end
assign miso = miso_r ;
endmodule
◆ Testbench
测试时,通过向 SPI 输入包含读写控制位、读写地址、和读写数据的并行数据,来判断 SPI Master 向 SPI Slave 写入和读出的数据是否一致,以达到仿真 SPI 功能的目的。
`timescale 1ns/1ps
module test ;
reg clk_200mhz ;
reg rstn ;
reg [15:0] tx_data ;
reg tx_data_en ;
wire sclk, csn, mosi, miso ;
wire [7:0] rdata ;
wire rdata_valid ;
wire ready ;
//==========================================
// clk and reset
initial begin
clk_200mhz = 0 ;
rstn = 0 ;
#11.3 rstn = 1 ;
end
always #(2.5) clk_200mhz = ~clk_200mhz ;
//==========================================
//driver task
task spi_cmd ;
input [15:0] data_send ;
begin
wait(ready) ;
@(posedge clk_200mhz) ;
# 0.7 ;
tx_data = data_send ;
tx_data_en = 1'b1 ;
@(posedge clk_200mhz) ;
# 0.7 ;
tx_data_en = 1'b0 ;
tx_data = 'b0 ;
wait(ready) ;
end
endtask // spi_rw
//==========================================
//driver
initial begin
tx_data = 16'b0 ;
tx_data_en = 1'b0 ;
//(1) wr address: 100-102
#133.7 ; spi_cmd({1'b1, 7'd100, 8'hAA}) ;
#501.3 ; spi_cmd({1'b1, 7'd101, 8'h55}) ;
#501.3 ; spi_cmd({1'b1, 7'd102, 8'hA5}) ;
//(2) rd address: 102-100
#2001.3 ; spi_cmd({1'b0, 7'd102, 8'h0}) ;
#501.3 ; spi_cmd({1'b0, 7'd101, 8'h0}) ;
#501.3 ; spi_cmd({1'b0, 7'd100, 8'h0}) ;
end
//finish
reg err_flag ;
initial begin
err_flag = 0 ;
#100;
//1st read
@(posedge rdata_valid) ;
@(negedge clk_200mhz) ;
if (rdata != 8'ha5) err_flag |= 1;
//2nd read
@(posedge rdata_valid) ;
@(negedge clk_200mhz) ;
if (rdata != 8'h55) err_flag |= 1;
//3rd 3read
@(posedge rdata_valid) ;
@(negedge clk_200mhz) ;
if (rdata != 8'haa) err_flag |= 1;
#13.7 ;
$display("-------------------------");
if (err_flag !== 0) begin
$display("Simulation Failed!");
end
else begin
$display("Simulation Succeed!");
end
$display();
$display();
#1000 ;
$finish ;
end
spi_master u_spi_master (
.rstn (rstn),
.clk (clk_200mhz),
//parallel
.tx_data (tx_data),
.tx_data_en (tx_data_en),
.ready (ready),
//spi intf
.sclk (sclk),
.csn (csn),
.mosi (mosi),
.miso (miso),
.rdata (rdata),
.rdata_valid (rdata_valid)
);
spi_slave u_spi_slave (
.sclk (sclk),
.csn (csn),
.mosi (mosi),
.miso (miso));
endmodule
◆ SPI Slave 设计
仿真中的自检验成功截图如下所示。
Master 向第 100 个寄存器写数据 0xAA 的仿真波形图如下所示。
Master 发送读取第 100 个寄存器的命令及 Slave 返回对应寄存器数据的波形图如下所示。
全部0条评论
快来发表一下你的评论吧 !