6

I’m trying to communicate with my Z80 computer (RC2014) using a wireless radio. The easy way to do this is to use something like an ESP8266 (and there’s even an RC2014 module for this), but where’s the fun in that?

I have tried to do this using a simple 433MHz receiver and software decoder (written by me, based on an Arduino ASK decoder). I thought that I might be able to accomplish this with a simple IO port (SC129), but speed and timing seem to be a limiting factor. I could certainly look for a more optimal hardware solution to decode the signal from the 433 receiver, but I thought perhaps I could look into using something like LoRa.

My next thought was, can you use a LoRa module such as the RFM95 on a Z80? It uses SPI. I asked a related question on EE SE. I’m aware that SPI has been around for a while, but is this something you can do with a Z80? Or would an older technology [than LoRa] be much more practical?

I want to turn a simple switch on/off, and read back simple sensor data.

Nick Bolton
  • 819
  • 4
  • 13
  • Using the 433 MHz transmitter / receiver (hardware decoding) ought to be straightforward (for example, sending 9600 baud asynchronous serial communication through that system (or slower)). The receiver has the crucial AGC built-in. – Peter Mortensen Jul 10 '22 at 23:33
  • @PeterMortensen Any tips on hardware decoding for 433 MHz transmitter / receiver? I tried software encoding/decoding, but at 12 milliseconds per iteration, it seems impractical or maybe impossible with software. – Nick Bolton Jul 24 '22 at 17:01

2 Answers2

14

SPI is eventually the most simple interface to implement. It was designed especially to work with low end processors. I tend to use SPI for all my micro controller designs (*1).

Unless one intends to use some dedicated hardware, like a shift register (useful if speed is of concern), all it needs are 3 port bits for the data interface, plus one port bit per device connected (*2) for it's basic signals:

  • MOSI - Out - Master Out Slave In, the data the CPU sends out
  • MISO - In - Master In Slave Out, the data returned by external hardware
  • SCLK - Out - Serial Clock, a signal to be toggled once per bit

and

  • SS/CS - Out - Slave Select, also called Chip Select, selecting the external interface.

The great advantage is that SPI does not require any specific timing (*3) as long as the basic sequence is followed. It can be handled as slow as a bit per day. Any interface able to provide three output lines and one input line can be used. Including a classic PC parallel port. Well, or a Z80 PIO.


Let's say we have a Z80-PIO connected to a Z80-CPU at I/O address 20h (*4) and use port A with its first 4 lines assigned as (*4):

  • A0 - MISO - In
  • A1 - MOSI - Out
  • A2 - SCLK - Out
  • A3 - SS/CS - Out

We also assume the device to be operated in Mode 0 (CPOL=0, CPHA=0) as the RFM95 does. For other modes this may need to be adapted.

A subroutine to initialize this setup may look like this:

(All references relate to the Z80 Family CPU Peripherals User Manual UM008101-0601)

[Caveat, this has been just hacked down, so no guarantee for this code being bug free or working at all - not at least due to actual Beer-Level :)]

PIO1   EQU  20h     ; First PIO
PIO1AD EQU  PIO1+0  ; Data Register
PIO1AC EQU  PIO1+2  ; Command Register

SPI_INIT: ; Initialized SPI transfer on port A ; A destroyed

   LD   A,011001111b  ; Set Mode 3, Control (see p.189)
   OUT  (PIO1AC),A
   LD   A,000001110b  ; Line 1/2/3 as output all other input (see p.190) (*5)
   OUT  (PIO1AC),A
   LD   A,000000111b  ; Interrupt control, no interrupt (see p.191)
   OUT  (PIO1AC),A
   LD   A,000001111b  ; Set CS high to deselect
   OUT  (PIO1AD),A
   RET

By calling SPI_INIT the interface will be put into operating condition. Since sending and receiving is interleaved a single routine can be used:

SPI_CLK EQU  4
SPI_CS  EQU  8

SPI_SENDREC: ; Starts or continues an SPI transaction by SENDing a byte ; while RECeiving the peripherals response at the same time ; Called with byte to send in A ; Returns with byte received in A PUSH DE PUSH BC LD D,A ; Byte to send in D LD A,000000000b ; CS Low, Clock Low OUT (PIO1AD),A LD B,8 ; 8 bits per byte BITLOOP: LD A,000000000 ; Clock Low, Data Low RL D ; Isolate bit to send JR NC,BCLR ; Is it a zero bit? LD A,000000100 ; No? Then set data high BCLR: OUT (PIO1AD),A ; Set data value MOV C,A ; Save value with clock low (6) OR A,SPI_CLK ; Set clock OUT (PIO1AD),A ; Data and Clock IN A,(PIO1AD) ; Read input data RRCA ; Isolate input bit to carry RRCA MOV A,C ; Restore Data with clock cleared (7) OUT (PIO1AD),A ; Clear Clock (*6) RL E ; Insert bit from Carry into E DJNZ BITLOOP ; Next bit?

   LD   A,E           ; Return value in A
   POP  BC
   POP  DE
   RET

