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