因为我自己正好也在为项目写一个控制软件,所以自己做到哪就写到哪吧。项目中软件与下位机之间通过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;