首页 > C++ > Win32串口API

Win32串口API

2014年5月26日 发表评论 阅读评论

哎哟我去,根据最近老板催我干活的状态,大概我离去帝都某查水表研究所搬砖的日子不远了,那边的活要做一个系统,系统里面包含一个上位机软件,上位机软件和各种硬件各种通信,然后其中一个精密的东西通信用的是串口,于是,有了这篇东西;

作为一个Google的代码搬运工,我看了好多别人的串口代码,但是都发现没有封装得很简洁很给力的API;

之前用的某个代码扔给师弟用后,发现有些端口检测不到,而且接受数据那个模块是每收到一个字节就会调用一次回调函数,有没有接收完还要我自己写代码判断;

其他的代码完全没有把串口这个模块彻底封装起来,在MFC里面操作串口那些函数还和各种修改窗口的控件的代码混在一起,基本不具备可移植性;

再有一些代码就是很底层很细的,每次用起来都好麻烦的样子。。

考虑到到时帝都的工作环境是绝对不允许我接触互联网的,所以我现在只能把可以做的那些模块尽可能的完善掉,保证去到那不会出什么意外,至少出了意外我要可以在不google的情况下解决;再另外,又为了可以尽快滚离帝都,我需要一个几行就可以用得上的API代码;懒得找,没找着,于是乎,我花了点时间,读了某个几千行的强大的串口助手的源码,一边剥一边改,捣鼓出来一个简单易用不知道稳不稳定的API来了。。

东西我扔Github上了。。有爱自取。。githubfavicon

下面把说明也扔一份在这博客了(其实是为了水一篇博文),搞不好可以帮助到路过的骚年们。。


食用方法:

环境:MFCVS2008下测试通过;(实验室的机子没有跑得起VS2013的,所以没法测试,估计没问题。。)

需要添加文件:

  • SerialPort.h
  • SerialPort.cpp
  • SerialPortApi.h
  • SerialPortApi.cpp

设置头文件:

#include "SerialPortApi.h"

添加封装的API变量:

CSerialPortApi COMPort;

打开串口并设置定时器(其实不一定要定时器,可以再开一个线程监视):

if(OPEN_PORT_SUCCESS == 
 COMPort.OpenPort("COM3",CBR_57600,8,NOPARITY,ONESTOPBIT)){
	SetTimer(RECV_TIMER_ID,50,NULL);
}
else{
	AfxMessageBox(COMPort.ErrorMsg());
}

定时器/监视线程内部读取数据方法:

if(COMPort.ReceiveFlag){//检测是否有接收到数据
	CString str += COMPort.ReadRecv();
	//Todo
}

发送数据:

COMPort.Send(str);

关闭串口:

COMPort.ClosePort();

妈蛋就这么几个,为什么别人要搞这么复杂?!


API说明:

1.打开串口:

INT CSerialPortApi::OpenPort(
       CString sPort,
       DWORD dwBaudRate,
       BYTE byDataBits,
       BYTE byParity,
       BYTE byStopBits);

输入参数:

  • sPort:端口号,一定要属于PortList(见下文)中的元素,否则会返回错误;
  • dwBaudRate:波特率,使用BAUDRATE中的宏元素,否则会返回错误;
  • static const DWORD BAUDRATE[] = {
    CBR_110,CBR_300,CBR_600,CBR_1200,CBR_2400,
    CBR_4800,CBR_9600,CBR_14400,CBR_19200,
    CBR_38400,CBR_56000 ,CBR_57600,
    CBR_115200 ,CBR_128000 ,CBR_256000};
    
  • byDataBits:数据位,可选值只有6,7,8,否则返回错误;
  • byParity:校验位,使用PARITY中的宏元素(分别表示无校验,奇校验,偶校验,标记,空格),否则会返回错误;
  • static const DWORD PARITY[] = {
    NOPARITY,ODDPARITY,EVENPARITY,MARKPARITY,SPACEPARITY};
    
  • byStopBits:停止位,使用STOPBITS中的宏元素(分别表示1,1.5,2),否则会返回错误;
  • static const DWORD STOPBITS[]={
    ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS};
    

返回值:

  • 串口名有误: PORT_NUM_INVALID
  • 波特率有误:BAUDRATE_INVALID
  • 数据位有误:DATABITS_INVALID
  • 校验位有误:PARITY_INVALID
  • 停止位有误:STOPBIT_INVALID
  • 成功:OPEN_PORT_SUCCESS
  • 打开失败:OPEN_PORT_FAIL

OPEN_PORT_FAIL的详细错误信息可以用CString CSerialPortApi::ErrorMsg()获取,另外如果打开串口失败又找不到原因的话,可以将SerialPortApi.h中的:

#define PORT_DEBUG_MODE 0

改成:

#define PORT_DEBUG_MODE 1

这样出错了会以弹窗显示错误原因;

2.关闭串口:

INT CSerialPortApi::ClosePort();

