Linux下SPI設備驅動開發

一.SPI協議介紹

1.硬件連接介紹

????????引腳含義:

????????DO(MOSI):Master Output, Slave Input,SPI主控用來發出數據,SPI從設備用來接收數據。

????????DI(MISO):Master Input, Slave Output,SPI主控用來發出數據,SPI從設備用來接收數據。

????????SCK:Serial Clock,時鐘。

??????? CS:Chip Select,芯片選擇引腳。

2.SPI協議

a.傳輸示例

????????設現在主控芯片要傳輸一個0x56數據給SPI FLASH,時序如下:

????????首先我們要先拉低 CS0 選中 SPI Flash,0x56的二進制為 0b0101 0110,因此我們在每個 SCK 時鐘周期,DO 輸出對應的電平。SPI Flash 會在每個時鐘周期的上升沿讀取 DO 上的電平。?

b.SPI 模式

????????在 SPI 協議中,有兩個值來確定 SPI 的模式。CPOL:表示 SPICLK 的初始電平,0 為低電平,1 為高電平。CPHA:表示相位,即第一個還是第二個時鐘沿采樣數據,0 為第一個時鐘沿,1 為第二個時鐘沿。

??????? 模式0:CPOL=0,CPHA=0,SPICLK初始電平為低電平,在第一個時鐘沿采樣數據。

??????? 模式1:CPOL=0,CPHA=1,SPICLK初始電平為低電平,在第二個時鐘沿采樣數據

??????? 模式2:CPOL=1,CPHA=0,SPICLK初始電平為高電平,在第一個時鐘沿采樣數據

??????? 模式3:CPOL=1,CPHA=1,SPICLK初始電平為低電平,在第一個時鐘沿采樣數據。

????????常用的是模式0和模式3,因為它們都是在上升沿采集數據,不用在乎時鐘電平的初始電平是什么,只要在上升沿采集數據就行。?

3.特點

a.采用主-從模式(Master-Slave) 的控制方式

????????SPI 規定了兩個 SPI 設備之間通信必須由主設備 (Master) 來控制次設備 (Slave). 一個 Master 設備可以通過提供 Clock 以及對 Slave 設備進行片選 (Slave Select) 來控制多個 Slave 設備, SPI 協議還規定 Slave 設備的 Clock 由 Master 設備通過 SCK 管腳提供給 Slave 設備, Slave 設備本身不能產生或控制 Clock, 沒有 Clock 則 Slave 設備不能正常工作。

c.采用同步方式(Synchronous)傳輸數據

????????Master 設備會根據將要交換的數據來產生相應的時鐘脈沖(Clock Pulse), 時鐘脈沖組成了時鐘信號(Clock Signal) , 時鐘信號通過時鐘極性 (CPOL) 和 時鐘相位 (CPHA) 控制著兩個 SPI 設備間何時數據交換以及何時對接收到的數據進行采樣, 來保證數據在兩個設備之間是同步傳輸的。

d.數據交換(Data Exchanges)

????????SPI 設備間的數據傳輸之所以又被稱為數據交換, 是因為 SPI 協議規定一個 SPI 設備不能在數據通信過程中僅僅只充當一個 “發送者(Transmitter)” 或者 “接收者(Receiver)”. 在每個 Clock 周期內, SPI 設備都會發送并接收一個 bit 大小的數據, 相當于該設備有一個 bit 大小的數據被交換了。

? ????????一個 Slave 設備要想能夠接收到 Master 發過來的控制信號, 必須在此之前能夠被 Master 設備進行訪問 (Access)。所以, Master 設備必須首先通過 SS/CS pin 對 Slave 設備進行片選, 把想要訪問的 Slave 設備選上。

????????? 在數據傳輸的過程中, 每次接收到的數據必須在下一次數據傳輸之前被采樣.。如果之前接收到的數據沒有被讀取, 那么這些已經接收完成的數據將有可能會被丟棄, 導致 SPI 物理模塊最終失效。因此, 在程序中一般都會在 SPI 傳輸完數據后, 去讀取 SPI 設備里的數據, 即使這些數據(Dummy Data)在我們的程序里是無用的。

二.SPI總線設備驅動模型

1.SPI主機驅動

????????SPI 主機驅動就是 SOC 的 SPI 控制器驅動,類似 I2C 驅動里面的適配器驅動。 Linux 內核使用 spi_master 表示 SPI 主機驅動, spi_master 是個結構體,定義在 include/linux/spi/spi.h 文件
中,部分代碼如下:

