diff --git a/armor/include/CSerialPort/SerialPort.h b/armor/include/CSerialPort/SerialPort.h new file mode 100644 index 0000000..89513fa --- /dev/null +++ b/armor/include/CSerialPort/SerialPort.h @@ -0,0 +1,82 @@ +#ifndef CSERIALPORT_SERIALPORT_H +#define CSERIALPORT_SERIALPORT_H + +#include +#include + +namespace itas109 { + +enum class Parity { + ParityNone = 0, + ParityOdd = 1, + ParityEven = 2, +}; + +enum class DataBits { + DataBits5 = 5, + DataBits6 = 6, + DataBits7 = 7, + DataBits8 = 8, +}; + +enum class StopBits { + StopOne = 1, + StopOneAndHalf = 3, + StopTwo = 2, +}; + +enum class FlowControl { + FlowNone = 0, + FlowHardware = 1, + FlowSoftware = 2, +}; + +class CSerialPort { +public: + CSerialPort(); + ~CSerialPort(); + + // 初始化串口参数(不打开) + void init(const char* portName, + int baudRate = 115200, + Parity parity = Parity::ParityNone, + DataBits dataBits = DataBits::DataBits8, + StopBits stopBits = StopBits::StopOne, + FlowControl flowControl = FlowControl::FlowNone, + int readTimeoutMs = 1000); + + // 打开串口,成功返回 true + bool open(); + + // 关闭串口 + void close(); + + // 写数据,返回实际写入字节数(失败返回 -1) + int writeData(const uint8_t* data, size_t length); + + // 读数据,返回实际读取字节数(失败返回 -1) + int readData(uint8_t* buffer, size_t maxLength); + + // 返回最近一次错误码 + int getLastError() const { return m_lastError; } + +private: + char m_portName[256]; + int m_baudRate; + Parity m_parity; + DataBits m_dataBits; + StopBits m_stopBits; + FlowControl m_flowControl; + int m_readTimeoutMs; + + int m_fd; // 文件描述符 + int m_lastError; + bool m_isOpen; + + // termios 波特率映射 + static int toBaudRate(int baud); +}; + +} // namespace itas109 + +#endif // CSERIALPORT_SERIALPORT_H diff --git a/armor/include/CSerialPort/SerialPortInfo.h b/armor/include/CSerialPort/SerialPortInfo.h new file mode 100644 index 0000000..eece7bd --- /dev/null +++ b/armor/include/CSerialPort/SerialPortInfo.h @@ -0,0 +1,27 @@ +#ifndef CSERIALPORT_SERIALPORTINFO_H +#define CSERIALPORT_SERIALPORTINFO_H + +#include +#include + +namespace itas109 { + +struct SerialPortInfo { + char portName[256]; + char description[256]; + + SerialPortInfo() { + std::memset(portName, 0, sizeof(portName)); + std::memset(description, 0, sizeof(description)); + } +}; + +class CSerialPortInfo { +public: + // 枚举系统上所有可用串口 + static std::vector availablePortInfos(); +}; + +} // namespace itas109 + +#endif // CSERIALPORT_SERIALPORTINFO_H diff --git a/armor/src/CSerialPort/SerialPort.cpp b/armor/src/CSerialPort/SerialPort.cpp new file mode 100644 index 0000000..ee91a1d --- /dev/null +++ b/armor/src/CSerialPort/SerialPort.cpp @@ -0,0 +1,207 @@ +#include "CSerialPort/SerialPort.h" + +#include +#include +#include +#include +#include +#include + +namespace itas109 { + +CSerialPort::CSerialPort() + : m_baudRate(115200) + , m_parity(Parity::ParityNone) + , m_dataBits(DataBits::DataBits8) + , m_stopBits(StopBits::StopOne) + , m_flowControl(FlowControl::FlowNone) + , m_readTimeoutMs(1000) + , m_fd(-1) + , m_lastError(0) + , m_isOpen(false) +{ + std::memset(m_portName, 0, sizeof(m_portName)); +} + +CSerialPort::~CSerialPort() { + close(); +} + +void CSerialPort::init(const char* portName, + int baudRate, + Parity parity, + DataBits dataBits, + StopBits stopBits, + FlowControl flowControl, + int readTimeoutMs) +{ + std::strncpy(m_portName, portName, sizeof(m_portName) - 1); + m_portName[sizeof(m_portName) - 1] = '\0'; + m_baudRate = baudRate; + m_parity = parity; + m_dataBits = dataBits; + m_stopBits = stopBits; + m_flowControl = flowControl; + m_readTimeoutMs = readTimeoutMs; +} + +bool CSerialPort::open() { + if (m_isOpen) close(); + + m_fd = ::open(m_portName, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (m_fd < 0) { + m_lastError = errno; + return false; + } + + struct termios tty; + std::memset(&tty, 0, sizeof(tty)); + if (tcgetattr(m_fd, &tty) != 0) { + m_lastError = errno; + ::close(m_fd); + m_fd = -1; + return false; + } + + // 波特率 + speed_t speed = static_cast(toBaudRate(m_baudRate)); + cfsetispeed(&tty, speed); + cfsetospeed(&tty, speed); + + // 数据位 + tty.c_cflag &= ~CSIZE; + switch (m_dataBits) { + case DataBits::DataBits5: tty.c_cflag |= CS5; break; + case DataBits::DataBits6: tty.c_cflag |= CS6; break; + case DataBits::DataBits7: tty.c_cflag |= CS7; break; + case DataBits::DataBits8: + default: tty.c_cflag |= CS8; break; + } + + // 停止位 + if (m_stopBits == StopBits::StopTwo) + tty.c_cflag |= CSTOPB; + else + tty.c_cflag &= ~CSTOPB; + + // 校验位 + switch (m_parity) { + case Parity::ParityOdd: + tty.c_cflag |= PARENB; + tty.c_cflag |= PARODD; + tty.c_iflag |= (INPCK | ISTRIP); + break; + case Parity::ParityEven: + tty.c_cflag |= PARENB; + tty.c_cflag &= ~PARODD; + tty.c_iflag |= (INPCK | ISTRIP); + break; + case Parity::ParityNone: + default: + tty.c_cflag &= ~PARENB; + tty.c_iflag &= ~(INPCK | ISTRIP); + break; + } + + // 流控 + if (m_flowControl == FlowControl::FlowHardware) + tty.c_cflag |= CRTSCTS; + else + tty.c_cflag &= ~CRTSCTS; + + if (m_flowControl == FlowControl::FlowSoftware) + tty.c_iflag |= (IXON | IXOFF | IXANY); + else + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + + // 本地模式 & 输入模式(raw) + tty.c_cflag |= (CREAD | CLOCAL); + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); + tty.c_oflag &= ~OPOST; + + // 非阻塞读,超时由 select 控制 + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 0; + + if (tcsetattr(m_fd, TCSANOW, &tty) != 0) { + m_lastError = errno; + ::close(m_fd); + m_fd = -1; + return false; + } + + tcflush(m_fd, TCIOFLUSH); + m_isOpen = true; + m_lastError = 0; + return true; +} + +void CSerialPort::close() { + if (m_fd >= 0) { + ::close(m_fd); + m_fd = -1; + } + m_isOpen = false; +} + +int CSerialPort::writeData(const uint8_t* data, size_t length) { + if (!m_isOpen || m_fd < 0) { + m_lastError = EBADF; + return -1; + } + ssize_t written = ::write(m_fd, data, length); + if (written < 0) { + m_lastError = errno; + return -1; + } + tcdrain(m_fd); // 等待发送完成 + return static_cast(written); +} + +int CSerialPort::readData(uint8_t* buffer, size_t maxLength) { + if (!m_isOpen || m_fd < 0) { + m_lastError = EBADF; + return -1; + } + + // 用 select 实现超时 + struct timeval tv; + tv.tv_sec = m_readTimeoutMs / 1000; + tv.tv_usec = (m_readTimeoutMs % 1000) * 1000; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + int ret = select(m_fd + 1, &fds, nullptr, nullptr, &tv); + if (ret <= 0) { + // 超时或错误 + m_lastError = (ret == 0) ? ETIME : errno; + return 0; + } + + ssize_t bytesRead = ::read(m_fd, buffer, maxLength); + if (bytesRead < 0) { + m_lastError = errno; + return -1; + } + m_lastError = 0; + return static_cast(bytesRead); +} + +int CSerialPort::toBaudRate(int baud) { + switch (baud) { + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; + case 460800: return B460800; + case 921600: return B921600; + default: return B115200; + } +} + +} // namespace itas109 diff --git a/armor/src/CSerialPort/SerialPortInfo.cpp b/armor/src/CSerialPort/SerialPortInfo.cpp new file mode 100644 index 0000000..68f0c83 --- /dev/null +++ b/armor/src/CSerialPort/SerialPortInfo.cpp @@ -0,0 +1,35 @@ +#include "CSerialPort/SerialPortInfo.h" + +#include +#include +#include + +namespace itas109 { + +std::vector CSerialPortInfo::availablePortInfos() { + std::vector result; + + // 扫描 /dev 目录,找 ttyUSB / ttyCH341USB / ttyACM / ttyS 等设备 + DIR* dir = opendir("/dev"); + if (!dir) return result; + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + const char* name = entry->d_name; + if (std::strstr(name, "ttyUSB") != nullptr || + std::strstr(name, "ttyCH341") != nullptr || + std::strstr(name, "ttyACM") != nullptr) + { + SerialPortInfo info; + std::string fullPath = std::string("/dev/") + name; + std::strncpy(info.portName, fullPath.c_str(), sizeof(info.portName) - 1); + std::strncpy(info.description, name, sizeof(info.description) - 1); + result.push_back(info); + } + } + closedir(dir); + + return result; +} + +} // namespace itas109