Calcnet2.2

CALCnet2.2 offers a robust, asynchronous end-to-end transport protocol capable of dynamic, stateless network topology reconfiguration, error detection and recovery, and full network features such as arbitrary simultaneous disjoint communication. It supports one-to-one directed transmissions and one-to-many broadcasts to support as wide a set of applications as possible, and is targeted at utilities, chat programs, file-transfer programs, conference applications, and especially multiplayer and massively-multiplayer gaming on TI graphing calculators. As of this publication, the CALCnet2.2 protocol has been written into Doors CS 7.1, a shell and GUI developed for calculators by the author. It can be used on TI-83+, TI-83+ Silver Edition, TI-84+, and TI-84+ Silver Edition graphing calculators, as well as via the TI-84+ emulator on the TI-Nspire calculator.

Routines #

CALCnet2.2 is designed to be relatively easy for programmers to use with minimal complications and training, and to that end exposes only four functions to assembly programmers, two of which need not be used in general programs. The four functions are as follows:

  • Cn2_Setup - Initialize the CALCnet2.2 system, including starting the interrupt and setting up the memory areas and buffers to be used by CALCnet . Once this routine has been called, 549 bytes starting as SavesScreen ($86EC) will be used by CALCnet and should not be used other than the methods to clear buffers manually or automatically as outlined below. In addition, the RAM segment of the CALCnet2.2 interrupt is stored in 42 bytes starting at AppBackupScreen+295d ($9999) and ending at AppBackupScreen+337d ($99C3), and the jump table uses 257 bytes from AppBackupScreen+398d ($9A00) to AppBackupScreen+655d ($9B01).
  • Cn2_Setdown - Disabled the CALCnet2.2 interrupt, after which the CALCnet buffers can be used again as normal safeRAM.
  • Cn2_ClearSendBuf - Clears the send buffer, a total of 256+5+2=263 bytes. In practice, the receive buffer may be conceptually cleared as far as the interrupt is concerned simply by resetting the MSB of the size field in the buffer, after which you may receive a new frame.
  • Cn2_ClearRecBuf - Clears the receive buffer, a total of 256+5+2=263 bytes. In practice, the send buffer may be conceptually cleared as far as the interrupt is concerned simply by resetting the MSB of the size field in the buffer, after which you may load in a new frame.
  • Cn2_GetK - Returns the Key Pressed in A, De-bounced.\

It has been shown that these sets of functions and features are sufficient to create a large set of network-aware games and applications. A screenshot of a sample application that has been released called NetPong v1.0 can be seen below.

Coding Practices #

There is one additional function that CALCnet2.2 programs should use; because the TI-OS interrupt that handles GetCSC is disabled while CALCnet is active, programmers should use call Cn2_GetK instead. It will debounce the keys entered, so for non-debounced, repeating keypresses like movement in a game, direct input should be used. Programs should also be careful of TI-OS ROM calls (bcalls) that disable or enable interrupts, such as _RunIndicOff and _RunIndicOn, and use alternatives for such routines. The CALCnet2.2 interrupt will handle turning the calculator off and on via the [ON] key, so user programs need not provide this functionality. Finally, be aware that during the CALCnet interrupt every calculator, regardless of type, will be running in 6MHz mode. Programs that require 15MHz mode are recommended not to simultaneously use 15MHz mode and CALCnet2.2: although the interrupt will properly handle transitioning to 6MHz mode for its duration and returning to 15MHz mode before the user program resumes execution, 15MHz mode will cause the CALCnet interrupt to trigger at 275Hz instead of 110Hz, thus spending proportionally more time in the interrupt than in the user program, and possibly nullifying the objective of 15MHz mode (ie, faster execution of the user program). An alternative here is to use the CALCnet2.2 prehook and do time slicing with your interrupts, doing this can keep CALCnet2.2 in the roughly 110hz ballpark.

Memory Areas #

As mentioned previously, CALCnet2.2 relies on the use of buffers to send and receive data. Programs need not call any routines in order to initiate transmission or reception of data, allowing for asynchronous, nonblocking communication. This means that a program can leave data to be sent over the network in a buffer and continue to execute; CALCnet will handle attempting to send the specified data until it succeeds. Similarly, the interrupt listens constantly (technically, 110 times per second) for data to be sent to the calculator, and will store any such data in a buffer until fetched by the userland program. Needless to say, it is important to periodically check for received data and remove it so that CALCnet can accept another frame. The memory areas used by CALCnet are enumerated in the table below and are further enumerated in the remainder of this section.

