MFC之路 第二节 串口通信篇

因为我自己正好也在为项目写一个控制软件,所以自己做到哪就写到哪吧。项目中软件与下位机之间通过232串口进行数据通信,所以今天打算实现串口通信的相关功能,那么就随着我一步一步地来完成串口通信功能吧。为了全面的学习串口通信的各种功能,我们一起完成一个常用的串口通信助手软件。
1、第一步,先新建一个MFC的对话框工程。
创建完成MFC对话框之后,将主对话框上的3个默认控件删除,方便我们下一步添加自己的控件。

2、第二步,首先完成串口参数设置功能和串口打开关闭的功能。
在空白的对话框上添加一个“串口打开/关闭”按钮,一个串口选择Combo控件,和波特率、校验位、数据位、停止位的Combo控件,如下图所示

为每一个Combo控件添加响应的变量,如下
//设波特率组合列表框
	TCHAR baudbuffer[][7]={"300","600","1200","2400","4800","9600","19200","38400","43000","56000","57600","115200"};
    for(int i=0;i<12;i++)
	{
		int judge_tf=m_ComboBaud.AddString(baudbuffer[i]);
		if((judge_tf==CB_ERR)||(judge_tf==CB_ERRSPACE))
           MessageBox("build baud error!");
	}
	m_ComboBaud.SetCurSel(5);

    //设串口组合列表框
	TCHAR seriou[][5]={"COM1","COM2","COM3","COM4"};
	for(int i=0;i<4;i++)
		m_ComboSeriou.AddString(seriou[i]);
	m_ComboSeriou.SetCurSel(0);

	//设校验位组合列表框
	TCHAR jiaoyan[][7]={"N","O","E"};
	for(int i=0;i<3;i++)
		m_ComboJiaoyan.AddString(jiaoyan[i]);
	m_ComboJiaoyan.SetCurSel(0);

	//设数据位组合列表框
	TCHAR data[][2]={"8","7","6"};
	for(int i=0;i<3;i++)
		m_ComboData.AddString(data[i]);
	m_ComboData.SetCurSel(0);

	//设停止位组合列表框
	TCHAR stop[][2]={"1","2"};
	for(int i=0;i<2;i++)
		m_ComboStop.AddString(stop[i]);
	m_ComboStop.SetCurSel(0);
接下来打开串口并设置相关参数:
COMMTIMEOUTS TimeOuts;  //定义超时时间

	m_ComboSeriou.GetLBText(Num,m_SeriouStr);     //获取串口Combo下拉框中对应于Num位置的串口名称,比如Num=0时,m_SeriouStr 为"COM1"
	m_hCom=CreateFile(m_SeriouStr,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,   //将串口作为一个文件来看,用CreateFile()函数打开串口,返回结果存储在m_hCom中
		FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);
	if(m_hCom==INVALID_HANDLE_VALUE)             //如果返回INVALID_HANDLE_VALUE表示打开串口失败,
	{
		AfxMessageBox(_T("打开串口失败!"));     //失败时弹出对话框提醒
		m_bConnected=0;                         //将串口连接标志设为0
		return FALSE;                           //打开失败后不再继续往下进行,直接返回FALSE
	}

	//设置新创建串口的响应事件
	SetCommMask(m_hCom,EV_RXCHAR);    //其中m_hCom是新创建串口文件返回的句柄,EV_RXCHAR代表读事件,有任何字符返回到串口时事件响应
	SetupComm(m_hCom,MAXBLOCK,MAXBLOCK);  	//设置读写缓冲区   其中m_hCom是新创建串口文件返回的句柄,MAXBLOCK是自己定义(#define MAXBLOCK 4096)的串口缓存区的大小,此处为4096字节,第2、3个参数分别为读缓存区和写缓存区大小
	
	//设置超时
	TimeOuts.ReadIntervalTimeout=MAXDWORD;  //读间隔超时
	TimeOuts.ReadTotalTimeoutMultiplier=0;  //读时间系数
	TimeOuts.ReadTotalTimeoutConstant=0;    //读时间常量
	TimeOuts.WriteTotalTimeoutMultiplier=0; //写时间系数
	SetCommTimeouts(m_hCom,&TimeOuts);   

	//设置串口参数
	DCB dcb;    //DCB结构,定义了串口通信设备的控制设置
	if(!GetCommState(m_hCom,&dcb))  //读取新创建的m_hCom串口句柄的DCB设备控制块结构体,当只需要设置一部分DCB参数时,可以通过此函数读取现有参数,只改变部分参数即可
		return FALSE;               //如果读取不成功直接结束

	//设置基本参数
	long baudrate[]={300,600,1200,2400,4800,9600,19200,38400,43000,56000,57600,115200};
	int baudindex=m_ComboBaud.GetCurSel();
	m_ComboBaud.GetLBText(baudindex,m_BaudStr);
	dcb.BaudRate=baudrate[baudindex];           //读取并设置波特率参数

	int databit[]={8,7,6};
	int dataindex=m_ComboData.GetCurSel();
	m_ComboData.GetLBText(dataindex,m_DataStr);
	dcb.ByteSize=databit[dataindex];             //读取并设置数据位参数

	int jiaoyanindex=m_ComboJiaoyan.GetCurSel();  
	m_ComboJiaoyan.GetLBText(jiaoyanindex,m_JiaoyanStr);
	switch(jiaoyanindex)
	{
	case 0:
		dcb.Parity=NOPARITY;                     //读取并设置校验位参数
		break;
	case 1:
		dcb.Parity=ODDPARITY;
		break;
	case 2:
		dcb.Parity=EVENPARITY;
		break;
	default:;
	}

	int stopindex=m_ComboStop.GetCurSel();
	m_ComboStop.GetLBText(stopindex,m_StopStr);
	switch(stopindex)
	{
	case 0:
		dcb.StopBits=ONESTOPBIT;                //读取并设置停止位参数
		break;
	case 1:
		dcb.StopBits=TWOSTOPBITS;
		break;
	default:;
	}

	//流控制
	dcb.fInX=TRUE;
	dcb.fOutX=TRUE;
	dcb.XonChar=XON;     //	#define XON 0x11
	dcb.XoffChar=XOFF;   //	#define XOFF 0x13
	dcb.XonLim=50;
	dcb.XoffLim=50;
	dcb.fNull=TRUE;

	BOOL SetComParameterSucceed = SetCommState(m_hCom,&dcb);     //设置串口参数信息,如果设置成功返回1,失败返回0
