一、SPI總述
SPI 是一種允許一個主設備啓動一個與從設備的同步通訊的協議,從而完成數據的交換。也就是說,SPI是一種規定好的通訊方式。這種通信方式的優點是佔用端口較少,一般4根就夠基本通訊了。同時傳輸速度也很高。一般來說要求主設備要有SPI控制器(但可用模擬方式),就可以與基於SPI的芯片通訊了。
常見的SPI外圍設備包括FLASHRAM、網絡控制器、LCD顯示驅動器、A/D轉換器和MCU等。
SPI 的通信原理很簡單,它需要至少4根線,事實上3根也可以。也是所有基於SPI的設備共有的,它們是SDI(數據輸入),SDO(數據輸出),SCK(時 鍾),CS(片選)。其中CS是控制芯片是否被選中的,也就是說只有片選信號爲預先規定的使能信號時(高電位或低電位),對此芯片的操作纔有效。這就允許在同一總線上連接多個SPI設備成爲可能。
接下來就負責通訊的3根線了。通訊是通過數據交換完成的,這裏先要知道SPI是串行通訊協議,也就是說數據是一位一位的傳輸的。這就是SCK時鐘線存在的原 因,由SCK提供時鐘脈衝,SDI,SDO則基於此脈衝完成數據傳輸。數據輸出通過SDO線,數據在時鐘上沿或下沿時改變,在緊接着的下沿或上沿被讀取。 完成一位數據傳輸,輸入也使用同樣原理。這樣,在至少8次時鐘信號的改變(上沿和下沿爲一次),就可以完成8位數據的傳輸。
要注意的是,SCK信號線只由主設備控制,從設備不能控制信號線。同樣,在一個基於SPI的設備中,至少有一個主控設備。這就不適用於多處理器的無主控通訊。
這樣傳輸的特點:這樣的傳輸方式有一個優點,與普通的串行通訊不同,普通的串行通訊一次連續傳送至少8位數據,而SPI允許數據一位一位的傳送,甚至允許暫停,因爲SCK時鐘線由主控設備控制,當沒有時鐘跳變時,從設備不採集或傳送數據。也就是說,主設備通過對SCK時鐘線的控制可以完成對通訊的控制。
SPI還是一個數據交換協議:因爲SPI的數據輸入和輸出線獨立,所以允許同時完成數據的輸入和輸出。
不同的SPI設備的實現方式不盡相同,主要是數據改變和採集的時間不同,在時鐘信號上沿或下沿採集有不同定義,需要參考相關器件的文檔。
二、SPI的信號線
之前說過,SPI一共有4根信號線,再回顧下作用:
SCLK:Serial Clock,(串行)時鐘 SDI(MISO):Master In Slave Out,主設備輸入,從設備輸出SDO(MOSI):Master Out Slave In,主設備輸出,從設備輸入 CS: Chip Select,選中從設備,片選 GPIO模擬SPI總的來說是比較簡單,把相應的管腳配置成GPIO功能,再按需要配置管腳的輸入輸出方向,然後根據SPI總線的時序設定IO口的電平。例如,要實現一塊LCD的驅動,LCD與主控芯片之間使用SPI協議通信,GPIO就可以這樣配置——由於主控芯片不需要從LCD讀取數據,SDI可以不接;LCD需要一直被控制,CS接地,使LCD一直處於使能狀態。
三、相位和極性
CKPOL (Clock Polarity) = CPOL = POL = Polarity = (時鐘)極性
CKPHA (Clock Phase) = CPHA = PHA = Phase = (時鐘)相位
1、CPOL 極性
先解釋下什麼是SCLK時鐘的空閒。SCLK空閒就是當SCLK在數發送8個bit比特數據之前和之後的狀態,於此對應的,SCLK在發送數據的時候,就是正常的工作的時候,有效active的時刻了。
簡單的說,SPI的CPOL,表示當SCLK空閒的時候,其電平的值是低電平0還是高電平。
CPOL=0,時鐘空閒idle時候的電平是低電平,所以當SCLK有效的時候,就是高電平,就是所謂的active-high
CPOL=1,時鐘空閒idle時候的電平是高電平,所以當SCLK有效的時候,就是低電平,就是所謂的active-low
2、CPHA 相位
相位,對應着數據採樣是在第幾個邊沿(edge),是第一個邊沿還是第二個邊沿,0對應着第一個邊沿,1對應着第二個邊沿。
CPHA=0,表示第一個邊沿:
對於CPOL=0,idle時候的是低電平,第一個邊沿就是從低變到高,所以是上升沿;
對於CPOL=1,idle時候的是高電平,第一個邊沿就是從高變到低,所以是下降沿;
CPHA=1,表示第二個邊沿:
對於CPOL=0,idle時候的是低電平,第二個邊沿就是從高變到低,所以是下降沿;
對於CPOL=1,idle時候的是高電平,第一個邊沿就是從低變到高,所以是上升沿;
如上所述,CPOL和CPHA可構成4種組合,這就是常說的SPI四種傳輸模式——
CPOL=0, CPHA=0
CPOL=0, CPHA=1
CPOL=1, CPHA=0
CPOL=1, CPHA=1
有圖有真相:
四、GPIO模擬SPI驅動
概念瞭解清楚了,我們來上代碼吧
#define SS 252 //定義SS所對應的GPIO接口編號
#define SCLK 253 //定義SCLK所對應的GPIO接口編號
#define MOSI 254 //定義SCLK所對應的GPIO接口編號
#define MISO 255 //定義MISO所對應的GPIO接口編號
#define OUTP 1 //表示GPIO接口方向爲輸出
#define INP 0 //表示GPIO接口方向爲輸入
/* SPI端口初始化 */
void spi_init()
{
set_gpio_direction(SS, OUTP);
set_gpio_direction(SCLK, OUTP);
set_gpio_direction(MOSI, OUTP);
set_gpio_direction(MISO, INP);
set_gpio_value(SCLK, 0); //CPOL=0
set_gpio_value(MOSI, 0);
}
/*
從設備使能
enable:爲1時,使能信號有效,SS低電平
爲0時,使能信號無效,SS高電平
*/
void ss_enable(int enable)
{
if (enable)
set_gpio_value(SS, 0); //SS低電平,從設備使能有效
else
set_gpio_value(SS, 1); //SS高電平,從設備使能無效
}
/* SPI字節寫 */
void spi_write_byte(unsigned char b)
{
int i;
for (i=7; i>=0; i--) {
set_gpio_value(SCLK, 0);
set_gpio_value(MOSI, b&(1<<i)); //從高位7到低位0進行串行寫入
delay(); //延時
set_gpio_value(SCLK, 1); // CPHA=1,在時鐘的第一個跳變沿採樣
delay();
}
}
/* SPI字節讀 */
unsigned char spi_read_byte()
{
int i;
unsigned char r = 0;
for (i=0; i<8; i++) {
set_gpio_value(SCLK, 0);
delay(); //延時
set_gpio_value(SCLK, 1); // CPHA=1,在時鐘的第一個跳變沿採樣
r = (r <<1) | get_gpio_value(MISO); //從高位7到低位0進行串行讀出
delay();
}
}
/*
SPI寫操作
buf:寫緩衝區
len:寫入字節的長度
*/
void spi_write (unsigned char* buf, int len)
{
int i;
spi_init(); //初始化GPIO接口
ss_enable(1); //從設備使能有效,通信開始
delay(); //延時
//寫入數據
for (i=0; i<len; i++)
spi_write_byte(buf[i]);
delay();
ss_enable(0); //從設備使能無效,通信結束
}
/*
SPI讀操作
buf:讀緩衝區
len:讀入字節的長度
*/
void spi_read(unsigned char* buf, int len)
{
int i;
spi_init(); //初始化GPIO接口
ss_enable(1); //從設備使能有效,通信開始
delay(); //延時
//讀入數據
for (i=0; i<len; i++)
buf[i] = spi_read_byte();
delay();
ss_enable(0); //從設備使能無效,通信結束
}