在寫其他相關USB 應用之前,我們先整理一下關於 USB to UART 的系統應用。
其實這個問題應該反過來說:是UART 轉 USB。因為現在很多DIY 的平台都是
會讓使用者或初學者,很容易在系統平台上利用UART 把一些系統所需的訊息
利用UART (Tx/Rx) 把所需的內容給傳輸出來。初期大家也都會利用網路上的
一些公用的UART(RS232) 通訊軟體來使用,但到了產品開發或是後期產品
開始要導入量產或是一些高階使用時,就會覺得這些公用通訊軟體對我的系統
需求就沒那麼方便了,就開始想可不可以動手寫一些屬於自己的UART 通訊軟體?
當然這其中牽涉到因為UART 的通訊沒有一定的封包格式定義,(不像標準的USB 介面)
就算是MOD Bus 也是只有簡單的格式,也不一定完全符合每個人的需求。
所以每個玩家到最後也多多少少想走到這個階段:寫一個屬於自己的通訊軟體。
尤其是在許多公司裡,許多產品應用到了最後品管測試或量產管理上,也是需要一套
讓其他作業人員可以簡單輕易的檢測你所開發的系統。當然啊,如果公司裡沒有這樣
的相關開發人員的話,那也只好把這些工作給外包出來:那也是你另一個可以謀生
接案子的機會了。(文章最後會給你一個參考的實質價值...)
所以我接下來稍微簡單的說明一下關於如何在 Host (PC) 端寫一隻簡單的通訊軟體
需要知道哪些相關知識與該留意的細節。首先我先列出這一篇文章之前的一些相關
內容,有技術內容,也有一些沿革八卦故事:有空的話。再找時間翻翻看看吧。
USB to UART (RS232/RS485) 故事與應用(一)
USB to UART (RS232/RS485) 故事與應用(二)
USB to UART (RS232/RS485) 故事與應用(三)
----
我就用市面上公認比較好的 UART 轉 USB 的方案:Silabs CP210X 及 FTDI 。
剛好是一個原廠沒有提供相關Library (CP210X);另一家則有(FTDI)。也可以
解說一下兩者的做法不同差異之處。如果你要用的是其他方案,不好意思,只能
靠你自己學習了解之後,想辦法自行處理之。
首先先講簡單一點的 FTDI 方案:
原廠官方網站會提供以下函數庫:分別就是 X86 (32Bits) 及AMD64 (64Bits) 兩種。
這個東西就沒甚麼好說的,你就是直接引用它的函數庫到你的PC host 端的開發平台。
我用的是 MicroSoft 的 Visual Studio 2017 。原廠提供的參考範例程式:連結。
但這些程式有的很簡單,也沒有整合到比較好的 MFC 開發介面,你比較難引用,
我就幫各位整理一個範例在Github 上了。畢竟漂亮易懂的視窗軟體是比較親民一點的。
它的作法很簡單:
// TODO: Add extra initialization here FT_HANDLE ftHandle; FT_STATUS ftStatus; ftStatus = FT_Open(0, &ftHandle); ftStatus |= FT_SetUSBParameters(ftHandle, 4096, 4096); // Set USB transfer sizes ftStatus |= FT_SetChars(ftHandle, false, 0, false, 0); // Disable event characters ftStatus |= FT_SetTimeouts(ftHandle, 5000, 5000); // Set read/write timeouts to 5 sec ftStatus |= FT_SetLatencyTimer(ftHandle, 16); // Latency timer at default 16ms ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_NONE, 0x11, 0x13); // No flow control ftStatus |= FT_SetBaudRate(ftHandle, 9600); // Baud rate = 9600 ftStatus |= FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1, FT_PARITY_NONE); if (ftStatus != FT_OK) // printf("ftStatus not ok %d\n", ftStatus); //check for error TRACE1("ftStatus not ok %d\n", ftStatus); else { char data_out[12] = "Hello World"; DWORD w_data_len = 12; DWORD data_written; ftStatus = FT_Write(ftHandle, data_out, w_data_len, &data_written); char data_in[12]; DWORD r_data_len = 12; DWORD data_read; ftStatus = FT_Read(ftHandle, data_in, r_data_len, &data_read); if (ftStatus != FT_OK) //printf("ftStatus not ok %d\n", ftStatus); TRACE1("ftStatus not ok %d\n", ftStatus); else TRACE1("Data Read: %s\n", data_in); //printf("Data Read: %s\n", data_in); } ftStatus = FT_Close(ftHandle);
因為原廠已經幫你包成函數庫了,所以你只要簡單的幾個步驟就可以了:
1. Open Device (Get the Handle)
2. Set COM Port Parameter (9600, n, 8, 1)
3 然後ReadFile 和 WriteFile 就可以了。
4. 最後記得 Close (handle) 就可以了。
但重點來了:你怎麼知道你的UART 轉 USB 到電腦端時,它是接到哪個 COMx 呢?
而且接到每一台電腦都不一定一樣啊。你光這個就不知道如何服務市場客戶了?
所以原廠有提供幾個作法:
第一就是綁UART 轉USB 的序列號碼(S/N),這是USB 宣告時,唯一的識別碼。
這兩家公司的解決方案都有提供這樣的訊息,不知道的可以參考以上連結文章。
以下是我一個範例示範,它是用FTDI 接到Fluke 的儀器設備上的。
//========================================================================================== //---- Enumeration Device based on Fixed FTDI I/F //---- Fluke 1523(A) whick include 1524 option.... //============================================================================ int CTemperatureLabUtilityDlg::Enumeration_Thermometer_1523AExisted(void) { FT_STATUS ftStatus; //-- int returnstatus = 0; ///--- Check RS232 communcation status and initialize --- ////--------------- //---- Reference Thermometer Enumeration ---- //------- bTimer_ThermometerConnect = 0; //--- Check 1594A existed ? //ftStatus = FT_OpenEx("AB0NM2CG", FT_OPEN_BY_SERIAL_NUMBER, &ft_MeterHandle); ftStatus = FT_OpenEx(FTDI_Sn1Number, FT_OPEN_BY_SERIAL_NUMBER, &ft1523AHandle); if (ftStatus == FT_OK) { TRACE0("Fluke 1523(A) is connected...\n"); // Initialize COM port parameters ftStatus |= FT_SetUSBParameters(ft1523AHandle, 4096, 4096); // Set USB transfer sizes ftStatus |= FT_SetChars(ft1523AHandle, false, 0, false, 0); // Disable event characters ftStatus |= FT_SetTimeouts(ft1523AHandle, 5000, 5000); // Set read/write timeouts to 5 sec ftStatus |= FT_SetLatencyTimer(ft1523AHandle, 16); // Latency timer at default 16ms ftStatus |= FT_SetFlowControl(ft1523AHandle, FT_FLOW_NONE, 0x11, 0x13); // No flow control ftStatus |= FT_SetBaudRate(ft1523AHandle, 9600); // Baud rate = 9600 ftStatus |= FT_SetDataCharacteristics(ft1523AHandle, FT_BITS_8, FT_STOP_BITS_1, FT_PARITY_NONE); //---Check 1523 first... int nDecive1523 = Enumeration_Thermometer_WhichExisted(ft1523AHandle, 0); if (nDecive1523 == 1) { //TRACE0("9170 Enumeration Successful..\n"); m_historylist.AddString("1523(A) Connected OK...", RGB(0, 0, 250)); mThermoMeterConnectStatus = 1; //1:1523, 2:1594A } else { // Maybe Com port fail TRACE0("1523(A) Enumeration Fail !! Comm port Fail !!\n"); m_historylist.AddString("Try Thermometer 1523(A) Connected Fail...", RGB(250, 0, 0)); returnstatus = 1; } } else { m_historylist.AddString("Try Thermometer 1523(A) Connected COM port Fail...", RGB(250, 0, 0)); returnstatus = 1; } return returnstatus; } ///=====
實際上,在PC 端上關於 COMM port 的 readfile 及 writefile 是用COMx 來定義的,所以
原廠 FTDI 也有提供一個直接找COMx 的做法,也就是我們要說的第二個方法:
就是官方網頁 C++ 範例連結中的:Example 5:
This example shows how to use the FT_GetComPortNumber function call to determine the COM port assigned to a device and then how to open it.
To download this example, click here.
但是一般來說:我們關於UART 的 Tx/Rx 也沒那麼單純,往往就是一發一收,而且你也
不一定料得到 Device 端甚麼時候要發送,你的應用程式也不可能一直在等啊。
(現代PC host 端的作業系統也不可能讓你寫甚麼中斷服務程式了,所以你也只能不斷
的一直輪詢作業系統:有沒有收到 Rx或已經完成Tx 的工作而已。)...這種要不斷輪詢
ReadFile 及 WriteFile (尤其是被動的 ReadFile) 就是要用 Multithread 的做法來做。
但Multithread 的做法會因你的應用方式而有所不同,這邊我就不再進一步說明了。
但後面的這個範例裡,會有一些參考範例的,請自行參考研究。(因為這部分講下來,
既不容易三兩句講清楚,你也未必能一次到位...需要一點時間練習與修練的。)
-----
第二的就是 Silabs 的CP210x 的方式。因為Silabs 官方沒有提供任何COMM 範例程式。
所以我們就回到Microsoft 裡對於 COMM 的基礎應用了,一切都得靠自己來處理。
這個對於新手或對於PC 端 Host 程式開發不甚了解的人來說真的會有一定的挑戰度的。
以下,看得懂的就看,看不懂的就自行跳過,等你有一天看得懂時,再回頭看一下吧。
其實,在PC Host 端的MicroSoft 平台中,要連結底層外接裝置的東西,基本上都是透過
Device Register 來讀取的。就是你可以按 Win+R (Run) 執行 Regedit 來查看裝置內容的。
像在我的電腦裡,我就可以查到我的CP210x 是接到 COM10 上的。(但接到另一台上就
不一定了)。所以我就可以透過一些Microsoft 所提供的相關函數來查詢裝置內容與訊息:
HKEY hDeviceKey; DWORD dwSize, dwType;// , dwMemberIdx; //HKEY hKey; BYTE lpData[1024]; hDeviceKey = SetupDiOpenDevRegKey( DeviceInfoTable, &DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ ); dwType = REG_SZ; dwSize = sizeof(lpData); RegQueryValueEx(hDeviceKey, _T("PortName"), NULL, &dwType, lpData, &dwSize); RegCloseKey(hDeviceKey); FindCOMPort.Format(_T("%s"), lpData);
你可以看到程式裡就是查Windows 裡,安裝驅動程式後所註冊的內容,系統也安排好
COMx 的位置了。所以我們就知道這個 UART 轉USB 的COMx 的值了。
但接下來要你寫這個跟作業系統底層互動的COMM的通訊程式,真的太難了,所以
我們就可以直接套用網路上現有的公開程式源了,這方面有個對岸一直持續在Github
上維護的是:CSerialPort 。
這也是原先國外的開放源碼,對岸有人持續發展維護。雖然目前版本已經到了 4.x.x 。
這是因為後來它轉向跨平台的函數庫,如果針對原來 Microsoft 的Windows 系統的話,
你只要 V 3.0.3 版本就可以了,它底層的 Readfile/Writefile 就是採用 multithread 方式,
處理的,我上述提到如果你用FTDI 時,不知道如何用multithread 的話,這個原始碼
就可以供你參考。只不過它的採用 overlapped 方式處理。關於這個 Overlapped 的名詞
來說:它對應的名詞是 synchronous ,(想當然耳,回過頭來,這個Overlapped 的意思
就是asynchronous),字面的意思很難理解,簡單從功能來說:synchronous 就是當我
下達 Tx/Rx 的傳輸命令(Function Call) 之後,就得等到 Rx/Tx 完成之後才會返回。
而 Overlapped (asynchronous) 的做法很簡單,下達 Rx/Tx 命令後就可以返回了。
這個函數的參數設定是在我們Creatfile 時就要決定了:
HANDLE hComm; hComm = CreateFile( gszPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if (hComm == INVALID_HANDLE_VALUE) // error opening port; abort
後面那個參數 FILE_HANDLE_OVERLAPPED 就是指定Tx/Rx 方式。
如果使用 mulithread 方式就都沒差了。只不過,要把 multithread 寫好,也不容易啊。
所以你可以直接套用 CSerialPort 這個Class 就可以了。
----
以上就是一般在 PC Host 端寫UART (串列)傳輸的基本概念。但既然是UART 轉 USB,
你就得不得不考慮到 USB 的基本特性:Plug & Play 功能。你總不能規定說:
客戶使用者在操作這條 USB 線時,不能熱插拔啊。尤其當你面對完全不懂技術的客戶
或生產線上的操作員啊。所以你除了以上那些基本的 COMM 基本傳輸功能外,你還是
得在考慮這些特殊情況,所以在 FTDI 的官方應用上也有提供一個使用技術說明:
Example 7 :
This example shows how to use the Windows RegisterDeviceNotification function to receive WM_DEVICECHANGE messages when USB devices are inserted or removed from a system as outlined in application note AN_152.
AN_152 :How To Detect The Connection And Removal Of USB Devices On A System
有興趣者,也可以參考研究一下做法。
(其實,有些不懂技術的操作作業員,才是你技術增進的最大助力,你要如何防呆地把
系統寫得既穩定又方便操作的軟體,才是這些應用軟體的價值。)
-------------------------
綜合以上的簡單整理說明一下關於這種 UART 轉USB 方案,如何在PC host 端寫一套
屬於自己傳輸應用程式的幾個基本概念與方法。
雖然許多喜歡 DIY 電子系統平台的人來說:輕鬆上手與入門是大家努力的方向,
但往往事情愈往後段發展,事情總是會變得很複雜與麻煩。但終究很多原本看似簡單
的東西(或技術),到了後面就變得不簡單了。而你想賺人家的錢,上班領老闆的薪水
你就得把這些技術的事情給搞定,就算你弄得很棒的技術解決方案平台給客人,
又希望你的東西可以適時符合許多客戶市場的應用領域,事半功倍達到許多客戶市場
的彈性使用,透過PC host 端的應用軟體來設定系統,就變成一個基本手段,
而在系統上採用 UART 是一個既簡單又方便的傳輸介面,市面上也提供很多這種
便宜又方便的 UART 轉 USB 模組(或傳輸線)。我們就不得不就得面臨這種應用軟體
的開發與維護。
----
感想:以前我年輕時,我總覺得我只要懂個一兩個非常專業領域的技術就好,應該
夠我一輩子受用無窮,但事與願違,畢竟就業領薪水或創業搞產品開發,往往隨著
年紀增長,經驗累積就會告訴你說:你可能在許多產品開發,越就有全面性的技術
開發能力,你才能真正保有你個人的競爭力,畢竟不是每個公司或環境都可以具有
那麼多的資源可以幫你的。你的老闆(或客戶)不懂,但你心裡卻非常清楚:沒有這些
輔助工具軟體,產品技術開發與驗證真的很辛苦,雖然學習過程很艱辛,但走過了,
你心裡就比誰還踏實多了。所謂踏實多了,它的實質意義與價值在哪?
我舉個簡單的例子給大家心裡有個參考依據吧。我上面有舉個程式範例:
就是我幫某個實驗室寫個透過與Fluke 儀器設備的通訊軟體,來達到自動量測記錄
的軟體程式。一片這樣子 FTDI /CP210x 的 UART 轉 USB 模組,在網路售價多少?
現在是絕對不會超過台幣一百元的。但如果你幫這塊模組綁在一套自動化量測
工具軟體上時,你認為它價值多少?因為有位專門協助與輔導業界做實驗室認證的
顧問有問我說:這軟體賣不賣?他可以幫我牽線銷售,我們問他:你要賣多少?
他說:我賣多少?你不要問,你們開價要賣多少先說。
據我所了解:一般這個市場行情是六位數起跳的,也至少三十萬元起跳的。
(你想一想:一套節省人力可以二十四小時量測記錄的工具軟體,以現在人力成本
這麼高,就算老闆花個六十萬元,他一年下來就回本了啦。)
你算一下:硬體成本六十元的模組板子,整合一套軟體之後就成了六十萬元之後,
他的投報率多少?你問我:到底硬體好賺呢?還是軟體好賺?答案似乎很清楚了吧。
----
PS : 我要補充一點,如果是這一種UART 轉USB 的做法或直接走標準USB 的做法,
你問我:到底哪一個比較好?我會回答你說:當然就選標準USB 的做法,畢竟
標準USB 會取代傳統 UART(RS232) 的做法,肯定在許多方面(包括傳輸效率,傳輸
穩定性與上層軟體架構等)都是這種不得不還要依附在一個已經被時代所淘汰的
傳輸介面的所能比較的啊。
現在用python+tkinter寫usb轉232的pc host程式, 也許會比較容易, 而且不用花錢買visual studio, 業內人士可以參考看看
回覆刪除那是現在,人家十幾年前要當商品賣,有很多東西十年前都不存在。現在的visual studio不要錢就可以用了。我在20年前就遇到USB,要寫driver的環境是很辛苦的,在MCU上還很不好弄,沒有分析儀根本寸步難行。現在都是閉眼寫USB,廠商都打包好了。分析儀?用軟體的還不要錢。
刪除用 python+tkinter 寫APP ? 喔~ 我太老了。
刪除這種應該是我兒子他們這一代他們可能會用的。
我自己年輕時,摸過 Turbo C, Borland C 還有VB 一點點。
現在只要用 Visual Studio + MFC 可以賺一點老年年金就很欣慰了。
或許年輕工程師們,可以參考一下,自己找機會學學看吧。
年輕就是本錢,未來日子還多得很...慢慢摸,慢慢學沒問題的啦。
只是不要一味地永遠都在幹這種事,---- 現在物價、房價很高的耶~
找個務實可以養家活口的比較實際吧。
謝謝你的留言與建議。
@Bee 是啊。不是十幾年前,而是 2004 年我離開園區時的事。
刪除已經二十年前了。
不過也真的告訴我們:真的不要以為自己在搞甚麼偉大高尚美的技術。
讓你過個五年、十年的~在年輕一代的眼中,就是跟不上時尚的
老古董了。
凡事想清楚:只要有市場需求,克服解決別人的問題,就是好生意。
也不要整天幻想想跟大公司一樣:有好的資金、設備與人才資源等。
想做甚麼大事業、大產品?小東西、小產品賣得好,
一樣可以養家活口,輕鬆過日子的啦。
能借就不要租、能租就不要買...能善用不要錢的,創造獲利,才是
王道啊。