串口编程:
无论那种操作方式,一般都通过四个步骤来完成:
(1) 打开串口
(2) 配置串口
(3) 读写串口
(4) 关闭串口
无论那种操作方式,一般都通过四个步骤来完成:
(1) 打开串口
(2) 配置串口
(3) 读写串口
(4) 关闭串口
设计步骤:
1.初始化/打开串口
打开串口的第一步是初始化或设置串口配置,目的是创建串口代理,整篇文章我们都将用文件句柄作为串口代理。
创建端口句柄
串口句柄是可以被用来存取的串口对象句柄,创建串口句柄的函数是CreateFile,如下代码所示:
m_hComDevice = CreateFile ((LPCTSTR)pcPortId, // 串口号" .//COM%d "
GENERIC_READ | GENERIC_WRITE, // 打开为读写
0, // 独占方式
NULL, // 引用安全性属性结构,缺省值为NULL
OPEN_EXISTING, // 如果设备不存在则函数失败
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 异步传输(采用异步读的时侯,ReadFile()立即返回)
NULL); // 默认
2.配置串口
2.1 设置DCB参数
DCB结构包含了串口的各项参数设置,下面仅介绍几个该结构常用的变量
2.1.1获取配置
在控制设备中获取当前配置,配置中包含了用于设置串口通讯设备的参数。
可以用 GetCommState函数得到当前设备配置并用指定通讯设备的当前配置填充设备控制块(DCB结构),如下代码所示:
// Set up the serial communications device
DCB dcb;
dcb.DCBlength = sizeof(DCB);
GetCommState (m_hComDevice, &dcb);
2.1.2.修改配置
dcb.BaudRate = nBaudRate; // 波特率 (默认 = 9600)
dcb.ByteSize = (UCHAR)nDataBits; // 数据位 4-8 (默认 = 8)
dcb.Parity = (UCHAR)nParity; // 奇偶校验位 0-4= 无, 奇, 偶, 标志, 空格 (默认 = 0)
dcb.StopBits = (UCHAR)nStopBits; // 停止位 0,1,2 = 1, 1.5, 2 (默认 = 0)
对于典型的通讯,建议程序员使用默认值。图3所示,Watch对话框显示了典型通讯使用的默认值。
// 波特率 9600
// 数据位 8
// 奇偶校验位 0
// 停止位 0
2.1.3.保存配置
调用SetCommState API函数保存配置。SetCommState函数设备控制块(DCB结构)配置通讯设备.
该函数重新初始化所有硬件控制设定,但不清空输入输出队列.
SetCommState (m_hComDevice, &dcb)
2.2.设置通讯超时
开启端口的最后一步是通过使用COMMTIMEOUTS数据结构和调用SetCommTimeouts函数进行通讯超时设置。如下代码所示:
// set up time-out parameters for the communications device
COMMTIMEOUTS CommTimeOuts;
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; // 指定通讯线上两个字符到达的最大时延,以毫秒为单位。如果收到的两 // 个字符之间的间隔超过该值,ReadFile操作完毕并返回所有缓冲数据。
// 如果ReadIntervalTimeout为0,则该值不起作用。
// 如果值为MAXDWORD,并且ReadTotalTimeoutMultiplier和
// ReadTotalTimeoutConstant都为0则指定读操作携带已经收到的字符立即
// 返回,即使没有收到任何字符。
CommTimeOuts.ReadTotalTimeoutMultiplier = 0; // 指定以毫秒为单位的累积值。用于计算读操作时的超时总数。
// 对于每次读操作,该值与所要读的字节数相乘。
CommTimeOuts.ReadTotalTimeoutConstant = 0; // 指定以毫秒为单位的常数。用于计算读操作时的超时总数。
// 对于每次读操作,ReadTotalTimeoutMultiplier与所要读的
// 字节数相乘后与该值相加。
CommTimeOuts.WriteTotalTimeoutMultiplier = 0; // 用于计算读操作时的超时总数。
CommTimeOuts.WriteTotalTimeoutConstant = 5000; // 用于计算读操作时的超时总数。
// 提示:用户设置通讯超时后,如没有出错,串口已经被打开。
SetCommTimeouts (m_hComDevice, &CommTimeOuts);
2.3 设置I/O缓冲区的大小和超时
除了在BCD中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。
SetupComm (m_hComDevice, // 通信设备的句柄
10000, // 输入缓冲区的大小(字节数)
10000); // 输出缓冲区的大小(字节数)
3. 收发数据
OVERLAPPED结构
OVERLAPPED结构包含了重叠I/O的一些信息,定义如下:
typedef struct _OVERLAPPED { // o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状
态,该结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该
事件得知是否读写完毕。
当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态.
3.1 发送数据
串口数据发送多作为写文件处理的,程序员可以应用文件操作函数发送数据到串口。采用WriteFile函数发送数据到串口。
提示:如果函数成功,返回非0值
OVERLAPPED m_OverlappedWrite;
memset (&m_OverlappedWrite, 0, sizeof(OVERLAPPED));
bWriteStatus = WriteFile(m_hComDevice, // 文件句柄
outputData, // 数据缓冲区指针
sizeBuffer, // 字节数
&length,
&m_OverlappedWrite); // 重叠操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL
1.初始化/打开串口
打开串口的第一步是初始化或设置串口配置,目的是创建串口代理,整篇文章我们都将用文件句柄作为串口代理。
创建端口句柄
串口句柄是可以被用来存取的串口对象句柄,创建串口句柄的函数是CreateFile,如下代码所示:
m_hComDevice = CreateFile ((LPCTSTR)pcPortId, // 串口号" .//COM%d "
GENERIC_READ | GENERIC_WRITE, // 打开为读写
0, // 独占方式
NULL, // 引用安全性属性结构,缺省值为NULL
OPEN_EXISTING, // 如果设备不存在则函数失败
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 异步传输(采用异步读的时侯,ReadFile()立即返回)
NULL); // 默认
2.配置串口
2.1 设置DCB参数
DCB结构包含了串口的各项参数设置,下面仅介绍几个该结构常用的变量
2.1.1获取配置
在控制设备中获取当前配置,配置中包含了用于设置串口通讯设备的参数。
可以用 GetCommState函数得到当前设备配置并用指定通讯设备的当前配置填充设备控制块(DCB结构),如下代码所示:
// Set up the serial communications device
DCB dcb;
dcb.DCBlength = sizeof(DCB);
GetCommState (m_hComDevice, &dcb);
2.1.2.修改配置
dcb.BaudRate = nBaudRate; // 波特率 (默认 = 9600)
dcb.ByteSize = (UCHAR)nDataBits; // 数据位 4-8 (默认 = 8)
dcb.Parity = (UCHAR)nParity; // 奇偶校验位 0-4= 无, 奇, 偶, 标志, 空格 (默认 = 0)
dcb.StopBits = (UCHAR)nStopBits; // 停止位 0,1,2 = 1, 1.5, 2 (默认 = 0)
对于典型的通讯,建议程序员使用默认值。图3所示,Watch对话框显示了典型通讯使用的默认值。
// 波特率 9600
// 数据位 8
// 奇偶校验位 0
// 停止位 0
2.1.3.保存配置
调用SetCommState API函数保存配置。SetCommState函数设备控制块(DCB结构)配置通讯设备.
该函数重新初始化所有硬件控制设定,但不清空输入输出队列.
SetCommState (m_hComDevice, &dcb)
2.2.设置通讯超时
开启端口的最后一步是通过使用COMMTIMEOUTS数据结构和调用SetCommTimeouts函数进行通讯超时设置。如下代码所示:
// set up time-out parameters for the communications device
COMMTIMEOUTS CommTimeOuts;
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; // 指定通讯线上两个字符到达的最大时延,以毫秒为单位。如果收到的两 // 个字符之间的间隔超过该值,ReadFile操作完毕并返回所有缓冲数据。
// 如果ReadIntervalTimeout为0,则该值不起作用。
// 如果值为MAXDWORD,并且ReadTotalTimeoutMultiplier和
// ReadTotalTimeoutConstant都为0则指定读操作携带已经收到的字符立即
// 返回,即使没有收到任何字符。
CommTimeOuts.ReadTotalTimeoutMultiplier = 0; // 指定以毫秒为单位的累积值。用于计算读操作时的超时总数。
// 对于每次读操作,该值与所要读的字节数相乘。
CommTimeOuts.ReadTotalTimeoutConstant = 0; // 指定以毫秒为单位的常数。用于计算读操作时的超时总数。
// 对于每次读操作,ReadTotalTimeoutMultiplier与所要读的
// 字节数相乘后与该值相加。
CommTimeOuts.WriteTotalTimeoutMultiplier = 0; // 用于计算读操作时的超时总数。
CommTimeOuts.WriteTotalTimeoutConstant = 5000; // 用于计算读操作时的超时总数。
// 提示:用户设置通讯超时后,如没有出错,串口已经被打开。
SetCommTimeouts (m_hComDevice, &CommTimeOuts);
2.3 设置I/O缓冲区的大小和超时
除了在BCD中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。
SetupComm (m_hComDevice, // 通信设备的句柄
10000, // 输入缓冲区的大小(字节数)
10000); // 输出缓冲区的大小(字节数)
3. 收发数据
OVERLAPPED结构
OVERLAPPED结构包含了重叠I/O的一些信息,定义如下:
typedef struct _OVERLAPPED { // o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状
态,该结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该
事件得知是否读写完毕。
当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态.
3.1 发送数据
串口数据发送多作为写文件处理的,程序员可以应用文件操作函数发送数据到串口。采用WriteFile函数发送数据到串口。
提示:如果函数成功,返回非0值
OVERLAPPED m_OverlappedWrite;
memset (&m_OverlappedWrite, 0, sizeof(OVERLAPPED));
bWriteStatus = WriteFile(m_hComDevice, // 文件句柄
outputData, // 数据缓冲区指针
sizeBuffer, // 字节数
&length,
&m_OverlappedWrite); // 重叠操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL
// ERROR_IO_PENDING,这说明重叠操作还未完成
if ((!bWriteStat) && (GetLastError() == ERROR_IO_PENDING))
{
// // 使用WaitForSingleObject函数等待,直到写操作完成或延时已达到1秒钟
if (WaitForSingleObject (m_OverlappedWrite.hEvent, 1000))
{
dwBytesWritten = 0;
}
else
{
GetOverlappedResult (m_hComDevice,
&m_OverlappedWrite,
&dwBytesWritten, FALSE);
if ((!bWriteStat) && (GetLastError() == ERROR_IO_PENDING))
{
// // 使用WaitForSingleObject函数等待,直到写操作完成或延时已达到1秒钟
if (WaitForSingleObject (m_OverlappedWrite.hEvent, 1000))
{
dwBytesWritten = 0;
}
else
{
GetOverlappedResult (m_hComDevice,
&m_OverlappedWrite,
&dwBytesWritten, FALSE);
m_OverlappedWrite.Offset += dwBytesWritten;
}
}
}
}
3.2 接收数据
串口数据接收多作为读文件处理。程序员可以应用文件操作函数从串口接收数据。用ReadFile函数接收串口的数据。
提示:如果函数成功,返回非0值
在使用ReadFile 函数进行读操作前,应先使用ClearCommError函数清除错误
LONG CSerialPort::ReadDataWaiting ()
{
DWORD dwBytesInQueue = 0;
串口数据接收多作为读文件处理。程序员可以应用文件操作函数从串口接收数据。用ReadFile函数接收串口的数据。
提示:如果函数成功,返回非0值
在使用ReadFile 函数进行读操作前,应先使用ClearCommError函数清除错误
LONG CSerialPort::ReadDataWaiting ()
{
DWORD dwBytesInQueue = 0;
// If the port is open and the device is not NULL
if ((m_bOpened) && (m_hComDevice != NULL))
{
DWORD dwErrorFlags = 0;
COMSTAT ComStat;
if ((m_bOpened) && (m_hComDevice != NULL))
{
DWORD dwErrorFlags = 0;
COMSTAT ComStat;
ClearCommError (m_hComDevice,
&dwErrorFlags, // 指向接收错误码的变量
&ComStat ); // 指向通讯状态缓冲区
&dwErrorFlags, // 指向接收错误码的变量
&ComStat ); // 指向通讯状态缓冲区
dwBytesInQueue = (DWORD)ComStat.cbInQue; // COMSTAT结构包含串口的信息,该成员变量的值代表输入缓冲区的字节数
}
}
return (dwBytesInQueue);
}
}
OVERLAPPED m_OverlappedRead;
memset (&m_OverlappedRead, 0, sizeof(OVERLAPPED));
LONG CSerialPort::ReadData (CHAR *pcData, LONG dwReadLimit)
{
LONG dwBytesRead = 0; // Number of bytes read
memset (&m_OverlappedRead, 0, sizeof(OVERLAPPED));
LONG CSerialPort::ReadData (CHAR *pcData, LONG dwReadLimit)
{
LONG dwBytesRead = 0; // Number of bytes read
LONG dwRxBytes = ReadDataWaiting ();
if (dwRxBytes > 0)
{
if( dwReadLimit < dwRxBytes )
{
dwRxBytes = dwReadLimit;
}
{
if( dwReadLimit < dwRxBytes )
{
dwRxBytes = dwReadLimit;
}
BOOL bReadStatus = ReadFile (m_hComDevice, // 句柄
(void *)(LPCTSTR)pcData, // 数据缓冲区指针
(DWORD)dwRxBytes, // 字节数
(DWORD*)&dwBytesRead, // 指向已经读入的字节数
&m_OverlappedRead); // 同步操作时,该参数为NULL
(void *)(LPCTSTR)pcData, // 数据缓冲区指针
(DWORD)dwRxBytes, // 字节数
(DWORD*)&dwBytesRead, // 指向已经读入的字节数
&m_OverlappedRead); // 同步操作时,该参数为NULL
if (bReadStatus == FALSE)
{
DWORD dwLastError = 0;
dwLastError = GetLastError ();
if (dwLastError == ERROR_IO_PENDING)
{
// 使用WaitForSingleObject函数等待,直到读操作完成或延时已达到2秒钟
WaitForSingleObject (m_OverlappedRead.hEvent, 2000);
}
else
{
dwBytesRead = 0;
}
}
}
{
DWORD dwLastError = 0;
dwLastError = GetLastError ();
if (dwLastError == ERROR_IO_PENDING)
{
// 使用WaitForSingleObject函数等待,直到读操作完成或延时已达到2秒钟
WaitForSingleObject (m_OverlappedRead.hEvent, 2000);
}
else
{
dwBytesRead = 0;
}
}
}
return (dwBytesRead);
}
}
4.关闭串口
可以调用CloseHandle API函数关闭串口
if (m_hComDevice != NULL)
{
// Close the handle to the Comm device
CloseHandle (m_hComDevice);
m_hComDevice = NULL;
}
可以调用CloseHandle API函数关闭串口
if (m_hComDevice != NULL)
{
// Close the handle to the Comm device
CloseHandle (m_hComDevice);
m_hComDevice = NULL;
}