struct spi_master {struct device dev;struct list_head list;
.....int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
.....int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
}

??????? 上述代碼中,transfer 函數,和 i2c_algorithm 中的 master_xfer 函數一樣,控制器數據傳輸函數。transfer_one_message 函數,也用于 SPI 數據發送,用于發送一個 spi_message,SPI 的數據會打包成 spi_message,然后以隊列方式發送出去。

????????SPI 主機端最終會通過 transfer 函數與 SPI 設備進行通信,因此對于 SPI 主機控制器的驅
動編寫者而言 transfer 函數是需要實現的,因為不同的 SOC 其 SPI 控制器不同,寄存器都不一
樣。和 I2C 適配器驅動一樣,SPI 主機驅動一般都是 SOC 廠商去編寫的。

????????SPI 主機驅動的核心就是申請 spi_master,然后初始化 spi_master,最后向 Linux 內核注冊
spi_master。

??????? spi_master的申請與釋放:

????????spi_alloc_master函數用于申請spi_master,函數定義如下所示:

struct spi_master *spi_alloc_master(struct device *dev,unsigned size)

??????? dev:設備,一般是 platform_device 中的 dev 成員變量。

??????? size:私有數據大小,可以通過 spi_master_get_devdata 函數獲取到這些私有數據。

?????????spi_master 的釋放通過 spi_master_put 函數來完成,當我們刪除一個 SPI 主機驅動的時候就需要釋放掉前面申請的 spi_master,spi_master_put 函數原型如下:

void spi_master_put(struct spi_master *master)

????????master:要釋放的 spi_master。

????????spi_master的注冊與注銷:

????????當 spi_master 初始化完成以后就需要將其注冊到 Linux 內核,spi_master 注冊函數為
spi_register_master,函數原型如下:

int spi_register_master(struct spi_master *master)

????????master:要注冊的 spi_master。

????????如果要注銷 spi_master 的話可以使用 spi_unregister_master 函數,此函數原型為:

void spi_unregister_master(struct spi_master *master)

????????master:要注銷的 spi_master。

2.SPI設備驅動

????????spi 設備驅動和 i2c 設備驅動也很類似,Linux 內核使用 spi_driver 結構體來表示 spi 設備驅動 , 我 們 在 編 寫 SPI 設 備 驅 動 的 時 候 需 要 實 現 spi_driver 。 spi_driver 結 構 體 定 義 在include/linux/spi/spi.h 文件中,結構體內容如下:

    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;  }; 

????????可以看出,spi_driver 和 i2c_driver、platform_driver 基本一樣,當 SPI 設備和驅動匹配成功
以后 probe 函數就會執行。spi_driver 初始化完成以后需要向 Linux 內核注冊,spi_driver 注冊函數為spi_register_driver,函數原型如下:

int spi_register_driver(struct spi_driver *sdrv)

????????sdrv:要注冊的 spi_driver。

????????注銷 SPI 設備驅動以后也需要注銷掉前面注冊的 spi_driver,使用 spi_unregister_driver 函
數完成 spi_driver 的注銷,函數原型如下:

void spi_unregister_driver(struct spi_driver *sdrv)

????????sdrv:要注銷的 spi_driver。

??????? driver是為device服務的,spi_driver注冊時會掃描SPI bus上的設備,進行驅動和設備的綁定,probe函數用于驅動和設備匹配時被調用。SPI的通信是通過消息隊列機制,而不是像I2C那樣通過與從設備進行對話的方式。

??????? spi_device結構體的內容如下:

    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];   }; 

????????spi_driver 注冊示例程序如下所示:

/* probe 函數 */
static int xxx_probe(struct spi_device *spi){/* 具體函數內容 */return 0;
}
/* remove 函數 */
static int xxx_remove(struct spi_device *spi){/* 具體函數內容 */return 0;
}
/* 傳統匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {{"xxx", 0},{}
};
/* 設備樹匹配列表 */
static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx" },{ /* Sentinel */ }
};
/* SPI 驅動結構體 */
static struct spi_driver xxx_driver = {.probe = xxx_probe,.remove = xxx_remove,.driver = {.owner = THIS_MODULE,.name = "xxx",.of_match_table = xxx_of_match,},.id_table = xxx_id,
};
/* 驅動入口函數 */
static int __init xxx_init(void){return spi_register_driver(&xxx_driver);
}
/* 驅動出口函數 */
static void __exit xxx_exit(void){spi_unregister_driver(&xxx_driver);
}module_init(xxx_init);
module_exit(xxx_exit);

