之所以把題目訂為 USB HID 與 UART 的關係,是想以大家比較普遍容易遇到的
應用問題拿出來討論,但實際上跟所謂的USB 轉I2C 或是USB 轉SPI 等等問題是
一樣的啦!所以呢?!我也實在不想在UART 這個基本問題上討論太多為什麼會
掉資料的問題。因為...您的UART(RS232 )會掉資料,那難道人家USB 轉I2C
或USB 轉SPI 就不會嗎?!您USB 轉RS232 還有一大堆超級終端機或是VB 程式
可以參考寫程式,那人家那個USB 轉I2C 或USB 轉SPI 怎麼辦?!人家又沒有
那個VCP Class 的東西可以參考啊?!您該不會今天碰到UART 時,就搞那個
USB 轉RS232 ,那下回遇到USB 轉I2C 時,怎麼辦?!...算了吧!
還是直接想個根本解決之道,不要再讓這一種問題困擾您了吧!
所以啦....在系列一的文章我們所討論的那個什麼Null Modem Protocol 的問題,
就不要理他了吧~反正,您在用USB 轉RS232時,您還不是只用到 RD/TD 兩根I/O
而已嗎?!其他硬體的Protocol 您也都是視而不見...有差嗎?這樣子的搞法,
跟USB 轉I2C 或是USB 轉SPI 有差嗎?!.........
-----
所以啦~我們就乾脆直接看根本的資料傳輸問題吧...我們就直接借用USB 的基本精髓
來解決我們的這一類問題吧!
之前文章中,有讀者回應提到網路通訊OSI 七層的基本概念,沒錯....如果我們以這一個
觀點來看USB 其實他已經幫您定義到至少 Physical/Data/Network 以上了...
這三層比較是偏硬體!另外四層是比較偏軟體層的...當然啊,如果您是採用USB
某些Class 來說:那他的層次定義就越高了~當然他的使用彈性就越少了!
---- 這是個取類似的比較(比喻)方式。
因為USB 基本上都其基本的Frame 、CRC Check 及基本的Protocol ...所以我們先前
在討論那個Null Modem 中那些軟體定義Protocol 對USB 來說:那不是問題!
所以我才說:還真的有點懶得討論這些什麼DTR/CTS 或什麼 DCD、DSR 的!
人家USB有自己的定義,而您在用TD/RD 時也沒在鳥?!用SPI 或I2C 更沒有!
您既然要用最簡單的TD/RD 兩根I/O ,我們就用最簡單的兩根I/O 來搞吧!
(很巧,USB 也剛好就是兩根I/O :D+ 及D- !哈~哈~.........)
--------------------
我們就直接切實際應用問題吧!
若以HOST (PC)端來看:TD 的問題比較少!這對UART 、I2C 及SPI 都一樣!
因為USB 是屬於主從架構的通訊方式,所以只要Host 要往下傳資料,那Device 端
就不得不接收...以USB 的基本精髓來說,很基本。這一點大家在使用上應該也沒啥問題!
在AP 應用軟體的程式撰寫上,也沒多大爭議,您只要往作業系統的底層一丟資料,
那怕是一個Byte 一個Byte 丟...我相信以現在這麼Powerful 的電腦硬體幾搭配作業系統
應該都不難!
但比較大的問題應該就在 RD 這一個方向的接收問題吧!
因為USB 是主從架構,所以當USB 轉XXX 這一棵USB Controller MCU 收到資料時,
該如何"主動"的通知 PC Host ?! 所以~以USB 的架構來看:那就非得要靠Intertupt pipe
不可。當然之所以會這麼考量也是因為也要考慮 PC 端AP 該如何配合!
因為寫PC 端AP 軟體又不能像寫MCU 韌體一樣,靠硬體中斷來寫啊!
您說:那用Multi-thread 來寫?!我沒意見,只是我自己寫這一種程式的功力不足,
會把自己在AP 端的程式搞得很辛苦,很複雜...只要稍微沒搞懂Multi-Thread 的作法,
就連Debug 都會搞死自己!
對於PC 端來說,您根本很難定義說:您該多久要檢查一次那個Receive 端的FIFO ?!
或是多少的Byte 時,該要抓一次RD 資料?!在抓的時候,會不會剛好又有資料進來?
當然這些問題很難一次得到正確的答案!
但如果我們真正的回到:資料傳輸的真正的基本精髓,因為我們在做資料傳輸時,
都應該都會定義一定的軟體通訊協定:譬如會有資料檔頭(header),或是宣告一次
傳輸的長度,甚至說多久會傳一次資料等等!...尤其是我們在玩UART 這一種傳輸介面時,
我們也常要求傳輸雙方要定義一定的傳輸命令集或傳輸資料格式。
譬如:若以DMX512 的規格來看,他每一次就是會傳個 512 bytes 資料...
中間至少也會停留幾個uSec 或甚至mSec 等。或是工控上用的MODBUS 等等。
所以...我們在USB 的Interrupt pipe 中,就可以很明確的定義這一個傳輸長度。
又因為在USB HID class 的宣告中也是要宣告Interrupt pipe 會多久傳一次...
(沒有人規定您一定多久就要傳"正確"的資料,您當然可以在所傳輸的資料中,
擺入Data Ready 的識別碼...讓AP 軟體判斷。)但以微軟底層的USB 驅動程式
是一定會依照您所宣告的Interrupt pipe 中的Interval time 來抓一次資料...
(當然啊...如果您USB Controller 沒有準時送出資料,微軟的驅動程式也不會怪您!)
這一部份我在USB DIY-- 自學計畫(十)中有作實驗給大家看!
那這樣子,您就可以很容易的把資料準確的送到PC HOST端了!
----
所以當我們直接利用一棵帶有UART 功能的USB Controller IC 來對您的UART 應用的
系統來說:我們可以在USB Controller MCU 那邊,把您所需的UART 傳輸功能處理掉,
然後,很輕鬆利用本身USB HID 介面,把資料準確的往上傳給 PC Host 端,也可以讓AP
準確的讀到正確資料,我所說的意思是如下圖所示:
之所以這麼說的原因是:很簡單,現在隨便要找一棵USB Controller MCU 都應該很容易了,
而且現在這一種MCU 若以32 bits ARM 來說:搞不好都比以前我剛搞USB時的PC 的
CPU 都還強大,讓他來處理您應用上的UART 應該都是綽綽有餘了!
您幹嘛還要都要依賴PC 端呢?!這樣子您的產品應用也比較單純也比較有彈性。
當然,您會說:這樣子不是有點要客製化?!我有點無法像一般通用型USB 轉RS232
那樣簡單嗎?!---但我們之前不是討論了嗎?...簡單好用的代價是什麼?!
您付出了什麼代價?!...
--------
所以我們再來看PC 端的程式該如何寫?!
以下是一段我利用 USB HID 的Interrupt pipe 來抓資料的範例程式:
BOOL CUSB_RFIDTool::GetUSBHID_MCUDetector_Interrupt(unsigned char *nReportID, unsigned char *SnapshotBuf)
{
BOOL success = FALSE;
WORD inputReportSize = HidDevice_GetInputReportBufferLength(m_hid); // Size of the biggest input interrupt report
DWORD numReports = HidDevice_GetMaxReportRequest(m_hid); // The maximum number of reports that can be read in a single read
DWORD bufferSize = inputReportSize * numReports;
BYTE* buffer = new BYTE[bufferSize];
DWORD bytesReturned = 0;
BYTE status;
memset(buffer, 0x00, bufferSize);
// Get the max number of input reports via the interupt endpoint
status = HidDevice_GetInputReport_Interrupt(m_hid, buffer, bufferSize, numReports, &bytesReturned);
// If the function times out, we still have to check for returned data
if (status == HID_DEVICE_SUCCESS || status == HID_DEVICE_TRANSFER_TIMEOUT)
{
// Go through each input report one at a time
for (DWORD i = 0; i < bytesReturned; i += inputReportSize)
{
// If the current input report is a Get MCU Detector report
if (buffer[i] == IN_ReportID02)
{
*nReportID = IN_ReportID02;
SnapshotBuf[0] = buffer[i + 1];
SnapshotBuf[1] = buffer[i + 2];
//*Detector = buffer[i + 1];
//*nValue = buffer[i + 2];
success = TRUE;
}
//--- Snapshot Report
if (buffer[i] == IN_ReportID10)
{
*nReportID = IN_ReportID10;
for(int j=0;j<0x10;j++){
SnapshotBuf[j] = buffer[i + j];
}
success = TRUE;
}
//--- Report
if (buffer[i] == IN_ReportID08)
{
*nReportID = IN_ReportID08;
for(int j=0;j<0x08;j++){
SnapshotBuf[j] = buffer[i + j];
}
success = TRUE;
}
if (buffer[i] == IN_ReportID0C)
{
*nReportID = IN_ReportID0C;
for(int j=0;j<0x0C;j++){
SnapshotBuf[j] = buffer[i + j];
}
success = TRUE;
}
// Note: Ignore any other type of input report
}
}
delete [] buffer;
return success;
}
您可以看到我利用了四組 HID 中的Report ID 來處理UART 的回傳資料...
不同的Report ID 可以拿來處理不同的Data Length 資料,反正對於微軟作業系統
來說:他才不管您USB Controller MCU 傳什麼資料?他只要發現USB Interrupt pipe
一有資料,他就用固定長度的FIFO ,把您的資料儲存起來...
這一點看起來至少比那個RS232 那一種Comm port 作法嚴謹及有規律多了!
也難怪人家微軟的作業系統的底層驅動程式喜歡這樣子的東西。
所以~只要您的USB Controller MCU 在處理 UART 那邊不會掉資料,那在USB 這邊,
您就不用擔心那個什麼Null Modem 裡那個什麼DTR/CTS ?!或是會不會需要
Checksum 或是Error Correction 問題?!因為USB 硬體本身就已經幫您處理了!
這樣子的處理與設計方式,您也可以同時套用在I2C 或是SPI 等其他奇奇怪怪的MCU 介面裡!
連USB HID 這一端來說:根本不用動到什麼?!因為全由USB Controller MCU 來處理!
您說:這樣子簡不簡單?!
------------------------------------------
您或許會覺得這樣子作,會不會很花功夫?!很簡單~您不花功夫,錢就讓別人賺!
當然,如果您又老是碰到我們一直在強調的~出問題搞不清楚問題在哪?!
也不之該如何下手?!那又是另當別論了!您的代價還不少耶!
如果關於這一點您不是很認同的話,那我就Show 兩張圖給您看:
以下是Silabs 這一家USB Controller MCU 廠在賣您所謂的 USB 轉UART (RS232) IC
的資料與IC 接腳圖:
CP2103 :
------
CP2104 :
---------------------------
我這兩張比較圖,您看到了什麼?!看得懂的人就不用我說什麼了吧!
如果您搞USB 韌體程式行的話,您也可以!
-----
那您就更不用說:現在市面上一缸子 32 bits ARM 都帶有USB 介面的MCU 。
------------------------------------------
關於本篇文章我們就簡單的下個註腳:
1:如果您與其要用USB 轉RS232 來讓PC 端來處理UART 傳輸問題,您倒不如
直接套用一棵帶有UART介面的USB Controller MCU 來幫您處理系統之間的UART
問題,讓USB Controller MCU 跟PC 端只要處理單純的USB HID 問題!
以現在帶有USB介面的 Controller MCU 很多,甚至都有32 bits ARM 可供選擇,
其UART 的處理能力是比早期PC 的CPU 效能好很多!
2:PC 端的應用程式只剩下HID 單純的程式設計,既簡單又容易與作業系統底層驅動程式
配合,那怕下回修改成為USB 轉I2C 或USB 轉SPI 等介面,都一樣適用的啦!
而且還可以提供其他USB Command Interface ,讓您的PC 與USB Controller MCU
之間有更好彈性與應用介面。
(待續)...
留言列表