The important part is, as mentioned, to stay with the sequence:

  1. Activate CS
  2. Apply MOSI
  3. Raise Clock
  4. Read MISO
  5. Lower Clock
  6. Repeat step 2 thru 5 for all bits/bytes to be transferred
  7. Deactivate CS

Note that the routine does not perform step 7, it leaves chip select active. This is the base for multi-byte transfer, important for the RFM95, as each of it's transfers always starts with an address followed by one or more data bytes. CS has to stay active during such a transfer over all bytes to be written (and read), acting as transaction marker.

The SENDREC function is written in a way that multiple bytes can be chained by calling it in sequence as often as needed. Of course we now need a another function to end a transfer:

SPI_DONE:
; Ends an SPI transaction
; A destroyed
       LD   A,000001111b  ; Set CS high to deselect
       OUT  (PIO1AD),A
       RET

Let's for example put the RFM95 into LoRa mode. This requires to set the high bit of the Operation Mode Register at address 01h needs (See p.102 of the manual). To do so we need to send one byte with the registers address, followed by the new value, all in in one transaction handled by CS:

       ...
       LD   A,01h         ; Address of Operation Mode Register
       CALL SPI_SENDREC   ; Start transaction and send address
       LD   A,010000000b  ; Set LoRa mode + Sleep
       CALL SPI_SENDREC   ; Continue transaction and send data
       CALL SPI_DONE      ; End transaction
       ...

Now the RFM95 is in LoRa mode (*7) - Easy, isn't it?

So while this function may not be super speed (*8,*9), it might be fast enough for 'some switching and sensor read out'.


Bottom Line: Z80, or any other classic CPU is quite capable of doing SPI (within reason).


P.S.: The very same code could ofc be used with the 245/374 based RC2014 I/O. Addresses need to be adjusted and initialization could be reduced to setting a default state. In fact, by using the same bit (2^0) for MOSI and MISO, one or two instructions could be saved.

And yes, it can be done in Pascal using the port[] array (*10). Of course, a bit slower, like 2-5 times. Then again, packaging above assembly into functions would eliminate most of that.


P.P.S.: This made me smile:

is this something you can do with a Z80? Or would an older technology be much more practical?

A Z80 is about as old as it gets (1978), not to mention the TTL used by the 2014's digital I/O


*1 - Foremost to add MMC/SDC support, as the basic interface/protocol spoken even with modern terabyte sized cards is still SPI. But also any other interface or other controllers.

*2 - Using a decoder can reduce this when more than 2-3 devices are connected to a single interface.

*3 - Note that most devices have an upper speed limit, usually in the 5..40 MHz range, so not even remotely relevant (*9).

*4 - Doesn't really matter, just assumed for simplicity of the example code.

*5 - Unused lines should always be set as input.

*6 - With strict Mode 0, this could be deleted, as data is only valid during raising clock.

*7 - Well, in real use you may want to do this twice, just in case that the device was not in Sleep mode first, but that's another story.

*8 - I bet there is still room for improvement - this is, as said, just a quick writeup how it could work, made for the fun of it. Not much thought put into.

*9 - A rough estimate is 7-800 clocks per byte transferred, which gives a maximum transfer speed of around 50 KiB/s on a 4 MHz Z80. Which is about the range one can expect from a Z80 system anyway.

If that is too low, some external shift register and a some glue logic might be the way to go.

*10 - Using the RC2014 digital I/O at address 00h this might look a bit like this:

program SetLoRa;
{$U+}

const InPort: Integer = 0; OutPort: Integer = 0; MOSI: Byte = 1; /* Bit value for output data / MISO: Byte = 1; / Bit value for input data / CLOCK: Byte = 2; / Bit value for clock handling / CS: Byte = 8; / Bit value for chip select */

function SPI_SendRec(Value: Byte):Byte; var i: Byte; bit: Byte; rdval: Byte;

begin

