SPI匯流排協議學習筆記
最近正在學習SPI匯流排協議,看了很多網上的相關內容,覺得有必要整理一下,既可以鞏固自己的學習內容,也可以和大家分享,方便以後的學習。
先來對SPI有個大概的瞭解,如下:
SPI是英語Serial Peripheral Interface的縮寫,也就是序列外圍裝置介面。SPI是一種高速的,全雙工,同步的通訊匯流排,並且在晶片的管腳上只佔用四根線,節約了晶片的管腳並且可以節省PCB的佈局空間,故現在越來越多的晶片集成了這種協議。四根線的埠如下:SDI(資料輸入),SDO(資料輸出),SCLK(時鐘訊號),CS(SS,片選)
SPI有四種工作模式(這裡參考了網友的部落格,http://www.linxh.blog.chinaunix.net/uid-23381466-id-257847.html),如下:
四種工作模式是按照 SPI時鐘極性CPOL 和 SPI時鐘相位CPHA 來劃分的。
當 SPI時鐘極性CPOL=0 時,表示沒有資料傳輸時為低電平;當 SPI時鐘極性CPOL=1 時,表示沒有資料傳輸時為高電平。
當 SPI時鐘相位CPHA=0 時,表示時鐘的第一個沿採集資料,第二個沿輸出資料;當 SPI時鐘相位CPHA=1 時,表示時鐘的第一個沿輸出資料,第二個沿採集資料。
需要注意的是,這裡的第一個沿和第二個沿可以是上升沿或者下降沿,因為時鐘極性不同,所以沿的方向也就跟著發生變化了。
接下來展示一下四種情況下的SPI工作模式圖,可以方便我們的理解,如下:
通過上面的圖,就能大致瞭解這四種模式了,但是僅僅這樣我覺得還是有點欠缺,有網友的SPI時序圖詳解,我覺得很有參考價值(網址,http://www.docin.com/p-76201096.html)。
其中,在文件的開頭他做了一個假設(這個假設的前提是上升沿輸出資料,空閒時時鐘保持在低電平,也就是對應上圖中的第二個工作模式,CPOL=0,CPHA=1),很好的給我們解釋了在時鐘脈衝作用下,資料傳輸的詳細過程,看完這個過程,我覺得會有更清楚的認識。
接著,在文件中間部分,他介紹了在CPOL=0,CPHA=0(空閒時時鐘保持在低電平,上升沿採集資料,下降沿輸出資料)的情況下,器件的電平變化情況,也很有學習價值,可以好好看一下。
最後,再附上用IO口來模擬的四種SPI模式程式,僅作參考理解用,還要根據實際情況改寫,如下:
//表示相關引腳高低電平,要根據實際引腳修改。
SSEL_D(0) SSEL_D(1) //片選
SCK_D(0) SCK_D(1) //時鐘訊號
MOSI_D(0) MOSI_D(1) //SDO
MISO_I(0) MISO_I(1) //SDI
#define _CPOL 1 //時鐘極性
#define _CPHA 0 //時鐘相位
//延時子程式
void delay()
{
unsigned char m,n;
for(n=0;n<5;n++);
for(m=0;m<100;m++);
}
/**********************************************
模式零 寫資料
***********************************************/
#if _CPOL==0&&_CPHA==0 //MODE 0 0
void SPI_Send_Dat(unsigned char dat)
{
unsigned char n;
for(n=0;n<8;n++)
{
SCK_D(0);
if(dat&0x80)MOSI_D(1);
else MOSI_D(0);
dat<<=1;
SCK_D(1);
}
SCK_D(0);
}
/*********************************************
模式零 讀資料
*********************************************/
unsigned char SPI_Receiver_Dat(void)
{
unsigned char n ,dat,bit_t;
for(n=0;n<8;n++)
{
SCK_D(0);
dat<<=1;
if(MISO_I())dat|=0x01;
else dat&=0xfe;
SCK_D(1);
}
SCK_D(0);
return dat;
}
#endif
/*********************************************
模式一 寫資料
*********************************************/
#if _CPOL==0&&_CPHA==1 //MODE 0 1
void SPI_Send_Dat(unsigned char dat)
{
unsigned char n;
SCK_D(0);
for(n=0;n<8;n++)
{
SCK_D(1);
if(dat&0x80)MOSI_D(1);
else MOSI_D(0);
dat<<=1;
SCK_D(0);
}
}
/*********************************************
模式一 讀資料
*********************************************/
unsigned char SPI_Receiver_Dat(void)
{
unsigned char n ,dat,bit_t;
for(n=0;n<8;n++)
{
SCK_D(1);
dat<<=1;
if(MISO_I())dat|=0x01;
else dat&=0xfe;
SCK_D(0);
}
SCK_D(0);
return dat;
}
#endif
/**********************************************
模式二 寫資料
***********************************************/
#if _CPOL==1&&_CPHA==0 //MODE 1 0
void SPI_Send_Dat(unsigned char dat)
{
unsigned char n;
for(n=0;n<8;n++)
{
SCK_D(1);
if(dat&0x80)MOSI_D(1);
else MOSI_D(0);
dat<<=1;
SCK_D(0);
}
SCK_D(1);
}
/*********************************************
模式二 讀資料
*********************************************/
unsigned char SPI_Receiver_Dat(void)
{
unsigned char n ,dat,bit_t;
for(n=0;n<8;n++)
{
SCK_D(1);
dat<<=1;
if(MISO_I())dat|=0x01;
else dat&=0xfe;
SCK_D(0);
}
SCK_D(1);
return dat;
}
#endif
/**********************************************
模式三 寫資料
***********************************************/
#if _CPOL==1&&_CPHA==1 //MODE 1 1
void SPI_Send_Dat(unsigned char dat)
{
unsigned char n;
SCK_D(1);
for(n=0;n<8;n++)
{
SCK_D(0);
if(dat&0x80)MOSI_D(1);
else MOSI_D(0);
dat<<=1;
SCK_D(1);
}
}
/************************************
模式三 讀資料
************************************/
unsigned char SPI_Receiver_Dat(void)
{
unsigned char n ,dat,bit_t;
SCK_D(0);
for(n=0;n<8;n++)
{ SCK_D(0);
dat<<=1;
if(MISO_I())dat|=0x01;
else dat&=0xfe;
SCK_D(1);
}
SCK_D(1);
return dat;
}
#endif
void main()
{
SPI_Init();
DDRB = 0XFF;
//#if _CPOL
//SCK_D(0);
//#endif
while(1)
{
//SSEL_D(0);
//SPI_Send_Dat(0x01);
//SPI_Send_Dat(0x31);
//SSEL_D(1);
SSEL_D(0);
SPI_Send_Dat(0x81);
PORTB =SPI_Receiver_Dat();
SSEL_D(1);
//delay();
}
}
注:以上所參考的網址為:
http://www.linxh.blog.chinaunix.net/uid-23381466-id-257847.html
http://www.docin.com/p-76201096.html
http://www.51hei.com/mcu/1392.html