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 (bcall
s) 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 #
- Additional information about the CALCnet2.2 protocol can be found in the CaLCnet2.2 Whitepaper, available here: https://www.ticalc.org/archives/files/fileinfo/433/43304.html
- A tutorial by a Cemetech member (geekboy1011) can be found here: Calcnet 2.2 For The Simple Minded
- If you’re trying to interface over direct USB directly to the computer without a gcnclient in between, check out Direct USB to gCnClient Connection by Sorunome
- You may also request assistance on the Cemetech forum in the CALCnet section