libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
linux_uart.hpp
1#pragma once
2
3#define termios asmtermios
4#include <asm/termbits.h>
5#undef termios
6#include <fcntl.h>
7#include <libudev.h>
8#include <linux/serial.h>
9#include <sys/ioctl.h>
10#include <sys/select.h>
11#include <termios.h>
12#include <unistd.h>
13
14#include <algorithm>
15#include <cstddef>
16#include <cstdint>
17#include <filesystem>
18#include <system_error>
19#include <vector>
20
21#include "libxr_def.hpp"
22#include "libxr_rw.hpp"
23#include "semaphore.hpp"
24#include "uart.hpp"
25
26namespace LibXR
27{
36class LinuxUART : public UART
37{
38 public:
42 LinuxUART(const char* dev_path, unsigned int baudrate = 115200,
43 Parity parity = Parity::NO_PARITY, uint8_t data_bits = 8,
44 uint8_t stop_bits = 1, uint32_t tx_queue_size = 5, size_t buffer_size = 512,
45 size_t thread_stack_size = 65536)
46 : UART(&_read_port, &_write_port),
47 rx_buff_(new uint8_t[buffer_size]),
48 tx_buff_(new uint8_t[buffer_size]),
49 buff_size_(buffer_size),
50 _read_port(buffer_size),
51 _write_port(tx_queue_size, buffer_size)
52 {
53 ASSERT(buff_size_ > 0);
54
55 while (!std::filesystem::exists(dev_path))
56 {
57 XR_LOG_WARN("Cannot find UART device: %s, retrying...", dev_path);
58 Thread::Sleep(100);
59 }
60
61 device_path_ = GetByPathForTTY(dev_path);
62
63 fd_ = open(device_path_.c_str(), O_RDWR | O_NOCTTY);
64 if (fd_ < 0)
65 {
66 XR_LOG_ERROR("Cannot open UART device: %s", device_path_.c_str());
67 ASSERT(false);
68 }
69 else
70 {
71 XR_LOG_PASS("Open UART device: %s", device_path_.c_str());
72 }
73
74 config_ = {};
75 config_.baudrate = baudrate;
76 config_.parity = parity;
77 config_.data_bits = data_bits;
78 config_.stop_bits = stop_bits;
79
80 SetConfig(config_);
81
82 _read_port = ReadFun;
83 _write_port = WriteFun;
84
85 rx_thread_.Create<LinuxUART*>(
86 this, [](LinuxUART* self) { self->RxLoop(); }, "rx_uart", thread_stack_size,
88
89 tx_thread_.Create<LinuxUART*>(
90 this, [](LinuxUART* self) { self->TxLoop(); }, "tx_uart", thread_stack_size,
92 }
93
97 LinuxUART(const std::string& vid, const std::string& pid,
98 unsigned int baudrate = 115200, Parity parity = Parity::NO_PARITY,
99 uint8_t data_bits = 8, uint8_t stop_bits = 1, uint32_t tx_queue_size = 5,
100 size_t buffer_size = 512, size_t thread_stack_size = 65536)
101 : LinuxUART(vid, pid, "", "", baudrate, parity, data_bits, stop_bits, tx_queue_size,
102 buffer_size, thread_stack_size)
103 {
104 }
105
116 LinuxUART(const std::string& vid, const std::string& pid,
117 const std::string& control_interface_name, unsigned int baudrate = 115200,
118 Parity parity = Parity::NO_PARITY, uint8_t data_bits = 8,
119 uint8_t stop_bits = 1, uint32_t tx_queue_size = 5, size_t buffer_size = 512,
120 size_t thread_stack_size = 65536)
121 : LinuxUART(vid, pid, control_interface_name, "", baudrate, parity, data_bits,
122 stop_bits, tx_queue_size, buffer_size, thread_stack_size)
123 {
124 }
125
136 LinuxUART(const std::string& vid, const std::string& pid,
137 const std::string& control_interface_name, const std::string& serial,
138 unsigned int baudrate = 115200, Parity parity = Parity::NO_PARITY,
139 uint8_t data_bits = 8, uint8_t stop_bits = 1, uint32_t tx_queue_size = 5,
140 size_t buffer_size = 512, size_t thread_stack_size = 65536)
141 : UART(&_read_port, &_write_port),
142 rx_buff_(new uint8_t[buffer_size]),
143 tx_buff_(new uint8_t[buffer_size]),
144 buff_size_(buffer_size),
145 _read_port(buffer_size),
146 _write_port(tx_queue_size, buffer_size)
147 {
148 while (!FindUSBTTYByVidPid(vid, pid, control_interface_name, serial, device_path_))
149 {
150 XR_LOG_WARN(
151 "Cannot find USB TTY device with VID=%s PID=%s SERIAL=%s CONTROL_INTERFACE=%s, "
152 "retrying...",
153 vid.c_str(), pid.c_str(), serial.empty() ? "*" : serial.c_str(),
154 control_interface_name.empty() ? "*" : control_interface_name.c_str());
155 Thread::Sleep(100);
156 }
157
158 XR_LOG_PASS("Found USB TTY: %s", device_path_.c_str());
159
160 if (std::filesystem::exists(device_path_) == false)
161 {
162 XR_LOG_ERROR("Cannot find UART device: %s", device_path_.c_str());
163 ASSERT(false);
164 return;
165 }
166
167 device_path_ = GetByPathForTTY(device_path_);
168
169 fd_ = open(device_path_.c_str(), O_RDWR | O_NOCTTY);
170 if (fd_ < 0)
171 {
172 XR_LOG_ERROR("Cannot open UART device: %s", device_path_.c_str());
173 ASSERT(false);
174 }
175 else
176 {
177 XR_LOG_PASS("Open UART device: %s", device_path_.c_str());
178 }
179
180 config_ = {};
181 config_.baudrate = baudrate;
182 config_.parity = parity;
183 config_.data_bits = data_bits;
184 config_.stop_bits = stop_bits;
185
186 SetConfig(config_);
187
188 _read_port = ReadFun;
189 _write_port = WriteFun;
190
191 rx_thread_.Create<LinuxUART*>(
192 this, [](LinuxUART* self) { self->RxLoop(); }, "rx_uart", thread_stack_size,
194
195 tx_thread_.Create<LinuxUART*>(
196 this, [](LinuxUART* self) { self->TxLoop(); }, "tx_uart", thread_stack_size,
198 }
199
200 std::string GetByPathForTTY(const std::string& tty_name)
201 {
202 const std::string BASE = "/dev/serial/by-path";
203 if (strncmp(tty_name.c_str(), BASE.c_str(), BASE.length()) == 0 ||
204 !std::filesystem::exists(BASE))
205 {
206 return tty_name;
207 }
208 for (const auto& entry : std::filesystem::directory_iterator(BASE))
209 {
210 std::error_code ec;
211 const auto full = std::filesystem::canonical(entry.path(), ec);
212 if (ec)
213 {
214 continue;
215 }
216 if (full == tty_name)
217 {
218 return entry.path().string(); // 返回符号链接路径
219 }
220 }
221 return tty_name; // 未命中 by-path 时保留原始 tty 路径
222 }
223
224 static bool FindUSBTTYByVidPid(const std::string& target_vid,
225 const std::string& target_pid, std::string& tty_path)
226 {
227 return FindUSBTTYByVidPid(target_vid, target_pid, "", "", tty_path);
228 }
229
230 static bool FindUSBTTYByVidPid(const std::string& target_vid,
231 const std::string& target_pid,
232 const std::string& target_control_interface_name,
233 std::string& tty_path)
234 {
235 return FindUSBTTYByVidPid(target_vid, target_pid, target_control_interface_name, "",
236 tty_path);
237 }
238
239 static bool FindUSBTTYByVidPid(const std::string& target_vid,
240 const std::string& target_pid,
241 const std::string& target_control_interface_name,
242 const std::string& target_serial, std::string& tty_path)
243 {
244 struct udev* udev = udev_new();
245 if (!udev)
246 {
247 XR_LOG_ERROR("Cannot create udev context");
248 return false;
249 }
250
251 struct udev_enumerate* enumerate = udev_enumerate_new(udev);
252 udev_enumerate_add_match_subsystem(enumerate, "tty");
253 udev_enumerate_scan_devices(enumerate);
254
255 struct udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate);
256 struct udev_list_entry* entry = nullptr;
257 std::vector<std::string> matches;
258
259 udev_list_entry_foreach(entry, devices)
260 {
261 const char* path = udev_list_entry_get_name(entry);
262 struct udev_device* tty_dev = udev_device_new_from_syspath(udev, path);
263 if (!tty_dev)
264 {
265 continue;
266 }
267
268 struct udev_device* usb_dev =
269 udev_device_get_parent_with_subsystem_devtype(tty_dev, "usb", "usb_device");
270 struct udev_device* usb_interface =
271 udev_device_get_parent_with_subsystem_devtype(tty_dev, "usb", "usb_interface");
272
273 if (usb_dev)
274 {
275 const char* vid = udev_device_get_sysattr_value(usb_dev, "idVendor");
276 const char* pid = udev_device_get_sysattr_value(usb_dev, "idProduct");
277 const char* serial = udev_device_get_sysattr_value(usb_dev, "serial");
278 const char* control_interface_name = nullptr;
279 if (usb_interface)
280 {
281 // For Linux cdc_acm tty nodes this parent is the CDC control interface
282 // (for example if00 / if02), not the CDC data interface.
283 control_interface_name =
284 udev_device_get_sysattr_value(usb_interface, "interface");
285 }
286
287 if (vid && pid && target_vid == vid && target_pid == pid &&
288 (target_serial.empty() || (serial && target_serial == serial)) &&
289 (target_control_interface_name.empty() ||
290 (control_interface_name &&
291 target_control_interface_name == control_interface_name)))
292 {
293 const char* devnode = udev_device_get_devnode(tty_dev);
294 if (devnode)
295 {
296 matches.emplace_back(devnode);
297 }
298 }
299 }
300
301 udev_device_unref(tty_dev);
302 }
303
304 udev_enumerate_unref(enumerate);
305 udev_unref(udev);
306 if (matches.empty())
307 {
308 return false;
309 }
310
311 std::sort(matches.begin(), matches.end());
312 tty_path = matches.front();
313
314 if (matches.size() > 1)
315 {
316 XR_LOG_WARN(
317 "Multiple USB TTY devices found with VID=%s PID=%s SERIAL=%s "
318 "CONTROL_INTERFACE=%s, using %s. Specify serial or control interface name to "
319 "disambiguate.",
320 target_vid.c_str(), target_pid.c_str(),
321 target_serial.empty() ? "*" : target_serial.c_str(),
322 target_control_interface_name.empty() ? "*"
323 : target_control_interface_name.c_str(),
324 tty_path.c_str());
325 }
326
327 return true;
328 }
329
330 void SetLowLatency(int fd)
331 {
332 struct serial_struct serinfo;
333 ioctl(fd, TIOCGSERIAL, &serinfo);
334 serinfo.flags |= ASYNC_LOW_LATENCY;
335 ioctl(fd, TIOCSSERIAL, &serinfo);
336 }
337
339 {
340 if (&config != &config_)
341 {
342 config_ = config;
343 }
344
345 struct termios2 tio{};
346 if (ioctl(fd_, TCGETS2, &tio) != 0)
347 {
348 return ErrorCode::INIT_ERR;
349 }
350
351 // 设置自定义波特率
352 tio.c_cflag &= ~CBAUD;
353 tio.c_cflag |= BOTHER;
354 tio.c_ispeed = config.baudrate;
355 tio.c_ospeed = config.baudrate;
356
357 // 输入模式:关闭软件流控、特殊字符处理
358 tio.c_iflag &= ~(IXON | IXOFF | IXANY | ISTRIP | IGNCR | INLCR | ICRNL
359#ifdef IUCLC
360 | IUCLC
361#endif
362 );
363
364 // 输出模式:关闭所有加工
365 tio.c_oflag &= ~(OPOST
366#ifdef ONLCR
367 | ONLCR
368#endif
369#ifdef OCRNL
370 | OCRNL
371#endif
372#ifdef ONOCR
373 | ONOCR
374#endif
375#ifdef ONLRET
376 | ONLRET
377#endif
378 );
379
380 // 本地模式:禁用行缓冲、回显、信号中断
381 tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
382
383 // 控制模式:设置数据位、校验、停止位、流控
384 tio.c_cflag &= ~CSIZE;
385 switch (config.data_bits)
386 {
387 case 5:
388 tio.c_cflag |= CS5;
389 break;
390 case 6:
391 tio.c_cflag |= CS6;
392 break;
393 case 7:
394 tio.c_cflag |= CS7;
395 break;
396 case 8:
397 tio.c_cflag |= CS8;
398 break;
399 default:
400 return ErrorCode::ARG_ERR;
401 }
402
403 // 停止位
404 tio.c_cflag &= ~CSTOPB;
405 if (config.stop_bits == 2)
406 {
407 tio.c_cflag |= CSTOPB;
408 }
409
410 // 奇偶校验
411 switch (config.parity)
412 {
414 tio.c_cflag &= ~PARENB;
415 break;
417 tio.c_cflag |= PARENB;
418 tio.c_cflag &= ~PARODD;
419 break;
421 tio.c_cflag |= PARENB;
422 tio.c_cflag |= PARODD;
423 break;
424 }
425
426 // 禁用硬件流控
427 tio.c_cflag &= ~CRTSCTS;
428
429 // 启用本地模式、读功能
430 tio.c_cflag |= (CLOCAL | CREAD);
431
432 // 控制字符配置:阻塞直到读到 1 字节
433 // for (int i = 0; i < NCCS; ++i) tio.c_cc[i] = 0;
434 tio.c_cc[VTIME] = 0;
435 tio.c_cc[VMIN] = 1;
436
437 if (ioctl(fd_, TCSETS2, &tio) != 0)
438 {
439 return ErrorCode::INIT_ERR;
440 }
441
442 SetLowLatency(fd_);
443
444 tcflush(fd_, TCIOFLUSH);
445
446 return ErrorCode::OK;
447 }
448
449 static ErrorCode ReadFun(ReadPort&, bool) { return ErrorCode::PENDING; }
450
451 static ErrorCode WriteFun(WritePort& port, bool)
452 {
453 auto* uart = LibXR::ContainerOf(&port, &LinuxUART::_write_port);
454 uart->write_sem_.Post();
455 return ErrorCode::PENDING;
456 }
457
458 private:
459 void RxLoop()
460 {
461 while (true)
462 {
463 if (!connected_)
464 {
465 close(fd_);
466 fd_ = open(device_path_.c_str(), O_RDWR | O_NOCTTY);
467
468 if (fd_ < 0)
469 {
470 XR_LOG_WARN("Cannot open UART device: %s", device_path_.c_str());
471 Thread::Sleep(1000);
472 }
473 else
474 {
475 SetConfig(config_);
476 XR_LOG_PASS("Reopen UART device: %s", device_path_.c_str());
477 connected_ = true;
478 }
479 }
480 auto n = read(fd_, rx_buff_, buff_size_);
481 if (n > 0)
482 {
483 read_port_->queue_data_->PushBatch(rx_buff_, n);
485 }
486 else
487 {
488 XR_LOG_WARN("Cannot read UART device: %s", device_path_.c_str());
489 connected_ = false;
490 }
491 }
492 }
493
494 void TxLoop()
495 {
496 WriteInfoBlock info;
497 while (true)
498 {
499 if (!connected_)
500 {
501 Thread::Sleep(1);
502 continue;
503 }
504
505 if (write_sem_.Wait() != ErrorCode::OK)
506 {
507 continue;
508 }
509
510 if (write_port_->queue_info_->Pop(info) == ErrorCode::OK)
511 {
512 if (write_port_->queue_data_->PopBatch(tx_buff_, info.data.size_) ==
514 {
515 auto written = write(fd_, tx_buff_, info.data.size_);
516 if (written < 0)
517 {
518 XR_LOG_WARN("Cannot write UART device: %s", device_path_.c_str());
519 connected_ = false;
520 }
521 write_port_->Finish(false,
522 (written == static_cast<int>(info.data.size_))
525 info);
526 }
527 else
528 {
529 info.op.UpdateStatus(false, ErrorCode::FAILED);
530 }
531 }
532 }
533 }
534
535 int fd_ = -1;
536 bool connected_ = true;
537 Configuration config_;
538 std::string device_path_;
539 Thread rx_thread_;
540 Thread tx_thread_;
541 uint8_t* rx_buff_ = nullptr;
542 uint8_t* tx_buff_ = nullptr;
543 size_t buff_size_ = 0;
544 Semaphore write_sem_;
545 Mutex read_mutex_;
546
547 ReadPort _read_port; // NOLINT
548 WritePort _write_port; // NOLINT
549};
550
551} // namespace LibXR
Linux UART 串口驱动实现 / Linux UART driver implementation.
ErrorCode SetConfig(UART::Configuration config) override
设置 UART 配置 / Sets the UART configuration
LinuxUART(const char *dev_path, unsigned int baudrate=115200, Parity parity=Parity::NO_PARITY, uint8_t data_bits=8, uint8_t stop_bits=1, uint32_t tx_queue_size=5, size_t buffer_size=512, size_t thread_stack_size=65536)
通过设备路径构造 UART / Construct UART by device path
LinuxUART(const std::string &vid, const std::string &pid, const std::string &control_interface_name, const std::string &serial, unsigned int baudrate=115200, Parity parity=Parity::NO_PARITY, uint8_t data_bits=8, uint8_t stop_bits=1, uint32_t tx_queue_size=5, size_t buffer_size=512, size_t thread_stack_size=65536)
通过 USB VID/PID/control interface name/serial 构造 UART Construct UART by USB VID/PID/control interface ...
LinuxUART(const std::string &vid, const std::string &pid, const std::string &control_interface_name, unsigned int baudrate=115200, Parity parity=Parity::NO_PARITY, uint8_t data_bits=8, uint8_t stop_bits=1, uint32_t tx_queue_size=5, size_t buffer_size=512, size_t thread_stack_size=65536)
通过 USB VID/PID/control interface name 构造 UART Construct UART by USB VID/PID/control interface name
LinuxUART(const std::string &vid, const std::string &pid, unsigned int baudrate=115200, Parity parity=Parity::NO_PARITY, uint8_t data_bits=8, uint8_t stop_bits=1, uint32_t tx_queue_size=5, size_t buffer_size=512, size_t thread_stack_size=65536)
通过 USB VID/PID 构造 UART / Construct UART by USB VID/PID
ReadPort class for handling read operations.
Definition read_port.hpp:18
SPSCQueue< uint8_t > * queue_data_
RX payload queue. 接收数据字节队列。
Definition read_port.hpp:55
void ProcessPendingReads(bool in_isr)
Processes pending reads.
ErrorCode PopBatch(Data *data, size_t size)
批量弹出多个 payload。
ErrorCode PushBatch(const Data *data, size_t size)
批量推入多个 payload。
ErrorCode Wait(uint32_t timeout=UINT32_MAX)
等待(减少)信号量 Waits (decrements) the semaphore
Definition semaphore.cpp:52
@ REALTIME
实时优先级 Realtime priority
void Create(ArgType arg, void(*function)(ArgType arg), const char *name, size_t stack_depth, Thread::Priority priority)
创建新线程 Creates a new thread
Definition thread.hpp:68
static void Sleep(uint32_t milliseconds)
让线程进入休眠状态 Puts the thread to sleep
Definition thread.cpp:15
通用异步收发传输(UART)基类 / Abstract base class for Universal Asynchronous Receiver-Transmitter (UART)
Definition uart.hpp:19
ReadPort * read_port_
读取端口 / Read port
Definition uart.hpp:53
Parity
奇偶校验模式 / Parity mode
Definition uart.hpp:29
@ NO_PARITY
无校验 / No parity
@ ODD
奇校验 / Odd parity
@ EVEN
偶校验 / Even parity
WritePort * write_port_
写入端口 / Write port
Definition uart.hpp:54
void Finish(bool in_isr, ErrorCode ans, WriteInfoBlock &info)
更新写入操作的状态。 Updates the status of the write operation.
SPSCQueue< WriteInfoBlock > * queue_info_
Metadata queue for pending write batches. 挂起写批次的元数据队列。
SPSCQueue< uint8_t > * queue_data_
Payload queue for pending write bytes. 挂起写入字节的数据队列。
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ INIT_ERR
初始化错误 | Initialization error
@ FAILED
操作失败 | Operation failed
@ PENDING
等待中 | Pending
@ OK
操作成功 | Operation successful
@ ARG_ERR
参数错误 | Argument error
ErrorCode(* ReadFun)(ReadPort &port, bool in_isr)
Function pointer type for read notifications.
ErrorCode(* WriteFun)(WritePort &port, bool in_isr)
Function pointer type for write operations.
OwnerType * ContainerOf(MemberType *ptr, MemberType OwnerType::*member) noexcept
通过成员指针恢复其所属对象指针
UART 配置结构体 / UART configuration structure.
Definition uart.hpp:44
uint8_t stop_bits
停止位长度 / Number of stop bits
Definition uart.hpp:50
Parity parity
校验模式 / Parity mode
Definition uart.hpp:47
uint8_t data_bits
数据位长度 / Number of data bits
Definition uart.hpp:48
uint32_t baudrate
波特率 / Baud rate
Definition uart.hpp:45