718 lines
32 KiB
C++
718 lines
32 KiB
C++
#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 流控制。接收端使用 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<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 流控制。接收端使用 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: 禁用外部处理功能,外部处理程序无法影响串口通信。
|
||
*/ |