# USBX ## USBX 的架構總覽 USBX 的架構主要分為 三個層級: ### 1️⃣ 控制器層 (Controller Layer) 硬體操作、暫存器控制、USB Host/Device 硬體驅動 #### 對應的 USBX API: ux_hcd_stm32_initialize() → HCD 初始化函數 (初始化 STM32 的 USB 硬體) ### 2️⃣ 堆疊層 (Stack Layer) USB 主機 (Host) 或裝置 (Device) 與應用層之間的「橋樑」,負責處理所有 USB 資料傳輸與裝置管理 #### 堆疊層包含四大核心功能: ![image](https://hackmd.io/_uploads/rkwY86Mjkl.png) #### 對應的 USBX API: ##### 主機模式 (Host Stack) ux_host_stack_initialize() → 初始化 USB 主機堆疊 ux_host_stack_class_register() → 註冊 USB 類別 (Mass Storage、HID、CDC 等) ux_host_stack_device_configuration_select() → 選擇設備的 Configuration ux_host_stack_transfer_request() → 發送 USB 傳輸請求 ux_host_stack_hcd_register() → 註冊 USB 控制器 ##### 裝置模式 (Device Stack) ux_device_stack_initialize() → 初始化 USB 裝置堆疊 ux_device_stack_class_register() → 註冊 USB 裝置類別 ux_device_stack_transfer_request() → 處理 USB 裝置端的傳輸請求 ### 3️⃣ 類別層 (Class Layer) 定義 USB 裝置的功能,例如: Mass Storage (隨身碟) HID (鍵盤、滑鼠) CDC/ACM (虛擬序列埠) Audio (USB 音訊) #### 對應的 USBX API: ##### 主機模式 ux_host_class_storage_entry() → USB 隨身碟 ux_host_class_hid_entry() → HID 設備 (鍵盤/滑鼠) ux_host_class_cdc_acm_entry() → 虛擬 COM Port (CDC ACM) ux_host_class_audio_entry() → USB 音訊 ##### 裝置模式 ux_device_class_storage_entry() → USB Mass Storage ux_device_class_hid_entry() → HID 裝置 ux_device_class_cdc_acm_entry() → CDC/ACM 裝置 :::spoiler app_usbx_host.c ```c= UINT MX_USBX_Host_Init(VOID *memory_ptr) { UINT ret = UX_SUCCESS; TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; /* 控制器層 (Controller Layer) - 分配 USBX 內存池 */ UCHAR *pointer; (void)byte_pool; /* =========================== 控制器層 (Controller Layer) - 記憶體分配 =========================== */ if (tx_byte_allocate(byte_pool, (VOID **)&pointer, USBX_HOST_MEMORY_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) { return TX_POOL_ERROR; } /* =========================== 堆疊層 (Stack Layer) - 初始化 USBX 記憶體 =========================== */ if (ux_system_initialize(pointer, USBX_HOST_MEMORY_STACK_SIZE, UX_NULL, 0) != UX_SUCCESS) { return UX_ERROR; } /* =========================== 堆疊層 (Stack Layer) - 初始化 USB 主機堆疊 =========================== */ if (ux_host_stack_initialize(ux_host_event_callback) != UX_SUCCESS) { return UX_ERROR; } /* 堆疊層 (Stack Layer) - 註冊錯誤回調函數 */ ux_utility_error_callback_register(&ux_host_error_callback); /* =========================== 類別層 (Class Layer) - 註冊 USB Mass Storage Class =========================== */ if (ux_host_stack_class_register(_ux_system_host_class_storage_name, ux_host_class_storage_entry) != UX_SUCCESS) { return UX_ERROR; } /* =========================== 控制器層 (Controller Layer) - 分配 USB 主機應用執行緒的記憶體 =========================== */ if (tx_byte_allocate(byte_pool, (VOID **) &pointer, UX_HOST_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) { return TX_POOL_ERROR; } /* =========================== 應用層 (Application Layer) - 創建 USB 主機應用執行緒 =========================== */ if (tx_thread_create(&ux_host_app_thread, "USBX App Host Main Thread", app_ux_host_thread_entry, 0, pointer, UX_HOST_APP_THREAD_STACK_SIZE, 10, 10, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) { return TX_THREAD_ERROR; } /* =========================== 控制器層 (Controller Layer) - 分配 USB Mass Storage 應用執行緒的記憶體 =========================== */ if (tx_byte_allocate(byte_pool, (VOID **) &pointer, UX_HOST_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) { return TX_POOL_ERROR; } /* =========================== 應用層 (Application Layer) - 創建 USB Mass Storage 應用執行緒 =========================== */ if (tx_thread_create(&msc_app_thread, "MSC App thread", msc_process_thread_entry,0, pointer, UX_HOST_APP_THREAD_STACK_SIZE, 30, 30, 0, TX_AUTO_START) != TX_SUCCESS) { return TX_THREAD_ERROR; } /* =========================== 應用層 (Application Layer) - 創建事件旗標 (Event Flag) =========================== */ if (tx_event_flags_create(&ux_app_EventFlag, "Event Flag") != TX_SUCCESS) { return TX_GROUP_ERROR; } return ret; } UINT ux_host_event_callback(ULONG event, UX_HOST_CLASS *current_class, VOID *current_instance) { UINT status = UX_SUCCESS; switch (event) { case UX_DEVICE_INSERTION: // 當 USB 設備插入 /* 檢查設備類型是否為 USB Mass Storage */ if (current_class -> ux_host_class_entry_function == ux_host_class_storage_entry) { if (storage == UX_NULL) // 確保 storage 變數為 NULL,避免重複初始化 { /* 取得 USB 存儲設備的實例 */ storage = (UX_HOST_CLASS_STORAGE *)current_instance; /* 顯示 USB 設備資訊 (PID/VID) */ printf("\nUSB Mass Storage Device Plugged\n"); printf("PID: %#x \n", (UINT)storage -> ux_host_class_storage_device -> ux_device_descriptor.idProduct); printf("VID: %#x \n", (UINT)storage -> ux_host_class_storage_device -> ux_device_descriptor.idVendor); /* 取得存儲媒體 (Storage Media) */ storage_media = (UX_HOST_CLASS_STORAGE_MEDIA *)current_class -> ux_host_class_media; if (storage_media -> ux_host_class_storage_media_lun != 0) { storage_media = UX_NULL; // 若 LUN 不為 0,則不支援 } else { /* 取得儲存媒體指標 */ media = &storage_media->ux_host_class_storage_media; } /* 確保 USB 存儲設備處於可用狀態 */ if (storage -> ux_host_class_storage_state == UX_HOST_CLASS_INSTANCE_LIVE) { /* 設定事件旗標 (Event Flag),通知應用程式 USB 設備已就緒 */ if (tx_event_flags_set(&ux_app_EventFlag, 0x01, TX_OR) != TX_SUCCESS) { Error_Handler(); } } } } break; case UX_DEVICE_REMOVAL: // 當 USB 設備拔除 /* 確保拔除的設備是當前註冊的存儲設備 */ if ((VOID*)storage == current_instance) { /* 清除 USB 設備資訊 */ storage = UX_NULL; storage_media = UX_NULL; media = UX_NULL; printf("\nUSB Mass Storage Device Unplugged"); } break; #if defined (UX_HOST_CLASS_STORAGE_NO_FILEX) case UX_STORAGE_MEDIA_INSERTION: // 存儲媒體插入 break; case UX_STORAGE_MEDIA_REMOVAL: // 存儲媒體移除 break; #endif case UX_DEVICE_CONNECTION: // 設備連接 break; case UX_DEVICE_DISCONNECTION: // 設備斷線 break; default: break; } return status; } VOID ux_host_error_callback(UINT system_level, UINT system_context, UINT error_code) { switch (error_code) { case UX_DEVICE_ENUMERATION_FAILURE: printf("USB Device Enumeration Failure"); break; case UX_NO_DEVICE_CONNECTED: printf("USB Device disconnected"); break; default: break; } } static VOID app_ux_host_thread_entry(ULONG thread_input) { /* 初始化 USBX 主機 (Host) */ USBX_APP_Host_Init(); } VOID USBX_APP_Host_Init(VOID) { UINT status; /* 初始化低階驅動 (LL Driver) */ MX_USB_OTG_FS_HCD_Init(); /* 註冊 USB 主機控制器 */ status = ux_host_stack_hcd_register(_ux_system_host_hcd_stm32_name,_ux_hcd_stm32_initialize,USB_OTG_FS_PERIPH_BASE,(ULONG)&hhcd_USB_OTG_FS); /* 檢查 HCD 註冊是否成功 */ if (status != UX_SUCCESS) { printf("Error: Host controller registration failed (0x%02X)\n", status); return; } /* 啟動 VBUS,為 USB 設備供電 */ USBH_DriverVBUS(1); /* 啟動 USB 主機模式 */ status = HAL_HCD_Start(&hhcd_USB_OTG_FS); if (status != HAL_OK) { printf("Error: HAL_HCD_Start failed.\n"); return; } /* 顯示啟動訊息 */ printf(" **** USB OTG HS in FS MSC Host **** \n"); printf("USB Host library started.\n"); /* 提示使用者插入 USB 設備 */ printf("Starting MSC Application\n"); printf("Connect your MSC Device\n"); } void USBH_DriverVBUS(uint8_t state) { if (state == 0) { /* 使能 Charge Pump,提供 VBUS 電源 */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_SET); } else { /* 關閉 Charge Pump,關閉 VBUS */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_RESET); } /* 等待 200ms,確保 VBUS 穩定 */ HAL_Delay(200); } ``` ::: ### UINT ux_host_stack_initialize UINT my_callback_function(ULONG event, UX_HOST_CLASS *current_class, VOID *current_instance); ![image](https://hackmd.io/_uploads/ry2UDkksye.png) ![image](https://hackmd.io/_uploads/HyKZN6foJg.png) :::spoiler ux_host_msc.c ```c= #include "app_usbx_host.h" #include "fx_api.h" #include "ux_host_class_storage.h" /* 外部變數,來自其他模組 */ extern TX_EVENT_FLAGS_GROUP ux_app_EventFlag; // USB 事件旗標 (Event Flag) extern FX_MEDIA *media; // 指向 USB 隨身碟的媒體結構 extern UX_HOST_CLASS_STORAGE *storage; // 指向 USB Mass Storage 設備的實例 /* RAM Disk 設定 */ #define RAM_DISK_SIZE (64 * 512) // 64 個扇區,每個扇區 512 bytes #define SECTOR_SIZE 512 // 扇區大小 /* 記憶體緩衝區 */ UCHAR m_memory[1024]; // 一般用於 USBX 操作的記憶體 UCHAR ram_disk_memory[RAM_DISK_SIZE]; // 模擬 RAM Disk 的記憶體 /* 函數宣告 */ void msc_process_thread_entry(ULONG arg); // USB Mass Storage 主處理執行緒 void my_media_driver(FX_MEDIA *media); // FileX 媒體驅動 (目前未使用) void msc_process_thread_entry(ULONG arg) { ULONG storage_media_flag = 0; UINT status; while (1) { /* 等待 STORAGE_MEDIA 旗標,當 USB 裝置插入時觸發 */ if (tx_event_flags_get(&ux_app_EventFlag, 0x01,TX_OR_CLEAR,&storage_media_flag, TX_WAIT_FOREVER) != TX_SUCCESS) { printf("發生錯誤:無法等待 USB 事件。\n"); Error_Handler(); } /* 確保 media 變數不為 NULL,表示 USB 隨身碟已正確偵測 */ if (media != NULL) { printf("\n=== 偵測到 USB Mass Storage 裝置 ===\n"); printf("🔹 正在格式化 USB 隨身碟...\n"); /* 格式化 USB 隨身碟 */ status = fx_media_format(media,_ux_host_class_storage_driver_entry, storage,m_memory, sizeof(m_memory), "MY_USB_DISK",1, 32, 0, 64, 512, 8, 1, 1); if (status != FX_SUCCESS) { printf("格式化失敗,錯誤碼: 0x%02X\n", status); continue; // 如果格式化失敗,則跳過這次循環 } printf(" 格式化成功!\n"); /* 掛載 USB 隨身碟 */ status = fx_media_open(media, "USB_MEDIA",_ux_host_class_storage_driver_entry,storage, m_memory,sizeof(m_memory)); if (status != FX_SUCCESS) { printf(" 開啟媒體失敗,錯誤碼: 0x%02X\n", status); continue; // 如果仍然無法開啟,則跳過這次循環 } printf("\n=== 開始檔案操作 ===\n"); /* 測試 */ create_dir(media); fx_file_create(media, "TEST123.TXT"); fx_directory_default_set(media, "Folder1"); // 設定當前目錄為 Folder1 /* 建立 TEST.TXT 檔案 */ status = App_File_Create(media); if (status != UX_SUCCESS) { printf(" 檔案建立失敗,錯誤碼: 0x%X\n", status); continue; } printf(" 檔案 TEST.TXT 已成功建立!\n"); /* 寫入檔案 */ printf("🔹 開始寫入檔案...\n"); if (App_File_Write(media) == UX_SUCCESS) { printf(" 檔案寫入成功!\n"); /* 讀取檔案 */ printf("🔹 開始讀取檔案...\n"); if (App_File_Read(media) == UX_SUCCESS) { printf(" 檔案讀取成功!\n"); printf("🔹 關閉檔案。\n"); printf("=== 檔案操作完成 ===\n"); } else { printf(" 檔案讀取失敗!\n"); } } else { printf(" 檔案寫入失敗!\n"); } /* 關閉 USB 隨身碟媒體 */ printf("\n=== 關閉 USB 隨身碟 ===\n"); status = fx_media_close(media); if (status != FX_SUCCESS) { printf(" 媒體關閉失敗,錯誤碼: 0x%X\n", status); } } else { /* 若 media 仍為 NULL,等待 10ms 再次檢查 */ tx_thread_sleep(10); } } } ``` ::: fx_directory_create():建立資料夾 fx_directory_default_set():設定 FileX 當前的「工作目錄 (Default Directory)」 :::spoiler app_filex.c ```c= #include "app_filex.h" /* 用於讀取檔案的緩衝區 */ static UCHAR Read_buffer[100]; /* 用於寫入檔案的字串 */ static UCHAR Write_buffer[] = "USBX_STM32_Host_Mass_Storage"; UINT MX_FileX_Init(VOID *memory_ptr) { UINT ret = FX_SUCCESS; TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; /* 避免未使用變數警告 */ (void)byte_pool; /* 初始化 FileX 系統 */ fx_system_initialize(); return ret; } UINT App_File_Write(FX_MEDIA *fx_media) { FX_FILE my_file; UINT status; /* 開啟 "TEST.TXT" 檔案,設定為可寫入模式 */ status = fx_file_open(fx_media, &my_file, "TEST.TXT", FX_OPEN_FOR_WRITE); if (status == FX_SUCCESS) { /* 移動檔案指標至開頭 */ fx_file_seek(&my_file, 0); /* 將 Write_buffer 內容寫入檔案 */ status = fx_file_write(&my_file, Write_buffer, sizeof(Write_buffer)); if (status == FX_SUCCESS) { /* 寫入成功後關閉檔案 */ fx_file_close(&my_file); /* 刷新媒體,確保資料寫入儲存裝置 */ fx_media_flush(fx_media); } else { /* 如果寫入失敗,關閉檔案並刪除錯誤檔案 */ fx_file_close(&my_file); fx_file_delete(fx_media, "TEST.TXT"); } } return status; } UINT App_File_Read(FX_MEDIA *fx_media) { FX_FILE my_file; ULONG requested_length; UINT file_attribute; UINT status; /* 讀取 "TEST.TXT" 檔案的屬性 */ status = fx_file_attributes_read(fx_media, "TEST.TXT", &file_attribute); if (status == FX_SUCCESS) { /* 確保該檔案不是目錄 */ if ((file_attribute & (FX_DIRECTORY | FX_VOLUME)) == 0U) { /* 開啟 "TEST.TXT" 檔案,設定為可讀取模式 */ status = fx_file_open(fx_media, &my_file, "TEST.TXT", FX_OPEN_FOR_READ); if (status == FX_SUCCESS) { /* 移動檔案指標至開頭 */ fx_file_seek(&my_file, 0); /* 讀取檔案內容到 Read_buffer */ status = fx_file_read(&my_file, Read_buffer, sizeof(Read_buffer), &requested_length); if (status == FX_SUCCESS) { /* 比對讀取的內容是否與原先寫入的內容一致 */ status = ux_utility_memory_compare(Write_buffer, Read_buffer, sizeof(Write_buffer)); } /* 讀取完成後關閉檔案 */ fx_file_close(&my_file); } } } return status; } UINT App_File_Create(FX_MEDIA *fx_media) { UINT status; /* 在根目錄中建立 "TEST.TXT" 檔案 */ status = fx_file_create(fx_media, "TEST.TXT"); /* 如果檔案已存在,則回傳成功 */ if (status == FX_ALREADY_CREATED) { status = FX_SUCCESS; } return status; } void create_dir(FX_MEDIA *media) { FX_FILE my_file; if (fx_directory_create(media, "Folder1") != FX_SUCCESS) printf("Failed to create 'Folder1'\n"); if (fx_directory_create(media, "Folder1/Folder2") != FX_SUCCESS) printf("Failed to create 'Folder1/Folder2'\n"); if (fx_file_create(media, "Folder1/Folder2/file666.txt") == FX_SUCCESS && fx_file_open(media, &my_file, "Folder1/Folder2/file666.txt", FX_OPEN_FOR_WRITE) == FX_SUCCESS) { fx_file_write(&my_file, "I did it!", 9); fx_file_close(&my_file); } else { printf("Failed to create/write 'Folder1/Folder2/file666.txt'\n"); } } ``` :::