Address Offset Size Function
$86EC 0 2 not for userland user
$86EE 2 5 Current calc’s ID (do not modify)
$86F3 7 5 Receive buffer sender ID
$86F8 12 2 Receive buffer size word
$86FA 14 256 Receive buffer
$87FA 270 5 Send buffer receiver ID
$87FF 275 2 Send buffer size word
$8801 277 256 Send buffer
$8901 533 16 not for userland use

Memory Areas to Avoid #

CALCnet requires that you not overwrite its interrupt stub code, starting at AppBackupScreen+295d ($9999) and ending at AppBackupScreen+337d ($99C3). You must also not overwrite the interrupt’s jump table, which uses 257 bytes from AppBackupScreen+398d ($9A00) to AppBackupScreen+655d ($9B01).

CALCnet2.2 PreHook #

DoorsCS 7.2 Beta 3 and up ONLY!
CALCnet2.2 provides a hook that when used will run custom code inside the calcnet ISR. When the hook is called, the calculator will be in the following state:

  • The clock will be set in 6Mhz mode, and should not be changed.
  • Interrupts will be disabled
  • The shadow registers will be swapped in.

To enable the hook, write an address to the two-byte location Cn2_Hook_Pre.

Cn2_Hook_Pre   .equ SavesScreen+512+28+3+3+1+1         ;8910h
ld hl,HOOKADDRESS
ld (Cn2_Hook_Pre),HL

To disable it, zero out the word.

Caveats #

It has been found that there are several TI-OS related caveats of which to be aware when using CALCnet2.2. All such caveats are due to the interactions between the TI-OS and user interrupts (Interrupt Mode 2 ISRs, such as CALCnet). It has been found that certain TI-OS bcalls, especially those that potentially interact with the LCD or Flash, maybe improperly disable interrupts including the CALCnet ISR. Known culprits including _vputs and _vputmap, but other ROM calls are likely to cause problems. It is strongly recommended that programs that using the CALCnet2.2 ISR re-enable interrupts via ei after one or more of such calls. Use this if there are sections of your code where you are unsure if interrupts are enabled, so the state will stay preserved after your code executes. (thanks to Brenden “Calc84Maniac” Fletcher for his assistance):

   ;begin protected section
   xor a
   push af
       pop af
   ld a,i
   jp pe,sectionbody
   dec sp
   dec sp
   pop af
   add a,a
   jr z,sectionbody
   xor a
sectionbody:
   push af
       ;di ;uncomment if interrupts should be disabled here
       ;------code that may have disable interrupts or-------
       ;------need interrupts disabled goes here-------------
       pop af
   jp po,sectionend
   ei
sectionend:

Finding Network Members #

CALCnet2.2 does not contain an explicit method of finding network members; the send and receive routines were initially designed to be purely point-to-point. Programs that already know the 5-byte address of the receiving calculator can simply fill in that address in a frame to be sent, and the data will arrive at the receiver. However, for dynamic networks, it will be necessary for a program to find other calculators in the network. In order to do so, each calculator can be set to periodically broadcast its address to other calculators during a discovery phase or even as a packet type during normal operation. Because CALCnet2.2 only defines at most the bottom three layers of the OSI model (physical protocol, the data layer, and the physical layer), it is up to each program to define the meaning of the contents of the data section of frames. However, if a program puts a frame in its calculator’s CALCnet send buffer with a 5-byte receiver address of 000000, CALCnet will interpret that address as indicating a broadcast frame, and will send that frame to every calculator on the network. Note that broadcast transmission is somewhat less reliable than point-to-point transmission. Whereas point-to-point guarantees that once the data disappears from the sender, the receiver has successfully received and acknowledged the data, successful transmission of a broadcast frame does not guarantee that every or even any calculator has received the data. Repeated broadcast transmission of the broadcast packet should offer a reasonable chance that each calculator will receive the packet, but the broadcast should not be continually attempted to avoid monopolizing the network.

Sending #

To send data via CALCnet2.2, a calculator program must first place the receiver ID, the frame contents, and the size of the frame to be transmitted in the associated memory areas, in that order (see the table above). The order is important because CALCnet notes whether a frame is pending transmission by checking the most significant bit of the two-byte send buffer size word. Because the z80 follows the little-endian storage model (the less significant byte of a word is stored before the more significant byte), the most significant bit is bit 7 of the byte at $8800 (see the table above if the derivation of this address is not clear). If that most significant bit is reset, the CALCnet2.2 interrupt assumes that there is no frame pending transmission. Once the bit is set, CALCnet may begin transmitting the frame at any point thereafter. Due to the asynchronous nature of interrupts, the interrupt may fire at any time, so it is vital that the high bit of the size word must only be set when the low size byte, receiver ID, and frame data contents have already been written. To facilitate direct copying into the buffer from RAM or ROM via ldir or an equivalent, the size may be copied with the MSB unset, then the MSB of the size word’s high byte should be set. The biggest danger of prematurely setting the bit and thus prematurely indicating to the CALCnet interrupt that a frame is pending transmission is that a partially-complete frame may be transmitted. Testing and verification shows that this can happen frequently under real-world operation if the caveats dictated in this paragraph are not followed.

