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);
}
程式碼實作詳解
nunchuk_accel_read_raw函式實作:此函式負責讀取 Nunchuk 加速度計的原始資料。透過 I2C 通訊,從裝置讀取資料並進行適當的轉換後,將結果儲存在val變數中。- 解讀:首先檢查是否成功從 Nunchuk 讀取資料,若讀取失敗,則傳回錯誤碼
-EIO。接著,根據通道的不同(X、Y 或 Z 軸),對讀取到的資料進行位元運算,以獲得正確的加速度值。
- 解讀:首先檢查是否成功從 Nunchuk 讀取資料,若讀取失敗,則傳回錯誤碼
IIO 裝置註冊流程:在
nunchuk_accel_probe函式中,進行 IIO 裝置的註冊。這包括了設定indio_dev結構中的相關欄位,如info、channels和modes。- 解讀:首先分配並初始化
iio_dev結構,接著設定其成員變數,包括裝置名稱、父裝置、IIO 資訊和通道組態等。最後,透過devm_iio_device_register將 IIO 裝置註冊到系統中。
- 解讀:首先分配並初始化
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);
}
內容解密:
- nunchuk_poll 函式:此函式負責定期讀取 Nunchuk 加速度計的資料,並將其轉換為輸入事件。
- iio_read_channel_raw:用於從指定的 IIO 頻道讀取原始資料。在此範例中,分別讀取了
accel_x、accel_y和accel_z三個頻道的資料。 - input_report_abs:將讀取到的加速度計資料轉換為絕對軸事件(
ABS_RX、ABS_RY、ABS_RZ),並報告給輸入系統。 - 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;
}
內容解密:
- nunchuk_probe 函式:在驅動程式與裝置樹匹配時被呼叫,負責初始化 Nunchuk 裝置。
- devm_iio_channel_get:用於取得 IIO 裝置的指定頻道。在此範例中,分別取得了
accel_x、accel_y和accel_z三個頻道。 - input_register_polled_device:註冊輪詢輸入裝置,使其能夠向輸入系統報告事件。
裝置樹設定
為了使驅動程式正常運作,需要在裝置樹中正確設定相關節點。以下是一個範例組態:
&i2c1 {
clock-frequency = <100000>;
status = "okay";
};
內容解密:
- i2c1 節點:設定 I2C 控制器的時脈頻率為 100kHz,並啟用該介面。
- Nunchuk 裝置連線:Nunchuk 裝置透過 I2C 介面與 Raspberry Pi 連線,最高支援 100kHz 的通訊頻率。