spi驅動

一 管腳

SPI以主從方式工作,這種模式通常有一個主設備和一個或多個從設備,需要至少4根線,事實上3根也可以(單向傳輸時):

1SDO     – 主設備數據輸出,從設備數據輸入

2SDI      – 主設備數據輸入,從設備數據輸出

3SCLK   – 時鐘信號,由主設備產生

4CS        – 從設備使能信號,由主設備控制

二四種工作方式

SPI輸出串行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協議沒有重大的影響。

如果 CPOL=0,串行同步時鐘的空閒狀態爲低電平;如果CPOL=1,串行同步時鐘的空閒狀態爲高電平。

時鐘相位(CPHA)能夠配置用於選擇兩種不同的傳輸協議之一進行數據傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)數據被採樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)數據被採樣。SPI主模塊和與之通信的外設備時鐘相位和極性應該一致。

SPI主模塊和與之通信的外設備時鐘相位和極性應該一致。這句話有2層意思:其一,主設備SPI時鐘和極性的配置應該由外設來決定;其二,二者的配置應該保持一致,即主設備的SDO同從設備的SDO配置一致,主設備的SDI同從設備的SDI配置一致。因爲主從設備是在SCLK的控制下,同時發送和接收數據,並通過2個雙向移位寄存器來交換數據。

它允許 MCU 以全雙工的同步串行方式與各種外圍設備進行高速數據通信.

在芯片中只佔用四根管腳 (Pin) 用來控制以及數據傳輸

CPOL: 時鐘極性表示 SPI 在空閒時時鐘信號是高電平還是低電平若 CPOL 被設爲 1, 那麼該設備在空閒時 SCK 管腳下的時鐘信號爲高電平當 CPOL 被設爲 時則正好相反.

        CPHA: 時鐘相位表示 SPI 設備是在 SCK 管腳上的時鐘信號變爲上升沿時觸發數據採樣還是在時鐘信號變爲下降沿時觸發數據採樣若 CPHA 被設置爲 1, 則 SPI 設備在時鐘信號變爲下降沿時觸發數據採樣在上升沿時發送數據當 CPHA 被設爲 時也正好相反.
  

        上圖裏的 "Mode 1, 1" 說明了本例所使用的 SPI 數據傳輸模式被設置成 CPOL = 1, CPHA = 1. 這樣在一個 Clock 週期內每個單獨的 SPI 設備都能以全雙工(Full-Duplex) 的方式同時發送和接收 1 bit 數據即相當於交換了 1 bit 大小的數據如果 SPI 總線的 Channel-Width 被設置成 Byte, 表示 SPI 總線上每次數據傳輸的最小單位爲 Byte, 那麼掛載在該 SPI 總線的設備每次數據傳輸的過程至少需要 個 Clock 週期(忽略設備的物理延遲). 因此, SPI 總線的頻率越快, Clock 週期越短則 SPI 設備間數據交換的速率就越快.

SPI是[單主設備( single-master )]通信協議,這意味着總線中的只有一支中心設備能發起通信。當SPI主設備想讀/寫[從設備]時,它首先拉低[從設備]對應的SS線(SS是低電平有效),接着開始發送工作脈衝到時鐘線上,在相應的脈衝時間上,[主設備]把信號發到MOSI實現,同時可對MISO採樣而實現

高位在前,低位在後

SPI的極性Polarity和相位Phase,最常見的寫法是CPOLCPHA,不過也有一些其他寫法,簡單總結如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (時鐘)極性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (時鐘)相位
(3) SCK=SCLK=SPI的時鐘
(4) Edge=邊沿,即時鐘電平變化的時刻,即上升沿(rising edge)或者下降沿(falling edge)
對於一個時鐘週期內,有兩個edge,分別稱爲:
(1)Leading edge=前一個邊沿=第一個邊沿,對於開始電壓是1,那麼就是1變成0的時候,對於開始電壓是0,那麼就是0變成1的時候;
(2)Trailing edge=後一個邊沿=第二個邊沿,對於開始電壓是1,那麼就是0變成1的時候(即在第一次1變成0之後,纔可能有後面的0變成1),對於開始電壓是0,那麼就是1變成0的時候;
本博文采用如下用法:
極性=CPOL

相位=CPHA

SCLK=時鐘

第一個邊沿和第二個邊沿

CPOLCPHA,分別都可以是0或時1,對應的四種組合就是:

下面詳細介紹。

CPOL極性

