一、前言
最近在学习windows编程中的串口编程,使用的软件平台是vs2019,串口通信方式采用的是重叠方式,
即读写数据操作在单独的线程中进行,发出读写要求的主线程可以继续运行,当读写数据成功后,写
数据线程可以通过某种方式通知主线程,实现两个线程协调工作。
二、相关函数介绍
1、线程创建函数CreateThread( )
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程安全属性,NULL表示该线程不能被继承
DWORD dwStackSize,//初始化栈大小,0表示按默认值或调用线程配置
LPTHREAD_START_ROUTINE lpStartAddress,//执行线程函数名称
LPVOID lpParameter,//向新线程传递的参数
DWORD dwCreationFlags,//创建标志,0表示创建后立即执行
LPDWORD lpThreadId //指向接收线程的ID
);
2、事件创建函数CreateEvent( )
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,//事件安全属性,NULL表示该事件句柄不能用于继承
BOOL bManualReset,//TRUE表示该事件需要人工复位
BOOL bInitialState,//事件对象的初始状态,TRUE表示有信号,反之无信号
LPCTSTR pName//指向事件对象的名称,NULL表示创建一个无名事件对象
);
3、WaitForSingleObject( )–获取后台读写命令返回结果函数
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
4、GetOverlappedResult( )–同上,但可以返回读操作实际读取的字节数量
BOOL GetOverlappedResult(
HANDLE hFile, //指向串口句柄
LPOVERLAPPED lpOverlapped, //执行读函数中使用的OVERLAPPED结构变量
LPDWORD lpNumberOfBytesTransferred, //存放实际读出字节数量变量的地址
//设为TRUE,表示直到读操作完成该函数才返回,若为FALSE,表示若读操作未完成是函数将
//返回FALSE,此时说明使用GetLastError()函数可以获取错误信息
BOOL bWait);
三、代码实现
1、定义相关全局变量
HANDLE hComm; //串口句柄
HANDLE hCom = INVALID_HANDLE_VALUE;
DWORD ThreadProcWrite(LPVOID pParam);//写线程函数
DWORD ThreadProcRead(LPVOID pParam);//读线程函数
OVERLAPPED Wol = { 0 };//写操作OVERLAPPED结构变量
OVERLAPPED Rol = { 0 };//读操作OVERLAPPED结构变量
HANDLE hThreadWrite;//写线程句柄
HANDLE hThreadRead;//读线程句柄
DWORD dwThreadID;
DWORD dwParam;
2、创建和配置串口
int hdinit() {
//HWND hWnd;
hCom = CreateFile(
"//.//COM6", //将要打开的串口逻辑名
GENERIC_READ | GENERIC_WRITE, //允许读和写
0, //指定共享属性,由于串口不能共享,该参数必须置为0,独占方式
NULL, //引用安全性属性结构,缺省值为NULL
OPEN_EXISTING, //创建标志,对串口操作该参数必须置为OPEN_EXISTING
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //属性描述,此处重叠方式
NULL); //对串口而言该参数必须置为NULL);
if (hCom == INVALID_HANDLE_VALUE)
{
DWORD error = GetLastError();//读取错误信息
printf("打开串口失败error:%d\n", error);
return 0;
}
else
{
printf("打开串口成功!\n");
DCB dcb;
GetCommState(hCom, &dcb); //首先获取串口的配置
dcb.BaudRate = 9600; //配置波特率
dcb.fParity = 0; //校验不是能
dcb.ByteSize = 8; //数据位为8
dcb.Parity = 0; //无奇偶校验
dcb.StopBits = 1; //停止位为1
SetCommState(hCom, &dcb); //设置上述配置信息
SetupComm(hCom, 4200, 4200);//配置缓冲区,分别表示设置输入输出缓冲区的大小
COMMTIMEOUTS timeout;
GetCommTimeouts(hCom, &timeout);
timeout.ReadTotalTimeoutConstant = 1000;//这里只设置了读时间常量
SetCommTimeouts(hCom, &timeout);
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);
printf("串口配置成功!\n");
return 1;
}
//hWnd = GetSafeHwnd();
}
注意:这里串口创建的是重叠方式
3、创建读操作线程
int threadReadinit(void) {
hThreadRead = CreateThread(NULL,
0,//初始化栈大小,0表示按默认值或调用线程配置
(LPTHREAD_START_ROUTINE)ThreadProcRead,//读线程函数名称
&dwParam,//向新线程传递的参数
0, //创建线程后,立即执行该线程
&dwThreadID);//指向接收线程的ID
if (hThreadRead == NULL) {
AfxMessageBox(_T("读线程创建失败!"));
return 0;
}
else {
printf("创建线程函数中:读线程创建成功!\n");
return 1;
}
}
4、创建写操作线程
int threadWriteinit(void) {
hThreadWrite = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadProcWrite,//读线程函数名称
&dwParam,
0, //创建线程后,立即执行该线程
&dwThreadID);
if (hThreadWrite == NULL) {
AfxMessageBox(_T("读线程创建失败!"));
return 0;
}
else {
AfxMessageBox(_T("写线程创建成功!"));
return 1;
}
}
5、读线程函数
DWORD ThreadProcRead(LPVOID pParam)//入口参数为任意指针
{
BYTE myByte{ 20 };
CString myStr;
DWORD dwRes;
DWORD dwRead;
BOOL fRes;
char myChar[20];
Rol.hEvent = CreateEvent(NULL,//创建Rol的hEvent成员为无信号状态
TRUE,
FALSE,
NULL);
if (Rol.hEvent == NULL) {
AfxMessageBox(_T("hEvent空"));
return -1;
}
if (ReadFile(hCom,//串口句柄
&myChar,//存放读取数据
10,//要读取的字节数
NULL,
&Rol)) //指向创建hCom时的Rol指针
{//如果ReadFile返回TRUE,表明操作已完成,可以处理读取的数据
printf("ReadFile之后的成功读出!\n");
//在这里加入存放已读数的代码,已读数据存放在myByte数组中
}
else//如果ReadFile返回FALSE,表明读操作为完成,此时使用WaitForSingleObject函数等待读操作的结果,根据不同的返回结果采取不同的处理
{
dwRes = WaitForSingleObject(Rol.hEvent,
5000);//设置5秒的超时---这里是等待后台读操作的命令的结果
switch (dwRes)
{
case WAIT_OBJECT_0: //返回成功,表示等待的对象已经被置为有信号状态;
//---表示读操作完成,读操作完成需要验证GetOverlappedResult,如果返回了实际读取的字节数表示完成
if (!GetOverlappedResult(hCom,
&Rol,
&dwRead,//实际读取的字节数
TRUE))//TRUE表示直到操作完成该函数才返回
{
//操作失败的代码
DWORD error = GetLastError();//读取错误信息
printf("操作失败:%d\n", error);
}
else {
printf("%d\n", myChar[0]);
printf("WAIT_OBJECT_0下的成功!\n");
//操作成功完成,数据读出,并存放到myByte数组中
//在这里加入处理数据的代码
}
break;
case WAIT_TIMEOUT:
//读操作失败,原因是读超时
break;
default:
//这里可以加入默认情况下的处理代码
break;
}
}
//CloseHandle(Rol.hEvent);//这里的关闭线程会报异常,待查!
//FindClose(Rol.hEvent);// file search handle
return 0;
}
6、写线程函数
DWORD ThreadProcWrite(LPVOID pParam) {
BYTE myByte[9];
CString myStr;
int i;
DWORD dwRes;
DWORD dwWrite;
BOOL fRes;
char myChar[10];
for (i = 0; i <= 9; i++) {
myByte[i] = i; //等待发送数据写入myBte数组中
}
Wol.Internal = 0; //设置OVERLAPPED结构Wol
Wol.InternalHigh = 0;
Wol.Offset = 0;
Wol.OffsetHigh = 0;
Wol.hEvent = CreateEvent(NULL,//创建Wol的hEvent成员为无信号状态
TRUE,
FALSE,
NULL);
if (Wol.hEvent == NULL) {
AfxMessageBox(_T("hEvent为空!"));
return -1;
}
if (WriteFile(hCom,//串口句柄
&myByte,//存放待发送的数据
3,//欲发送的字节数
NULL,
&Wol))//指向创建hCom时的Wol指针
{
AfxMessageBox(_T("成功发送!"));
//这里加入成功发送的代码
}
else {
dwRes = WaitForSingleObject(Wol.hEvent, 500);
switch (dwRes) {
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hCom,
&Wol,
&dwWrite,
TRUE)) {
//操作失败,可以使用GetLastError()来获取错误信息
}
else {
//发送数据成功,这里加入发送成功的处理代码
}
break;
case WAIT_TIMEOUT:
//发送失败,失败原因是发送超时
break;
default:
break;
}
}
CloseHandle(Wol.hEvent);
return 0;
}
6、main函数
int main()
{
if (hdinit()) {
LPVOID pParam;
unsigned char* ret = (unsigned char*)malloc(1000);
if (threadReadinit()) {
printf("main读线程创建成功\n");
}
ThreadProcRead(ret);
}
}