USB in 3mins

Johnny Chang
13 min readAug 4, 2023

USB Protocol 說明
USB support — The Linux Kernel documentation

USB 硬體線路

  • USB2.0的部分
  • USB3.0的部分
  • 與USB 2.0 的480Mb/s 半雙工廣播相比,USB 3.0 具有5 Gb/s 的全雙工點對點傳送速率。

USB插拔事件

  • Master:在USB主機端,D+和D-信號線通過15KΩ的下拉電阻連接到GND(0V),這個下拉電阻的作用是將D+和D-信號線拉低,形成低電平。
  • Slave(外部設備):在USB從機端,D+和D-信號線通過1.5KΩ的上拉電阻連接到3.3V電源,這個上拉電阻的作用是將D+和D-信號線拉高,形成高電平。
  • 插拔事件檢測:當從機設備插入到主機時,從機的上拉電阻將D+和D-信號線拉高到3.3V附近,主機端的下拉電阻則將D+和D-信號線拉低到0V。因此,當插入時,D+和D-信號線之間的電壓差會變為約3V,這個變化會被主機端檢測到,從而觸發USB插拔事件。
  • Low Speed Slave: Pull up接到D-
  • High Speed Slave: Pull up接到D+

NRZI USB Encode 在 Probe USB訊號時注意NRZI

NRZI(Non-Return-to-Zero Inverted)是一種數據編碼技術,主要用於將二進位數據轉換為適合在通信媒介上傳輸的電信號。NRZI 編碼的主要功能是確保傳輸中的時鐘同步,同時減少長時間連續的相同位元(0 或 1),以避免數據傳輸中的時序問題。

NRZI 編碼的原理如下:

  • 傳輸規則:
  1. 當原始二進位數據為 1 時,NRZI 將保持電信號不變。

2. 當原始二進位數據為 0時,NRZI 會進行信號反轉(從高電位到低電位或從低電位到高電位)。

  • 特點:
  1. NRZI 編碼的主要特點是它不會返回至零位(Return-to-Zero),而是在每個位元的開始位置進行轉換。

2. 連續的 0 或 1 不會在信號中產生額外的過渡,以確保時鐘同步性。

3. 當在數據中發現7個連續的「1」時,接下來會再第6個1後面自動插入一個「0」,稱為「Bit Stuffing」。

4. 接收端在接收到「6個連續的1」後,會自動刪除插入的「0」,還原原始數據。

USB 通訊地址

  • 主機為所有從機分配唯一的裝置地址,通過該地址來存取從機。
    以PC為例,一般PC的USB裝置可能包括滑鼠、鍵盤、HUB擴充套件、藍芽/WiFi介面卡等。
  • 主機給所有已連線的從機分配裝置地址,並確保不會重複。對剛接入還沒來得及分配地址的從機,主機使用預設地址<Addr0>與之通訊。交換少量的資訊後,主機分配新地址,然後雙方用新地址(Addr1~AddrN)通訊。
    Q: 列舉成功後,Slave再次拔插還可以用之前分配的地址通訊嗎?
    A: 主機會重新分配裝置地址,但可能分配的碰巧就是之前的地址。
  • 主機通過<裝置地址(Address),裝置端點(Endpoint)>存取指定從機的指定介面(功能)
    假設裝置A是USB複合裝置,同時支援滑鼠、鍵盤、CDC功能,那麼主機給裝置A分配裝置地址後,使用Endpoint來存取Slave A的其中一個功能(比如鍵盤功能)
    Q:主機未識別從機的功能之前用什麼端點通訊?
    A: 與預設地址0一樣,從機也會有預設端點0(Default Endpoint, EP0)。準確來講,對初次接入的從機,雙方通過<Addr0,EP0>進行通訊。

