TIPI Protocol - jedimatt42/tipi GitHub Wiki

TIPI Protocol Details

The communication protocol is a multi-layer protocol. From highest to lowest we have:

  • TIPI Messaging API - sending a string of bytes from the TI to the PI, and receiving a string of bytes as a response.
  • Shared Registers - the io-ports that messages are transmitted through.
  • Serial wire protocol - the signal pattern used by the PI to read or write to the io-ports.

TIPI Messaging API

In the TIPI Device Service Routine ROM there are 4 assembly functions for sending strings of bytes from CPU or VDP RAM to and from the Raspberry PI. These briefly described in Raw Extensions

  • recvmsg - copy data from PI to TI CPU RAM
  • sendmsg - copy data from TI CPU RAM to PI
  • vrecvmsg - copy data from PI to TI VDP RAM
  • vsendmsg - copy data from TI VDP RAM to PI

The address of these are saved into a table in the ROM at a fixed location just after the ROM header.

On the Raspberry PI side, the counterparts are implemented in tipi/services/tipi/TipiMessage.py

This abstraction layer provides the routines necessary to hide the complexity of the asynchronous serial wire protocol. It allows for alternative implementations, such as a much faster parallel bus, to be implemented and retain software compatibility with existing TIPI based applications.

Application clients must not go below the TIPI Messaging API layer to retain compatibility with the TIPI Messaging API and future TIPI compatible hardware both virtual(emulation) and physical

Shared Registers

The TIPI CPLD holds 4 shift registers for the PI to interact with. 2 for the TI to write to, and 2 for the Raspberry PI to write to. The PI can read from the 2 that the TI writes, but it cannot read from the ones it writes.

The PI drives a clock signal to interact with the selection of the registers, and pushing data in or out. This approach was taken so we wouldn't have to rely on RTOS modes on the PI, given that there are many other services running on the Raspberry PI such as file sharing, webserver, networking, and who knows what else. The design allows for the timing on the PI to be unreliable.

From the TI's point of view, these are memory mapped addresses. The TI can read all 4 address.

Address Function
>5FF9 RC - Raspberry PI Control Byte
>5FFB RD - Raspberry PI Data Byte
>5FFD TC - TI-99/4A Control Byte
>5FFF TD - TI-99/4A Data Byte

The Raspberry PI can only write to RD and RC. The TI can only write to TD and TC.

The TI is in control. The PI is polling the registers waiting for the TI's requests. And the TI then polls the registers to process the PI's response.

Register level protocol

The Control registers TC and RC can be given one of three commands

  • TSRSET >F1 (reset-sync)
  • TSWB >02 (write-byte)
  • TSRB >06 (read-byte)

TSRSET (reset-sync)

The TI-99/4A sends a TSRSET by writing >F1 to the TC register. The Raspberry PI has been sitting polling the TC register, expecting >F1. Every messaging operation starts with this. When the PI observes >F1 in TC, it responds by writing >F1 back to RC as an ackowledgment.

Note: to save energy, and make cpu available for other services, when the PI is waiting for TSRSET, it will back off the polling and introduce ~10ms sleep between polling operations. Once TSRSET is observed, it runs full speed to handle the request.

TSWB (write-byte)

Writing a single byte from the TI to the PI involves a syn-ack style model.

The TI first copies the byte it wants to write into TD register. Then it sets TC register to >02. The PI polls TC waiting for >02, and once it is seen, it reads TD.

The PI then sets RC to the value it found in TC as an ACK.

To send a subsequent byte, the TI sets TD to the value, and then it toggles the low bit of TC, so it is different than the previous. The PI has also toggled the low bit in the value it is polling TC for. When it sees that, it then reads the new byte, and sets RC to what it read in TC as an ACK.

TSRB (read-byte)

Reading a byte from the PI into the TI is also driven by the TI in the same fashion, except bit 3 is also set to indicate a read operation.

The TI writes the read byte command (>06) to TC. It then polls the RC register for the same value.

The PI has been polling TC, it sees the expected read byte command. The PI then sets RD with the value to transmit to the TI, and echos what was read in TC back in RC as an ACK.

The TI then sees the ACK in RC, and proceeds to read the value in RD.

Composition to Messaging

This scheme is similar to syn-ack in the TCP world, except that the sync counter is only 1 bit.

The TI and Raspberry PI software have to be in lock step with each other. There are no retry mechanisms at this level. No timeouts. The TI and PI services will just hang if they are polling and don't see the request that is expected in TC or ACK in RC.

