4.1 USB 學(xué)習(xí)指南
閱讀源碼時(shí),經(jīng)常碰到如下術(shù)語(yǔ):
- HCD(Host Controller Driver)
- DCD(Device Controller Driver)
- PCD(Low layer USB Peripheral Control Driver)
- CDC(Communication Device Class)
4.1 USB 學(xué)習(xí)指南
USB 本身是一個(gè)很龐大、復(fù)雜的體系, 本課程的重點(diǎn)在于工業(yè)互聯(lián), USB 是其中的一個(gè) 小小知識(shí)點(diǎn)。本章課程的目的在于:能理解 USB 的一些概念,能使用 USB 傳輸數(shù)據(jù)。 4.24.5 節(jié), 介紹 USB 概念;4.64.7 節(jié),移植 USBX 實(shí)現(xiàn) USB 串口功能。
參考資料:
- 《圈圈教你玩 USB》
- 官網(wǎng):https://www.usb.org/documents
- 我們從官網(wǎng)下載后放在網(wǎng)盤如下目錄:
- ST 官方資料:
https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX過(guò) USB 設(shè)備驅(qū)動(dòng),直接通過(guò) USB 控制器驅(qū)動(dòng)訪問(wèn) USB 設(shè)備。
4.2 USB 系統(tǒng)硬件框架和軟件框架
4.2.1 實(shí)驗(yàn)現(xiàn)象
現(xiàn)象: 把 USB 設(shè)備比如 Android 手機(jī)接到 PC
- 右下角彈出"發(fā)現(xiàn) android phone"
- 跳出一個(gè)對(duì)話框, 提示你安裝驅(qū)動(dòng)程序
- 問(wèn) 1:USB 設(shè)備插到電腦上去, 接觸到的對(duì)方設(shè)備是什么?
答 1:是 USB 控制器,是 USB 控制器內(nèi)嵌的 root hub - 問(wèn) 2. 既然還沒有"驅(qū)動(dòng)程序",為何能知道是"android phone"?
答 2. windows 里已經(jīng)有了 USB 的總線驅(qū)動(dòng)程序, 接入 USB 設(shè)備后, 是"總線驅(qū)動(dòng)程序" 知道你是"android phone"、提示你安裝的是"設(shè)備驅(qū)動(dòng)程序"。USB 總線驅(qū)動(dòng)程序負(fù)責(zé):識(shí) 別 USB 設(shè)備, 給 USB 設(shè)備找到對(duì)應(yīng)的驅(qū)動(dòng)程序。 - 問(wèn) 3. 為什么一接入 USB 設(shè)備, PC 機(jī)就能發(fā)現(xiàn)它?
答 3. PC 的 USB 口內(nèi)部,D-和 D+接有 15K 的下拉電阻, 未接 USB 設(shè)備時(shí)為低電平。USB 設(shè)備的 USB 口內(nèi)部, D-或 D+接有 1.5K 的上拉電阻;它一接入 PC,就會(huì)把 PC USB 口的 D-或 D+拉高,從硬件的角度通知 PC 有新設(shè)備接入。 - 問(wèn) 4. USB 設(shè)備種類非常多,為什么一接入電腦, 就能識(shí)別出來(lái)它的種類?
答 4. PC 和 USB 設(shè)備都得遵守一些規(guī)范。比如: USB 設(shè)備接入電腦后, PC 機(jī)會(huì)發(fā)出"你 是什么"?USB 設(shè)備就必須回答"我是 xxx", 并且回答的格式是固定的。USB 總線驅(qū)動(dòng)程序會(huì) 發(fā)出某些命令想獲取設(shè)備信息(描述符),USB 設(shè)備必須返回"描述符"給 PC。 - 問(wèn) 5. PC 機(jī)上接有非常多的 USB 設(shè)備, 怎么分辨它們?
答 5. 每一個(gè) USB 設(shè)備接入 PC 時(shí), USB 總線驅(qū)動(dòng)程序都會(huì)給它分配一個(gè)編號(hào)。 PC 機(jī)想 訪問(wèn)某個(gè) USB 設(shè)備時(shí),發(fā)出的命令都含有對(duì)應(yīng)的編號(hào)(地址)。 - 問(wèn) 6. USB 設(shè)備剛接入 PC 時(shí), 還沒有編號(hào); 那么 PC 怎么把"分配的編號(hào)"告訴它?
答 6. 新接入的 USB 設(shè)備的默認(rèn)編號(hào)是 0,在未分配新編號(hào)前, PC 使用 0 編號(hào)和它通 信。
- 問(wèn) 1:USB 設(shè)備插到電腦上去, 接觸到的對(duì)方設(shè)備是什么?
4.2.2 硬件框架
在 USB 系統(tǒng)中, 有 2 個(gè)硬件概念:
- USB Host:它跟處理器相連,處理器通過(guò) USB Host 跟各類 USB 設(shè)備通信。 USB Host 中 集成有一個(gè) root hub
- USB Device:這分為兩類設(shè)備
- Hub:用來(lái)擴(kuò)展 USB 接口
- Function:就是普通的 USB 設(shè)備,比如 U 盤、聲卡等
4.2.3 軟件框架
APP 可以通過(guò) USB 設(shè)備驅(qū)動(dòng)程序訪問(wèn) USB 設(shè)備,也可以繞過(guò) USB 設(shè)備驅(qū)動(dòng),直接通過(guò) USB 控制器驅(qū)動(dòng)訪問(wèn) USB 設(shè)備。
4.3 軟件工程師眼里的 USB 電氣信號(hào)
參考資料:
- 《圈圈教你玩 USB》
- 簡(jiǎn)書 jianshu_kevin@126.com 的文章
- https://www.jianshu.com/p/3afc1eb5bd32
- https://www.jianshu.com/p/cf8e7df5ff09
- USB 協(xié)議(三):https://www.jianshu.com/p/2a6e22194cd3
- 官網(wǎng):https://www.usb.org/documents
- 《usb_20.pdf》的《chapter5 7 Electrical》
- USB 的 NRZI 信號(hào)格式: https://zhuanlan.zhihu.com/p/460018993
- USB2.0 包 Packet 的組成: https://www.usbzh.com/article/detail-459.html
4.3.1 USB 設(shè)備狀態(tài)切換圖
USB 2.0 協(xié)議支持 3 種速率: 低速(Low Speed,1.5Mbps)、全速(Full Speed, 12Mbps)、 高速(High Speed, 480Mbps)。
USB Hub、USB 設(shè)備, 也分為低速、全速、高速三種類型。 一個(gè) USB 設(shè)備, 可能兼容低 速、全速, 可能兼容全速、高速, 但是不會(huì)同時(shí)兼容低速、高速。
4.3.2 硬件線路
下圖是兼容高速模式的 USB 收發(fā)器電路圖:
USB 連接涉及 Hub Port 和 USB 設(shè)備,硬件連接如下:
4.3.3 電子信號(hào)
USB 連接線有 4 條: 5V、D+、D-、GND。數(shù)據(jù)線 D+、D-,只能表示 4 種狀態(tài)。 USB 協(xié)議 中,很巧妙地使用這兩條線路實(shí)現(xiàn)了空閑(Idle)、開始(SOP)、傳輸數(shù)據(jù)(Data)、結(jié)束(EOP) 等功能。
4.3.4 低速/全速信號(hào)電平
4.3.5 高速信號(hào)電平
4.3.6 設(shè)備連接與斷開
1. 連接
Hub 端口的 D+、D-都有 15K 的下拉電阻,平時(shí)為低電平。全速設(shè)備內(nèi)部的 D+有 1.5K 的 上拉電阻, 低速設(shè)備內(nèi)部的 D-有 1.5K 的上拉電阻,連接到 Hub 后會(huì)導(dǎo)致 Hub 的 D+或 D-電 平變化,Hub 根據(jù)變化的引腳分辨接進(jìn)來(lái)的是全速設(shè)備還是低速設(shè)備。
高速設(shè)備一開始也是作為全速設(shè)備被識(shí)別的。
全速設(shè)備、高速設(shè)備連接時(shí), D+引腳的電平由低變高:
低速設(shè)備連接時(shí),D-引腳的電平由低變高:
2. 斷開
對(duì)于低速、全速設(shè)備,接到 Hub 時(shí)導(dǎo)致 D-或 D+引腳變?yōu)楦唠娖剑?斷開設(shè)備后, D-或 D+ 引腳變?yōu)榈碗娖剑?/p>
對(duì)于高速設(shè)備,它先作為全速設(shè)備被識(shí)別出來(lái),然后再被識(shí)別為高速設(shè)備。工作于高 速模式時(shí), D+的上拉電阻是斷開的,所以對(duì)于工作于高速模式的 USB 設(shè)備, 無(wú)法通過(guò) D+的 引腳電平變化監(jiān)測(cè)到它已經(jīng)斷開。
工作于高速模式的設(shè)備, D+、D-兩邊有 45 歐姆的下拉電阻,用來(lái)消除反射信號(hào):
當(dāng)斷開高速設(shè)備后, Hub 發(fā)出信號(hào),得到的反射信號(hào)無(wú)法衰減, Hub 監(jiān)測(cè)到這些信號(hào)后 就知道高速設(shè)備已經(jīng)斷開,內(nèi)部電路圖如下:
4.3.7 復(fù)位
從狀態(tài)切換圖上看,一個(gè) USB 設(shè)備連接后,它將會(huì)被供電, 然后被復(fù)位。當(dāng)軟件出錯(cuò) 時(shí),我們也可以發(fā)出復(fù)位信號(hào)重新驅(qū)動(dòng)設(shè)備。
那么, USB Hub 端口或 USB 控制器端口如何發(fā)出復(fù)位信號(hào)? 發(fā)出 SE0 信號(hào),并維持至少 10ms。
USB 設(shè)備看到 Reset 信號(hào)后,需要準(zhǔn)備接收"SetAddress()"請(qǐng)求; 如果它不能回應(yīng)這個(gè) 請(qǐng)求, 就是"不能識(shí)別的設(shè)備"。
4.3.8 設(shè)備速率識(shí)別
1. 低速/全速
Hub 端口的 D+、D-都有 15K 的下拉電阻,平時(shí)為低電平。全速設(shè)備內(nèi)部的 D+有 1.5K 的 上拉電阻, 低速設(shè)備內(nèi)部的 D-有 1.5K 的上拉電阻,連接到 Hub 后會(huì)導(dǎo)致 Hub 的 D+或 D-電
平變化,Hub 根據(jù)變化的引腳分辨接進(jìn)來(lái)的是全速設(shè)備還是低速設(shè)備。
2. 高速
高速設(shè)備必定兼容全速模式, 所以高速設(shè)備內(nèi)部 D+也有 1.5K 的上拉電阻, 只不過(guò)這個(gè) 電阻是可以斷開的: 工作于高速模式時(shí)要斷開它。
高速設(shè)備首先作為全速設(shè)備被識(shí)別出來(lái),然后 Hub 如何確定它是否支持高速模式? Hub 端口如何監(jiān)測(cè)一個(gè)新插入的 USB 設(shè)備能否工作于高速模式? 流程如下:
- 對(duì)于低速設(shè)備,Hub 端口不會(huì)監(jiān)測(cè)它能否工作于高速模式。低速設(shè)備不能兼容高速模式。
- Hub 端口發(fā)出 SE0 信號(hào),這就是復(fù)位信號(hào)
- USB 設(shè)備監(jiān)測(cè)到 SE0 信號(hào)后,會(huì)發(fā)出"a high-speed detection handshake"信號(hào)表示自 己能支持高速模式, 這可以細(xì)分為一下 3 種情景:
- 如果 USB 設(shè)備原來(lái)處于"suspend"狀態(tài),它檢測(cè)到 SE0 信號(hào)后, 就發(fā)出"a high- speed detection handshake"信號(hào)。
- 如果 USB 設(shè)備原來(lái)處于"non-suspend"狀態(tài),并且處于全速模式, 它檢測(cè)到 SE0 信 號(hào)后, 就發(fā)出"a high-speed detection handshake"信號(hào)。這個(gè)情景,就是一個(gè)設(shè)備剛插 到 Hub 端口時(shí)的情況,它一開始工作于全速模式。
- 如果 USB 設(shè)備原來(lái)處于"non-suspend"狀態(tài), 并且處于高速模式,它會(huì)切換回到全 速模式(重新連接 D+的上拉電阻),然后發(fā)出"a high-speed detection handshake"信號(hào)。
"a high-speed detection handshake"信號(hào),就是"高速設(shè)備監(jiān)測(cè)握手信號(hào)",既然是 握手信號(hào), 自然是有來(lái)有回:
- USB 設(shè)備維持 D+的上拉電阻,發(fā)出"Chirp K "信號(hào), 表示自己能支持高速模式
- 如果 Hub 沒監(jiān)測(cè)到"Chirp K "信號(hào), 它就知道這個(gè)設(shè)備不支持高速模式
- 如果 Hub 監(jiān)測(cè)到"Chirp K "信號(hào)后, 如果 Hub 能支持高速模式, 就發(fā)出一系列的"Chirp K"、"Chirp J"信號(hào),這是用來(lái)通知 USB 設(shè)備: Hub 也能支持高速模式。發(fā)出一系列的 "Chirp K"、"Chirp J"信號(hào)后,Hub 繼續(xù)維持 SE0 信號(hào)直到 10ms。
- USB 設(shè)備發(fā)出"Chirp K "信號(hào)后,就等待 Hub 回應(yīng)一系列的"Chirp K"、"Chirp J"信號(hào)
- 收到一系列的"Chirp K"、"Chirp J"信號(hào): USB 設(shè)備端口 D+的上拉電阻,使能高速模式
- 沒有收到一系列的"Chirp K"、"Chirp J"信號(hào): USB 設(shè)備轉(zhuǎn)入全速模式
4.3.9 數(shù)據(jù)信號(hào)
1. 低速/全速的 SOP 和 EOP
SOP:Start Of Packet,Hub 驅(qū)動(dòng) D+、D-這兩條線路從 Idle 狀態(tài)變?yōu)?K 狀態(tài)。 SOP 中 的 K 狀態(tài)就是 SYNC 信號(hào)的第 1 位數(shù)據(jù), SYNC 格式為 3 對(duì) KJ 外加 2 個(gè) K。
EOP:End Of Packet,由數(shù)據(jù)的發(fā)送方發(fā)出EOP,數(shù)據(jù)發(fā)送方驅(qū)動(dòng)D+、D-這兩條線路, 先設(shè)為 SE0 狀態(tài)并維持 2 位時(shí)間, 再設(shè)置為 J 狀態(tài)并維持 1 位時(shí)間, 最后 D+、D-變?yōu)楦咦?狀態(tài), 這時(shí)由線路的上下拉電阻使得總線進(jìn)入 Idle 狀態(tài)。
2. 高速的 SOP
高速的 EOP 比較復(fù)雜,作為軟件開發(fā)人員無(wú)需掌握。
高速模式中,Ide 狀態(tài)為:D+、D-接地。SOP 格式為: 從 Idle 狀態(tài)切換為 K 狀態(tài)。 SOP 中的 K 狀態(tài)就是 SYNC 信號(hào)的第 1 位數(shù)據(jù)。
高速模式中的 SYNC 格式為:KJKJKJKJ KJKJKJKJ KJKJKJKJ KJKJKJKK,即 15 對(duì) KJ,外 加 2 個(gè) K。
3. NRZI 與位填充
參考文章:USB 的 NRZI 信號(hào)格式, https://zhuanlan.zhihu.com/p/460018993
NRZI:Non Return Zero Inverted Code,反向不歸零編碼。 NRZI 的編碼方位為:對(duì)于 數(shù)據(jù) 0,波形翻轉(zhuǎn);對(duì)于數(shù)據(jù) 1,波形不變。
使用 NRZI,發(fā)送端可以很巧妙地把"時(shí)鐘頻率"告訴接收端: 只要傳輸連續(xù)的數(shù)據(jù) 0 即 可。在下圖中, 低速/全速協(xié)議中"Sync Pattern"的原始數(shù)據(jù)是"00000001",接收端從前面 的 7 個(gè) 0 波形就可以算出"時(shí)鐘頻率"。
使用 NRZI 時(shí), 如果傳輸?shù)臄?shù)據(jù)總是"1",會(huì)導(dǎo)致波形維持不變。如果電平長(zhǎng)時(shí)間維持 不變, 比如傳輸 100 位 1 時(shí), 如果接收方稍有偏差,就可能認(rèn)為接收到了 99 位 1、101 位 1。而 USB 中采用了 Bit-Stuffing 位填充處理,即在連續(xù)發(fā)送 6 個(gè) 1 后面會(huì)插入 1 個(gè) 0,強(qiáng) 制翻轉(zhuǎn)發(fā)送信號(hào),從而讓接收方調(diào)整頻率,同步接收。而接收方在接收時(shí)只要接收到連續(xù) 的 6 個(gè) 1 后,直接將后面的 0 刪除即可恢復(fù)數(shù)據(jù)的原貌。
NRZI 數(shù)據(jù)格式如上圖所示。
sidebar_position: 5
4.4 USB 協(xié)議層數(shù)據(jù)格式
參考資料:
- 《圈圈教你玩 USB》
- 簡(jiǎn)書 jianshu_kevin@126.com 的文章
- https://www.jianshu.com/p/3afc1eb5bd32
- https://www.jianshu.com/p/cf8e7df5ff09
- USB 協(xié)議(三):https://www.jianshu.com/p/2a6e22194cd3
- 官網(wǎng):https://www.usb.org/documents
- 《usb_20.pdf》的《chapter5 8 Protocol Layer》
- USB 的 NRZI 信號(hào)格式: https://zhuanlan.zhihu.com/p/460018993
- USB2.0 包 Packet 的組成: https://www.usbzh.com/article/detail-459.html
4.4.1 硬件拓?fù)浣Y(jié)構(gòu)
compound device :多個(gè)設(shè)備組合起來(lái),通過(guò) HUB 跟 Host 相連
composite device :一個(gè)物理設(shè)備有多個(gè)邏輯設(shè)備(multiple interfaces)
在軟件開發(fā)過(guò)程中, 我們可以忽略 Hub 的存在,硬件拓?fù)鋱D簡(jiǎn)化如下:
一個(gè)物理設(shè)備里面可能有多個(gè)邏輯設(shè)備, Hos 可以外接多個(gè)邏輯設(shè)備, 硬件拓?fù)鋱D如 下:
4.4.2 協(xié)議層
要理解協(xié)議層、理解數(shù)據(jù)如何傳輸,帶著這幾個(gè)問(wèn)題去看文檔、看視頻:
- 如何尋址設(shè)備?
- 如何表示數(shù)據(jù)方向(讀、還是寫)
- 如何確認(rèn)結(jié)果?
提前羅列出答案:
- USB 系統(tǒng)是一個(gè) Host 對(duì)應(yīng)多個(gè)設(shè)備, 要傳輸數(shù)據(jù)首先要通知設(shè)備:
- 發(fā)出 IN 令牌包: 表示想讀數(shù)據(jù),里面含有設(shè)備地址
- 發(fā)出 OUT 令牌包:表示想寫數(shù)據(jù), 里面含有設(shè)備地址
- 數(shù)據(jù)階段:
- Host 想讀數(shù)據(jù): 前面發(fā)出 IN 令牌包后, 現(xiàn)在讀取數(shù)據(jù)包
- Host 想發(fā)出數(shù)據(jù):前面發(fā)出 OUT 令牌包后, 現(xiàn)在發(fā)出數(shù)據(jù)包
- 結(jié)果如何?有握手包
- Host 想讀數(shù)據(jù), 設(shè)備可能未就緒, 就會(huì)回應(yīng) NAK 包
- Host 想寫數(shù)據(jù), 它發(fā)出數(shù)據(jù)后,設(shè)備正確接收了, 就回復(fù) ACK 包
4.4.3 字節(jié)/位傳輸順序
先傳輸最低位(LSB)。在后續(xù)文檔中,描述數(shù)據(jù)時(shí)按照傳輸順序從左到右列出來(lái)
4.4.4 SYNC 域
Host 發(fā)出 SOP 信號(hào)后, 就會(huì)發(fā)出 SYNC 信號(hào):它是一系列的、最大傳輸頻率的脈沖,接 收方使用它來(lái)同步數(shù)據(jù)。對(duì)于低速/全速設(shè)備, SYNC信號(hào)是8位數(shù)據(jù)(從做到右是00000001); 對(duì)于高速設(shè)備, SYNC信號(hào)是32位數(shù)據(jù)(從左到右是00000000000000000000000000000001)。 使用 NRZI 編碼時(shí),前面每個(gè)"0"都對(duì)應(yīng)一個(gè)跳變。
在很多文檔里, 把 SOP 和 SYNC 統(tǒng)一稱為"SYNC",它的意思是"SYNC"中含有"SOP"。
4.4.5包格式
USB 總線上傳輸?shù)臄?shù)據(jù)以包為單位。 USB 包里含有哪些內(nèi)容("域")?
- SOP:用來(lái)表示包的起始
- SYNC:用來(lái)同步時(shí)鐘
- PID:表示包的類型
- 地址:在 USB 硬件體系中, 一個(gè) Host 對(duì)應(yīng)多個(gè) Logical Device,那么 Host 發(fā)出的包, 如何確定發(fā)給誰(shuí)?
- 發(fā)給所有設(shè)備:包里不含有設(shè)備地址
- 發(fā)給某個(gè)設(shè)備:包里含有設(shè)備地址、端點(diǎn)號(hào)
- 幀號(hào)、數(shù)據(jù)等跟 PID 相關(guān)的內(nèi)容
- CRC 校驗(yàn)碼
發(fā)起一次完整的傳輸, 可能涉及多個(gè)包。那么,第 1 個(gè)包里含有設(shè)備地址、端點(diǎn)號(hào), 后續(xù)的包就沒必要包含設(shè)備地址、端點(diǎn)號(hào)。
1. PID 域
注意: 所有的 USB 文檔提到的"輸入"、"輸出",都是基于 Host 的角度, "輸出"表示從 Host 輸出到設(shè)備,"輸入"表示 Host 從設(shè)備得到數(shù)據(jù)。
有哪些 USB 包? 根據(jù)包數(shù)據(jù)里的 PID 的 bit1, bit0 可以分為 4 類:
- 令牌包(Token):01B
- 數(shù)據(jù)包(Data):11B
- 握手包(Handshake):10B
- 特殊包(Special):00B
PID 有 4 位,使用 bit1,bit0 確定分類, 使用 bit3,bit2 進(jìn)一步細(xì)分。如下表(來(lái)自 《圈圈教你玩 USB》)所示:
在 USB 包中,PID 域使用 8 位來(lái)表示,格式如下:
前 4 位表示 PID,后 4 位是對(duì)應(yīng)位的取反。接收方發(fā)現(xiàn)后 4 位不是前 4 位的取反的話, 就認(rèn)為發(fā)生了錯(cuò)誤。
2. 令牌包(Token)
令牌類 的 PID ,起 "通知作用 " ,通知誰(shuí) ?SOF 令牌包被用來(lái)通 知所有設(shè) 備, OUT/IN/SETUP 令牌包被用來(lái)通知某個(gè)設(shè)備。
對(duì)于 OUT、IN、SETUP 令牌包, 它們都是要通知到具體的設(shè)備, 格式如下:
USB 設(shè)備的地址有 7 位,格式如下:
USB 設(shè)備的端點(diǎn)號(hào)有 4 位, 格式如下:
對(duì)于 SOF 包,英文名為"Start-of-Frame marker and frame number"。對(duì)于 USB 全速 設(shè)備, Host 每 1ms 產(chǎn)生一個(gè)幀; 對(duì)于高速設(shè)備, 每 125us 產(chǎn)生一個(gè)微幀, 1 幀里有 8 個(gè)微 幀。 Host 會(huì)對(duì)當(dāng)前幀號(hào)進(jìn)行累加計(jì)數(shù), 在每幀或每微幀開始時(shí), 通過(guò) SOF 令牌包發(fā)送幀號(hào)。 對(duì)于高速設(shè)備, 每 1 毫秒里有 8 個(gè)微幀,這 8 個(gè)微幀的幀號(hào)是一樣的, 每 125us 發(fā)送一個(gè) SOF 令牌包。
SOF 令牌包格式如下:
3. 數(shù)據(jù)包
Host 使用 OUT、IN、SETUP 來(lái)通知設(shè)備:我要傳輸數(shù)據(jù)了。數(shù)據(jù)通過(guò)"數(shù)據(jù)包"進(jìn)行傳 輸。
數(shù)據(jù)包也有 4 種類型:DATA0、DATA1、DATA2、MDATA。其中 DATA2、MDATA 在高速設(shè)備 中使用。對(duì)軟件開發(fā)人員來(lái)說(shuō),我們暫時(shí)僅需了解 DATA0、DATA1。
為什么要引入 DATA0、DATA1 這些不同類型的數(shù)據(jù)包? 為了糾錯(cuò)。
Host 和設(shè)備都會(huì)維護(hù)自己的數(shù)據(jù)包切換機(jī)制,當(dāng)數(shù)據(jù)包成功發(fā)送或者接收時(shí),數(shù)據(jù)包 類型切換。當(dāng)檢測(cè)到對(duì)方使用的數(shù)據(jù)包類型不對(duì)時(shí),USB 系統(tǒng)認(rèn)為發(fā)生了錯(cuò)誤。
比如:
- Host 發(fā)送 DATA0 給設(shè)備,設(shè)備返回 ACK 表示成功接收, 設(shè)備期待下一個(gè)數(shù)據(jù)是 DATA1
- 但是 Host 沒有接收到 ACK,Host 認(rèn)為數(shù)據(jù)沒有發(fā)送成功,Host 繼續(xù)使用 DATA0 發(fā)送上 一次的數(shù)據(jù)
- 設(shè)備再次接收到 DATA0 數(shù)據(jù)包, 它就知道:哦,這是重傳的數(shù)據(jù)包
數(shù)據(jù)包格式如下:
對(duì)于全速設(shè)備, 數(shù)據(jù)包中的數(shù)據(jù)做大是 1023 字節(jié);對(duì)于全速設(shè)備, 數(shù)據(jù)包中的數(shù)據(jù)做 大是 1024 字節(jié)。
4. 握手包
握手包有 4 類: ACK、NAK、STALL、NYET
- ACK:數(shù)據(jù)接收方用來(lái)回復(fù)發(fā)送方,表示正確接收到了數(shù)據(jù)并且有足夠的空間保存數(shù)據(jù)。
- NAK:Host 發(fā)送數(shù)據(jù)給設(shè)備時(shí), 設(shè)備可以回應(yīng) NAK 表示"我還沒準(zhǔn)備好,沒辦法接收數(shù)據(jù)"; Host 想讀取設(shè)備的數(shù)據(jù)時(shí), 設(shè)備可以回復(fù) NAK 表示"我沒有數(shù)據(jù)給你"。
- STALL:表示發(fā)生了錯(cuò)誤,比如設(shè)備無(wú)法執(zhí)行這個(gè)請(qǐng)求(不支持該斷點(diǎn)等待)、斷點(diǎn)已經(jīng)掛起。設(shè)備返回 STALL 后,需要主機(jī)進(jìn)行干預(yù)才能接觸 STALL 狀態(tài)。
- NYET:僅適用于高速設(shè)備。 Host 可以發(fā)出 PING 包用來(lái)確認(rèn)設(shè)備有數(shù)據(jù),設(shè)備可以回應(yīng) NYET 表示"還沒呢"。Hub 也可以回應(yīng) NYET 表示低速/全速傳輸還沒完結(jié)。
4.4.6 傳輸細(xì)節(jié)
1. 傳輸(Transfer)和事務(wù)(Transaction)
USB 傳輸?shù)幕締挝皇前?Packet),包的類型由PID 表示。 一個(gè)單純的包,是無(wú)法傳輸 完整的數(shù)據(jù)。
為什么?比如想輸出數(shù)據(jù),可以發(fā)出 OUT 令牌包, OUT 令牌包可以指定目的地。但是數(shù) 據(jù)如何傳輸呢? 還需要發(fā)出 DATA0 或 DATA1 數(shù)據(jù)包。設(shè)備收到數(shù)據(jù)后, 還要回復(fù)一個(gè) ACK 握手包。
所以,完整的數(shù)據(jù)傳輸, 需要涉及多個(gè)包:令牌包、數(shù)據(jù)包、握手包。這個(gè)完整的數(shù) 據(jù)傳輸過(guò)程,被稱為事務(wù)(Transaction)。
有些事務(wù)需要握手包,有些事務(wù)不需要握手包,有些事務(wù)可以傳輸很大的數(shù)據(jù),有些 事務(wù)只能傳輸小量數(shù)據(jù)。
- 有四類事務(wù):
- 批量事務(wù):用來(lái)傳輸大量的數(shù)據(jù),數(shù)據(jù)的正確性有保證,時(shí)效沒有保證。
- 中斷事務(wù):用來(lái)傳輸周期性的、小量的數(shù)據(jù), 數(shù)據(jù)的正確性和時(shí)效都有保證。 ③ 實(shí)時(shí)事務(wù):用來(lái)傳輸實(shí)時(shí)數(shù)據(jù), 數(shù)據(jù)的正確性沒有保證,時(shí)效有保證。
- 建立事務(wù):跟批量事務(wù)類似,只不過(guò)令牌包是 SETUP 令牌包。
- 有四類傳輸(Transfer):
- 批量傳輸:就是使用批量事務(wù)實(shí)現(xiàn)數(shù)據(jù)傳輸, 比如 U 盤。
- 中斷傳輸:就是使用中斷事務(wù)實(shí)現(xiàn)數(shù)據(jù)傳輸, 比如鼠標(biāo)。
- 實(shí)時(shí)傳輸:就是使用實(shí)時(shí)事務(wù)實(shí)現(xiàn)數(shù)據(jù)傳輸, 比如攝像頭。
- 控制傳輸:由建立事務(wù)、批量事務(wù)組成,所有的 USB 設(shè)備都必須支持控制傳輸, 用于" 識(shí)別/枚舉"
- 暫時(shí)記住這個(gè)關(guān)系:
- BIT 組成域(Field)
- 域組成包(Packet)
- 包組成事務(wù)(Transaction)
- 事務(wù)組成傳輸(Transfer)
2. 過(guò)程(stage)和階段(phase)
事務(wù)由多個(gè)包組成, 比如 Host 要發(fā)送數(shù)據(jù)給設(shè)備,這就會(huì)涉及很多個(gè)包:
- Host 發(fā)出 OUT 令牌包, 表示要發(fā)數(shù)據(jù)給哪個(gè)設(shè)備
- Host 發(fā)出 DATA0 數(shù)據(jù)包
- 設(shè)備收到數(shù)據(jù)后, 回應(yīng) ACK 包
這個(gè)完整的事務(wù)涉及 3 個(gè)包(Packet),分為 3 個(gè)階段(Phase):
- 令牌階段(Token phase):由令牌包實(shí)現(xiàn)
- 數(shù)據(jù)階段(Data phase):由數(shù)據(jù)包實(shí)現(xiàn)
- 握手階段(Handshake phase):由握手包實(shí)現(xiàn)
事務(wù)由包組成, 這些包分別處于 3 個(gè)階段(phase):令牌階段,數(shù)據(jù)階段, 握手階段。
對(duì)于批量傳輸、中斷傳輸、實(shí)時(shí)傳輸,它們分別由一個(gè)事務(wù)組成,不再細(xì)分為若干個(gè) 過(guò)程。
但是控制傳輸由多個(gè)事務(wù)組成,這些事務(wù)分別處于 3 個(gè)過(guò)程: 建立過(guò)程(stage)、數(shù)據(jù) 過(guò)程(stage)、狀態(tài)過(guò)程(stage)。
總結(jié)起來(lái)就是:
- 控制傳輸由多個(gè)過(guò)程(stage)組成, 每個(gè)過(guò)程由一個(gè)事務(wù)來(lái)實(shí)現(xiàn)
- 每個(gè)事務(wù)由多個(gè)階段(phase)組成, 每個(gè)階段有一個(gè)包來(lái)實(shí)現(xiàn)
3. 批量傳輸
批量傳輸用批量事務(wù)來(lái)實(shí)現(xiàn),用于傳輸大量的數(shù)據(jù), 數(shù)據(jù)的正確性有保證, 時(shí)效沒有 保證。
批量事務(wù)由 3 個(gè)階段(phase)組成: 令牌階段、數(shù)據(jù)階段、握手階段。每個(gè)階段都是一 個(gè)完整的包,含有 SOP、SYNC、PID、EOP。
下圖中各個(gè)矩形框就對(duì)應(yīng)一個(gè)完整的包。
《圈圈教你玩 USB》中有詳細(xì)的示例:
4.中斷傳輸
中斷傳輸用中斷事務(wù)來(lái)實(shí)現(xiàn),用于傳輸小量的、周期性的數(shù)據(jù),數(shù)據(jù)的正確性和時(shí)效 都有保證。
中斷事務(wù)由 3 個(gè)階段(phase)組成: 令牌階段、數(shù)據(jù)階段、握手階段。每個(gè)階段都是一 個(gè)完整的包,含有 SOP、SYNC、PID、EOP。
下圖中各個(gè)矩形框就對(duì)應(yīng)一個(gè)完整的包。
中斷事務(wù)跟批量事務(wù)非常類似,Host 使用它來(lái)周期性地讀數(shù)據(jù)、寫數(shù)據(jù)。
以鼠標(biāo)為例,我們需要及時(shí)獲得鼠標(biāo)的數(shù)據(jù), 不及時(shí)的話你會(huì)感覺鼠標(biāo)很遲鈍。但是 USB 協(xié)議中并沒有中斷功能,它使用"周期性的讀、寫"來(lái)實(shí)現(xiàn)及時(shí)性。具體過(guò)程如下:
- Host 每隔 n 毫秒發(fā)出一個(gè) IN 令牌包
- 鼠標(biāo)有數(shù)據(jù)的話,發(fā)出 DATA0 或 DATA1 數(shù)據(jù)包給 Host;鼠標(biāo)沒有數(shù)據(jù)的話,發(fā)出 NAK 給 Host。
中斷事務(wù)的優(yōu)先級(jí)比批量事務(wù)更高,它要求實(shí)時(shí)性,而批量事務(wù)不要求實(shí)時(shí)性。
5.實(shí)時(shí)傳輸
實(shí)時(shí)傳輸用實(shí)時(shí)事務(wù)來(lái)實(shí)現(xiàn), 用于傳輸實(shí)時(shí)數(shù)據(jù), 對(duì)數(shù)據(jù)的正確性沒有要求。
實(shí)時(shí)事務(wù)由 2 個(gè)階段(phase)組成: 令牌階段、數(shù)據(jù)階段。每個(gè)階段都是一個(gè)完整的包, 含有 SOP、SYNC、PID、EOP。
實(shí)時(shí)事務(wù)不需要握手階段,一個(gè)示例的場(chǎng)景是:為了傳輸攝像頭的實(shí)時(shí)數(shù)據(jù),偶爾的 數(shù)據(jù)錯(cuò)誤是可以忍受的,大不了出現(xiàn)短暫的花屏。如果為了解決花屏而重傳數(shù)據(jù), 那就會(huì)
導(dǎo)致后續(xù)畫面被推遲,實(shí)時(shí)性無(wú)法得到保證。
下圖中各個(gè)矩形框就對(duì)應(yīng)一個(gè)完整的包。
實(shí)時(shí)事務(wù)跟中斷事務(wù)非常類似,Host 也會(huì)周期性的發(fā)起實(shí)時(shí)事務(wù),主要區(qū)別在于:
- 實(shí)時(shí)事務(wù)不要求準(zhǔn)確性,沒有握手階段
- 實(shí)時(shí)事務(wù)傳輸?shù)臄?shù)據(jù)量比較大, 中斷事務(wù)傳輸?shù)臄?shù)據(jù)量比較小
6. 控制傳輸
在使用批量傳輸時(shí), 使用 IN 令牌包或 OUT 令牌包表示數(shù)據(jù)傳輸方向。
控制傳輸?shù)牧钆瓢肋h(yuǎn)是 SETUP,怎么分辨是讀數(shù)據(jù), 還是寫數(shù)據(jù)? 發(fā)出 SETUP 令牌包 后,還要發(fā)出 DATA0 數(shù)據(jù)包,根據(jù)數(shù)據(jù)的內(nèi)容來(lái)確定后續(xù)是讀數(shù)據(jù),還是寫數(shù)據(jù)。這個(gè)過(guò) 程稱為"建立事務(wù)"(SETUP Transaction)
但是控制傳輸由多個(gè)事務(wù)組成,這些事務(wù)分別處于 3 個(gè)過(guò)程: 建立過(guò)程(stage)、數(shù)據(jù) 過(guò)程(stage)、狀態(tài)過(guò)程(stage)。
- 建立過(guò)程(stage),使用 SETUP 事務(wù):Host 發(fā)出 SETUP 令牌包、DATA0 數(shù)據(jù)包、得到 ACK 握手包
- 數(shù)據(jù)過(guò)程(stage),使用批量事務(wù):
- 對(duì)于輸出:Host 發(fā)出 OUT 令牌包,發(fā)出 DATA0、DATA1 數(shù)據(jù)包、得到 ACK 握手包
- 對(duì)于輸入:Host 發(fā)出 IN 令牌包,讀到 DATA0、DATA1 數(shù)據(jù)包、發(fā)出 ACK 握手包 ③ 狀態(tài)過(guò)程(stage),使用批量事務(wù):
- 對(duì)于輸出:Host 發(fā)出 IN 令牌包,讀到 DATA1 數(shù)據(jù)包,發(fā)出 ACK 握手包 b. 對(duì)于輸入:Host 發(fā)出 OUT 令牌包,發(fā)出 DATA1 數(shù)據(jù)包,等待 ACK 握手包
上圖中的每一個(gè)方框,都是一個(gè)完整的事務(wù), 含有: Token Packet、Data Packet、 Handshake Packet。
4.4.7 使用工具體驗(yàn)數(shù)據(jù)格式
LeCroy(力科)成立于 1964 年, 是一家專業(yè)生產(chǎn)示波器廠家。旗下生產(chǎn)有數(shù)字示波器、 SDA 系列數(shù)字示波器、混合信號(hào)示波器、模塊化儀器、任意波形發(fā)生器。
官網(wǎng)是:https://teledynelecroy.com/,似乎無(wú)法注冊(cè)新用戶,無(wú)法下載軟件。 可以在搜索引擎里搜"usbprotocolsuite"。
安裝"usbprotocolsuite"后, 可以在文檔目錄里找打很多示程序(后綴名為 usb):
使用"usbprotocolsuite"打開這些文件,即可體驗(yàn) USB 數(shù)據(jù)傳輸:
4.5 USB 描述符
4.5.1
1. USB 設(shè)備狀態(tài)切換圖
4.5.2 標(biāo)準(zhǔn)設(shè)備請(qǐng)求
1.SETUP事務(wù)的數(shù)據(jù)格式
Host 使用控制傳輸來(lái)識(shí)別設(shè)備、設(shè)置設(shè)備地址、啟動(dòng)設(shè)備的某些特性, 對(duì)于控制傳輸, 它首先發(fā)出"setup 事務(wù)",如下:
在"setup 事務(wù)"中,
- SETUP 令牌包:用來(lái)通知設(shè)備, "要開始傳輸了"
- DATA0 數(shù)據(jù)包:它含有固定的格式, 用來(lái)告訴設(shè)備"是讀還是寫"、"讀什么"、"寫什么"
Host 通過(guò) DATA0 數(shù)據(jù)包發(fā)送 8 字節(jié)數(shù)據(jù)給設(shè)備,它的格式如下圖所示:
2. 標(biāo)準(zhǔn)設(shè)備請(qǐng)求
控制傳輸?shù)慕⑹聞?wù)中, 可以使用下列格式的數(shù)據(jù):
上表中各個(gè)"宏"取值如下:
3. 設(shè)備/配置/接口/端點(diǎn)
在 SETUP 事務(wù)的數(shù)據(jù)里, 表示了要訪問(wèn)的是什么: Device?Interface?Endpoint?
對(duì)于一個(gè)USB 設(shè)備, 它可以多種配置(Configuration)。比如4G 上網(wǎng)卡就有 2 種配置: U 盤、上網(wǎng)卡。第 1 次把 4G 上網(wǎng)卡插入電腦時(shí),它是一個(gè) U 盤,可以按照里面的程序。裝 好程序后, 把它再次插入電腦,它就是一個(gè)上網(wǎng)卡。驅(qū)動(dòng)程序可以選擇讓它工作于哪種配 置,同一時(shí)間只能有一種配置。大多數(shù)的 USB 設(shè)備只有一種配置。
一個(gè)配置下,可以有多個(gè)接口(Interface),接口等同于功能(Function)。比如 USB 耳 機(jī)有兩個(gè)接口(功能):聲音收發(fā)、按鍵控制。
一個(gè)接口, 可能有多個(gè)設(shè)置(Setting),比如默認(rèn)設(shè)置下它使用較低的帶寬, 可以選擇 其他設(shè)置以使用更高帶寬。
一個(gè)接口, 由一個(gè)或多個(gè)端點(diǎn)(Endpoint)組成。端點(diǎn) 0 屬于整個(gè)設(shè)備的, 端點(diǎn) 0 是雙 向的。接口還可以有其他端點(diǎn), 這些端點(diǎn)是單向的, 要么是批量(Bulk)端點(diǎn)、要么是中斷 (Interrupt)端點(diǎn)、要么是同步(Isochronous)端點(diǎn)。
4.5.3 描述符
怎么描述設(shè)備、配置、接口、端點(diǎn)?使用描述符(Descriptors),有設(shè)備描述符、配置 描述符、接口描述符、端點(diǎn)描述符。所謂描述符,就是一些格式化的數(shù)據(jù), 用來(lái)描述信息。
一個(gè) USB 設(shè)備:
- 只有一個(gè)設(shè)備描述符:用來(lái)表示設(shè)備的 ID、它有多少個(gè)配置、它的端點(diǎn) 0一次最大能傳 輸多少字節(jié)數(shù)據(jù)
- 可能有多個(gè)配置描述符:用來(lái)表示它有多少個(gè)接口、供電方式、最大電流
- 一個(gè)配置描述符下面,可能有多個(gè)接口描述符:用來(lái)表示它是哪類接口、有幾個(gè)設(shè)置 (Setting)、有幾個(gè)端點(diǎn)
- 一個(gè)接口描述符符下面,可能有多個(gè)端點(diǎn)描述符: 用來(lái)表示端點(diǎn)號(hào)、方向(IN/OUT)、類 型(批量/中斷/同步)
還有一些字符串描述符(String descriptors),它用可讀的文字來(lái)描述設(shè)備,是可選 的。
1. 設(shè)備描述符
2. 配置描述符
3. 接口描述符
4. 端點(diǎn)描述符
5.示例
在 Ubuntu 中可以執(zhí)行 lsusb -v查看 USB 設(shè)備的描述符信息:
book@100ask:~$ sudo lsusb -v
[sudo] password for book:
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 9 Hub
bDeviceSubClass 0 Unused
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 64
idVendor 0x1d6b Linux Foundation
idProduct 0x0002 2.0 root hub
bcdDevice 5.04
iManufacturer 3 Linux 5.4.0-124-generic ehci_hcd
iProduct 2 EHCI Host Controller
iSerial 1 0000:02:03.0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 25
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 12
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 6
wHubCharacteristic 0x000a
No power switching (usb 1.0)
Per-port overcurrent protection
bPwrOn2PwrGood
bHubContrCurrent DeviceRemovable PortPwrCtrlMask
Hub Port Status:
Port 1: 0000.0100 Port 2: 0000.0100 Port 3: 0000.0100 Port 4: 0000.0100 Port 5: 0000.0100
Port 6: 0000.0100
10 * 2 milli seconds
0 milli Ampere
0x00
0xff
power
power
power
power
power
power
Device Status: 0x0001
Self Powered
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 9 Hub
bDeviceSubClass 0 Unused
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 8
idVendor 0x0e0f VMware, Inc.
idProduct 0x0002 Virtual USB Hub
bcdDevice 1.00
iManufacturer 1 VMware, Inc.
iProduct 2 VMware Virtual USB Hub
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 25
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 1 VMware, Inc.
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 1 VMware, Inc.
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0001 1x 1 bytes
bInterval 255
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 7
wHubCharacteristic 0x0009
Per-port power switching
Per-port overcurrent protection
bPwrOn2PwrGood
bHubContrCurrent DeviceRemovable PortPwrCtrlMask
Hub Port Status:
Port 1: 0000.0100 Port 2: 0000.0100 Port 3: 0000.0100 Port 4: 0000.0100 Port 5: 0000.0100 Port 6: 0000.0100
Port 7: 0000.0100
50 * 2 milli seconds
100 milli Ampere
0x00
0xfe
power
power
power
power
power
power
power
Device Status: 0x2909
Self Powered
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0e0f VMware, Inc.
idProduct 0x0003 Virtual Mouse
bcdDevice 1.03
iManufacturer 1 VMware
iProduct 2 VMware Virtual USB Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 1 VMware
bmAttributes 0xc0
Self Powered
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 2 Mouse
iInterface 1 VMware
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 46
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 1
Device Status: 0x0001
Self Powered
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 9 Hub
bDeviceSubClass 0 Unused
bDeviceProtocol 0 Full speed (or root) hub
bMaxPacketSize0 64
idVendor 0x1d6b Linux Foundation
idProduct 0x0001 1.1 root hub
bcdDevice 5.04
iManufacturer 3 Linux 5.4.0-124-generic uhci_hcd
iProduct 2 UHCI Host Controller
iSerial 1 0000:02:00.0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 25
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xe0
Self Powered
Remote Wakeup
MaxPower 0mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 9 Hub
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0 Full speed (or root) hub
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0002 1x 2 bytes
bInterval 255
Hub Descriptor:
bLength 9
bDescriptorType 41
nNbrPorts 2
wHubCharacteristic 0x000a
No power switching (usb 1.0)
Per-port overcurrent protection
bPwrOn2PwrGood 1 * 2 milli seconds
bHubContrCurrent 0 milli Ampere
DeviceRemovable 0x00
PortPwrCtrlMask 0xff
Hub Port Status:
Port 1: 0000.0103 power enable connect
Port 2: 0000.0107 power suspend enable connect
Device Status: 0x0001
Self Powered
4.5.4 設(shè)備枚舉過(guò)程示例
使用"usbprotocolsuite"打開,可以看到設(shè)備的枚舉過(guò)程:
- 使用控制傳輸,讀取設(shè)備信息(設(shè)備描述符):第一次讀取時(shí), 它只需要得到 8 字節(jié)數(shù)據(jù), 因?yàn)榈?8 個(gè)數(shù)據(jù)表示端點(diǎn) 0 能傳輸?shù)淖畲髷?shù)據(jù)長(zhǎng)度。
- Host 分配地址給設(shè)備, 然后把新地址發(fā)給設(shè)備:
- 使用新地址, 重新讀取設(shè)備描述符, 設(shè)備描述符長(zhǎng)度是 18:
- 讀取配置描述符: 它傳入的長(zhǎng)度是 255,想一次性把當(dāng)前配置描述符、它下面的接口描 述符、端點(diǎn)描述符全部讀出來(lái)
- 讀取字符描述符
4.6 USBX 組件
4.6.1 Azure RTOS 介紹
Azure RTOS 平臺(tái)是運(yùn)行時(shí)解決方案的集合,包括 Azure RTOS ThreadX、Azure RTOS NetX 和 NetX Duo、Azure RTOS FileX、Azure RTOS GUIX 和 Azure RTOS USBX。
Azure RTOS ThreadX 是專用于深度嵌入式應(yīng)用程序的高級(jí)實(shí)時(shí)操作系統(tǒng) (RTOS)。 Azure RTOS ThreadX 具有多種優(yōu)勢(shì),其中包括高級(jí)調(diào)度設(shè)施、消息傳遞、中斷管理和消息 服務(wù)。 Azure RTOS ThreadX 具有許多高級(jí)功能, 其中包括 picokernel 體系結(jié)構(gòu)、搶占 式閾值調(diào)度、事件鏈和一系列豐富的系統(tǒng)服務(wù)。
USBX 是 Azure?RTOS USB 主機(jī)和 USB 設(shè)備嵌入式堆棧。它與 ThreadX 緊密耦合。在某些 類中, 它需要 FileX 和 NetX Duo 堆棧。它允許使用具有多種配置的 USB 設(shè)備、復(fù)合設(shè)備和 USB OTG 進(jìn)行操作。它支持 USB 電源管理。
USBX 為 USB 主機(jī)和 USB 設(shè)備堆棧提供了大量的 USB 類。 一旦低級(jí)驅(qū)動(dòng)程序能夠響應(yīng) USBX 請(qǐng)求, 模塊化架構(gòu)就可以更容易地移植到不同的 USB 硬件 IP 上。
所有 STM32 USB IP(主機(jī)、設(shè)備、 OTG、高速和全速) 均由 USBX 通過(guò)通用 STM32 HAL 驅(qū)動(dòng)程序 API 透明支持。
4.6.2 USBX 層次
參考資料:
https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX
USBX 分為三層, 如下圖所示:
- 控制器層:最底層,USB 設(shè)備控制器的驅(qū)動(dòng)程序,通常是 HAL 庫(kù)
- stack layer:實(shí)現(xiàn) USB 設(shè)備的基本操作,比如描述符的操作、使用 endpoint 進(jìn)行數(shù)據(jù) 傳輸
- Class layer:實(shí)現(xiàn)各類 USB 設(shè)備的操作,比如 HID 設(shè)備、音頻設(shè)備、虛擬串口,給 APP 提供接口
在 STM32 的固件中, 可以看到 USBX 目錄,比如:
移植 Controller layer、stack layer、Class layer 并不復(fù)雜, 重點(diǎn)在于 2 點(diǎn):
- 怎么初始化硬件以確保 Controller layer 可以正常運(yùn)行
- 怎么編寫 APP:提供設(shè)備信息、傳輸數(shù)據(jù)
4.6.3 USBX 的基本配置
USBX 依賴于 Azure?RTOS ThreadX,但是也可以單獨(dú)使用 USBX,這需要配置。通常在 “ux_user.h”里進(jìn)行配置,配置項(xiàng)如下:
- 使用單獨(dú)模式或 RTOS 模式:
/* Defined, this macro will enable the standalone mode of usbx. */
#define UX_STANDALONE
當(dāng)沒有定義“UX_STANDALONE”時(shí)就是使用 RTOS 模式, 可以使用 ThreadX 提供的互斥 量函數(shù)實(shí)現(xiàn)阻塞式讀寫(“blocking”), 比如對(duì)于 USB 虛擬串口, 可以使用如下函數(shù):
UINT _ux_device_class_cdc_acm_read(UX_SLAVE_CLASS_CDC_ACM *cdc_acm, UCHAR *buffer,
ULONG requested_length, ULONG *actual_length);
UINT _ux_device_class_cdc_acm_write(UX_SLAVE_CLASS_CDC_ACM *cdc_acm, UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
這 2 個(gè)函數(shù)發(fā)起數(shù)據(jù)傳輸,在傳輸過(guò)程中線程阻塞,傳輸完成后線程被喚醒。
當(dāng)定義“UX_STANDALONE”時(shí)就是使用單獨(dú)模式, 不能再使用上面的阻塞函數(shù),而要使 用非阻塞的函數(shù)(non-blocke):
UINT _ux_device_class_cdc_acm_read_run(UX_SLAVE_CLASS_CDC_ACM *cdc_acm,
UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
UINT _ux_device_class_cdc_acm_write_run(UX_SLAVE_CLASS_CDC_ACM *cdc_acm,
UCHAR *buffer, ULONG requested_length, ULONG *actual_length);
它們只是發(fā)起傳輸,然后就即刻返回。需要提供回調(diào)函數(shù),在回調(diào)函數(shù)里分辨數(shù)據(jù)是 否傳輸完成。
- 非阻塞模式:
/* Defined, this macro disables CDC ACM non-blocking transmission support. */ //#define UX_DEVICE_CLASS_CDC_ACM_TRANSMISSION_DISABLE
定義 UX_DEVICE_CLASS_CDC_ACM_TRANSMISSION_DISABLE 是,就禁止了“非阻塞模式”, 這時(shí)只能使用基于 RTOS 的阻塞函數(shù)。
換句話說(shuō), 要使用單獨(dú)模式的非阻塞函數(shù), 就不能定義這個(gè)配置項(xiàng)。
- USB HOST/Device 模式
/* Defined, this value will only enable the host side of usbx. */
/* #define UX_HOST_SIDE_ONLY */
/* Defined, this value will only enable the device side of usbx. */
#define UX_DEVICE_SIDE_ONLY
本課程定義“UX_DEVICE_SIDE_ONLY”, 僅作為 USB Device。
4.7 移植 USBX 實(shí)現(xiàn)虛擬串口
本節(jié)程序源碼為“3_程序源碼01_視頻配套的源碼 4-7_移植 USBX 實(shí)現(xiàn)虛擬串口 uart_usb.7z”,在上一節(jié)代碼 uart_rtos.7z 的基礎(chǔ)上修改得來(lái)。
移植 Controller layer、stack layer、Class layer 并不復(fù)雜, 重點(diǎn)在于 2 點(diǎn):
- 怎么初始化硬件以確保 Controller layer 可以正常運(yùn)行
- 怎么編寫 APP:提供設(shè)備信息、傳輸數(shù)據(jù)
4.7.1 配置 USB
4.7.2 添加 USBX 代碼
1. 復(fù)制代碼
找到固件庫(kù),如下:
把 usbx 整個(gè)目錄復(fù)制到工程“MiddlewaresThird_Party”目錄下, 如下:
2. 添加進(jìn)工程
需要添加 USBX 的 3 層源碼。
先仿照下圖添加“Class layer”源碼,添加含有“ux_device_class_cdc_acm ”前綴 的 C 文件:
再仿照下圖添加“stack layer”源碼,可以從文件名的前面看出它們的作用, 比如 “ ux_device_stack ”表示這是 stack 源碼,“ ux_utility ”表示這 是 輔助 函數(shù) , “ux_system”表示是這是系統(tǒng)函數(shù):
最后仿照下圖添加“Controller layer”, 添加“ux_dcd_stm32”前綴的 C 文件:
4.7.3 添加 USBX APP 代碼
參考工程:
- GIT 倉(cāng)庫(kù):https://github.com/STMicroelectronics/STM32CubeH5.git,
- 工程路徑:
STM32CubeH5ProjectsNUCLEO-H563ZIApplicationsUSBXUx_Device_HID_CDC_ACM
在網(wǎng)盤資料中, 找到如下目錄:
把 app 文件夾復(fù)制到工程的“MiddlewaresThird_Partyusbx”目錄下, 如下圖所示:
各個(gè)文件的作用為:
- ux_user.h:配置 USBX
- ux_stm32_config.h:里面含有配置項(xiàng), 表示 STM32 支持多少個(gè) endpoint
- ux_device_descriptors.c/h:USB 虛擬串口的描述符信息
- ux_device_cdc_acm.c:USB 串口的 Activate/DeActivate 函數(shù)
- app_usbx_device.c:調(diào)用 stack layer 函數(shù), 模擬 USB 串口
在工程里添加上述文件, 如下圖所示:
4.7.4 修改 usb.c
使用 STM32CubeMX 配置 usb 后生成的 usb.c 里,只是初始化了 USB 控制器,并未啟動(dòng) 它,也沒有跟 USBX 建立聯(lián)系, 需要修改代碼。
代碼如下:
23 /* USER CODE BEGIN 0 */
24 #include "ux_port.h"
25 #include "ux_device_descriptors.h"
26 #include "ux_dcd_stm32.h"
27 /* USER CODE END 0 */
/* 省略 */
33 void MX_USB_PCD_Init(void)
34 {
35
36 /* USER CODE BEGIN USB_Init 0 */
37 UINT MX_USBX_Device_Init(void);
38 MX_USBX_Device_Init();
39
40 /* USER CODE END USB_Init 0 */
41
42 /* USER CODE BEGIN USB_Init 1 */
43
44 /* USER CODE END USB_Init 1 */
45 hpcd_USB_DRD_FS.Instance = USB_DRD_FS;
46 hpcd_USB_DRD_FS.Init.dev_endpoints = 8;
47 hpcd_USB_DRD_FS.Init.speed = USBD_FS_SPEED;
48 hpcd_USB_DRD_FS.Init.phy_itface = PCD_PHY_EMBEDDED;
49 hpcd_USB_DRD_FS.Init.Sof_enable = DISABLE;
50 hpcd_USB_DRD_FS.Init.low_power_enable = DISABLE;
51 hpcd_USB_DRD_FS.Init.lpm_enable = DISABLE;
52 hpcd_USB_DRD_FS.Init.battery_charging_enable = DISABLE;
53 hpcd_USB_DRD_FS.Init.vbus_sensing_enable = DISABLE;
54 hpcd_USB_DRD_FS.Init.bulk_doublebuffer_enable = DISABLE;
55 hpcd_USB_DRD_FS.Init.iso_singlebuffer_enable = DISABLE;
56 if (HAL_PCD_Init(&hpcd_USB_DRD_FS) != HAL_OK)
57 {
58 Error_Handler();
59 }
60 /* USER CODE BEGIN USB_Init 2 */
61
62 HAL_PWREx_EnableVddUSB();
63 HAL_PWREx_EnableUSBVoltageDetector();
64
65 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x00, PCD_SNG_BUF, 0x14);
66 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x80, PCD_SNG_BUF, 0x54);
67 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPINCMD_ADDR, PCD_SNG_BUF, 0x94); 68 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPOUT_ADDR, PCD_SNG_BUF, 0xD4);
69 HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, USBD_CDCACM_EPIN_ADDR, PCD_SNG_BUF, 0x114); 70 ux_dcd_stm32_initialize((ULONG)USB_DRD_FS, (ULONG)&hpcd_USB_DRD_FS);
71
72 HAL_PCD_Start(&hpcd_USB_DRD_FS);
73
74 /* USER CODE END USB_Init 2 */
75 }
第 38 行:調(diào)用 USBX 的函數(shù), 添加 USB 串口的支持。
第 62~63 行:使能 USB 控制器的電源。
第 65 69 行:設(shè)置 endpoint 的“Packet Buffer Memory”,這個(gè)概念可以參考:
http://www.51hei.com/bbs/dpj-40953-1.html。
第 70 行 : 把 STM32 USB 控 制 器 的 句 柄 , 傳 給 USBX 系 統(tǒng) ,
“usbx_stm32_device_controllers”的代碼會(huì)使用這個(gè)句柄來(lái)操作硬件。 第 72 行:?jiǎn)?dòng) USB 控制器。
4.7.5 創(chuàng)建 USBX 任務(wù)
使 用 單 獨(dú) 模 式 (STANDALONE ) 時(shí) , 需 要 創(chuàng) 建 一 個(gè) 任 務(wù) , 不 斷 運(yùn) 行 “_ux_system_tasks_run ”函數(shù)。以下代碼是在 FreeRTOS 的默認(rèn)任務(wù)里運(yùn)行和這個(gè)函數(shù):
26 /* USER CODE BEGIN Includes */
27 #include "stdio.h"
28 #include "draw.h"
29 #include "ux_api.h"
30 /* USER CODE END Includes */
/* 省略 */
195 /* USER CODE END Header_StartDefaultTask */
196 void StartDefaultTask(void *argument)
197 {
198 /* USER CODE BEGIN defaultTask */
199 /* Infinite loop */
200 for(;;)
201 {
202 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET);
203 vTaskDelay(500);
204
205 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);
206 vTaskDelay(500);
207 ux_system_tasks_run();
208 }
209 /* USER CODE END defaultTask */
210 }
第 29 行,包含 USBX 的頭文件。
第 207 行, 調(diào)用 USBX 的系統(tǒng)函數(shù)。
4.7.6 設(shè)置 MDK-ARM 工程
如下圖配置:
- 添加宏開關(guān): UX_INCLUDE_USER_DEFINE_FILE(圖中標(biāo)號(hào) 2)
- 添加頭文件目錄(圖中標(biāo)號(hào) 5)
4.7.7 添加使用串口的代碼
在“CoreSrcapp_freertos.c”里添加 USB 串口的發(fā)送測(cè)試代碼:
26 /* USER CODE BEGIN Includes */
27 #include "stdio.h"
28 #include "draw.h"
29 #include "ux_api.h"
30 /* USER CODE END Includes */
/* 省略 */
69 static void SPILCDTaskFunction( void *pvParameters )
70 {
71 char buf[100];
72 int cnt = 0;
73
74 while (1)
75 {
76 sprintf(buf, "USB Serial Send Test : %drn", cnt++);
77 //Draw_String(0, 0, buf, 0x0000ff00, 0);
78
79 int ux_device_cdc_acm_send(uint8_t *datas, uint32_t len, uint32_t timeout);
80 ux_device_cdc_acm_send((uint8_t *)buf, strlen(buf), 1000);
81 vTaskDelay(1000);
82 }
83 }
第 29 行:包含頭文件。
第 79~80 行:使用 USB 串口發(fā)送數(shù)據(jù)。
在“MiddlewaresThird_Partyusbxappux_device_cdc_acm.c”中,有如下代碼:
111 static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length)
112 {
113 int Draw_String(uint32_t x, uint32_t y, char *str, uint32_t front_color, uint32_t
back_color);
114 if (status == UX_SUCCESS)
115 {
116 data_pointer[length] = '?';
117 Draw_String(0, 0, (char *)data_pointer, 0x0000ff00, 0);
118 }
119 return 0;
120 }
當(dāng) USB 串口收到數(shù)據(jù)后, ux_device_class_cdc_acm_read_callback 函數(shù)被調(diào)用。 第 117 行把接收到的數(shù)據(jù)在 LCD 上顯示處來(lái)。
4.7.8 上機(jī)實(shí)驗(yàn)
燒寫運(yùn)行程序后,接上 USB 線,在電腦上可以識(shí)別出 USB 串口,查看設(shè)備管理器,可 以看到如下設(shè)備:
使用串口工具打開這個(gè)串口, 可以連續(xù)不斷接收到數(shù)據(jù),如下所示:
在串口工具上發(fā)送數(shù)據(jù)時(shí),在板子的 LCD 上會(huì)有顯示。
4.8 虛擬串口源碼分析與改造
本節(jié)程序源碼為“3_程序源碼?1_視頻配套的源碼 4-8_虛擬串口源碼分析與改造 uart_usb_freertos.7z”,在上一節(jié)代碼 uart_usb.7z 的基礎(chǔ)上修改得來(lái)。
4.8.1 描述符的設(shè)置
在“MiddlewaresThird_Partyusbxappux_device_descriptors.c”有設(shè)備描述符、 配置描述符、接口描述符、端點(diǎn)描述符的定義。
比如, 設(shè)備描述符在如下代碼中設(shè)置:
配置描述符在如下代碼中設(shè)置:
4.8.2 數(shù)據(jù)收發(fā)函數(shù)
涉及文件為:demoMiddlewaresThird_Partyusbxappux_device_cdc_acm.c。 開發(fā)板通過(guò) USB 串口發(fā)出數(shù)據(jù)時(shí), 使用以下函數(shù):
/* 啟動(dòng)發(fā)送 */
UINT ux_device_class_cdc_acm_write_with_callback(UX_SLAVE_CLASS_CDC_ACM *cdc_acm, UCHAR *buffer, ULONG requested_length);
/* 發(fā)送完畢的回調(diào)函數(shù) */
static UINT ux_device_class_cdc_acm_write_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, ULONG length);
我們將會(huì)實(shí)現(xiàn)如下函數(shù),它使用“ux_device_class_cdc_acm_write_with_callback ” 來(lái)啟動(dòng)發(fā)送,然后等待“ux_device_class_cdc_acm_write_callback”喚醒:
int ux_device_cdc_acm_send(uint8_t *datas, uint32_t len, uint32_t timeout);
開發(fā)板接收到 USB 串口數(shù)據(jù)時(shí),以下回調(diào)函數(shù)被調(diào)用:
static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length);
我們可以改造這個(gè)函數(shù), 把接收到的數(shù)據(jù)寫入隊(duì)列。
4.8.3 使用 FreeRTOS 改造代碼
對(duì)于發(fā)送, 實(shí)現(xiàn)以下函數(shù):?jiǎn)?dòng)發(fā)送之后阻塞,等待回調(diào)函數(shù)喚醒或超時(shí)。
static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length);
對(duì)于接收, 實(shí)現(xiàn)以下函數(shù):把接收到的數(shù)據(jù)寫入隊(duì)列。
static UINT ux_device_class_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT *cdc_acm, UINT status, UCHAR *data_pointer, ULONG length);
然后提供這個(gè)函數(shù):
int ux_device_cdc_acm_getchar(uint8_t *pData, uint32_t timeout);
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19126瀏覽量
305242 -
usb
+關(guān)注
關(guān)注
60文章
7945瀏覽量
264682 -
編程
+關(guān)注
關(guān)注
88文章
3616瀏覽量
93738
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論