返回值:

  • 成功:CLOSE_PORT_SUCCESS
  • 失败:CLOSR_PORT_FAIL

3.发送数据:

void CSerialPortApi::Send(CString str);

输入参数:

  • str:待发送数据

4.读取数据:

CString CSerialPortApi::ReadRecv();

返回值:

  • 读取到的数据

说明:读取数据之前请先判断ReceiveFlag是否为TRUE,否则读到的是空字符串;

5.读取错误信息:

CString CSerialPortApi::ErrorMsg();

返回值:

  • 错误代码和错误消息

可用变量和方法:

1.可用的端口:
CString PortList[MAX_PORT_NUM];

2.获取可用的端口数目:
size_t GetPortNum();

3.缓冲区是否有接受数据未读取:
BOOL ReceiveFlag;

Private函数说明:

说明:由于是私有函数,这一部分函数原则上是无法直接拿来用的,使用者原则上无需知晓内部怎么实现,如为了读代码或者修复BUG可以稍作了解;

1.获取所有可用的端口:

void CSerialPortApi::getExistPort()

基于读取注册表项HARDWARE\DEVICEMAP\SERIALCOMM,理论上和设备管理器看到的一样。

调用后将会初始化CSerialPortApi内的两个变量:PortNum,PortList,分别表示读取到的串口的数目和列表;

API类变量外部读取PortNum要使用size_t GetPortNum()函数;

getExistPort放在了CSerialPortApi的构造函数中,所以定义变量后即可读取PortList;

2.发送数据线程:

static UINT CSerialPortApi::SendThreadProc(LPVOID pParam);

线程开启后,只要b_portIsOpenTRUE,即一直在一个while循环中等待,由m_hSendEvent变量激活发送模块,激活方法是:SetEvent(m_hSendEvent);,激活后即会发送Str4Send中的信息;

3.接受数据线程:

static UINT CSerialPortApi::RevThreadProc(LPVOID pParam);

该线程如果接收到数据,会把数据放到deque m_dequeRevData的尾部,并且设置ReceiveFlagTRUE;

如果检测到ReceiveFlagTRUE,可以调用ReadRecv()读取数据,将读取到的缓存数据转化为CString类型变量返回,并且清空队列,设置ReceiveFlagFALSE

由于采用了信号锁机制,所以不会发生RevThreadProcm_dequeRevData写信息的同时ReadRecv()m_dequeRevData读信息这种事情;

其他:

如果想像串口助手等辅助软件一样添加若干个CComboBox,然后供用户选择的话,可以设置如下五个CComboBox变量关联相关控件:

CComboBox m_port;
CComboBox m_baudrate;
CComboBox m_parity;
CComboBox m_databits;
CComboBox m_stopbits;

然后使用下面代码初始化即可:

	m_port.ResetContent();

	//Set Port
	for(size_t i = 0;i < COMPort.GetPortNum();i++)
	{
		m_port.AddString(COMPort.PortList[i]);
	}
	m_port.SetCurSel(0);

	//Set Baudrate
	CString str = "";
	m_baudrate.ResetContent();
	int i = 0;
	for(i = 0;i<sizeof(BAUDRATE)/sizeof(DWORD);i++)
	{
		str.Format(_T("%d"),BAUDRATE[i]);
		m_baudrate.AddString(str);
	}
	m_baudrate.SelectString(-1,"57600");

	m_parity.ResetContent();
	m_parity.AddString(_T("无校验"));
	m_parity.AddString(_T("奇校验"));
	m_parity.AddString(_T("偶校验"));
	m_parity.AddString(_T("标记"));
	m_parity.AddString(_T("空格"));
	m_parity.SetCurSel(0);

	m_databits.ResetContent();
	m_databits.AddString(_T("6"));
	m_databits.AddString(_T("7"));
	m_databits.AddString(_T("8"));
	m_databits.SelectString(-1,"8");

	m_stopbits.ResetContent();
	m_stopbits.AddString(_T("1"));
	m_stopbits.AddString(_T("1.5"));
	m_stopbits.AddString(_T("2"));
	m_stopbits.SetCurSel(0);

以上!!【唉~刷完存在感~浑身舒畅!!】


【完】

本文内容遵从CC版权协议,转载请注明出自http://www.kylen314.com