Typical messaging is then performed with the following sequence:

  1. TSRSET
  2. TSWB - MSB of len of message
  3. TSWB - LSB of len of message
  4. TSWB - repeat for each actual byte in message
  5. TSRB - MSB of len of message
  6. TSRB - LSB of len of message
  7. TSRB - repeat for each expected byte

The DISK IO routines in the DSR ROM will often take liberties with how response data is fed back to ease the assembly programming, such as reading discrete pieces of the response into different memory buffers to reduce copying.

Serial Wiring

Only the PI operates the serial interface. The TI sees all ports as memory mapped devices, and they live on the TI's address bus, within the CPLD. The PI however needs access to them and so uses the interface described here.

Data interchange occurs over a serial communication model at the Raspberry PI's 3v logic level.

The CPLD and Raspberry PI are wired together with no intermediate circuits or buffers. Each device implements internal pull-up resisters on their corresponding input pins.

The wiring uses the following pin assignments on the TIPI's rear PI port:

The pin functions are as followings:

TIPI Pin PI Pin GPIO # Description
1 31 6 R_CLK serial latch clock
2 32 12 TEST_1P
3 33 13 R_RT latch select Rpi vs TI
4 34 GND GND
5 35 19 R_LE latch enable
6 36 16 R_DOUT latch serial data output
7 37 26 R_RESET TIPI Service Reset
8 38 20 R_DIN latch serial data input
9 39 GND GND
10 40 21 R_DC latch select Data vs Control

See TIPI-to-PI-Wiring for assignments to Raspberry PI

Note: As described in the CRU documentation, the R_RESET signal will trigger a hard reset of the python services running in support of TIPI. This is different from the TSRSET used in communication.

Select a register

NOTE: All signal transitions are given a small delay on the PI before the next. This is a hot-active loop reading and writing to volatile memory. It is here to just delay the PI enough that it doesn't outpace the CPLD and that the signals can fully raise or lower on the transmission lines in time.

Each register can be addressed by the Raspberry PI by setting R_RT and R_DC appropriately, and then raising first R_LE, then R_CLK. This latches the register selection.

R_RC R_DC Register selected
0 0 RC
0 1 RD
1 0 TC
1 1 TD

Read a byte

After the register is selected ( the PI can only read from TC and TD ), to read from it the PI shifts 8 bits out of the register MSB to LSB by raising R_CLK, then lowering R_CLK. (The signaling, due to pull up resisters is active low) The data bit is then available on R_DIN.

Now we have the read value. In addition to the value, there is a parity bit to minimally check integrity of the data. The parity bit is calculated internally in the CPLD shift register. Raise R_CLK, then lower R_CLK, and the parity bit is available on R_DIN.

If the parity is wrong, the register selection and write byte sequence are repeated until it succeeds without parity error. If any errors occurred, a flag is raised, and once successful the error count is written to a log.

Transmit a byte

Just like reading, first select a register ( the PI can only write to RD and RC ), then shift 8 bits out by setting R_DOUT to the bit value ( from MSB to LSB ) and then raising R_CLK, and lowering R_CLK.

After that, R_DIN will contain the parity bit. It read, and checked for correctness against the calculated parity of the transmitted byte. If it is not a match, the register selection and byte transmission are repeated until success. Errors are logged on the PI.

Implementation

The Python GPIO access libraries are too slow for ad-hoc pin usage. The implementation of the serial IO mechanism is in a native library for Python tipiports.c

Connection to Emulation - Websocket Protocol

It is now possible to connect the TIPI service to a TI emulator, such as JS99er, and run the TIPI SD card image in QEMU. On the PI/QEMU side, the GPIO message system is replaced by a Websocket server that listens for connection from the emulator. On the emulator side, the Websocket is used to transmit requests to the server, and simulates the shared registers in the memory mapped space for the TIPI DSR in emulation.

The websocket works at the TIPI Messaging API layer, with a few additions for synchronization. Websocket binary frames are used to transmit the message string of bytes, one message per frame. The length of the message is included in the header of the binary frame.

Websocket text frames are sent from the emulator side to the PI side:

  • "RESET" indicates the TI was reset, and the TIPI service should restart as well.
  • "SYNC" when TC=>F1 (TSRSET) indicates the websocket is ready to transmit/receive a message. This should always be followed by a binary frame containing the message.
  • "MOUSE mask dx dy" sends the button mask and x,y movements of mouse when it is within the emulation canvas. The mask,dx,dy are in decimal, and are accumulated on the PI side until requested by the TI program.