GPIO模擬SPI通訊接口

一、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提供時鐘脈衝,SDISDO則基於此脈衝完成數據傳輸。數據輸出通過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);       //從設備使能無效,通信結束
}




   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章