@[toc] ## 概要 ` 提示:USB Descriptor、STM MCU` 本文以 Descriptor 層級實作為核心(Device/Configuration/BOS + Microsoft OS 2.0),說明如何讓裝置在使用 Windows/Linux 內建類別驅動(如 HID/WinUSB)時,也能安全、可預期地進入 USB Suspend/選擇性省電,並視需要啟用/禁止遠端喚醒。 ### Microsoft OS 2.0參考資料 [官方網址](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification) [官方文檔](https://download.microsoft.com/download/3/5/6/3563ED4A-F318-4B66-A181-AB1D8F6FD42D/MS_OS_2_0_desc.docx) ## 整體架構流程 ` 提示:BOS 是入口(宣告能力),MS OS 2.0 描述符才是「告訴 Windows 要建立哪些登錄屬性」的清單。` 1. 枚舉:主機讀取 Device/Configuration/Interface/Endpoint。 2. BOS:若啟用 `USBD_CLASS_BOS_ENABLED=1`,主機會再取 BOS Descriptor。 3. MS OS 2.0 要求:Windows 依 BOS 之 Platform Capability,透過 Vendor Control Request 讀取 `MS_OS_20_DESCRIPTOR_SET`。 4. Registry 層設定:若 `MS_OS_20_DESCRIPTOR_SET` 內含 Registry Property Descriptor(如 `SelectiveSuspendEnabled=1`、`EnhancedPowerManagementEnabled=1`),Windows 自動建立/更新登錄值。 5. 省電執行:在無 I/O 的情況下,主機依協議/登錄策略讓裝置進入 USB Suspend(總線無活動 ≥3ms) 或對應的 D-State(對 OS 而言)。 ### 示意圖 ` 提示:[]屬於USB Device動作。沒有[]屬於電腦Host端的動作` ``` [Device Descriptor] → [Configuration] → [Interface] ↓ [BOS Descriptor] ↓ [Platform Capability (MS OS 2.0 GUID)] ↓ Windows 傳送 Vendor Request → 要 MS_OS_20_DESCRIPTOR_SET ↓ [裝置回傳 Registry Property Descriptor] ↓ Windows 自動建立/更新機碼 ``` ## 技術名詞解釋 - Suspend(USB 層):USB 匯流排無活動達閾值(USB2 約≥3ms)後,裝置進入低功耗。USB需降至 ≤2.5mA(總線供電時)。 - Remote Wakeup:裝置可向主機發出喚醒訊號;是否允許由 Configuration Descriptor 的 bmAttributes bit5 與主機 `SET_FEATURE/CLEAR_FEATURE(REMOTE_WAKEUP)` 決定。 - BOS (Binary Object Store):集中宣告裝置「進階能力」的容器(如 USB 2.0 Extension、SuperSpeed、Platform Capability)。 - USB 2.0 Extension(LPM):提供 Link Power Management 能力,縮短進入低功耗的延遲、提升相容性與效率。 - MS OS 2.0 Descriptor:Microsoft 定義的描述符集合。Windows 讀取後可自動建立 Registry 屬性(如 `SelectiveSuspendEnabled`、`EnhancedPowerManagementEnabled`、`DeviceInterfaceGUIDs`)。 - D-State(OS 抽象):作業系統對裝置電源狀態的抽象(D0/D2/D3hot 等),與 USB 總線層 Suspend 互相關聯但非一個一個對應。 ## 技術細節 #### 1.Configuration Descriptor 關鍵欄位 在USBD_FS_BOSDesc中提供Microsoft OS 2.0 Platform Capability Descriptor Header ![BOSDesc增加MOS20](https://i.postimg.cc/Ls1Fq2gt/2025-08-27-140431.png) #### 2.BOS 與兩個常見開關 ##### 1️⃣ `USBD_LPM_ENABLED` - **LPM = Link Power Management** - 這是 **USB 2.0 Extension** 的一部分(屬於 BOS 裡的一個 Capability)。 - 當你要在 BOS 裡放 **USB 2.0 Extension Descriptor**(例如支援 LPM、Remote Wakeup 增強),就必須把 `USBD_LPM_ENABLED = 1`。 - 如果沒開,裝置的 BOS 可能會少掉 LPM 相關 Capability,導致 Windows 判斷「裝置不支援 LPM」。 ``` /*---------- -----------*/ #define USBD_MAX_NUM_INTERFACES 1U /*---------- -----------*/ #define USBD_MAX_NUM_CONFIGURATION 1U /*---------- -----------*/ #define USBD_MAX_STR_DESC_SIZ 2048U /*---------- -----------*/ #define USBD_DEBUG_LEVEL 0U /*---------- -----------*/ #define USBD_LPM_ENABLED 1U /*---------- -----------*/ #define USBD_SELF_POWERED 1U /*---------- -----------*/ ``` ##### 2️⃣ `USBD_CLASS_BOS_ENABLED` - **BOS = Binary Object Store** - 這是一個 USB 2.1 引入的描述符,用來集中宣告裝置的「進階能力」,例如:USB 2.0 Extension(LPM)、USB 3.0 SuperSpeed、Platform Capability (MS OS 2.0 / WebUSB)。 - `USBD_CLASS_BOS_ENABLED = 1` 代表韌體會在主機請求 BOS 描述符時回應正確的 BOS 結構。 - 如果沒開,裝置就完全不會回 BOS,主機也無法得知裝置的進階能力,像 MS OS 2.0 或 WebUSB 等功能就不會被觸發。 #### 3.MS OS 2.0 描述符 usbd_msos20.h內容 ``` #ifndef __USBD_MSOS20_H__ #define __USBD_MSOS20_H__ #include "usbd_def.h" extern const uint8_t MS_OS_20_DESCRIPTOR_SET[]; extern const uint16_t MS_OS_20_DESCRIPTOR_SET_TOTAL_LENGTH; #endif // __USBD_MSOS20_H__ ``` usbd_msos20.c內容 ``` //usbd_msos20.c #include "usbd_def.h" #include "usbd_msos20.h" __ALIGN_BEGIN const uint8_t MS_OS_20_DESCRIPTOR_SET[] __ALIGN_END = { //-------------------------------------------- // MS OS 2.0 Set Header Descriptor (10 bytes) //-------------------------------------------- 0x0A, 0x00, // wLength - 10 bytes 0x00, 0x00, // wDescriptorType = MS_OS_20_SET_HEADER (0) 0x00, 0x00, 0x03, 0x06, // dwWindowsVersion = 0x06030000 (Windows 8.1+) 0x48, 0x00, // wTotalLength = 72 bytes // // Microsoft OS 2.0 Registry Value Feature Descriptor // 0x3E, 0x00, // wLength - 62 bytes 0x04, 0x00, // wDescriptorType – 4 for Registry Property 0x04, 0x00, // wPropertyDataType - 4 for REG_DWORD 0x30, 0x00, // wPropertyNameLength – 48 bytes 0x53, 0x00, 0x65, 0x00, // Property Name - “SelectiveSuspendEnabled” 0x6C, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x76, 0x00, 0x65, 0x00, 0x53, 0x00, 0x75, 0x00, 0x73, 0x00, 0x70, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x45, 0x00, 0x6E, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x64, 0x00, 0x00, 0x00, 0x04, 0x00, // wPropertyDataLength – 4 bytes 0x01, 0x00, 0x00, 0x00 // PropertyData - 0x00000001 }; const uint16_t MS_OS_20_DESCRIPTOR_SET_TOTAL_LENGTH = sizeof(MS_OS_20_DESCRIPTOR_SET); ``` #### 4.在Setup中攔截0x20 快速測試的作法,建議還是在`HAL_PCD_SetupStageCallback()`中進行更完整的實作。 ![快速實作](https://i.postimg.cc/3x6HJdNr/2025-08-27-140332.png) ``` extern USBD_HandleTypeDef hUsbDeviceFS; int8_t USBD_CUSTOM_HID_VendorRequest(uint8_t cmd, uint8_t* pbuf, uint16_t length) { USBD_CtlSendData(&hUsbDeviceFS, (uint8_t*)MS_OS_20_DESCRIPTOR_SET, MS_OS_20_DESCRIPTOR_SET_TOTAL_LENGTH); return (USBD_OK); } ``` ### 技術細節改善建議 #### 當前作法 在 `HAL_PCD_SetupStageCallback()` 只攔截「標準 + 裝置收件」的 `SET_FEATURE/CLEAR_FEATURE(REMOTE_WAKEUP)`,處理完就 `USBD_CtlSendStatus()` 後 return 其他請求一律丟回 ST 既有流程(`USBD_LL_SetupStage`)。 同時,在 `USBD_CUSTOM_HID_Setup()` 裡,凡是 `USB_REQ_TYPE_VENDOR` 且 `bRequest==0x20`,就直接呼叫自訂的 `USBD_CUSTOM_HID_VendorRequest()`,未檢查方向(IN/OUT)、收件者(Device/Interface/Endpoint)、也未檢查 `wIndex==0x0007` 與長度邊界,由外部 handler 來回應資料。 #### 優點 最小侵入、結構乾淨:標準請求只處理 Remote-Wakeup 的最小集,其他交還內建流程,降低與 ST Stack 的耦合度與回歸風險。 相容度高:因沒有「吃掉」大多數請求,Windows 仍能順利透過預設路徑完成 BOS/MS OS 2.0 的查詢。 易於除錯:Remote-Wakeup 與 Vendor-Request 的路徑清楚分離,問題定位較直覺。 #### 缺點與風險 規範檢查不嚴謹:在 Class Setup 端攔 Vendor 請求但未檢查 `bmRequestType==0xC0`(Device→Host | Vendor | Device)與 `wIndex==0x0007`,容易「看似可用」但不符合 MS OS 2.0 規範。 可移植性與未來相容性風險:主機堆疊若更新得更嚴格,或加入其他 Vendor 路徑(例如 OS 2.0 sub-set、OS 1.0 相容路徑)時,未檢查 `wIndex` 可能導致誤回應。 資料階段處理不完整:未在這段邏輯中明確使用 `USBD_CtlSendData()` 依 `wLength` 進行長度裁剪與分段,若外部 handler 也未處理,可能出現短傳/過長/分段錯誤。 收件者混淆的可能:未限制 **Device recipient**(有些主機會對 Interface/Endpoint 發 Vendor 請求),若誤接管,可能干擾其它介面或類別行為。 職責邊界模糊:在 **Class(HID)Setup** 內處理「全裝置層」的 OS 2.0 請求,對多介面或非 HID 介面(如 WinUSB/CDC)來說,權責不清,後續擴充較不利。 #### 改善建議 MS OS 2.0 的處理邏輯應統一放在 **EP0 通用層**(例如 `usbd_ctlreq.c` 或在 `HAL_PCD_SetupStageCallback()` 嚴格過濾),僅在 `bmRequestType=0xC0`(Device-to-Host|Vendor|Device recipient)、`bRequest=MS_OS20_VENDOR_CODE`、`wIndex=0x0007` 時回傳 `MS_OS_20_DESCRIPTOR_SET`(資料長度以 `min(wLength, sizeof(set))` 送出);若 `bRequest=MS_OS20_VENDOR_CODE` 但參數不符合(例如 `wIndex` 非 0x0007),則回 `USBD_CtlError()`(STALL);其它所有請求一律交回預設流程處理,以確保合規且不誤攔。 所有控制相關的巨集建議集中管理,例如在 `usbd_conf.h` 中定義 `USBD_CLASS_BOS_ENABLED`、`USBD_LPM_ENABLED` 與 `MS_OS20_VENDOR_ENABLED`,並確保 BOS 的 Platform Capability 之 `wMSVendorCode` 與 `MS_OS20_VENDOR_CODE` 一致,方便專案層設定且確保 BOS/LPM 確實啟用。 BOS 與 MS OS 2.0 描述符建議分檔維護:`usbd_desc.c` 可承載 BOS(包含 USB 2.0 Extension 與 MS OS 2.0 Platform Capability),`usbd_msos20.c/.h` 專責 MS OS 2.0 Descriptor Set,並務必驗證各 `wLength/wTotalLength` 與 UTF-16LE 編碼正確。 Class 層(例如 HID Setup)不應再攔截 MS OS 2.0 的請求,該路徑交由通用層處理;Class 僅保留與該介面自身相關的其他 Vendor 請求,以避免重複或衝突。 最後,建議加入防呆與紀錄機制:檢查 `bmRequestType/bRequest/wIndex/wLength` 合理性,在 Debug 模式下輸出事件計數與錯誤碼,以利除錯與追蹤;若裝置同時有 FS/HS 兩個 PCD 例項,兩邊均應套用相同過濾規則。 ## AI Prompt ` 提示:針對STM MCU,下列的描述與本文的技術細節有一些不同,本文提供的技術細節並沒有針對wMSVendorCode 0x20、==*wIndex 0x07*==於HAL_PCD_SetupStageCallback進行辨識,而是再USBD_CUSTOM_HID_Setup前段中,直接攔截0x20進行處理。關於MS_OS_20_DESCRIPTOR_SET技術實作中只要SelectiveSuspendEnabled設為1便能達到效果。 ` ``` 請幫我在 STM32 專案(使用 STM32Cube HAL 與 STM32 USB Device Library)中加入「不需自訂驅動也能自動進入 USB Suspend」的功能。實作方式必須透過 Descriptor 來驅動低功耗機制,也就是在 BOS 描述符中開啟 USB 2.0 Extension 以支援 LPM,同時啟用 BOS 本身的回應,並且在 BOS 內新增一個 Microsoft OS 2.0 Platform Capability,其 GUID 為 {D8DD60DF-4589-4CC7-9CD2-659D9E648A9F},其中 dwWindowsVersion 設定為 0x06030000,wMSVendorCode 設定為 0x20,AltEnumCode 設為 0。接著請建立一份完整的 MS_OS_20_DESCRIPTOR_SET,並且在其中至少包含兩個 Registry Property Descriptor,分別為 SelectiveSuspendEnabled 與 EnhancedPowerManagementEnabled,兩者的值都必須是 REG_DWORD little-endian 形式的 1,名稱要以 UTF-16LE 編碼正確填寫。韌體必須能處理主機透過 EP0 傳送的 Vendor Request,當 bRequest 為 0x20 且 wIndex 為 0x07 時,要能正確回傳整個 MS_OS_20_DESCRIPTOR_SET,並且根據主機的 wLength 做長度檢查,必要時允許短傳或送出 STALL。Configuration Descriptor 中的 bmAttributes bit5 需要能依據巨集切換是否支援 Remote Wakeup,預設情況為關閉,巨集設為 1 時才允許。所有新增的常數請集中定義在 usbd_conf.h,例如 USBD_CLASS_BOS_ENABLED、USBD_LPM_ENABLED 與 MS_OS20_VENDOR_ENABLED。若原始專案沒有 BOS 檔案,請新增 usbd_desc.c/.h 來定義 BOS 描述符,並新增 usbd_msos20.c/.h(或在 usbd_desc.c 中)放置 MS_OS_20_DESCRIPTOR_SET 的內容。最後請提供完整可編譯的檔案內容與 unified diff,並附上一份 README_USB_Power.md,清楚描述巨集的使用方式、Vendor Request 的流程。 ``` ## 結論 只要在 **Descriptor 層**把基礎功課做好(**Configuration 的 Remote Wakeup 與電力宣告**、**BOS 的 LPM 與 Platform Capability**、**MS OS 2.0 的 Registry 屬性**),即使**不撰寫自訂驅動**,也能讓裝置在 Windows/Linux 內建驅動下,可靠地進入 **USB Suspend/選擇性省電**。BOS 負責「讓主機看見能力」,MS OS 2.0 負責「讓 Windows 形成正確的電源管理機碼」,兩者相輔相成,構成一條 **描述符驅動電源管理** 的最佳實踐路徑。