01SPI基礎(chǔ)知識(shí)
SPI(Serial Perripheral Interface, 串行外圍設(shè)備接口)是 Motorola 公司推出的一種同步串行接口技術(shù)。SPI 總線在物理上是通過(guò)接在外圍設(shè)備微控制器(PICmicro) 上面的微處理控制單元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模塊(Module)來(lái)實(shí)現(xiàn)的, 它允許 MCU 以全雙工的同步串行方式, 與各種外圍設(shè)備進(jìn)行高速數(shù)據(jù)通信。
SPI 主要應(yīng)用在 EEPROM, Flash, 實(shí)時(shí)時(shí)鐘(RTC), 數(shù)模轉(zhuǎn)換器(ADC), 數(shù)字信號(hào)處理器(DSP) 以及數(shù)字信號(hào)解碼器之間。它在芯片中只占用四根管腳 (Pin) 用來(lái)控制以及數(shù)據(jù)傳輸, 節(jié)約了芯片的 pin 數(shù)目, 同時(shí)為 PCB 在布局上節(jié)省了空間。正是出于這種簡(jiǎn)單易用的特性, 現(xiàn)在越來(lái)越多的芯片上都集成了 SPI技術(shù)。

SPI主要具有以下特點(diǎn):
1.1 采用主-從模式(Master-Slave) 的控制方式SPI
規(guī)定了兩個(gè) SPI 設(shè)備之間通信必須由主設(shè)備 (Master) 來(lái)控制次設(shè)備 (Slave). 一個(gè) Master 設(shè)備可以通過(guò)提供 Clock 以及對(duì) Slave 設(shè)備進(jìn)行片選 (Slave Select) 來(lái)控制多個(gè) Slave 設(shè)備, SPI 協(xié)議還規(guī)定 Slave 設(shè)備的 Clock 由 Master 設(shè)備通過(guò) SCK 管腳提供給 Slave 設(shè)備, Slave 設(shè)備本身不能產(chǎn)生或控制 Clock, 沒(méi)有 Clock 則 Slave 設(shè)備不能正常工作.
1.2 采用同步方式(Synchronous)傳輸數(shù)據(jù)
采用同步方式(Synchronous)傳輸數(shù)據(jù)Master 設(shè)備會(huì)根據(jù)將要交換的數(shù)據(jù)來(lái)產(chǎn)生相應(yīng)的時(shí)鐘脈沖(Clock Pulse), 時(shí)鐘脈沖組成了時(shí)鐘信號(hào)(Clock Signal) , 時(shí)鐘信號(hào)通過(guò)時(shí)鐘極性 (CPOL) 和 時(shí)鐘相位 (CPHA) 控制著兩個(gè) SPI 設(shè)備間何時(shí)數(shù)據(jù)交換以及何時(shí)對(duì)接收到的數(shù)據(jù)進(jìn)行采樣, 來(lái)保證數(shù)據(jù)在兩個(gè)設(shè)備之間是同步傳輸?shù)?
1.3 數(shù)據(jù)交換(Data Exchanges)
SPI 設(shè)備間的數(shù)據(jù)傳輸之所以又被稱為數(shù)據(jù)交換, 是因?yàn)?SPI 協(xié)議規(guī)定一個(gè) SPI 設(shè)備不能在數(shù)據(jù)通信過(guò)程中僅僅只充當(dāng)一個(gè) "發(fā)送者(Transmitter)" 或者 "接收者(Receiver)". 在每個(gè) Clock 周期內(nèi), SPI 設(shè)備都會(huì)發(fā)送并接收一個(gè) bit 大小的數(shù)據(jù), 相當(dāng)于該設(shè)備有一個(gè) bit 大小的數(shù)據(jù)被交換了.一個(gè) Slave 設(shè)備要想能夠接收到 Master 發(fā)過(guò)來(lái)的控制信號(hào), 必須在此之前能夠被 Master 設(shè)備進(jìn)行訪問(wèn) (Access). 所以, Master 設(shè)備必須首先通過(guò) SS/CS pin 對(duì) Slave 設(shè)備進(jìn)行片選, 把想要訪問(wèn)的 Slave 設(shè)備選上.
在數(shù)據(jù)傳輸?shù)倪^(guò)程中, 每次接收到的數(shù)據(jù)必須在下一次數(shù)據(jù)傳輸之前被采樣. 如果之前接收到的數(shù)據(jù)沒(méi)有被讀取, 那么這些已經(jīng)接收完成的數(shù)據(jù)將有可能會(huì)被丟棄, 導(dǎo)致 SPI 物理模塊最終失效. 因此, 在程序中一般都會(huì)在 SPI 傳輸完數(shù)據(jù)后, 去讀取 SPI 設(shè)備里的數(shù)據(jù), 即使這些數(shù)據(jù)(Dummy Data)在我們的程序里是無(wú)用的.

