框架-SPI四種模式+通用裝置驅動實現
阿新 • • 發佈:2020-11-01
[toc]
---
### 前言
* **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)
* 主裝置輸入/從裝置輸出引腳。
* 從機發出,主機接收。
* **SPI 四種模式**
* ***請移步到下面章節學習***
* SPI的協議層
* SPI協議定義了通訊的起始和停止訊號、資料有效性、時鐘同步等環節。
* 基本通訊過程
* 圖解
1. 標號1:NSS訊號線由高變低,是SPI通訊的起始訊號。
2. 標號6:NSS訊號由低變高,是SPI通訊的停止訊號。
* 簡單時序圖![](https://img2020.cnblogs.com/blog/2085252/202010/2085252-20201031193956127-1618655976.png)
* 模式時序圖![](https://img2020.cnblogs.com/blog/2085252/202010/2085252-20201031194011237-428911815.png)
### 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的 **上升沿** 被取樣
### SPI 驅動框架 **
#### 框架
* 實現方法參考 [I2C裝置驅動拆解](https://www.cnblogs.com/lizhuming/p/13834535.html)
* 自己先在寫出四種模式的讀寫時序,便會發現以下規律
* 讀寫的邏輯差不多都一樣,只是 **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 寫函式
```c
/**
* @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 讀函式
```c
/**
* @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 讀寫函式
```c
/**
* @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);