在上面的程序注释中已经对各条语句进行了详细的说明,现在对其中的几个重要语句另做一些解释:
首先是CreateFile()函数:
m_hCom=CreateFile(m_SeriouStr,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);
这是一个多功能的函数,可用于打开或创建以下对象,并返回可访问的句柄:控制台、通信资源、目录(只读打开)、磁盘驱动器、文件、邮槽、管道。
函数结构如下:
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
返回值:如果创建成功,则返回创建的文件的句柄,如果出错则返回INVALID_HANDLE_VALUE,并会设置GetLastError值。即使函数成功,但若文件存在,且指定了CREATE_ALWAYS 或 OPEN_ALWAYS,GetLastError也会设为ERROR_ALREADY_EXISTS。
参数说明:
lpFileName String要打开的文件的名或设备名。这个字符串的最大长度在ANSI版本中为MAX_PATH,在unicode版本中为32767。
dwDesiredAccess指定类型的访问对象。如果为 GENERIC_READ 表示允许对设备进行读访问;如果为 GENERIC_WRITE 表示允许对设备进行写访问(可组合使用);如果为零,表示只允许获取与一个设备有关的信息。
dwShareMode,共享模式, 如果是零表示不共享; 如果是FILE_SHARE_DELETE表示随后打开操作对象会成功只要删除访问请求;如果是FILE_SHARE_READ随后打开操作对象会成功只有请求读访问;如果是FILE_SHARE_WRITE 随后打开操作对象会成功只有请求写访问。
lpSecurityAttributes指向安全属性的指针, 指向一个SECURITY_ATTRIBUTES结构的指针,定义了文件的安全特性(如果操作系统支持的话)
dwCreationDisposition,如何创建。下述常数之一:
CREATE_NEW 创建文件;如文件存在则会出错
CREATE_ALWAYS 创建文件,会改写前一个文件
OPEN_EXISTING 文件必须已经存在。由设备提出要求
OPEN_ALWAYS 如文件不存在则创建它
TRUNCATE_EXISTING 将现有文件缩短为零长度
dwFlagsAndAttributes文件属性, 一个或多个下述常数
FILE_ATTRIBUTE_ARCHIVE 标记归档属性
FILE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式
FILE_ATTRIBUTE_NORMAL 默认属性
FILE_ATTRIBUTE_HIDDEN 隐藏文件或目录
FILE_ATTRIBUTE_READONLY 文件为只读
FILE_ATTRIBUTE_SYSTEM 文件为系统文件
FILE_FLAG_WRITE_THROUGH 操作系统不得推迟对文件的写操作
FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作
FILE_FLAG_NO_BUFFERING 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
FILE_FLAG_RANDOM_ACCESS 针对随机访问对文件缓冲进行优化
FILE_FLAG_SEQUENTIAL_SCAN 针对连续访问对文件缓冲进行优化
FILE_FLAG_DELETE_ON_CLOSE 关闭了上一次打开的句柄后,将文件删除。特别适合 临时文件
也可在Windows NT下组合使用下述常数标记:
SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, SECURITY_DELEGATION, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY
hTemplateFile,hTemplateFile为一个文件或设备句柄,表示按这个参数给出的句柄为模板创建文件(就是将该句柄文件拷贝到 lpFileName指定的路径,然后再打开)。它将指定该文件的属性扩展到新创建的文件上面,这个参数可用于将某个新文件的属性设置成与现有文件一样,并且这样会忽略dwAttrsAndFlags。通常这个参数设置为NULL,为空表示不使用模板,一般为空。

