For reasons (explained at the end of the post) I need to have a pin change state immediately as the UART finishes outputting a set of bytes.

I'm using pyserial & pigpiod on a RPi3B+ and bluetooth is disabled so I'm using the 'good' serial chip at the moment.

The 'obvious' solution is simply

pi.write(ASSERT_PIN, 1)

but as the write function simply triggers an asynchronous process, the pin is changed mid message, about 2 bytes in. Adding a sleep isn't an option as the message length varies and the pin's state change must be as close to the end of the message as possible.

So far I haven't found a callback or other way of directly checking the status of the serial buffer. Are there any?

As I couldn't see anything in software I've tried playing with the RTS/CTS pins.

My first attempt has been to wire another of the GPIO pins to the CTS pin and toggle it as part of the send function. The theory is:

  • CTS is held high via a jumper cable by software, UART (basically) thinks the line is busy.
  • serial.write("my_bytes") out, CTS still high so UART holds the bytes in it's buffer and sets RTS high as it wants to send but can't.
  • Toggle CTS low and then high again, UART sees the line as open briefly and takes the opportunity to send the bytes out. RTS goes low as the request appears to have been approved.
  • serial.write("dummy_bytes") to add something to the output buffer again after CTS has been toggled.
  • There are now bytes to be sent that arrived after the CTS was toggled, once the current write command is complete it will want to send again but see the line as busy. RTS will go high immediately after the bytes in the current send command are gone which I can then use.

I've actually (somewhat surprisingly) got this to mostly work. I can keep bytes in the UART buffer by holding CTS high and trigger it to send by pulling it low.

The problem is that I still can't guarantee the order in which the GPIOs/serial buffers are handled and I have a horrible feeling that the write() commands aren't handled atomically. Sometimes the dummy bytes just get added to the real bytes, they are output and with nothing left to send RTS never goes high. Other times the dummy bytes don't get sent but it takes between 30 and 300ms before RTS goes high, implying the lag is on the CPU side getting the data to the buffer.

Any ideas are welcome, I'm going to keep stabbing at it.

The Reasons: I'm making a SDI-12 interface for the raspberry Pi, an archaic half-duplex, 1 wire, 5 volt protocol used almost exclusively by expensive weather monitoring equipment.

I have a circuit based on this https://daycounter.com/Circuits/SDI-12/SDI-12-Interface.phtml (which again, surprisingly, actually works well).

UART must be inverted, level shifted to 5v and have the RX and TX lines routed to a single data line.

Very basically:

  • When the Pi is transmitting, the output is inverted and level shifted by the tri-state buffer but is otherwise just sent straight to the SDI-12 bus at 1200 BAUD (7bit, even parity).
  • As there is only a single data line, the line must be put into a high impedence state for the sensor to respond, this is done by disabling the output of the tri-state buffer (effectively disconnecting the TX pin from the data line).
  • With SDI-12, once the device receives a command, it immediately replies. 20ms delay at the most.
  • This means that the tri-state buffer's output must be disabled AFTER the message is sent (otherwise it is truncated) but BEFORE the sensor starts to reply, leaving a small 5-10ms window.

tl;dr : I'm trying to convince two devices to talk to each other in a way they weren't really designed to do and want to rope you into helping me.

  • I've not tried this :-) but there is a function out_waiting - could you monitor that and flip the GPIO pin as soon as that's zero? Remember though, the Pi does not have a real-time OS so something may come along and clobber your time slot. Maybe a microcontroller between the two?
    – user130616
    Apr 1 at 22:56
  • I haven't tried that either, but I will. If it's as simple as a loop checking out_waiting I will kick myself. Treating myself to some sleep first though. And yea, I spend a lot of my time writing MCU firmware so that's my backup plan. I'm TRYING to avoid my normal solution of 'just throw more MCUs on it' for once :P
    – Mudf4ce
    Apr 2 at 0:36
  • Do not forget that a tight loop may not give enough processor resources to allow the send to complete correctly (that would be a real bummer) Maybe add a minuscule sleep() as the while process? I've never worked out how the Pi cores are used / shared / sliced in Python and TBH just 'hope' threads get time somedays :-)
    – user130616
    Apr 2 at 10:29
  • Ok I've tried it now and unfortunately it doesn't work. If I send a long string of bytes, out_waiting does count down correctly as the bytes leave the RPi (so it's definitely 'working') but it gets to 0 whilst there are still a few bytes physically leaving the RPi. Either it has the same delay/buffering issue as the other functions or the bytes are marked as sent in the buffer when the hardware send starts rather than when it finishes.
    – Mudf4ce
    Apr 2 at 22:10
  • Yuck - that's not good, thought it felt like there's an O/S buffer before the shift register connected to the pin (though write function is supposed to block if no timeout set on write and stty shows no buffer size settings). Found this github.com/pyserial/pyserial-asyncio/pull/56 Wonder if writing to the /dev/xxx as a file would be better? Maybe CircuitPython or MicroPython may operate differently? Other than that the only other thought is to bit bang a serial port...
    – user130616
    Apr 3 at 0:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.