前言 & 吐槽
前幾天重裝了系統(tǒng),然后之前寫的用MsComm控件(Microsoft Communications Control)進行串口通信的程序無法運行了敌卓,用VS打開項目進入資源視圖,發(fā)現(xiàn)打不開包含MsComm控件的對話框資源癣防,提示未在此計算機上注冊ActiveX控件肪虎。而VS工具箱提供的COM組件里也沒有MsComm控件惧蛹。
根本原因是這個ActiveX控件根本不是win10安裝自帶的,高版本VS也不會自帶的迅腔,你要寫這種串口通信程序沧烈,要么用底層API像云,要么用微軟大力支持的.Net系語言,而不是MFC這個非常非常過時的破東西了腋逆,然而就這個破東西惩歉,很多時候還是被迫得寫俏蛮,嗯,前幾天才看到2018年的二級C/C++上機環(huán)境終于從萬年不變的VC6變成了VS2010争涌,簡直了辣恋。
注冊MsComm控件
首先給下載地址抑党,其實谷歌MsComm.ocx第一條就是
https://www.ocxme.com/files/mscomm32_ocx
當然國內(nèi)用百度的多,一堆文章全部都指向了CSDN花幾個積分的下載鏈接害晦,好點的有百度盤鏈接,不過天知道過段時間會不會被和諧鲫剿。還是這種專門的免費下載站更舒心灵莲。
下完解壓后就是MSCOMM32.OCX殴俱,然后需要注意(我踩了這個坑),對于64位系統(tǒng)需要把它放在C:\Windows\SysWOW64
目錄下明场,32位系統(tǒng)才是C:\Windows\system32
目錄苦锨。之前我放在system32目錄然后注冊出了問題趴泌。
然后右鍵管理員權限打開cmd窗口,進入對應目錄輸入指令regsvr32 MSCOMM32.OCX
即可(如下圖所示)
網(wǎng)上有些教程還有一步是修改注冊表秃励,不過我發(fā)現(xiàn)regsvr32命令已經(jīng)修改了注冊表莺治,無需手動修改谣旁。
為何使用MsComm控件滋早?
其實直接用底層API進行串口編程未嘗不可,MSDN也給出了示例搁进,但是事件回調(diào)的步驟得自己寫饼问,如果不是時間充裕用來學習/練手的情況揭斧,沒必要重復造輪子。
網(wǎng)上也可以搜到不少包裝好的C++類盅视,但是串口這東西本來就是古老物了闹击,搜到的代碼還是很多年前的,代碼風格不一定很好贺归。而且有的接口已經(jīng)和現(xiàn)代C++標準不兼容了。
比如之前師兄用了一個簡單小巧的庫(只包含1個頭文件和1個源文件,添加進工程即可)提供了這樣的接口
void WriteToPort(char* string);
然而C++11標準在近幾個版本的VS里已經(jīng)得到了支持,字符串字面值是不能直接轉(zhuǎn)換成char*類型的丹莲,也就是說實際調(diào)用的時候得像這樣 xxx.WriteToPort((char*)"hello")
或xxx.WriteToPort(const_cast<char*>("hello"))
然后錯誤信息字符串操作全都是基于char*的甥材,MFC默認Unicode,而且方便移植的代碼應該都對TCHAR*來操作洲赵,直接編譯會出錯的,需要一個個用宏_T()把字符串包含起來芝发。當然辅鲸,會正則表達式的話起來替換相對會比較輕松。
我對MsComm控件不甚了解独悴,但是明顯這也是古老物了刻炒,畢竟還不支持64位程序自沧,VS里用Debug或Release x64來編譯的話會失敗。
但是好處在于,這個控件是官方的移迫,值得信賴厨埋,不像很多開源庫那樣缺乏大量測試捐顷。
為何不使用MsComm控件?
這篇文章是2018-01-17發(fā)布的废赞,我在2018-02-01完成課題程序時發(fā)現(xiàn)了問題叮姑。正如上文所言传透,MsComm控件是古老物了,只適用于32位的程序朱盐。如果程序必須編譯成64位的兵琳,那么MsComm控件無法派上用場。我的程序里需要用到OpenCV和一個第三方庫者春,兩者剛好都只提供了64位的lib和dll碧查,所以只有使用MsComm控件,后來使用了上文提到的CSerialPort忠售,才發(fā)現(xiàn)這個古老版本原來國內(nèi)有不少人進行了維護稻扬,現(xiàn)在還是非常好用的羊瘩,強力推薦盼砍。
添加MsComm控件
首先需要在VS中添加該控件到工具箱中
按上述操作點確定即可
PS:可以看到版本才1.1,還是version 6.0黔宛,目測從VC6之后再也沒更新過……
然后在對話框資源編輯框中右鍵,插入ActiveX控件
不同于常規(guī)的界面控件,MsComm控件不會顯示出來案淋,所以隨便拖到哪個位置都可以险绘。
拖了控件之后就是為控件添加變量了
這一步隆圆,VS會自動生成一對.h和.cpp文件,然后在xxxDlg.h中添加成員變量
CMscomm1 m_comm1;
并在xxxDlg.cpp中添加數(shù)據(jù)交換操作
void CTestDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_MSCOMM1, m_comm1);
}
可以發(fā)現(xiàn)和使用其他控件一樣的套路蹬屹。
從串口發(fā)送和接收字節(jié)
文本框/復選框這種自定義參數(shù)輸入的控件就不祥述了慨默,以我的需求為例
波特率: 9600 停止位: 1 傳送位數(shù): 8位 奇偶校驗: 無
串口協(xié)議是7個字節(jié)表示1個數(shù)據(jù)包
MsComm控件類型為CMscomm_las,變量名為m_commLaser
- 打開/關閉串口
m_commLaser.put_CommPort("COM1");
m_commLaser.put_InputMode(CMscomm_las::comInputModeBinary);
m_commLaser.put_InBufferSize(512); // 接收緩沖區(qū)大小
m_commLaser.put_OutBufferSize(512); // 發(fā)送緩沖區(qū)大小
m_commLaser.put_Settings(_T("9600,n,8,1"));
if (!m_commLaser.get_PortOpen())
{
try {
m_commLaser.put_PortOpen(TRUE); // 打開串口
}
catch (CException* e) {
TCHAR error_msg[1024];
e->GetErrorMessage(error_msg, 1024);
MessageBox(error_msg);
return;
}
m_commLaser.put_RThreshold(7); // 每當接收緩沖區(qū)有7個字符時則接收串口數(shù)據(jù)
m_commLaser.put_InputLen(0);
m_commLaser.get_Input();
}
else
{
MessageBox(_T("打開端口失敗!"));
}
注意put_Settings的參數(shù)潮太,n代表無奇偶校驗虾攻,是DCB結(jié)構(gòu)的Parity成員的可選取值NOPARITY的縮寫,同理奇钞,奇校驗ODDPARITY是o漂坏,偶校驗EVENPARITY是e。
打開串口是put_PortOpen(TRUE)谷徙,關閉串口自然就是put_PortOpen(FALSE)。
- 發(fā)送字節(jié)序列
void put_Output(VARIANT newValue)
直接調(diào)用上述方法即可图呢,問題來了骗随,VARIANT類型是什么鸿染?這就是ActiveX控件的蛋疼之處,它的數(shù)據(jù)交換必須用Ole那一套來涨椒。
正常來說蚕冬,發(fā)送給串口的都是字節(jié)序列,即uint8_t或BYTE數(shù)組猎提。因此這里只需要知道怎么轉(zhuǎn)換成VARIANT類型即可旁蔼。
MFC是上古時期的產(chǎn)物棺聊,那個時候C++98標準都沒確立,因此微軟弄了一堆自定義的容器類葵诈,雖然除了CString外幾乎都被C++標準庫的STL取代了作喘。但在這里耐亏,MFC的CByteArray剛好能直接用來構(gòu)造COleVariant對象,COleVariant繼承自VARIANT(C結(jié)構(gòu)體的typedef別名)暇矫,僅僅是增加了若干方法,可以隱式類型轉(zhuǎn)換李根。
比如發(fā)送3個字節(jié)0x01 0x02 0x04給串口的代碼如下
CByteArray m_baSend;
m_baSend.Add(0x01);
m_baSend.Add(0x02);
m_baSend.Add(0x04);
m_commLaser.put_Output(COleVariant(m_baSend));
-
接收字節(jié)序列
給MsComm控件添加OnComm事件處理程序即可
和其他控件一樣粤攒,MsComm控件也是事件驅(qū)動囱持,在后臺接收數(shù)據(jù),然后處理不同的事件
enum
{
comEvSend = 1,
comEvReceive = 2,
comEvCTS = 3,
comEvDSR = 4,
comEvCD = 5,
comEvRing = 6,
comEvEOF = 7
}OnCommConstants
接收數(shù)據(jù)只需要處理comEvReceive事件即可盔几,其他幾個事件等真正有需求的時候再去處理掩幢。下面給出我接收7個字節(jié)的代碼
void CMsCommDemoDlg::OnCommMscommLas()
{
if (m_commLaser.get_CommEvent() == CMscomm_las::comEvReceive)
{
// 讀取串口的接收緩沖區(qū)(之前打開串口時設置過緩沖區(qū)大小)
COleSafeArray safearray_obj = m_commLaser.get_Input();
// 填充數(shù)據(jù)到自定義緩沖區(qū)中
const int BUFF_SIZE = 7;
BYTE buffer[BUFF_SIZE];
for (long i = 0; i < BUFF_SIZE; i++)
{
safearray_obj.GetElement(&i, &buffer[i]);
}
// TODO: 處理緩沖區(qū)buffer[]的數(shù)據(jù)
}
}
這里就和發(fā)送數(shù)據(jù)反過來了芯丧,接收的數(shù)據(jù)類型是VARIANT世曾,需要轉(zhuǎn)換成BYTE數(shù)組來處理。套路就是借助COleSafeArray這個中間物及其GetElement方法肿轨。
總結(jié)
其實這個程序是我?guī)字苤皩懙腄emo,最近要重新寫了驼唱,照著我的代碼寫下來玫恳,自己也梳理了用MsComm控件的步驟京办。
控件方面,由于這東西實在太古老了而被拋棄了不恭,而官方也沒給C++提供什么替代品,于是得手動引入
- 注冊MSCOMM32.OCX
- 在VS工具箱的COM組件中找到MsComm控件添加進來
- 像使用其他MFC控件一樣使用它(添加關聯(lián)的控件變量/事件處理函數(shù))
代碼方面折晦,雖然COM接口的實現(xiàn)看起來非常復雜且蛋疼沾瓦,但是其實接口很清晰,都是一堆put(設置)和get(獲确缋)方法魂莫,注意打開/關閉串口也是通過putxxx來執(zhí)行的豁鲤,相當于設置串口的連接狀態(tài)鲸沮。
難點在于數(shù)據(jù)交換格式都是VARIANT琳骡,因此需要借用MFC專門提供的類來轉(zhuǎn)換成方便處理的BYTE數(shù)組。大致過程如下
BYTE[] -> CByteArray -> COleVariant -> VARIANT
VARIANT -> COleSafeArray -> BYTE[]