port[OutPort] := 0; /* Clear Clock, activate CS / rdval :=0 for i:= 1 to 8 do begin bit := (Value and 128) shl 7; / isolate next data bit / Value := Value shl 1; / prepare follow up bit / port[OutPort] = bit; / set MOSI to data bit / port[OutPort] = bit+ CLOCK; / Raise CLOCK / rdval := rdval shl 1; / Make room for input data / rdval := rdval or (port[InPort] and 1); / Move input bit / port[OutPort] = bit; / Clear Clock but keep data (5) / end; SPI_SendRec := rdval; /* Retrun what has been read */

end;

procedure SPI_Done; /* Procedure to end a transaction / / or first time port initialisation */ begin

port[OutPort] := CS + CLOCK + MOSI; /* disable operation */

end;

var Dummy: Byte;

begin /* Program to set RFM95 into LoRa Mode */

SPI_Done; /* Initialize Port */

Dummy := SPI_SendRec(01); /* Register address / Dummy := SPI_SendRec(128); / Value to be set / SPI_Done; / End transaction */

end.

(And yes, I was bored :))

Raffzahn
  • 222,541
  • 22
  • 631
  • 918
  • 1
    SPI was not designed to work on low end processors. Devices/boards/chips that used synchronous serial communications already existed before SPI was invented. And processors communicated with them in software. Just like many MCUs had UART/USART peripherals in them to ease asynchronous communication, Motorola decided to design in a peripheral that allowed configurable byte oriented synchronous communications and decided to call it SPI. It just off-loaded the software bit-banging to hardware module inside the MCU. – Justme Jul 09 '22 at 22:13
  • 1
    SPI was designed to allow use of simple standardized peripherals with minimal effort. SPI does not need any 'hardware module'. These are developments done way after SPI was established - much as you already not, to offload some tasks. – Raffzahn Jul 09 '22 at 22:29
  • 1
    No, it's the exact same thing than with UART. Asynchronous serial comms existed before UARTs existed and when UARTs existed they became the de facto devices to implement async serial comms, and they got integrated to MCUs. Synchronous serial comms also existed before SPI and was not standardized. When first MCU with SPI peripheral came to market (maybe MC6805 in 1983?) then you could use MCU SPI module to communicate with the same old non-standard logic chips but it also enabled comms between two MCUs with SPI modules in a non-standard way. Which resulted into boards with SPI interface. – Justme Jul 09 '22 at 22:46
  • 1
    @Justme But there is not need for an 'SPI module' same way as there is no need for a UART. exactly as you argue. And it works exactly as that. SPI is a protocol (like RS232), not a device. – Raffzahn Jul 09 '22 at 23:17
  • 1
    You can bit-bang UART or SPI in software but generally you just use the UART or SPI to transfer bytes. But SPI is not a protocol, and neither is RS-232 (it's a standard for a physical electrical interface). Just like Motorola calls their built-in asynchronous communications functionality as Serial Communication Interface Module or SCI, they call their built-in synchronous communications functionality as Serial Peripheral Interface Module or SPI. SPI and SCI are both peripherals integrated in the MCU. SPI is just basically an UART without start/stop bits and configurable clock polarity. – Justme Jul 09 '22 at 23:56
  • 3
    @Justme " use the UART or SPI" Erm, I guess that is where it goes wrong: A UART is a device implementing a serial protocol. SPI is not a device. SPI is an interface and protocol. What you talk about is an SPI-Module within an MCU/SoC which implements that interface (like an UART). These are two different items that need to be kept apart when talking about. – Raffzahn Jul 10 '22 at 00:10
  • 1
    I did kept them apart. An UART is an UART whether it is an external chip or built-in peripheral. And SPI is a name for a built-in peripheral for handling data byte transfer in hardware on SPI peripheral pins, which was previously bit-banged in software on GPIO. So if SPI is a protocol, it still existed before it was given the SPI name, as synchonous comms was done before it was named SPI. It's just clock and data, on whatever clock phase and polarity you need, so as a protocol it brought nothing new. As a peripheral, it brought interrupt based byte transfers. – Justme Jul 10 '22 at 00:23
  • @Justme: An async transmitter needs to output data at precisely-measured intervals, but gets to choose when the sequence starts. An async receiver needs to be capable of capturing data at precisely-measured intervals, starting at a time chosen by the remote party. An SPI master can send and receive data at times of its choosing, generally with no precision required. An SPI slave needs to be capable of both sending and receiving data at times of the remote device's choosing. Among these four possibilities, SPI master is most easily supported without specialized hardware. – supercat Jul 10 '22 at 13:24
  • 1
    @Raffzahn I edited one of your comments (edit) to remove what I thought was a benign remark. Try to mind your language; this site is frequented by people from cultures where swear words are almost always insulting. – wizzwizz4 Jul 10 '22 at 17:16
  • @wizzwizz4 Beside that public shaming isn't a great feat, not naming what it is about is doubling. So please be clear about your action as mod when going public. On a side note, at times next to every word can be classified as 'bad' if one wants to see it that way. So if one wants to see something insulting he always will, independent of culture – Raffzahn Jul 10 '22 at 18:15
  • @Justme Of course it is a simple protocol of 4 lines (you may have forgotten CS). That's the whole point of SPI. And like always, it's parts have existed before. It is the whole purpose of a standardization to describe and codify some behaviour. Standards do not invent. And just because some manufacturer writes 'SPI' into a box on a slide, doesn't make it anything else, or would writing RS232 in such Box mark it containing something different than a UART? – Raffzahn Jul 10 '22 at 18:22
  • @Justme I know, Wiki isn't an authorative source, but it's usually a good hint, looking at the Wiki page about SPI shows a clear description of a vendor and implementation independent standard. In fact, it even touches the interrupts, describing them as device dependent and clearly outside of SPI. Bottom line: Points about some device some manufacturer added, are not related or relevant to a standard like SPI, but only to about that device. – Raffzahn Jul 10 '22 at 18:25
  • @Raffzahn Better? I was planning to delete that comment as soon as you'd read it, but I get what you mean about public shaming. – wizzwizz4 Jul 10 '22 at 18:44
  • 1
    SPI was not a standard and still isn't, even if it is a de facto thing cloned and used by everyone :) It was just a simple helper tool that started to live it's own life. Now people say it's an interface and bus and standard and it's extended to QuadSPI etc. I just wanted to make sure it is not a standard or specifically designed for making standardized peripherals, even if that is how it grew to be in practice and is used in that context today. Unlike I2C which is a standard. And RS-232 (and many other PHY standards) does not define any serial protocol, that is out of scope for that standard. – Justme Jul 10 '22 at 19:33
  • Ah, by this, I meant "older than LoRa": "Or would an older technology be much more practical?" – Nick Bolton Jul 24 '22 at 16:59
  • @NickBolton SPI is perfect for classic processors. Simple to implement and fast enough for most tasks. – Raffzahn Jul 24 '22 at 19:00
  • 1
    This is just a phenomenal answer. Every time I read it with fresh eyes, I learn something new. – Nick Bolton Nov 06 '22 at 10:50
  • 1
    @NickBolton Thank you. I would assume it's more the awesome usability of SPI while being extreme simple at the same time. So did you get your LoRa Modul working? Any Github to peek at? Maybe add that as footnote to the question? – Raffzahn Nov 06 '22 at 15:34
  • 1
    It’s next on my todo list! I’ll definitely share the GitHub repo when done. Probably will be written in C. – Nick Bolton Nov 06 '22 at 22:43
  • @NickBolton ;) But consider Assembler, at least for all basic rend/receive routines covering bytes and whole messages. Just look at Introspec's (and others) Answers abotu C and Z80. It isn't that hard and will result in way better code. Always :)) – Raffzahn Nov 06 '22 at 22:48
  • 1
    Ok! Maybe I will give Assembler a shot… maybe ;) – Nick Bolton Nov 07 '22 at 02:36
