4.1 USB 學(xué)習(xí)指南
閱讀源碼時,經(jīng)常碰到如下術(shù)語:
- 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 本身是一個很龐大、復(fù)雜的體系, 本課程的重點在于工業(yè)互聯(lián), USB 是其中的一個 小小知識點。本章課程的目的在于:能理解 USB 的一些概念,能使用 USB 傳輸數(shù)據(jù)。 4.24.5 節(jié), 介紹 USB 概念;4.64.7 節(jié),移植 USBX 實現(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過 USB 設(shè)備驅(qū)動,直接通過 USB 控制器驅(qū)動訪問 USB 設(shè)備。
4.2 USB 系統(tǒng)硬件框架和軟件框架
4.2.1 實驗現(xiàn)象
現(xiàn)象: 把 USB 設(shè)備比如 Android 手機(jī)接到 PC
- 右下角彈出"發(fā)現(xiàn) android phone"
- 跳出一個對話框, 提示你安裝驅(qū)動程序
- 問 1:USB 設(shè)備插到電腦上去, 接觸到的對方設(shè)備是什么?
答 1:是 USB 控制器,是 USB 控制器內(nèi)嵌的 root hub - 問 2. 既然還沒有"驅(qū)動程序",為何能知道是"android phone"?
答 2. windows 里已經(jīng)有了 USB 的總線驅(qū)動程序, 接入 USB 設(shè)備后, 是"總線驅(qū)動程序" 知道你是"android phone"、提示你安裝的是"設(shè)備驅(qū)動程序"。USB 總線驅(qū)動程序負(fù)責(zé):識 別 USB 設(shè)備, 給 USB 設(shè)備找到對應(yīng)的驅(qū)動程序。 - 問 3. 為什么一接入 USB 設(shè)備, PC 機(jī)就能發(fā)現(xiàn)它?
答 3. PC 的 USB 口內(nèi)部,D-和 D+接有 15K 的下拉電阻, 未接 USB 設(shè)備時為低電平。USB 設(shè)備的 USB 口內(nèi)部, D-或 D+接有 1.5K 的上拉電阻;它一接入 PC,就會把 PC USB 口的 D-或 D+拉高,從硬件的角度通知 PC 有新設(shè)備接入。 - 問 4. USB 設(shè)備種類非常多,為什么一接入電腦, 就能識別出來它的種類?
答 4. PC 和 USB 設(shè)備都得遵守一些規(guī)范。比如: USB 設(shè)備接入電腦后, PC 機(jī)會發(fā)出"你 是什么"?USB 設(shè)備就必須回答"我是 xxx", 并且回答的格式是固定的。USB 總線驅(qū)動程序會 發(fā)出某些命令想獲取設(shè)備信息(描述符),USB 設(shè)備必須返回"描述符"給 PC。 - 問 5. PC 機(jī)上接有非常多的 USB 設(shè)備, 怎么分辨它們?
答 5. 每一個 USB 設(shè)備接入 PC 時, USB 總線驅(qū)動程序都會給它分配一個編號。 PC 機(jī)想 訪問某個 USB 設(shè)備時,發(fā)出的命令都含有對應(yīng)的編號(地址)。 - 問 6. USB 設(shè)備剛接入 PC 時, 還沒有編號; 那么 PC 怎么把"分配的編號"告訴它?
答 6. 新接入的 USB 設(shè)備的默認(rèn)編號是 0,在未分配新編號前, PC 使用 0 編號和它通 信。
- 問 1:USB 設(shè)備插到電腦上去, 接觸到的對方設(shè)備是什么?
4.2.2 硬件框架
在 USB 系統(tǒng)中, 有 2 個硬件概念:
- USB Host:它跟處理器相連,處理器通過 USB Host 跟各類 USB 設(shè)備通信。 USB Host 中 集成有一個 root hub
- USB Device:這分為兩類設(shè)備
- Hub:用來擴(kuò)展 USB 接口
- Function:就是普通的 USB 設(shè)備,比如 U 盤、聲卡等
4.2.3 軟件框架
APP 可以通過 USB 設(shè)備驅(qū)動程序訪問 USB 設(shè)備,也可以繞過 USB 設(shè)備驅(qū)動,直接通過 USB 控制器驅(qū)動訪問 USB 設(shè)備。
4.3 軟件工程師眼里的 USB 電氣信號
參考資料:
- 《圈圈教你玩 USB》
- 簡書 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 信號格式: 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è)備, 也分為低速、全速、高速三種類型。 一個 USB 設(shè)備, 可能兼容低 速、全速, 可能兼容全速、高速, 但是不會同時兼容低速、高速。
4.3.2 硬件線路
下圖是兼容高速模式的 USB 收發(fā)器電路圖:
USB 連接涉及 Hub Port 和 USB 設(shè)備,硬件連接如下:
4.3.3 電子信號
USB 連接線有 4 條: 5V、D+、D-、GND。數(shù)據(jù)線 D+、D-,只能表示 4 種狀態(tài)。 USB 協(xié)議 中,很巧妙地使用這兩條線路實現(xiàn)了空閑(Idle)、開始(SOP)、傳輸數(shù)據(jù)(Data)、結(jié)束(EOP) 等功能。
4.3.4 低速/全速信號電平
4.3.5 高速信號電平
4.3.6 設(shè)備連接與斷開
1. 連接
Hub 端口的 D+、D-都有 15K 的下拉電阻,平時為低電平。全速設(shè)備內(nèi)部的 D+有 1.5K 的 上拉電阻, 低速設(shè)備內(nèi)部的 D-有 1.5K 的上拉電阻,連接到 Hub 后會導(dǎo)致 Hub 的 D+或 D-電 平變化,Hub 根據(jù)變化的引腳分辨接進(jìn)來的是全速設(shè)備還是低速設(shè)備。
高速設(shè)備一開始也是作為全速設(shè)備被識別的。
全速設(shè)備、高速設(shè)備連接時, D+引腳的電平由低變高:
低速設(shè)備連接時,D-引腳的電平由低變高:
2. 斷開
對于低速、全速設(shè)備,接到 Hub 時導(dǎo)致 D-或 D+引腳變?yōu)楦唠娖剑?斷開設(shè)備后, D-或 D+ 引腳變?yōu)榈碗娖剑?/p>
對于高速設(shè)備,它先作為全速設(shè)備被識別出來,然后再被識別為高速設(shè)備。工作于高 速模式時, D+的上拉電阻是斷開的,所以對于工作于高速模式的 USB 設(shè)備, 無法通過 D+的 引腳電平變化監(jiān)測到它已經(jīng)斷開。
工作于高速模式的設(shè)備, D+、D-兩邊有 45 歐姆的下拉電阻,用來消除反射信號:
當(dāng)斷開高速設(shè)備后, Hub 發(fā)出信號,得到的反射信號無法衰減, Hub 監(jiān)測到這些信號后 就知道高速設(shè)備已經(jīng)斷開,內(nèi)部電路圖如下:
4.3.7 復(fù)位
從狀態(tài)切換圖上看,一個 USB 設(shè)備連接后,它將會被供電, 然后被復(fù)位。當(dāng)軟件出錯 時,我們也可以發(fā)出復(fù)位信號重新驅(qū)動設(shè)備。
那么, USB Hub 端口或 USB 控制器端口如何發(fā)出復(fù)位信號? 發(fā)出 SE0 信號,并維持至少 10ms。
USB 設(shè)備看到 Reset 信號后,需要準(zhǔn)備接收"SetAddress()"請求; 如果它不能回應(yīng)這個 請求, 就是"不能識別的設(shè)備"。
4.3.8 設(shè)備速率識別
1. 低速/全速
Hub 端口的 D+、D-都有 15K 的下拉電阻,平時為低電平。全速設(shè)備內(nèi)部的 D+有 1.5K 的 上拉電阻, 低速設(shè)備內(nèi)部的 D-有 1.5K 的上拉電阻,連接到 Hub 后會導(dǎo)致 Hub 的 D+或 D-電
平變化,Hub 根據(jù)變化的引腳分辨接進(jìn)來的是全速設(shè)備還是低速設(shè)備。
2. 高速
高速設(shè)備必定兼容全速模式, 所以高速設(shè)備內(nèi)部 D+也有 1.5K 的上拉電阻, 只不過這個 電阻是可以斷開的: 工作于高速模式時要斷開它。
高速設(shè)備首先作為全速設(shè)備被識別出來,然后 Hub 如何確定它是否支持高速模式? Hub 端口如何監(jiān)測一個新插入的 USB 設(shè)備能否工作于高速模式? 流程如下:
- 對于低速設(shè)備,Hub 端口不會監(jiān)測它能否工作于高速模式。低速設(shè)備不能兼容高速模式。
- Hub 端口發(fā)出 SE0 信號,這就是復(fù)位信號
- USB 設(shè)備監(jiān)測到 SE0 信號后,會發(fā)出"a high-speed detection handshake"信號表示自 己能支持高速模式, 這可以細(xì)分為一下 3 種情景:
- 如果 USB 設(shè)備原來處于"suspend"狀態(tài),它檢測到 SE0 信號后, 就發(fā)出"a high- speed detection handshake"信號。
- 如果 USB 設(shè)備原來處于"non-suspend"狀態(tài),并且處于全速模式, 它檢測到 SE0 信 號后, 就發(fā)出"a high-speed detection handshake"信號。這個情景,就是一個設(shè)備剛插 到 Hub 端口時的情況,它一開始工作于全速模式。
- 如果 USB 設(shè)備原來處于"non-suspend"狀態(tài), 并且處于高速模式,它會切換回到全 速模式(重新連接 D+的上拉電阻),然后發(fā)出"a high-speed detection handshake"信號。
"a high-speed detection handshake"信號,就是"高速設(shè)備監(jiān)測握手信號",既然是 握手信號, 自然是有來有回:
- USB 設(shè)備維持 D+的上拉電阻,發(fā)出"Chirp K "信號, 表示自己能支持高速模式
- 如果 Hub 沒監(jiān)測到"Chirp K "信號, 它就知道這個設(shè)備不支持高速模式
- 如果 Hub 監(jiān)測到"Chirp K "信號后, 如果 Hub 能支持高速模式, 就發(fā)出一系列的"Chirp K"、"Chirp J"信號,這是用來通知 USB 設(shè)備: Hub 也能支持高速模式。發(fā)出一系列的 "Chirp K"、"Chirp J"信號后,Hub 繼續(xù)維持 SE0 信號直到 10ms。
- USB 設(shè)備發(fā)出"Chirp K "信號后,就等待 Hub 回應(yīng)一系列的"Chirp K"、"Chirp J"信號
- 收到一系列的"Chirp K"、"Chirp J"信號: USB 設(shè)備端口 D+的上拉電阻,使能高速模式
- 沒有收到一系列的"Chirp K"、"Chirp J"信號: USB 設(shè)備轉(zhuǎn)入全速模式
4.3.9 數(shù)據(jù)信號
1. 低速/全速的 SOP 和 EOP
SOP:Start Of Packet,Hub 驅(qū)動 D+、D-這兩條線路從 Idle 狀態(tài)變?yōu)?K 狀態(tài)。 SOP 中 的 K 狀態(tài)就是 SYNC 信號的第 1 位數(shù)據(jù), SYNC 格式為 3 對 KJ 外加 2 個 K。
EOP:End Of Packet,由數(shù)據(jù)的發(fā)送方發(fā)出EOP,數(shù)據(jù)發(fā)送方驅(qū)動D+、D-這兩條線路, 先設(shè)為 SE0 狀態(tài)并維持 2 位時間, 再設(shè)置為 J 狀態(tài)并維持 1 位時間, 最后 D+、D-變?yōu)楦咦?狀態(tài), 這時由線路的上下拉電阻使得總線進(jìn)入 Idle 狀態(tài)。
2. 高速的 SOP
高速的 EOP 比較復(fù)雜,作為軟件開發(fā)人員無需掌握。
高速模式中,Ide 狀態(tài)為:D+、D-接地。SOP 格式為: 從 Idle 狀態(tài)切換為 K 狀態(tài)。 SOP 中的 K 狀態(tài)就是 SYNC 信號的第 1 位數(shù)據(jù)。
高速模式中的 SYNC 格式為:KJKJKJKJ KJKJKJKJ KJKJKJKJ KJKJKJKK,即 15 對 KJ,外 加 2 個 K。
3. NRZI 與位填充
參考文章:USB 的 NRZI 信號格式, https://zhuanlan.zhihu.com/p/460018993
NRZI:Non Return Zero Inverted Code,反向不歸零編碼。 NRZI 的編碼方位為:對于 數(shù)據(jù) 0,波形翻轉(zhuǎn);對于數(shù)據(jù) 1,波形不變。
使用 NRZI,發(fā)送端可以很巧妙地把"時鐘頻率"告訴接收端: 只要傳輸連續(xù)的數(shù)據(jù) 0 即 可。在下圖中, 低速/全速協(xié)議中"Sync Pattern"的原始數(shù)據(jù)是"00000001",接收端從前面 的 7 個 0 波形就可以算出"時鐘頻率"。
使用 NRZI 時, 如果傳輸?shù)臄?shù)據(jù)總是"1",會導(dǎo)致波形維持不變。如果電平長時間維持 不變, 比如傳輸 100 位 1 時, 如果接收方稍有偏差,就可能認(rèn)為接收到了 99 位 1、101 位 1。而 USB 中采用了 Bit-Stuffing 位填充處理,即在連續(xù)發(fā)送 6 個 1 后面會插入 1 個 0,強(qiáng) 制翻轉(zhuǎn)發(fā)送信號,從而讓接收方調(diào)整頻率,同步接收。而接收方在接收時只要接收到連續(xù) 的 6 個 1 后,直接將后面的 0 刪除即可恢復(fù)數(shù)據(jù)的原貌。
NRZI 數(shù)據(jù)格式如上圖所示。
sidebar_position: 5
4.4 USB 協(xié)議層數(shù)據(jù)格式
參考資料:
- 《圈圈教你玩 USB》
- 簡書 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 信號格式: 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 :多個設(shè)備組合起來,通過 HUB 跟 Host 相連
composite device :一個物理設(shè)備有多個邏輯設(shè)備(multiple interfaces)
在軟件開發(fā)過程中, 我們可以忽略 Hub 的存在,硬件拓?fù)鋱D簡化如下:
一個物理設(shè)備里面可能有多個邏輯設(shè)備, Hos 可以外接多個邏輯設(shè)備, 硬件拓?fù)鋱D如 下:
4.4.2 協(xié)議層
要理解協(xié)議層、理解數(shù)據(jù)如何傳輸,帶著這幾個問題去看文檔、看視頻:
- 如何尋址設(shè)備?
- 如何表示數(shù)據(jù)方向(讀、還是寫)
- 如何確認(rèn)結(jié)果?
提前羅列出答案:
- USB 系統(tǒng)是一個 Host 對應(yīng)多個設(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è)備可能未就緒, 就會回應(yīng) NAK 包
- Host 想寫數(shù)據(jù), 它發(fā)出數(shù)據(jù)后,設(shè)備正確接收了, 就回復(fù) ACK 包
4.4.3 字節(jié)/位傳輸順序
先傳輸最低位(LSB)。在后續(xù)文檔中,描述數(shù)據(jù)時按照傳輸順序從左到右列出來
4.4.4 SYNC 域
Host 發(fā)出 SOP 信號后, 就會發(fā)出 SYNC 信號:它是一系列的、最大傳輸頻率的脈沖,接 收方使用它來同步數(shù)據(jù)。對于低速/全速設(shè)備, SYNC信號是8位數(shù)據(jù)(從做到右是00000001); 對于高速設(shè)備, SYNC信號是32位數(shù)據(jù)(從左到右是00000000000000000000000000000001)。 使用 NRZI 編碼時,前面每個"0"都對應(yīng)一個跳變。
在很多文檔里, 把 SOP 和 SYNC 統(tǒng)一稱為"SYNC",它的意思是"SYNC"中含有"SOP"。
4.4.5包格式
USB 總線上傳輸?shù)臄?shù)據(jù)以包為單位。 USB 包里含有哪些內(nèi)容("域")?
- SOP:用來表示包的起始
- SYNC:用來同步時鐘
- PID:表示包的類型
- 地址:在 USB 硬件體系中, 一個 Host 對應(yīng)多個 Logical Device,那么 Host 發(fā)出的包, 如何確定發(fā)給誰?
- 發(fā)給所有設(shè)備:包里不含有設(shè)備地址
- 發(fā)給某個設(shè)備:包里含有設(shè)備地址、端點號
- 幀號、數(shù)據(jù)等跟 PID 相關(guān)的內(nèi)容
- CRC 校驗碼
發(fā)起一次完整的傳輸, 可能涉及多個包。那么,第 1 個包里含有設(shè)備地址、端點號, 后續(xù)的包就沒必要包含設(shè)備地址、端點號。
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ì)分。如下表(來自 《圈圈教你玩 USB》)所示:
在 USB 包中,PID 域使用 8 位來表示,格式如下:
前 4 位表示 PID,后 4 位是對應(yīng)位的取反。接收方發(fā)現(xiàn)后 4 位不是前 4 位的取反的話, 就認(rèn)為發(fā)生了錯誤。
2. 令牌包(Token)
令牌類 的 PID ,起 "通知作用 " ,通知誰 ?SOF 令牌包被用來通 知所有設(shè) 備, OUT/IN/SETUP 令牌包被用來通知某個設(shè)備。
對于 OUT、IN、SETUP 令牌包, 它們都是要通知到具體的設(shè)備, 格式如下:
USB 設(shè)備的地址有 7 位,格式如下:
USB 設(shè)備的端點號有 4 位, 格式如下:
對于 SOF 包,英文名為"Start-of-Frame marker and frame number"。對于 USB 全速 設(shè)備, Host 每 1ms 產(chǎn)生一個幀; 對于高速設(shè)備, 每 125us 產(chǎn)生一個微幀, 1 幀里有 8 個微 幀。 Host 會對當(dāng)前幀號進(jìn)行累加計數(shù), 在每幀或每微幀開始時, 通過 SOF 令牌包發(fā)送幀號。 對于高速設(shè)備, 每 1 毫秒里有 8 個微幀,這 8 個微幀的幀號是一樣的, 每 125us 發(fā)送一個 SOF 令牌包。
SOF 令牌包格式如下:
3. 數(shù)據(jù)包
Host 使用 OUT、IN、SETUP 來通知設(shè)備:我要傳輸數(shù)據(jù)了。數(shù)據(jù)通過"數(shù)據(jù)包"進(jìn)行傳 輸。
數(shù)據(jù)包也有 4 種類型:DATA0、DATA1、DATA2、MDATA。其中 DATA2、MDATA 在高速設(shè)備 中使用。對軟件開發(fā)人員來說,我們暫時僅需了解 DATA0、DATA1。
為什么要引入 DATA0、DATA1 這些不同類型的數(shù)據(jù)包? 為了糾錯。
Host 和設(shè)備都會維護(hù)自己的數(shù)據(jù)包切換機(jī)制,當(dāng)數(shù)據(jù)包成功發(fā)送或者接收時,數(shù)據(jù)包 類型切換。當(dāng)檢測到對方使用的數(shù)據(jù)包類型不對時,USB 系統(tǒng)認(rèn)為發(fā)生了錯誤。
比如:
- Host 發(fā)送 DATA0 給設(shè)備,設(shè)備返回 ACK 表示成功接收, 設(shè)備期待下一個數(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ù)包格式如下:
對于全速設(shè)備, 數(shù)據(jù)包中的數(shù)據(jù)做大是 1023 字節(jié);對于全速設(shè)備, 數(shù)據(jù)包中的數(shù)據(jù)做 大是 1024 字節(jié)。
4. 握手包
握手包有 4 類: ACK、NAK、STALL、NYET
- ACK:數(shù)據(jù)接收方用來回復(fù)發(fā)送方,表示正確接收到了數(shù)據(jù)并且有足夠的空間保存數(shù)據(jù)。
- NAK:Host 發(fā)送數(shù)據(jù)給設(shè)備時, 設(shè)備可以回應(yīng) NAK 表示"我還沒準(zhǔn)備好,沒辦法接收數(shù)據(jù)"; Host 想讀取設(shè)備的數(shù)據(jù)時, 設(shè)備可以回復(fù) NAK 表示"我沒有數(shù)據(jù)給你"。
- STALL:表示發(fā)生了錯誤,比如設(shè)備無法執(zhí)行這個請求(不支持該斷點等待)、斷點已經(jīng)掛起。設(shè)備返回 STALL 后,需要主機(jī)進(jìn)行干預(yù)才能接觸 STALL 狀態(tài)。
- NYET:僅適用于高速設(shè)備。 Host 可以發(fā)出 PING 包用來確認(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 表示。 一個單純的包,是無法傳輸 完整的數(shù)據(jù)。
為什么?比如想輸出數(shù)據(jù),可以發(fā)出 OUT 令牌包, OUT 令牌包可以指定目的地。但是數(shù) 據(jù)如何傳輸呢? 還需要發(fā)出 DATA0 或 DATA1 數(shù)據(jù)包。設(shè)備收到數(shù)據(jù)后, 還要回復(fù)一個 ACK 握手包。
所以,完整的數(shù)據(jù)傳輸, 需要涉及多個包:令牌包、數(shù)據(jù)包、握手包。這個完整的數(shù) 據(jù)傳輸過程,被稱為事務(wù)(Transaction)。
有些事務(wù)需要握手包,有些事務(wù)不需要握手包,有些事務(wù)可以傳輸很大的數(shù)據(jù),有些 事務(wù)只能傳輸小量數(shù)據(jù)。
- 有四類事務(wù):
- 批量事務(wù):用來傳輸大量的數(shù)據(jù),數(shù)據(jù)的正確性有保證,時效沒有保證。
- 中斷事務(wù):用來傳輸周期性的、小量的數(shù)據(jù), 數(shù)據(jù)的正確性和時效都有保證。 ③ 實時事務(wù):用來傳輸實時數(shù)據(jù), 數(shù)據(jù)的正確性沒有保證,時效有保證。
- 建立事務(wù):跟批量事務(wù)類似,只不過令牌包是 SETUP 令牌包。
- 有四類傳輸(Transfer):
- 批量傳輸:就是使用批量事務(wù)實現(xiàn)數(shù)據(jù)傳輸, 比如 U 盤。
- 中斷傳輸:就是使用中斷事務(wù)實現(xiàn)數(shù)據(jù)傳輸, 比如鼠標(biāo)。
- 實時傳輸:就是使用實時事務(wù)實現(xiàn)數(shù)據(jù)傳輸, 比如攝像頭。
- 控制傳輸:由建立事務(wù)、批量事務(wù)組成,所有的 USB 設(shè)備都必須支持控制傳輸, 用于" 識別/枚舉"
- 暫時記住這個關(guān)系:
- BIT 組成域(Field)
- 域組成包(Packet)
- 包組成事務(wù)(Transaction)
- 事務(wù)組成傳輸(Transfer)
2. 過程(stage)和階段(phase)
事務(wù)由多個包組成, 比如 Host 要發(fā)送數(shù)據(jù)給設(shè)備,這就會涉及很多個包:
- Host 發(fā)出 OUT 令牌包, 表示要發(fā)數(shù)據(jù)給哪個設(shè)備
- Host 發(fā)出 DATA0 數(shù)據(jù)包
- 設(shè)備收到數(shù)據(jù)后, 回應(yīng) ACK 包
這個完整的事務(wù)涉及 3 個包(Packet),分為 3 個階段(Phase):
- 令牌階段(Token phase):由令牌包實現(xiàn)
- 數(shù)據(jù)階段(Data phase):由數(shù)據(jù)包實現(xiàn)
- 握手階段(Handshake phase):由握手包實現(xiàn)
事務(wù)由包組成, 這些包分別處于 3 個階段(phase):令牌階段,數(shù)據(jù)階段, 握手階段。
對于批量傳輸、中斷傳輸、實時傳輸,它們分別由一個事務(wù)組成,不再細(xì)分為若干個 過程。
但是控制傳輸由多個事務(wù)組成,這些事務(wù)分別處于 3 個過程: 建立過程(stage)、數(shù)據(jù) 過程(stage)、狀態(tài)過程(stage)。
總結(jié)起來就是:
- 控制傳輸由多個過程(stage)組成, 每個過程由一個事務(wù)來實現(xiàn)
- 每個事務(wù)由多個階段(phase)組成, 每個階段有一個包來實現(xiàn)
3. 批量傳輸
批量傳輸用批量事務(wù)來實現(xiàn),用于傳輸大量的數(shù)據(jù), 數(shù)據(jù)的正確性有保證, 時效沒有 保證。
批量事務(wù)由 3 個階段(phase)組成: 令牌階段、數(shù)據(jù)階段、握手階段。每個階段都是一 個完整的包,含有 SOP、SYNC、PID、EOP。
下圖中各個矩形框就對應(yīng)一個完整的包。
《圈圈教你玩 USB》中有詳細(xì)的示例:
4.中斷傳輸
中斷傳輸用中斷事務(wù)來實現(xiàn),用于傳輸小量的、周期性的數(shù)據(jù),數(shù)據(jù)的正確性和時效 都有保證。
中斷事務(wù)由 3 個階段(phase)組成: 令牌階段、數(shù)據(jù)階段、握手階段。每個階段都是一 個完整的包,含有 SOP、SYNC、PID、EOP。
下圖中各個矩形框就對應(yīng)一個完整的包。
中斷事務(wù)跟批量事務(wù)非常類似,Host 使用它來周期性地讀數(shù)據(jù)、寫數(shù)據(jù)。
以鼠標(biāo)為例,我們需要及時獲得鼠標(biāo)的數(shù)據(jù), 不及時的話你會感覺鼠標(biāo)很遲鈍。但是 USB 協(xié)議中并沒有中斷功能,它使用"周期性的讀、寫"來實現(xiàn)及時性。具體過程如下:
- Host 每隔 n 毫秒發(fā)出一個 IN 令牌包
- 鼠標(biāo)有數(shù)據(jù)的話,發(fā)出 DATA0 或 DATA1 數(shù)據(jù)包給 Host;鼠標(biāo)沒有數(shù)據(jù)的話,發(fā)出 NAK 給 Host。
中斷事務(wù)的優(yōu)先級比批量事務(wù)更高,它要求實時性,而批量事務(wù)不要求實時性。
5.實時傳輸
實時傳輸用實時事務(wù)來實現(xiàn), 用于傳輸實時數(shù)據(jù), 對數(shù)據(jù)的正確性沒有要求。
實時事務(wù)由 2 個階段(phase)組成: 令牌階段、數(shù)據(jù)階段。每個階段都是一個完整的包, 含有 SOP、SYNC、PID、EOP。
實時事務(wù)不需要握手階段,一個示例的場景是:為了傳輸攝像頭的實時數(shù)據(jù),偶爾的 數(shù)據(jù)錯誤是可以忍受的,大不了出現(xiàn)短暫的花屏。如果為了解決花屏而重傳數(shù)據(jù), 那就會
導(dǎo)致后續(xù)畫面被推遲,實時性無法得到保證。
下圖中各個矩形框就對應(yīng)一個完整的包。
實時事務(wù)跟中斷事務(wù)非常類似,Host 也會周期性的發(fā)起實時事務(wù),主要區(qū)別在于:
- 實時事務(wù)不要求準(zhǔn)確性,沒有握手階段
- 實時事務(wù)傳輸?shù)臄?shù)據(jù)量比較大, 中斷事務(wù)傳輸?shù)臄?shù)據(jù)量比較小
6. 控制傳輸
在使用批量傳輸時, 使用 IN 令牌包或 OUT 令牌包表示數(shù)據(jù)傳輸方向。
控制傳輸?shù)牧钆瓢肋h(yuǎn)是 SETUP,怎么分辨是讀數(shù)據(jù), 還是寫數(shù)據(jù)? 發(fā)出 SETUP 令牌包 后,還要發(fā)出 DATA0 數(shù)據(jù)包,根據(jù)數(shù)據(jù)的內(nèi)容來確定后續(xù)是讀數(shù)據(jù),還是寫數(shù)據(jù)。這個過 程稱為"建立事務(wù)"(SETUP Transaction)
但是控制傳輸由多個事務(wù)組成,這些事務(wù)分別處于 3 個過程: 建立過程(stage)、數(shù)據(jù) 過程(stage)、狀態(tài)過程(stage)。
- 建立過程(stage),使用 SETUP 事務(wù):Host 發(fā)出 SETUP 令牌包、DATA0 數(shù)據(jù)包、得到 ACK 握手包
- 數(shù)據(jù)過程(stage),使用批量事務(wù):
- 對于輸出:Host 發(fā)出 OUT 令牌包,發(fā)出 DATA0、DATA1 數(shù)據(jù)包、得到 ACK 握手包
- 對于輸入:Host 發(fā)出 IN 令牌包,讀到 DATA0、DATA1 數(shù)據(jù)包、發(fā)出 ACK 握手包 ③ 狀態(tài)過程(stage),使用批量事務(wù):
- 對于輸出:Host 發(fā)出 IN 令牌包,讀到 DATA1 數(shù)據(jù)包,發(fā)出 ACK 握手包 b. 對于輸入:Host 發(fā)出 OUT 令牌包,發(fā)出 DATA1 數(shù)據(jù)包,等待 ACK 握手包
上圖中的每一個方框,都是一個完整的事務(wù), 含有: Token Packet、Data Packet、 Handshake Packet。
4.4.7 使用工具體驗數(shù)據(jù)格式
LeCroy(力科)成立于 1964 年, 是一家專業(yè)生產(chǎn)示波器廠家。旗下生產(chǎn)有數(shù)字示波器、 SDA 系列數(shù)字示波器、混合信號示波器、模塊化儀器、任意波形發(fā)生器。
官網(wǎng)是:https://teledynelecroy.com/,似乎無法注冊新用戶,無法下載軟件。 可以在搜索引擎里搜"usbprotocolsuite"。
安裝"usbprotocolsuite"后, 可以在文檔目錄里找打很多示程序(后綴名為 usb):
使用"usbprotocolsuite"打開這些文件,即可體驗 USB 數(shù)據(jù)傳輸:
4.5 USB 描述符
4.5.1
1. USB 設(shè)備狀態(tài)切換圖
4.5.2 標(biāo)準(zhǔn)設(shè)備請求
1.SETUP事務(wù)的數(shù)據(jù)格式
Host 使用控制傳輸來識別設(shè)備、設(shè)置設(shè)備地址、啟動設(shè)備的某些特性, 對于控制傳輸, 它首先發(fā)出"setup 事務(wù)",如下:
在"setup 事務(wù)"中,
- SETUP 令牌包:用來通知設(shè)備, "要開始傳輸了"
- DATA0 數(shù)據(jù)包:它含有固定的格式, 用來告訴設(shè)備"是讀還是寫"、"讀什么"、"寫什么"
Host 通過 DATA0 數(shù)據(jù)包發(fā)送 8 字節(jié)數(shù)據(jù)給設(shè)備,它的格式如下圖所示:
2. 標(biāo)準(zhǔn)設(shè)備請求
控制傳輸?shù)慕⑹聞?wù)中, 可以使用下列格式的數(shù)據(jù):
上表中各個"宏"取值如下:
3. 設(shè)備/配置/接口/端點
在 SETUP 事務(wù)的數(shù)據(jù)里, 表示了要訪問的是什么: Device?Interface?Endpoint?
對于一個USB 設(shè)備, 它可以多種配置(Configuration)。比如4G 上網(wǎng)卡就有 2 種配置: U 盤、上網(wǎng)卡。第 1 次把 4G 上網(wǎng)卡插入電腦時,它是一個 U 盤,可以按照里面的程序。裝 好程序后, 把它再次插入電腦,它就是一個上網(wǎng)卡。驅(qū)動程序可以選擇讓它工作于哪種配 置,同一時間只能有一種配置。大多數(shù)的 USB 設(shè)備只有一種配置。
一個配置下,可以有多個接口(Interface),接口等同于功能(Function)。比如 USB 耳 機(jī)有兩個接口(功能):聲音收發(fā)、按鍵控制。
一個接口, 可能有多個設(shè)置(Setting),比如默認(rèn)設(shè)置下它使用較低的帶寬, 可以選擇 其他設(shè)置以使用更高帶寬。
一個接口, 由一個或多個端點(Endpoint)組成。端點 0 屬于整個設(shè)備的, 端點 0 是雙 向的。接口還可以有其他端點, 這些端點是單向的, 要么是批量(Bulk)端點、要么是中斷 (Interrupt)端點、要么是同步(Isochronous)端點。
4.5.3 描述符
怎么描述設(shè)備、配置、接口、端點?使用描述符(Descriptors),有設(shè)備描述符、配置 描述符、接口描述符、端點描述符。所謂描述符,就是一些格式化的數(shù)據(jù), 用來描述信息。
一個 USB 設(shè)備:
- 只有一個設(shè)備描述符:用來表示設(shè)備的 ID、它有多少個配置、它的端點 0一次最大能傳 輸多少字節(jié)數(shù)據(jù)
- 可能有多個配置描述符:用來表示它有多少個接口、供電方式、最大電流
- 一個配置描述符下面,可能有多個接口描述符:用來表示它是哪類接口、有幾個設(shè)置 (Setting)、有幾個端點
- 一個接口描述符符下面,可能有多個端點描述符: 用來表示端點號、方向(IN/OUT)、類 型(批量/中斷/同步)
還有一些字符串描述符(String descriptors),它用可讀的文字來描述設(shè)備,是可選 的。
1. 設(shè)備描述符
2. 配置描述符
3. 接口描述符
4. 端點描述符
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è)備枚舉過程示例
使用"usbprotocolsuite"打開,可以看到設(shè)備的枚舉過程:
- 使用控制傳輸,讀取設(shè)備信息(設(shè)備描述符):第一次讀取時, 它只需要得到 8 字節(jié)數(shù)據(jù), 因為第 8 個數(shù)據(jù)表示端點 0 能傳輸?shù)淖畲髷?shù)據(jù)長度。
- Host 分配地址給設(shè)備, 然后把新地址發(fā)給設(shè)備:
- 使用新地址, 重新讀取設(shè)備描述符, 設(shè)備描述符長度是 18:
- 讀取配置描述符: 它傳入的長度是 255,想一次性把當(dāng)前配置描述符、它下面的接口描 述符、端點描述符全部讀出來
- 讀取字符描述符
4.6 USBX 組件
4.6.1 Azure RTOS 介紹
Azure RTOS 平臺是運行時解決方案的集合,包括 Azure RTOS ThreadX、Azure RTOS NetX 和 NetX Duo、Azure RTOS FileX、Azure RTOS GUIX 和 Azure RTOS USBX。
Azure RTOS ThreadX 是專用于深度嵌入式應(yīng)用程序的高級實時操作系統(tǒng) (RTOS)。 Azure RTOS ThreadX 具有多種優(yōu)勢,其中包括高級調(diào)度設(shè)施、消息傳遞、中斷管理和消息 服務(wù)。 Azure RTOS ThreadX 具有許多高級功能, 其中包括 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 類。 一旦低級驅(qū)動程序能夠響應(yīng) USBX 請求, 模塊化架構(gòu)就可以更容易地移植到不同的 USB 硬件 IP 上。
所有 STM32 USB IP(主機(jī)、設(shè)備、 OTG、高速和全速) 均由 USBX 通過通用 STM32 HAL 驅(qū)動程序 API 透明支持。
4.6.2 USBX 層次
參考資料:
https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Introduction_to_USBX
USBX 分為三層, 如下圖所示:
- 控制器層:最底層,USB 設(shè)備控制器的驅(qū)動程序,通常是 HAL 庫
- stack layer:實現(xiàn) USB 設(shè)備的基本操作,比如描述符的操作、使用 endpoint 進(jìn)行數(shù)據(jù) 傳輸
- Class layer:實現(xiàn)各類 USB 設(shè)備的操作,比如 HID 設(shè)備、音頻設(shè)備、虛擬串口,給 APP 提供接口
在 STM32 的固件中, 可以看到 USBX 目錄,比如:
移植 Controller layer、stack layer、Class layer 并不復(fù)雜, 重點在于 2 點:
- 怎么初始化硬件以確保 Controller layer 可以正常運行
- 怎么編寫 APP:提供設(shè)備信息、傳輸數(shù)據(jù)
4.6.3 USBX 的基本配置
USBX 依賴于 Azure?RTOS ThreadX,但是也可以單獨使用 USBX,這需要配置。通常在 “ux_user.h”里進(jìn)行配置,配置項如下:
- 使用單獨模式或 RTOS 模式:
/* Defined, this macro will enable the standalone mode of usbx. */
#define UX_STANDALONE
當(dāng)沒有定義“UX_STANDALONE”時就是使用 RTOS 模式, 可以使用 ThreadX 提供的互斥 量函數(shù)實現(xiàn)阻塞式讀寫(“blocking”), 比如對于 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 個函數(shù)發(fā)起數(shù)據(jù)傳輸,在傳輸過程中線程阻塞,傳輸完成后線程被喚醒。
當(dāng)定義“UX_STANDALONE”時就是使用單獨模式, 不能再使用上面的阻塞函數(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 是,就禁止了“非阻塞模式”, 這時只能使用基于 RTOS 的阻塞函數(shù)。
換句話說, 要使用單獨模式的非阻塞函數(shù), 就不能定義這個配置項。
- 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 實現(xiàn)虛擬串口
本節(jié)程序源碼為“3_程序源碼01_視頻配套的源碼 4-7_移植 USBX 實現(xiàn)虛擬串口 uart_usb.7z”,在上一節(jié)代碼 uart_rtos.7z 的基礎(chǔ)上修改得來。
移植 Controller layer、stack layer、Class layer 并不復(fù)雜, 重點在于 2 點:
- 怎么初始化硬件以確保 Controller layer 可以正常運行
- 怎么編寫 APP:提供設(shè)備信息、傳輸數(shù)據(jù)
4.7.1 配置 USB
4.7.2 添加 USBX 代碼
1. 復(fù)制代碼
找到固件庫,如下:
把 usbx 整個目錄復(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 代碼
參考工程:
STM32CubeH5ProjectsNUCLEO-H563ZIApplicationsUSBXUx_Device_HID_CDC_ACM
在網(wǎng)盤資料中, 找到如下目錄:
把 app 文件夾復(fù)制到工程的“MiddlewaresThird_Partyusbx”目錄下, 如下圖所示:
各個文件的作用為:
- ux_user.h:配置 USBX
- ux_stm32_config.h:里面含有配置項, 表示 STM32 支持多少個 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 控制器,并未啟動 它,也沒有跟 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”,這個概念可以參考:
http://www.51hei.com/bbs/dpj-40953-1.html。
第 70 行 : 把 STM32 USB 控 制 器 的 句 柄 , 傳 給 USBX 系 統(tǒng) ,
“usbx_stm32_device_controllers”的代碼會使用這個句柄來操作硬件。 第 72 行:啟動 USB 控制器。
4.7.5 創(chuàng)建 USBX 任務(wù)
使 用 單 獨 模 式 (STANDALONE ) 時 , 需 要 創(chuàng) 建 一 個 任 務(wù) , 不 斷 運 行 “_ux_system_tasks_run ”函數(shù)。以下代碼是在 FreeRTOS 的默認(rèn)任務(wù)里運行和這個函數(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)號 2)
- 添加頭文件目錄(圖中標(biāo)號 5)
4.7.7 添加使用串口的代碼
在“CoreSrcapp_freertos.c”里添加 USB 串口的發(fā)送測試代碼:
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 上顯示處來。
4.7.8 上機(jī)實驗
燒寫運行程序后,接上 USB 線,在電腦上可以識別出 USB 串口,查看設(shè)備管理器,可 以看到如下設(shè)備:
使用串口工具打開這個串口, 可以連續(xù)不斷接收到數(shù)據(jù),如下所示:
在串口工具上發(fā)送數(shù)據(jù)時,在板子的 LCD 上會有顯示。
4.8 虛擬串口源碼分析與改造
本節(jié)程序源碼為“3_程序源碼?1_視頻配套的源碼 4-8_虛擬串口源碼分析與改造 uart_usb_freertos.7z”,在上一節(jié)代碼 uart_usb.7z 的基礎(chǔ)上修改得來。
4.8.1 描述符的設(shè)置
在“MiddlewaresThird_Partyusbxappux_device_descriptors.c”有設(shè)備描述符、 配置描述符、接口描述符、端點描述符的定義。
比如, 設(shè)備描述符在如下代碼中設(shè)置:
配置描述符在如下代碼中設(shè)置:
4.8.2 數(shù)據(jù)收發(fā)函數(shù)
涉及文件為:demoMiddlewaresThird_Partyusbxappux_device_cdc_acm.c。 開發(fā)板通過 USB 串口發(fā)出數(shù)據(jù)時, 使用以下函數(shù):
/* 啟動發(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);
我們將會實現(xiàn)如下函數(shù),它使用“ux_device_class_cdc_acm_write_with_callback ” 來啟動發(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ù)時,以下回調(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);
我們可以改造這個函數(shù), 把接收到的數(shù)據(jù)寫入隊列。
4.8.3 使用 FreeRTOS 改造代碼
對于發(fā)送, 實現(xiàn)以下函數(shù):啟動發(fā)送之后阻塞,等待回調(diào)函數(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);
對于接收, 實現(xiàn)以下函數(shù):把接收到的數(shù)據(jù)寫入隊列。
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);
然后提供這個函數(shù):
int ux_device_cdc_acm_getchar(uint8_t *pData, uint32_t timeout);
-
嵌入式
+關(guān)注
關(guān)注
5090文章
19173瀏覽量
306844 -
usb
+關(guān)注
關(guān)注
60文章
7978瀏覽量
265548 -
編程
+關(guān)注
關(guān)注
88文章
3637瀏覽量
93901
發(fā)布評論請先 登錄
相關(guān)推薦
評論