Skip to content

Commit

Permalink
🔧 串口通讯增加阻塞以及不包含头尾帧的 IO
Browse files Browse the repository at this point in the history
  • Loading branch information
zhaoxi-scut committed Nov 22, 2024
1 parent 6eb82a9 commit daf8bb0
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 60 deletions.
75 changes: 61 additions & 14 deletions modules/core/include/rmvl/core/io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#pragma once

#include <array>
#include <cstring>
#include <iosfwd>
#include <memory>
#include <string>
Expand Down Expand Up @@ -140,11 +141,28 @@ void readCorners(std::istream &in, std::vector<std::vector<std::array<float, 2>>

///////////////////////////////////// 串口通信 /////////////////////////////////////

enum class BaudRate
enum class BaudRate : uint8_t
{
BR_1200, //!< 波特率 1200
BR_4800, //!< 波特率 4800
BR_9600, //!< 波特率 9600
BR_19200, //!< 波特率 19200
BR_57600, //!< 波特率 57600
BR_115200, //!< 波特率 115200
BR_230400, //!< 波特率 230400
};

//! 串口数据读取模式
enum class SPReadMode : uint8_t
{
BLOCK, //!< 阻塞模式,即读取数据时会一直等待直到有数据到来
NONBLOCK //!< 非阻塞模式,即读取数据时不会等待,如果没有数据到来则立即返回 `-1`
};

//! 串口通信模式
struct RMVL_EXPORTS SerialPortMode
{
BaudRate baud_rate{BaudRate::BR_115200}; //!< 波特率
SPReadMode read_mode{SPReadMode::BLOCK}; //!< 读取模式
};

//! 串行接口通信库
Expand All @@ -155,53 +173,82 @@ class RMVL_EXPORTS SerialPort
* @brief 构造新 SerialPort 对象
*
* @param[in] device 设备名
* @param[in] baud_rate 波特率,一般为 `BaudRate::BR_115200`
* @param[in] mode 串口通信模式
*/
SerialPort(std::string_view device, BaudRate baud_rate);
SerialPort(std::string_view device, SerialPortMode mode = {});

SerialPort(const SerialPort &) = delete;
SerialPort(SerialPort &&) = default;
SerialPort &operator=(const SerialPort &) = delete;
SerialPort &operator=(SerialPort &&) = default;

/**
* @brief 从串口读取数据到结构体
* @brief 从串口读取数据到聚合体中
* @note 每次读取后会清空缓冲区
*
* @tparam Tp 读取到结构体的类型
* @tparam Tp 读取到聚合体的类型
* @param[in] head_flag 头帧
* @param[in] tail_flag 尾帧
* @param[out] data 读取的结构体数据
* @param[out] data 读取的聚合体数据
* @return 是否读取成功
*/
template <typename Tp>
template <typename Tp, typename Enable = std::enable_if_t<std::is_aggregate_v<Tp>>>
inline bool read(unsigned char head_flag, unsigned char tail_flag, Tp &data)
{
bool retval{false};
bool retval{};
constexpr int LENGTH = 512, SIZE = sizeof(Tp);
unsigned char buffer[LENGTH] = {0};
auto len_result = fdread(buffer, LENGTH);
for (long int i = 0; (i + SIZE + 1) < len_result; i++)
if (buffer[i] == head_flag && buffer[i + SIZE + 1] == tail_flag)
{
auto p = memcpy(&data, &buffer[i + 1], SIZE);
auto p = std::memcpy(&data, &buffer[i + 1], SIZE);
if (p == &data)
retval = true;
}
return retval;
}

/**
* @brief 结构体数据写入串口
* @brief 不带头尾标志的数据读取,从串口读取数据到聚合体中
*
* @tparam Tp 读取到聚合体的类型
* @param[out] data 读取的聚合体数据
* @return 是否读取成功
*/
template <typename Tp, typename Enable = std::enable_if_t<std::is_aggregate_v<Tp>>>
inline bool read(Tp &data)
{
bool retval{};
constexpr int LENGTH = 512, SIZE = sizeof(Tp);
unsigned char buffer[LENGTH] = {0};
auto len_result = fdread(buffer, LENGTH);
if (len_result >= SIZE)
{
auto p = std::memcpy(&data, buffer, SIZE);
if (p == &data)
retval = true;
}
return retval;
}

template <typename Tp, typename Enable = std::enable_if_t<std::is_aggregate_v<Tp>>>
inline SerialPort &operator>>(Tp &data) { return (this->read(data), *this); }

/**
* @brief 数据写入串口
* @note 每次写入前会清空缓冲区
*
* @tparam Tp 写入结构体的类型
* @param[in] data 要写入的结构体
* @tparam Tp 写入聚合体的类型
* @param[in] data 要写入的聚合体
* @return 是否写入成功
*/
template <typename Tp>
template <typename Tp, typename Enable = std::enable_if_t<std::is_aggregate_v<Tp>>>
inline bool write(const Tp &data) { return (sizeof(data) == fdwrite(&data, sizeof(data))); }

template <typename Tp, typename Enable = std::enable_if_t<std::is_aggregate_v<Tp>>>
inline SerialPort &operator<<(const Tp &data) { return (this->write(data), *this); }

//! 串口是否打开
bool isOpened() const;

Expand Down
15 changes: 5 additions & 10 deletions modules/core/src/io_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,17 @@

#ifdef _WIN32
#include <windows.h>
#else
#include <termios.h>
#endif

#include "rmvl/core/io.hpp"

namespace rm
{

int getBaudRate(BaudRate baud_rate);

class SerialPort::Impl
{
public:
explicit Impl(std::string_view device, BaudRate baud_rate) : _device(device), _baud_rate(getBaudRate(baud_rate)) { open(); }
explicit Impl(std::string_view device, SerialPortMode mode = {}) : _device(device), _mode(mode) { open(); }

~Impl() { close(); }

Expand All @@ -45,12 +41,11 @@ class SerialPort::Impl
#ifdef _WIN32
HANDLE _handle{INVALID_HANDLE_VALUE}; //!< 文件句柄
#else
int _fd{-1}; //!< 文件描述符
termios _option; //!< 终端控制
int _fd{-1}; //!< 文件描述符
#endif
bool _is_open{}; //!< 串口打开标志位
std::string _device; //!< 设备名
int _baud_rate{}; //!< 波特率
bool _is_open{}; //!< 串口打开标志位
std::string _device; //!< 设备名
SerialPortMode _mode; //!< 串口通信模式
};

class PipeServer::Impl
Expand Down
120 changes: 85 additions & 35 deletions modules/core/src/serial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#ifndef _WIN32
#include <dirent.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#endif

Expand All @@ -24,60 +25,102 @@ namespace rm

RMVL_IMPL_DEF(SerialPort)

int getBaudRate(BaudRate baud_rate)
static unsigned int getBaudRate(BaudRate baud_rate)
{
#ifdef _WIN32
switch (baud_rate)
{
case BaudRate::BR_1200:
return 1200;
case BaudRate::BR_4800:
return 4800;
case BaudRate::BR_9600:
return 9600;
case BaudRate::BR_57600:
return 57600;
case BaudRate::BR_115200:
return 115200;
case BaudRate::BR_230400:
return 230400;
default:
return 115200;
}
#else
switch (baud_rate)
{
case BaudRate::BR_1200:
return B1200;
case BaudRate::BR_4800:
return B4800;
case BaudRate::BR_9600:
return B9600;
case BaudRate::BR_57600:
return B57600;
case BaudRate::BR_115200:
return B115200;
case BaudRate::BR_230400:
return B230400;
default:
return B115200;
}
#endif
}

SerialPort::SerialPort(std::string_view device, BaudRate baud_rate) : _impl(new Impl(device, baud_rate)) {}
SerialPort::SerialPort(std::string_view device, SerialPortMode mode) : _impl(new Impl(device, mode)) {}
bool SerialPort::isOpened() const { return _impl->isOpened(); }
long int SerialPort::fdwrite(const void *data, size_t length) { return _impl->fdwrite(data, length); }
long int SerialPort::fdread(void *data, size_t len) { return _impl->fdread(data, len); }

#ifdef _WIN32
void SerialPort::Impl::open()
{
_is_open = false;
INFO_("Opening the serial port: %s", _device.c_str());
_handle = CreateFileA(_device.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);

_handle = CreateFileA(
_device.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (_handle == INVALID_HANDLE_VALUE)
{
ERROR_("Failed to open the serial port.");
_is_open = false;
return;
}

DCB dcb;
COMMTIMEOUTS timeouts{};
if (_mode.read_mode == SPReadMode::BLOCK)
{
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
}
else
{
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
}
timeouts.WriteTotalTimeoutConstant = 1;
timeouts.WriteTotalTimeoutMultiplier = 1;
if (!SetCommTimeouts(_handle, &timeouts))
{
WARNING_("Failed to set the serial port timeout.");
_is_open = false;
return;
}

DCB dcb{};
GetCommState(_handle, &dcb);
dcb.BaudRate = _baud_rate;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
SetCommState(_handle, &dcb);
DWORD bps = getBaudRate(_mode.baud_rate);
dcb.BaudRate = bps; // 波特率
dcb.ByteSize = 8; // 数据位
dcb.Parity = NOPARITY; // 无校验
dcb.StopBits = ONESTOPBIT; // 1 位停止位
if (!SetCommState(_handle, &dcb))
{
WARNING_("Failed to set the serial port state.");
_is_open = false;
return;
}

_is_open = true;
}
Expand All @@ -89,14 +132,14 @@ void SerialPort::Impl::close()
_is_open = false;
}

long int SerialPort::Impl::fdwrite(void *data, std::size_t len)
long int SerialPort::Impl::fdwrite(const void *data, std::size_t len)
{
DWORD len_result{};
if (_is_open)
{
if (!WriteFile(_handle, data, static_cast<DWORD>(len), &len_result, nullptr))
{
WARNING_("Unable to write to serial port, error code: %d", ::GetLastError());
WARNING_("Unable to write to serial port, error code: %ld", ::GetLastError());
open();
}
else
Expand All @@ -114,7 +157,7 @@ long int SerialPort::Impl::fdread(void *data, std::size_t len)
{
if (!ReadFile(_handle, data, static_cast<DWORD>(len), &len_result, nullptr))
{
WARNING_("The serial port cannot be read, error code: %d, restart...", ::GetLastError());
WARNING_("The serial port cannot be read, error code: %ld, restart...", ::GetLastError());
open();
}
}
Expand All @@ -128,33 +171,40 @@ long int SerialPort::Impl::fdread(void *data, std::size_t len)
#else
void SerialPort::Impl::open()
{
_is_open = false;
INFO_("Opening the serial port: %s", _device.c_str());
_fd = ::open(_device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); // 非堵塞情况

_fd = ::open(_device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (_fd == -1)
{
ERROR_("Failed to open the serial port.");
_is_open = false;
return;
}
tcgetattr(_fd, &_option);
if (_mode.read_mode == SPReadMode::BLOCK)
{
// 清除 O_NDELAY 标志,设置为阻塞模式
int flags = fcntl(_fd, F_GETFL, 0);
flags &= ~O_NONBLOCK;
fcntl(_fd, F_SETFL, flags);
}

termios option;
tcgetattr(_fd, &option);

// 修改所获得的参数
_option.c_iflag = 0; // 原始输入模式
_option.c_oflag = 0; // 原始输出模式
_option.c_lflag = 0; // 关闭终端模式
_option.c_cflag |= (CLOCAL | CREAD); // 设置控制模式状态,本地连接,接收使能
_option.c_cflag &= ~CSIZE; // 字符长度,设置数据位之前一定要屏掉这个位
_option.c_cflag &= ~CRTSCTS; // 无硬件流控
_option.c_cflag |= CS8; // 8位数据长度
_option.c_cflag &= ~CSTOPB; // 1位停止位
_option.c_cc[VTIME] = 0;
_option.c_cc[VMIN] = 0;
cfsetospeed(&_option, _baud_rate); // 设置输入波特率
cfsetispeed(&_option, _baud_rate); // 设置输出波特率
option.c_iflag = 0; // 原始输入模式
option.c_oflag = 0; // 原始输出模式
option.c_lflag = 0; // 关闭终端模式
option.c_cflag |= (CLOCAL | CREAD); // 设置控制模式状态,本地连接,接收使能
option.c_cflag &= ~CSIZE; // 字符长度,设置数据位之前一定要屏掉这个位
option.c_cflag &= ~CRTSCTS; // 无硬件流控
option.c_cflag |= CS8; // 8 位数据长度
option.c_cflag &= ~CSTOPB; // 1 位停止位
option.c_cc[VTIME] = 0;
option.c_cc[VMIN] = _mode.read_mode == SPReadMode::BLOCK ? 1 : 0;
cfsetspeed(&option, getBaudRate(_mode.baud_rate)); // 设置输入波特率

// 设置新属性,TCSANOW:所有改变立即生效
tcsetattr(_fd, TCSANOW, &_option);
tcsetattr(_fd, TCSANOW, &option);

_is_open = true;
}
Expand Down
3 changes: 2 additions & 1 deletion modules/light/include/rmvl/light.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
/**
* @defgroup light 光源控制器
* @{
* @defgroup opt_light_control OPT 奥普特光源控制器
* @defgroup opt_light_control OPT 奥普特 GigE 光源控制库
* @defgroup hik_light_control 海康机器人 RS-232 光源控制库
* @}
*/

Expand Down

0 comments on commit daf8bb0

Please sign in to comment.