Success! The Go code I’d been struggling with is working, although it also required changes to the C++ code running on the ATMEGA328PB.
Here’s a quick overview of how the process works.
On the SmartParallel, the ATMEGA328PB code sits in a loop watching for incoming data on the serial port. It also monitors the state of the printer by frequently calling a function called updatePrinterState() which checks the state of various lines on the printer cable. It then determines if the printer is in one of several defined states – READY, BUSY, ERROR, PAPER_END or OFFLINE. And each time it’s called, it sets the status of the CTS line (more on this later) – low if the printer state is READY, high for all others.
When the SmartParallel code detects incoming serial data, it reads it into a buffer until it encounters a terminator byte (ASCII 0), or the buffer is full (at which point it inserts a terminator byte as the last byte) or it times out (in which case it dumps the data received as a hopeless case).
Normally it will read until it receives the terminator byte, at which point it clears anything else in the incoming serial stream. That means that whatever is sending data needs to be careful to send just one thing at a time.
Also, the SmartParallel controls a line on the serial port I’ve called CTS (clear to send), although I’ve no idea whether this complies with other, standardised uses of CTS. This is active low. Basically, the SmartParallel pulls this line low whenever it is happy to receive data on the serial port, but takes it high when it’s busy doing stuff like printing.
This is important because, as it turns out, timing is everything here.
Too fast to print
My Go code (which we’ll get to in a minute) is capable of preparing and sending data significantly faster than the SmartParallel is capable of receiving and processing it. And orders of magnitude faster than the SmartParallel and Epson are capable of printing it. Flow control, then, is critical.
The Go code – a command-line program called spprintfile – reads a text file line by line into a string array (oh alright, slice). It does some clever stuff, like splitting lines longer than the print width without splitting words, if required. It then sends the lines to the printer one at a time, adding carriage return, linefeed and terminator byte to each.
Each time spprintfile sends a line, if first checks to see if the CTS line is low. If not, it waits a short while and tries again, but will eventually timeout.
The snag was, the Go code checked CTS, sent a line, then went to do the same for the next line – but it was so fast that the SmartParallel hadn’t yet had a chance to take the CTS line high, to prevent the next line being sent before the previous one had been printed. This resulted in lines disappearing into the ether.
So I changed the Go code so that, after successfully printing a line, it would loop until the CTS line went high. Finding the right place to put this loop caused me an hour or so’s worth of anguish.
I also had a problem in the SmartParallel code. Remember that updatePrinterState() function I mentioned? The process I used inside that was to pull the CTS line high at the beginning, as a kind of default, then only return it low (active) again if the printer state was READY. If the printer was in any of the other states, the CTS would remain high (inactive) to deter any client from sending more data to print.
The problem was, the updatePrinterState() function was getting called in the main loop – many, many times. Essentially, if the SmartParallel is idle, calling this function is about the only thing the main loop is doing. That meant the CTS line was alternating low and high at a furious rate. When the Go program on the Raspberry Pi tried to make contact, checking the state of the CTS line in the process, whether it saw a low or a high was a complete crapshoot.
I changed the function so that it does this:
if (printer.state == READY && setCts) { setCTS(CTS_ONLINE); } else { setCTS(CTS_OFFLINE); }
The upshot is that the CTS line state is changed only if there’s a change in printer state. Also, I added a parameter to the function (setCts) which defaults to true but which, at certain critical points of the code can be set to false if I want the program to not bother with controlling the CTS line.
What does this mean?
I’m now confident that the prototype hardware works both when talking to the SmartParallel direct, via a serial terminal, and programmatically through a Raspberry Pi’s serial port.
As I’ve made no changes to the hardware, and my PCB design is based directly on it, it’s worth my while making up a couple of the PCBs. And if they work with the current code, I can declare this project finished.
Of course, it won’t be… I’ll be tinkering with code for a while yet. But at least the hardware will be sorted.
Here are some useful links:
- All SmartParallel posts.
- Code for the ATMEGA328PB on the SmartParallel on GitHub.
- AVR libraries on GitHub (some are needed for above).
- Go code (spprintfile) for Raspberry Pi, on GitHub.
- Go libraries on GitHub.
Please note – I offer no support for any of this. The code is made available simply for entertainment purposes.