在嵌入式系統開發中,撰寫 USB 驅動程式是常見的需求。本文將引導讀者開發一個 Linux USB 驅動程式,用於與 PIC32MX USB HID 裝置通訊,實作控制 LED 和讀取按鈕狀態的功能。文章將著重於 USB 狀態機的設計與實作,並提供程式碼範例說明如何處理 USB 事件和資料傳輸。透過理解 USB 狀態機的運作原理,讀者可以更有效地開發和維護 USB 驅動程式,確保裝置與主機之間的穩定通訊。
USB裝置驅動程式開發
步驟3:修改生成的程式碼
HID(Human Interface Device)類別通常用於實作人機介面產品,如滑鼠和鍵盤。然而,HID協定相當靈活,可以用於向USB裝置傳送/接收一般用途的資料。在以下兩個實驗中,您將學習如何使用USB協定進行基本的一般用途USB資料傳輸。您將開發Linux USB驅動程式,向PIC32MX USB HID裝置傳送USB命令,以切換包含在PIC32MX Curiosity板上的三個LED(LED1、LED2、LED3)。PIC32MX USB HID裝置還將檢查使用者按鈕(S1)的值,並回復包含其值的封包給Raspberry Pi。
USB裝置端實作
- 切換LED:Linux USB驅動程式傳送報告給HID裝置。報告的第一個位元組可以是0x01、0x02或0x03。當接收到0x01時,HID裝置必須切換LED1;接收到0x02時,切換LED2;接收到0x03時,切換LED3。
- 取得按鈕狀態:Linux USB驅動程式傳送報告給HID裝置。報告的第一個位元組是0x00。HID裝置必須回復另一個報告,其中第一個位元組是S1按鈕的狀態(“0x00"表示按下,“0x01"表示未按下)。
程式碼實作
// app.c中的USB_Task()函式內部實作USB狀態機
void USB_Task(void) {
switch (appData.stateUSB) {
case USB_STATE_INIT:
// 初始化狀態機
appData.stateUSB = USB_STATE_WAITING_FOR_DATA;
// 排程讀取請求以接收報告
USB_DEVICE_HID_ReportReceive(appData.handleUsbDevice, &appData.rxTransferHandle, appData.receiveDataBuffer, 64);
break;
case USB_STATE_WAITING_FOR_DATA:
// 等待主機傳送報告
if (appData.hidDataReceived) {
// 處理接收到的報告
uint8_t receivedCommand = appData.receiveDataBuffer[0];
if (receivedCommand == 0x00) {
// 取得按鈕狀態並回復
appData.transmitDataBuffer[0] = (PORTB & (1 << 12)) ? 0x01 : 0x00; // 假設S1連線到RB12
USB_DEVICE_HID_ReportSend(appData.handleUsbDevice, &appData.txTransferHandle, appData.transmitDataBuffer, 64);
appData.stateUSB = USB_STATE_SEND_REPORT;
} else if (receivedCommand >= 0x01 && receivedCommand <= 0x03) {
// 切換對應的LED
switch (receivedCommand) {
case 0x01:
LATB ^= (1 << LED1_PIN); // 假設LED1連線到RBx
break;
case 0x02:
LATB ^= (1 << LED2_PIN); // 假設LED2連線到RBy
break;
case 0x03:
LATB ^= (1 << LED3_PIN); // 假設LED3連線到RBz
break;
}
// 排程下一次讀取
USB_DEVICE_HID_ReportReceive(appData.handleUsbDevice, &appData.rxTransferHandle, appData.receiveDataBuffer, 64);
appData.stateUSB = USB_STATE_WAITING_FOR_DATA;
}
appData.hidDataReceived = false;
}
break;
case USB_STATE_SEND_REPORT:
if (appData.hidDataTransmitted) {
// 傳送完成,排程下一次讀取
USB_DEVICE_HID_ReportReceive(appData.handleUsbDevice, &appData.rxTransferHandle, appData.receiveDataBuffer, 64);
appData.stateUSB = USB_STATE_WAITING_FOR_DATA;
appData.hidDataTransmitted = false;
}
break;
default:
break;
}
}
內容解密:
- USB狀態機初始化:在
USB_STATE_INIT狀態下,狀態機轉移到USB_STATE_WAITING_FOR_DATA,並排程一個讀取請求以接收來自主機的報告。 - 等待資料:在
USB_STATE_WAITING_FOR_DATA狀態下,裝置等待主機傳送報告。一旦接收到報告,就根據報告的第一個位元組執行相應的操作(切換LED或回復按鈕狀態)。 - 傳送報告:在
USB_STATE_SEND_REPORT狀態下,裝置傳送包含按鈕狀態的報告給主機,並在傳送完成後排程下一次讀取請求。
步驟4:宣告USB狀態機狀態
在app.h檔案中宣告一個列舉型別(例如USB_STATES),它包含實作狀態機所需的四個狀態的標籤。
typedef enum {
USB_STATE_INIT = 0,
USB_STATE_WAITING_FOR_DATA,
USB_STATE_SCHEDULE_READ,
USB_STATE_SEND_REPORT
} USB_STATES;
步驟5:新增成員到APP_DATA型別
在app.h檔案中的APP_DATA結構型別中新增必要的成員,用於儲存USB狀態機的狀態、接收和傳送資料的緩衝區指標、HID傳輸控制程式碼以及傳輸狀態旗標。
typedef struct {
// ...
USB_STATES stateUSB; // USB任務的當前狀態
uint8_t *receiveDataBuffer; // 接收資料緩衝區
uint8_t *transmitDataBuffer; // 傳送資料緩衝區
USB_DEVICE_HID_TRANSFER_HANDLE txTransferHandle; // 傳送報告傳輸控制程式碼
USB_DEVICE_HID_TRANSFER_HANDLE rxTransferHandle; // 接收報告傳輸控制程式碼
bool hidDataReceived; // HID資料接收旗標
bool hidDataTransmitted; // HID資料傳送旗標
} APP_DATA;
內容解密:
- stateUSB:儲存USB狀態機的當前狀態。
- receiveDataBuffer和transmitDataBuffer:分別用於儲存接收和傳送的資料。
- txTransferHandle和rxTransferHandle:用於控制HID報告的傳輸。
- hidDataReceived和hidDataTransmitted:分別指示HID資料是否已接收或傳送。
USB裝置驅動程式開發
初始化USB狀態機
在開發USB裝置驅動程式時,第一步是初始化USB狀態機。這個過程涉及設定狀態機的初始狀態以及相關的緩衝區指標。
程式碼實作
void APP_Initialize(void) {
/* 將應用程式狀態機設定為初始狀態 */
appData.state = APP_STATE_INIT;
/* 初始化USB HID裝置應用程式資料 */
appData.handleUsbDevice = USB_DEVICE_HANDLE_INVALID;
appData.usbDeviceIsConfigured = false;
appData.idleRate = 0;
/* 初始化USB任務狀態機的appData成員 */
appData.receiveDataBuffer = &receiveDataBuffer[0];
appData.transmitDataBuffer = &transmitDataBuffer[0];
appData.stateUSB = USB_STATE_INIT;
}
內容解密:
appData.state = APP_STATE_INIT;將應用程式狀態機重置為初始狀態,確保每次啟動時都從同一個起點開始。appData.handleUsbDevice = USB_DEVICE_HANDLE_INVALID;初始化USB裝置控制程式碼為無效狀態,表示尚未組態。appData.usbDeviceIsConfigured = false;設定USB裝置組態狀態為未組態,這個標誌用於判斷裝置是否已經被主機組態。appData.receiveDataBuffer和appData.transmitDataBuffer被設定為指向相應的緩衝區,用於接收和傳送資料。appData.stateUSB = USB_STATE_INIT;初始化USB狀態機的狀態為初始狀態。
處理USB裝置斷開事件
當USB裝置被斷開或電源被移除時,需要處理相關事件以重置裝置狀態。
程式碼實作
case USB_DEVICE_EVENT_POWER_REMOVED:
/* VBUS不再可用,斷開裝置 */
USB_DEVICE_Detach(appData.handleUsbDevice);
appData.usbDeviceIsConfigured = false;
break;
內容解密:
- 當檢測到
USB_DEVICE_EVENT_POWER_REMOVED事件時,表示VBUS電源被移除,裝置需要斷開連線。 USB_DEVICE_Detach(appData.handleUsbDevice);執行裝置斷開操作,通知主機該裝置不再可用。appData.usbDeviceIsConfigured = false;重置裝置組態狀態為未組態,準備下一次連線。
處理HID事件
HID(Human Interface Device)事件包括報告傳送和接收事件,需要更新相關標誌以反映傳輸狀態。
程式碼實作
static void APP_USBDeviceHIDEventHandler(USB_DEVICE_HID_INDEX hidInstance, USB_DEVICE_HID_EVENT event, void* eventData, uintptr_t userData) {
APP_DATA* appData = (APP_DATA*)userData;
switch (event) {
case USB_DEVICE_HID_EVENT_REPORT_SENT:
{
USB_DEVICE_HID_EVENT_DATA_REPORT_SENT* report = (USB_DEVICE_HID_EVENT_DATA_REPORT_SENT*)eventData;
if (report->handle == appData->txTransferHandle) {
appData->hidDataTransmitted = true;
}
break;
}
case USB_DEVICE_HID_EVENT_REPORT_RECEIVED:
{
USB_DEVICE_HID_EVENT_DATA_REPORT_RECEIVED* report = (USB_DEVICE_HID_EVENT_DATA_REPORT_RECEIVED*)eventData;
if (report->handle == appData->rxTransferHandle) {
appData->hidDataReceived = true;
}
break;
}
// ...
}
}
內容解密:
- 當收到
USB_DEVICE_HID_EVENT_REPORT_SENT事件時,表示報告已傳送給主機。透過比較傳輸控制程式碼確認事件與當前傳輸請求的關聯性。 - 如果控制程式碼匹配,設定
appData->hidDataTransmitted = true;表示資料已成功傳輸。 - 對於
USB_DEVICE_HID_EVENT_REPORT_RECEIVED事件,類別似地檢查控制程式碼並更新appData->hidDataReceived標誌。
建立USB狀態機
USB狀態機負責管理USB裝置的不同狀態,包括初始化、讀寫操作等。
程式碼實作
static void USB_Task(void) {
if (appData.usbDeviceIsConfigured) {
switch (appData.stateUSB) {
case USB_STATE_INIT:
{
appData.hidDataTransmitted = true;
appData.txTransferHandle = USB_DEVICE_HID_TRANSFER_HANDLE_INVALID;
appData.rxTransferHandle = USB_DEVICE_HID_TRANSFER_HANDLE_INVALID;
appData.stateUSB = USB_STATE_SCHEDULE_READ;
break;
}
// 其他case處理
}
} else {
appData.stateUSB = USB_STATE_INIT;
}
}
內容解密:
- 狀態機根據
appData.usbDeviceIsConfigured的狀態決定是否執行。 - 在
USB_STATE_INIT狀態下,初始化傳輸標誌和控制程式碼,並轉移到USB_STATE_SCHEDULE_READ狀態以安排讀取操作。 - 如果裝置未組態,則重置狀態機到初始狀態。