6

Of course you can interface a Z80 with an SPI device, just like you can with any CPU or MCU that has no built-in SPI. All you need is a GPIO port of some sorts and control the pins in software.

This is how CPUs and MCUs interfaced with synchronous serial devices before Motorola decided to integrate a configurable peripheral to interface with synchronous serial devices into their MCUs and call it SPI.

It is also possible to build a hardware card/board/module that sits on the CPU bus and implements a synchronous byte oriented serial interface for the Z80.

Justme
  • 31,506
  • 1
  • 73
  • 145
  • I just have the need to add that original Z80 had no GPIO so you need to add address decoder + LATCH register (at least 4 or 3 bits) and implement SPI in SW ... still easily doable however with low Z80 clock and pure SW implementation the speed will be slow ... – Spektre Jul 10 '22 at 03:44
  • @Spektre The assumption is that there is some IO on the bus to connect to and that's a non-issue. The RC2014 computer platform does have I/O modules that can be used. What's even more important is that the RC2014 platform uses 5V levels, and the LoRa module uses 3.3V levels, so there must be some form of voltage level conversion between the 5V host and 3.3V LoRa module so they are compatible and don't get damaged. – Justme Jul 10 '22 at 11:29
  • @Spektre: If one has a single-bit input port for MISO, and a single-bit output port device select, an I/O address decode that's active for two addresses could be used as CLK, and an address bit that was ignored by the address decoder could be used for MOSI. – supercat Jul 10 '22 at 13:27