接下来对超时结构体进行说明:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; // 读间隔超时
DWORD ReadTotalTimeoutMultiplier; // 读时间系数
DWORD ReadTotalTimeoutConstant; // 读时间常量
DWORD WriteTotalTimeoutMultiplier; // 写时间系数
DWORD WriteTotalTimeoutConstant; // 写时间常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
在用ReadFile和WriteFile读写 串行口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会 结束。要查询当前的超时设置应调用 GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用 SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。 有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。
COMMTIMEOUTS结构的成员都以毫秒为单位。
ReadIntervalTimeout:两字符之间最大的延时,当读取串口数据时,一旦两个字符传输的时间差超过该时间,读取函数将返回现有的数据。设置为0表示该参数不起作用。指定时间最大值(毫秒),允许接收的2个字节间有时间差。也就 是说,刚接收了一个字节后,等了ReadIntervalTimeout时间后还没有新的字节到达,就 认为本次读串口操作结束(后面的字节等下一次读取操作来处理)。即使你想读8个字节,但读第2个字节后,过了ReadIntervalTimeout时间后,第3个字节还没到。实际上就只读了2个字节。
ReadTotalTimeoutMultiplier:指定比例因子(毫秒),实际上是设置读取一个字节和等待下一个字节所需的时间,这样总的超时时间为读取的字节数乘以该值,同样一次读取操作到达这个时间后,也认为本次读操作己经结束。
ReadTotalTimeoutConstant:一次读取串口数据的固定超时。所以在一次读取串口的操作中,其超时为ReadTotalTimeoutMultiplier乘以读取的字节数再加上 ReadTotalTimeoutConstant。将ReadIntervalTimeout设置为MAXDWORD,并将ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant设置为0,表示读取操作将立即返回存放在输入缓冲区的字符。可以理解为一个修正时间,实际上就是按ReadTotalTimeoutMultiplier计算出的超时时间再加上该时间才作为整个超时时间。
WriteTotalTimeoutMultiplier:写入每字符间的超时。
WriteTotalTimeoutConstant:一次写入串口数据的固定超时。所以在一次写入串口的操作中,其超时为WriteTotalTimeoutMultiplier乘以写入的字节数再加上 WriteTotalTimeoutConstant。
总超时的计算公式是:
总超时=时间系数×要求读/写的字符数 + 时间 常量
例如,如果要读入10个字符,那么读操作的总超时的计算公式为:
读总超时=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant
在用重叠方式读写 串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。

