#include #include #ifdef __linux__ #include "Serial_p.h" #include #include #include #define SERIAL_END " " << __FILE__ << " " << __LINE__ << std::endl; #ifdef USE_SERIAL_STRUCT #define FLAG flags #else #define FLAG c_cflag #endif //stty -F /dev/pts/1 9601 // sudo setserial /dev/pts/1 baud_base 115200 custom_divisor 1 // sudo setserial /dev/pts/1 -g // sudo setserial /dev/ttyS4 -g namespace Psc::serial { //#define LOG(MSG) log_serial_error(__func__, __FILE__, __LINE__, MSG); void set_binary_mode(Serial_Settings &serial_info) { #if defined(USE_TERMIOS2) || defined(USE_TERMIOS) { // -ignbrk: 启用时忽略断开信号(如挂断电话信号等),不做处理。 // -brkint: 启用时接收到断开信号(如挂断电话信号)会中断程序。 // -ignpar: 启用时,接收到具有错误奇偶校验的字符会被丢弃。 // -parmrk: 启用时,接收到奇偶校验错误的字符会被标记为特殊字符(通常是某种转义字符),并且系统可能做出反应。 // -inpck: 启用时,接收到的字符将不再进行奇偶校验检查。 // -istrip: 启用时,接收到的每个字符的第 8 位会被去除(通常用于旧的串行协议或无符号字符的通信)。 // -inlcr: 启用时,输入中的换行符(\n)(0x0A)不会被转换为回车符(\r)(0x0D),保留原样传递。 // -igncr: 启用时,回车符(\r)会被忽略,不会传递给程序。 // icrnl: 启用时,接收到的回车符(\r)会被转换为换行符(\n)。 // ixon: 启用时,启用 XON/XOFF 流控制。接收端使用 XON(ASCII 17,Ctrl-Q, 0x11)和 XOFF(ASCII 19,Ctrl-S, 0x13)来控制数据流的暂停和恢复。 // -ixoff: 启用时,启用 XOFF 流控制。与 ixon 配合使用,接收端通过发送 XOFF 来暂停数据传输,并等待 XON 来恢复数据流。 // -iuclc: 启用时,所有输入字符都将以原始形式传递,不会自动转换成小写字母。 // -ixany: 启用时,允许任何字符作为流控制符,通常用于自定义流控制字符。 // -imaxbel: 启用时,接收到 BEL(响铃,ASCII 7)字符时会触发响铃或警告。 // -iutf8: 启用时,禁用 UTF-8 编码字符的处理,串口通信将不支持 UTF-8 编码格式,而是使用传统字符编码格式。 // 获取当前的输入模式标志 auto &flag = serial_info.c_iflag; // 禁用一些默认的转换设置,以便读取原始二进制数据流 flag &= ~IGNBRK; // 禁用忽略断开信号 flag &= ~BRKINT; // 禁用接收到断开信号中断程序 flag &= ~IGNPAR; // 禁用忽略奇偶校验错误 flag &= ~PARMRK; // 禁用标记奇偶校验错误字符 flag &= ~INPCK; // 禁用奇偶校验检查 flag &= ~ISTRIP; // 禁用去除字符的第 8 位 flag &= ~INLCR; // 禁用输入中的换行符转换为回车符 flag &= ~IGNCR; // 禁用忽略回车符 flag &= ~ICRNL; // 禁用回车符转换为换行符 flag &= ~IXON; // 禁用 XON 流控 flag &= ~IXOFF; // 禁用 XOFF 流控 flag &= ~IUCLC; // 禁用输入字符转换为小写字母 flag &= ~IXANY; // 禁用允许任何字符作为流控制符 flag &= ~IMAXBEL; // 禁用接收到 BEL 字符时触发响铃 flag &= ~IUTF8; // 禁用 UTF-8 编码处理 } { // opost: 启用时,允许输出处理(例如处理换行符、回车符等)。 // -olcuc: 启用时,将大写字母转换为小写字母(用于某些控制台模式)。 // -ocrnl: 启用时,将输出中的换行符(\n)转换为回车符(\r)。 // onlcr: 启用时,将输出中的换行符(\n)转换为回车换行符(\r\n),通常用于旧式终端。 // -onocr: 启用时,回车符(\r)不会被输出到设备(即忽略回车符)。 // -onlret: 启用时,输出的换行符(\n)会让设备进行回车操作,等同于输出换行时,光标回到行首。 // -ofill: 启用时,输出时对填充字符(如0)进行处理(在某些协议中有用)。 // -ofdel: 启用时,删除字符会被处理为“删除”操作(ASCII DEL字符)而不是其他字符。 // nl0: 启用时,禁用换行符的处理,换行符不做任何转换。 // cr0: 启用时,禁用回车符的处理,回车符不做任何转换。 // tab0: 启用时,禁用制表符的处理,制表符不会做任何转换。 // bs0: 启用时,禁用退格符的处理,退格符不会做任何转换。 // vt0: 启用时,禁用垂直制表符(VT)的处理,垂直制表符不会做任何转换。 // ff0: 启用时,禁用换页符(FF)的处理,换页符不会做任何转换。 auto &flag = serial_info.c_oflag; // 禁用所有的字符转换和处理 flag &= ~OPOST; // 禁用输出处理 flag &= ~OLCUC; // 禁用大写转换为小写 flag &= ~OCRNL; // 禁用换行符转换为回车符 flag &= ~ONLCR; // 禁用换行符转换为回车换行符 flag &= ~ONOCR; // 禁用回车符不输出 flag &= ~ONLRET; // 禁用换行符引起的回车操作 flag &= ~OFILL; // 禁用填充字符的处理 flag &= ~OFDEL; // 禁用删除字符的处理 flag &= ~NL0; // 禁用换行符的处理 flag &= ~CR0; // 禁用回车符的处理 flag &= ~TAB0; // 禁用制表符的处理 flag &= ~BS0; // 禁用退格符的处理 flag &= ~VT0; // 禁用垂直制表符的处理 flag &= ~FF0; // 禁用换页符的处理 } { // isig: 启用终端信号处理(如 SIGINT, SIGQUIT 等)。 // icanon: 启用规范模式,允许行编辑,按下退格键删除字符。 // iexten: 启用扩展输入字符处理,如支持 `Ctrl`+`D` 等功能。 // echo: 启用输入回显,输入的字符会在终端上显示。 // echoe: 启用回显删除字符功能,按下退格键时字符被删除并显示删除过程。 // echok: 启用回显回车字符,当按下回车键时,回显相应字符。 // -echonl: 禁用回显换行符,换行符不会显示在终端上。 // -noflsh: 禁用输入输出缓冲区的刷新操作,不自动同步数据到终端。 // -xcase: 禁用大小写转换,输入的字母不会自动转换大小写。 // tostop: 启用后台进程输出停止功能,后台进程生成输出时会被暂停。 // -echoprt: 禁用回显打印字符,输出的字符不会显示在终端。 // echoctl: 启用回显控制字符,按下 `Ctrl`+`C` 等控制字符时会显示。 // echoke: 启用回显字符删除时的效果,删除字符时会显示删除过程。 // -flusho: 禁用数据的实时刷新,数据不会立即显示到终端。 // -extproc: 禁用外部处理功能,外部处理程序无法影响串口通信。 auto &flag = serial_info.c_lflag; // 将串口设置为原始输入模式 flag &= ~ISIG; // 禁用终端信号处理(如 SIGINT, SIGQUIT 等) flag &= ~ICANON; // 禁用规范模式,禁止行编辑,按下退格键不会删除字符 flag &= ~IEXTEN; // 禁用扩展输入字符处理,如不支持 `Ctrl`+`D` 等功能 flag &= ~ECHO; // 禁用输入回显,输入的字符不会显示在终端上 flag &= ~ECHOE; // 禁用回显删除字符功能,按下退格键时字符不会被删除并显示删除过程 flag &= ~ECHOK; // 禁用回显回车字符,按下回车键时不显示回车字符 flag &= ~ECHONL; // 禁用回显换行符,换行符不会显示在终端上 flag &= ~NOFLSH; // 禁用输入输出缓冲区的刷新操作,不自动同步数据到终端 flag &= ~XCASE; // 禁用大小写转换,输入的字母不会自动转换大小写 flag &= ~TOSTOP; // 禁用后台进程输出停止功能,后台进程的输出不会被暂停 flag &= ~ECHOPRT; // 禁用回显打印字符,输出的字符不会显示在终端 flag &= ~ECHOCTL; // 禁用回显控制字符,按下 `Ctrl`+`C` 等控制字符时不会显示 flag &= ~ECHOKE; // 禁用回显字符删除时的效果,删除字符时不会显示删除过程 flag &= ~FLUSHO; // 禁用数据的实时刷新,数据不会立即显示到终端 flag &= ~EXTPROC; // 禁用外部处理功能,外部处理程序无法影响串口通信 } #endif } void set_buffer_byte_size(Serial_FD fd, int byte_size) { struct serial_struct serinfo; if (ioctl(fd, TIOCGSERIAL, &serinfo) == -1) { LOG_ERROR(VAR_STR_2(fd, byte_size) + " 设置串口buffer_byte_size失败!") return; } serinfo.xmit_fifo_size = byte_size; if (ioctl(fd, TIOCSSERIAL, &serinfo) == -1) { LOG_ERROR(VAR_STR_2(fd, byte_size) + " 设置串口buffer_byte_size失败!") } } // #if defined(USE_TERMIOS) || defined(USE_TERMIOS2) #if defined(USE_TERMIOS) static std::vector > list{ {0, B0}, {50, B50}, {75, B75}, {110, B110}, {134, B134}, {150, B150}, {200, B200}, {300, B300}, {600, B600}, {1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800}, {9600, B9600}, {19200, B19200}, {38400, B38400}, {57600, B57600}, {115200, B115200}, {230400, B230400}, {460800, B460800}, {500000, B500000}, {576000, B576000}, {921600, B921600}, {1000000, B1000000}, {1152000, B1152000}, {1500000, B1500000}, {2000000, B2000000}, {2500000, B2500000}, {3000000, B3000000}, {3500000, B3500000}, {4000000, B4000000} }; long baud_to_value(Baud_Rate_Type baud_rate) { static std::map map = []() { std::map map; for (auto &item: list) { map[item.second] = item.first; } return map; }(); auto iter = map.find(baud_rate); if (iter != map.end()) { return iter->second; } else { LOG_ERROR(VAR_STR_1(baud_rate) + " 不支持的波特率!") return -1; } } std::optional value_to_baud(const long baud_rate) { static std::map map = []() { std::map map; for (auto &item: list) { map[item.first] = item.second; } return map; }(); auto iter = map.find(baud_rate); if (iter != map.end()) { return iter->second; } else { return std::nullopt; } } #endif std::string get_serial_info_str(std::string_view serial_name) { Serial serial; serial.set_serial_name(std::move(serial_name)); serial.open(); std::string ret = serial.to_string(); serial.close(); return ret; } int open(std::string_view serial_name) { // 打开串口设备 O_RDWR | O_NOCTTY | O_CLOEXEC Serial_FD fd = ::open(serial_name.data(), O_RDWR | O_NOCTTY | O_CLOEXEC); // O_RDWR: 可读可写, O_NOCTTY: 不设置为控制终端, O_NDELAY: 非阻塞 if (fd == -1) { LOG_ERROR(VAR_STR_1(serial_name.data()) + "串口打开失败!") Psc::fail_fast(); } return fd; } // 设置串口阻塞/非阻塞模式 void set_block(Serial_FD fd, bool block) { int flags = fcntl(fd, F_GETFL); // 获取当前文件描述符的标志 if (flags == -1) { LOG_ERROR(VAR_STR_2(fd, block) + "串口打开失败!") } if (block) { flags &= ~O_NONBLOCK; // 如果需要阻塞模式,则移除 O_NONBLOCK 标志 } else { flags |= O_NONBLOCK; // 如果需要非阻塞模式,则设置 O_NONBLOCK 标志 } if (fcntl(fd, F_SETFL, flags) == -1) { LOG_ERROR(VAR_STR_2(fd, block)) } } void close(Serial_FD fd) { ::close(fd); } Serial_Settings get_serial_info(Serial_FD fd) { Serial_Settings serial_info{}; #if defined(USE_TERMIOS) if (tcgetattr(fd, &serial_info) != 0) { LOG_ERROR("") } #elif defined(USE_TERMIOS2) if (ioctl(fd, TCGETS2, &serial_info) < 0) { perror(("获取串口信息失败! fd:[" + std::to_string(fd) + "]").c_str()); } #elif defined(USE_SERIAL_STRUCT) if (ioctl(fd, TIOCGSERIAL, &serial_info) == -1) { perror(("获取串口信息失败! fd:[" + std::to_string(fd) + "]").c_str()); } #endif return serial_info; } void reset(Serial_Settings &serial_info) { cfmakeraw(&serial_info); } void set_serial_info(Serial_FD fd, Serial_Settings serial_info) { #if defined(USE_TERMIOS) if (tcsetattr(fd, TCSANOW, &serial_info) != 0) { LOG_ERROR(VAR_STR_1(fd)) } #elif defined(USE_TERMIOS2) if (ioctl(fd, TCSETS2, &serial_info) < 0) { perror(("设置串口信息失败! fd:[" + std::to_string(fd) + "]").c_str()); } #elif defined(USE_SERIAL_STRUCT) if (ioctl(fd, TIOCSSERIAL, &serial_info) == -1) { perror(("设置串口信息失败! fd:[" + std::to_string(fd) + "]").c_str()); } #endif } // #define BOTHER 0010000 void set_baud_rate(Serial_Settings &serial_info, Serial_FD fd, Baud_Rate_Type baud_rate) { #if defined(USE_TERMIOS) auto &flags = serial_info.c_cflag; flags |= CBAUDEX; // flags &= ~BOTHER; auto br = value_to_baud(static_cast(baud_rate)); if (br.has_value()) { if (cfsetispeed(&serial_info, br.value()) != 0) { LOG_ERROR(VAR_STR_1(baud_rate) + "cfsetispeed") } if (cfsetospeed(&serial_info, br.value()) != 0) { LOG_ERROR(VAR_STR_1(baud_rate) + "cfsetospeed") } //LOG_ERROR(VAR_STR_1(baud_rate) + " Failed to set buffer size!") } else { struct serial_struct serinfo; // 获取当前串口配置 if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0) { LOG_ERROR(VAR_STR_1(fd) + "获取串口信息失败") } // 设置自定义波特率 serinfo.flags = (serinfo.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST; // 计算分频值,并确保不小于1 int tmp = serinfo.baud_base / baud_rate; serinfo.custom_divisor = std::max(1, tmp); int actual_baud = serinfo.baud_base / serinfo.custom_divisor; double error = std::abs(1.0 - (double) actual_baud / baud_rate); if (error > 0.02) { LOG_ERROR("无法精确设置请求的波特率(误差" + std::to_string(error * 100) + "%)") } else { std::cout << "已设置波特率为" + std::to_string(actual_baud) + "bps" << std::endl; } if (ioctl(fd, TIOCSSERIAL, &serinfo) < 0) { LOG_ERROR("设置自定义波特率失败"); } if (cfsetispeed(&serial_info, B38400) != 0) { LOG_ERROR(VAR_STR_1(baud_rate) + "cfsetispeed") } if (cfsetospeed(&serial_info, B38400) != 0) { LOG_ERROR(VAR_STR_1(baud_rate) + "cfsetospeed") } } #elif defined(USE_TERMIOS2) auto &flags = serial_info.c_cflag; flags &= ~CBAUDEX; flags |= BOTHER; serial_info.c_ispeed = baud_rate; serial_info.c_ospeed = baud_rate; // serial_info.c_cc[VMIN] = 25; // serial_info.c_cc[VTIME] = 0; #elif defined(USE_SERIAL_STRUCT) // baud_rate = baud_base / custom_divisor // baud_base = baud_rate * custom_divisor // custom_divisor = baud_base / baud_rate std::cout << "baud_rate " << baud_rate << std::endl; std::cout << "baud_base " << serial_info.baud_base << std::endl; std::cout << "custom_divisor " << serial_info.custom_divisor << std::endl; serial_info.custom_divisor = serial_info.baud_base / baud_rate; std::cout << "after baud_base " << serial_info.baud_base << std::endl; std::cout << "after custom_divisor " << serial_info.custom_divisor << std::endl; //serial_info.baud_base = baud * 100; #endif } Baud_Rate_Type get_baud_rate(Serial_Settings &serial_info) { #if defined(USE_SERIAL_STRUCT) return serial_info.baud_base / serial_info.custom_divisor; #elif defined(USE_TERMIOS2) return serial_info.c_ispeed; #elif defined(USE_TERMIOS) return baud_to_value(cfgetispeed(&serial_info)); #endif } void set_data_bits(Serial_Settings &serial_info, DataBits data_bits) { auto &flag = serial_info.FLAG; // 清除当前的数据位设置 flag &= ~CSIZE; // 清除数据位标志 switch (data_bits) { case DataBits::Data5: flag |= CS5; break; case DataBits::Data6: flag |= CS6; break; case DataBits::Data7: flag |= CS7; break; case DataBits::Data8: flag |= CS8; break; case DataBits::UnknownDataBits: default: static Log_Type log_type({}, {{"POS", LOG_POS}}); serial_logger->error("", log_type, VAR_STR_1(data_bits) + get_error_message()); return; } } DataBits get_data_bits(Serial_Settings &serial_info) { auto &flag = serial_info.FLAG; switch (flag & CSIZE) { case CS5: return DataBits::Data5; case CS6: return DataBits::Data6; case CS7: return DataBits::Data7; case CS8: return DataBits::Data8; default: return DataBits::UnknownDataBits; // 如果数据位设置不正确,则返回 Unknown } } void set_stop_bits(Serial_Settings &serial_info, StopBits stop_bits) { auto &flag = serial_info.FLAG; // 清除当前停止位设置 flag &= ~CSTOPB; // 清除 2 停止位标志 switch (stop_bits) { case StopBits::OneStop: // 设置为 1 个停止位,不需要更改 c_cflag,因为 1 个停止位是默认值 break; case StopBits::OneAndHalfStop: // 1.5 停止位不是所有系统都支持,如果不支持,可能会默认为 2 停止位 // Linux 系统一般没有明确的 1.5 停止位设置,可能会自动转换为 2 停止位 { static Log_Type log_type({}, {{"POS", LOG_POS}}); serial_logger->error("", log_type, VAR_STR_1(stop_bits) + get_error_message() + "Linux 系统一般没有明确的 1.5 停止位设置,自动转换为 2 停止位"); } // 使用 2 停止位标志 flag |= CSTOPB; break; case StopBits::TwoStop: // 设置为 2 个停止位 flag |= CSTOPB; break; case StopBits::UnknownStopBits: default: std::cerr << "Unknown stop bits configuration." << std::endl; { static Log_Type log_type({}, {{"POS", LOG_POS}}); serial_logger->error("", log_type, VAR_STR_1(stop_bits) + get_error_message() + " 未知的停止位!"); } return; } } StopBits get_stop_bits(Serial_Settings &serial_info) { auto &flag = serial_info.FLAG; if (flag & CSTOPB) { // 如果 CSTOPB 设置了,表示是 2 停止位或者 1.5 停止位 if (flag & CSTOPB) { return StopBits::TwoStop; } return StopBits::UnknownStopBits; // 如果无法明确,返回未知停止位 } else { // 如果 CSTOPB 没有设置,表示是 1 停止位 return StopBits::OneStop; } } void set_parity(Serial_Settings &serial_info, Parity parity) { auto &flag = serial_info.FLAG; // 清除原有的校验位设置 flag &= ~(PARENB | PARODD | CMSPAR); // 清除 PARENB, PARODD 和 CMSPAR switch (parity) { case Parity::NoParity: // 无校验 break; case Parity::EvenParity: // 偶校验 flag |= PARENB; // 启用校验 flag &= ~PARODD; // 设置为偶校验 break; case Parity::OddParity: // 奇校验 flag |= PARENB; // 启用校验 flag |= PARODD; // 设置为奇校验 break; case Parity::SpaceParity: // 空格校验(校验位固定为 0) flag |= PARENB; // 启用校验 flag |= CMSPAR; // 强制校验位为 0 flag &= ~PARODD; // 设置为偶校验(只需设置 CMSPAR 即可) break; case Parity::MarkParity: // 标记校验(校验位固定为 1) flag |= PARENB; // 启用校验 flag |= CMSPAR; // 强制校验位为 1 flag |= PARODD; // 设置为奇校验(只需设置 CMSPAR 即可) break; case Parity::UnknownParity: default: static Log_Type log_type({}, {{"POS", LOG_POS}}); serial_logger->error("", log_type, VAR_STR_1(parity) + get_error_message() + " 未知的停止位!"); return; } } Parity get_parity(Serial_Settings &serial_info) { auto &flag = serial_info.FLAG; if (!(flag & PARENB)) { return Parity::NoParity; } // 如果启用了校验并且设置了 CMSPAR if (flag & CMSPAR) { // 空格校验或者标记校验 if (flag & PARODD) { return Parity::MarkParity; // 设置 PARODD 表示标记校验 } else { return Parity::SpaceParity; // 未设置 PARODD 表示空格校验 } } // 启用了校验且没有设置 CMSPAR if (flag & PARODD) { return Parity::OddParity; // 奇校验 } else { return Parity::EvenParity; // 偶校验 } } void set_flow_control(Serial_Settings &serial_info, FlowControl flow_control) { #if defined(USE_TERMIOS2) || defined(USE_TERMIOS) auto &flag = serial_info.FLAG; flag &= ~(CRTSCTS); // 清除硬件流控设置 serial_info.c_iflag &= ~(IXON | IXOFF | IXANY); // 清除软件流控设置 switch (flow_control) { case FlowControl::NoFlowControl: // 不启用任何流控,默认设置 break; case FlowControl::HardwareControl: // 启用硬件流控 (RTS/CTS) flag |= CRTSCTS; break; case FlowControl::SoftwareControl: // 启用软件流控 (XON/XOFF) serial_info.c_iflag |= IXON | IXOFF | IXANY; break; case FlowControl::UnknownFlowControl: default: static Log_Type log_type({}, {{"POS", LOG_POS}}); serial_logger->error("", log_type, VAR_STR_1(flow_control) + get_error_message() + " 未知的流控类型!"); return; } #endif } FlowControl get_flow_control(Serial_Settings &serial_info) { #if defined(USE_TERMIOS2) || defined(USE_TERMIOS) auto &flag = serial_info.FLAG; // 检查硬件流控 (RTS/CTS) if (flag & CRTSCTS) { return FlowControl::HardwareControl; } // 检查软件流控 (XON/XOFF) if (serial_info.c_iflag & (IXON | IXOFF)) { return FlowControl::SoftwareControl; } #endif // 如果没有流控设置 return FlowControl::NoFlowControl; } // 在 Linux 系统中,SIOCINQ 和 SIOCOUTQ 是用于通过 ioctl() 系统调用获取套接字缓冲区中数据量的命令。 // // 🔍 SIOCINQ 和 SIOCOUTQ 的作用 // TIOCINQ 和 TIOCOUTQ。 // FIONREAD FIONWRITE // 读取数据接口实现 int get_available_bytes(Serial_FD fd) { int bytes_available = 0; // 获取串口缓冲区中待读取的字节数 if (ioctl(fd, FIONREAD, &bytes_available) == -1) { LOG_ERROR("获取可用字节数错误!") return -1; } return bytes_available; } int get_written_bytes(Serial_FD fd) { int bytes_written = 0; // 获取串口发送缓冲区中待发送的字节数 if (ioctl(fd, TIOCOUTQ, &bytes_written) == -1) { LOG_ERROR(VAR_STR_2(fd, bytes_written) + "获取写缓冲区字节数错误!") return -1; } return bytes_written; } int get_buffer_byte_size(Serial_FD fd) { struct serial_struct serinfo; // 获取串口的当前设置 if (ioctl(fd, TIOCGSERIAL, &serinfo) == -1) { LOG_ERROR(VAR_STR_2(fd, serinfo.xmit_fifo_size)) return -1; } // 返回缓冲区大小 return serinfo.xmit_fifo_size; } // int64_t read_data(Serial_FD fd, char* data, int64_t maxSize) { // // 从文件描述符读取数据 // ssize_t bytesRead = read(fd, data, maxSize); // if (bytesRead < 0) { // // ,返回 -1 // static Log_Type log_type({}, {{"POS", LOG_POS}}); // serial_logger->error("", log_type, VAR_STR_2(fd, maxSize)+ "读取错误!"); // return 0; // } // return bytesRead; // 返回实际读取的字节数 // } int64_t read_data(Serial_FD fd, char *data, int64_t maxSize) { ssize_t bytesRead = read(fd, data, maxSize); if (bytesRead < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; // 非阻塞模式下没有数据可读时返回 0 } else { LOG_ERROR("read!") return -1; // 其他错误 } } return bytesRead; // 返回读取的字节数 } int64_t write(Serial_FD fd, const char *data, int64_t maxSize) { return ::write(fd, data, maxSize); } } #undef FLAG #undef SERIAL_END #endif /* root@RK356X:~# stty -F /dev/ttyS4 -a 控制模式 speed 9600 baud; // 设置串口的波特率为9600比特每秒 rows 0; // 设置终端的行数为0,通常这由终端的大小自动决定 columns 0; // 设置终端的列数为0,通常这由终端的大小自动决定 line = 0; // 设置串口线路的配置为0,默认配置 intr = ^C; // `^C`(Ctrl+C)是中断字符,用于终止当前程序 quit = ^\; // `^\\`(Ctrl+\)用作退出字符,用于退出当前进程 erase = ^?; // `^?`(通常是退格键)用于删除前一个字符 kill = ^U; // `^U`(Ctrl+U)用于删除当前行的所有字符 eof = ^D; // `^D`(Ctrl+D)用于发送文件结束符(EOF),常用于退出程序 eol = ; // 结束符(end-of-line)未定义(没有设置) eol2 = ; // 第二个结束符(eol2)未定义(没有设置) swtch = ; // 切换字符未定义(没有设置) start = ^Q; // `^Q`(Ctrl+Q)作为流控制的开始字符(XON,恢复数据传输) stop = ^S; // `^S`(Ctrl+S)作为流控制的停止字符(XOFF,暂停数据传输) susp = ^Z; // `^Z`(Ctrl+Z)用于挂起进程(常见于Unix-like系统) rprnt = ^R; // `^R`(Ctrl+R)用于重新打印当前行 werase = ^W; // `^W`(Ctrl+W)用于删除单词 lnext = ^V; // `^V`(Ctrl+V)用于插入字面上的控制字符(取消转义控制字符) discard = ^O; // `^O`(Ctrl+O)用于丢弃字符(不处理的字符) min = 1; // 设置最小字符数为1,表示至少读取1个字符才能继续 time = 0; // 设置超时为0,表示没有超时限制 -parenb // 禁用奇偶校验位(parity check),不使用奇偶校验 -parodd // 禁用奇数奇偶校验 -cmspar // 禁用奇偶校验的标准模式(CMSPAR用于特定硬件) cs8 // 设置数据位为8位(数据位为8个比特) hupcl // 启用挂起时清除线路(当挂起时,自动关闭串口) -cstopb // 禁用2个停止位,使用1个停止位 cread // 启用读取数据 clocal // 启用本地连接,表示该串口会忽略一些线路状态信号(如DCD) -crtscts // 禁用硬件流控制(如RTS/CTS硬件流控制) 输入模式 -ignbrk: 启用时忽略断开信号(如挂断电话信号等),不做处理。 -brkint: 启用时接收到断开信号(如挂断电话信号)会中断程序。 -ignpar: 启用时,接收到具有错误奇偶校验的字符会被丢弃。 -parmrk: 启用时,接收到奇偶校验错误的字符会被标记为特殊字符(通常是某种转义字符),并且系统可能做出反应。 -inpck: 启用时,接收到的字符将不再进行奇偶校验检查。 -istrip: 启用时,接收到的每个字符的第 8 位会被去除(通常用于旧的串行协议或无符号字符的通信)。 -inlcr: 启用时,输入中的换行符(\n)(0x0A)不会被转换为回车符(\r)(0x0D),保留原样传递。 -igncr: 启用时,回车符(\r)会被忽略,不会传递给程序。 icrnl: 启用时,接收到的回车符(\r)会被转换为换行符(\n)。 ixon: 启用时,启用 XON/XOFF 流控制。接收端使用 XON(ASCII 17,Ctrl-Q, 0x11)和 XOFF(ASCII 19,Ctrl-S, 0x13)来控制数据流的暂停和恢复。 -ixoff: 启用时,启用 XOFF 流控制。与 ixon 配合使用,接收端通过发送 XOFF 来暂停数据传输,并等待 XON 来恢复数据流。 -iuclc: 启用时,所有输入字符都将以原始形式传递,不会自动转换成小写字母。 -ixany: 启用时,允许任何字符作为流控制符,通常用于自定义流控制字符。 -imaxbel: 启用时,接收到 BEL(响铃,ASCII 7)字符时会触发响铃或警告。 -iutf8: 启用时,禁用 UTF-8 编码字符的处理,串口通信将不支持 UTF-8 编码格式,而是使用传统字符编码格式。 输出模式 opost: 启用时,允许输出处理(例如处理换行符、回车符等)。 -olcuc: 启用时,将大写字母转换为小写字母(用于某些控制台模式)。 -ocrnl: 启用时,将输出中的换行符(\n)转换为回车符(\r)。 onlcr: 启用时,将输出中的换行符(\n)转换为回车换行符(\r\n),通常用于旧式终端。 -onocr: 启用时,回车符(\r)不会被输出到设备(即忽略回车符)。 -onlret: 启用时,输出的换行符(\n)会让设备进行回车操作,等同于输出换行时,光标回到行首。 -ofill: 启用时,输出时对填充字符(如0)进行处理(在某些协议中有用)。 -ofdel: 启用时,删除字符会被处理为“删除”操作(ASCII DEL字符)而不是其他字符。 nl0: 启用时,禁用换行符的处理,换行符不做任何转换。 cr0: 启用时,禁用回车符的处理,回车符不做任何转换。 tab0: 启用时,禁用制表符的处理,制表符不会做任何转换。 bs0: 启用时,禁用退格符的处理,退格符不会做任何转换。 vt0: 启用时,禁用垂直制表符(VT)的处理,垂直制表符不会做任何转换。 ff0: 启用时,禁用换页符(FF)的处理,换页符不会做任何转换。 本地模式 isig: 启用终端信号处理(如 SIGINT, SIGQUIT 等)。 icanon: 启用规范模式,允许行编辑,按下退格键删除字符。 iexten: 启用扩展输入字符处理,如支持 `Ctrl`+`D` 等功能。 echo: 启用输入回显,输入的字符会在终端上显示。 echoe: 启用回显删除字符功能,按下退格键时字符被删除并显示删除过程。 echok: 启用回显回车字符,当按下回车键时,回显相应字符。 -echonl: 禁用回显换行符,换行符不会显示在终端上。 -noflsh: 禁用输入输出缓冲区的刷新操作,不自动同步数据到终端。 -xcase: 禁用大小写转换,输入的字母不会自动转换大小写。 tostop: 启用后台进程输出停止功能,后台进程生成输出时会被暂停。 -echoprt: 禁用回显打印字符,输出的字符不会显示在终端。 echoctl: 启用回显控制字符,按下 `Ctrl`+`C` 等控制字符时会显示。 echoke: 启用回显字符删除时的效果,删除字符时会显示删除过程。 -flusho: 禁用数据的实时刷新,数据不会立即显示到终端。 -extproc: 禁用外部处理功能,外部处理程序无法影响串口通信。 */