02SPI傳輸協(xié)議
2.1 SPI協(xié)議層相關(guān)組件和接口

時(shí)鐘相關(guān)概念:
CPOL(clock polarity): 時(shí)鐘極性, 表示 SPI 在空閑時(shí), 時(shí)鐘信號(hào)是高電平還是低電平. 若 CPOL 被設(shè)為 1, 那么該設(shè)備在空閑時(shí) SCK 管腳下的時(shí)鐘信號(hào)為高電平. 當(dāng) CPOL 被設(shè)為 0 時(shí)則正好相反;
CPHA(clock phase): 時(shí)鐘相位, 表示 SPI 設(shè)備是在 SCK 管腳上的時(shí)鐘信號(hào)變?yōu)樯仙貢r(shí)觸發(fā)數(shù)據(jù)采樣, 還是在時(shí)鐘信號(hào)變?yōu)橄陆笛貢r(shí)觸發(fā)數(shù)據(jù)采樣. 若 CPHA 被設(shè)置為 1, 則 SPI 設(shè)備在時(shí)鐘信號(hào)變?yōu)橄陆笛貢r(shí)觸發(fā)數(shù)據(jù)采樣, 在上升沿時(shí)發(fā)送數(shù)據(jù). 當(dāng) CPHA 被設(shè)為 0 時(shí)也正好相反.

SSPBUF(Synchronous Serial Port Buffer): 泛指 SPI 設(shè)備里面的內(nèi)部緩沖區(qū), 一般在物理上是以 FIFO 的形式, 保存?zhèn)鬏斶^(guò)程中的臨時(shí)數(shù)據(jù);
SSPSR(Synchronous Serial Port Register):泛指 SPI 設(shè)備里面的移位寄存器(Shift Regitser), 它的作用是根據(jù)設(shè)置好的數(shù)據(jù)位寬(bit-width) 把數(shù)據(jù)移入或者移出 SSPBUF;
Controller:泛指 SPI 設(shè)備里面的控制寄存器, 可以通過(guò)配置它們來(lái)設(shè)置 SPI 總線的傳輸模式.
通常情況下, 我們只需要對(duì)上圖所描述的四個(gè)管腳(pin) 進(jìn)行控制即可實(shí)現(xiàn)整個(gè) SPI 設(shè)備之間的數(shù)據(jù)通信:
SCK(Serial Clock):主要的作用是 Master 設(shè)備往 Slave 設(shè)備傳輸時(shí)鐘信號(hào), 控制數(shù)據(jù)交換的時(shí)機(jī)以及速率;
SS/CS(Slave Select/Chip Select):用于 Master 設(shè)備片選 Slave 設(shè)備, 使被選中的 Slave 設(shè)備能夠被 Master 設(shè)備所訪問(wèn);
SDO/MOSI(Serial Data Output/Master Out Slave In):在 Master 上面也被稱為 Tx-Channel, 作為數(shù)據(jù)的出口, 主要用于 SPI 設(shè)備發(fā)送數(shù)據(jù);
SDI/MISO(Serial Data Input/Master In Slave Out):在 Master 上面也被稱為 Rx-Channel, 作為數(shù)據(jù)的入口, 主要用于SPI 設(shè)備接收數(shù)據(jù)。
2.2 SPI協(xié)議層傳輸規(guī)則

