Files
CPP_Core/Core/Serial/Serial_linux.cpp
T
2026-06-16 10:56:40 +08:00

718 lines
32 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <thread>
#ifdef __linux__
#include "Serial_p.h"
#include <map>
#include <utility>
#include <linux/serial.h>
#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 流控制。接收端使用 XONASCII 17Ctrl-Q, 0x11)和 XOFFASCII 19Ctrl-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<std::pair<int, Baud_Rate_Type>> 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<int, Baud_Rate_Type> map = []() {
std::map<int, Baud_Rate_Type> 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<Baud_Rate_Type> value_to_baud(const long baud_rate) {
static std::map<int, Baud_Rate_Type> map = []() {
std::map<int, Baud_Rate_Type> 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 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(const std::string& serial_name) {
// 打开串口设备 O_RDWR | O_NOCTTY | O_CLOEXEC
Serial_FD fd = ::open(serial_name.c_str(), O_RDWR | O_NOCTTY | O_CLOEXEC); // O_RDWR: 可读可写, O_NOCTTY: 不设置为控制终端, O_NDELAY: 非阻塞
if (fd == -1) {
LOG_ERROR(VAR_STR_1(serial_name) + "串口打开失败!")
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<int>(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 = <undef>; // 结束符(end-of-line)未定义(没有设置)
eol2 = <undef>; // 第二个结束符(eol2)未定义(没有设置)
swtch = <undef>; // 切换字符未定义(没有设置)
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 流控制。接收端使用 XONASCII 17Ctrl-Q, 0x11)和 XOFFASCII 19Ctrl-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: 禁用外部处理功能,外部处理程序无法影响串口通信。
*/