elfprince13 wrote:
page 1: "GUI developed by calculators by the author"

Also, feel free to list me as Thomas "elfprince13" Dickerson in the acknowledgments.
Thanks elfprince! Done and done.
Wonderful, I didn't see any other typos in my perusal of the write-up, and hopefully someone else can confirm that to be the case.
elfprince13 wrote:
Wonderful, I didn't see any other typos in my perusal of the write-up, and hopefully someone else can confirm that to be the case.
Great. I have the second half of my hard copy to peruse in the morning; I found a few things here and there, and I'm running a spellcheck at the moment. I'll probably release this whitepaper with a video of CALCnet2.2, so the time frame is probably Fridayish for release of the two, at which point I'll try to offer a beta of DCS 7.1.
*bump* So the whitepaper has been out for almost a week now, and I haven't heard any significant criticisms thus far. I know I'm way past any deadlines I had set for releasing a beta or even the final version of Doors CS 7.1, but rest assured that it will be delivered soon.
Hi Kerm,

I found an inconsistency in the CALCnet 2.2 Whitepaper. In the Byte-Level Protocol section, it describes the 8-bit transmission time as being only ~2600 cycles/500us, but in the Bit-Level Protocol section, it says each bit lasts for 624 cycles/104us. If each bit lasts for 104us, then a whole byte would take 832us. On the other hand, if a byte is transferred in 500us, then each bit would have to be only 62.5us long.

Also, have you considered using a 16-bit CRC instead of a simple 16-bit modulus checksum? CRC would be able to catch bit errors somewhat better than the checksum. I've seen a table-driven function that can calculate a 16-bit CRC check value in about 74 cycles per byte (excluding saving/restoring registers, etc). The function can be run after each byte (it doesn't have to be run over a whole buffer at once), so it should easily fit inside the synchronization time before each byte is transferred.

By the way, what is the significance in the two separate time periods labeled (1) and (2) in Figure 3? There is no transition in the clock nor the data line between the two, and the two times (52us, 110us) don't line up with a 110Hz interrupt, as far as I can tell.

EDIT: I was also wondering about how CALCnet handles timeouts. For example, if a receiver is waiting for the next bit or byte, how long should it wait before timing out? I assume it shouldn't wait more than a few milliseconds, otherwise the calculator would basically lock up waiting for more data from the sender that might never come.
Regarding the byte time inconsistency, that certainly is a head-scratcher; I can only assume I meant around 832us, not 500us. Sorry for the confusion. I have indeed considered using a 16-bit CRC, but in my practical testing, bit errors occurred so infrequently, even on a noisy 12-branch hub with a lot of unterminated, floating ends, that it just wasn't worth it. The difference is that section (1) is simply a timing spacing to ensure the receiver(s) were able to catch up, while in (2) it actively listens to make sure that no link activity occurs. There's really no great engineering rationale for no watching for link activity during (1), I suppose. Some astute questioning there, sir! I needed you around when I was first proposing the protocol. Wink
How does CALCnet handle timeouts? The whitepaper doesn't go into detail on those. How long does the receiver wait for each bit and byte before it times out? Similarly, how long does the transmitter wait for the receiver to send the checksum value back?
Regarding your first question, for bits:
Code:
   ld hl,40
Cn2_Int_GetByte7:
   dec hl
   in a,(0)
   and ClockMask
   cp ClockLow
   jr z,Cn2_Int_GetByte8
   ld a,h
   or l
   jr z,Cn2_Int_GetByte_PopFail
In other words, 10+40*(11+6+7+7+7+4+4+7) = 10+40*(53) cycles = 2130 cycles = 355 us. For bytes, it's also 2130 cycles, so the answer is 355us in all cases.
KermMartian wrote:
Regarding your first question, for bits:
Code:
   ld hl,40
Cn2_Int_GetByte7:
   dec hl
   in a,(0)
   and ClockMask
   cp ClockLow
   jr z,Cn2_Int_GetByte8
   ld a,h
   or l
   jr z,Cn2_Int_GetByte_PopFail
In other words, 10+40*(11+6+7+7+7+4+4+7) = 10+40*(53) cycles = 2130 cycles = 355 us. For bytes, it's also 2130 cycles, so the answer is 355us in all cases.