Once a completed frame has been placed in the send buffer, the interrupt may take a number of seconds to transmit the given frame in the range [ε,∞), in practice roughly [0.01,∞) seconds. Transmission will take a finite amount of time if the receiving calculator is on the network and the network is sufficiently non-noisy and has low enough end-to-end latency to ensure proper transmission. A back-of-the-envelope calculation, taking the minimum granularity of network time as the unrealistically strict value >10μs, dictates that a maximum transmission distance based on the speed of light is 29979 meters, or 30 kilometers or 18 miles. Therefore, any transmission over a wide-area network should be performed via an intermediary, such as the planned globalCALCnet (gCn) system. If the receiving calculator is not on the network, for example if the receiver’s ID was incorrectly entered into the associated field in CALCnet send buffer, then the transmission will take an infinite amount of time, as the remote calculator will never acknowledge the packet. Transmitting calculators should provide a means of removing a frame from the CALCnet send buffer if it has been pending for a long time and has not been send successfully; it is up to the programmer to decide how to do this. One recommended method follows in the code listing below:

xor a
halt              ;halt to ensure that the store
ld (8800h),a      ;happens immediately after an interrupt

A program will almost definitely need to know when it is safe to load a new frame into the CALCnet send buffer. This can be done via the same method that the interrupt itself uses, ie, loading the byte at (8800h), masking off the most significant bit, and loading a new frame if and only if that bit is reset (zero). One note: the MSB is not considered part of the size, so that a size of $8002 is considered a 2-byte frame, not a 32770-byte frame.

Receiving #

Receiving a packet carries similar caveats and complexities as transmission. As with transmission, there is a single bit to read and write to indicate the presence or absence of pending data. As with transmission, there are three field in the relevant buffer: the two-byte size of the data (for which a value of $8004 is considered a 4-byte frame, not a 32772-byte frame), the five-byte ID of the sending calculator, and the 1- to 255-byte data section. As with transmission, broadcast frames must be handled, although from the receiver side, a broadcast frame is indistinguishable from a normal directed (point-to-point) frame. Finally, in symmetry to the transmission routine, the user program must clear the present-data bit when it has read the data out of the receive buffer in order to allow the CALCnet2.2 interrupt to accept another frame.

As with transmission, the memory areas relevant to receiving data and their locations and sizes are in the table above. The receive buffer will contain a size word indicating the size of the data in the buffer; the data is only valid if the highest bit of the size word is set. CALCnet uses the receive buffer to store frames currently in transit to this calculator before each frame’s checksum is verified, so only when the high bit of the size word is set should the data be read. Once the receive buffer is used, the CALCnet2.2 will ignore further frames transmitted to this calculator until the receive buffer is cleared, as indicated by the high bit of the size word being reset, so to maintain a low-latency network, the receive buffer should be checked and handled as often as possible.

Using Debugging Mode #

CALCnet has a built-in set of debugging routines that can help you debug CALCnet-based programs. When you enable debug mode, the bottom row of the graph buffer will be modified with a series of pixels to indicate the current state of the CALCnet interrupt. If you wish to see this debug information in anything close to realtime, your program should be running a fairly tight loop that includes iFastCopy at the same time. To turn on debug mode, you must do the following:

ld a,1
ld ($890F),a   ;Turn on debug display

The output is grouped in sets of 8 pixels at a time. I’ll call the leftmost 8 pixels “group 0”, and the rightmost 8 pixels “group 11”, since the display is 12 groups of 8 pixels wide.

  • Group 0: Rotates by 1 pixel each time the interrupt begins
  • Group 1: Rotates by 1 pixel each time the interrupt thinks a calculator is holding the lines to send a frame
  • Group 2: Rotates by 1 pixel each time the interrupt prepares to send a frame, regardless of whether the send succeeds or collides or isn’t acknowledged
  • Group 10: Bitmask relevant to the current state of Direct USB CALCnet
  • Group 11: Changes between 0xff and 0x00 each time a collision is detected

More Information #