返回文章列表

Linux USB HID 驅動程式開發

本講解 Linux 系統下 USB HID 裝置驅動程式的開發流程,包含狀態機設計、報告讀寫、Linux 核心模組編寫、sysfs 介面應用以及 URB 非同步通訊機制等核心技術,並搭配程式碼範例與圖表說明,引導開發者逐步掌握 USB HID 驅動程式開發技巧。

嵌入式系統 驅動程式開發

Linux 系統下開發 USB HID 裝置驅動程式,需理解 USB HID 協定與 Linux 核心驅動框架。本文介紹如何透過狀態機管理 USB 裝置狀態,並使用核心 API 進行報告讀寫與事件處理。文章涵蓋 Linux 核心模組的編寫、編譯與佈署,同時也說明如何透過 sysfs 介面與使用者空間程式互動,以及使用 URB 進行非同步資料傳輸。最後,以 USB LED 控制驅動程式為例,示範如何整合這些技術,並提供程式碼解析與圖表說明,幫助開發者快速上手。

USB裝置驅動程式開發:深入理解與實踐

概述

本章節重點介紹USB裝置驅動程式的開發,特別是在Linux環境下如何實作對USB HID(Human Interface Device)裝置的控制。透過本,開發者能夠掌握從基礎的USB裝置驅動概念到實際編寫Linux USB驅動程式的技能。

USB HID裝置驅動開發步驟

步驟1:初始化USB裝置狀態機

首先,需要定義一個狀態機來管理USB裝置的不同狀態,例如初始化、讀取資料、傳送報告等。狀態機的實作可以透過列舉型別(enum)來定義不同的狀態,並使用switch陳述式進行狀態轉換。

typedef enum {
    USB_STATE_INIT,
    USB_STATE_SCHEDULE_READ,
    USB_STATE_WAITING_FOR_DATA,
    USB_STATE_SEND_REPORT
} USB_STATE;

USB_STATE stateUSB = USB_STATE_INIT;

步驟2:排程讀取請求

USB_STATE_SCHEDULE_READ狀態下,設定接收旗標為false,並使用USB_DEVICE_HID_ReportReceive函式排程新的報告讀取請求。完成後,將狀態轉換為USB_STATE_WAITING_FOR_DATA

case USB_STATE_SCHEDULE_READ:
    appData.hidDataReceived = false;
    USB_DEVICE_HID_ReportReceive(USB_DEVICE_HID_INDEX_0, &appData.rxTransferHandle, appData.receiveDataBuffer, 64);
    appData.stateUSB = USB_STATE_WAITING_FOR_DATA;
    break;

步驟3:接收和處理報告

當收到報告時,檢查接收到的資料並根據資料內容進行相應的操作,例如切換LED燈的狀態。根據不同的命令值(0x01、0x02、0x03),執行不同的操作。

case USB_STATE_WAITING_FOR_DATA:
    if (appData.hidDataReceived) {
        if (appData.receiveDataBuffer[0] == 0x01) {
            BSP_LED_1Toggle();
            appData.stateUSB = USB_STATE_SCHEDULE_READ;
        }
        // 其他條件判斷...
    }
    break;

步驟4:傳送報告

USB_STATE_SEND_REPORT狀態下,根據按鈕的狀態準備要傳送的報告,並使用USB_DEVICE_HID_ReportSend函式傳送報告。

case USB_STATE_SEND_REPORT:
    if (appData.hidDataTransmitted) {
        if (BSP_SwitchStateGet(BSP_SWITCH_1) == BSP_SWITCH_STATE_PRESSED) {
            appData.transmitDataBuffer[0] = 0x00;
        } else {
            appData.transmitDataBuffer[0] = 0x01;
        }
        appData.hidDataTransmitted = false;
        USB_DEVICE_HID_ReportSend(USB_DEVICE_HID_INDEX_0, &appData.txTransferHandle, appData.transmitDataBuffer, 1);
        appData.stateUSB = USB_STATE_SCHEDULE_READ;
    }
    break;

在Linux下開發USB驅動程式

LAB 13.2:開發"USB LED"模組

本實驗旨在開發一個Linux USB驅動程式,以控制PIC32MX470 Curiosity Development Board上的LED燈。驅動程式將透過sysfs介面接收來自使用者空間的命令,並將其傳輸到PIC32MX HID裝置。

關鍵步驟:
  1. 阻止hid-generic驅動程式接管裝置:在hid-quirks.c檔案中,將自定義驅動程式的USB_VENDOR_IDUSB_DEVICE_ID新增至hid_ignore_list[]陣列中。
  2. 實作驅動程式邏輯:編寫驅動程式以接收來自使用者空間的命令,並根據命令控制LED燈的狀態。
此圖示說明瞭USB裝置驅動程式的狀態轉換流程:
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title LAB 13.2:開發"USB LED"模組

rectangle "初始化" as node1
rectangle "排程讀取請求" as node2
rectangle "接收資料" as node3
rectangle "傳送報告" as node4
rectangle "傳送完成" as node5

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5

@enduml

內容解密:

