C++ 串口读写操作 (Windows)
计算学徒
编辑于 2024年12月25日 21:04
收录于文集
共106篇

文章 C++读取串口超时功能​ 介绍了使用 Boost::Asio 中提供的 serial_port 串口类进行串口 IO 操作. Boost::Asio 对串口等 IO 操作进行了很好地封装, 便于跨平台使用. 

如果仅限于 Windows 平台编程, 则可以更方便地使用 Windows API 进行串口读写操作. 在 Windows 平台中, 读写串口和读写一个普通的文件没有很大差异, 具体看如下使用 Windows  API 封装的串口类:

代码块
C++
自动换行
复制代码
#include <Windows.h>
#include <spdlog/spdlog.h>

class SerialPort {
 public:
  SerialPort(const std::string& portname, const DWORD baudRate,
             const BYTE stopBits, const BYTE byteSize, const BYTE parity,
             const DWORD timeout_ms)
      : m_portname(portname),
        m_baudRate(baudRate),
        m_byteSize(byteSize),
        m_stopBits(stopBits),
        m_parity(parity),
        m_timeout_ms(timeout_ms) {
    /* 打开串口 */
    m_hSerial = CreateFile(portname.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0,
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (m_hSerial == INVALID_HANDLE_VALUE) {
      throw std::runtime_error("ERROR: CreateFile");
    }

    /* 设置串口参数 */
    DCB dcbSerialParam = {0};
    dcbSerialParam.DCBlength = sizeof(dcbSerialParam);
    if (!GetCommState(m_hSerial, &dcbSerialParam)) {
      CloseHandle(m_hSerial);
      throw std::runtime_error("ERROR: GetCommState");
    }

    dcbSerialParam.BaudRate = m_baudRate;
    dcbSerialParam.ByteSize = m_byteSize;
    dcbSerialParam.StopBits = m_stopBits;
    dcbSerialParam.Parity = m_parity;

    if (!SetCommState(m_hSerial, &dcbSerialParam)) {
      CloseHandle(m_hSerial);
      throw std::runtime_error("ERROR: SetCommState");
    }

    /* 设置读写超时参数 */
    COMMTIMEOUTS timeout = {0};
    timeout.ReadIntervalTimeout = 0;
    timeout.ReadTotalTimeoutConstant = m_timeout_ms;
    timeout.ReadTotalTimeoutMultiplier = 0;
    timeout.WriteTotalTimeoutConstant = m_timeout_ms;
    timeout.WriteTotalTimeoutMultiplier = 0;

    if (!SetCommTimeouts(m_hSerial, &timeout)) {
      CloseHandle(m_hSerial);
      throw std::runtime_error("ERROR: SetCommTimeouts");
    }
  }

  ~SerialPort() { CloseHandle(m_hSerial); }

  /**
   * @brief 从串口中读取一个字节
   *
   * @param ch 保存读取的字节数据
   *
   * @return true 读取成功
   * @return false 读取失败
   */
  bool read(char* ch) {
    if (!ch) {
      throw std::runtime_error("ERROR: nullptr");
    }

    DWORD dwRead = 0;

    if (!ReadFile(m_hSerial, ch, 1, &dwRead, NULL)) {
      return false;
    }

    return dwRead == 1;
  }

  /**
   * @brief 向串口中写入1个字节
   *
   * @param ch 要写入的字节数据
   *
   * @return true 写入成功
   * @return false 写入失败
   */
  bool write(const char* ch) {
    if (!ch) {
      throw std::runtime_error("ERROR: nullptr");
    }

    DWORD dwWritten = 0;

    if (!WriteFile(m_hSerial, ch, 1, &dwWritten, NULL)) {
      return false;
    }

    return dwWritten == 1;
  }

 private:
  HANDLE m_hSerial;
  std::string m_portname;
  DWORD m_baudRate;
  BYTE m_stopBits;
  BYTE m_parity;
  BYTE m_byteSize;
  DWORD m_timeout_ms;
};

int main() {
  SerialPort sp("COM5", CBR_19200, ONESTOPBIT, 8, NOPARITY, 5000);
  char x = 'x';
  if (sp.write(&x)) {
    spdlog::info("Write: {}", x);
  } else {
    spdlog::error("Write: TIMEOUT");
  }

  char ch = '\0';
  if (sp.read(&ch)) {
    spdlog::info("Read: {}", ch);
  } else {
    spdlog::error("Read: TIMEOUT");
  }

  return 0;
}

/*运行会输出类似如下结果:
[2024-12-25 20:56:51.866] [info] Write: x
[2024-12-25 20:56:56.878] [error] Read: TIMEOUT

或者

[2024-12-25 20:57:09.737] [info] Write: x
[2024-12-25 20:57:14.297] [info] Read: y
*/
复制成功

CreateFile 函数用于打开一个串口并返回一个文件句柄 (handle), ReadFile 从文件句柄中读取数据, WriteFile 向文件句柄中写入数据. 串口使用 SetCommState 设置串口通讯参数, 使用 SetCommTimeouts 设置串口读写超时参数. 当然, 上面的示例只展示了如何同步对串口进行操作, 如果要进行异步操作, 相应的代码逻辑会复杂一些, 如果真的有需要建议直接使用 Boost::Asio 中的串口类提供的异步读写功能.

(完)