USB 通訊過程

  • 一次完整的通訊分為三個過程:請求過程(Token Packet)、資料過程(Data Packet)和狀態過程(Status Packet)。
  • 主機傳送Token開始請求過程, 與USB全速裝置通訊時,
    主機將每秒等分為1000個幀(Frame)。
    主機在每幀開始時,向所有從機廣播一個幀起始令牌包(Start Of Frame,SOF包)。
    它的作用有兩個:
    1. 是通知所有Slave,主機的USB匯流排正常工作
    2. 是Slave以此同步主機的時序。
    USB高速裝置通訊時,主機將幀進一步等分為8個微幀(Microframe),每個微幀佔125 μs。在同一幀內,8個微幀的幀號都等於當前SOF包的幀號。
  • 就算沒有要傳輸資料,主機還是會一個一個詢問狀況
  • Packet 最小的傳輸單位,之後的討論都會將Sync Bit省略
  • Token Packet
  • 用PID來識別OUT、IN、SOF與SETUP處理。
  • SOF Token Packet
    Q: 為什麼PID是4bit的,欄位長度卻有8bit?
    A: 因為PID欄位高4bit是低4bit的校驗位:pid(i+4) = ~pid(i)。
    Q: 為什麼CRC不校驗PID欄位?
    A: 因為PID欄位本身帶有校驗位。
#pragma data_alignment=1
typedef struct _USB_Token_SOF_t{
uint8_t bPID; // 0xA5, SOF(0101B)
uint16_t b11FrameID:11; // 幀號
uint16_t b5CRC:5; // wFrameID欄位(11bit)的CRC校驗碼
}USB_Token_SOF_t;
  • OUT/IN/SETUP Toke Packet
    在Enumeraqtor過程中,主機使用SETUP包請求從機的資訊。列舉成功後,主機使用IN包請求輸入資料,OUT包請求輸出資料。
#pragma data_alignment=1    //對齊方式為Byte
typedef struct _USB_Token_t{
uint8_t bPID; // 0xE1, OUT (0001B);
0x69, IN (1001B);
0x2D, SETUP (1101B);
uint16_t b7Addr:7; // 要存取的裝置地址
uint16_t b4Endpoint:4; // 要存取的端點號
uint16_t b5CRC:5; // wFrameID欄位(11bit)的CRC校驗碼
}USB_Token_t;
  • 如果是Setup PID Token,需要接Request Packet
#pragma data_alignment=1    //對齊方式為Byte
typedef struct _USB_Request_t{
uint8_t bmRequestType; // 請求型別
uint8_t bRequest; // 具體請求,參考USB 2.0 Spec 下表
uint16_t wIndex; // 內容和Request有關
uint16_t wLength; // 資料過程可傳輸的最大位元組數
}USB_Request_t;

typedef struct _bmRequestType_t{
uint8_t b5Recipient:5; // 0 = Device, 1 = Interface
2 = Endpoint, 3 = Other
4..31 = Reserved
uint8_t b2Type:2; // 0 = Standard, 1 = Class
2 = Vendor, 3 = Reserved
uint8_t b1Direction:1; // 0 = Host-to-device
1 = Device-to-host
}bmRequestType_t;
  • Data Packet
    Q: 為什麼要分DATA0和DATA1?
    A: 在USB Full Speed中,Data Packet以DATA0、DATA1的PID交替傳送。當接收方連續收到兩個PID相同的DATA包時,就知道Packet Loss.
#pragma data_alignment=1    //對齊方式為Byte
typedef struct _USB_Data_Packet_t{
uint8_t bPID; // 0xC3, DATA0 (0011B); even
0x4B, DATA1 (1011B); odd
0x87, DATA2 (0111B); for usb high speed
0x0F, MDATA (1111B); for usb high speed
uint8_t bData[]; // 0 ~ 8192B
uint16_t wCRC16; // bData欄位的CRC校驗碼
}USB_Data_Packet_t;

USB Descriptor

  • Device Descriptor
    當bDeviceClass為0時,需要再Interface Descriptor Defined
