Linux 核心中的 Industrial I/O (IIO) 子系統提供標準化介面,方便處理加速度計等感測器。本文說明如何開發 Nunchuk 加速度計的 IIO 驅動程式,並探討其與消費者驅動程式的互動。IIO 驅動程式核心結構 nunchuk_accel 包含 I2C 通訊所需資訊,透過 nunchuk_read_registers 讀取加速度計資料,並在 nunchuk_accel_probe 中註冊 IIO 裝置。消費者驅動程式則利用 Input 子系統,透過 iio_read_channel_raw 函式讀取 IIO 通道資料,並在 nunchuk_poll 中將資料轉換為 Input 事件,最後透過 input_report_abs 函式回報給 Input 子系統,實作 Nunchuk 加速度計資料的讀取和應用。

Nunchuk 加速度計驅動程式開發與 IIO 子系統應用

在 Linux 核心中,Industrial I/O (IIO) 子系統提供了一個標準化的介面,用於處理各種感測器裝置,如加速度計、陀螺儀和磁力計等。本文將介紹如何開發一個 Nunchuk 加速度計的 IIO 驅動程式,並探討其與消費者驅動程式之間的互動。

Nunchuk 加速度計 IIO 驅動程式

首先,我們來看看 Nunchuk 加速度計的 IIO 驅動程式實作。此驅動程式負責與 Nunchuk 裝置進行通訊,並提供加速度資料給上層應用程式。

驅動程式結構

驅動程式的核心結構是 struct nunchuk_accel,它包含了與 I2C 裝置通訊所需的客戶端指標。

struct nunchuk_accel *nunchuk_accel = iio_priv(indio_dev);
struct i2c_client *client = nunchuk_accel->client;

資料讀取

驅動程式使用 nunchuk_read_registers 函式從 Nunchuk 裝置讀取資料。讀取到的資料會被轉換成合適的格式,並儲存在 val 變數中。

if (nunchuk_read_registers(client, buf, ARRAY_SIZE(buf)) < 0) {
    dev_info(&client->dev, "Error reading the nunchuk registers.\n");
    return -EIO;
}

switch (chan->channel2) {
case IIO_MOD_X:
    *val = (buf[2] << 2) | ((buf[5] >> 2) & 0x3);
    break;
case IIO_MOD_Y:
    *val = (buf[3] << 2) | ((buf[5] >> 4) & 0x3);
    break;
case IIO_MOD_Z:
    *val = (buf[4] << 2) | ((buf[5] >> 6) & 0x3);
    break;
default:
    return -EINVAL;
}
return IIO_VAL_INT;

驅動程式註冊

nunchuk_accel_probe 函式中,驅動程式會註冊 IIO 裝置並設定相關的回呼函式。

indio_dev->info = &nunchuk_info;
indio_dev->channels = nunchuk_channels;
indio_dev->num_channels = 3;
indio_dev->modes = INDIO_DIRECT_MODE;

ret = devm_iio_device_register(&client->dev, indio_dev);
if (ret)
    return ret;

Nunchuk 消費者驅動程式

Nunchuk 消費者驅動程式是一個平台驅動程式,它使用 Input 子系統來消費 Nunchuk 加速度計提供的 IIO 通道。

私有結構

消費者驅動程式定義了一個私有結構 struct nunchuk_dev,其中包含了指向 IIO 通道的指標。

struct nunchuk_dev {
    struct input_polled_dev *polled_input;
    struct iio_channel *accel_x, *accel_y, *accel_z;
};

IIO 通道存取

nunchuk_probe 函式中,消費者驅動程式會取得 Nunchuk 加速度計提供的 IIO 通道的指標。

nunchuk->accel_x = devm_iio_channel_get(dev, "accel_x");
nunchuk->accel_y = devm_iio_channel_get(dev, "accel_y");
nunchuk->accel_z = devm_iio_channel_get(dev, "accel_z");

輪詢處理

nunchuk_poll 函式中,消費者驅動程式會讀取 IIO 通道的原始值,並將其轉換成 Input 事件報告給 Input 子系統。

static void nunchuk_poll(struct input_polled_dev *polled_input)
{
    int accel_x, accel_y, accel_z;
    struct nunchuk_dev *nunchuk;

    nunchuk = polled_input->private;

    iio_read_channel_raw(nunchuk->accel_x, &accel_x);
    input_report_abs(polled_input->input, ABS_RX, accel_x);

    iio_read_channel_raw(nunchuk->accel_y, &accel_y);
    input_report_abs(polled_input->input, ABS_RY, accel_y);

    iio_read_channel_raw(nunchuk->accel_z, &accel_z);
    input_report_abs(polled_input->input, ABS_RZ, accel_z);

    input_sync(polled_input->input);
}

程式碼實作詳解

  1. nunchuk_accel_read_raw 函式實作:此函式負責讀取 Nunchuk 加速度計的原始資料。透過 I2C 通訊,從裝置讀取資料並進行適當的轉換後,將結果儲存在 val 變數中。

    • 解讀:首先檢查是否成功從 Nunchuk 讀取資料,若讀取失敗,則傳回錯誤碼 -EIO。接著,根據通道的不同(X、Y 或 Z 軸),對讀取到的資料進行位元運算,以獲得正確的加速度值。
  2. IIO 裝置註冊流程:在 nunchuk_accel_probe 函式中,進行 IIO 裝置的註冊。這包括了設定 indio_dev 結構中的相關欄位,如 infochannelsmodes

    • 解讀:首先分配並初始化 iio_dev 結構,接著設定其成員變數,包括裝置名稱、父裝置、IIO 資訊和通道組態等。最後,透過 devm_iio_device_register 將 IIO 裝置註冊到系統中。
  3. nunchuk_poll 函式實作:此函式是 Nunchuk 消費者驅動程式的核心,用於輪詢 Nunchuk 加速度計的資料並將其轉換為 Input 事件。

    • 解讀:在該函式中,首先取得私有結構 nunchuk_dev 的指標。接著,透過 iio_read_channel_raw 函式讀取各個軸的原始加速度資料。讀取到的資料隨後被報告給 Input 子系統,最後呼叫 input_sync 以同步事件。
重點整理
  • Nunchuk 加速度計 IIO 驅動程式:負責與 Nunchuk 裝置通訊,提供加速度資料。
  • Nunchuk 消費者驅動程式:使用 Input 子系統消費 Nunchuk 加速度計提供的 IIO 通道,將資料轉換為 Input 事件。
  • IIO 子系統的作用:提供標準化的介面處理感測器裝置,使得裝置資料可以被靈活地存取和利用。

隨著物聯網和智慧裝置的發展,各類別感測器的應用將越來越廣泛。Linux 的 IIO 子系統提供了一個強大的框架,用於處理這些感測器資料。未來,可以預見會有更多根據 IIO 子系統的驅動程式被開發出來,以支援更多種類別的感測器裝置。

Plantuml 圖表說明

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Nunchuk 加速度計 IIO 驅動程式開發

package "Linux Shell 操作" {
    package "檔案操作" {
        component [ls/cd/pwd] as nav
        component [cp/mv/rm] as file
        component [chmod/chown] as perm
    }

    package "文字處理" {
        component [grep] as grep
        component [sed] as sed
        component [awk] as awk
        component [cut/sort/uniq] as text
    }

    package "系統管理" {
        component [ps/top/htop] as process
        component [systemctl] as service
        component [cron] as cron
    }

    package "管線與重導向" {
        component [| 管線] as pipe
        component [> >> 輸出] as redirect
        component [$() 命令替換] as subst
    }
}

nav --> file : 檔案管理
file --> perm : 權限設定
grep --> sed : 過濾處理
sed --> awk : 欄位處理
pipe --> redirect : 串接命令
process --> service : 服務管理

note right of pipe
  命令1 | 命令2
  前者輸出作為後者輸入
end note

@enduml

此圖示展示了 Nunchuk 加速度計的資料如何從裝置傳遞到應用程式的過程:首先透過 I2C 通訊被 IIO 驅動程式讀取,接著由消費者驅動程式消費並轉換為 Input 事件,最終被應用程式所使用。

Wii Nunchuk 消費者驅動程式開發與 IIO 子系統整合應用

概述

本章節主要探討如何開發 Wii Nunchuk 消費者驅動程式,並將其與 Linux 工業 I/O(IIO)子系統整合。透過此驅動程式,可以讀取 Nunchuk 的加速度計資料,並將其轉換為輸入事件,進一步應用於遊戲或其他互動裝置。

驅動程式核心功能實作

驅動程式的核心功能包括初始化、輪詢(polling)以及事件報告。以下程式碼片段展示了主要的實作細節:

static void nunchuk_poll(struct input_polled_dev *polled_input)
{
    int accel_x, accel_y, accel_z;
    struct nunchuk_dev *nunchuk;
    int ret;

    nunchuk = polled_input->private;
    /* 讀取 IIO "accel_x" 頻道的原始值 */
    ret = iio_read_channel_raw(nunchuk->accel_x, &accel_x);
    if (unlikely(ret < 0))
        return;
    /* 向輸入系統報告 ABS_RX 事件 */
    input_report_abs(polled_input->input, ABS_RX, accel_x);

    /* 讀取 IIO "accel_y" 頻道的原始值 */
    ret = iio_read_channel_raw(nunchuk->accel_y, &accel_y);
    if (unlikely(ret < 0))
        return;
    /* 向輸入系統報告 ABS_RY 事件 */
    input_report_abs(polled_input->input, ABS_RY, accel_y);

    /* 讀取 IIO "accel_z" 頻道的原始值 */
    ret = iio_read_channel_raw(nunchuk->accel_z, &accel_z);
    if (unlikely(ret < 0))
        return;
    /* 向輸入系統報告 ABS_RZ 事件 */
    input_report_abs(polled_input->input, ABS_RZ, accel_z);

    /* 同步輸入事件 */
    input_sync(polled_input->input);
}

內容解密:

  1. nunchuk_poll 函式:此函式負責定期讀取 Nunchuk 加速度計的資料,並將其轉換為輸入事件。
  2. iio_read_channel_raw:用於從指定的 IIO 頻道讀取原始資料。在此範例中,分別讀取了 accel_xaccel_yaccel_z 三個頻道的資料。
  3. input_report_abs:將讀取到的加速度計資料轉換為絕對軸事件(ABS_RXABS_RYABS_RZ),並報告給輸入系統。
  4. input_sync:通知輸入系統一個完整的事件報告已經完成。

驅動程式初始化與註冊

驅動程式的初始化涉及裝置樹的匹配、資源分配以及輸入裝置的註冊。以下展示了 nunchuk_probe 函式的部分實作:

static int nunchuk_probe(struct platform_device *pdev)
{
    int ret;
    struct device *dev = &pdev->dev;
    enum iio_chan_type type;
    struct nunchuk_dev *nunchuk;

    /* 分配私有結構體記憶體 */
    nunchuk = devm_kzalloc(dev, sizeof(*nunchuk), GFP_KERNEL);
    if (nunchuk == NULL)
        return -ENOMEM;

    /* 取得 IIO 頻道 "accel_x"、"accel_y"、"accel_z" */
    nunchuk->accel_x = devm_iio_channel_get(dev, "accel_x");
    nunchuk->accel_y = devm_iio_channel_get(dev, "accel_y");
    nunchuk->accel_z = devm_iio_channel_get(dev, "accel_z");

    /* 設定輪詢裝置 */
    polled_device->private = nunchuk;
    polled_device->poll_interval = 50;
    polled_device->poll = nunchuk_poll;

    /* 初始化輸入裝置 */
    input = polled_device->input;
    input->name = "WII accel consumer";
    set_bit(EV_ABS, input->evbit);
    set_bit(ABS_RX, input->absbit);
    set_bit(ABS_RY, input->absbit);
    set_bit(ABS_RZ, input->absbit);

    /* 註冊輪詢輸入裝置 */
    ret = input_register_polled_device(nunchuk->polled_input);
    if (ret < 0)
        return ret;

    return 0;
}

內容解密:

  1. nunchuk_probe 函式:在驅動程式與裝置樹匹配時被呼叫,負責初始化 Nunchuk 裝置。
  2. devm_iio_channel_get:用於取得 IIO 裝置的指定頻道。在此範例中,分別取得了 accel_xaccel_yaccel_z 三個頻道。
  3. input_register_polled_device:註冊輪詢輸入裝置,使其能夠向輸入系統報告事件。

裝置樹設定

為了使驅動程式正常運作,需要在裝置樹中正確設定相關節點。以下是一個範例組態:

&i2c1 {
    clock-frequency = <100000>;
    status = "okay";
};

內容解密:

  1. i2c1 節點:設定 I2C 控制器的時脈頻率為 100kHz,並啟用該介面。
  2. Nunchuk 裝置連線:Nunchuk 裝置透過 I2C 介面與 Raspberry Pi 連線,最高支援 100kHz 的通訊頻率。