先說什麼是SCLK時鐘的空閒時刻,其就是當SCLK在發送8bit比特數據之前和之後的狀態,於此對應的,SCLK在發送數據的時候,就是正常的工作的時候,有效active的時刻了。其英文精簡解釋爲:Clock Polarity = IDLE state of SCK
SPICPOL,表示當SCLK空閒idle的時候,其電平的值是低電平0還是高電平1
CPOL=0,時鐘空閒idle時候的電平是低電平,所以當SCLK有效的時候,就是高電平,就是所謂的active-high
CPOL=1,時鐘空閒idle時候的電平是高電平,所以當SCLK有效的時候,就是低電平,就是所謂的active-low


從上圖中可以看出,(CPOL=0)SCK 波形,它有(傳輸)個脈衝,而在脈衝傳輸前和完成後都保持在【低電平狀態】。此時的狀態就是時鐘的空閒狀態或無效狀態,因爲此時沒有脈衝,也就不會有數據傳輸。同理得出,(CPOL=的圖,時鐘的空閒狀態或無效狀態時SCK 是保持【高電平的】。

CPHA相位

首先說明一點,capture strobe = latch = read = sample,都是表示數據採樣,數據有效的時刻。相位,對應着數據採樣是在第幾個邊沿(edge),是第一個邊沿還是第二個邊沿,0對應着第一個邊沿,1對應着第二個邊沿。
對於:
CPHA=0,表示第一個邊沿:
對於CPOL=0idle時候的是低電平,第一個邊沿就是從低變到高,所以是上升沿;
對於CPOL=1idle時候的是高電平,第一個邊沿就是從高變到低,所以是下降沿;
CPHA=1,表示第二個邊沿:
對於CPOL=0idle時候的是低電平,第二個邊沿就是從高變到低,所以是下降沿;
對於CPOL=1idle時候的是高電平,第一個邊沿就是從低變到高,所以是上升沿;

我們看上面的圖,發現數據 SI 是對應 SCK 的第一個時鐘沿,再仔細看,數據是在SCK的第一個時鐘邊沿保持穩定【數據被採樣捕獲】,在下一個邊沿改變【SCK 的下降沿數據改變】因此我們得出結論:該系列FLASH 是【數據在第一個時鐘沿被採樣捕獲】或【數據在SPCK 起始邊沿捕獲,在SPCK 下一個邊沿改變】

如何判斷CPOLCPHA

如果起始的SCLK的電平是0,那麼CPOL=0,如果是1,那麼CPOL=1,然後看數據採樣時刻,即時序圖數據線上的數據那個矩形區域的中間所對應的位置,對應到上面SCLK時鐘的位置,對應着是第一個邊沿或是第二個邊沿,即CPHA01。(對應的是上升沿還是還是下降沿,要根據對應的CPOL的值,才能確定)。

1)如何判斷CPOLSCLK的空閒時候的電壓,是0還是1,決定了CPOL0還是1
2)如何判斷CPHA:而數據採樣時刻對應着的SCLK的電平,是第一個邊沿還是第二個邊沿,對應着CPHA0還是1

SCLK的極性,相位,邊沿之間的內在邏輯

最後來看一下S3C2440SPICPOLCPHA,結合前面講的理論知識,下面的圖就很好理解啦!

SPI概述