#pragma data_alignment=1    //對齊方式為Byte
typedef struct _USB_Desc_Device_t {
uint8_t bLength; // 固定值18B
uint8_t bDescriptorType; // 固定值Device(0x01)
uint16_t wBcdUSB; // USB Spec版本
uint8_t bDeviceClass; // 裝置型別
uint8_t bDeviceSubClass; // 裝置子型別
uint8_t bDeviceProtocol; // 協定型別
uint8_t bMaxPacketSize0; // EP0的最大包長度
uint16_t wIdVendor; // 廠商ID
uint16_t wIdProduct; // 產品ID
uint16_t wBcdDevice; // 裝置軟體版本
uint8_t bStringIndexManufacturer; // 廠商名稱字串索引號
uint8_t bStringIndexProduct; // 產品名稱字串索引號
uint8_t bStringIndexSerialNumber; // 序列號索字串引號
uint8_t bNumConfigurations // 設定數量>=1
}USB_Desc_Device_t;
  • Configuration Descriptor
#pragma data_alignment=1    //對齊方式為Byte
typedef struct _USB_Desc_Configuration_t {
uint8_t bLength; // 固定值9B
uint8_t bDescriptorType; // 固定值Configuration(0x02)
uint16_t wTotalConfigurationSize; // 設定集合的總大小
uint8_t bTotalInterfaces; // 設定集合的介面數量
uint8_t bConfigurationNumber; // 當前設定的序號(從1開始)
uint8_t bConfigurationStrIndex; // 設定名稱的字串索引號
uint8_t bConfigAttributes; // 設定集合的屬性
uint8_t bMaxPowerConsumption; // 最大供電電流,單位是2mA
}USB_Desc_Configuration_t;

// 設定集合的屬性
typedef struct _bConfigAttributes_t{
uint8_t b5reserved:5; // 保留置0
uint8_t b1RemoteWakeup:1; // 置1表示支援遠端喚醒
uint8_t b1Selfpowerd:1; // 置1表示支援自己供電
uint8_t b1reserved:1; // 保留置1
}bConfigAttributes_t;
#pragma data_alignment=1    //對齊方式為Byte
typedef struct _USB_Desc_Interface_t {
uint8_t bLength; // 固定值9B
uint8_t bDescriptorType; // 固定值Interface(0x04)
uint8_t bInterfaceNum; // 介面索引號
uint8_t bAlternateSetting; // 備用介面號
uint8_t bNumberEndpoints; // 端點數量
uint8_t bInterfaceClass; // 介面型別
uint8_t bInterfaceSubclass; // 介面子型別
uint8_t bInterfaceProtocol; // 介面協定
uint8_t bInterfaceStringIndex; // 介面名稱的字串索引號
}USB_Desc_Interface_t;
  • Endpoint Descriptor
#pragma data_alignment=1    //對齊方式為Byte
//參考USB Spec 2.0 Table 9-13
typedef struct _USB_Desc_Endpoint_t{
uint8_t bLength; // 固定值7B
uint8_t bDescriptorType; // 固定值Endpoint(0x05)
uint8_t bEndpointAddress; // 端點地址
uint8_t bmAttributes; // 端點屬性
uint16_t wMaxPacketSize; // 端點支援的最大包大小
uint8_t bInterval; // 輪詢間擱(僅中斷端點有效)
}USB_Desc_Endpoint_t;

// 端點地址
typedef struct _bEndpointAddress_t{
uint8_t b4EndpointNumber:4; // 端點號
uint8_t b3Reserved:3; // 保留置0
uint8_t b1Direction:1; // 傳輸方向(IN/OUT)
}bEndpointAddress_t;
// 端點屬性
typedef struct _bmAttributes_t{
uint8_t b2TransferType:2; // 傳輸型別
** 00 = Control
** 01 = Isochronous
** 10 = Bulk
** 11 = Interrupt

uint8_t b2SynchronizationType:2; // 僅iso傳輸有效
uint8_t b2UsageType:2; // 僅iso傳輸有效
uint8_t b2Reserved:2; // 保留置0
}bmAttributes_t;

--

--