Linux 核心提供 Input Subsystem 作為管理輸入裝置的框架,本文將逐步講解如何開發 Wii Nunchuk 的 Linux 驅動程式,並整合至 Input Subsystem。首先,需要定義私有資料結構體以儲存裝置資訊,接著初始化 Input 裝置並設定支援的事件型別,例如絕對座標事件和按鍵事件。 Nunchuk 的資料讀取仰賴 I2C 通訊協定,透過 nunchuk_read_registers() 函式讀取暫存器資料,再於 nunchuk_poll() 輪詢函式中解析搖桿、按鍵及加速度計資料。解析後的資料會透過 input_report_abs() 和 input_event() 回報至 Input Subsystem,讓系統得以處理 Nunchuk 的輸入事件。最後,文章也說明瞭編譯、佈署驅動程式至 Raspberry Pi 的步驟,並提供 python-evdev 應用程式開發範例與對應的驅動程式修改方式。
Wii Nunchuk 驅動程式開發與 Input Subsystem 整合實務
在 Linux 核心中開發 Wii Nunchuk 驅動程式需要深入瞭解 Input Subsystem 的運作機制以及 I2C 通訊協定的應用。本文將根據實際案例,探討如何實作 Nunchuk 驅動程式並與 Input Subsystem 進行整合。
驅動程式開發流程
建立私有資料結構
- 定義
struct nunchuk_dev結構以儲存裝置相關資訊,包括input_polled_dev和i2c_client指標。
- 定義
初始化 Input 裝置
- 設定
input_dev結構的name、phys和id屬性。 - 設定裝置支援的事件型別,包括絕對座標事件 (
EV_ABS) 和按鍵事件 (EV_KEY)。
- 設定
組態絕對座標軸引數
- 使用
input_set_abs_params()設定各個絕對座標軸的引數範圍。
- 使用
註冊 Polled Input 裝置
- 呼叫
input_register_polled_device()將polled_input裝置註冊至 Input Subsystem。
- 呼叫
Nunchuk 資料讀取與事件回報
實作 nunchuk_read_registers() 函式
static int nunchuk_read_registers(struct i2c_client *client, u8 *buf, int buf_size)
{
mdelay(10);
buf[0] = 0x00;
i2c_master_send(client, buf, 1);
mdelay(10);
i2c_master_recv(client, buf, buf_size);
return 0;
}
該函式負責讀取 Nunchuk 裝置的暫存器資料,需遵循特定的 I2C 通訊時序。
輪詢處理函式 nunchuk_poll()
static void nunchuk_poll(struct input_polled_dev *polled_input)
{
u8 buf[6];
int joy_x, joy_y, z_button, c_button, accel_x, accel_y, accel_z;
struct i2c_client *client;
struct nunchuk_dev *nunchuk;
nunchuk = polled_input->private;
client = nunchuk->client;
nunchuk_read_registers(client, buf, ARRAY_SIZE(buf));
// 解析搖桿資料
joy_x = buf[0];
joy_y = buf[1];
// 解析按鍵狀態
z_button = (buf[5] & BIT(0)) ? 0 : 1;
c_button = (buf[5] & BIT(1)) ? 0 : 1;
// 解析加速度計資料
accel_x = (buf[2] << 2) | ((buf[5] >> 2) & 0x3);
accel_y = (buf[3] << 2) | ((buf[5] >> 4) & 0x3);
accel_z = (buf[4] << 2) | ((buf[5] >> 6) & 0x3);
// 回報事件至 Input Subsystem
input_report_abs(polled_input->input, ABS_X, joy_x);
input_report_abs(polled_input->input, ABS_Y, joy_y);
input_event(polled_input->input, EV_KEY, BTN_Z, z_button);
input_event(polled_input->input, EV_KEY, BTN_C, c_button);
input_report_abs(polled_input->input, ABS_RX, accel_x);
input_report_abs(polled_input->input, ABS_RY, accel_y);
input_report_abs(polled_input->input, ABS_RZ, accel_z);
input_sync(polled_input->input);
}
輪詢函式流程圖
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Linux 核心 Wii Nunchuk 驅動程式開發
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
編譯與佈署
- 建立新的驅動程式資料夾並編寫 Makefile。
- 編譯並佈署模組至 Raspberry Pi。
- 更新 Device Tree 並重新開機。
重點回顧
- 正確組態 Input Subsystem 相關結構和函式呼叫是驅動程式開發的關鍵。
- I2C 通訊協定在讀取 Nunchuk 資料時扮演重要角色。
- 正確解析 Nunchuk 資料並回報給 Input Subsystem 是實作功能的核心步驟。
未來可進一步最佳化輪詢頻率、改善資料解析準確度,或是擴充套件支援更多型別的輸入裝置。持續關注 Linux 核心更新和相關技術的發展,將有助於提升驅動程式的相容性和效能。
Wii Nunchuk 驅動程式解析與實作
Wii Nunchuk 是任天堂 Wii 遊戲機的附屬裝置,透過 I2C 介面與主機通訊,提供遊戲中的控制功能。本文將探討 Linux 核心中 Wii Nunchuk 驅動程式的實作細節,包括其初始化、資料讀取、事件回報等關鍵功能。
驅動程式初始化流程
在驅動程式初始化過程中,主要涉及 nunchuk_probe 函式的執行。該函式負責:
- 分配私有結構體:為新裝置分配
nunchuk_dev結構體,用於儲存裝置相關資訊。 - 初始化 I2C 使用者端:將 I2C 使用者端與私有結構體進行關聯。
- 分配輪詢輸入裝置:使用
devm_input_allocate_polled_device分配輪詢輸入裝置結構體。 - 設定輪詢間隔與處理函式:設定輪詢間隔為 50 毫秒,並指定
nunchuk_poll為輪詢處理函式。 - 註冊輸入裝置:設定輸入裝置的事件型別和事件程式碼,並註冊輪詢輸入裝置。
程式碼範例:初始化流程
static int nunchuk_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 分配私有結構體
struct nunchuk_dev *nunchuk = devm_kzalloc(&client->dev, sizeof(*nunchuk), GFP_KERNEL);
// 設定 I2C 使用者端
i2c_set_clientdata(client, nunchuk);
// 分配輪詢輸入裝置
struct input_polled_dev *polled_device = devm_input_allocate_polled_device(&client->dev);
// 設定輪詢間隔與處理函式
polled_device->poll_interval = 50;
polled_device->poll = nunchuk_poll;
// 註冊輸入裝置
input_register_polled_device(nunchuk->polled_input);
return 0;
}
內容解密:
devm_kzalloc分配私有結構體:確保在裝置移除時自動釋放記憶體,避免記憶體洩漏。i2c_set_clientdata設定 I2C 使用者端:將 I2C 使用者端與私有結構體進行關聯,方便後續存取。devm_input_allocate_polled_device分配輪詢輸入裝置:為裝置分配輪詢輸入裝置結構體,簡化資源管理。poll_interval與poll設定:定義輪詢週期和處理函式,實作週期性資料讀取。input_register_polled_device註冊輸入裝置:將輪詢輸入裝置註冊到輸入子系統,使其可被上層應用程式存取。
資料讀取與事件回報
Wii Nunchuk 驅動程式透過 nunchuk_poll 函式週期性讀取裝置資料,並將資料轉換為輸入事件回報給輸入子系統。
資料讀取流程
- 讀取暫存器資料:透過
nunchuk_read_registers函式讀取 Nunchuk 裝置的暫存器資料。 - 解析資料:將讀取到的資料解析為搖桿位置、按鈕狀態和加速度計資料。
- 回報輸入事件:使用
input_report_abs和input_event將解析後的資料回報給輸入子系統。
程式碼範例:資料讀取與事件回報
static void nunchuk_poll(struct input_polled_dev *polled_input)
{
// 讀取暫存器資料
u8 buf[6];
nunchuk_read_registers(client, buf, ARRAY_SIZE(buf));
// 解析資料
int joy_x = buf[0];
int z_button = (buf[5] & BIT(0)) ? 0 : 1;
// 回報輸入事件
input_report_abs(polled_input->input, ABS_X, joy_x);
input_event(polled_input->input, EV_KEY, BTN_Z, z_button);
input_sync(polled_input->input);
}
內容解密:
nunchuk_read_registers讀取資料:透過 I2C 通訊讀取 Nunchuk 裝置的暫存器資料,取得搖桿和按鈕狀態。- 資料解析:將讀取到的原始資料解析為可用資訊,如搖桿位置和按鈕狀態。
input_report_abs與input_event:將解析後的資料轉換為輸入事件,回報給輸入子系統。input_sync同步事件:通知輸入子系統一個完整的報告已傳送完畢,確保事件的完整性。
深入理解 Linux 輸入子系統與 Nunchuk 驅動開發
實驗室 10.3:Nunchuk 應用程式開發
在之前的實驗中,我們使用 evtest 應用程式來顯示由 Nunchuk 裝置產生的所有事件型別和事件程式碼(以及它們的值)。在本實驗 10.3 中,我們將使用 python-evdev 軟體包,這將允許我們建立 Python 應用程式,以在 Linux 上讀取和寫入輸入事件。evdev 介面用於將核心產生的事件直接傳遞到使用者空間,透過通常位於 /dev/input/ 的字元裝置。
安裝 python-evdev
首先,我們需要在 Raspberry Pi 上使用 pip 安裝 python-evdev:
root@raspberrypi:/home/pi# pip3 install evdev
接下來,在 nunchuk_drivers 資料夾內建立一個新的 python_apps 資料夾,用於存放本實驗 10.3 中開發的 Python 應用程式:
~/linux_5.4_rpi3_drivers/nunchuk_drivers$ mkdir python_apps
修改 Nunchuk 驅動程式
在本實驗 10.3 中開發的應用程式不會接收到由 Nunchuk 加速度計裝置產生的事件程式碼 ABS_RX、ABS_RY 和 ABS_RZ,因此我們需要註解掉 Nunchuk 驅動程式中相關的程式碼行。
static int nunchuk_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
u8 buf[2];
struct device *dev = &client->dev;
[...]
/* 設定 EV_KEY 事件型別和按鈕事件程式碼 */
set_bit(EV_KEY, input->evbit);
set_bit(BTN_C, input->keybit); /* 按鈕 */
set_bit(BTN_Z, input->keybit);
/*
* 設定 EV_ABS 事件型別和絕對軸事件程式碼
* ABS_X 和 ABS_Y 用於搖桿
*/
set_bit(EV_ABS, input->evbit);
set_bit(ABS_X, input->absbit); /* 搖桿 */
set_bit(ABS_Y, input->absbit);
// 停用加速度計相關事件程式碼
// set_bit(ABS_RX, input->absbit); /* 加速度計 */
// set_bit(ABS_RY, input->absbit);
// set_bit(ABS_RZ, input->absbit);
/*
* 為每個絕對軸填充額外的欄位
*/
input_set_abs_params(input, ABS_X, 0x00, 0xff, 0, 0);
input_set_abs_params(input, ABS_Y, 0x00, 0xff, 0, 0);
// 停用加速度計引數設定
// input_set_abs_params(input, ABS_RX, 0x00, 0x3ff, 0, 0);
// input_set_abs_params(input, ABS_RY, 0x00, 0x3ff, 0, 0);
// input_set_abs_params(input, ABS_RZ, 0x00, 0x3ff, 0, 0);
[...]
}
輪詢函式修改
在 nunchuk_poll 函式中,也需要註解掉與加速度計相關的事件上報程式碼:
static void nunchuk_poll(struct input_polled_dev *polled_input)
{
u8 buf[6];
int joy_x, joy_y, z_button, c_button, accel_x, accel_y, accel_z;
struct i2c_client *client;
struct nunchuk_dev *nunchuk;
[...]
/* 上報搖桿和按鈕事件 */
input_report_abs(polled_input->input, ABS_X, joy_x);
input_report_abs(polled_input->input, ABS_Y, joy_y);
input_event(polled_input->input, EV_KEY, BTN_Z, z_button);
input_event(polled_input->input, EV_KEY, BTN_C, c_button);
// 停用加速度計事件上報
// input_report_abs(polled_input->input, ABS_RX, accel_x);
// input_report_abs(polled_input->input, ABS_RY, accel_y);
// input_report_abs(polled_input->input, ABS_RZ, accel_z);
/* 同步輸入事件 */
input_sync(polled_input->input);
}
重點解析
- 事件型別與程式碼:本實驗主要涉及
EV_KEY和EV_ABS事件型別,分別對應按鍵事件和絕對軸事件。 python-evdev使用:透過安裝python-evdev,我們可以在使用者空間讀取和寫入輸入事件,實作與輸入裝置的互動。- 驅動程式修改:根據實驗需求,註解掉了與加速度計相關的事件程式碼,以適應新的應用程式開發需求。