【问题标题】:How do i set up termios parameters in C++ for a Raspberry PI USB UART connection?如何在 C++ 中为 Raspberry PI USB UART 连接设置 termios 参数?
【发布时间】:2020-07-05 01:29:00
【问题描述】:

我有一个 RPI 3 和一个 LoRa USB 模块,我正在用 C++ 编写一些代码来连接设备的端口。我能够连接到端口(在 udev 规则中分配为 ttyUSBPort1)。但是,当我向端口发送数据时,出现错误。我只是对 termios 和端口通信知之甚少,无法确定这是否是问题所在(是的,我已阅读手册页)。

LoRa 模块是一个 RN2903 设备,以下是参考表上的 UART 接口说明:

RN2903 模块的所有设置和命令均使用 UART 通过 UART 传输 ASCII 接口。 所有命令都需要以 (为格式化添加空格) 终止,并且它们生成的任何回复都将 也被相同的序列终止。 UART 接口的默认设置为 57600 bps,8 位,无奇偶校验,1 个停止位, 没有流量控制。

发送命令时,我可以通过监控端口看到设备正在响应“invalid_parameter”

sudo cat /dev/ttyUSBPort1

我假设我的某些 termios 标志设置不正确,或者写入命令设置不正确。这是我设置端口的代码:

int openPort(void) {
struct termios tty;
memset(&tty, 0, sizeof tty);
if ((usb_port = open(device, O_RDWR))>=0) {// | O_NOCTTY | O_SYNC
    std::cout << "DEVICE OPENED: " << device << " handle number: " << usb_port << std::endl;
} else {
    fprintf(stderr, "unable to open serial device");
    return -1;
}
if(tcgetattr(usb_port, &tty) != 0) {
    printf("Error %i \n", errno);
}
cfsetispeed(&tty, B57600);
cfsetospeed(&tty, B57600);

tty.c_cflag     &=  ~PARENB;            // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;

tty.c_cflag     &=  ~CRTSCTS;           // no flow control
tty.c_cc[VMIN]   =  0;                  // read doesn't block
tty.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout

tcflush( usb_port, TCIFLUSH );
if (tcsetattr(usb_port, TCSANOW, &tty) != 0) {
    printf("Error %i\n", errno);
}
return usb_port;
}

这是从设备获取版本信息的调用命令:

void radioCMD(string tmp) {
string tmp2 = tmp + "\r\n";
int n = tmp2.length();
char cmd[n];
strcpy(cmd, tmp2.c_str());
std::cout << write(usb_port, cmd, sizeof(cmd)) << " " << cmd << "Writing to " << usb_port << " Delay: " << delay << " Command Size: " << sizeof(cmd) << std::endl;
}
void setupRadio() {
radioCMD("sys get ver");
usleep(delay);
}

当写入控制台 std::cout 时,我看到了这个:

13 sys get ver
Writing to 3 Delay: 200000 Command Size: 13

表明消息确实被正确写入。

设备的 cat 输出应以如下内容响应(来自数据表):

2.3.6.1 系统获取版本 响应:RN2903 X.Y.Z MMM DD YYYY HH:MM:SS,其中 X.Y.Z 是固件 版本,MMM 为月,DD 为日,HH:MM:SS 为时、分、秒(格式:[HW] [FW] [日期] [时间])。 [日期] 和 [时间] 是指固件的发布。 该命令返回与硬件平台、固件相关的信息 固件创建的版本、发布日期和时间戳。 示例:sys 获取版本

我实际得到的是“invalid_param\r\n”,如果调用中的某些内容不正确,这是设备的适当响应。

有什么想法我可能会在这里出错吗?

编辑

感谢 Ted 为我指明了正确的方向并简化了我的代码。缺少两个 termios 标志。一旦我设置了这些(最后两个),它就可以正常工作了。

tty.c_cflag     &=  ~PARENB;            // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;
tty.c_cflag     &=  ~CRTSCTS;           // no flow control
tty.c_cc[VMIN]   =  0;                  // read doesn't block
tty.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout
        ***ADDITIONAL TWO FLAGS THAT FIXED IT****
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
tty.c_oflag &= ~OCRNL; // Prevent conversion of newline to carriage return/line feed

新的写调用函数:

void radioCMD(string cmd) {
    cmd += "\r\n";
    write(usb_port, cmd.c_str(), cmd.size());
}

【问题讨论】:

  • 在开始设置之前,您是否检查过打开设备时默认获得的模式不起作用?建议:发送时去掉这部分:string tmp2 = tmp + "\r\n"; int n = tmp2.length(); char cmd[n]; strcpy(cmd, tmp2.c_str());。而是使用tmp += "\r\n";,然后在write 调用中直接使用tmp.c_str()tmp.size()
  • 谢谢特德。您推荐的更改与一些不同的 termios 标志一起起作用。
  • “一旦我设置了这些(最后两个),它就可以正常工作了” -- 你的程序不可靠,因为它依赖于要设置的附加 termios 属性,即 ICANON 和 OPOST .为了可靠运行,您的程序应配置它需要的所有相关 termios 属性。做一个简单的测试:在运行程序之前发出命令stty raw -F /dev/ttyUSBPort1。顺便说一句,使用sudo 访问终端设备是不明智的,应该是不必要的。 VMIN 和 VTIME 不用于规范模式。

标签: c++ raspberry-pi usb uart lora


【解决方案1】:

这会创建一个VLA,它是一个太短的char,无法容纳C 字符串的空终止符:

void radioCMD(string tmp) {
    string tmp2 = tmp + "\r\n";
    int n = tmp2.length();
    char cmd[n];                       // should be n+1 for strcpy to work
    strcpy(cmd, tmp2.c_str());         // undefined behavior \0 is written out of bounds
    write(usb_port, cmd, sizeof(cmd)); // sizeof(cmd) should be replaced by n
}

更好的选择是使用std::memcpy 而不是std::strcpy 来复制C 字符串没有空终止符 - 并避免VLA:s。

更好的选择是直接使用std::string 作为参数:

void radioCMD(string cmd) {
    cmd += "\r\n";
    write(usb_port, cmd.c_str(), cmd.size());
}

这可能不是唯一的问题,但由于 std::strcpy 当前使您的程序具有未定义的行为,因此这是一个很好的起点。

【讨论】:

  • Ted,你的 void radioCMD 函数有效,但作为一个参考,以防其他人遇到这个问题,我不得不添加两个额外的 tty termios 标志(但你让我在正确的轨道上思考CRLF。我将编辑原始帖子以添加 termios 标志。
  • @Max 太好了!也许最好将| 的所有 I/O 和控制标志从0 集中在一起。 :-)
猜你喜欢
  • 2013-06-19
  • 2015-12-17
  • 2018-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多