3.SPI 設備和驅動匹配過程

????????SPI 設備和驅動的匹配過程是由 SPI 總線來完成的,SPI 總線為 spi_bus_type,定義在 drivers/spi/spi.c 文件中,內容如下所示:

struct bus_type spi_bus_type = {.name = "spi",.dev_groups = spi_dev_groups,.match = spi_match_device,.uevent = spi_uevent,
};

????????SPI 設備和驅動的匹配函數為 spi_match_device,其函數內容如下:

static int spi_match_device(struct device *dev, struct device_driver *drv) {const struct spi_device *spi = to_spi_device(dev);const struct spi_driver *sdrv = to_spi_driver(drv);/* 用于完成設備樹設備和驅動匹配 */if (of_driver_match_device(dev, drv))return 1;/* 用于ACPI形式的匹配 */if (acpi_driver_match_device(dev, drv))return 1;/* 用于傳統無設備樹設備和驅動匹配 */if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi);/* 比較modalias成員變量和name成員變量是否相等 */return strcmp(spi->modalias, drv->name) == 0;
}

4.SPI 設備數據收發處理

????????當向 Linux 內核注冊成功 spi_driver 后就可以使用 SPI 核心層提供的 API 函數來對設備進行讀寫操作了。首先是 spi_transfer 結構體,此結構體用于描述 SPI 傳輸信息,結構體內容如下:

struct spi_transfer {const void *tx_buf;	//保存著要發送的數據void *rx_buf;		//用于保存接收到的數據unsigned len;		//要進行傳輸的數據長度dma_addr_t tx_dma;	dma_addr_t rx_dma;	struct sg_table tx_sg;struct sg_table rx_sg;unsigned cs_change:1;unsigned tx_nbits:3;unsigned rx_nbits:3;#define SPI_NBITS_SINGLE 0x01 	/* 1bit transfer */#define SPI_NBITS_DUAL 0x02 	/* 2bits transfer */#define SPI_NBITS_QUAD 0x04 	/* 4bits transfer */u8 bits_per_word;u16 delay_usecs;u32 speed_hz;struct list_head transfer_list;
};

????????spi_transfer 需要組織成 spi_message, spi_message 也是一個結構體,內容如下:

struct spi_message {struct list_head transfers;	struct spi_device *spi;	unsigned is_dma_mapped:1;....../* completion is reported through a callback */void (*complete)(void *context);void *context;unsigned frame_length;unsigned actual_length;int status;	struct list_head queue;void *state;
};

????????在使用spi_message之前需要對其進行初始化:

void spi_message_init(struct spi_message *m)
//m:要初始化的 spi_message

????????spi_message 初始化完成以后需要將 spi_transfer 添加到 spi_message 隊列中:

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
//t:要添加到隊列中的 spi_transfer
//m:spi_transfer 要加入的 spi_message

????????spi_message 準備好后既可以進行數據傳輸,數據傳輸分為同步傳輸和異步傳輸:

/***同步傳輸會阻塞的等待 SPI 數據傳輸完成***/
int spi_sync(struct spi_device *spi, struct spi_message *message)
//spi:要進行數據傳輸的 spi_device
//message:要傳輸的 spi_message
/***異步傳輸不會阻塞等待,需設置spi_message中的
complete回調函數,當異步傳輸完成后此函數就會被調用***/
int spi_async(struct spi_device *spi, struct spi_message *message)
//spi:要進行數據傳輸的 spi_device
//message:要傳輸的 spi_message

????????SPI 數據傳輸示例代碼如下:

/********** SPI 多字節發送 **********/
static int spi_send(struct spi_device *spi, u8 *buf, int len){int ret;struct spi_message m;struct spi_transfer t = {	  //1. 定義一個spi_transfer結構體變量,并設置成員變量.tx_buf = buf,.len = len,};spi_message_init(&m); 		  //2. 初始化 spi_messagespi_message_add_tail(t, &m);  //3. 將 spi_transfer 添加到 spi_message 隊列ret = spi_sync(spi, &m); 	  //4. 同步傳輸return ret;
}
/********** SPI 多字節接收 **********/
static int spi_receive(struct spi_device *spi, u8 *buf, int len){int ret;struct spi_message m;struct spi_transfer t = {	  //1. 定義一個spi_transfer結構體變量,并設置成員變量.rx_buf = buf,.len = len,};spi_message_init(&m); 		  //2. 初始化 spi_messagespi_message_add_tail(t, &m);  //3. 將 spi_transfer 添加到 spi_message 隊列ret = spi_sync(spi, &m);	  //4. 同步傳輸return ret;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92219.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92219.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92219.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

用Dify構建氣象智能體:從0到1搭建AI工作流實戰指南

作為一名Agent產品經理,我最近在負責氣象智能體的建設項目。傳統氣象服務面臨三大痛點:數據孤島嚴重(氣象局API、衛星云圖、地面觀測站等多源數據格式不一)、響應鏈路長(從數據采集到預警發布需人工介入多個環節)、交互體驗單一(用戶只能被動接收標準化預警,無法個性化…

Android NDK ffmpeg 音視頻開發實戰

文章目錄接入FFmpeg1.下載FFmpeg 源碼2.編譯FFmpeg.so庫異常處理3.自定義FFmpeg交互so庫創建4.配置CMakeLists.txt5.CMakeLists.txt 環境配置6.Native與Java層調用解碼器準備接入FFmpeg 1.下載FFmpeg 源碼 FFmpeg官網地址 2.編譯FFmpeg.so庫 移動 FFmpeg 源碼文件夾至 Andr…

使用 go-redis-entraid 實現 Entra ID 無密鑰認證

1、依賴與安裝 步驟命令說明安裝(或升級) go-redis v9.9go get github.com/redis/go-redis/v9latestentraid 必須 ≥ 9.9.0安裝 go-redis-entraidgo get github.com/redis/go-redis-entraid自動拉取 transit 依賴 2、認證方式一覽 方式說明創建 Stream…

window上docker安裝RabbitMQ

1、要進http://localhost:15672管理頁面需要安裝management版本2、搜索鏡像并pull3、啟動鏡像時將端口映射出來4、啟動成功,點擊可查看日志詳情,瀏覽器訪問5、直接使用guest/guest登錄會報錯User can only log in via localhost解決辦法有兩個&#xff1…

異世界歷險之數據結構世界(排序(插入,希爾,堆排))

前言 介紹 插入排序 基本知識: 直接插入排序是一種簡單的插入排序法,其基本思想是: 把待排序的記錄按其關鍵碼值的大小逐個插入到一個已經排好序的有序序列中,直到所有的記錄插入完為止,得到一個新的有序序列 直接插入…

oracle 數據庫中,將幾張表的數據按指定日期范圍實時同步至同一個數據庫的備份表中。

以下是一個Oracle數據庫中實現表數據按指定日期范圍實時同步至備份表的解決方案。這個方案使用存儲過程和觸發器組合實現: 1. 創建備份表結構 首先需要為每張需要備份的表創建對應的備份表,結構與原表相同: -- 為原表創建備份表(示…

電腦網絡連接正常,微信、QQ能正常使用,但無法訪問網頁?DNS問題的解決方案和背后原理。

文章目錄1. 問題背景2. 解決方案2.1 手動刷新DNS2.1.1 Windows版本2.1.2 Mac版本2.2 手動設置DNS服務器2.2.1 Windows版2.2.2 Mac版2.3 其他解決方案3. DNS是什么?3.1 詳細解釋DNS3.1.1 A distributed, hierarchical database(一個分布式和分層數據庫結構…

【HTML】圖片比例和外部div比例不一致,最大程度占滿

圖片比例和外部div比例不一致&#xff0c;最大程度占滿&#xff0c;并且圖片比例不變 其中1.jpg,2.jpg,1.html在同一目錄 |-----|- 1.jpg|- 2.jpg|- 1.html1.jpg2.jpg<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /&g…

如何使用python網絡爬蟲批量獲取公共資源數據技術

如何快速批量地獲取海量公共資源數據決定了科研的效率。Python網絡爬蟲是快速批量獲取網絡數據的重要手段&#xff0c;它按照發送請求、獲得頁面、解析頁面、下載內容、儲存內容等流程&#xff1f; 一&#xff1a;Python軟件的安裝及入門1 Python軟件安裝及入門1)Anaconda軟件安…

Kiro vs Cursor: AI IDE 終極對比指南

概述 隨著生成式 AI 革命性地改變了我們編寫代碼的方式&#xff0c;新一代 AI 驅動的集成開發環境 (IDE) 正在崛起。Kiro 和 Cursor 代表了這一運動的前沿&#xff0c;但它們采用了截然不同的方法。 核心理念對比 特性AWS KiroCursor核心理念結構化開發流程 (Spec-driven)對…

Python獲取網頁亂碼問題終極解決方案 | Python爬蟲編碼處理指南

在Python網絡爬蟲開發中&#xff0c;亂碼是最常見的問題之一。本文將深入探討亂碼產生的原因&#xff0c;并提供多種有效的解決方案&#xff0c;幫助您徹底解決Python獲取網頁內容時的亂碼問題。常見網頁編碼格式編碼類型使用場景Python解碼方式UTF-8現代網站標準編碼.decode(u…

Android MTK平臺預置多張靜態壁紙

執行 adb shell pm list package -f wallpaper 命令&#xff0c;查看壁紙應用路徑&#xff1a; /product/app/MtkWallpaperPicker/MtkWallpaperPicker.apkcom.android.wallpaperpicker 結果中帶 Mtk 就可確定MTK有對應用進行重構。其源碼路徑在 vendor/mediatek/proprietary/…

基于Django的個人博客系統開發(開題報告)

畢業論文(設計)開題報告論文(設計)題目 基于Django的個人博客系統開發 1.選題目的和意義 隨著云服務器的普及化以及編程培訓機構大量涌現,學習網站開發技術以及編程技術,通過租用個人云服務器部署代碼,構建個人博客網站,創建學習文檔,記錄學習過程,與他人交流技術學…

C++ 分配內存釋放內存

C 分配內存釋放內存一、new、delete、malloc和free最簡單的分配內存自定義對象分配和釋放內存二、new、delete與虛析構的問題三、一維、二維、多維數值創建和釋放一維二維多維四、new的缺點以及連續內存的優點一、new、delete、malloc和free 最簡單的分配內存 int* p_m (int*…

奧比中光深度相機開發

一、開發環境準備 1.1 硬件要求 奧比中光深度相機&#xff08;如Astra Pro、Gemini等&#xff09;USB 3.0接口&#xff08;確保數據傳輸穩定&#xff09;支持OpenGL的顯卡&#xff08;可選&#xff0c;用于點云可視化&#xff09; 1.2 軟件環境 SDK安裝&#xff1a; 從奧比…

標題 “Python 網絡爬蟲 —— selenium庫驅動瀏覽器

一、Selenium 庫核心認知 Selenium 庫是 Web 應用程序測試與自動化操作的利器 &#xff0c;能驅動瀏覽器&#xff08;如 Edge、Firefox 等&#xff09;執行點擊、輸入、打開、驗證等操作 。與 Requests 庫差異顯著&#xff1a;Requests 庫僅能獲取網頁原始代碼&#xff0c;而 …

從實踐出發--探究C/C++空類的大小,真的是1嗎?

文章目錄測試代碼VS2022正常運行編譯失敗GCC總結Author: NemaleSu Data: 2025/07/21 測試環境&#xff1a; Win11&#xff1a;VS2022Ubuntu22.04&#xff1a;gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 相信眾多cpper聽過太多書籍、視頻、文檔、博客等資料&#xff0c;說C/C…

數據結構自學Day11-- 排序算法

一、排序算法的概念排序&#xff08;Sorting&#xff09;是指&#xff1a;將一組“無序”的數據&#xff0c;按照某種“順序規則”排列成“有序”的過程。1、按排序順序分類&#xff1a;升序&#xff1a;從小到大排列&#xff0c;如 1, 3, 5, 7, 9降序&#xff1a;從大到小排列…

電子元器件—三極管(一篇文章搞懂電路中的三極管)(筆記)(面試考試必備知識點)

三極管的定義及工作原理1. 定義三極管&#xff08;Transistor&#xff09;是一種具有三層半導體材料&#xff08;P-N-P 或 N-P-N&#xff09;構成的半導體器件&#xff0c;用于信號放大、開關控制和信號調制等應用。三極管有三個引腳&#xff1a;發射極&#xff08;Emitter&…

數據結構之克魯斯卡爾算法

前言&#xff1a;和Prim算法一樣&#xff0c;Kruskal 算法也是用來生成最小生成樹的&#xff0c;這篇文章來學習一下Kruskal算法的實現 一、實現流程 初始化的時候&#xff0c;將所有的邊用一個數組存儲&#xff0c;并且按權值從小到大進行排序&#xff0c;每次選一個權值最小的…