还有一个需要说明的是DCB结构体,DCB(Device Control Block)结构定义了串口通信设备的控制设置。
typedef struct _DCB { 
  DWORD DCBlength;            //DCB结构大小,即sizeof(DCB),在调用SetCommState来更新DCB前必须作设置 
  DWORD BaudRate;             //指定当前采用的波特率,应与所连接的通讯设备相匹配 
  DWORD fBinary: 1;           //指定是否允许二进制模式。Win32 API不支持非二进制模式传输,应设置为true 
  DWORD fParity: 1;           //指定奇偶校验是否允许,在为true时具体采用何种校验看Parity 设置 
  DWORD fOutxCtsFlow:1;       //是否监控CTS(clear-to-send)信号来做输出流控。
			      //当设置为true时: 若CTS为低电平,则数据发送将被挂起,直至CTS变为高。 
			      //CTS的信号一般由DCE(通常是一个Modem)控制,DTE(通常是计算机)发送数据时监测CTS信号。 
				//也就是说DCE通过把CTS置高来表明自己可以接收数据了 
  DWORD fDtrControl:2; 
  DWORD fDsrSensitivity:1;      // 通讯设备是否对DSR信号敏感。若设置为TRUE,则当DSR为低时将会忽略所有接收的字节
  DWORD fTXContinueOnXoff:1;    //当输入缓冲区满且驱动程序已发出XOFF字符时,是否停止发送。 当为TRUE时,XOFF被发送后发送仍然会继续;为FALSE时,则                                //发送会停止, 直至输入缓冲区有XonLim字节的空余空间、驱动程序已发送XON字符之后发送继续
  DWORD fOutX: 1;               //XON/XOFF 流量控制在发送时是否可用。 如果为TRUE, 当 XOFF 值被收到的时候,发送停止;当 XON 值被收到的时候,发                                 //送继续 
  DWORD fInX: 1;                //XON/XOFF 流量控制在接收时是否可用。 如果为TRUE, 当 输入缓冲区已接收满XoffLim 字节时,发送XOFF字符; 当输入缓                                //冲区已经有XonLim 字节的空余容量时,发送XON字符 
  DWORD fErrorChar: 1;           //该值为TRUE,则用ErrorChar指定的字符代替奇偶校验错误的接收字符
  DWORD fNull: 1;                //为TRUE时,接收时自动去掉空(0值)字节 
  DWORD fRtsControl:2;           //设置RTS (request-to-send)流控,若为0则缺省取 RTS_CONTROL_HANDSHAKE。可取值及其意义: 
			         //            取值                         意义 
			         //      RTS_CONTROL_DISABLE      打开设备时置RTS信号为低电平,应用程序可通过调用 EscapeCommFunction函数/                                                                 //来改变RTS线电平状态 
				 //      RTS_CONTROL_ENABLE       打开设备时置RTS信号为高电平,应用程序可通过调用 EscapeCommFunction函数/                                                                 //来改变RTS线电平状态 
				 //      RTS_CONTROL_HANDSHAKE    允许RTS信号握手,此时应用程序不能调用EscapeCommFunction函数。 当输入缓/                                                                 //冲区已经有足够空间接收数据时,驱动程序置RTS为高以允许 DCE来发送;反之置R                                                                 // TS为低以阻止DCE发送数据。 
				 //      RTS_CONTROL_TOGGLE       有字节要发送时RTS变高,当所有缓冲字节已被发送完毕后,RTS变低。
  DWORD fAbortOnError:1;         //读写操作发生错误时是否取消操作。若设置为true,则当发生读写错误时,将取消所有读写操作 (错误状态置为ERROR_IO_A                                  //BORTED),直到调用ClearCommError函数后才能重新进行通讯操作
  DWORD fDummy2:17;               //保留,未启用 
  WORD wReserved;                //未启用,必须设置为0 
  WORD XonLim;                   //在XON字符发送前接收缓冲区内可允许的最小字节数 
  WORD XoffLim;                  //在XOFF字符发送前接收缓冲区内可允许的最大字节数 
  BYTE ByteSize; 
  BYTE Parity;                  // 指定端口数据传输的校验方法。以下是可取值及其意义: 
                                //	 取值        意义 
                                //EVENPARITY    偶校验 
                                //MARKPARITY    标记校验,所发信息帧第9位恒为1 
                                //NOPARITY      无校验 
                                //ODDPARITY     奇校验 
  DWORD fOutxDsrFlow:1;         //是否监控DSR (data-set-ready) 信号来做输出流控。当设置为true时:若DSR为低电平,则数据发送将被挂起,直至DSR变                                //为高。DSR的信号一般由DCE来控制 fDtrControl DTR (data-terminal-ready)流控,可取值如下: 
				//              取值                    意义 
				//        DTR_CONTROL_DISABLE     打开设备时置DTR信号为低电平,应用程序可通过调用 
				//        EscapeCommFunction      函数来改变DTR线电平状态 
				//        DTR_CONTROL_ENABLE      打开设备时置DTR信号为高电平,应用程序可通过调用 
				//        EscapeCommFunction      函数来改变DTR线电平状态 
				//        DTR_CONTROL_HANDSHAKE   允许DTR信号握手,此时应用程序不能调用EscapeCommFunction函数 
  BYTE StopBits;                //指定端口当前使用的停止位数,可取值: 
				//取值         意义 
				//ONESTOPBIT    1停止位 
				//ONE5STOPBITS  1.5停止位 
				//TWOSTOPBITS   2停止位
  char XonChar;                 //指定XON字符 
  char XoffChar;                //指定XOFF字符
  char ErrorChar;              //指定ErrorChar字符(代替接收到的奇偶校验发生错误时的字节) 
  char EofChar;                //指定用于标示数据结束的字符 
  char EvtChar;               // 当接收到此字符时,会产生一个EV_RXFLAG事件,如果用SetCommMask函数中指定了EV_RXFLAG ,则可用WaitCommEvent 来监                              //测该事件 
  WORD wReserved1;            //保留,未启用 
} DCB; 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值