Search code examples
qtembeddedembedded-linuxgpio

How to use a GPIO pin, for serial flow control with Qt?


THE GOAL

In my Qt application, I need to control a GPIO pin, depending on data being sent over the serial bus. So, I need to set it to HIGH for as long as I transmit data, and to LOW, immediately after the transmission ends. Consider it as a serial communication flow control pin, which when set to 1 it enables transmission, and when set to 0 enables receive of data. The entire system is half-duplex and communicates in a master-slave fashion.

THE PROBLEM

I managed to come close to a solution, by setting it to HIGH immediately before any transmission, introducing some constant delay (I used QThread:usleep() ) depending on the baud rate and then setting it to low again, but I was getting random "stretchings" of the pulse (staying HIGH longer than it should) when I was visualizing it with an oscilloscope.

ATTEMPTED SOLUTIONS

Well, it seems that some "magic" is taking place, which adds some extra delay, on top of the one I have manually defined. In order to get rid of that possibility, I used the bytesWritten() signal, so I can fire my setPinLow() slot when we finish writing the actual data to the port. So my code now looks like this:

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");
    }
}

After implementing it the pin started to give really short pulses, much more like "spikes", which implies (I think) that now it stays HIGH for as long as the Qt write() process lasts, and not while the actual propagation of the data lasts.

THE QUESTION(S)

  1. What is that extra delay being added when I use the naive, QThread::usleep approach, that causes the stretch of the pulse?
  2. Why the signal-slot approach is not working, since it is event-driven?
  3. In general, how can I instruct the pin to go active ONLY during the transmission of data and then drop again to zero, so I can receive the slave's reply?

Solution

    1. What is that extra delay being added when I use the naive, QThread::usleep approach, that causes the stretch of the pulse?

    Linux is not a real-time operating system a thread sleep suspends the process fo no less than the time specified. During the sleep, other threads and processes may run and may not yield the processor for a longer time than your sleep period, or may not yield at all and consume their entire OS allocated time-slice. Beside that kernel driver interrupt handlers will always preempt a user-level process. Linus has a build option for real-time scheduling, but the guarantees remain less robust that a true RTOS and latencies typically worse.

    Note also that not only can your thread be suspended for longer than the sleep period, but the transmission may be extended by more than the number of bits over baud-rate - the kernel driver can be preempted by other drivers and introduce inter-character gaps over which you have no control.

    1. Why the signal-slot approach is not working, since it is event-driven?

    The documentation for QSerialPort::waitForBytesWritten() states:

    This function blocks until at least one byte has been written to the serial port and the bytesWritten() signal has been emitted.

    So it is clear that the semantics of this are that "some data has been written" rather than "all data has been written". It will return whenever a byte is written, then if you call it again, it will likely return immediatly if bytes are continuing to be written (because QSerialPort is buffered and will write data independently of you application).

    1. In general, how can I instruct the pin to go active ONLY during the transmission of data and then drop again to zero, so I can receive the slave's reply?

    Qt is not unfortunately the answer; this behaviour needs to be implemented in the serial port kernel driver or at least at a lower-level that Qt. The Qt QSerialPort abstraction does not give you the level of control or insight into the actual occurrence "on the wire" that you need. It is somewhat arms-length from the hardware - for good reason.

    However there is a simple solution - don't bother! it seems entirely unnecessary. It is a master-slave communication, and as such the data itself is flow control. The slave does not talk until spoken to, and the master must expect and wait for a reply after it has spoken. Why does the slave need any permission to speak other than that implied by being spoken to?