此圖示展示了USB裝置驅動程式的主要狀態轉換流程。首先,在初始化狀態(USB_STATE_INIT)下,系統會轉換到排程讀取請求狀態(USB_STATE_SCHEDULE_READ)。在該狀態下,系統會排程新的讀取請求,並轉換到等待資料狀態(USB_STATE_WAITING_FOR_DATA)。當接收到資料後,系統會處理資料並根據需要傳送報告,最終回到排程讀取請求狀態,形成一個迴圈。

USB裝置驅動程式開發

本章節主要介紹如何在Linux系統下開發USB裝置驅動程式,並以一個具體的USB LED模組為例,詳細闡述驅動程式的開發流程。

USB驅動程式基礎

在開始開發USB驅動程式之前,需要了解一些基礎知識。首先,USB驅動程式是Linux核心的一部分,用於管理USB裝置的存取和控制。其次,USB驅動程式需要與USB裝置進行通訊,這通常涉及到USB通訊協定的使用。

USB LED模組驅動程式開發

以下是以USB LED模組為例的驅動程式開發流程:

1. 定義USB裝置ID

首先,需要定義USB裝置的Vendor ID和Product ID,這些資訊用於識別特定的USB裝置。

#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F

2. 建立ID表

建立一個ID表,用於支援熱插拔。ID表包含了支援的USB裝置的Vendor ID和Product ID。

static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

3. 建立私有資料結構

建立一個私有資料結構,用於儲存驅動程式的資料。

struct usb_led {
    struct usb_device *udev;
    u8 led_number;
};

4. 實作probe()函式

probe()函式是當USB裝置被插入時被呼叫的,用於初始化裝置和驅動程式。

static int led_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    // 取得usb_device結構
    struct usb_device *udev = interface_to_usbdev(interface);
    struct usb_led *dev = NULL;
    int retval = -ENOMEM;

    // 分配私有資料結構
    dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);

    // 將usb裝置儲存到私有資料結構中
    dev->udev = usb_get_dev(udev);

    // 將私有資料結構附加到USB介面上
    usb_set_intfdata(interface, dev);

    // 建立led sysfs入口
    device_create_file(&interface->dev, &dev_attr_led);

    return 0;
}

5. 實作led_store()函式

led_store()函式是當使用者空間應用程式寫入led sysfs入口時被呼叫的,用於控制LED。

static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    // 取得usb_interface結構
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_led *led = usb_get_intfdata(intf);
    u8 val;

    // 將字元陣列轉換為u8值
    kstrtou8(buf, 10, &val);
    led->led_number = val;

    // 傳送命令給USB裝置
    usb_bulk_msg(led->udev, usb_sndctrlpipe(led->udev, 1), &led->led_number, 1, NULL, 0);

    return count;
}

編譯和佈署驅動程式

最後,需要編譯和佈署驅動程式到Raspberry Pi上。

編譯驅動程式

~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make

佈署驅動程式

~/linux_5.4_rpi3_drivers/linux_5.4_USB_drivers$ make deploy
詳細內容解密:
  1. probe()函式的作用:當USB裝置被插入時,probe()函式被呼叫,用於初始化裝置和驅動程式。它負責分配私有資料結構、儲存usb裝置資訊、建立sysfs入口等工作。
  2. led_store()函式的作用:當使用者空間應用程式寫入led sysfs入口時,led_store()函式被呼叫,用於控制LED。它負責將使用者輸入的命令轉換為u8值,並透過usb_bulk_msg()函式傳送給USB裝置。
  3. usb_bulk_msg()函式的使用:usb_bulk_msg()函式用於傳送批次訊息給USB裝置。它需要指定usb裝置、端點管道、資料緩衝區、資料長度等引數。
  4. 私有資料結構的作用:私有資料結構用於儲存驅動程式的相關資料,例如usb裝置指標、LED狀態等。它使得驅動程式能夠有效地管理USB裝置。
  5. 編譯和佈署驅動程式的步驟:編譯驅動程式需要使用Makefile檔案,而佈署驅動程式則需要將編譯好的模組檔案複製到Raspberry Pi的適當目錄下。

這些內容有助於讀者更深入地理解USB驅動程式的開發流程和相關技術細節。

此圖示說明瞭 USB 裝置驅動程式的架構

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 此圖示說明瞭 USB 裝置驅動程式的架構 

rectangle "連線" as node1
rectangle "管理" as node2
rectangle "控制" as node3
rectangle "sysfs 入口" as node4
rectangle "寫入命令" as node5

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5

@enduml

此圖示詳細說明:

  • USB 裝置:代表實際的 USB 裝置,例如 USB LED 模組。
  • Linux 系統:代表運作 Linux 作業系統的裝置,例如 Raspberry Pi。
  • USB 裝置驅動程式:負責管理 USB 裝置,與 Linux 系統核心互動,並提供 sysfs 入口給使用者空間應用程式。
  • sysfs 入口:允許使用者空間應用程式透過寫入命令來控制 USB 裝置。
  • 使用者空間應用程式:代表使用者層級的應用程式,可以透過 sysfs 入口與 USB 裝置驅動程式互動,進而控制 USB 裝置。

