
In our last episode, our intrepid hero lamented the lack of hardware flow control on the TTL-based something-like-a-serial-port that the Raspberry Pi provides. Well, in the comments someone pointed out that the Pi hardware actually does support flow control on the serial line, after you jump through some hoops to activate the "secondary function mode" on a couple of random pins. So I hooked that up.
But, unsurprisingly, it did not just magically work on the first try.
There are any number of things that could be going wrong here, so the first question I want to ask is probably, "when the Pi attempts to turn RTS on and off, does the light on my serial tester cable change state?"
So like, how do I do that? How do I toggle the RTS line through software?
Also, I'm not 100% sure I'm hooking up the right pins. Some sanity checking, please?
(Fun fact that I managed to go my entire life until today without realizing, which suddenly explains so many things: did you know that the RS232 DB25 spec has TX/RX on pins 2/3, but the RS232 DB9 spec swaps them to 3/2? And that is not for a null modem.)
So. The DB25 on the terminal is using pins 2 (TX), 3 (RX), 4 (RTS) and 20 (DTR). Pin 5 (CTS) is wired to ground.
This has made me realize that I don't actually know the difference between DTR and CTS. I thought they were synonyms, not different pins. Uhhhh...
So here's how I wired it up. TX/RX are swapped to null-modem it, as are RTS/DTR. Does this seem right?
Update: No, that was wrong, I got turned around: CTS is not wired to ground. However, there is a DTR with no corresponding DSR. So this is more sensible:
Also, some questions about booting Debian, unrelated to serial ports:
The Raspbian install I'm using uses systemd, and is presumably loading a zillion services that I do not need like disk quotas, "remote file systems", "encrypted volumes", etc. How do I tell what it's doing and how do I identify and disable the useless cruft? ("Replace systemd with something else" is a common suggestion that is harder that it sounds.)
Also how do I make DHCP be non-blocking, that is, get me to a login prompt while the network is still negotiating?
And how do I make rc.local or something similar run as early as possible? Right now it runs very near the end.
DTR says "there's a terminal connected, and it's turned on." RTS/CTS say "ok, I want data now/no more data".
As I recall it.
The "raspi-config" tool, included with Raspbian, allows you to toggle "wait for network".
DTR/DCD are (approximately) logically paired, as are RTS/CTS (eg, Wikipedia table of common RS232 signals). Ie Terminal Ready/Carrier Detected, Ready to Send/Clear to Send. Some quick searching turns up this DB9 RS232 null modem with handshaking example -- for which you'll have to do the DB9 pin name to DB25 pin name if you want to build that as well (the Wikipedia link has DB25 pin numbers). The example looks basically sane from what I remember of my days of dealing with async modems...
The main difference from what you've done is that I'd normally expect to cross RTS to CTS in each direction in a null modem, as you cross TX to RX in each direction. DTR and DCD are often also hooked together, but not always necessary (depends on the equipment; some won't do anything without DTR/DCD). Off the top of my head I don't think linking DTR and RTS together makes sense, and linking DTR and CTS together wouldn't give you flow control the way you want. (DTS is basically continuously asserted -- "I am a terminal and I am here" -- where as RTS/CTS will come and go depending on data flows and buffer capacity.) Also RS232 is a positive/negative signalling system, so grounding something is "neutral" (unless you're doing it on the TTL side, in which case that would translate as "low" through to RS232).
Ewen
PS: The traditional way to debug serial issues is with a serial breakout box that has flashing LEDs showing the status of the lines.
Ok, so maybe what's going on here is that the terminal supports RTS (which it turns off when its input buffer is full) but does not support CTS (that pin being wired to ground, which would mean that it does not allow the other end to tell the terminal "stop sending").
Perhaps it uses DTR for when it is in "offline" mode or something. Because that line is hooked up to logic, not just to power or ground. DSR isn't even wired up.
I have a serial cable with LEDs on it, but the CTS/RTS lights are always on even if those pins are not connected, so I am hoping for a way to tell the kernel to "RTS off". If that makes the LED go off, then I know that both: the kernel is doing what I think it should be doing, and I have the right pins connected.
It sounds plausible that the "dumb" terminal only supports unidirectional flow control (ie, the terminal can say "wait, wait, I need to catch up" but has no outgoing buffer so is entirely uninterested in the other end saying "I can't keep up with the typist at the keyboard" -- if it's going to be dropped it doesn't really matter if it's dropped on the wire or inside the terminal). In which case you'd only need one direction of RTS/CTS crossing to get the flow control you want; the other probably could be connected but sounds like it'll just be ignored.
A key RS232 concept is that it is defined asymmetrically. There are DTE devices (Data Terminal Equipment -- "terminals") and DCE devices (Data Communications Equipment -- "modems"). The meanings of the signals are defined with respect to the DTE -> DCE and DCE -> DTE interactions. However in the "local terminal hard wired to the serial port of a host expecting to be a terminal" situation, you have two DTE devices back to back -- hence the null modem. Mostly this means that the meaning of signals like RTS, CTS, etc, change depending on which end you look at. Eg, DCD is an output from the point of view a DCE device ("I am a modem and I see carrier on the line"), but it is an input of a DTE device ("Aha, looks like my attached DCE -- modem -- is seeing carrier"). And thus when you connect two DTE devices together via a null modem, some mental logistics is required to make sense of it all.
Ewen
PS: Ingmar's observation "DTR/DCD are basically the session control, while RTS/CTS are the flow control." is the most concise explanation I've seen of how its supposed to work. However in practice... implementations did various other things, as they point out. Your terminal appears to be one of those "not 100% following the complete original RS232 theory" situations. Hopefully its manual explains what it actually expects to happen when hooked to a modem (DCE), and then you can translate that into DTE-speak for the Pi's terminal to create.
PPS: Being session control DTR is a fairly easily controlled output n a terminal (eg, the Pi, running Linux); and DCD is a fairly easily inspected input on a terminal (eg, the Pi, running Linux). Something like
kermit
should allow you to control/see those. You might want to experiment with those to understand what the terminal expects.`systemctl --all` will show all "units," which includes services
`systemctl disable [unitname]` should prevent it from being started automatically
Also: `systemd-analyze` will show you where your boot time is going; `systemd-analyze blame` will sort by most costly service, and `systemd-analyze critical-chain` will show you where your dependencies actually are.
Wow, I am shocked that this exists. I thought this kind of analysis was the sort of thing that Linux avoids at all costs, let alone includes in the default release.
Debian's particularly bat-shittiness lies elsewhere. They do tend to like Data.
It's been a huge, ongoing shitfight to get a certain sort of neckbeard to admit the possibility that systems design could have advanced since 1985. systemd is actually pretty good, overall.
Small addendum: systemd disable won’t actually turn a service off if something else depends on it. You need another command for that, although I can’t for the life of me remember what it is offhand.
# systemctl mask <service>
will prevent a service from being loaded, even if another service depends on it.
(also, you want "systemctl disable" not "systemd disable")
DTR/DCD are basically the session control, while RTS/CTS are the flow control. Back in the days of modems, DTR (Data Terminal Ready) was the terminal telling the modem it was good and ready, and when the terminal dropped DTR, the modem took that as a signal to hang up. In the other direction, the modem would raise DCD (Data Carrier Detect) when it established a session, and drop it when that session ended (and often also say things like "NO CARRIER" in-band).
RTS/CTS are the flow control. RTS (Request To Send) is the terminal telling the modem "speak now" or "waitwait, my buffer is full, yes, all 8 byte of it" (when lowered). CTS (Clear To Send) is the same for the modem, so when that is lowered, the modem has a full buffer and asks the terminal to stop sending.
Then there were some perverts that for some weird reason used DTR for flow control, so that's why many terminal emulators have DTR/DCD flow control options. But don't do that when you have perfectly fine RTS/CTS lines.
And those pairs always go together, DTR/DCD and RTS/CTS. So you'd swap RTS and CTS, and you'd swap DTR/DCD (although you probably don't need those at all). You'd never swap RTS and DTR like you did because that way lies insanity.
Oh, and there was also a RI (Ring Indicator), which the modem raised when the telephone line was ringing. You can safely ignore that though.
Wikipedia has the pinout and a helpful table that explains which side gets to control which pins.
RTS originally had a completely different meaning that what became popular for flow control. The DTE (terminal or computer) asserted RTS when it wanted to transmit, and then the DCE (modem) asserted CTS when it was actually ready for the DTE to transmit. This was back when half-duplex modems were fairly common; since the 1980s almost all modems are full-duplex, and when carrier detect (CD) is asserted, they are always ready for the DTE to send (transmit). With full-duplex modems (or full-duplex connections in general), the original purpose of RTS is obsolete.
RTS/CTS flow control has repurposed RTS to have NOTHING to do with when the DTE wants to "send" (transmit), and instead the DTE asserts RTS when it is ready to RECEIVE data from the DCE. In recognition of this, the EIA/TIA-232-E standard (successor to RS-232) designates a new "circuit", "Ready To Receive" (RTR), which is assigned the same connector pin as RTS. Obviously that pin can only be used for RTR or RTS at any given time, but not both.
RTS/CTS flow control should now more properly be called RTR/CTS flow control, but due to historical precedent, no one does that.
Not sure if this is what you're looking for, but this directory has some code I use on Linux to toggle the RTS and DTR lines.
https://github.com/deater/rapl_validation/tree/master/serial_port
Are you sure you wouldn't do better with an Arduino handling this protocol and just treating the various RS-232 ports as ordinary digital ports? If you get an Arduino with WiFi you can just pass the serial data on to a real computer. I suppose this is a bit of an ending is mending strategy, but it seems like there is an awful lot of stuff between the bits and what you want.
Having an Arduino mediate the data between a serial port and a Pi, when the Pi already has an onboard serial port with flow control, seems like the very definition of putting more stuff in the way. I'm just trying to turn the thing on.
I'm lazy, perhaps, but I've always had better luck with a USB serial dongle than the onboard serial. Unfortunately the flyback transformer on my VT-100 died before I got the chance to try this sort of thing. The VT-100 has enough empty space for a mac mini, really.
In principle I agree with you, but I have spent too many years in the computer business to necessarily agree in practice. I can't recall how many times a particular device, driver or software package did 99.998% of everything I wanted then challenged me inch by inch for the remaining 0.002%. It might be the ability to clear to the end of the line, set an otherwise ordinary hardware mode or just exploit a certain combination of UI element options, but somehow or another the desired configuration would remain beyond reach. That usually meant circumventing the driver, modifying the device or building some horrible kludge that actually delivered 100%. It wasn't just me, but this elusive last bit was a common war story when commiserating with friends in the business.
You are trying to use a serial line in a certain way, but you have an entire operating system standing in your way. I'm probably projecting my own experience inappropriately, but it might make sense to exploit all the modern cheap hardware and get the OS out of the way. Hook up an Arduino via USB and have it drive the lines directly or have it control a UART to your satisfaction.
Yes, I know that you will figure out the proper OS configuration, but you aren't trying to meet a deliverable date. Sometimes necessity is the mother of, what in better times, would be called idiocy.
P.S. I keep thinking of the story an older tech told me. His company tried to cheap out on air conditioning and locked the thermostat. They rigged a soldering iron to keep the AC running. It was like something out of a Phil Silvers comedy.
ISTR that those Arduino Wi-Fi modules actually ran Linux internally. Having an Arduino mediate between the Pi and the serial port, when that in turn actually involves having an additional Pi-class computer mediate between the Pi and the Arduino..., definitely does seem like just putting more stuff in the way for the sake of putting more stuff in the way.
You'd also presumably be bridging RS-232 onto TCP over Wi-Fi, or vice versa..., which is... I've never been able to figure out why so many people seem to think that's a good idea.
If you want to get rid of systemd, Debian still supports SysV-style init. "apt-get sysvinit-core" will install it.
Don't expect it to speed up startup, though. In my experience, SysV init is actually a bit slower to get things going.
sysvinit is tiny and fast and is done doing most of its work a split second after it starts.
When you say Debian boots slowly you are referring to the Debian-specific init scripts (that you should not confuse with sysvinit itself) which mostly run sequentially and use all kinds of funky time-outs.
Well, yes. Thanks for being pedantic. But what matters from a practical perspective isn't how much time it takes init to start up, it's how long it takes the whole system to start up. Which is what I meant.
I wasn't being pedantic. I was perhaps trying to rectify a common misunderstanding. The point being that scripting systems like, say, openrc (perhaps supported by dependency graph generating compiled programs), have long replaced Debian's failing attempts at providing an efficient init script system, for other Linux distributions, that is.
> How do I toggle the RTS line through software?
If you're just looking to toggle it interactively to see if it lights an LED, this is a good way:
$ python
>> from serial import Serial
>> port = Serial('/dev/whatever')
>> port.setRTS(1)
>> port.setRTS(0)
Likewise you have setDTR, getCTS, getDSR and getCD.
Huh. Well, if I do either setRTS(1) or setRTS(0) (from another terminal), it makes the connection on ttyAMA0 totally lose its mind: starts sending the wrong bytes until getty exits.
The serial_port C code doesn't seem to do anything at all.
And neither of them make the LEDs on my serial tester cable change state.
So I guess I know that the python code is able to do something to the kernel that makes something happen. I still don't have verification that the kernel is affecting pins on the Pi, or that I have my wires on the right pins.
Are you using a Pi3?
You probably know this, but the pi3 switched the primary serial port to hook into the bluetooth adapter and the exported serial pins were remapped to the stripped down mini-uart that doesn't have RTS support.
I vaguely feel like it's possible to disable bluetooth and switch the uarts back again but it might involve some device tree magic.
Yes, obnoxious all around, I was foolishly trying to teach an operating systems class where we programmed bare-metal on the Pi and they pulled these games that make each generation of Pi incompatible at the very low level.
Yes, Pi 3 B 1.2, and I think I have it configured to use the real serial port instead of Bluetooth. I'm running my console on ttyAMA0 instead of ttyS0 and did all this rigamarole:
I should have known you would have done all that already.
This is a more interesting problem than the stuff I'm supposed to be working on. Most of my equipment is back at my lab, but I do have a pi3 and a multimeter here, I'm going to see if I can get anything out of the RTS line.
re-read the old article and you are using a Pi3.
So one thing to try is to add dtoverlay=pi3-disable-bt to your /boot/config.txt reboot and then retry everything.
Possibly you also have to disable the hciuart code at boot so bluetooth doesn't try to take over the connection.
This suggestion is based on the info from this page: https://www.raspberrypi.org/forums/viewtopic.php?t=138223
Using pi3-disable-bt instead of pi3-miniuart-bt seems to necessitate moving everything back to ttyS0 instead of ttyAMA0, but does not magically make flow control start working...
OK, so can I assume you are still using rpirtscts to try to enable RTS?
The one in their git repository is broken for Pi3. The GPIOBASE on pi2/pi3 is at a completely different address.
There was also an embarassing bug in my toggle_rts C code.
Anyway I have some code in a new repository here, and with it I can run rpirtscts then pulse_rts and the RTS line (GPIO17) toggles at 1Hz.
https://github.com/deater/uarch-configure/tree/master/rasp-pi-serial
And of course someone had reported this bug to them back in May but apparently it wasn't worth fixing.
Yeah, I was using that but added a case for bcm2709. Looks like it needed more than that...
Anyway, now I'm running your code: "rpirtscts on" followed by "pulse_rts", and my cable's LEDs are staying resolutely on.
I've got RTS on Pi 3 pin 11 (BCM 17) and CTS on pin 36 (BCM 16). I also tried swapping them.
That all looks like it should :(
I have an LED hooked to RTS/pin11/gpio17 and it blinks at 1Hz when I run pulse_rts.
In /boot/config.txt the only change I made from stock is dtoverlay=pi3-miniuart-bt
Probably not that it matters but I'm running Linux 4.4.30-v7+
Ok, progress! I didn't hook up an LED but just poked a voltmeter at it, and pulse_rts is indeed making pin 11 go from 0v to 3.2v at 1Hz!
One possibility is that my tester cable is lying to me. Seems unlikely. Also, when hooked up to the AAA, I'm still seeing overruns.
Another is that this thing is not implementing RTS/CTS properly. Though it is definitely doing the right thing with RX/TX...
Not sure why it's not letting me reply to your response.
In any case it's quite possible Linux or the BCM2835 are doing stupid things too, I've been burned many times by both of them.
Anyway I put together a dump_serial_regs utility you can use while your connection is active to see what the actual pi hardware thinks about your CTS/RTS state.
If I fire up minicom it looks like everything gets set properly by the Linux driver, but I actually don't have an adapter with CTS/RTS so I can't test things any further than this.
It cuts off nested thread display at depth 8, but if you hit reply from the notification email it puts it in the right place.
dump_serial_regs is showing me CTS and RTS enabled, but I haven't seen CTS say anything but "low" or RTS say anything but "0", even while the AAA was beeping because it had overflowed its inbound serial buffer.
I ran it in a loop while typing ^L at xemacs to provoke it. "RX FIFO" and "TX FIFO" change from empty/not empty/full, UART becomes busy/not busy, and FR and IMSC registers take on several different values. But not CTS/RTS changes.
Ahh, you're assuming I'm not some sort of luddite that still checks e-mail using pine and so I don't always think to click links in e-mails.
Anyway you've probably already tried this, but I can verify that if I hook 3.3V directly to the GPIO16/CTS pin the UART_FR CTSin register bit shows the line as going high.
So possibly a problem on the incoming signal? Maybe the level converter?
Ok, more progress! It turns out the TTL-DB9 dingus has some pins that need to be jumpered to enable RTS/CTS, which I didn't notice because I was a dummy and didn't actually search for a manual. With that set up, my serial tester cable does indeed pulse the light when running pulse_rts, and I see 8.5v toggling on DB9 pin 7! Yay!
However, the effect of this when hooked up to the aaa60 is that "rpirtscts on" causes the display to stop updating, and "rpirtscts off" causes it to resume. So it's behaving as if enabling CTS causes CTS to never be asserted.
So I've spent a lot of time digging through this whole mess and I really can't see anything obvious that is wrong.
What value does RTS pin have through all this? I'd think in the working state RTS out of the Pi would be 1 and CTS into the Pi would also be 1?
Does transmission restart if you manually force RTS to be 1?
I was searching for any evidence that anyone has ever successfully gotten hardware flow control on the Pi working and while many have tried and failed I never found a successful report (though maybe no one ever posts good news).
So some further thoughts.
Things working without RTS/CTS pins enabled makes sense. By default the rpi GPIO pins are set to input, which means floating high-impedeance. And the aaa60 manual says that if the CTS input is left floating then it means to allow transmit (and presumably the line is pulled high by a pullup resistor somewhere).
When RTS/CTS is enabled though it sounds like the pi is pulling the RTS line low, which is stopping everything. Why is it low? One possibility is that the Pi has the RTS output inverted from what the aaa60 expects (I've come across people talking about having to hack an inverter into the line for some devices to work). Obviously that would be a pain :(
So I measured voltages on the DB9. In all cases, CTS stays at +1.3v (between pins 8 and 1, which is wired to signal ground on both Pi and AAA).
rpirtscts on: output stops, RTS -5.7v.
rpirtscts off: output resumes, RTS +8.9v.
rpirtscts on, but unplug GPIO 16: output resumes, RTS: -5.7v.
rpirtscts off, pulse_rts: RTS stays at +8.9v.
rpirtscts on, pulse_rts: RTS flops between -5.7v and +10.2v (I think: my meter goes a little wonky). Also I kind of expected the display to update for 1 sec then stop, and it doesn't update at all.
I am confused about why I am seeing different voltage readings on RTS at all: isn't that communication from the AAA to the Pi? So long as the AAA's serial buffer is not full, it should be sending whatever-voltage-means-go, and when the buffer is full, the opposite.
The Pi telling the AAA "my buffer is full" is not a thing that should be able to happen.
So I finally just went out and bought a rs232 level converter, I think the same model you have.
I've hooked up my pi3 to a terminal (in this case an x86 machine running minicom, sadly I don't have any old terminal hardware).
Ran agetty on the pi3 in 115200/8/n/1/rtscts mode (had to run it by hand, hate systemd)
While connected from the terminal, did a huge screen dump (cat /boot/kernel.img | uuencode -m -)
This itself was not enough to kick in the flow control (I guess modern hardware is so good). But if I did something to stop reading the serial line (such as bring up a menu in minicom) the Pi side of the connection went from CTS=0/RTS=0 to CTS=1/RTS=0 and the flow stopped. And when I closed down the menu it went back to CTS=0/RTS=0 and the flow resumed. So I assume this means the flow control is working?
My settings, in case it's useful:
I'm using a 9-pin null-modem cable
PI3 ------- RS232-breakout
___________________________
3.3V ------- VCC
GND -------- GND
RX(pin10) -- RX
TX(pin8) --- TX
RTS(pin11) - RTS
CTS(pin36) - CTS
plus it's jumpered for rts/cts
This is at least very adjacent to the kinds of comments you really hate, so I'm really hesitant here. But there is a neat and way under-known project called Alpine Linux that is maybe worth considering since you're already getting pretty fiddly with this:
https://alpinelinux.org/about/
It's radically smaller and more streamlined than anything else (and especially Debian), but retains most of the usability/ease. So while I know your feelings about Linux-fiddling, this isn't totally that, and I also know your feelings about Debian....
Runs on the Pi: https://wiki.alpinelinux.org/wiki/Raspberry_Pi though I don't know enough about what you're trying to accomplish overall to know if it's a good fit for you.
To make rc.local run as early as possible change the sort order of the symlink:
runlvl=$(runlevel | cut -c3-)
cd /etc/rc${runlvl}.d && sudo mv S*rc.local S00rc.local
Stop reading if this works for you.
I don't know what the rpi environment puts into rc.local by default, and it traditionally runs last, so check that there's nothing in there that's not going to like running early.
On older debian systems the Right Thing to do would be to use the update-rc.d command to change the symlinks.
update-rc.d -f $service \
start $start_order $start_levels . \
stop $stop_order $stop_levels .
YMMV. This may be yet another thing that I used to know how to do that is now obsolete.
The real answer seems to be to remove "After=network.target" from /lib/systemd/system/rc-local.service.
Allowing getty and other services to start initializing before DHCP handshaking is complete seems to be accomplished by replacing "auto" with "allow-hotplug" in /etc/network/interfaces.
Changing the number on the rc script would still be the easy thing to do, had SysVinit not been needlessly replaced with something "modern". systemd is a gaping head wound.
jwz said:
Wait, what? I mean, obviously, the pins aren't going to line up, and hoping that the DB9 ports would just map to the first 9 on the DB25 but what super-villain decided straight numerically swapping RX/TX's pin numbers would be a neat prank to play on the world? Yeesh.
Er, "... just map to the first 9 on the DB25" was wishful thinking, "but ..."
It's really starting to bug me that cmatrix is not optimized for low-baud-rate devices...
No. I'm not doing this. No no no no no.
Just low-baud-rate? I just installed it on my mac and full-screened the terminal and that redraw rate is abysmal...
So of course cmatrix is just using ncurses to do the obvious thing in the obvious way, and surprise surprise... apparently ncurses's idea of how you "optimize" that screen update between the old array and the new array is you start at the top left and move right, and if as soon as there is a single character of difference, you move the cursor there and overwrite it.
NCURSES YOU HAD ONE JOB.
What makes this so amazing is that every time I have ever updated a Linux system in the last two decades, every time, there's a new version of ncurses. More than once I've yelled at the screen, WHAT THE FUCK ARE THEY CHANGING IN NCURSES! STOP! STOP TOUCHING THAT! IT'S DONE! I've never actually looked at the diffs, but seriously, every time!
I assume they're updating the licensing terms or localization strings or something, because hey, that too should be an ongoing area of innovation in code that literally should not have changed since 1992. But come on.
Anyway, it's good to know that the one thing that hasn't changed, and that they never got right, is the thing Emacs was doing properly by 1981 at the latest.
And let's not forget that this is ncurses. The new version of curses. Because it's supposedly better than plain-old curses was in some way. Some way apparently unrelated to getting characters onto your screen efficiently.
How I feel when I run pkg to update packages on FreeBSD and before it can figure out what packages to update it first has to update itself. Happens about half the time. Why isn't pkg DONE?
ncurses was last updated fifteen months ago. I checked out its website and noticed this.
It's ugly hacks all the way down.
That's telling, and not in a good way. RTS from AAA (CTS to RPi) matters here, other direction not so much. Could there be a setting (DIP switch?) in the AAA to make it drop (raise/change/whatever) its RTS on a full buffer?
Just an FYI, Adafruit now has a neat little Serial to USB board for about $6 that claims to understand flow control. Probably way too late for this project, but probably useful in the future for turning ancient terminals into USB devices. (Yes, one can also use a standard USB to Serial dongle, though this gives you the ability to directly wire whatever signals you want a bit more conveniently.) I'm posting it here partially because I bet someone will end up trawling this blog post someday and will want to know.