SPI是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口,是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線,節約了芯片的管腳,同時爲PCB的佈局上節省空間,提供方便。
SPI的通信原理很簡單,它以主從方式工作,這種模式通常有一個主設備和一個或多個從設備,需要4根線,事實上3根也可以。也是所有基於SPI的設備共有的,它們是SDI(數據輸入),SDO(數據輸出),SCLK(時鐘),CS(片選)。
MOSI(SDO):主器件數據輸出,從器件數據輸入。
MISO(SDI):主器件數據輸入,從器件數據輸出。
SCLK :時鐘信號,由主器件產生。
CS:從器件使能信號,由主器件控制。
其中CS是控制芯片是否被選中的,也就是說只有片選信號爲預先規定的使能信號時(高電位或低電位),對此芯片的操作纔有效,這就允許在同一總線上連接多個SPI設備成爲可能。需要注意的是,在具體的應用中,當一條SPI總線上連接有多個設備時,SPI本身的CS有可能被其他的GPIO腳代替,即每個設備的CS腳被連接到處理器端不同的GPIO,通過操作不同的GPIO口來控制具體的需要操作的SPI設備,減少各個SPI設備間的干擾。
SPI是串行通訊協議,也就是說數據是一位一位從MSB或者LSB開始傳輸的,這就是SCK時鐘線存在的原因,由SCK提供時鐘脈衝,MISO、MOSI則基於此脈衝完成數據傳輸。 SPI支持4-32bits的串行數據傳輸,支持MSB和LSB,每次數據傳輸時當從設備的大小端發生變化時需要重新設置SPI Master的大小端。
2 Linux SPI驅動總體架構
在2.6的linux內核中,SPI的驅動架構可以分爲如下三個層次:SPI 核心層、SPI控制器驅動層和SPI設備驅動層。
Linux 中SPI驅動代碼位於drivers/spi目錄。
2.1 SPI核心層
SPI核心層是Linux的SPI核心部分,提供了核心數據結構的定義、SPI控制器驅動和設備驅動的註冊、註銷管理等API。其爲硬件平臺無關層,向下屏蔽了物理總線控制器的差異,定義了統一的訪問策略和接口;其向上提供了統一的接口,以便SPI設備驅動通過總線控制器進行數據收發。
Linux中,SPI核心層的代碼位於driver/spi/ spi.c。由於該層是平臺無關層,本文將不再敘述,有興趣可以查閱相關資料。
2.2 SPI控制器驅動層
SPI控制器驅動層,每種處理器平臺都有自己的控制器驅動,屬於平臺移植相關層。它的職責是爲系統中每條SPI總線實現相應的讀寫方法。在物理上,每個SPI控制器可以連接若干個SPI從設備。
在系統開機時,SPI控制器驅動被首先裝載。一個控制器驅動用於支持一條特定的SPI總線的讀寫。一個控制器驅動可以用數據結構struct spi_master來描述。
在include/liunx/spi/spi.h文件中,在數據結構struct spi_master定義如下:
struct spi_master { 
struct device dev; 
s16 bus_num; 
u16 num_chipselect; 
int (*setup)(struct spi_device *spi); 
int (*transfer)(struct spi_device *spi, struct spi_message *mesg); 
void (*cleanup)(struct spi_device *spi); 
}; 
bus_num爲該控制器對應的SPI總線號。
num_chipselect 控制器支持的片選數量,即能支持多少個spi設備 
setup函數是設置SPI總線的模式,時鐘等的初始化函數, 針對設備設置SPI的工作時鐘及數據傳輸模式等。在spi_add_device函數中調用。 
transfer函數是實現SPI總線讀寫方法的函數。實現數據的雙向傳輸,可能會睡眠
cleanup註銷時候調用
2.3 SPI設備驅動層
SPI設備驅動層爲用戶接口層,其爲用戶提供了通過SPI總線訪問具體設備的接口。
SPI設備驅動層可以用兩個模塊來描述,struct spi_driver和struct spi_device。
相關的數據結構如下:
struct spi_driver { 
int (*probe)(struct spi_device *spi); 
int (*remove)(struct spi_device *spi); 
void (*shutdown)(struct spi_device *spi); 
int (*suspend)(struct spi_device *spi, pm_message_t mesg); 
int (*resume)(struct spi_device *spi); 
struct device_driver driver; 
}; 
Driver是爲device服務的,spi_driver註冊時會掃描SPI bus上的設備,進行驅動和設備的綁定,probe函數用於驅動和設備匹配時被調用。從上面的結構體註釋中我們可以知道,SPI的通信是通過消息隊列機制,而不是像I2C那樣通過與從設備進行對話的方式。
struct spi_device { 
struct device dev; 
struct spi_master *master; 
u32 max_speed_hz; 
u8 chip_select; 
u8 mode; 
u8 bits_per_word; 
int irq; 
void *controller_state; 
void *controller_data; 
char modalias[32]; 
}; 
.modalias = "m25p10",
.mode =SPI_MODE_0, //CPOL=0, CPHA=0 此處選擇具體數據傳輸模式
.max_speed_hz = 10000000, //最大的spi時鐘頻率
/* Connected to SPI-0 as 1st Slave */
.bus_num = 0, //設備連接在spi控制器0上
.chip_select = 0, //片選線號,在S5PC100的控制器驅動中沒有使用它作爲片選的依據,而是選擇了下文controller_data裏的方法。
.controller_data = &smdk_spi0_csi[0], 
通常來說spi_device對應着SPI總線上某個特定的slave。並且spi_device封裝了一個spi_master結構體。spi_device結構體包含了私有的特定的slave設備特性,包括它最大的頻率,片選那個,輸入輸出模式等等
4 spi_device以下一系列的操作是在platform板文件中完成!
spi_device的板信息用spi_board_info結構體來描述:
struct spi_board_info {
charmodalias[SPI_NAME_SIZE];
const void*platform_data;
void*controller_data;
intirq;
u32max_speed_hz;
u16bus_num;
u16chip_select;
u8mode;
};
這個結構體記錄了SPI外設使用的主機控制器序號、片選信號、數據比特率、SPI傳輸方式等
構建的操作是以下的兩個步驟:
1.
static struct spi_board_info s3c_spi_devs[] __initdata = {
{
.modalias = "m25p10a",
.mode = SPI_MODE_0,
.max_speed_hz = 1000000,
.bus_num = 0,
.chip_select = 0,
.controller_data = &smdk_spi0_csi[SMDK_MMCSPI_CS],
},
};
2.
而這個info在init函數調用的時候會初始化:
spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));
spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));//註冊spi_board_info。這個代碼會把spi_board_info註冊到鏈表board_list上。spi_device封裝了一個spi_master結構體,事實上spi_master的註冊會在spi_register_board_info之後,spi_master註冊的過程中會調用scan_boardinfo掃描board_list,找到掛接在它上面的spi設備,然後創建並註冊spi_device。
至此spi_device就構建並註冊完成了!!!!!!!!!!!!!
5 spi_driver的構建與註冊
driver有幾個重要的結構體:spi_driver、spi_transfer、spi_message
driver有幾個重要的函數 :spi_message_init、spi_message_add_tail、spi_sync
//spi_driver的構建
static struct spi_driver m25p80_driver = { 
.driver = {
.name ="m25p80",
.bus =&spi_bus_type,
.owner = THIS_MODULE,
},
.probe = m25p_probe,
.remove =__devexit_p(m25p_remove),
};
//spidriver的註冊
spi_register_driver(&m25p80_driver);
在有匹配的spi_device時,會調用m25p_probe
probe裏完成了spi_transfer、spi_message的構建;
spi_message_init、spi_message_add_tail、spi_sync、spi_write_then_read函數的調用
例如:
*/ 
static int m25p10a_read( struct m25p10a *flash, loff_t from, 
size_t len, char *buf ) 
{ 
int r_count = 0, i; 
struct spi_transfer st[2]; 
struct spi_message msg; 

spi_message_init( &msg ); 
memset( st, 0, sizeof(st) ); 

flash->cmd[0] = CMD_READ_BYTES; 
flash->cmd[1] = from >> 16; 
flash->cmd[2] = from >> 8; 
flash->cmd[3] = from; 

st[ 0 ].tx_buf = flash->cmd; 
st[ 0 ].len = CMD_SZ; 
spi_message_add_tail( &st[0], &msg ); 

st[ 1 ].rx_buf = buf; 
st[ 1 ].len = len; 
spi_message_add_tail( &st[1], &msg ); 

mutex_lock( &flash->lock ); 

/* Wait until finished previous write command. */ 
if (wait_till_ready(flash)) { 
mutex_unlock( &flash->lock ); 
return -1; 
} 

spi_sync( flash->spi, &msg ); 
r_count = msg.actual_length - CMD_SZ; 
printk( "in (%s): read %d bytes\n", __func__, r_count ); 
for( i = 0; i < r_count; i++ ) { 
printk( "0x%02x\n", buf[ i ] ); 
} 

mutex_unlock( &flash->lock );

return 0; 
}