這個圖示清晰地展示了 USB 裝置驅動程式在 Linux 系統中的角色,以及它如何與 USB 裝置和使用者空間應用程式互動。

重點回顧

  1. 理解 USB 裝置驅動程式的基本概念:包括其在 Linux 系統中的角色和功能。
  2. 學習如何開發 USB 裝置驅動程式:涵蓋從定義 USB 裝置 ID 到實作 probe() 和 led_store() 函式的整個流程。
  3. 掌握編譯和佈署驅動程式的方法:包括使用 Makefile 編譯和將模組佈署到 Raspberry Pi 上。
  4. 深入理解相關技術細節:例如 usb_bulk_msg() 函式的使用、私有資料結構的作用等。

這些重點有助於讀者全面掌握 USB 裝置驅動程式的開發技術。

USB 裝置驅動程式開發

簡介

本章節將探討 USB 裝置驅動程式的開發,特別是在 Linux 環境下的實作與應用。透過本章的學習,讀者將能夠理解如何為特定的 USB 裝置撰寫驅動程式,並實作裝置的控制與資料傳輸。

USB 驅動程式基礎

USB 驅動程式是作業系統與 USB 裝置之間的橋樑,負責管理資料傳輸、裝置控制等任務。在 Linux 中,USB 驅動程式的開發涉及多個層面,包括裝置的識別、資料傳輸機制的實作等。

實作範例:USB LED 控制驅動程式

以下是一個簡單的 USB LED 控制驅動程式範例,該驅動程式允許使用者透過 sysfs 介面控制連線至 PIC32MX470 Curiosity Development Board 的 LED。

程式碼解析

#include <linux/module.h>
#include <linux/usb.h>

// 定義 USB 裝置的 Vendor ID 和 Product ID
#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F

// 建立裝置 ID 表
static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

// 定義驅動程式的私有資料結構
struct usb_led {
    struct usb_device *udev;
    u8 led_number;
};

// probe 函式:當裝置被插入時呼叫
static int led_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    struct usb_device *udev = interface_to_usbdev(interface);
    struct usb_led *dev = NULL;
    int retval = -ENOMEM;

    // 組態驅動程式的私有資料結構
    dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);
    if (!dev) {
        dev_err(&interface->dev, "out of memory\n");
        goto error;
    }
    dev->udev = usb_get_dev(udev);
    usb_set_intfdata(interface, dev);

    // 建立 sysfs 檔案用於控制 LED
    retval = device_create_file(&interface->dev, &dev_attr_led);
    if (retval)
        goto error_create_file;

    return 0;

error_create_file:
    usb_put_dev(udev);
    usb_set_intfdata(interface, NULL);
error:
    kfree(dev);
    return retval;
}

// disconnect 函式:當裝置被拔出時呼叫
static void led_disconnect(struct usb_interface *interface)
{
    struct usb_led *dev;
    dev = usb_get_intfdata(interface);
    device_remove_file(&interface->dev, &dev_attr_led);
    usb_set_intfdata(interface, NULL);
    usb_put_dev(dev->udev);
    kfree(dev);
    dev_info(&interface->dev, "USB LED now disconnected\n");
}

// 定義 USB 驅動程式結構
static struct usb_driver led_driver = {
    .name = "usbled",
    .probe = led_probe,
    .disconnect = led_disconnect,
    .id_table = id_table,
};

// 使用 module_usb_driver 巨集註冊驅動程式
module_usb_driver(led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a synchronous led usb controlled module");

#### 內容解密:

  1. 包含必要的標頭檔:匯入必要的 Linux 核心模組開發標頭檔,如 linux/module.hlinux/usb.h
  2. 定義裝置 ID:使用 USB_DEVICE 巨集定義支援的 USB 裝置的 Vendor ID 和 Product ID。
  3. 建立私有資料結構:定義 struct usb_led 結構以儲存驅動程式的相關資料,如對應的 USB 裝置指標和 LED 狀態。
  4. probe 函式實作:在裝置插入時,led_probe 函式被呼叫,用於組態驅動程式的私有資料結構並建立 sysfs 檔案。
  5. disconnect 函式實作:在裝置拔出時,led_disconnect 函式被呼叫,用於清理資源和移除 sysfs 檔案。
  6. 註冊 USB 驅動程式:使用 module_usb_driver 巨集將 led_driver 結構註冊到核心。

非同步通訊機制:URB 的應用

在某些情況下,USB 裝置需要非同步地與主機進行通訊。Linux 提供了一種稱為 USB Request Block(URB)的機制來支援非同步資料傳輸。

URB 的基本使用步驟

  1. 分配 URB 結構:使用 usb_alloc_urb 函式分配一個 URB 結構。
  2. 初始化 URB:設定 URB 的相關引數,如傳輸型別、端點位址、資料緩衝區等。
  3. 提交 URB:使用 usb_submit_urb 函式將 URB 提交給 USB 子系統進行處理。
  4. 處理 URB 回呼:當 URB 的非同步操作完成後,相關的回呼函式將被呼叫,以處理傳輸結果。