框架-SPI四種模式+通用設備驅動實現
- 2020 年 10 月 31 日
- 筆記
- /labbel/C, /label/frame, /label/lzm, /label/MCU, C語言, MCU, 程式框架
前言
- SPI 介紹為搜集百度資料+個人理解
- 其餘為原創(有誤請指正)
- 集四種模式於一身
筆錄草稿
SPI介紹
-
SPI 協議簡介
- SPI 協議是由摩托羅拉公司提出的通訊協議(Serial Peripheral Interface),即串列外圍設備介面,是一種高速全雙工的通訊匯流排。
- 是一個環形匯流排結構
- 由 ss(cs)、sck、sdi、sdo 構成
- 其時序主要是在 sck 的控制下,兩個雙向移位暫存器進行數據交換。
-
物理線說明
- SS
- 從設備選擇訊號線,常稱為片選訊號線,也稱為NSS、CS。
- 用於選擇從機。
- SCK (Serial Clock)
- 時鐘訊號線
- 用於通訊數據同步。
- MOSI (Master Output, Slave Input)
- 主設備輸出/從設備輸入引腳。
- 主機發出,從機接收。
- MISO (Master Input,,Slave Output)
- 主設備輸入/從設備輸出引腳。
- 從機發出,主機接收。
- SS
-
SPI 四種模式
- 請移步到下面章節學習
-
SPI的協議層
- SPI協議定義了通訊的起始和停止訊號、數據有效性、時鐘同步等環節。
- 基本通訊過程
- 圖解
- 標號1:NSS訊號線由高變低,是SPI通訊的起始訊號。
- 標號6:NSS訊號由低變高,是SPI通訊的停止訊號。
-
簡單時序圖
-
模式時序圖
SPI四種模式 **
- 四種模式由 CPOL 和 CPHA 組合區分
- CPOL
- 時鐘極性
- 是指SPI通訊設備處於空閑狀態時,SCK訊號線的電平訊號
- 為 0 時
- SCK 空閑狀態為 低電平
- 為 1 時
- SCK 空閑狀態為 高電平
- CPHA
- 時鐘相位
- 是指數據的取樣的時刻
- 為 0 時
- MOSI或MISO數據線上的訊號將會在SCK時鐘線的「奇數邊沿」被取樣。(即是第一個邊沿)
- 這種模式適合那種從設備一旦被片選後就輸出數據到MISO線上。
- 為 1 時
- 數據線在SCK的「偶數邊沿」取樣。(即是第二個邊沿)
- 這種模式適合那種從設備被片選後還需要一個時鐘才能 輸出數據到MISO線上。
- 四種模式(CPOL, CPHA)
- 模式 0:(0, 0)
- SCK空閑為 低電平,數據在SCK的 上升沿 被取樣
- 模式 1:(0, 1)
- SCK空閑為 低電平,數據在SCK的 下降沿 被取樣
- 模式 2:(1, 0)
- SCK空閑為 高電平,數據在SCK的 下降沿 被取樣
- 模式 3:(1, 1)
- SCK空閑為 高電平,數據在SCK的 上升沿 被取樣
- 模式 0:(0, 0)
SPI 驅動框架 **
框架
- 實現方法參考 I2C設備驅動拆解
- 自己先在寫出四種模式的讀寫時序,便會發現以下規律
- 讀寫的邏輯差不多都一樣,只是 SCK 訊號線出現的位置及高低電平會因不同模式而不同。(這裡我就不分別寫出4種模式的單獨實現了,直接上規律表,然後實現統一的源碼)
R/W | CPOL | CPHA | 位置1-SCK | 位置2-SCK | 位置3-SCK | 位置4-SCK |
---|---|---|---|---|---|---|
R | 0 | 0 | X | 0 | 1 | 0 |
R | 0 | 1 | X | 1 | 0 | 0 |
R | 1 | 0 | X | 1 | 0 | 1 |
R | 1 | 1 | X | 0 | 1 | 1 |
– | – | – | – | – | – | – |
W | 0 | 0 | X | 0 | 1 | 0 |
W | 0 | 1 | 0 | 1 | 0 | X |
W | 1 | 0 | X | 1 | 0 | 1 |
W | 1 | 1 | 1 | 0 | 1 | X |
由上規律得出 支援四種模式的 SPI 讀寫源碼
- SPI 寫函數
/**
* @brief SPI 寫函數
* @param
* @retval
* @author lzm
*/
void spiWriteOneByte(eSPI_ID id, unsigned char data)
{
unsigned char i;
const spi_t * spi = &spiDriverElem[id];
// 位置1
if(spi->CPHA){
spiOut(spi->sckGpiox, spi->sckPin, spi->CPOL);
}
for(i=0; i<8; i++)
{
// 位置2
spiOut(spi->sckGpiox, spi->sckPin, (spi->CPOL != spi->CPHA));
if(data & 0x80){
spiMosiOutHi(spi);
}
else{
spiMosiOutLo(spi);
}
data <<= 1;
spi->delayUsFun(spi->readDelayUsCnt);
// 位置3
spiOut(spi->sckGpiox, spi->sckPin, (spi->CPOL == spi->CPHA));
}
// 位置4
if(!(spi->CPHA)){
spiOut(spi->sckGpiox, spi->sckPin, spi->CPOL);
}
}
- SPI 讀函數
/**
* @brief SPI 讀函數
* @param
* @retval
* @author lzm
*/
unsigned char spiReadOneByte(eSPI_ID id)
{
unsigned char i;
unsigned char ret;
const spi_t * spi = &spiDriverElem[id];
// 位置1
for(i=0; i<8; i++)
{
// 位置2
spiOut(spi->sckGpiox, spi->sckPin, (spi->CPOL != spi->CPHA));
ret <<= 1;
if(spiMisoIn(spi))
ret |= 0x01;
else
ret &= 0xfe;
spi->delayUsFun(spi->readDelayUsCnt);
// 位置3
spiOut(spi->sckGpiox, spi->sckPin, (spi->CPOL == spi->CPHA));
}
// 位置4
spiOut(spi->sckGpiox, spi->sckPin, spi->CPOL);
return ret;
}
- SPI 讀寫函數
/**
* @brief SPI 讀寫一體函數
* @param
* @retval
* @author lzm
*/
unsigned char spiRWOneByte(eSPI_ID id, unsigned char data)
{
unsigned char i;
unsigned char ret;
const spi_t * spi = &spiDriverElem[id];
// 位置1
if(spi->CPHA){
spiOut(spi->sckGpiox, spi->sckPin, spi->CPOL);
}
for(i=0; i<8; i++)
{
// 位置2
spiOut(spi->sckGpiox, spi->sckPin, (spi->CPOL != spi->CPHA));
if(data & 0x80){
spiMosiOutHi(spi);
}
else{
spiMosiOutLo(spi);
}
data <<= 1;
spi->delayUsFun(spi->readDelayUsCnt);
// 位置3
spiOut(spi->sckGpiox, spi->sckPin, (spi->CPOL == spi->CPHA));
ret <<= 1;
if(spiMisoIn(spi))
ret |= 0x01;
else
ret &= 0xfe;
spi->delayUsFun(spi->readDelayUsCnt);
}
// 位置4
if(!(spi->CPHA)){
spiOut(spi->sckGpiox, spi->sckPin, spi->CPOL);
}
}