static int m25p10a_write( struct m25p10a *flash, loff_t to, 
size_t len, const char *buf ) 
{ 
int w_count = 0, i, page_offset;

struct spi_transfer st[2]; 

struct spi_message msg;

write_enable( flash ); //寫使能 
spi_message_init( &msg ); 
memset( st, 0, sizeof(st) ); 
flash->cmd[0] = CMD_PAGE_PROGRAM; 
flash->cmd[1] = to >> 16; 
flash->cmd[2] = to >> 8; 
flash->cmd[3] = to; 
st[ 0 ].tx_buf = flash->cmd; 
st[ 0 ].len = CMD_SZ; 
//填充spi_transfer,將transfer放在隊列後面
spi_message_add_tail( &st[0], &msg ); 
st[ 1 ].tx_buf = buf; 
st[ 1 ].len = len; 
spi_message_add_tail( &st[1], &msg ); 
spi_sync( flash->spi, &msg ); 調用spi_master發送spi_message
return 0; 
} 
static int m25p10a_probe(struct spi_device *spi) 
{ 
int ret = 0; 
struct m25p10a *flash; 
char buf[ 256 ]; 
flash = kzalloc( sizeof(struct m25p10a), GFP_KERNEL ); 
flash->spi = spi; 
/* save flash as driver's private data */ 
spi_set_drvdata( spi, flash ); 
memset( buf, 0x7, 256 ); 
m25p10a_write( flash, 0, 20, buf); //0地址寫入20個7 
memset( buf, 0, 256 ); 
m25p10a_read( flash, 0, 25, buf ); //0地址讀出25個數 
return 0; 

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