2024年2月18日 星期日

USB to UART (RS232/RS485) 故事與應用(四) ---Host 端軟體整理

在寫其他相關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_152How 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) 的做法,肯定在許多方面(包括傳輸效率,傳輸

穩定性與上層軟體架構等)都是這種不得不還要依附在一個已經被時代所淘汰的

傳輸介面的所能比較的啊。

4 則留言:

  1. 現在用python+tkinter寫usb轉232的pc host程式, 也許會比較容易, 而且不用花錢買visual studio, 業內人士可以參考看看

    回覆刪除
    回覆
    1. 那是現在,人家十幾年前要當商品賣,有很多東西十年前都不存在。現在的visual studio不要錢就可以用了。我在20年前就遇到USB,要寫driver的環境是很辛苦的,在MCU上還很不好弄,沒有分析儀根本寸步難行。現在都是閉眼寫USB,廠商都打包好了。分析儀?用軟體的還不要錢。

      刪除
    2. 用 python+tkinter 寫APP ? 喔~ 我太老了。
      這種應該是我兒子他們這一代他們可能會用的。
      我自己年輕時,摸過 Turbo C, Borland C 還有VB 一點點。
      現在只要用 Visual Studio + MFC 可以賺一點老年年金就很欣慰了。
      或許年輕工程師們,可以參考一下,自己找機會學學看吧。

      年輕就是本錢,未來日子還多得很...慢慢摸,慢慢學沒問題的啦。
      只是不要一味地永遠都在幹這種事,---- 現在物價、房價很高的耶~
      找個務實可以養家活口的比較實際吧。
      謝謝你的留言與建議。

      刪除
    3. @Bee 是啊。不是十幾年前,而是 2004 年我離開園區時的事。
      已經二十年前了。
      不過也真的告訴我們:真的不要以為自己在搞甚麼偉大高尚美的技術。
      讓你過個五年、十年的~在年輕一代的眼中,就是跟不上時尚的
      老古董了。
      凡事想清楚:只要有市場需求,克服解決別人的問題,就是好生意。
      也不要整天幻想想跟大公司一樣:有好的資金、設備與人才資源等。
      想做甚麼大事業、大產品?小東西、小產品賣得好,
      一樣可以養家活口,輕鬆過日子的啦。
      能借就不要租、能租就不要買...能善用不要錢的,創造獲利,才是
      王道啊。

      刪除