當(dāng)SPI進(jìn)行數(shù)據(jù)傳輸時(shí),片選信號(hào)cs_n置低,數(shù)據(jù)位按照SPI同步時(shí)鐘發(fā)送。主設(shè)備發(fā)給從設(shè)備的數(shù)據(jù)沿mosi數(shù)據(jù)線發(fā)送,從設(shè)備同步發(fā)給主設(shè)備的數(shù)據(jù)沿miso數(shù)據(jù)線發(fā)送,主從設(shè)備接收的數(shù)據(jù)依次分別存入主從設(shè)備內(nèi)的FIFO中。
2.3 說(shuō)明
1. Timing
首先, 在這里解釋一下兩個(gè)概念:
CPOL: 時(shí)鐘極性, 表示 SPI 在空閑時(shí), 時(shí)鐘信號(hào)是高電平還是低電平. 若 CPOL 被設(shè)為 1, 那么該設(shè)備在空閑時(shí) SCK 管腳下的時(shí)鐘信號(hào)為高電平. 當(dāng) CPOL 被設(shè)為 0 時(shí)則正好相反.
CPHA: 時(shí)鐘相位, 表示 SPI 設(shè)備是在 SCK 管腳上的時(shí)鐘信號(hào)變?yōu)樯仙貢r(shí)觸發(fā)數(shù)據(jù)采樣, 還是在時(shí)鐘信號(hào)變?yōu)橄陆笛貢r(shí)觸發(fā)數(shù)據(jù)采樣. 若 CPHA 被設(shè)置為 1, 則 SPI 設(shè)備在時(shí)鐘信號(hào)變?yōu)橄陆笛貢r(shí)觸發(fā)數(shù)據(jù)采樣, 在上升沿時(shí)發(fā)送數(shù)據(jù). 當(dāng) CPHA 被設(shè)為 0 時(shí)也正好相反.
例如,SPI 數(shù)據(jù)傳輸模式被設(shè)置成 CPOL = 1, CPHA = 1. 這樣, 在一個(gè) Clock 周期內(nèi), 每個(gè)單獨(dú)的 SPI 設(shè)備都能以全雙工(Full-Duplex) 的方式, 同時(shí)發(fā)送和接收 1 bit 數(shù)據(jù), 即相當(dāng)于交換了 1 bit 大小的數(shù)據(jù). 如果 SPI 總線的 Channel-Width 被設(shè)置成 Byte, 表示 SPI 總線上每次數(shù)據(jù)傳輸?shù)淖钚挝粸?Byte, 那么掛載在該 SPI 總線的設(shè)備每次數(shù)據(jù)傳輸?shù)倪^(guò)程至少需要 8 個(gè) Clock 周期(忽略設(shè)備的物理延遲). 因此, SPI 總線的頻率越快, Clock 周期越短, 則 SPI 設(shè)備間數(shù)據(jù)交換的速率就越快.
2. SSPSR
SSPSR 是 SPI 設(shè)備內(nèi)部的移位寄存器(Shift Register). 它的主要作用是根據(jù) SPI 時(shí)鐘信號(hào)狀態(tài), 往 SSPBUF 里移入或者移出數(shù)據(jù), 每次移動(dòng)的數(shù)據(jù)大小由 Bus-Width 以及 Channel-Width 所決定.
Bus-Width 的作用是指定地址總線到 Master 設(shè)備之間數(shù)據(jù)傳輸?shù)膯挝?
例如, 我們想要往 Master 設(shè)備里面的 SSPBUF 寫(xiě)入 16 Byte 大小的數(shù)據(jù): 首先, 給 Master 設(shè)備的配置寄存器設(shè)置 Bus-Width 為 Byte; 然后往 Master 設(shè)備的 Tx-Data 移位寄存器在地址總線的入口寫(xiě)入數(shù)據(jù), 每次寫(xiě)入 1 Byte 大小的數(shù)據(jù)(使用 writeb 函數(shù)); 寫(xiě)完 1 Byte 數(shù)據(jù)之后, Master 設(shè)備里面的 Tx-Data 移位寄存器會(huì)自動(dòng)把從地址總線傳來(lái)的1 Byte 數(shù)據(jù)移入 SSPBUF 里; 上述動(dòng)作一共需要重復(fù)執(zhí)行 16 次.
Channel-Width 的作用是指定 Master 設(shè)備與 Slave 設(shè)備之間數(shù)據(jù)傳輸?shù)膯挝? 與 Bus-Width 相似, Master 設(shè)備內(nèi)部的移位寄存器會(huì)依據(jù) Channel-Width 自動(dòng)地把數(shù)據(jù)從 Master-SSPBUF 里通過(guò) Master-SDO 管腳搬運(yùn)到 Slave 設(shè)備里的 Slave-SDI 引腳, Slave-SSPSR 再把每次接收的數(shù)據(jù)移入 Slave-SSPBUF里.
通常情況下, Bus-Width 總是會(huì)大于或等于 Channel-Width, 這樣能保證不會(huì)出現(xiàn)因 Master 與 Slave 之間數(shù)據(jù)交換的頻率比地址總線與 Master 之間的數(shù)據(jù)交換頻率要快, 導(dǎo)致 SSPBUF 里面存放的數(shù)據(jù)為無(wú)效數(shù)據(jù)這樣的情況.
3. SSPBUF
我們知道, 在每個(gè)時(shí)鐘周期內(nèi), Master 與 Slave 之間交換的數(shù)據(jù)其實(shí)都是 SPI 內(nèi)部移位寄存器從 SSPBUF 里面拷貝的. 我們可以通過(guò)往 SSPBUF 對(duì)應(yīng)的寄存器 (Tx-Data / Rx-Data register) 里讀寫(xiě)數(shù)據(jù), 間接地操控 SPI 設(shè)備內(nèi)部的 SSPBUF.
例如, 在發(fā)送數(shù)據(jù)之前, 我們應(yīng)該先往 Master 的 Tx-Data 寄存器寫(xiě)入將要發(fā)送出去的數(shù)據(jù), 這些數(shù)據(jù)會(huì)被 Master-SSPSR 移位寄存器根據(jù) Bus-Width 自動(dòng)移入 Master-SSPBUF 里, 然后這些數(shù)據(jù)又會(huì)被 Master-SSPSR 根據(jù) Channel-Width 從 Master-SSPBUF 中移出, 通過(guò) Master-SDO 管腳傳給 Slave-SDI 管腳, Slave-SSPSR 則把從 Slave-SDI 接收到的數(shù)據(jù)移入 Slave-SSPBUF 里. 與此同時(shí), Slave-SSPBUF 里面的數(shù)據(jù)根據(jù)每次接收數(shù)據(jù)的大小(Channel-Width), 通過(guò) Slave-SDO 發(fā)往 Master-SDI, Master-SSPSR 再把從 Master-SDI 接收的數(shù)據(jù)移入 Master-SSPBUF.在單次數(shù)據(jù)傳輸完成之后, 用戶程序可以通過(guò)從 Master 設(shè)備的 Rx-Data 寄存器讀取 Master 設(shè)備數(shù)據(jù)交換得到的數(shù)據(jù).
4. Controller
Master 設(shè)備里面的 Controller 主要通過(guò)時(shí)鐘信號(hào)(Clock Signal)以及片選信號(hào)(Slave Select Signal)來(lái)控制 Slave 設(shè)備. Slave 設(shè)備會(huì)一直等待, 直到接收到 Master 設(shè)備發(fā)過(guò)來(lái)的片選信號(hào), 然后根據(jù)時(shí)鐘信號(hào)來(lái)工作.
Master 設(shè)備的片選操作必須由程序所實(shí)現(xiàn). 例如: 由程序把 SS/CS 管腳的時(shí)鐘信號(hào)拉低電平, 完成 SPI 設(shè)備數(shù)據(jù)通信的前期工作; 當(dāng)程序想讓 SPI 設(shè)備結(jié)束數(shù)據(jù)通信時(shí), 再把 SS/CS 管腳上的時(shí)鐘信號(hào)拉高電平.
03SPI從機(jī)配置方式
3.1 常規(guī)多片選模式

