spi應(yīng)用層寫(xiě)法
【重點(diǎn)是 傳輸?shù)撵`活應(yīng)用,想要每次傳輸之間沒(méi)有間隔,或者小點(diǎn)】
1.1 重要的數(shù)據(jù)結(jié)構(gòu)
1. spi_device
雖然用戶空間不需要直接用到spi_device結(jié)構(gòu)體,但是這個(gè)結(jié)構(gòu)體和用戶空間的程序有密切的關(guān)系,理解它的成員有助于理解SPI設(shè)備節(jié)點(diǎn)的IOCTL命令,所以首先來(lái)介紹它。
在內(nèi)核中,每個(gè)spi_device代表一個(gè)物理的SPI設(shè)備。它的成員如程序清單 1.1所示。
程序清單 1.1 spi_device
struct spi_device {
structdevice dev;
structspi_master *master;
u32 max_speed_hz; /* 通信時(shí)鐘最大頻率 */
u8 chip_select; /* 片選號(hào) */
u8 mode; /*SPI設(shè)備的模式,下面的宏是它各bit的含義 */
#define SPI_CPHA 0x01 /* 采樣的時(shí)鐘相位 */
#define SPI_CPOL 0x02 /* 時(shí)鐘信號(hào)起始相位:高或者是低電平*/
#define SPI_MODE_0 (0|0)
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* 為1時(shí)片選的有效信號(hào)是高電平*/
#define SPI_LSB_FIRST 0x08 /* 發(fā)送時(shí)低比特在前 */
#define SPI_3WIRE 0x10 /* 輸入輸出信號(hào)使用同一根信號(hào)線 */
#define SPI_LOOP 0x20 /* 回環(huán)模式 */
u8 bits_per_word; /* 每個(gè)通信字的字長(zhǎng)(比特?cái)?shù)) */
int irq; /*使用到的中斷 */
void *controller_state;
void *controller_data;
char modalias[32]; /* 設(shè)備驅(qū)動(dòng)的名字*/
};
由于一個(gè)SPI總線上可以有多個(gè)SPI設(shè)備,因此需要片選號(hào)來(lái)區(qū)分它們,SPI控制器根據(jù)片選號(hào)來(lái)選擇不同的片選線,從而實(shí)現(xiàn)每次只同一個(gè)設(shè)備通信。
spi_device的mode成員有兩個(gè)比特位含義很重要。SPI_CPHA選擇對(duì)數(shù)據(jù)線采樣的時(shí)機(jī),0選擇每個(gè)時(shí)鐘周期的第一個(gè)沿跳變時(shí)采樣數(shù)據(jù),1選擇第二個(gè)時(shí)鐘沿采樣數(shù)據(jù);SPI_CPOL選擇每個(gè)時(shí)鐘周期開(kāi)始的極性,0表示時(shí)鐘以低電平開(kāi)始,1選擇高電平開(kāi)始。這兩個(gè)比特有四種組合,對(duì)應(yīng)SPI_MODE_0~SPI_MODE_3。
另一個(gè)比較重要的成員是bits_per_word。這個(gè)成員指定每次讀寫(xiě)的字長(zhǎng),單位是比特。雖然大部分SPI接口的字長(zhǎng)是8或者16,仍然會(huì)有一些特殊的例子。需要說(shuō)明的是,如果這個(gè)成員為零的話,默認(rèn)使用8作為字長(zhǎng)。
最后一個(gè)成員并不是設(shè)備的名字,而是需要綁定的驅(qū)動(dòng)的名字。
2. spi_ioc_transfer(用戶層對(duì)一些屬性消息的封裝,和內(nèi)核中spi_transfer基本一樣,少了一些成員)
在用戶使用設(shè)備節(jié)點(diǎn)的IOCTL命令傳輸數(shù)據(jù)的時(shí)候,可能需要用到 spi_ioc_transfer結(jié)構(gòu)體,它的成員如程序清單 1.2所示。
程序清單 1.2 spi_ioc_transfer
struct spi_ioc_transfer {
__u64 tx_buf; /* 寫(xiě)數(shù)據(jù)緩沖 */
__u64 rx_buf; /* 讀數(shù)據(jù)緩沖 */
__u32 len; /* 緩沖的長(zhǎng)度 */
__u32 speed_hz; /* 通信的時(shí)鐘頻率 */
__u16 delay_usecs; /* 兩個(gè)spi_ioc_transfer之間的延時(shí) */
__u8 bits_per_word; /* 字長(zhǎng)(比特?cái)?shù)) */
__u8 cs_change; /* 是否改變片選 */
__u32 pad;
};
每個(gè) spi_ioc_transfer都可以包含讀和寫(xiě)的請(qǐng)求,其中讀和寫(xiě)的長(zhǎng)度必須相等。所以成員len不是tx_buf和rx_buf緩沖的長(zhǎng)度之和,而是它們各自的長(zhǎng)度。SPI控制器驅(qū)動(dòng)會(huì)先將tx_buf寫(xiě)到SPI總線上,然后再讀取len長(zhǎng)度的內(nèi)容到rx_buf。如果只想進(jìn)行一個(gè)方向的傳輸,把另一個(gè)方向的緩沖置為0就可以了。
speed_hz和bits_per_word這兩個(gè)成員可以為每次通信配置不同的通信速率(必須小于spi_device的max_speed_hz)和字長(zhǎng),如果它們?yōu)?的話就會(huì)使用spi_device中的配置。
delay_usecs可以指定兩個(gè)spi_ioc_transfer之間的延時(shí),單位是微妙。一般不用定義。
cs_change指定這個(gè)cs_change結(jié)束之后是否需要改變片選線。一般針對(duì)同一設(shè)備的連續(xù)的幾個(gè)spi_ioc_transfer,只有最后一個(gè)需要將這個(gè)成員置位。這樣省去了來(lái)回改變片選線的時(shí)間,有助于提高通信速率。
1.2 獲得同SPI設(shè)備通信的設(shè)備節(jié)點(diǎn)
為了在用戶空間獲得和SPI設(shè)備直接通信的設(shè)備節(jié)點(diǎn),必須有兩個(gè)條件要滿足:首先要有SPI控制器驅(qū)動(dòng),其次是要在內(nèi)核初始化的時(shí)候注冊(cè)一個(gè)spi_board_info,它的modalias成員必須為“spidev”。有了這兩個(gè)條件,就可以和SPI設(shè)備進(jìn)行通信了??刂破鞯尿?qū)動(dòng)一般由芯片廠家提供,開(kāi)發(fā)者只需提供第二個(gè)條件。
spi_board_info的定義如程序清單 1.3所示。
程序清單 1.3 struct spi_board_info
struct spi_board_info {
char modalias[32]; /* 要綁定的驅(qū)動(dòng)的名字 */
constvoid *platform_data;
void *controller_data;
int irq;
u32 max_speed_hz; /* 通信時(shí)鐘最大速率 */
u16 bus_num; /* 總線編號(hào) */
u16 chip_select; /* 片選號(hào) */
u8 mode; /* 和spi_device中的mode成員類(lèi)似 */
};
要了解這個(gè)結(jié)構(gòu)體各個(gè)成員的意義請(qǐng)參考程序清單 1.1。
定義并注冊(cè)structspi_board_info的位置一般是內(nèi)核的arch/xxx/mach-xxxx/board-xxxx.c,比如3250的內(nèi)核,這個(gè)文件是arch/arm/mach-lpc32xx/board-smartarm3250.c。定義并注冊(cè)struct spi_board_info的代碼如程序清單 1.4所示。
程序清單 1.4 定義并注冊(cè)spi_board_info
static int __init smartarm3250_spi_usp_register(void)
{
structspi_board_info info =
{
.modalias= "spidev",
.max_speed_hz= 5000000,
.bus_num= 0,
.chip_select= 0,
};
returnspi_register_board_info(&info, 1);
}
arch_initcall(smartarm3250_spi_usp_register);
由于3250內(nèi)核代碼在arch/arm/mach-lpc32xx/board-smartarm3250.c已經(jīng)定義了一個(gè)smartarm3250_spi_eeprom_register函數(shù),因此在增加程序清單 1.4代碼前先將這個(gè)函數(shù)注釋掉。
程序清單 1.4注冊(cè)了一個(gè)掛在0號(hào)SPI總線上的設(shè)備信息,它的片選號(hào)為0。增加完這段代碼后將內(nèi)核重新編譯。在內(nèi)核啟動(dòng)的時(shí)候,會(huì)為這個(gè)設(shè)備建立一個(gè)spi_device并和0號(hào)SPI總線的驅(qū)動(dòng)進(jìn)行綁定。同時(shí)內(nèi)核會(huì)為這個(gè)設(shè)備申請(qǐng)一個(gè)主設(shè)備號(hào)為153的的設(shè)備號(hào),次設(shè)備號(hào)和注冊(cè)的順序有關(guān),最多支持32個(gè)同類(lèi)設(shè)備。
內(nèi)核重新編譯并重啟之后,如果系統(tǒng)中運(yùn)行了udev,/dev下就會(huì)生成一個(gè)spidevX.D設(shè)備節(jié)點(diǎn),其中X是總線編號(hào),D是片選號(hào)。對(duì)于程序清單 1.4的代碼應(yīng)該自動(dòng)生成的設(shè)備節(jié)點(diǎn)是spidev0.0。
一般SPI控制器驅(qū)動(dòng)由芯片廠商提供,開(kāi)發(fā)者所要在內(nèi)核做的工作就是添加類(lèi)似程序清單 1.4的內(nèi)容。這樣內(nèi)核空間的工作減少了,用戶空間的工作量加大了,因?yàn)橛脩艨臻g的開(kāi)發(fā)者需要全面了解SPI設(shè)備的工作方式和接口協(xié)議。
1.3 用戶空間同設(shè)備節(jié)點(diǎn)的接口
對(duì)于/dev/spidevX.D設(shè)備節(jié)點(diǎn),可以進(jìn)行各種操作,這一小節(jié)介紹它支持的函數(shù)接口。
1. open/close
打開(kāi)和關(guān)閉設(shè)備節(jié)點(diǎn)沒(méi)有特別之處,直接使用open/write就可以了。
2. read/write
讀寫(xiě)SPI設(shè)備可以直接使用read/write函數(shù),但是每次讀或者寫(xiě)的大小不能大于4096Byte。
3. IOCTL命令
用戶空間對(duì)spidev設(shè)備節(jié)點(diǎn)使用IOCTL命令失敗會(huì)返回-1。
l SPI_IOC_RD_MODE
讀取SPI設(shè)備對(duì)應(yīng)的spi_device.mode,mode的含義請(qǐng)參考程序清單 1.1。使用的方法如下:
ioctl(fd,SPI_IOC_RD_MODE, &mode);
其中第三個(gè)參數(shù)是一個(gè)uint8_t類(lèi)型的變量。
l SPI_IOC_WR_MODE
設(shè)置SPI設(shè)備對(duì)應(yīng)的spi_device.mode。使用的方式如下:
ioctl(fd,SPI_IOC_WR_MODE, &mode);
l SPI_IOC_RD_LSB_FIRST
查看設(shè)備傳輸?shù)臅r(shí)候是否先傳輸?shù)捅忍匚?。如果是的話,返?。使用的方式如下:
ioctl(fd,SPI_IOC_RD_LSB_FIRST, &lsb);
其中l(wèi)sb是一個(gè)uint8_t類(lèi)型的變量。返回的結(jié)果存在lsb中。
l SPI_IOC_WR_LSB_FIRST
設(shè)置設(shè)備傳輸?shù)臅r(shí)候是否先傳輸?shù)捅忍匚?。?dāng)傳入非零的時(shí)候,低比特在前,當(dāng)傳入0的時(shí)候高比特在前(默認(rèn))。使用的方式如下:
ioctl(fd,SPI_IOC_WR_LSB_FIRST, &lsb);
l SPI_IOC_RD_BITS_PER_WORD
讀取SPI設(shè)備的字長(zhǎng)。使用的方式如下:
ioctl(fd,SPI_IOC_RD_BITS_PER_WORD, &bits);
其中bits是一個(gè)uibt8_t類(lèi)型的變量。返回的結(jié)果保存在bits中。
l SPI_IOC_WR_BITS_PER_WORD
設(shè)置SPI通信的字長(zhǎng)。使用的方式如下:
ioctl(fd,SPI_IOC_WR_BITS_PER_WORD, &bits);
l SPI_IOC_RD_MAX_SPEED_HZ
讀取SPI設(shè)備的通信的最大時(shí)鐘頻率。使用的方式如下:
ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ, &speed);
其中speed是一個(gè)uint32_t類(lèi)型的變量。返回的結(jié)果保存在speed中。
l SPI_IOC_WR_MAX_SPEED_HZ
設(shè)置SPI設(shè)備的通信的最大時(shí)鐘頻率。使用的方式如下:
ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ, &speed);
l SPI_IOC_MESSAGE(N)
一次進(jìn)行雙向/多次讀寫(xiě)操作。使用的方式如下:
structspi_ioc_transfer xfer[2];
......
status= ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
其中N是本次通信中xfer的數(shù)組長(zhǎng)度。spi_ioc_transfer的信息請(qǐng)參考程序清單 1.2。
/************************************************************************************/
如果想要在用戶空間編寫(xiě)spi驅(qū)動(dòng),這就要在內(nèi)核的arch/.../mach-*/board-*.c 中聲明一個(gè)spi_board_info,
它的名字一定要是“spidev”,比如:
struct spi_board_info info =
{
.modalias = "spidev",
.max_speed_hz = 5000000,
.bus_num = 0,
.chip_select = 0,
};
return spi_register_board_info(&info, 1);
這樣只要控制器驅(qū)動(dòng)加載了,spidev模塊就會(huì)和這個(gè)設(shè)備綁定,并為設(shè)備申請(qǐng)一個(gè)設(shè)備號(hào),主設(shè)備號(hào)為153,次設(shè)備號(hào)和設(shè)備加載的次序有關(guān)。
目前spidev支持最多32個(gè)設(shè)備。設(shè)備的名字是spidevX.D,其中X是總線編號(hào),D是設(shè)備的片選號(hào)。如果正確安裝并配置了udev,/dev目錄下便會(huì)生成spidevX.D
設(shè)備節(jié)點(diǎn)。直接對(duì)這些設(shè)備節(jié)點(diǎn)操作就行了。
spidev的設(shè)備節(jié)點(diǎn)的接口包括open/close/read/write/ioctl。
~~~~~~~~~~~~~~~~~~~~~~~~~
其中open/close沒(méi)有什么特別之處。
read/write的話有大小的限制,讀寫(xiě)的大小默認(rèn)不能超過(guò)4096字節(jié)。這個(gè)大小是一個(gè)模塊加載參數(shù),可以修改。
允許多個(gè)用戶同時(shí)打開(kāi)設(shè)備節(jié)點(diǎn),spidev使用mutext進(jìn)行互斥,多個(gè)用戶同時(shí)讀寫(xiě)時(shí)只有一個(gè)活動(dòng)的用戶,其他用戶睡眠。
spidev的ioctl命令。
~~~~~~~~
SPI_IOC_RD_MODE:讀取spi_device的mode。
SPI_IOC_RD_LSB_FIRST:如果是SPI_LSB_FIRST的方式則返回1。
SPI_IOC_RD_BITS_PER_WORD:讀取spi_device的bits_per_word.
SPI_IOC_RD_MAX_SPEED_HZ:讀取spi_device的max_speed_hz.
SPI_IOC_WR_MODE:設(shè)置spi_device的mode,并調(diào)用spi_setup立即使設(shè)置生效。
SPI_IOC_WR_LSB_FIRST:設(shè)置spi使用SPI_LSB_FIRST的傳輸模式。立即生效。
SPI_IOC_WR_BITS_PER_WORD:讀取字長(zhǎng)。
SPI_IOC_WR_MAX_SPEED_HZ:設(shè)置時(shí)鐘速率。
無(wú)論讀取,用戶傳輸?shù)牡谌齻€(gè)參數(shù)都被當(dāng)作緩沖地址指針。讀取時(shí)存放結(jié)果,寫(xiě)入時(shí)存放要寫(xiě)的內(nèi)容。
struct spi_ioc_transfer {
__u64 tx_buf;
__u64 rx_buf;
__u32 len;
__u32 speed_hz;
__u16 delay_usecs;
__u8 bits_per_word;
__u8 cs_change;
__u32 pad;
/* If the contents of 'struct spi_ioc_transfer' ever change
* incompatibly, then the ioctl number (currently 0) must change;
* ioctls with constant size fields get a bit more in the way of
* error checking than ones (like this) where that field varies.
*
* NOTE: struct layout is the same in 64bit and 32bit userspace.
*/
};
內(nèi)核文檔中一個(gè)例子:
static void do_msg(int fd, int len)
{
struct spi_ioc_transfer xfer[2];
unsigned char buf[32], *bp;
int status;
memset(xfer, 0, sizeof xfer);
memset(buf, 0, sizeof buf);
if (len > sizeof buf)
len = sizeof buf;
buf[0] = 0xaa;
xfer[0].tx_buf = (__u64) buf;
xfer[0].len = 1;
xfer[1].rx_buf = (__u64) buf;
xfer[1].len = len;
status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return;
}
printf("response(%2d, %2d): ", len, status);
for (bp = buf; len; len--)
printf(" %02x", *bp++);
printf("/n");
}
內(nèi)核在documentation/spi目錄下有spidev的例子。
注意
~~~~
雖然多個(gè)用戶不能同一時(shí)刻對(duì)spi進(jìn)行設(shè)置或讀寫(xiě),但是同一用戶卻無(wú)法組織其他用戶修改同一設(shè)備的設(shè)置。
舉例來(lái)說(shuō),usr1打開(kāi)設(shè)備節(jié)點(diǎn),然后使用ioctl設(shè)置了時(shí)鐘速率,此時(shí)usr1線程被調(diào)度出去,然后usr2操作同一個(gè)設(shè)備,將它的時(shí)鐘設(shè)為另一個(gè)值。
此時(shí)usr1重新調(diào)度去使用read函數(shù),則達(dá)不到預(yù)期的效果。
建議不要有兩個(gè)程序操作spidevX.D設(shè)備節(jié)點(diǎn)。
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。