windows下串口编程还是linux有区别的,将最近调试的demo和查到的相关资料做了总结
1、打开串口
打开串口使用CreateFile()函数。以打开COM1为例:
HANDLE hComm; hComm = CreateFile( TEXT("COM6"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
其中:
- “COM1”,待打开串口的串口名。
- GENERIC_READ | GENERIC_WRITE,串口读写权限。
- 0,固定值。
- NULL,指向SECURITY_ATTRIBUTES的指针。通常设置为NULL,此时CreateFile()函数返回的句柄不能被子进程继承。
- OPEN_EXISTING,固定值。
- FILE_ATTRIBUTE_NORMAL,文件属性。
- NULL,固定值。
- hComm,函数返回的句柄。如果打开串口成功,则在后续操作中使用该句柄访问串口。如果打开串口失败,函数返回句柄为INVALID_HANDLE_VALUE。
注意事项1
是CreateFile()、CreateFileA()和CreateFileW()的区别。在大部分说明如何使用Win32 API打开串口的文档中都介绍用CreateFile()函数打开串口,但某些文档中却使用CreateFileA()或CreateFileW()函数。实际上三个函数的功能是相同的,只是所采用的字符串编码格式不同。CreateFileA()函数名中的A代表ANSI,而CreateFileW()中的W代表UNICODE。所谓ANSI编码,是各国根据自己的语言定义的字符编码格式,其中0~0x7F与ASCII字符相同,其余则与具体语言相关。因此,中文ANSI编码(GB2312)和日文ANSI编码无法互通。UNICODE则是将所有语言的编码进行统一,用同一个编码空间覆盖所有语言文字。从下面的代码中可以清楚地看出三个函数的关系。 在前面CreateFile()示例中,TEXT宏的用途就是根据当前操作系统的编码格式对字符串"COM1"进行适当的格式转换。
HANDLE CreateFileA( __in LPCSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); HANDLE CreateFileW( __in LPCWSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); #ifdef UNICODE #define CreateFile CreateFileW #else #define CreateFile CreateFileA #endif
注意事项2
特别特别坑人!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
在Windows上,要打开COM10以后的串口(包括COM10),串口名称不再是“COM10”,而是“\.\COM10”,因此在open函数中,如果是COM10及以后的串口,串口名(假设当前要打开COM10)应该写“\\.\COM10”,其中“\\.\”为“\.\”的转义。(.\COM10)
注意事项3
在visual studio的项目中,编译报错,错误指向WzSerialPort.cpp的157行、211行的问题:
157| m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, L"WriteEvent");
2、关闭串口
关闭串口则使用CloseHandle()函数。示例如下:
CloseHandle(hComm);
3、配置串口工作参数
以设置串口为波特率115200,数据位8bit,停止位1bit为例:
DCB dcb; GetComm(hComm, &dcb); dcb.BaudRate = CBR_115200; dcb.ByteSize = 8; dcb.StopBits = ONESTOPBIT; SetCommState(hComm, &dcb);
4、写串口
以发送字符串"abcd"为例:
char buf[] = "abcd"; DWORD buf_len = 4; // 待写入串口的字节数 DWORD written_cnt; // 实际写入串口的字节数 WreteFile( hComm, (void *)buf, buf_len, &written_cnt, NULL );
5、读串口
以读取12个字符为例:
char buf[128]; DWORD toread_cnt = 12; // 要从串口读入的字节数 DWORD read_cnt; // 实际从串口读入的字节数 ReadFile( hComm, (void *)buf, toread_cnt, &read_cnt, NULL );
6、清除串口缓冲区
当串口接收到一个字节时,串口驱动程序将接收到的字节写入内存的某个位置(输入缓冲区)。当应用程序读取串口时,操作系统按照“先进先出”的原则从输入缓冲区取出数据交给应用程序。在某些应用场景下,应用程序需要舍弃输入缓冲区内当前数据。这可通过PurgeComm()函数实现。
PurgeComm( hComm, PURGE_RXCLEAR );
测试demo
Serial.h
#ifndef _WZSERIALPORT_H #define _WZSERIALPORT_H #include <iostream> using namespace std; /* 作者:xin.han 日期:2021-7-14 类名:WZSerialPort 用途:串口读写 示例: WZSerialPort w; if(w.open("COM1",115200,0,8,1)) { w.send("helloworld",10); char buf[1024]; w.receive(buf,1024); } */ class WZSerialPort { public: WZSerialPort(); ~WZSerialPort(); // 打开串口,成功返回true,失败返回false // portname(串口名): 在Windows下是"COM1""COM2"\\\\.\\COM10等,在Linux下是"/dev/ttyS1"等 // baudrate(波特率): 9600、19200、38400、43000、56000、57600、115200 // parity(校验位): 0为无校验,1为奇校验,2为偶校验,3为标记校验 // databit(数据位): 4-8,通常为8位 // stopbit(停止位): 1为1位停止位,2为2位停止位,3为1.5位停止位 // synchronizable(同步、异步): 0为异步,1为同步 bool open(const char* portname, int baudrate = 115200, char parity = 0, char databit = 8, char stopbit = 1, char synchronizeflag = 0); //关闭串口,参数待定 void close(); //发送数据或写数据,成功返回发送数据长度,失败返回0 int send(string dat); //接受数据或读数据,成功返回读取实际数据的长度,失败返回0 string receive(); private: int pHandle[16]; char synchronizeflag; }; #endif
Serial.cpp
#include "Serial.h" #include <stdio.h> #include <string.h> #include <WinSock2.h> #include <windows.h> #include<iostream> using namespace std; WZSerialPort::WZSerialPort() { } WZSerialPort::~WZSerialPort() { } bool WZSerialPort::open(const char* portname, int baudrate, char parity, char databit, char stopbit, char synchronizeflag) { this->synchronizeflag = synchronizeflag; HANDLE hCom = NULL; if (this->synchronizeflag) { //同步方式 hCom = CreateFileA(portname, //串口名 GENERIC_READ | GENERIC_WRITE, //支持读写 0, //独占方式,串口不支持共享 NULL,//安全属性指针,默认值为NULL OPEN_EXISTING, //打开现有的串口文件 0, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式 NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL } else { //异步方式 hCom = CreateFileA(portname, //串口名 GENERIC_READ | GENERIC_WRITE, //支持读写 0, //独占方式,串口不支持共享 NULL,//安全属性指针,默认值为NULL OPEN_EXISTING, //打开现有的串口文件 FILE_FLAG_OVERLAPPED, //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式 NULL);//用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL } if (hCom == (HANDLE)-1) { return false; } //配置缓冲区大小 if (!SetupComm(hCom, 1024, 1024)) { return false; } // 配置参数 DCB p; memset(&p, 0, sizeof(p)); p.DCBlength = sizeof(p); p.BaudRate = baudrate; // 波特率 p.ByteSize = databit; // 数据位 switch (parity) //校验位 { case 0: p.Parity = NOPARITY; //无校验 break; case 1: p.Parity = ODDPARITY; //奇校验 break; case 2: p.Parity = EVENPARITY; //偶校验 break; case 3: p.Parity = MARKPARITY; //标记校验 break; } switch (stopbit) //停止位 { case 1: p.StopBits = ONESTOPBIT; //1位停止位 break; case 2: p.StopBits = TWOSTOPBITS; //2位停止位 break; case 3: p.StopBits = ONE5STOPBITS; //1.5位停止位 break; } if (!SetCommState(hCom, &p)) { // 设置参数失败 return false; } //超时处理,单位:毫秒 //总超时=时间系数×读或写的字符数+时间常量 COMMTIMEOUTS TimeOuts; TimeOuts.ReadIntervalTimeout = 1000; //读间隔超时 TimeOuts.ReadTotalTimeoutMultiplier = 500; //读时间系数 TimeOuts.ReadTotalTimeoutConstant = 5000; //读时间常量 TimeOuts.WriteTotalTimeoutMultiplier = 500; // 写时间系数 TimeOuts.WriteTotalTimeoutConstant = 2000; //写时间常量 SetCommTimeouts(hCom, &TimeOuts); PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);//清空串口缓冲区 memcpy(pHandle, &hCom, sizeof(hCom));// 保存句柄 return true; } void WZSerialPort::close() { HANDLE hCom = *(HANDLE*)pHandle; CloseHandle(hCom); } int WZSerialPort::send(string dat) { HANDLE hCom = *(HANDLE*)pHandle; if (this->synchronizeflag) { // 同步方式 DWORD dwBytesWrite = dat.length(); //成功写入的数据字节数 BOOL bWriteStat = WriteFile(hCom, //串口句柄 (char*)dat.c_str(), //数据首地址 dwBytesWrite, //要发送的数据字节数 &dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数 NULL); //NULL为同步发送,OVERLAPPED*为异步发送 if (!bWriteStat) { return 0; } return dwBytesWrite; } else { //异步方式 DWORD dwBytesWrite = dat.length(); //成功写入的数据字节数 DWORD dwErrorFlags; //错误标志 COMSTAT comStat; //通讯状态 OVERLAPPED m_osWrite; //异步输入输出结构体 //创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做 memset(&m_osWrite, 0, sizeof(m_osWrite)); m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, L"WriteEvent"); ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态 BOOL bWriteStat = WriteFile(hCom, //串口句柄 (char*)dat.c_str(), //数据首地址 dwBytesWrite, //要发送的数据字节数 &dwBytesWrite, //DWORD*,用来接收返回成功发送的数据字节数 &m_osWrite); //NULL为同步发送,OVERLAPPED*为异步发送 if (!bWriteStat) { if (GetLastError() == ERROR_IO_PENDING) //如果串口正在写入 { WaitForSingleObject(m_osWrite.hEvent, 1000); //等待写入事件1秒钟 } else { ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误 CloseHandle(m_osWrite.hEvent); //关闭并释放hEvent内存 return 0; } } return dwBytesWrite; } } string WZSerialPort::receive() { HANDLE hCom = *(HANDLE*)pHandle; string rec_str = ""; char buf[1024]; if (this->synchronizeflag) { //同步方式 DWORD wCount = 1024; //成功读取的数据字节数 BOOL bReadStat = ReadFile(hCom, //串口句柄 buf, //数据首地址 wCount, //要读取的数据最大字节数 &wCount, //DWORD*,用来接收返回成功读取的数据字节数 NULL); //NULL为同步发送,OVERLAPPED*为异步发送 for (int i = 0; i < 1024; i++) { if (buf[i] != -52) rec_str += buf[i]; else break; } return rec_str; } else { //异步方式 DWORD wCount = 1024; //成功读取的数据字节数 DWORD dwErrorFlags; //错误标志 COMSTAT comStat; //通讯状态 OVERLAPPED m_osRead; //异步输入输出结构体 //创建一个用于OVERLAPPED的事件处理,不会真正用到,但系统要求这么做 memset(&m_osRead, 0, sizeof(m_osRead)); m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, L"ReadEvent"); ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误,获得设备当前状态 if (!comStat.cbInQue)return 0; //如果输入缓冲区字节数为0,则返回false //std::cout << comStat.cbInQue << std::endl; BOOL bReadStat = ReadFile(hCom, //串口句柄 buf, //数据首地址 wCount, //要读取的数据最大字节数 &wCount, //DWORD*,用来接收返回成功读取的数据字节数 &m_osRead); //NULL为同步发送,OVERLAPPED*为异步发送 if (!bReadStat) { if (GetLastError() == ERROR_IO_PENDING) //如果串口正在读取中 { //GetOverlappedResult函数的最后一个参数设为TRUE //函数会一直等待,直到读操作完成或由于错误而返回 GetOverlappedResult(hCom, &m_osRead, &wCount, TRUE); } else { ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通讯错误 CloseHandle(m_osRead.hEvent); //关闭并释放hEvent的内存 return 0; } } for (int i = 0; i < 1024; i++) { if (buf[i] != -52) rec_str += buf[i]; else break; } return rec_str; } }
Demo.cpp
#include "Serial.h" #include<iostream> using namespace std; int main() { WZSerialPort w; if (w.open("\\\\.\\COM12")) { string str = "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld"; w.send(str); cout << w.receive().c_str(); w.close(); } while (true) { } return 0; }