上圖所示系統(tǒng)中,主機(jī)需要為每一個(gè)從機(jī)提供單獨(dú)的片選信號(hào),一旦主機(jī)使能(拉低)片選信號(hào),MOSI/MISO線上的時(shí)鐘和數(shù)據(jù)便可用于所選的從機(jī)。如果使能多個(gè)片選信號(hào),則MISO線上的數(shù)據(jù)會(huì)被破壞,因?yàn)橹鳈C(jī)無(wú)法識(shí)別哪個(gè)從機(jī)正在傳輸數(shù)據(jù)。
但是,隨著從機(jī)數(shù)量的增加,來(lái)自主機(jī)的片選線的數(shù)量也增加。這會(huì)快速增加主機(jī)需要提供的輸入和輸出數(shù)量,并限制可以使用的從機(jī)數(shù)量??梢允褂闷渌夹g(shù)來(lái)增加常規(guī)模式下的從機(jī)數(shù)量,例如使用多路復(fù)用器產(chǎn)生片選信號(hào)。
3.2 菊花鏈模式

在菊花鏈模式下,所有從機(jī)的片選信號(hào)連接在一起,數(shù)據(jù)從一個(gè)從機(jī)傳播到下一個(gè)從機(jī)。在此配置中,所有從機(jī)同時(shí)接收同一SPI時(shí)鐘。來(lái)自主機(jī)的數(shù)據(jù)直接送到第一個(gè)從機(jī),該從機(jī)將數(shù)據(jù)提供給下一個(gè)從機(jī),依此類推。
使用該方法時(shí),由于數(shù)據(jù)是從一個(gè)從機(jī)傳播到下一個(gè)從機(jī),所以傳輸數(shù)據(jù)所需的時(shí)鐘周期數(shù)與菊花鏈中的從機(jī)位置成比例。例如,在上圖所示的8位系統(tǒng)中,為使第3個(gè)從機(jī)能夠獲得數(shù)據(jù),需要24個(gè)時(shí)鐘脈沖,而常規(guī)SPI模式下只需8個(gè)時(shí)鐘脈沖。
時(shí)鐘周期和通過(guò)菊花鏈的數(shù)據(jù)傳播如下圖所示:

