返回文章列表

Linux裝置驅動Regmap與IIO框架應用

本文探討在 Linux 驅動程式中使用 Regmap 和 IIO 框架的技巧,以 ADXL345 三軸加速度計為例,詳細說明如何透過 Regmap 簡化暫存器存取,並結合 IIO 框架實作資料採集和事件監控。文章涵蓋驅動程式初始化、探測、移除,以及 sysfs

Linux 驅動程式 嵌入式系統

在嵌入式系統開發中,高效的硬體資源管理至關重要。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();
}

內容解密:

  1. usb_device_init函式負責初始化USB裝置。
  2. usb_set_endpoint用於設定預設端點(Endpoint0),型別為控制端點,方向為雙向(INOUT)。
  3. usb_enable_interrupts函式使能USB中斷,以便處理USB相關事件。
  4. 這段程式碼展示瞭如何初始化USB裝置並設定必要的端點,以便與主機進行通訊。
圖表解析:
  • 主機發起控制傳輸給集線器。
  • 集線器再將控制傳輸轉發給裝置。
  • 裝置透過端點與主機進行資料傳輸。
  • 資料傳輸可以是IN(輸入到主機)或OUT(從主機輸出)。