在嵌入式系統開發中,高效的硬體資源管理至關重要。Linux 核心提供的 Regmap 和 IIO 框架為驅動程式開發者提供了強大的工具。Regmap API 抽象了底層暫存器存取細節,簡化了驅動程式碼,而 IIO 框架則提供了一套標準化的介面,用於管理各種感測器和資料採集裝置。本文將以 ADXL345 三軸加速度計的驅動程式為例,示範如何結合使用這兩種框架,實作高效的裝置控制和資料採集。透過 SPI 介面與 ADXL345 通訊,驅動程式利用 Regmap API 讀寫晶片的暫存器,並透過 IIO 框架將加速度計的資料暴露給使用者空間。此外,文章還將探討如何使用 sysfs 介面控制裝置,以及如何使用觸發器和緩衝區機制實作資料採集。
使用Regmap API於裝置驅動程式中的深入解析
在Linux裝置驅動程式開發中,Regmap API提供了一種高效、簡潔的方式來管理硬體暫存器。本文將探討如何在裝置驅動程式中使用Regmap API,以ADXL345三軸加速器為例,詳細介紹其實作過程及關鍵技術。
Regmap API的基本概念與優勢
Regmap API是Linux核心提供的一種用於簡化暫存器存取的介面。它抽象了底層的硬體存取細節,使驅動程式開發者能夠專注於裝置邏輯的實作。Regmap API的主要優勢包括:
- 簡化暫存器存取:Regmap提供了一致的介面來讀寫暫存器,無需直接操作硬體。
- 提高程式碼可移植性:透過抽象硬體存取,Regmap使得驅動程式更容易在不同硬體平台上移植。
- 增強錯誤處理:Regmap內建錯誤處理機制,有助於提高驅動程式的穩定性。
ADXL345驅動程式實作詳解
初始化與註冊
首先,驅動程式需要初始化並註冊到Linux核心。以下程式碼展示瞭如何使用module_spi_driver巨集註冊SPI驅動程式:
static struct spi_driver adxl345_driver = {
.driver = {
.name = "adxl345",
.owner = THIS_MODULE,
.of_match_table = adxl345_dt_ids,
},
.probe = adxl345_spi_probe,
.remove = adxl345_spi_remove,
.id_table = adxl345_id,
};
module_spi_driver(adxl345_driver);
探測函式實作
當裝置被偵測到時,adxl345_spi_probe函式會被呼叫,用於初始化裝置並註冊到IIO子系統。
static int adxl345_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
const struct spi_device_id *id = spi_get_device_id(spi);
regmap = devm_regmap_init_spi(spi, &adxl345_spi_regmap_config);
if (IS_ERR(regmap)) {
dev_err(&spi->dev, "Error initializing spi regmap: %ld\n", PTR_ERR(regmap));
return PTR_ERR(regmap);
}
return adxl345_core_probe(&spi->dev, regmap, id->name);
}
#### 內容解密:
devm_regmap_init_spi函式用於初始化SPI介面的Regmap組態。adxl345_core_probe函式負責核心裝置的初始化和註冊。
核心裝置初始化與註冊
adxl345_core_probe函式是裝置初始化的核心部分,負責設定裝置、組態觸發器和註冊到IIO子系統。
int adxl345_core_probe(struct device *dev, struct regmap *regmap, const char *name)
{
// 初始化裝置並註冊到IIO子系統
// ...
ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
dev_err(dev, "iio_device_register failed: %d\n", ret);
goto error_standby;
}
return 0;
error_standby:
dev_info(dev, "set standby mode due to an error\n");
regmap_write(data->regmap, POWER_CTL, PCTL_STANDBY);
return ret;
}
#### 內容解密:
devm_iio_device_register函式用於註冊IIO裝置。- 在錯誤處理路徑中,裝置會被設定為待機模式,以節省能源。
裝置移除處理
當裝置被移除時,adxl345_core_remove函式會被呼叫,用於清理資源並將裝置設定為待機模式。
int adxl345_core_remove(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct adxl345_data *data = iio_priv(indio_dev);
dev_info(data->dev, "my_remove() function is called.\n");
return regmap_write(data->regmap, POWER_CTL, PCTL_STANDBY);
}
#### 內容解密:
- 在移除裝置時,驅動程式會將裝置設定為待機模式。
實際應用與測試
在實際應用中,可以透過sysfs介面來讀取ADXL345的資料。以下展示瞭如何啟用觸發器、設定掃描元素並讀取資料。
# 啟用觸發器
echo 1 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
# 設定掃描元素
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_x_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_y_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_z_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_timestamp_en
# 讀取資料
echo 1 > /sys/bus/iio/devices/trigger0/trigger_now
hexdump -v -e '16/1 "%02x " "\n"' < /dev/iio\:device0
#### 內容解密:
- 透過sysfs介面,可以靈活地控制IIO裝置的行為。
hexdump命令用於讀取並顯示從裝置取得的原始資料。
使用 Regmap API 在裝置驅動程式中的實踐
在 Linux 核心中,Regmap API 提供了一種抽象化的介面,用於簡化裝置驅動程式對硬體暫存器的存取操作。透過 Regmap API,驅動程式開發者可以更輕鬆地管理裝置的暫存器存取,從而提高程式碼的可讀性和可維護性。
IIO 裝置驅動程式範例:ADXL345 加速度計
本範例根據 ADXL345 加速度計裝置,展示如何使用 IIO 框架和 Regmap API 開發裝置驅動程式。ADXL345 是一款高精確度、三軸加速度計,廣泛應用於移動裝置和工業控制系統。
載入和移除模組
首先,載入編譯好的驅動程式模組 adxl345_rpi3_iio.ko:
root@raspberrypi:/home/pi# insmod adxl345_rpi3_iio.ko
移除模組的指令如下:
root@raspberrypi:/home/pi# rmmod adxl345_rpi3_iio.ko
adxl345 spi0.0: my_remove() function is called.
當移除模組時,驅動程式的 my_remove() 函式會被呼叫,以執行必要的清理操作。
設定觸發器和緩衝區
本範例使用 iio_generic_buffer 應用程式來設定緩衝區長度和取樣次數,並啟用掃描元素。以下是相關的操作步驟:
root@raspberrypi:/home/pi# echo 1 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
root@raspberrypi:/home/pi# echo sysfstrig1 > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
root@raspberrypi:/home/pi# ./iio_generic_buffer --device-num 0 -T 0 -a -l 10 -c 5 &
上述指令建立了一個 sysfs 觸發器,並將其附加到 ADXL345 裝置上。然後,使用 iio_generic_buffer 應用程式設定緩衝區引數並啟用掃描元素。
觸發資料採集
執行以下指令觸發資料採集:
root@raspberrypi:/home/pi# echo 1 > /sys/bus/iio/devices/trigger0/trigger_now
每次執行上述指令,ADXL345 裝置就會進行一次資料採集,並將結果輸出到終端機上。
使用 hrtimer 觸發器
本範例還展示瞭如何使用 hrtimer 觸發器來進行定時資料採集。首先,建立 hrtimer 觸發器:
root@raspberrypi:/home/pi# mkdir /config/iio/triggers/hrtimer/trigger0
然後,設定取樣頻率:
root@raspberrypi:/home/pi# echo 50 > /sys/bus/iio/devices/trigger0/sampling_frequency
將 hrtimer 觸發器附加到 ADXL345 裝置上:
root@raspberrypi:/home/pi# echo trigger0 > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
使用 iio_generic_buffer 應用程式進行資料採集:
root@raspberrypi:/home/pi# ./iio_generic_buffer --device-num 0 -T 0 -a -l 10 -c 10 &
事件監測
最後,本範例展示瞭如何使用 iio_event_monitor 應用程式來監測 ADXL345 裝置的事件。執行以下指令:
root@raspberrypi:/home/pi# ./iio_event_monitor /dev/iio\:device0
當 ADXL345 裝置發生單次點選事件時,iio_event_monitor 應用程式會輸出相關的事件資訊。
程式碼解析
Regmap API 的使用
在 ADXL345 裝置驅動程式中,Regmap API 被用來簡化對裝置暫存器的存取操作。以下是相關的程式碼片段:
// 初始化 Regmap
regmap = devm_regmap_init_spi(dev, &adxl345_regmap_config);
if (IS_ERR(regmap)) {
dev_err(dev, "Failed to initialize Regmap\n");
return PTR_ERR(regmap);
}
// 使用 Regmap 讀寫暫存器
regmap_write(regmap, ADXL345_REG_CTL, 0x00);
regmap_read(regmap, ADXL345_REG_XLSB, &data);
在上述程式碼中,devm_regmap_init_spi() 函式用於初始化 Regmap,而 regmap_write() 和 regmap_read() 函式則用於對 ADXL345 裝置的暫存器進行讀寫操作。
IIO 框架的使用
本範例使用 IIO 框架來開發 ADXL345 裝置驅動程式。以下是相關的程式碼片段:
// 初始化 IIO 裝置
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev) {
dev_err(dev, "Failed to allocate IIO device\n");
return -ENOMEM;
}
// 設定 IIO 裝置的屬性
indio_dev->name = "adxl345";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &adxl345_info;
// 註冊 IIO 裝置
ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
dev_err(dev, "Failed to register IIO device\n");
return ret;
}
在上述程式碼中,devm_iio_device_alloc() 函式用於分配 IIO 裝置結構,而 devm_iio_device_register() 函式則用於註冊 IIO 裝置。
USB裝置驅動程式
USB(Universal Serial Bus)被設計為一種低成本、串列介面的解決方案,由USB主機提供匯流排電源,以支援廣泛的外設裝置。最初的USB匯流排速度分為低速1.5 Mbps、全速12 Mbps和高速480 Mbps。隨著USB 3.0規範的出現,超高速被定義為4.8 Gbps。最大資料吞吐量,即線路速率減去額外負擔,分別約為低速384 Kbps、全速9.728 Mbps和高速425.984 Mbps。需要注意的是,這是最大資料吞吐量,可能會受到多種因素的影響,包括軟體處理、同一匯流排上其他USB頻寬利用率等。
USB的優勢
USB的最大優勢之一是它支援動態連線和移除,這是一種被稱為「即插即用」的介面型別。當USB外設裝置連線後,主機和裝置之間會自動進行通訊,以使裝置狀態從連線狀態推進到供電、預設、定址,最終到組態狀態。此外,所有裝置都必須符合暫停狀態,在此狀態下必須滿足非常低的匯流排功耗規範。暫停狀態下的省電是USB的另一個好處。
USB 2.0規範
在本章中,我們將重點關注USB 2.0規範,其中包括低速、全速和高速裝置規範。符合USB 2.0規範的外設裝置不一定是高速裝置,但宣稱符合USB 2.0規範的集線器必須具備高速能力。USB 2.0裝置可以是高速、全速或低速裝置。
USB 2.0匯流排拓撲
USB裝置分為集線器(提供額外的下游連線點)和功能裝置(為系統提供某種功能)。USB實體互連是一種分層星型拓撲結構(如圖所示)。從主機和「根集線器」開始,位於第一層,最多可支援七層,總共最多127個裝置。第二層到第六層可以有一個或多個集線器裝置,以支援與下一層的通訊。複合裝置(同時具有集線器和外設裝置功能的裝置)不能佔據第七層。
USB實體互連
所有USB 2.0(最高高速)裝置的實體互連都是透過簡單的4線介面實作的,具有雙向差分資料(D+和D-)、電源(VBUS)和接地。VBUS電源的名義電壓為+5V。「A型」聯結器和插頭用於所有主機埠以及導向下游的集線器埠。「B型」聯結器和插頭用於所有外設裝置以及導向上游的集線器埠。主機、集線器和裝置之間的電纜連線最長可達5米或約16英尺。在最多7層的情況下,電纜連線總長度可達30米或約98英尺。
USB匯流排列舉和裝置佈局
USB是一種由主機控制的輪詢匯流排,所有事務都由USB主機發起。匯流排上不會發生任何事情,除非主機首先發起;USB裝置無法發起事務,而是由主機輪詢每個裝置,請求資料或傳送資料。所有連線和移除的USB裝置都透過一個名為「匯流排列舉」的過程來識別。
裝置識別
當新裝置連線到匯流排時,主機透過信令機制使用D+/D-資料對來識別其速度(低速、全速或高速)。當新USB裝置透過集線器連線到匯流排時,裝置列舉過程開始。每個集線器提供一個IN端點,用於通知主機新連線的裝置。主機不斷輪詢此端點,以接收來自集線器的裝置連線和移除事件。一旦新裝置連線並且集線器通知主機該事件,主機的USB匯流排驅動程式會啟用該裝置,並開始透過預設控制管道向裝置的端點零請求資訊。
USB描述符
資訊以描述符的形式請求。USB描述符是由裝置提供的資料結構,用於描述其所有屬性。這包括產品/供應商ID、任何裝置類別從屬關係以及描述產品和供應商的字串。此外,還提供了有關所有可用端點的資訊。在主機讀取裝置的所有必要資訊後,它會嘗試找到匹配的裝置驅動程式。
端點
每個USB裝置都由多個獨立的端點組成,這些端點為主機和裝置之間的通訊流提供了終點。端點可以分為控制端點和資料端點。每個USB裝置必須至少提供一個控制端點,地址為0,稱為預設端點或Endpoint0。該端點是雙向的,即主機可以在一次傳輸中向端點傳送資料並接收資料。控制傳輸的目的在於使主機能夠取得裝置資訊、組態裝置或執行特定於裝置的控制操作。
端點地址編碼
端點地址是端點號和端點方向的組合,例如EP 1 IN、EP 1 OUT和EP 3 IN。端點地址被編碼在單個位元組中,其中方向是最高位元(1=IN,0=OUT),而編號是低四位。例如:
- EP 1 IN = 0x81
- EP 1 OUT = 0x01
程式碼範例與解析
// 初始化USB裝置
void usb_device_init() {
// 設定預設端點
usb_set_endpoint(0, USB_EP_TYPE_CONTROL, USB_EP_DIR_INOUT);
// 使能USB中斷
usb_enable_interrupts();
}
內容解密:
usb_device_init函式負責初始化USB裝置。usb_set_endpoint用於設定預設端點(Endpoint0),型別為控制端點,方向為雙向(INOUT)。usb_enable_interrupts函式使能USB中斷,以便處理USB相關事件。- 這段程式碼展示瞭如何初始化USB裝置並設定必要的端點,以便與主機進行通訊。
圖表解析:
- 主機發起控制傳輸給集線器。
- 集線器再將控制傳輸轉發給裝置。
- 裝置透過端點與主機進行資料傳輸。
- 資料傳輸可以是IN(輸入到主機)或OUT(從主機輸出)。