分类: C++ 标签: , ,
  • 我们的同志遍布五湖四海,甚至打入了帝都某极密科学机关内部(雾查了下上位机和串口是啥,发现你从事的工作好杂啊。

    • 我早就这样觉得了。。

      • 其实工作不杂,只是我那些乱七八糟的博文会让人以为那是我的正业。。其实都是业余兴趣而已。。

        • 佩服你有这么多的业余兴趣并且能做的这么好啊

    • 其实不是打入,而是被扔进去了。。只是隔行如隔山,电子类的专业大一的就会接触到这些概念了。。

      • 我就是电子类的,我到大二都没在课上学到,只是这学期玩单片机的时候接触了些。

        • 额。。好吧,我们以前那个班是按三年毕业标准来设立课程的可能会早一些。。

          • 我们学校确实学的很慢,中科大大二上就把模电和数电学完了,我们大二下才开模电,大三上才开数电,落后一年啊。哎不知学校是怎么想的,貌似没有比我们进度更慢的学校。

            • 大三才开数电?!是有点慢。。我们大一下是数电和电路,大二上是模电,大三上还是大二下开的高频和射频。

      • 大一净学了一堆乱七八糟的通识课,不分专业有空余时间都不知道该学神马,一直觉得无比坑爹……

        • 我们那个班级还好,因为入学时学制是三年,所以赶紧让我们提前进实验室做项目,提前学课程以及。。提前考四六级。。但是最后我们集体要求改成四年,所以大四就完全没事干在学校混日子。。

  • 话说开始好奇菊苣是啥专业的说……

    • 本科入学录取时是微电子,后来转通信工程,现在是微波光子学,搞的是水声。。【别吐槽

      • 通信啊,难怪…话说微电子具体是学啥的一直不明白我学校通信工程全是一群学霸,果断分专业的时候挤不进去

        • 微电子主要偏向于半导体器件的制作,工艺,封装,原理这一块的吧。。信工确实竞争很紧张,我们本科那里信工一个是转专业班,对于有第二次生命的他们普通的觉悟根本不够战斗;然后我们班虽然三年毕业,但是是淘汰制的。。。

        • 通信工程全是一群学霸。。我学校也是啊,谁能解释下

          • 因为这个专业最抢手,学的东西广度比较大

  • 云里雾里

  • Vespa每次刷存在感都异常给力啊

    • 这个博客唯一的作用就是给我刷存在感用。。

      • 有内容才刷得出存在感啊

        • 所以这篇本来讲串口的博文活生生被评论搞成一篇讨论大学专业方向以及如何刷存在感的东西。。

          • 不要在意细节

          • 刷存在感……我也是刚刚发了篇特别水的文章刷存在感。

  • 下午好,

  • 完全是代码水啊

    • 没人说过不可以用代码水啊。。

  • Yu

    直接fork了,然后突然发现几百年没用windows写过程序了,唯一的用来下各种番的win8只装了个qt,连vs2008都没得

    • “砖”务需求,无法彻底脱离windows。。总不能给某所开发一个linux程序吧。。。而且有些仿真软件只有win有。。虽然我现在办公室两台电脑,一台win的用来搬砖,另一台搜刮来的电脑装了个ubuntu完全自娱自乐调教用。。

      • Yu

        我给某神秘机关做个东西,表示"我们的东西只能在linux下用"。然后一本正经的给他们机架上的服务器装了个fedora因为实验室mac使用者最多,linux其次,windows最后,所以基本没啥"必须要windows"的情况。突然感觉好幸运。然后linuxer貌似都ubuntu,只有一个货是arch,我大fedora er 好孤独....

        • 专业不一样导致待遇不一样。。比如我们这边macer=0,我说我做了个linux下的东西给别人的话,老板非劈了我不可。。

          • Yu

            主要大概是面向的用户在程序中的地位。我们主要是搞数据的,所以后台想咋折腾咋折腾,最后随便开个页面展示下,只要让用户浏览器看到的巴掌大地方没问题就成。但前台有时候也挺扯的。比如前面提到的那个神秘机关的头头说他们用的是IE6,看不了基于CSS3的d3js生成的效果。为了让所有人都看到,不得不将若干页面做了两份,一份漂亮一份兼容。居然要为10年前的浏览器擦屁股,唉。。。不过,反正不是我做,对我来说反正各种model各种index各种service搞定就成

            • 我们这种是纯粹做光学的实验室,实验室码代码的也就我一个人,linux这种东西根本普及不了。。果然还是计算机相关的专业好玩啊~

              • 信电的搞光电,光电的搞信电了

  • 你学啥专业的。

  • yuki

    某所到底是啥。。。该不会是⑨所吧。。。终于还是走上了毁灭世界的道路

    • 当然不是⑨。。HTS,项目就是某次在你家跟你讲的那个。。是呀,国家又让我造高达去了。。

      • yuki

        还好。。。如果是⑨所搞不好真的就消失于人世了

        • 靠。。打脸了,真是⑨。。。。

          • yuki

            跪,我吓得不敢说话了。。。

            • 为啥可怕,我觉得还好啊

  • 我之前做这个,先是用了labview,然后又用的pyserial

    • 我这个项目必须C++。。木有办法。。

      • 貌似我从来没有真正的使用过c++这门语言。最多写个cout和include iostream,然后里面的代码就是C了

        • 毕竟不需要做软件,脚本化的话都很少会用C++

  • gnaggnoyil

    fork一发

  • 下次进来之前先滴好眼药水。。