Windows操作系统:串口篇

简介: windows下串口编程还是linux有区别的

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;
}



相关文章
|
1天前
|
数据安全/隐私保护 虚拟化 Windows
如何在 VM 虚拟机中安装 Windows Server 2012 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Windows Server 2012 操作系统保姆级教程(附链接)
|
1天前
|
数据安全/隐私保护 虚拟化 Windows
如何在 VM 虚拟机中安装 Windows Server 2003 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Windows Server 2003 操作系统保姆级教程(附链接)
|
1天前
|
数据安全/隐私保护 虚拟化 Windows
如何在 VM 虚拟机中安装 Windows 7 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Windows 7 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Windows 7 操作系统保姆级教程(附链接)
|
1天前
|
数据安全/隐私保护 虚拟化 Windows
如何在 VM 虚拟机中安装 Windows XP 操作系统保姆级教程(附链接)
如何在 VM 虚拟机中安装 Windows XP 操作系统保姆级教程(附链接)
|
1天前
|
安全 Windows
关于 Windows 操作系统的 Recovery 目录
关于 Windows 操作系统的 Recovery 目录
6 0
|
1天前
|
存储 安全 网络安全
Windows操作系统中:共享文件夹以及防火墙介绍
Windows操作系统中:共享文件夹以及防火墙介绍
|
1天前
|
前端开发 Java 应用服务中间件
在虚拟机的Windows操作系统中:通过Jar方式若依项目,以及在外部的访问!
在虚拟机的Windows操作系统中:通过Jar方式若依项目,以及在外部的访问!
|
1天前
|
网络协议 安全 Linux
Windows电脑如何使用固定TCP公网地址远程连接内网Deepin深度操作系统
Windows电脑如何使用固定TCP公网地址远程连接内网Deepin深度操作系统
17 3
|
1天前
|
监控 API 数据库
Python语言Windows操作系统
Python语言Windows操作系统