Sunday, 26 January 2014

Sinclair QL Serial

The Sinclair QL serial port can be used for poor man's file transfer between a PC and QL, but as usual the knowledge needed for doing this is not in handy one place. Continuing to celebrate the QL's thirtieth anniversary with some beginner-level material, I'll have a look at practical serial communication between the QL and PC/Mac.

The cable on the QL is end is similar to these puppies, a 6-pin BT.
The pictured plug had to be carved slightly to fit the QL.

First, the cable. The cable is connected to the SER2 on the QL. There are also QL's with 9-pin ports, and the image below depicts the original UK connector. Apparently the 9-pin connectors have a different pin order than a PC RS-232.

A 6-pin BT plug might be difficult to get so the more adventurous hackers might be better off by modifying the connector itself. The plugs appear to be similar to a British telephone standard. Googling for a "British Telecom Plug" ought to reveal sites for buying a cord. It's not a 100% fit, but as long as they have the correct number of pins, the plastic shape may be modified.

A 630W appears to be the exact right model, but I've yet to see anyone sell them new. There are also left-hand and right-hand versions, for example I have a CTL-fitting 9-pin converter which cannot be made to fit the SER port.

I can't imagine how many times I've soldered a cable as a mirror image. The confusing thing is, that here a mirrored cable "sort-of" appears to work, because the transmit pin is in the middle of the 9-pin connector.

This should be correct and the one I use currently. Both connectors are depicted as they are seen from outside the computer.

Connection between the QL and the PC 9-pin serial connectors, as viewed from the outside.
Obviously not to scale! SER2 1:GND,  2:TxD, 3:RxD, 4:DTR, 5:CTS

The next step is to make the computers talk to each other. A terminal program at the PC/Mac end, such as Zterm, may be sufficient for testing the connection. Oh, and your PC/Mac might be one of those new-fangled computers without a 9-pin serial port. So you'll need to have an USB-RS232 adapter.

After all the hassle, on the QL end, you can use a program like this for scanning incoming text:

10 BAUD 2400
20 OPEN #8,ser2
30 I$=INKEY$(#8)
40 PRINT I$;
50 GOTO 20

With a terminal set to 2400 on the PC/Mac end, you can start talking between the computers. Of course it's not very reliable, but some of text typed in the terminal ought to show on the QL screen. You can test the reception of the PC end by writing PRINT #8,"Hello!" on the QL.

File transfer

After testing the connection it's time to move to more interesting topics.

QL BASIC files can be transferred to PC via serial:

SAVE ser2

With a terminal using the same speed setting, you can grab the basic listing from the terminal window, and maybe copy/paste it to a text editor for storage. To send files to QL, your terminal needs to have functionality for sending files in plain ASCII, terminated with character code 26.

LOAD ser2z 

The QL will wait for a BASIC listing to be sent. (The 'z' is there to indicate that the terminating character will be expected.)

But how to send binaries and raw data?

LBYTES ser2,131072

...makes the QL expect raw data, which will be placed from address 131072 onwards (the screen memory). But before the actual data, there has to be a header, otherwise the QL can't know how long the file will be. (The code 26 cannot be used as a terminator, as the data itself might contain the value.)

From the PC end, headers can be examined by viewing the incoming serial data in numeric format. (Not usually possible in a standard terminal.) Data can be sent to the PC by using SBYTES ser2,131072,16 (for example). There are apparently more complicated headers, but this is the simplest way and the QL does not seem to use anything else for the SBYTES.


[length highest byte]
[length lowest byte]
[byte x length of actual data...]

So, sending a file with a length of 16 bytes would mean sending this data over the serial:


(The 16 bytes here are just bogus numbers)

QL loader

Below is a small Processing source for sending files to the QL. As a simple terminal is handy enough for receiving BASIC files, I did not bother with reception functionality. Copy and paste the source into your Processing editor and run it from there.

It might be used for building some kind of file repository for QL. The program assumes you have the required file stored in the Processing folder for the program. (In this case, QLDATA.BIN and/or QLBASIC.BAS)

Use the b key to send BASIC files and s for sending binary data. The BASIC file should be a plain text ASCII file.

It ought to be obvious from the source how to add or change your own filenames. With binary, I'd recommend loading to screen memory for testing. This way you'll get an immediate visual response as the screen fills with data. Executable code has to be run with CALL command.

Sadly, 2400 baud seems to be the fastest reliable speed for loading directly. At least I could load 32k and 48 k files without any apparent errors.

The source below is very rudimentary, for brevity's sake. The serial port selection is quite crude. Refer to your configuration and Processing reference for the serial library to get best results.

Note: I've found the serial to be sometimes more prone to errors, especially when loading longer BASIC files. I've sometimes used a few millisecond delay between characters. Also, sometimes resetting the QL helps clear problems, and is recommended if the loading fails. Sometimes it seems the computer gets up on the wrong foot.

import processing.serial.*;
Serial myPort;

void setup()
  String portName = Serial.list()[0];
  myPort = new Serial(this, Serial.list()[0], 2400,'N',8,1.0);
  println("At the QL end:");
  println("BAUD 2400");
  println("LBYTES ser2,address [for binary]");
  println("LOAD ser2z [for basic]");
  println("Here, activate the window and");
  println("Press s to send binary");
  println("Press b to send basic");

void sersend(int b)

void bin_send(String fname)
  byte bas[] = loadBytes(fname);
  int lenni,lea,leb,lec,a,iad;
  for(int n=1;n<=10;n++){
  for(int i=0;i<=lenni;i++){

void bas_send(String fname)
    byte bas[] = loadBytes(fname);
    int lenni=bas.length;
    int a,iad;
    for(int i=0;i<=lenni;i++){
//the delay may help if the QL does not behave


void keyPressed()

void draw()

Friday, 17 January 2014

QL Networking

Happy 30th Sinclair QL!

But what's better than a Sinclair QL? Two Sinclair QLs, of course! Here's my noob's experience of connecting two Sinclair QLs with the NET connectors.

The service and user manuals are a bit silent about what kind of cable is used for connecting the computers. Even the internet was a bit unhelpful. It almost started to seem like the best kept non-secret on the topic of Sinclair machines. In the end I just looked at an auction photograph and decided that the Sinclair net cable appears to be identical to a 3,5mm mono audio cable of a "phone plug" variety. Pretty much what was used for loading tapes on a Speccy.

A short cable might be preferable. Only one is needed for connecting two QL's for two-directional transfer. It doesn't seem to matter how the cable is connected on the two available connectors. But, at least in case of ZX Spectrum/Interface 1, you are not supposed to "loop" the network if you have multiple computers. This might hold true for QL too.

In SuperBASIC, each of the computers are given an ID via the NET command (1-64). After this, the neti_ and neto_ can be used to indicate input and output for the appropriate identity, such as neti_1 and neti_2 in this case.


On computer A, type
and press enter.

On computer B, type
and press enter.

On computer A, type
LBYTES neti_2,131072
and press enter

On computer B, type
SBYTES neto_1,131072,32768
and press enter.

The screen memory (32k) of the computer A will be filled with the contents sent from the other QL in about 11 seconds. LOAD, SAVE and LRUN work too.

Edit: There was a horrible mistake in the above (both loading and saving using neto_2) but it is now correct.

Incidentally, here's the way to draw filled triangles on QL Basic.
neto_0 and neti_0 can apparently be used for broadcasting and receiving on every ID. The manual says broadcasting more than 256 bytes is unreliable, possibly because there's no two-way protocol when broadcasting. So things like sending screen memory the way described above, is not likely to work with "broadcasting".

It should be possible to OPEN channels (OPEN #10,neti_) for printing and inputting text, but this is also not so straightforward. Perhaps with machine code.

Whether it is possible to hijack the signal with a PC (or, say, Arduino) I don't know.

Friday, 10 January 2014


I have dismantled a Cheetah joystick interface circuit board for the ZX Spectrum. This is pretty much identical to the original Kempston joystick interface, which allows Atari-style joysticks. Better yet, it only has one joystick port with no additions. (Some interfaces have loudspeakers and multiple joystick ports.)

The interface is stuck directly to the ZX Spectrum edge connector. The Kempston joystick can be read from Basic via the IN 31 function. The joystick needs five bits to deliver the status of each direction and the fire button. When used in BASIC, the IN 31 defaults to 0, which does not necessarily tell much of how the port works electronically. (I think in z80 Assembler this is achieved with IN A,#31) These things in mind, I started looking inside the interface.

  • 1 x 74LS138N chip = 3-line to 8-line decoder / De - Multiplexer
  • 1 x 74LS366AN chip = Hex Bus Driver
  • 5 x 10000R resistors
  • 1 x 0R resistor
  • 2 x diodes (1N4148 seems to be it)
  • 44-pin edge connector
  • 9-pin (joystick) connector
I then searched for datasheets for the two chips to complete the picture. I combined the overall information to one (messy) whole, so that I don't have to refer to different documents to make sense of the interface. The logical diagrams of the chips are overlaid to the pins, and all the edge connector pins are named. The names for the edge pins are taken from here.

Click to make bigger.

The picture can be a bit confusing as the chips are pictured from "below", as they are obviously on the other side of the board. I wanted to portray the Cheetah circuit as it appears, and show the pin order as it is from the outside the ZX Spectrum.

For clarity, I've coloured the various lines, red for +5 and blue for ground. The green indicates the interrupt pins at the Spectrum end whereas the black are used for the address and data pins.The yellow refers to the joystick connections. It's quite simple to track how the chips are powered and how the joystick pins relate to the chips.

I've also tested the individual chip functionality on a breadboard, connecting their output to a LED and triggered the inputs manually and with an Arduino just to make sure I've understood how they work. As the HIGH/LOW signals get reversed within the logic in a few places, picturing the whole can become a bit confusing.

The five 10K resistors to the left act as pull-up resistors for each of the joystick directions/fire. The 0 resistor is, I think, simply a way to go over a few circuit paths. I suppose it could be changed to a jumper wire. The diodes apparently keep the non-relevant data pins unambiguously "off". The D5 line might also be made this way, but the makers opted to connect it to the chip.

The chips

The CPU basically has to tell the interface when it is appropriate to bring the joystick status back to the computer via the data lines. Otherwise the CPU could be messed with, as the same address and data lines are needed for its other operation. Catching the "good moment" is achieved with the chip on the left, which takes the interrupt pins and three address lines as inputs. The chip on the right is connected to the data lines and uses the information from the other chip to enable or cut off the joystick status.

The 74LS138 pins and truth table. Only Y0 is used as output in the Cheetah adapter.
Looking at the 74LS138N chip, it has 8 outputs and six inputs. The datasheet gives a truth table showing that only one of the 8 outputs can be LOW at a time, whereas the rest are always HIGH. Now, as only one of the outputs is used, it is enough to check the input combinations that are relevant for that pin.

The truth table says that G1 has to be HIGH and the rest of the inputs LOW to achieve LOW output on the y0 pin. If any of the input statuses are otherwise, the output will be HIGH.

So, looking from the edge connector, !M1 has to be HIGH, whereas all others, A5, A6, A7, !IORG and !RD at the Spectrum end have to be LOW, for this chip to output the LOW signal that "lets through" the joystick status.

The bus driver chip. The E1 and E2 are both connected to the Y0 output of the other chip, and control the output of all the gates.
The single output is connected from both sides to an AND gate within the bus driver chip. The (reversed) AND gate controls whether the chip outputs are active. Only if both inputs are LOW then the gates are operational. The outputs of the bus driver are tri-state, they can be "off", HIGH or LOW. The gates also reverse the signal, if the input is LOW the output will be HIGH and vice versa.

The interface's main purpose, apart from channeling the joystick states to IN 31, is to "chop" the joystick pin connection signals and make the information available to the CPU port at the proper moment. Funnily enough, the joystick status can be read from any port including 31 and below. It's just that if the bits 5-7 of the port number are high, a value will not be read.

Additional notes

I did not include everything in my above image. Of course, a more complete picture would also show the connections within the Spectrum. The picture below shows the Z80 CPU and the pins that are used in the Cheetah (Kempston) interface.
The relevant pins at the Z80 CPU. 

So, I learned a bit about how the joystick interface works, and how this simple peripheral works. A couple of logic chips seem clearer to me now, too. Far more could be done to understand the interrupt timing from the CPU end and visualise the process of the whole thing.

Thanks for Marq for insights. Clever people are welcome to give better explanations, as long as they are understandable to the layman :)