Absolumar. That's how the userspace program knows that a frame is pending in the buffer.
Well, I'll fetch new captures from the wire, but I can't see any checksum bytes in the frames sent by the 84+SE [while it's running Netpong] ?
There are only 13 bytes here:
* receiver ID = 00 00 00 00 00;
* sender ID = 0A 34 44 12 B6;
* length bytes = 01 80;
* data byte = 01.

EDIT: my 89T now sends
00 00 00 00 00
09 20 23 22 67
01 80
but it does so ~30% too fast (~22ms instead of ~32 ms for the prolog + 13-byte frame).
For the first milestone of making the 84+ accept a packet sent by the 89T, I'll just hack up the timings; however, such a large difference wrt. my estimations shows that the calibration step is the highest priority task after milestone #1. I knew, from the beginning, that the calibration step was necessary, since the older TI-68k calculator models run at a slower pace than the newer models.
Milestone #2 will be the successful implementation of the calibration step, yielding the 89T HW4 and the 89 HW2 being both detected by the reference implementation running on the 84+SE.

With my setup, I can participate in testing christop's 86 implementation (but not in modifying it), or other implementations Smile
I guess I'll set up a Git repository on github for mine.
Ooooh, you're actually right, but I can't understand why I omitted the checksum for the broadcast case at all. Sigh. Sad
Well, you can fix this glitch by upgrading the CALCnet code in DoorsCS Smile
Anyway, users have to upgrade the DCS FlashApp in order to receive the DUSB gCn code.

The timings produced by my implementation are now very similar to those produced by the reference implementation (without having to tweak the 89T timings for the 89 HW2, which was another unexpected event).

But the 84+SE and the 83+SE running Netpong still don't detect the 89T HW4 and the 89 HW2. And it doesn't seem to be due to phase 2 of the bit-level protocol of the first byte being missing in the reference implementation.

Current state of the code:

 * TI-68k implementation of the CALCnet 2.2 protocol defined by Christopher Mitchell "Kerm Martian" at Cemetech.
 * Code: Lionel Debroux (TICT) 2011.
 * Debugging: Lionel, Kerm, Christopher Williams ("christop").
 * Hardware adapter (female 2.5mm stereo jack <-> female 3.5 mm stereo jack) soldered by Jean-François Debroux.
 * This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details.

#define USE_TI89
#define USE_TI92P
#define USE_V200

#define MIN_AMS 100

#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 INT_HANDLER saved_int_1;*/
static volatile uint32_t int5_ticks = 0;
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.

//! New AUTO_INT_5 handler: count ticks, but don't execute the AMS timers and battery checker at a non-default rate.
    uint32_t new_int5_ticks = int5_ticks;
    if (new_int5_ticks == 4) {
        new_int5_ticks = 0;
        send_frame(frame, 13);
    int5_ticks = new_int5_ticks;

// 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.

.set DATA_READ_BIT,   3

| Write clock
    bset    #CLOCK_WRITE_BIT,(%a1)

    bclr    #CLOCK_WRITE_BIT,(%a1)

| Write data
    bset    #DATA_WRITE_BIT,(%a1)

    bclr    #DATA_WRITE_BIT,(%a1)

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

    sf.b    (%a1) | clr.b (%a1)

    move.b  #0x2,(%a1)

    move.b  #0x1,(%a1)

| Read clock / data / both
    btst    #CLOCK_READ_BIT,(%a1)

.macro READ_DATA
    btst    #DATA_READ_BIT,(%a1)

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

.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   #33-1,%d0
    dbf     %d0,0b

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

    moveq   #101-1,%d0
    dbf     %d0,2b

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

| A dumb listening check, will not send anything unless the line has been free for long enough (>= 833 us)
| TODO: a better check !
| It listens for up to 2500us for 833 continuous microseconds of silence. If it gets to that, it starts sending. If after 2500us it still hasn't seen 833 continuous us of silence, it does not send.
    move.w #500,%d2
    move.b (%a1),%d3
    dbne   %d2,loop_listen
    addq.w #1,%d2
    bne.s  end

| Compute checksum
| TODO: compute checksum incrementally !
    move.w  %d0,%d3
    lea     12(%a0),%a0
    sub.w  #12+1,%d0  | len
| TODO: handle improper lengths
    moveq   #0,%d1  | val
    moveq   #0,%d2  | accumulator for 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
| 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  #13500-1,%d0
    dbf     %d0,loop_jamming


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

    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
|    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

    move.w  %d3,-(%sp)
| Both high for 52 + 110 µs
    move.w  #314-1,%d0
    dbf     %d0,loop1_byte

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

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

    move.b  (%a0)+,%d1

    moveq   #8-1,%d3
| From bit 7 (MSB) down to bit 0 (LSB)
    rol.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
    dbf     %d3,loop_send_bit

    move.w  (%sp)+,%d3

|    .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);


    /*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
    _memset(frame, 0, 5);    // Receiver ID, 0 for broadcast
    memcpy(frame+5, calc_id, 5); // Sender ID
    frame[10] = 0x01;          // Length, little endian 0x0001 + MSB set (the reference implementation lets this bit set)
    frame[11] = 0x80;
    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);
    // 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);


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

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

Lionel, true, but I'd also have to upgrade all the gCnClients and all the Arduino firmware. I guess it wouldn't be the end of the world. What does everyone think? I feel like I haven't really seen any errors indicative of corrupted broadcasts.
Yes, it's true as well.

Any idea why the 84+SE and the 89T/89 are not interoperable yet ? Smile
KermMartian wrote:
Lionel, true, but I'd also have to upgrade all the gCnClients and all the Arduino firmware. I guess it wouldn't be the end of the world. What does everyone think? I feel like I haven't really seen any errors indicative of corrupted broadcasts.

I think checksums are important, even for broadcast frames, and since the implementation in DCS deviates from the spec in at least a few ways (I count 3 so far), I think it would be best to update the code. Code updates don't necessarily have to happen right away because the differences shouldn't completely hamper interoperability (except some clients won't accept broadcast frames if they don't see the checksum).

The high bit in the payload length can be masked out easily enough in the receiver, so it may be sufficient to update the spec with a note that the high bit may be set and that receivers should ignore it.

Regarding the missing phase 2 in the byte-level protocol for the first byte that Lionel mentioned, that could be changed in the spec by moving that phase to the end of the byte protocol, since that appears to be what the reference implementation is actually doing, and it also shouldn't affect interoperability with anything but a picky receiver implementation.

EDIT: Lionel, could you post or email the sound files that you captured to me so I can compare them to mine and iron out any differences in my code? Also, I like the license you chose for your code. Very Happy
The Audacity 1.3.12 beta project containing the capture of the 84+, 89T HW4, 89 HW2 and probably 89T HW4 again, is available at http://tict.ticalc.org/beta/calcnet_capture_3.tar.bz2
My code is probably not perfect, though, since the reference implementation doesn't understand the signals it produces.

Before this partial implementation of CALCnet for TI-68k calculators, I already used the WTFPL for tvnoise (which I could now listen to, through the adapter), and amspatch (but not tiosmod) Smile
The timing of your code looks spot-on, aside from the first byte. I couldn't see anything wrong with the bit times or anything.

I imported my recording into your project and I can clearly see that mine runs about 50% faster than the others:

I was trying to match the times in the whitepaper, but the reference implementation runs much slower than that. I'll try to match the reference timing and upload a new version (though I'm going out of state for over a week in a couple days so I might not get to this until after my trip).
Yes, mine used to be too fast as well, though it was supposed to match (approximately) the timings described in the whitepaper.
Your implementation seems to pull down the clock line again, a couple bytes after pulling it up - are you sending packets at a high rate ?

It will be interesting to see if, after changing the timings, your 86 can be detected by the 84+. If it can be detected, then it's likely that voltage on the wires makes a difference.

Kerm: to help us debugging, it would be great to have a sniffer (printing the received packets to the screen, or to a file) running on the 83+/84+, on top of the reference implementation built into DCS Smile
I'm quite concerned to hear that the timing numbers quoted in the whitepaper are inaccurate, as I had thought I had carefully checked them. It definitely behooves me to double-check them all, I suppose. Smile Christop, I of course agree that broadcast frames should at least have a checksum tacked on the end, and I'm frustrated that I omitted that. My hesitation is to have yet another Doors CS version in the wild that completely breaks interoperability, because that caused a lot of confusion and headaches when DCS 7.1.1 came out and contained CALCnet2.2 code that fixed a bug present in 7.1 and earlier.

I'm also concerned to hear that something is missing in the reference implementation regarding Phase 2, and slightly confused; would you mind clarifying that?
The screenshots we posted, http://tict.ticalc.org/beta/calcnet.png and http://i3.photobucket.com/albums/y64/abbrev/calc/calcnet2.png , show that for the first byte of a frame, right at the end of the ~10 ms jamming period, the the reference implementation (the top-most waveform) has the clock line pulled high for a shorter period than for the subsequent bytes Smile
I modified my code to force a shorter duration for the first byte, but it did not enable the 84+SE to understand the 89T.

Before releasing a new version of the whitepaper and possibly breaking interoperability once again, perhaps we should wait for more progress on both christop's and my implementation of the protocol ?
My implementation does not currently contain any code for receiving frames, and even the frame sending code does not support checksums.
Ah, I see exactly what you mean, but I don't see a reason in my code for that to be the case (nor did I have to make my receiver code do anything special for the first byte). I would expect having every byte, first or otherwise, pull the clock line high for the same amount of time would work properly based on that fact. I'd be happy to throw together a program that would display each frame received on the LCD, but I suspect that's not precisely what you're requesting. Smile
I'd be happy to throw together a program that would display each frame received on the LCD, but I suspect that's not precisely what you're requesting. Smile

I think that being able to read the raw frames (as produced by the listener part of the CALCnet reference implementation) - even if their checksum is invalid, or has other oddities - would give us a way to see what's wrong with the frames sent by other implementations.
I'd be pretty curious to see what's wrong, from the POV of the 83+/84+, with the frames sent by the 89T & 89 - given that it's neither the timings, nor the bits' values (besides the sender ID, of course), both matching closely those of the reference implementation.

But maybe the CALCnet API / implementation, in its current form, doesn't allow accessing raw, invalid frames from a client program ?
It definitely does not, unless I were to hack you a special version of Doors CS that cut out some stuff to give me space to do that in the interrupt, which might be a bit awkward. I'm staring at your traces and I'm not seeing any great reason why the 84+ wouldn't be listening to your frames. Sad
Lionel Debroux wrote:
But maybe the CALCnet API / implementation, in its current form, doesn't allow accessing raw, invalid frames from a client program ?

A client program could access invalid frames just by reading the receive buffer regardless of the high bit in the payload length (unless DCS clears the entire receive buffer when it receives an invalid frame). The program won't really know when a new invalid frame arrives, but this technique can still be useful for debugging.
An excellent point! The only time that could be still impossible is if even the receiver ID is getting messed up, so the hope would be that at least the receiver ID is correct.
KermMartian wrote:
An excellent point! The only time that could be still impossible is if even the receiver ID is getting messed up, so the hope would be that at least the receiver ID is correct.
That's true. In my implementation, I actually store the receiver ID for the client to read, so a program can theoretically take different actions depending on if the frame was broadcast or not. That would make it easier to see if it is receiving the receiver id correctly.

Could you make a "debug" version of DCS (just for us to use) that saves the receiver id in the receive buffer, and then also write a simple application that prints out the receiver id and the rest of the frame? That would hopefully not require too many changes to DCS nor require more space, aside from 5 more bytes of RAM for the the receiver id.
It actually already stores it so it can check if it's correct; I just need to tell you guys what the address of that is (it's Cn2_SafeRAM_7b .equ SavesScreen+512+5+2+14 ;8901h). Perhaps I shall make you guys a tiny little program that will constantly flush the contents of that location and the receive buffer to the LCD for you guys?
That would be great Smile
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 8 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