返回文章列表

Linux 核心 Wii Nunchuk 驅動程式開發

本文探討在 Linux 核心中開發 Wii Nunchuk 驅動程式的實務方法,包含 Input Subsystem 整合、I2C 通訊、資料讀取與事件回報等關鍵技術。文章詳細說明瞭驅動程式的初始化流程、輪詢處理函式以及如何將搖桿、按鍵和加速度計資料轉換為輸入事件,並提供程式碼範例與流程圖,最後探討瞭如何使用

嵌入式系統 驅動程式開發

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 進行整合。

驅動程式開發流程

  1. 建立私有資料結構

    • 定義 struct nunchuk_dev 結構以儲存裝置相關資訊,包括 input_polled_devi2c_client 指標。
  2. 初始化 Input 裝置

    • 設定 input_dev 結構的 namephysid 屬性。
    • 設定裝置支援的事件型別,包括絕對座標事件 (EV_ABS) 和按鍵事件 (EV_KEY)。
  3. 組態絕對座標軸引數

    • 使用 input_set_abs_params() 設定各個絕對座標軸的引數範圍。
  4. 註冊 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

編譯與佈署

  1. 建立新的驅動程式資料夾並編寫 Makefile。
  2. 編譯並佈署模組至 Raspberry Pi。
  3. 更新 Device Tree 並重新開機。

重點回顧

  • 正確組態 Input Subsystem 相關結構和函式呼叫是驅動程式開發的關鍵。
  • I2C 通訊協定在讀取 Nunchuk 資料時扮演重要角色。
  • 正確解析 Nunchuk 資料並回報給 Input Subsystem 是實作功能的核心步驟。

未來可進一步最佳化輪詢頻率、改善資料解析準確度,或是擴充套件支援更多型別的輸入裝置。持續關注 Linux 核心更新和相關技術的發展,將有助於提升驅動程式的相容性和效能。

Wii Nunchuk 驅動程式解析與實作

Wii Nunchuk 是任天堂 Wii 遊戲機的附屬裝置,透過 I2C 介面與主機通訊,提供遊戲中的控制功能。本文將探討 Linux 核心中 Wii Nunchuk 驅動程式的實作細節,包括其初始化、資料讀取、事件回報等關鍵功能。

驅動程式初始化流程

在驅動程式初始化過程中,主要涉及 nunchuk_probe 函式的執行。該函式負責:

  1. 分配私有結構體:為新裝置分配 nunchuk_dev 結構體,用於儲存裝置相關資訊。
  2. 初始化 I2C 使用者端:將 I2C 使用者端與私有結構體進行關聯。
  3. 分配輪詢輸入裝置:使用 devm_input_allocate_polled_device 分配輪詢輸入裝置結構體。
  4. 設定輪詢間隔與處理函式:設定輪詢間隔為 50 毫秒,並指定 nunchuk_poll 為輪詢處理函式。
  5. 註冊輸入裝置:設定輸入裝置的事件型別和事件程式碼,並註冊輪詢輸入裝置。

程式碼範例:初始化流程

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;
}

內容解密:

  1. devm_kzalloc 分配私有結構體:確保在裝置移除時自動釋放記憶體,避免記憶體洩漏。
  2. i2c_set_clientdata 設定 I2C 使用者端:將 I2C 使用者端與私有結構體進行關聯,方便後續存取。
  3. devm_input_allocate_polled_device 分配輪詢輸入裝置:為裝置分配輪詢輸入裝置結構體,簡化資源管理。
  4. poll_intervalpoll 設定:定義輪詢週期和處理函式,實作週期性資料讀取。
  5. input_register_polled_device 註冊輸入裝置:將輪詢輸入裝置註冊到輸入子系統,使其可被上層應用程式存取。

資料讀取與事件回報

Wii Nunchuk 驅動程式透過 nunchuk_poll 函式週期性讀取裝置資料,並將資料轉換為輸入事件回報給輸入子系統。

資料讀取流程

  1. 讀取暫存器資料:透過 nunchuk_read_registers 函式讀取 Nunchuk 裝置的暫存器資料。
  2. 解析資料:將讀取到的資料解析為搖桿位置、按鈕狀態和加速度計資料。
  3. 回報輸入事件:使用 input_report_absinput_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);
}

內容解密:

  1. nunchuk_read_registers 讀取資料:透過 I2C 通訊讀取 Nunchuk 裝置的暫存器資料,取得搖桿和按鈕狀態。
  2. 資料解析:將讀取到的原始資料解析為可用資訊,如搖桿位置和按鈕狀態。
  3. input_report_absinput_event:將解析後的資料轉換為輸入事件,回報給輸入子系統。
  4. 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_RXABS_RYABS_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);
}

重點解析

  1. 事件型別與程式碼:本實驗主要涉及 EV_KEYEV_ABS 事件型別,分別對應按鍵事件和絕對軸事件。
  2. python-evdev 使用:透過安裝 python-evdev,我們可以在使用者空間讀取和寫入輸入事件,實作與輸入裝置的互動。
  3. 驅動程式修改:根據實驗需求,註解掉了與加速度計相關的事件程式碼,以適應新的應用程式開發需求。