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裝置。
關鍵步驟:
- 阻止hid-generic驅動程式接管裝置:在
hid-quirks.c檔案中,將自定義驅動程式的USB_VENDOR_ID和USB_DEVICE_ID新增至hid_ignore_list[]陣列中。 - 實作驅動程式邏輯:編寫驅動程式以接收來自使用者空間的命令,並根據命令控制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
詳細內容解密:
- probe()函式的作用:當USB裝置被插入時,probe()函式被呼叫,用於初始化裝置和驅動程式。它負責分配私有資料結構、儲存usb裝置資訊、建立sysfs入口等工作。
- led_store()函式的作用:當使用者空間應用程式寫入led sysfs入口時,led_store()函式被呼叫,用於控制LED。它負責將使用者輸入的命令轉換為u8值,並透過usb_bulk_msg()函式傳送給USB裝置。
- usb_bulk_msg()函式的使用:usb_bulk_msg()函式用於傳送批次訊息給USB裝置。它需要指定usb裝置、端點管道、資料緩衝區、資料長度等引數。
- 私有資料結構的作用:私有資料結構用於儲存驅動程式的相關資料,例如usb裝置指標、LED狀態等。它使得驅動程式能夠有效地管理USB裝置。
- 編譯和佈署驅動程式的步驟:編譯驅動程式需要使用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 裝置和使用者空間應用程式互動。
重點回顧
- 理解 USB 裝置驅動程式的基本概念:包括其在 Linux 系統中的角色和功能。
- 學習如何開發 USB 裝置驅動程式:涵蓋從定義 USB 裝置 ID 到實作 probe() 和 led_store() 函式的整個流程。
- 掌握編譯和佈署驅動程式的方法:包括使用 Makefile 編譯和將模組佈署到 Raspberry Pi 上。
- 深入理解相關技術細節:例如 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");
#### 內容解密:
- 包含必要的標頭檔:匯入必要的 Linux 核心模組開發標頭檔,如
linux/module.h和linux/usb.h。 - 定義裝置 ID:使用
USB_DEVICE巨集定義支援的 USB 裝置的 Vendor ID 和 Product ID。 - 建立私有資料結構:定義
struct usb_led結構以儲存驅動程式的相關資料,如對應的 USB 裝置指標和 LED 狀態。 probe函式實作:在裝置插入時,led_probe函式被呼叫,用於組態驅動程式的私有資料結構並建立 sysfs 檔案。disconnect函式實作:在裝置拔出時,led_disconnect函式被呼叫,用於清理資源和移除 sysfs 檔案。- 註冊 USB 驅動程式:使用
module_usb_driver巨集將led_driver結構註冊到核心。
非同步通訊機制:URB 的應用
在某些情況下,USB 裝置需要非同步地與主機進行通訊。Linux 提供了一種稱為 USB Request Block(URB)的機制來支援非同步資料傳輸。
URB 的基本使用步驟
- 分配 URB 結構:使用
usb_alloc_urb函式分配一個 URB 結構。 - 初始化 URB:設定 URB 的相關引數,如傳輸型別、端點位址、資料緩衝區等。
- 提交 URB:使用
usb_submit_urb函式將 URB 提交給 USB 子系統進行處理。 - 處理 URB 回呼:當 URB 的非同步操作完成後,相關的回呼函式將被呼叫,以處理傳輸結果。