【问题标题】:How to use a GPIO pin, for serial flow control with Qt?如何使用 GPIO 引脚进行 Qt 的串行流控制?
【发布时间】:2019-03-03 15:40:29
【问题描述】:

目标

在我的 Qt 应用程序中,我需要控制一个 GPIO 引脚,具体取决于通过串行总线发送的数据。因此,只要我传输数据,我就需要将其设置为 HIGH,并在传输结束后立即将其设置为 LOW。将其视为串行通信流控制引脚,设置为 1 时启用传输,设置为 0 时启用数据接收。整个系统是半双工的,以主从方式进行通信。

问题

我设法接近解决方案,通过在任何传输之前立即将其设置为 HIGH,根据波特率引入一些恒定延迟(我使用 QThread:usleep()),然后再次将其设置为低,但我得到了当我用示波器对其进行可视化时,脉冲的随机“拉伸”(保持比它应该更长的时间)。

尝试的解决方案

好吧,除了我手动定义的延迟之外,似乎发生了一些“魔法”,这增加了一些额外的延迟。为了摆脱这种可能性,我使用了bytesWritten() 信号,所以当我们完成将实际数据写入端口时,我可以触发我的setPinLow() 插槽。所以我的代码现在看起来像这样:

classTTY::classTTY(/*someStuff*/) : port(/*some other stuff*/)
{
    s_port = new QSerialPort();
    connect(s_port, SIGNAL(bytesWritten(qint64)), this, SLOT(setPinLow()));

    if(GPIOPin->open(QFile::ReadWrite | QFile::Truncate | QFile::Text | QFile::Unbuffered)) {
        qDebug() << "GPIO pin ready to switch.";
    } else {
        qDebug() << "Failed to access GPIO pin";
    }

bool classTTY::sendData(data, replyLength)
{
    directionPinEnable(true);

    if(m_port->isOpen()) {
        s_expectedReplyLength = replyLength;
        s_receivedData.clear();

        s_port->flush();
        s_port->write(data);

        return true;
    }

return false;
}

void classTTY::setPinLow()
{
    gpioPinEnable(false);
}

void classTTY::gpioPinEnable(bool enable){

    if(enable == true){
        GPIOPin->write("1");
    } else if (enable == false) {
        GPIOPin->write("0");
    }
}

在实现它之后,引脚开始发出非常短的脉冲,更像是“尖峰”,这意味着(我认为)现在只要 Qt write() 进程持续,它就会保持高电平,而不是实际数据的传播持续。

问题

  1. 当我使用 naive 时添加的额外延迟是什么, QThread::usleep 接近,导致脉搏拉长?
  2. 为什么信号槽方法不起作用,因为它是 事件驱动?
  3. 一般来说,我怎样才能指示引脚仅在 传输数据然后再次降为零,所以我可以接收 奴隶的回复?

【问题讨论】:

  • 可能是因为您使用的是正常的操作系统并且日程安排会妨碍您。如果您需要较高的计时精度,请尝试使用实时操作系统
  • 我不知道 Qt 的工作原理,但我的猜测是使用 QThread::usleep 允许其他线程运行,所以你在你的线程上获得睡眠加上另一个线程上的一些运行时在你回到你的之前。
  • 因为Linux不是RTOS,而UART串口数据可以包含字符间空间,不仅会拉长你的延迟,还会导致串口突发,所以你的方法有严重缺陷。 GPIO 控制应在串行驱动程序级别执行,当输入缓冲区和 UART FIFO/TX 变空时将其删除。但这似乎没有必要;这是主从关系。奴隶在被询问之前不应该回复,并且主人将等待(有一些适当的超时)该回复。数据本身已经是流控了。
  • @Clifford “但这似乎没有必要;这是主从关系。”确切地。这也是我尝试第二种方法的原因。写入所有字节后,将方向引脚设置回 0(这样我们现在可以接收从机的回复)。 TBH,我真的看不出这不起作用的原因......
  • 使用延迟是有问题的。 Qt 是否有与tcdrain() function 等价的东西?

标签: qt embedded embedded-linux gpio


【解决方案1】:
  1. 当我使用朴素的 QThread::usleep 方法时添加的额外延迟是什么,会导致脉冲延长?

Linux 不是实时操作系统,线程休眠会在不少于指定的时间暂停进程。在睡眠期间,其他线程和进程可能会运行并且可能不会在比您的睡眠周期更长的时间内让处理器,或者可能根本不会让处理器并消耗它们整个操作系统分配的时间片。除此之外,内核驱动程序中断处理程序将始终抢占用户级进程。 Linus 有一个实时调度的构建选项,但保证仍然不如真正的 RTOS 强大,延迟通常更差。

还要注意,不仅你的线程可以被挂起的时间超过睡眠时间,而且传输可能会超过波特率的位数 - 内核驱动程序可以被其他驱动程序抢占并引入inter -您无法控制的字符间隙。

  1. 为什么信号槽方法不起作用,因为它是事件驱动的?

QSerialPort::waitForBytesWritten() 的文档指出:

此函数会一直阻塞,直到至少一个字节已写入串行端口并发出 bytesWritten() 信号。

因此很明显,其语义是“some data has beenwritten”而不是“all data已经写好了”。每当写入一个字节时它就会返回,然后如果您再次调用它,如果继续写入字节,它可能会立即返回(因为QSerialPort 被缓冲并且将独立于您的应用程序写入数据)。

  1. 一般情况下,我如何才能指示引脚仅在数据传输期间变为活动状态,然后再次降至零,以便接收从机的回复?

不幸的是,Qt 不是答案;这种行为需要在串行端口内核驱动程序中实现,或者至少在 Qt 的较低级别实现。 Qt QSerialPort 抽象不会为您提供所需的“在线”实际发生的控制级别或洞察力。它与硬件有点距离 - 有充分的理由。

但是有一个简单的解决方案 - 不要打扰!这似乎完全没有必要。它是一种主从通信,因此数据本身流控制的。奴隶在与它交谈之前不会说话,主人必须在它说话后期待并等待回复。为什么奴隶需要任何许可才能说话,而不是被说话所暗示的许可?

【讨论】:

  • “内核驱动程序可以被其他驱动程序抢占并引入字符间间隙” -- 在使用 DMA 时不太可能。 “为什么从机需要任何权限才能说话,而不是被说话所暗示的?” -- 也许这个 GPIO 控制(例如 RS-485)收发器?
  • @sawdust :并非所有嵌入式 Linux 实现都将 DMA 用于串行 I/O。我使用 Atmel 的 Linux 发行版在 Atmel ARM 上进行了一个项目 - 在版本之间,串行驱动程序从启用 DMA 莫名其妙地更改为中断驱动,并且我们的应用程序不再支持我们使用的 230400 波特。点虽然;如果启用了 DMA,则不太可能。重点仍然是它不是 RTOS,因此不是完全确定的,因此应用程序级延迟不太可能与传输突发长度匹配,原因有多种不同的可能性和影响。
  • “我在 Atmel ARM 上使用 Atmel 的 Linux 进行了一个项目”——我想我知道你指的是什么。设备树中有遗漏。 “重点仍然是它不是 RTOS” -- 只要要传输的消息是在单个系统调用中编写的,并且涉及到合理的波特率,那么字符间空闲时间是不可能的。缺少 RTOS 并不是问题的根本原因。使用延迟或休眠是与传输结束同步的错误方法。
  • @sawdust :我不认为我们不同意 - 提到 RTOS 只是为了指出您不能期望 Linux 具有确定性,而不是因为使用 RTOS 是解决方案。这是对为什么它不起作用的解释的一部分,而不是建议的解决方案。而且,它是在睡眠延迟的上下文中提到的,而不是字符间空间。即使在 RTOS 中,这也是一种糟糕的方法,但如果给予适当的优先级分配,它可能会起作用。
  • @sawdust “也许这个 GPIO 控制(例如 RS-485)收发器?”牛眼。从机不需要说话的权限,但我需要将 GPIO 引脚设置为 0 才能听他讲话。克利福德,你说得对,我需要更接近硬件。修改串口驱动是唯一的办法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-03
  • 1970-01-01
  • 1970-01-01
  • 2021-03-11
  • 1970-01-01
相关资源
最近更新 更多