04SPI的Verilog實(shí)現(xiàn)代碼
4.1 SPI目標(biāo)實(shí)現(xiàn)功能
設(shè)計(jì)4線SPI master 模塊和slave 模塊,要求如下:
1. 主機(jī)模塊(master)接口定義:
module spi_master(
input clk_40k, //時(shí)鐘信號(hào),40kHz
input rst_n, //復(fù)位信號(hào),低有效
input [7:0] data_in, //主機(jī)準(zhǔn)備要輸出給從機(jī)的數(shù)據(jù),8位寬
input send_start,//通信使能信號(hào),高有效,寬度為1個(gè)時(shí)鐘周期(40kHz),收到該信號(hào)后開(kāi)始一次主從設(shè)備通信
output [7:0] data_out, //主機(jī)從從機(jī)接收到的數(shù)據(jù),8位寬
output data_out_vld, //輸出數(shù)據(jù)有效標(biāo)志,高電平有效,寬度為1個(gè)時(shí)鐘周期(40kHz)
output cs_n, //從設(shè)備片選使能信號(hào),低有效,低電平時(shí)選中從設(shè)備與主設(shè)備進(jìn)行通信,處于通信狀態(tài)時(shí)維持低電平
output sclk, //同步時(shí)鐘,1kHz,空閑時(shí)置低電平
input miso, //主機(jī)當(dāng)前從從機(jī)收到的串行數(shù)據(jù)
output mosi //主機(jī)當(dāng)前發(fā)送給從機(jī)的串行數(shù)據(jù)
);
2. 從機(jī)模塊(slave)接口定義:
module spi_slave(
input rst_n, //復(fù)位信號(hào),低有效
input cs_n, //從設(shè)備片選使能信號(hào)
input sclk, //SPI時(shí)鐘,1kHz空閑時(shí)置低電平
input mosi, //從機(jī)從主機(jī)接收到的串行數(shù)據(jù)
output miso, //從機(jī)要發(fā)送給主機(jī)的串行數(shù)據(jù)
output [7:0] reg0_out, //內(nèi)部寄存器0的值
output [7:0] reg1_out, //內(nèi)部寄存器1的值
output [7:0] reg2_out, //內(nèi)部寄存器2的值
output [7:0] reg3_out //內(nèi)部寄存器3的值
);
3.電路功能描述:
Slave模塊中有四個(gè)八位內(nèi)部寄存器(reg0、reg1、reg2、reg3),地址分別為 0~3,master模塊通過(guò)SPI總線配置slave模塊中四個(gè)寄存器的值,slave寄存器的值直接通過(guò)其端口輸出。Master模塊收到send_start信號(hào)之后,將數(shù)據(jù)data_in通過(guò)spi總線發(fā)送到slave模塊的reg0,然后將data_in循環(huán)右移兩位后發(fā)送到slave模塊的reg1,然后再將data_in循環(huán)右移兩位發(fā)送到reg2,最后將data_in再循環(huán)右移兩位發(fā)送到reg3。至此,master完成對(duì)slave中所有寄存器的配置。然后master再通過(guò)spi總線將slave中reg2的數(shù)據(jù)讀出來(lái),通過(guò)data_out輸出,并同時(shí)給出一個(gè)周期寬度的data_out_vld。
4. SPI傳輸格式:
SPI每幀數(shù)據(jù)包含16位,最先發(fā)送的第0位為讀寫(xiě)控制位,該位為0代表master向slave寫(xiě)數(shù)據(jù),為1則代表master從slave讀數(shù)據(jù);隨后發(fā)送的第1-7位為地址位,先發(fā)高位地址再發(fā)低位地址,9-16位為數(shù)據(jù)位,高位數(shù)據(jù)先發(fā)。所有數(shù)據(jù)均在sclk的上升沿產(chǎn)生,下降沿采樣。
SPI寫(xiě)數(shù)據(jù)格式如圖所示:

SPI讀數(shù)據(jù)格式如圖所示:

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
2. 從模塊(SPI_slave):
module spi_slave(
input rst_n,
input cs_n,
input sclk,
input mosi,
output miso,
output [7:0] reg0_out,
output [7:0] reg1_out,
output [7:0] reg2_out,
output [7:0] reg3_out
);
reg miso_r;
reg [7:0] reg1_out_r;
reg [7:0] reg2_out_r;
reg [7:0] reg3_out_r;
reg [7:0] reg0_out_r;
reg start;
reg wr_rd;
reg [3:0] bit_cnt;
reg [6:0] reg_addr;
reg [7:0] reg_data;
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
//start
always @ (posedge sclk or negedge rst_n)
begin
if(~rst_n)
start <= 1'b0;
else
start <= 1'b1;
end
//bit_cnt
always @ (posedge sclk or negedge rst_n)
begin
if(~rst_n)
bit_cnt <= 4'b0;
else if(start)
bit_cnt <= bit_cnt + 1'b1;
end
//wr_rd
always @ (posedge sclk or negedge rst_n)
begin
if(~rst_n)
wr_rd <= 1'b0;
else if(bit_cnt == 4'b0 && mosi == 1'b0)
wr_rd <= 1'b0;
else if(bit_cnt == 4'b0 && mosi == 1'b1)
wr_rd <= 1'b1;
end
//reg_addr
always @ (negedge sclk or negedge rst_n)
begin
if(~rst_n)
reg_addr <= 7'b0;
else if(bit_cnt >= 4'd1 && bit_cnt <= 4'd7)
reg_addr <= {reg_addr[5:0],mosi};
end
//reg_data
always @ (posedge sclk or negedge rst_n)
begin
if(~rst_n)
reg_data <= 8'b0;
else if(!wr_rd && bit_cnt >= 4'd7)
reg_data <= {reg_data[6:0],mosi};
else if(wr_rd && bit_cnt == 4'd6)
reg_data <= reg2_out_r;
else if(wr_rd && bit_cnt >= 4'd7)
reg_data <= {reg_data[6:0],reg_data[7]};
end
//reg_out
assign reg0_out = reg0_out_r;
assign reg1_out = reg1_out_r;
assign reg2_out = reg2_out_r;
assign reg3_out = reg3_out_r;
always @ (negedge sclk or negedge rst_n)
begin
if(~rst_n)
begin
reg0_out_r <= 8'b0;
reg1_out_r <= 8'b0;
reg2_out_r <= 8'b0;
reg3_out_r <= 8'b0;
end
else if(!wr_rd && bit_cnt == 4'd0)
case(reg_addr)
reg0_address: reg0_out_r <= reg_data;
reg1_address: reg1_out_r <= reg_data;
reg2_address: reg2_out_r <= reg_data;
reg3_address: reg3_out_r <= reg_data;
endcase
end
//miso
assign miso = miso_r;
always @ (negedge sclk or posedge rst_n)
begin
if(~rst_n)
miso_r <= 1'b0;
else if(wr_rd && bit_cnt >= 4'd7)
miso_r <= reg_data[7];
end
endmodule
3. Testbench(tb):
`timescale 1us/1us
module tb();
regclk_40k;
regrst_n;
reg [7:0] data_in;
regsend_start;
wiresclk;
wirecs_n;
wiremosi;
wiremiso;
wire [7:0] data_out;
wire data_out_vld;
wire [7:0] reg0_out;
wire [7:0] reg1_out;
wire [7:0] reg2_out;
wire [7:0] reg3_out;
spi_master i_spi_master(
.clk_40k (clk_40k),
.rst_n (rst_n),
.data_in (data_in),
.send_start (send_start),
.sclk (sclk),
.cs_n (cs_n),
.mosi (mosi),
.miso (miso),
.data_out (data_out),
.data_out_vld (data_out_vld)
);
spi_slave i_spi_slave(
.rst_n(rst_n),
.cs_n (cs_n),
.sclk (sclk),
.mosi(mosi),
.miso(miso),
.reg0_out(reg0_out),
.reg1_out (reg1_out),
.reg2_out (reg2_out),
.reg3_out (reg3_out)
);
initial
begin
rst_n = 1'b0;
#10rst_n = 1'b1;
end
initial
begin
clk_40k = 1'b0;
forever
#1clk_40k = ~clk_40k;
end
initial
begin
send_start = 1'b0;
data_in = 8'd0;
forever
begin
#200;
data_in = $random()%256;
send_start = 1'b1;
#2
send_start = 1'b0;
#8000;
end
end
endmodule
4. 仿真結(jié)果:
按照testbench對(duì)SPI主從設(shè)備進(jìn)行仿真,仿真結(jié)果如圖:

1. 系統(tǒng)時(shí)鐘和SPI時(shí)鐘不一致,clk_40k為高頻系統(tǒng)時(shí)鐘,利用計(jì)數(shù)器分頻實(shí)現(xiàn)1k波特率SPI時(shí)鐘;
2. 復(fù)位信號(hào)rst_n低電平有效,正常傳輸時(shí)始終處于高電平;
3. 開(kāi)始傳輸時(shí)send_start信號(hào)拉高,傳輸結(jié)束時(shí)data_out_vld信號(hào)拉高;
4. SPI主設(shè)備將輸入數(shù)據(jù)data_in并行轉(zhuǎn)mosi串行輸出,SPI從設(shè)備將接收到的串行存入數(shù)據(jù),將移位后的數(shù)據(jù)data_out并行轉(zhuǎn)miso串行輸出。
05SPI的優(yōu)缺點(diǎn)
5.1 SPI協(xié)議優(yōu)點(diǎn)
1. 全雙工同步串行通信;
2. 允許數(shù)據(jù)逐位傳遞;
3. 允許數(shù)據(jù)傳輸暫停;
4. 硬件結(jié)構(gòu)簡(jiǎn)單,不需要精密時(shí)鐘;
5. 從機(jī)不需要唯一地址,也不需要收發(fā)器。
5.1 SPI協(xié)議缺點(diǎn)
1. 需要4個(gè)引腳接口;
2. 支持傳輸距離較短;
3. 硬件層面沒(méi)有定義校錯(cuò)協(xié)議和從機(jī)應(yīng)答信號(hào)。
審核編輯:湯梓紅
-
接口
+關(guān)注
關(guān)注
33文章
9441瀏覽量
156076 -
Verilog
+關(guān)注
關(guān)注
30文章
1370瀏覽量
114082 -
SPI
+關(guān)注
關(guān)注
17文章
1865瀏覽量
99687 -
串行總線
+關(guān)注
關(guān)注
1文章
186瀏覽量
31514
原文標(biāo)題:常用串行總線(二)——SPI協(xié)議(Verilog實(shí)現(xiàn))
文章出處:【微信號(hào):Carlinx FPGA,微信公眾號(hào):Carlinx FPGA】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
SPI總線的原理與Verilog設(shè)計(jì)實(shí)現(xiàn)
SPI、I2C、UART三種串行總線協(xié)議的區(qū)別
常用的串行總線協(xié)議有哪些
SPI協(xié)議的相關(guān)資料分享
一文介紹SPI串行總線
使用Verilog實(shí)現(xiàn)SPI串行總線接口的資料和源代碼免費(fèi)下載
基于SPI串行總線接口的Verilog實(shí)現(xiàn)
基于FPGA的SPI協(xié)議及設(shè)計(jì)實(shí)現(xiàn)
FPGA實(shí)現(xiàn)的SPI協(xié)議(一)----SPI驅(qū)動(dòng)
常用串行總線——SPI協(xié)議(上)
常用串行總線——SPI協(xié)議(下)

常用串行總線(二)——SPI協(xié)議(Verilog實(shí)現(xiàn))
評(píng)論