Thanks for the info.
Do you have a version of DCS7, or at least CALCnet, that runs on a plain TI-83? My sister has one (and she's willing to let me borrow it Smile), so if I can run CALCnet on it I can ensure that my code is compatible with yours. I'd like to get a version working on the TI-86 and the 92+ (in Punix, of course) if possible.
There actually is still a TI-83 version of Doors CS that gets published with each new build of Doors CS, but it doesn't have any CALCnet code in it, sadly. Sad
Alright! I whipped up some pseudo-code just now for CALCnet. Can you look at this and see if there's anything missing or wrong before I try to implement it in asm?

Code:

sending a bit:
   set data line to appropriate level
   wait 17us
   pull clock low
   wait 35us
   release clock and data
   wait 52us
   return

sending a byte:
   keep data and clock high
   wait 52+110us (and sense if anyone else is pulling these low)
   pull clock and data low
   wait 52us
   release clock and data
   wait 43us (and sense if anyone else is pulling them low)
   send 8 bits
   return

sending a frame:
   set counter to 3
loop:
   wait up to 833us (5000 cycles) or until data or clock are pulled low by
     another calc
   if data or clock were pulled low, decrement counter and goto loop if
     counter > 0
   if counter == 0, return and wait for the next interrupt
   pull clock low
   wait 9ms
   release clock
   send recvrid (5 bytes)
   send sendrid (5 bytes)
   send payloadlen (2 bytes)
   send payload (1-256 bytes)
   send checksum (2 bytes)
   get checksum from receiver (2 bytes)
   if checksums match, send ACK (1 byte)
   else send NAK (1 byte)
   mark frame as sent
   return

receiving a bit:
   wait up to 355us or until clock is pulled low
   if clock is not pulled low, goto timeout
   read data bit immediately
   wait up to 355us or until clock is released
   if clock is not released, goto timeout
   return bit value

receiving a byte:
   wait up to 355us? or until clock and data are pulled low
   if clock and data are not pulled low, goto timeout
   wait up to 355us? or until clock and data are released
   if clock and data are not released, goto timeout
   receive 8 bits
   return byte

receiving a frame:
   if clock is not pulled low, return
   wait up to 9ms (or a little bit longer?) or until clock is released
   if clock is not released, goto timeout
   receive recvrid (5 bytes)
   if recvrid is not broadcast and is not our calcid, return
   receive sendrid (5 bytes)
   receive payloadlen (2 bytes)
   receive payload (1-256 bytes)
   receive checksum (2 bytes)
   if recvrid is broadcast, goto complete_frame
   send our checksum (2 bytes)
   receive ack/nak (1 byte)
   if nak or timeout, return
complete_frame:
   if checksum does not match our checksum, return
   mark frame as received
   return

timeout:
   return timeout condition


Note: sending a bit can be inline with sending a byte, and likewise with receiving a bit/byte, but they are shown here separately for clarity.
I don't see anything terribly wrong with that, but I should warn you that I am extremely tired and may have overlooked possible problems. I will proofread it once again in the morning.
FWIW, here's the development code I've made between four and three weeks ago (and sent for backup to Kerm, of course). It tries to produce the handshake frame used by Netpong and Flourish; however, it's not using the CALCnet interrupt-based API described in the whitepaper Smile

The reference implementation (TI-Z80) doesn't understand the frames produced by this code, and I haven't yet used an oscilloscope with a memory at work, outside work hours, to debug the problem...


Code:
#define USE_TI89
#define USE_TI92P
#define USE_V200

#define MIN_AMS 100
#define OPTIMIZE_ROM_CALLS
#define SAVE_SCREEN

#include <alloc.h>
#include <cert.h>
#include <flash.h>
#include <graph.h>
#include <intr.h>
#include <inttypes.h>
#include <kbd.h>
#include <link.h>
#include <peekpoke.h>
#include <stdio.h>
#include <string.h>
#include <system.h>

static volatile uint32_t int1_ticks = 0;
static volatile uint32_t int5_ticks = 0;
static INT_HANDLER saved_int_1;
static INT_HANDLER saved_int_5;

static uint8_t frame[30];

__attribute__((__regparm__(2))) void send_frame(uint8_t * ptr asm("%a0"), uint16_t len asm("%d0"));

//! New AUTO_INT_1 handler: count ticks, and execute AMS's handler, which handles keyboard reading.
DEFINE_INT_HANDLER(MyInt1)
{
    int1_ticks++;
    ExecuteHandler(saved_int_1);
}

//! New AUTO_INT_5 handler: count ticks, but don't execute the AMS timers and battery checker at a non-default rate.
DEFINE_INT_HANDLER(MyInt5)
{
    int5_ticks++;
    send_frame(frame, 13);
    //ExecuteHandler(saved_int_5);
}

// Notes:
// * clock -> tip
//   * SET bit 0 of 60000E to pull down
//   *     bit 2 of 60000E SET means pulled down.
// * data -> ring
//   * SET bit 1 of 60000E to pull down
//   *     bit 3 of 60000E SET means pulled down.

// Length is currently limited to 255 bytes.
asm("

.set CLOCK_WRITE_BIT, 0
.set DATA_WRITE_BIT,  1
.set CLOCK_READ_BIT,  2
.set DATA_READ_BIT,   3

| Write clock
.macro PULLDOWN_CLOCK
    bset    #CLOCK_WRITE_BIT,(%a1)
.endm

.macro PULLUP_CLOCK
    bclr    #CLOCK_WRITE_BIT,(%a1)
.endm

| Write data
.macro PULLDOWN_DATA
    bset    #DATA_WRITE_BIT,(%a1)
.endm

.macro PULLUP_DATA
    bclr    #DATA_WRITE_BIT,(%a1)
.endm


| Write clock and data at the same time
.macro PULLDOWN_BOTH
    st.b    (%a1)
.endm

.macro PULLUP_BOTH
    sf.b    (%a1) | clr.b (%a1)
.endm

.macro PULLUP_CLOCK_PULLDOWN_DATA
    move.b  #0x2,(%a1)
.endm

.macro PULLDOWN_CLOCK_PULLUP_DATA
    move.b  #0x1,(%a1)
.endm


| Read clock / data / both
.macro READ_CLOCK
    btst    #CLOCK_READ_BIT,(%a1)
.endm

.macro READ_DATA
    btst    #DATA_READ_BIT,(%a1)
.endm

.macro READ_BOTH dest
    move.b  (%a1),dest
.endm


.macro SEND_BIT
| Clock high for 17 µs, low for 35 µs, high for 52 µs
| Data = input bit for 52 µs, high for 52 µs
    bclr    #CLOCK_WRITE_BIT,%d2
    move.b  %d2,(%a1)
    moveq   #19,%d0
0:
    dbf     %d0,0b

    bset    #CLOCK_WRITE_BIT,%d2
    move.b  %d2,(%a1)
    moveq   #39,%d0
1:
    dbf     %d0,1b

    PULLUP_BOTH
    move.b  %d2,(%a1)
    moveq   #59,%d0
2:
    dbf     %d0,2b
.endm

    .text
    .even
    .globl send_frame
send_frame:
    move.w  %d3,-(%sp)
| Direct link I/O wire access
    lea     0x60000E,%a1

| Compute checksum
| TODO: compute checksum incrementally !
    move.w  %d0,%d3
    subq.w  #1,%d0  | len
| TODO: handle improper lengths
    moveq   #0,%d1  | val
    moveq   #0,%d2  | accumulator for checksum
loop_checksum:
    move.b  (%a0)+,%d1
    add.w   %d1,%d2
    dbf     %d0,loop_checksum

| Write checksum in little-endian form
    move.b  %d2,(%a0)
    lsr.w   #8,%d2
    move.b  %d2,1(%a0)

| Jamming phase: ~9.8ms
    PULLDOWN_CLOCK_PULLUP_DATA
| dbf timings: 10 CPU clock cycles when cc false and branch taken, i.e. <~ 1 us @ ~12 MHz.
| We need to loop ~12K times.
    move.w  #12000-1,%d0
loop_jamming:
    dbf     %d0,loop_jamming

    PULLUP_BOTH

| Get back to the beginning of the frame
    suba.w  %d3,%a0
| Add checksum length
    addq.w  #2-1,%d3

loop_send_bytes:
    bsr.s   send_byte
    dbf     %d3,loop_send_bytes

| TODO for directed frames: actually listen for the checksum...
|    move.w  #(182+60+50+8*(20+40+60)),%d0
|loop_wait_checksum:
|    dbf     %d0,loop_wait_checksum

| TODO: ACK/NAK the checksum if it's a directed frame
|    lea     ack(%pc),%a0
|    bsr.s   send_byte

    move.w  (%sp)+,%d3
    rts


    .text
    .even
send_byte:
    move.w  %d3,-(%sp)
| Both high for 52 + 110 µs
    PULLUP_BOTH
    move.w  #182-1,%d0
loop1_byte:
    dbf     %d0,loop1_byte

| Both low for 52 µs
    PULLDOWN_BOTH
    moveq   #60-1,%d0
loop2_byte:
    dbf     %d0,loop2_byte

| Both high for 43 µs
    PULLUP_BOTH
    moveq   #50-1,%d0
loop3_byte:
    dbf     %d0,loop3_byte

    move.b  (%a0)+,%d1

    moveq   #8-1,%d3
loop_send_bit:
| From bit 7 (MSB) down to bit 0 (LSB)
    ror.b   #1,%d1  | Leftmost bit goes to C
| We want the data line to have the same state as the input bit, so we want the DATA_WRITE_BIT to be not(input bit)
| if carry clear (i.e. input bit is 0) then pull down (i.e. set bit) else pull up (i.e. clear bit)
| if carry set   (i.e. input bit is 1) then pull up (i.e. clear bit) else pull down (i.e. set bit)
| Sxx is [if condition xx = true then 0xFF -> dest else 0 -> dest]

    scc     %d2
    SEND_BIT
    dbf     %d3,loop_send_bit

    move.w  (%sp)+,%d3
    rts

|
|ack:
|    .byte   0xAA
|    .even

");

//! Where all the fun starts...
void _main(void) {
    short saved_int5_rate = PRG_getRate();
    HANDLE cert_handle;
    unsigned long cert_len;
    CFILE context;
    CERT_FIELD field;
    unsigned short i;
    uint8_t calc_id[5];


    int1_ticks = 0,
    int5_ticks = 0;

    // Retrieve unique calculator ID, the proper (but large and slow) way.
    // The improper way, with very little chance of false positives, would be searching for 0A 15 <5 bytes> 0A 2D 40.
    FL_getCert(&cert_handle, &cert_len, FALSE);
    if (cert_handle != H_NULL && cert_len > 0) {
        unsigned char *ptr = HeapDeref(cert_handle);

        copen (&context, ptr, cert_len);
        // Root cert field of the Certificate memory.
        if (cfindfield (&context, 0x330, &field)) {
            copensub (&context, &field);
            // Serial number field.
            if (cfindfield (&context, 0xA10, &field)) {
                copensub(&context, &field);
                memcpy(calc_id, context.Pos, 5);
            }
        }

        HeapFree(cert_handle);
    }

    saved_int_1 = GetIntVec(AUTO_INT_1);
    SetIntVec(AUTO_INT_1, MyInt1);
    saved_int_5 = GetIntVec(AUTO_INT_5);
    SetIntVec(AUTO_INT_5, MyInt5);

    // Disable all linking interrupts
    pokeIO(0x60000C, 0x60);

    // Setup frame
    memcpy(frame, calc_id, 5); // Sender ID
    _memset(frame+5, 0, 5);    // Receiver ID, 0 for broadcast
    frame[10] = 0x01;          // Length, little endian 0x0001
    frame[11] = 0x00;
    frame[12] = 0x01;          // Data: 0x01: first part of the handshake used in Flourish & Netpong.

    // Let's fire AUTO_INT_5 at ~109.95 Hz (very close to the 110 Hz used by CALCnet)
    /*PRG_setStart(257 - 149);
    PRG_setRate(0);
    // Check AUTO_INT_5 frequency against AUTO_INT_1 frequency (well-defined to 256 Hz on HW1).
    while (!(_rowread(0xF000))) {
        if ((int1_ticks % 1024) == 0) {
            printf_xy(0, 0, "%" PRIu32 " %" PRIu32, int1_ticks, int5_ticks);
        }
    }

    PRG_setRate(saved_int5_rate);*/

    while (!(_rowread(0xF000)));

    SetIntVec(AUTO_INT_1, saved_int_1);
    SetIntVec(AUTO_INT_5, saved_int_5);

    OSLinkReset();
    GKeyFlush();
}
Lionel, your code appears to calculate the checksum over the entire frame. It should be only over the payload data, according to the whitepaper:
Quote:
Stage (6) is a simple two-byte modulus checksum of all the bytes in the payload only


Since you're only sending one byte (0x01), the checksum should be 0x0001.

If I were to improve the protocol, I would also compute the checksum over the entire frame, similar to the CRC in Ethernet frames.
Well spotted Smile
Fixing the problem does, however, not enable detection of the 89T and the 89 by the 83+SE running Netpong.
Regarding your original question, my code seems to wait up to 8.1ms in the receive frame code, but I think 9ms is probably safer.
So I wrote a CALCnet 2.2 driver for the TI-86. It supports the following routines:
  • Cn2_Setup
  • Cn2_Setdown
  • Cn2_ClearRecBuf
  • Cn2_ClearSendBuf

It doesn't include the Cn2_GetK routine, as my interrupt calls the normal TI-OS interrupt handler, so you can use the regular GET_KEY or _getKey routines.

Total code size is 646 bytes, and total data size is 539 bytes.

I also wrote a simple test program that continuously sends a one-byte broadcast frame (byte value is 1), similar to Lionel's code. If my timing is correct, other CALCnet calculators should see the frames (you may have to run Netpong or Flourish for you to notice the frames). I don't have an 83+ or 84+, so I need other people who have an 86 and an 83+/84+ to test it out:

Download testcn2.86p here.

As soon as the program starts it sends broadcast frames, one right after another. Press EXIT to quit.

Let me know if this works or doesn't work with calculators running DCS on the same network.

Note: I don't think there is any way to retrieve the serial number of the TI-86 in code, so in this demo I hard-coded the serial number as "TI-86". This won't work very well in a network with multiple TI-86's, but it shouldn't be a problem with only one.
Well sadly I tried both netpong and flourish and neither picked up my 86 so something must be amiss. Maybe if you post your code someone can see what is causing the issues.
Ok, I suspect the length of the jamming phase might be too long for DCS to handle. I made it as long as 2 interrupts, which is about 10.9ms on the TI-86. The receiver in DCS probably times out before this jamming phase is completed. I can easily change my code to simply busy-wait for (almost) exactly 9ms if this is the case.

Anyway, here's the calcnet.asm source for you to look over:

Code:
; CALCnet driver for TI-85/86
; Copyright 2011 Christopher Williams
;
; some notes about register usage:
; c is used as the data byte in the byte sender/receiver routines
; zero flag is cleared by byte sender to indicate collision
; zero flag is set by byte receiver to indicate timeout
;
; d is used as a port IN mask for the delay_and_sense routine
; e is the delay length for the delay and delay_and_sense routines
;
; b is used for loops (djnz)
;
; bc is byte count (eg, frame size)
; hl is frame start
; de is misc (checksum, etc)

LINKPORT equ 7

; output with *_OUT_MASK set to pull that line low (0=high, 1=low)
NONE_OUT_MASK equ 0b11000000
TIP_OUT_MASK  equ 0b11010100
RING_OUT_MASK equ 0b11101000
BOTH_OUT_MASK equ TIP_OUT_MASK|RING_OUT_MASK

; *_IN_MASK is clear when that line is low (0=low, 1=high)
TIP_IN_MASK  equ 0b00000001
RING_IN_MASK equ 0b00000010
BOTH_IN_MASK equ TIP_IN_MASK|RING_IN_MASK


CLOCK_OUT_MASK equ TIP_OUT_MASK
DATA_OUT_MASK  equ RING_OUT_MASK
CLOCK_IN_MASK equ TIP_IN_MASK
DATA_IN_MASK  equ RING_IN_MASK

ACK_BYTE equ 0xaa
NAK_BYTE equ 0x42   ; anything other than 0xaa

; input:
;  E = number of loops (delay=E*16+5 cycles, not including CALL)
;   (unconditional CALL is 17 cycles)
; output:
;  E = 0
delay:
   dec   e      ; 4
   jr   nz,delay   ; 12/7
   ret         ; 10

; input:
;  E = number of loops (delay=E*40+5 cycles, not including CALL)
;   (unconditional CALL is 17 cycles)
;   (delay may be shorter if a line is pulled low)
;  D = mask for which lines to sense
; output:
;  Z flag is clear if any masked line is pulled low
;   (timeout: ret z, collision: ret nz)
;  E = 0 after a full delay
;  A is link state ANDed with the input mask (D)
wait_any:
   in   a,(LINKPORT)   ; 11
   and   d      ; 4
   cp   d      ; 4
   ret   nz      ; 5
   dec   e      ; 4
   jr   nz,wait_any   ; 12/7
   ret         ; 10

; input:
;  BC = number of loops (delay=BC*50+5 cycles)
;  D = mask for which lines to sense
; output:
;  Z is clear if any masked line is pulled low
;  A is link state ANDed with the input mask (D)
waitms_any:
   in   a,(LINKPORT)   ; 11
   and   d      ; 4
   cp   d      ; 4
   ret   nz      ; 5
   dec   bc      ; 6
   ld   a,b      ; 4
   or   c      ; 4
   jr   nz,waitms_any   ; 12/7
   ret         ; 10



; input:
;  E = number of loops (delay=E*38+5 cycles)
;  D = mask for which lines to sense (all lines must be pulled low)
; output:
;  Z is clear if all masked lines are pulled low
;  E = 0 after a full delay
;  A is link state ANDed with the input mask (D), and possibly with high bit set
wait_all:
   in   a,(LINKPORT)   ; 11
   and   d      ; 4
   jr   z,@all      ; 7
   dec   e      ; 4
   jr   nz,wait_all   ; 12/7
   ret         ; 10
@all
   or   0x80   ; clear z flag
   ret

; input:
;  E = number of loops (delay=E*42+5 cycles)
; output:
;  Z is clear if all masked lines are released, or set on timeout
wait_all_high:
   in   a,(LINKPORT)   ; 11
   and   d      ; 4
   cp   d      ; 4
   jr   z,@all      ; 7
   dec   e      ; 4
   jr   nz,wait_all_high   ; 12/7
   ret         ; 10
@all
   or   0x80
   ret

; delay=BC*46+5 cycles
waitms_any_high:
   in   a,(LINKPORT)   ; 11
   and   d      ; 4
   ret   nz      ; 5
   dec   bc      ; 6
   ld   a,b      ; 4
   or   c      ; 4
   jr   nz,waitms_any_high   ; 12/7
   ret         ; 10

; input:
;  C = byte to send
; output:
;  zero is clear on collision
;  C is complemented
;  A, B, D, E are destroyed
;  HL is preserved
send_byte:
   ld   d,BOTH_IN_MASK

   ; wait 52+110us and sense
   ld   e,24 ;(972+40)/41
   call   wait_any
   ret   nz      ; collision

   ; pull clock and data low
   ld   a,BOTH_OUT_MASK
   out   (LINKPORT),a

   ; wait 52us
   ld   e,18
   call   delay

   ; release clock and data
   ld   a,NONE_OUT_MASK
   out   (LINKPORT),a
   
   ; wait 43us and sense if anyone is pulling either line low
   ld   e,5
   call   wait_any
   ret   nz      ; collision

   ; send 8 bits
   ld   b,8

   ; complement the byte so we can output the correct levels below
   ; (unless my logic is backwards, in which case these 3 lines can
   ; be removed)
   ld   a,c
   cpl
   ld   c,a
@bit
   rlc   c      ; LSB first
   ; set data line to appropriate level (0=low, 1=high)
   sbc   a,a      ; 4
   and   DATA_OUT_MASK   ; 7
   or   NONE_OUT_MASK   ; 7
   out   (LINKPORT),a
   
   ; wait 17us
   ; do we need to sense the clock line during this delay?
   ld   e,5
   call   delay
   
   ; pull clock low
   or   CLOCK_OUT_MASK
   out   (LINKPORT),a
   
   ; wait 35us
   ld   e,11
   call   delay

   ; release clock and data
   ld   a,NONE_OUT_MASK
   out   (LINKPORT),a

   ; wait 52us
   ; should we sense for collision here too?
   ld   e,18
   call   delay

   djnz   @bit

   ret

; input:
;  hl = base address
;  bc = size
; output:
;  zero is clear on collision
;  hl = base address + size
;  de = checksum
send_bytes:
   push   bc
   ld   c,(hl)

   ld   a,e
   add   a,c
   ld   e,a

   adc   a,d
   sub   e
   ld   d,a

   push   de
   call   send_byte
   pop   de
   pop   bc
   ret   nz   ; collision

   cpi
   jp   pe,send_bytes

   xor   a   ; set z
   ret

; setting WAIT_FULL_9ms to non-zero causes send_frame to loop for the full 9ms
; clearing it to zero causes send_frame to wait 2 interrupts (~10.9ms) instead
WAIT_FULL_9ms equ 0

Cn2_ClearRecBuf:
   ld   hl,inframe
   jr   clear_frame

Cn2_ClearSendBuf:
   ld   hl,outframe
   ; fall through

; input:
;  hl = frame start
clear_frame:
   ld   bc,5+5+2+256
   xor   a
   ; fall through

; input:
;  hl = frame start
;  bc = bytes to set
;  a = byte to set
; note: bc must be at least 2, otherwise this routine will treat it as 65538!
memset:
   ld   d,h
   ld   e,l
   inc   de
   dec   bc
   ld   (hl),a
   ldir
   ret

Cn2_Setup:
   .if !WAIT_FULL_9ms
   call   reset_send_state
   .endif
   call   Cn2_ClearRecBuf
   call   Cn2_ClearSendBuf

int_addr equ 0xf8f8                ;Start of interrupt code
int_vectors equ 0xfa00

   ; set up im2 vectors
   ld   hl,int_vectors
   ld   de,int_vectors+1
   ld   (hl),int_addr/256
   ld   bc,256
   ldir

   ; set up int handler
   ld   hl,int_copy
   ld   de,int_addr
   ld   bc,int_end-int_start
   ldir

   ; set i register
   ld   a,int_vectors/256
   ld   i,a

   im   2
   ret

int_copy:
   .rorg int_addr
int_start:
   ex   af,af'
   exx
   ld   hl,0xfc00
   inc   (hl)
   call   send_frame
   ; call receive_frame only if send_frame completed
   ;call   nz,receive_frame
   jp   0x66
   exx
   ex   af,af'
   reti
int_end
   .rorg $$

Cn2_Setdown:
   im   1
   .if WAIT_FULL_9ms
   ret
   .else
   ; fall through
   .endif

   .if !WAIT_FULL_9ms
reset_send_state
   ld   hl,send_frame_start_state
   ; fall through
; input:
;  hl = state
; output:
;  none
set_send_state
   ld   (send_state),hl
   ret
   .endif

; send a frame if one is ready to send
; input:
;  none
; output:
;  Z is clear if this is completed
send_frame:
   ld   hl,0xfff0
   inc   (hl)

   ; see if frame is ready to send
   ld   hl,outpayloadlen+1
   ld   a,(hl)
   and   0x80
   jr   z,@reset_link

   push   hl
   ld   hl,0xfff1
   inc   (hl)
   pop   hl

   ; clear high bit in payload length
   xor   (hl)
   ld   b,a
   dec   hl
   ld   c,(hl)
   ; bc = payload length
   
   ; can't send more than 256 bytes
   ld   hl,256
   sbc   hl,bc
   .if WAIT_FULL_9ms
   jr   nc,send_frame_start_state
   .else
   jr   c,@reset_link

@restore_send_state
   ld   hl,(send_state)
   jp   (hl)
   .endif

@reset_link
   ld   a,NONE_OUT_MASK
   out   (LINKPORT),a
   or   a   ; clear Z (NONE_OUT_MASK is non-zero)
   .if WAIT_FULL_9ms
   ret
   .else
   jr   reset_send_state
   .endif

send_frame_start_state
   ld   hl,0xfff2
   inc   (hl)

   ; see if this is a broadcast frame
   ld   hl,outdstid
   xor   a
   ld   b,5
@broadcastloop
   or   (hl)
   inc   hl
   djnz   @broadcastloop
   ld   (notbroadcast),a

   ; set the srcid to our calcid
   ld   hl,mycalcid
   ld   de,outsrcid
   ld   bc,5
   ldir

   ; set counter to 3
   ld   b,3
   ld   d,BOTH_IN_MASK
@loop
   ; wait up to 833us or until data or clock are pulled low
   ld   e,124
   call   wait_any
   jr   z,@mine

   ; if a line was pulled low,
   ;  decrement counter and goto @loop if counter>0
   djnz   @loop

   ; if counter == 0, return and wait for the next interrupt
   or   1
   ret

@mine
   ; pull clock low
   ld   a,CLOCK_OUT_MASK
   out   (LINKPORT),a

   ; wait 9ms
   .if WAIT_FULL_9ms
   ; We could return from our interrupt at this point and come back here
   ; at the next interrupt (by saving our state information), but we will
   ; not be able to sense any activity on the data line if we do that.
   ld   d,DATA_IN_MASK
   ld   bc,1080
   call   waitms_any
   ret   nz      ; collision
   .else
   ; Here we keep the clock low for 2 complete interrupts (about 10.9ms)
   ; return to send_frame_5461us_1 after the next interrupt
   xor   a   ; set Z
   ld   hl,@send_frame_5461us_1
   jr   set_send_state

@send_frame_5461us_1
   ; return to send_frame_5461us_2 after the next interrupt
   xor   a   ; set Z
   ld   hl,@send_frame_5461us_2
   jr   set_send_state

@send_frame_5461us_2
   call   reset_send_state
   .endif

   ; release clock
   ld   a,NONE_OUT_MASK
   out   (LINKPORT),a

   ; send frame header (5+5+2)
   ld   hl,outframe
   ld   bc,5+5+2
   call   send_bytes
   ret   nz   ; collision


   ; send payload
   ld   bc,(outpayloadlen)
   ld   a,0x7f
   and   b
   ld   b,a
   ld   de,0   ; start with a fresh checksum
   call   send_bytes

   ; send our checksum
   call   send_word
   ret   nz   ; collision

   ; if it's a broadcast frame, then we're done
   ld   a,(notbroadcast)
   or   a
   jr   z,@success

   ; get checksum from receiver
   push   hl   ; save our checksum
   call   receive_word
   pop   de   ; de = our checksum
   jr   z,@timeout   ; timeout

   ;ld   hl,(checksum)
   ; hl = our checksum from earlier
   or   a
   sbc   hl,de
   jr   nz,@nak

   ; send an ack byte
   ld   c,ACK_BYTE
   call   send_byte

@success
   ; clear high bit in payload length
   ld   hl,outpayloadlen+1
   ld   a,(hl)
   and   0x7f
   ld   (hl),a
@timeout
   or   1   ; clear z
   jp   reset_send_state

@nak   ; checksums no matchy
   ; send a nak byte
   ld   c,NAK_BYTE
   call   send_byte
   or   1   ; clear z
   jp   reset_send_state


   .if 0
; input:
;  hl = base address
;  bc = byte count
; output:
;  de = checksum
;  bc = 0
;  hl = base address + byte count
compute_checksum:
   ld   de,0
add_checksum      ; call this if you want to continue a checksum
   ld   a,(hl)
   add   a,e
   ld   e,a

   adc   a,d
   sub   e
   ld   d,a

   cpi
   jp   pe,add_checksum
   ret
   .endif


; receive_byte
; input: none
; output:
;  c = data byte
;  Z flag is set on timeout
;  HL is unaffected
receive_byte:
   ld   d,BOTH_IN_MASK

   ; wait up to 355us or until clock and data are both pulled low
   ld   e,56
   call   wait_all
   ret   z   ; timeout

   ; wait up to 355us or until clock and data are released
   ld   e,56
   call   wait_all_high
   ret   z   ; timeout

   ;ld   b,8
   ;ld   c,0
   ld   bc,0x0800   ; b=8, c=0
   ld   d,CLOCK_IN_MASK
   ; read in all 8 bits
@bit
   ; wait up to 355us or until clock is pulled low
   ld   e,56
   call   wait_all
   ret   z   ; timeout
   
   ; read in the link state immediately
   in   a,(LINKPORT)
   ; A contains link state
   ; rotate link state right 2 times to get data bit (bit 1)
   rrca
   rrca
   rr   c   ; rotate that bit into C

   ld   e,56
   call   wait_all_high
   ret   z   ; timeout
   djnz   @bit

   ; return data in C (zero flag is also clear)
   ret
   ; 40 bytes :)

; input:
;  hl = base address to put bytes in
;  bc = size
; output:
;  Z is set on timeout
;  hl = base address + size
;  DE = checksum?
receive_bytes
   push   de
   push   bc
   call   receive_byte
   ld   a,c
   pop   bc
   pop   de
   ret   z   ; timeout

   ; add checksum
   add   a,e
   ld   e,a
   adc   a,d
   sub   e
   ld   d,a
   
   cpi
   jp   pe,receive_bytes

   or   1   ; clear z
   ret

; input:
;  de = word
; output:
;  Z is clear on collision
;  hl = sent word
send_word:
   ex   de,hl
send_word_hl
   ld   c,l
   call   send_byte
   ret   nz
   ld   c,h
   jp   send_byte

; input:
;  none
; output:
;  Z is set on timeout
;  hl = word
receive_word:
   call   receive_byte
   ret   z   ; timeout
   push   bc
   call   receive_byte
   pop   hl
   ld   h,c
   ret      ; return our timeout state

; input:
;  none
; output:
;  Z is set on timeout or if dstid is not broadcast id nor mycalcid
; note: this is unrolled into 3 loops:
;  1. tests both broadcast and calc id
;  2. tests for calc id
;  3. tests for broadcast id
; the first loop jumps to either loop 2 or 3 if one of the tests fails,
; and then loops 2 and 3 exit if the opposite test fails too
; this could be shortened by tracking the byte differences for each id
; (broadcast and mycalc) in 2 registers (or variables) and then failing if both
; differences become non-zero
receive_dstid:
   ld   hl,indstid
   ld   de,mycalcid
   ld   b,5
@dstidloop:
   push   bc
   push   de
   call   receive_byte
   ld   (hl),c
   pop   de
   pop   bc
   ret   z   ; timeout
   xor   a
   cp   c
   jr   nz,@not_bcast
   ld   a,(de)
   cp   c
   jr   nz,@not_mycalcid
   djnz   @dstidloop
   or   1   ; clear z
   ret

@not_bcast_loop
   push   bc
   push   de
   call   receive_byte
   ld   (hl),c
   pop   de
   pop   bc
   ret   z
@not_bcast
   ld   a,(de)
   cp   c
   inc   de
   inc   hl
   jr   nz,@not_us
   djnz   @not_bcast_loop
   or   1   ; clear Z
   ret

@not_mycalcid_loop
   push   bc
   call   receive_byte
   ld   (hl),c
   pop   bc
   ret   z
   xor   a
   cp   c
   jr   nz,@not_us
@not_mycalcid
   inc   hl
   djnz   @not_mycalcid_loop
   or   1   ; clear Z
   ret

@not_us
   xor   a
   ret

   

receive_frame:
   ld   a,(inpayloadlen+1)
   and   0x80
   ret   nz   ; we have a valid frame in the receive buffer already

   in   a,(LINKPORT)
   ld   d,CLOCK_IN_MASK
   and   d
   ret   nz   ; clock is high - no imminent transmission

   ; wait up to 9ms (or a little longer) or until clock is released
   ld   bc,1566   ; 12ms
   call   waitms_any_high
   ret   z   ; timeout

   call   receive_dstid
   ret   z   ; timeout, or frame is not for us

   ; receive srcid and payloadlen
   ; ld   hl,insrcid
   ld   bc,7
   call   receive_bytes
   ret   z   ; timeout

   ; payload len must be between 1 and 255
   ld   bc,(inpayloadlen)
   xor   a
   cp   b
   ret   nz   ; MSB must be zero
   cp   c
   ret   z   ; LSB must be non-zero
   ; CALCnet2.2 provides no provisions for informing the sender that the
   ; frame is too large for us to handle, so the sender may repeatedly try
   ; to send the same frame without any luck

   ld   de,0   ; start with a fresh checksum
   call   receive_bytes
   ret   z   ; timeout

   ; receive checksum from sender
   push   de   ; save our checksum
   call   receive_word
   pop   de   ; restore our checksum
   ret   z   ; timeout

   or   a
   sbc   hl,de
   ret   nz   ; bad checksum - we exit
         ; (should we send our checksum anyway?)
   ; send our checksum
   call   send_word
   ret   nz   ; collision

   call   receive_byte
   ret   z   ; timeout
   ld   a,ACK_BYTE   ; is it ack?
   cp   c
   ret   nz      ; not ack

   ld   hl,inpayloadlen+1
   ld   a,(hl)
   or   0x80
   ld   (hl),a      ; set MSb of payload len
   ret

mycalcid
   db "TI-86" ; XXX

@code_end
   .segu data
   .org @code_end
   ; misc variables
notbroadcast ds 1
   .if !WAIT_FULL_9ms
send_state ds 2
   .endif

outframe
outdstid      ds 5
outsrcid      ds 5
outpayloadlen ds 2
outpayload    ds 256

inframe
indstid      ds 5
insrcid      ds 5
inpayloadlen ds 2
inpayload    ds 256

   .seg code


And here is testcn2.asm:

Code:
GET_KEY equ 0x5371
K_EXIT equ 55

   .org 0xd748
   call   Cn2_Setup

   ld   hl,frame
   ld   de,outframe
   ld   bc,5+5+2+1
   ldir

@loop
   halt

   .if 1
   ; continuously send the frame
   ld   hl,outpayloadlen+1
   ld   a,(hl)
   or   0x80
   ld   (hl),a

   .else
   ; exit after the frame is sent
   ld   a,(outpayloadlen+1)
   and   0x80
   jr   z,@done
   .endif
   call   GET_KEY
   cp   K_EXIT
   jr   nz,@loop

@done
   call   Cn2_Setdown
   ret

frame:
   db 0,0,0,0,0 ; broadcast
   db 0,0,0,0,0 ; our calc id (will be filled in by Cn2)
   dw 1         ; payload length
   db 1         ; payload

   .include "calcnet.asm"
That would be better; I'd be interested to see if that fixes things. Do you have access to an oscilloscope to help you debug? This might be painful without one.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
» Goto page Previous  1, 2, 3, 4, 5, 6, 7, 8, 9  Next
» View previous topic :: View next topic  
Page 4 of 9
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement