Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Link and data transfer

The data-transfer subsystem sends variables and system objects through the packet layer over either the 2.5 mm link or the TI-84 Plus USB/link-assist path. It builds on _SendAByte (3C:420D) and _RecAByteIO (3C:443F), described in Two-wire link port hardware. USB ASIC and link assist covers the ASIC-facing ports.

Raw disassembly preserves the register-passed arguments and SET/RES/BIT b,(IY+d) state operations that the decompiler can mis-render. The silent-link engine shares Flash page 3C with archive command code.

Transfer layers

flowchart TB
    SRC(["user 'Send…' / TI-Connect"])
    subgraph VAR["Variable layer · page 3C"]
      LX["link_xfer_op 3C:4DD2<br/>silent-link variable send"]
      SV["_SendVarCmd 3C:4A14→4EDD<br/>DI / cleanup-wraps a send"]
    end
    subgraph PKT["PACKET layer"]
      direction LR
      SH["send header 41C3"]
      RH["receive header 4338"]
      SD["send DATA 40DA"]
      RD["receive DATA 4292"]
      AK["send ACK 42FB · cmd 0x56"]
      CK["checksum 4167 / 6356"]
    end
    subgraph BYTE["BYTE layer · keyboard & link"]
      direction LR
      SB["_SendAByte 420D"]
      RB["_RecAByteIO 443F"]
      HW["bit-bang port 0 + HW-assist FIFO ports 8/9/0D"]
    end
    SRC --> VAR --> PKT --> BYTE

RAM state block [confirmed]

All labels below are confirmed from ti83plus.inc. This contiguous block at 0x8670 is the silent-link control/scratch area:

AddrLabel (.inc)Meaning
8670ioFlagI/O state flags (bit4 tested on receive completion)
8672sndRecStatetransfer type / phase: 0x08 selects backup-send framing, 0x0A appears in backup receive/orchestration, 0x15 is variable DATA, and 0x0B is request/directory
8673ioErrStatelink error sub-state
8674headerpacket header byte 0 = machine-ID
8675header+1packet header byte 1 = command-ID
8676header+2packet length, word (LE) — also the running payload byte budget
8678(running)running 16-bit checksum accumulator (sum of payload byte values)
867DioDatascratch: built var-header length / data ptr setup
867Fthe variable header (type+name) copied from OP1 via _MovFrOP1
8688/8689ioNewData“new var arrived” status (bit7 of 8689)
868BbakHeadersaved 9-byte header for echo/ACK comparison (_Mov9B to/from 8674)
84DBiMathPtr5active data pointer during a streaming transfer
848E8492three backup-section lengths parsed from or written to the backup header
8494saved user-memory boundary used after backup restore
9834pagedCountbytes buffered in the 16-byte staging block (Flash-write batching)
9836pagedGetPtrwrite cursor into pagedBuf
983ApagedBuf16-byte staging block for received Flash-window data
9C86HW-assist TX timeout reload (0xFA)
9CACHW-assist TX/RX timeout down-counter (seeded from CPU speed, port 0x20)
85D9varClassvariable class (backup sub-type check, =0x0A)

IY-relative flag bytes used by the link code (IY = flags base, 0x89F0): IY+0x1B is the link-mode/peer-type byte (which machine-ID to advertise, USB-vs-DBUS, single-byte mode), IY+0x12 bit2 “command in progress”, IY+0x24 bit1/2 transfer-active, IY+0xC bit2 APD-disable save, IY+0x3E bit0 / IY+0x3D bit5 USB-presence.


Byte layer [confirmed]

Two-wire link port hardware covers the complete raw port-0x00 send and receive handshakes. This section records how the byte entries select and report the hardware-assist path.

Hardware-assist send [confirmed]

_SendAByte (3C:420D) starts:

CALL probe_hw_model_keep_a
JP Z,0x6BB2

If the model probe sets Z, the 84+ link-assist hardware is present, and the routine jumps to 3C:6BB2:

6BB2: setup line / 2× short delay (6BD2 seeds 9CAC from port 0x20 = CPU speed)
6BBB: LD A,0xFA
      LD (0x9C86),A            ; reload inner timeout
      IN A,(0x09)
      BIT 5,A                  ; port 0x09 bit 5 = TX buffer empty/ready
      JR Z,6BCA                ; not ready → spin
      LD A,C
      OUT (0x0D),A             ; write the byte to the assist FIFO
      RET
6BCA: CALL 6BE4                ; decrement 0x9CAC
      JR Z,6BBB                ; retry
      JP 4434                  ; timeout

So the assist path is: poll port 0x09 bit 5, then OUT (0x0D),byte, with a CPU-speed-scaled timeout. The legacy fall-through writes port 0x00 directly; see Two-wire link port hardware for its two-read polling loop and four-transition handshake.

Receive path and decoder [confirmed]

443F: DI
      CALL 447E                  ; arm/clock the line
      CALL 444A                  ; get status
      RET C/NZ                   ; loop if Z
444A: CP 1                       ; status 1 selects the error-status path
      LD A,C                     ; A = candidate byte/status marker
      JR NZ,4456                 ; other status: normal byte or marker
      CP 0xE0
      JP NZ,_ErrLinkXmit
      JR 4470
4456: CP 0xE0
      RET NZ                     ; return an ordinary byte from C
      IN A,(0x02)
      AND 0x80                   ; port 0x02 bit 7 set = non-83+-Basic
      JR Z,4469                  ; legacy path: 6CC1 polls the bit-bang lines
      IN A,(0x09)
      BIT 6,A
      JR NZ,4470                 ; transmission error → abort
      AND 0x19
      JR NZ,4475                 ; link error/active flags
4470: CALL 6D17
      XOR A
      RET                        ; error/no byte → return 0

Key port semantics (84+ assist): port 0x09 bit 5 = TX ready, bit 6 = transmission error, bit 4 = byte received, bits 0x19 = error/active; port 0x0D = data FIFO; port 0x02 bit 7 = non-83+-Basic (used here as the assist-present gate; WikiTI’s dedicated “link-assist available” flag is port 0x02 bit 6). The assist receiver at 3C:6C20 returns a normal port-0x0A byte in C with A=0; the port-0x09 bit-6 path returns A=1. lnk_rec_status compares the returned C value with 0xE0; _RecAByteIO does not preserve a caller-supplied A value. The exceptional byte is the TI-Keyboard frame prefix. The public _KeyboardGetKey = 50E9 table entry resolves to 3C:6D5E, whose decoder requires 0xE0, a deliberate DBUS error delimiter, command byte 0x01, and a final scan-code or modifier byte. 3C:6D17 preserves the comparison of the command byte with 0x01 while receiving the final byte. The public routine then discards that byte and returns status 0x01. [confirmed]

The ROM proves what the calculator accepts, not what a physical keyboard emits. The historical WikiTI _KeyboardGetKey revision 5510 independently describes the peripheral sending the same four-part sequence; that transmitter behavior remains [standard] until captured from hardware. The linked external disassembly is no longer available and was not used as evidence. See Two-wire link port hardware for the status tails and executable decoder model. _Rec1stByte (3C:439C) / _Rec1stByteNC (3C:43A3, “no-clear”) are the same logic wrapped with APD/_ApdSetup and the bit-bang start-bit detect, used to wait for the first byte of an incoming packet (peer may be idle for a long time).


A TI link packet is a 4-byte header optionally followed by data + 2-byte checksum:

  +--------+--------+--------+--------+   +============+----------+
  | mach-ID|  cmd   |  len-lo|  len-hi|   |  data[len] | chk16 LE |
  +--------+--------+--------+--------+   +============+----------+
   8674     8675     8676     8677         streamed      8678 acc

As a C struct:

typedef struct {
    uint8_t  machine_id;   /* +0: peer/local device class */
    uint8_t  command_id;   /* +1: command byte            */
    uint16_t data_length;  /* +2: little-endian length    */
} LinkPacketHeader;      /* 4 bytes at header = 0x8674 */

The typed RAM view therefore exposes header.machine_id, header.command_id, and header.data_length; the disassembly below retains the concrete addresses that establish those fields. [confirmed]

Sending a header [confirmed]

41C3: 6D4B (drive line)
      short delay
      CALL probe_hw_model_keep_a (model probe)
      … (HW handshake on 84+, or bit-bang line-idle wait, with failure reaching _ErrLinkXmit) …
41F2: (8678)=0                       ; reset checksum accumulator
      LD A,(8674)
      CALL _SendAByte                ; machine-ID
      LD A,(8675)
      CALL _SendAByte                ; command-ID
      LD A,(8676)
      CALL _SendAByte                ; length lo
      LD A,(8677)
      CALL _SendAByte                ; length hi

419B is the generic “send a 0-length control packet”: it sets the local machine-ID (620A), stores the command from H, and calls 41C3. Convenience entries: 4195 H=0x92 (EOT), 4199 H=0x09 (CTS), 41BC ID=0x73/cmd=0x68 (RTS).

Receiving a header [confirmed]

4338: CALL _RecAByteIO
      (8674)=A                       ; machine-ID, validated against the known set:
      0x95 0x73 0x23 0x74 0x82 0x02 0x12 0x83 0x03 0x13 0x08   (else fall to 2nd-byte machine list)
4370: CALL _RecAByteIO
      (8675)=A                       ; command-ID, validated: 0x68 0x47 0x74 0x2D … else _JErrorNo
438F: CALL _RecAByteIO
      (8675)=A                       ; command ID on the validated path
4392: CALL _RecAByteIO
      (8676)=A                       ; length lo
4395: CALL _RecAByteIO
      (8677)=A                       ; length hi
      RET

An unrecognised machine-ID or command-ID byte aborts via _JErrorNo (→ E_LnkErr 0x9F).

Machine-ID selector [confirmed]

The local machine-ID advertised in outgoing packets depends on the peer-type bits in IY+0x1B:

620A: LD L,0x82                     ; default / TI-84+ silent
      BIT 2,(IY+0x1B)
      RET NZ
      LD L,0x95                     ; computer / TI-Connect USB host
      BIT 1,(IY+0x1B)
      RET NZ
      LD L,0x83
      BIT 3,(IY+0x1B)
      RET NZ
      LD L,0x03                     ; TI-83
      BIT 4,(IY+0x1B)
      RET NZ
      LD L,0x73                     ; TI-73 / fallback
      RET

Command-ID byte reference [hypothesis]

Confirmed in the code; semantics are the standard TI link protocol:

cmdnameseen atmeaning
0x06VARlink_xfer_op reply check 4E86 CP 6variable header packet (type+name+size)
0x09CTS4199 (H=0x09)clear-to-send (receiver ready for DATA)
0x15DATA40DA/407C send, 426D CP 0x15 recvthe variable’s data bytes
0x2DDELheader-validate 4382 CP 0x2Ddelete / directory variants
0x36SKIP/EXITlink_xfer_op 4E7C CP 0x36peer refused this var → abort transfer
0x56ACKbuilt by 42FB (LD H,0x56); checked 418F CP 0x56acknowledge
0x5AERR/NAKbuilt by 6356/6385 (LD H,0x5A)checksum/length error reply
0x68RTS41BC (LD H,0x68)request-to-send
0x92EOT4195 (H=0x92)end of transmission
0xA2/0xB7requestlink_xfer_op 4E2B/4E2Frequest var (A2=DATA-type, B7=other)

_SendPacket [confirmed]

_SendPacket = 0x4ED6, body 3C:4139, consumes the shared LinkPacketHeader at ram:8674, takes the payload pointer from iMathPtr5 at ram:84DB, and uses header.data_length as the payload byte count. It clears pagedPN at ram:9835, sends the four-byte header through 3C:41C3, clears the 16-bit checksum at ram:8678, and streams the payload. A zero pagedPN reads bytes directly from HL; a nonzero value uses the paged read helper at ram:17BB. Each byte is added to the checksum before _SendAByte is called.

After the last byte, the routine sends checksum low then high, receives a reply header, and returns only when its command byte is 0x56 ACK. A line failure, bad reply, or checksum-side failure takes the link error machinery rather than returning a status code.

A one-sided controlled trace supplies a one-byte DATA packet and an installed calculator error handler. With no peer, the run reaches _SendPacket once, the header helper once, and _SendAByte twice before _ErrLinkXmit and _JErrorNo transfer control to the fixture’s handler. It does not reach the payload read at 3C:4160. This dynamically establishes the no-peer boundary; the payload, checksum, and ACK success path above remains ROM control-flow evidence until a paired trace is captured. The reduced result is in tools/data/community-send-packet.csv. [confirmed] under TilEm for the one-sided path.

Checksum and acknowledgement tail [confirmed]

After the data payload, the sender appends the 16-bit sum and waits for the ACK:

4167: LD HL,(8678)
      LD A,L
      CALL _SendAByte               ; checksum lo
      LD A,H
      CALL _SendAByte               ; checksum hi
4178: CALL 4318 (save hdr→bakHeader)
      CALL 4338 (recv reply header)
417E: LD A,(8675)
      … CALL 430F (compare/store)
      CP 0x56
      RET Z
      JP _JErrorNo

On the receive side the matching check is 6356: after streaming the payload it compares the accumulated checksum 8678 against the received 16-bit checksum; on mismatch it sends a 0x5A ERR packet:

6385: LD H,0x5A
      CALL 419B

It then raises _JErrorNo. The ACK-builder 42FB saves the caller’s header to 868B bakHeader, then builds an ACK with a fresh local machine-ID (CALL 620A), command = 0x56, length = 0, sends it, and _Mov9B restores the saved header.


DATA payload receive path [confirmed]

3C:4261 stores the destination in iMathPtr5 at 0x84DB, validates a DATA header, and enters 3C:4292. The payload loop loads the destination once at 3C:42AB. Bit 7 of H then selects one of two storage paths: [confirmed]

  • A RAM destination (HL >= 0x8000) is written directly at 3C:42D4, and the loop increments HL after each byte.
  • A Flash-window destination (HL < 0x8000) is buffered at 0x983A. 3C:42CF flushes each full 16-byte block through 3C:6AB1, and 3C:42EC flushes a nonzero remainder.
4292: BC=(8676) len
      (8678)=0
      if BC==0 → checksum tail
      pagedGetPtr=983A
      pagedCount=0
      HL=(84DB) dest
      loop: 1FD6 (break check)
            _RecAByteIO → A
            if BIT 7,H: (HL)=A
                        INC HL
            else: store A via pagedGetPtr
                  INC pagedCount
                  when pagedCount==0x10 → CALL 6AB1
            (8678) += received_byte
            DEC BC
            loop while BC
      if pagedCount!=0 → CALL 6AB1
42EF: _RecAByteIO ×2 → received checksum
      CALL 6356 (verify len/sum, NAK 0x5A on mismatch)
42FB: send ACK (cmd 0x56)

Flash-window staging flush — 3C:6AB1 [confirmed]

flush_paged_flash_block at 3C:6AB1 clears pagedCount, resets pagedGetPtr to 0x983A, and loads the write state below. It preserves caller BC, DE, and HL. [confirmed]

_WriteFlash inputSource at 3C:6AB1
A destination pagearcInfo.page at 0x83EE
DE destination addressiMathPtr5 at 0x84DB
BC lengthB=0, C=pagedCount from 0x9834
HL RAM sourcepagedBuf at 0x983A

The protected sequence at 3C:6AD93C:6AE5 opens the port-0x14 command gate. The routine classifies the page through 3C:6B79, calls _WriteFlash (80C9h) at 3C:6AF5, and relocks through 3C:66D5. The bytes are EF C9 80; this is the guarded _WriteFlash entry, not _WriteFlashUnsafe (8087h). [confirmed]

3C:6B79 preserves the incoming A around the model probes at 00:1837 and 00:182F, then applies one range: [confirmed]

Model branchPage maskUpper bound, exclusivePages accepted by 3C:6AB1
TI-84 Plus0x3F0x2A0x080x29
legacy0x1F0x160x080x15
expanded0x7F0x6A0x080x69

The TI-84 Plus branch requires port 0x02 bit 7 set and port 0x21 bits 0–1 clear. A page below 0x08 or at or above the selected upper bound skips the bcall. [confirmed]

After the bcall or page rejection, 3C:6B06 saves the resulting DE in iMathPtr5 (0x84DB). The comparison at 3C:6B0A increments arcInfo.page at 0x83EE when the starting DE is greater than or equal to the final DE. Normal receive callers pass 1–16 bytes on an eligible page. A dispatcher call with zero count or an invalid page leaves DE unchanged, so the equality case still increments the stored page. [confirmed]

The direct callers are the full-block and remainder sites at 3C:42CF and 3C:42EC. Dispatcher mode 3 at 3C:6F57 also jumps here. The page-0 bjump stub at 00:2D45 targets that dispatcher; its only adjacent mode-3 caller is 36:415C. [confirmed]

That caller belongs to the USB receive-to-memory loop at 36:40E7. The Flash branch at 36:413A caps a chunk at 16 bytes, points HL at 0x983A, and calls the page-0 bjump stub at 00:2E17. The stub targets the endpoint helper at 35:4FA1, whose byte loop reads port 0xA1 at 35:500E. The page-36 loop then stores the count at 0x9834 and invokes dispatcher mode 3. Its RAM branch at 36:416C uses the same endpoint helper with chunks of at most 64 bytes and does not call the Flash flush. [confirmed]

tools/ti84re/link/analyze_flash_staging.py checks the ROM signatures and complete caller sets. Its importable model also reports page classification, RAM-direct versus Flash-buffered routing, block counts, destination crossing, and the equality quirk.

The header-classifier 6994 shows the receive-and-store sequence a var-receive runs:

6994: 4255 (reset chk)
      6298 (machine-ID re-validate)
      RST4 on (867F) (classify var header)
      6D4B/4338 recv header
      expect (8675)==0x09 (VAR/CTS) else _JErrorNo
      4338 recv DATA header
      expect (8675)==0x15 (DATA) else _JErrorNo
      BC=(8676) len
      RST5 → store the variable into the VAT (creates RAM/Flash entry)

i.e. the receiver reproduces the VAT-create / _InsertMem path from sub-vat-archive.md.


This is the path a “Send” hits (TI-Connect pulls a var, or a calc-to-calc send). OP1 = the variable name. It negotiates, sends the VAR header, waits for CTS, then streams the DATA.

link_xfer_op (3C:4DD2):
  CALL probe_hw_model_keep_a        ; model/HW probe, spin on port 0x20 if assist busy
  SET 1,(IY+0x24)                   ; mark "transfer active"
  RES 3,(IY+0x1B)
  save IY+0xC (APD)
  install cleanup handler 4F3E via 27DA
  CALL _OP1ToOP6                    ; preserve the var name
  (build the var header into 867F) :
      LD DE,0x867F
      CALL _MovFrOP1                ; header = var type byte + name token(s)
  decide request command:
      LD A,(8672) sndRecState
      CP 0x15
      A = 0xA2 (DATA-type) else 0xB7
      CALL 6971 (set "cmd in progress")
  USB negotiation (when IY+0x1B bit0 & bit5/6 set): poll port 0x4D bits 5/6, cross_page 2E0B
  CALL 4055 (send the VAR/request header via 40DA→41C3)
  CALL 6184 → _Rec1stByteNC (wait for peer reply)
      CP 0x36 (SKIP/EXIT) → 427E
      _JErrorNo                     ; peer refused
      CP 0x06 (VAR/CTS ok) → continue, else 4D45 _JErrorNo
  CALL 4255
  CALL 687A (check transfer state 8688==0x07)
  if sndRecState==0x15 (DATA):
      CALL 4763 (resolve var data: type/size/ptr, archive-aware)
      CALL ... send DATA
  else: send the symbol-table/listing payload (4261)
  RES 1,(IY+0x24)
  FUN_ram_2800 (restore)
  JP 4F3E (cleanup)

Resolving the variable for sending [confirmed]

lnk_resolve_var (3C:4763) reads the var-header type byte at 0x867F and branches by class. For graph/equation types (0x0F0x14) it uses a cross-page helper. Otherwise 3C:47AB calls _CkOP1Real, checks the size, then calls _ChkFindSym (ram:0E60) to locate the VAT entry. An archived variable routes through the Flash path, where _Chk_Batt_Low saves arcInfo.size at 0x83F7. _SetupPagedPtr supplies the data pointer, page, and length inside the DATA sender.

Sending the DATA payload [confirmed]

40DA: CALL _SetupPagedPtr (17AC)            ; initialize the paged source from HL, DE, and B
      (84DB)=ptr                            ; iMathPtr5
      (8676)=len                            ; packet length
      6971
      620A (machine-ID)
      (8674)=ID
      if sndRecState == 0x08 and varClass == 0x0A and len > 0x037D:
          (8676)=0x037D
          send header
          checksum=0
          send 0x63,0x00
          DE=0x037B
          HL=data ptr+2
413D: CALL 41C3 (send DATA header, cmd already 0x15 from 4055)
      HL=(84DB) ptr
      DE=(8676) len
      (8678)=0
      loop 4150: 1FD6 (clock)
                 _PagedGet (17BB) the next byte (handles Flash page-cross)
                 41AB → _SendAByte
                 accumulate (8678)
                 DEC DE
                 loop
4167: send 2-byte checksum (8678 lo,hi)
      recv reply header
      CP 0x56 (ACK)
      else _JErrorNo

The comparison at 3C:410A computes 0x037D - len. An equal length takes the ordinary path; only a larger source enters the backup branch. The resulting wire payload is byte-pinned as follows. [confirmed]

Source lengthDATA header lengthDATA payload
len <= 0x037Dlensource[0:len]
len > 0x037D with sndRecState = 0x08, varClass = 0x0A0x037D63 00 followed by source[2:0x037D]

The exceptional source is the first section of a three-part calculator backup. At 3C:4B52, the backup reply passes HL=0x89F0 and DE=0x13A5 to the DATA sender. This source spans flags through 0x9D94. The setup at 3C:4CCD caps the advertised VAR length to 0x037D; 3C:410F applies the same cap to the DATA packet. The transmitted section therefore covers 0x89F00x8D6C. [confirmed]

The bytes 63 00 are the normalized image of RAM 0x89F00x89F1, not an embedded section length. The restore path at 3C:46FC loads the first section length from 0x848E, sets DE=0x89F0, and calls the DATA receiver at 3C:4261. The receiver writes the packet bytes to that destination. The first restored system-flags byte is thus 0x63 (bits 0, 1, 5, and 6 set), and the second is zero. The sender fixes these bytes instead of copying their live values. [confirmed]

The fixed word selects a mixture of persistent mode, input, display, and unnamed bits. The symbol column below comes from the bundled public ti83plus.inc; the instruction counts come from an independent raw scan of the retail ROM. Each count covers an exact memory-only BIT, RES, or SET instruction using IY = 0x89F0. [standard] for the public names; [confirmed] for the fixed values and byte-pattern counts.

RAM bitSentPublic symbolDirect ROM bit operations
0x89F0.01inDelete10 BIT, 4 RES, 1 SET
0x89F0.115 BIT, 4 RES, 2 SET
0x89F0.20trigDeg13 BIT, 3 RES, 2 SET
0x89F0.30kbdSCR2 BIT, 2 RES, 2 SET
0x89F0.40kbdKeyPress1 BIT, 1 RES, 2 SET
0x89F0.51donePrgm1 BIT, 0 RES, 4 SET
0x89F0.61none
0x89F0.704 BIT, 1 RES, 2 SET
0x89F1.00none
0x89F1.10none
0x89F1.20editOpen39 BIT, 2 RES, 2 SET
0x89F1.30AnsScroll6 BIT, 5 RES, 3 SET
0x89F1.40monAbandon13 BIT, 12 RES, 8 SET
0x89F1.501 BIT, 1 RES, 1 SET
0x89F1.6–70none

This rules out a live-state snapshot. The fixed word clears the degree-mode bit, both pending-keyboard bits, the editor-open bit, answer scrolling, the monitor-abandon bit, and the unnamed active bit at 0x89F1.5. It sets the public donePrgm bit. Those choices are consistent with a canonical post-restore state. Bits 0x89F0.0, .1, and .6 keep the stronger conclusion open: .0 and .1 have active consumers, while .6 has no direct indexed bit operation anywhere in this ROM. No TI source or older-ROM comparison has yet been found that establishes whether those three values instead encode model or OS-version compatibility. [hypothesis]

The audit is reproducible without subsystem-specific parsing:

python3 -m ti84re.link.describe_backup legacy-flags

External format evidence. [standard] tilibs commit 791d2535813fa7ffef8f9feadf110998d4ae57fb provides an independent format check. calc_73.cc::send_backup passes data_part1 unchanged to SEND_XDP. files8x.cc::ti8x_file_write_backup writes data_length1 before data_part1, outside the section bytes. The file and wire implementations therefore agree that 0x0063 belongs to the RAM image. The reason the ROM chooses this particular system-flags mask remains [hypothesis].

Calls to 3C:41AB add 0x63, 0x00, and the remaining 0x037B bytes to the same 16-bit checksum at 0x8678. The checksum covers all 893 transmitted bytes modulo 0x10000. [confirmed]

_PagedGet makes the streamer transparent to RAM-vs-archived data: an archived program is read straight out of the Flash window, advancing the bank-A page (port 0x06) at the 0x8000 boundary, exactly like _FlashToRam.


_SendVarCmd [confirmed]

The bcall most code/TI-BASIC reaches for to silent-send. It is a thin DI-wrapped front for the same machinery:

4EDD: DI
      save IY+0xC (APD)
      RES 2,(IY+0xC)
      install cleanup 4F3E via 27DA
      LD A,0x0B
      LD (8672),A                   ; sndRecState = request/directory
      LD A,0xC9
      CALL 6971                     ; command setup
      CALL 62B0                     ; clear link sub-state in 8A0B
      SET 2,(IY+0x1B)
      CALL 58ED                     ; sets IY+0x24 bit 1 and calls _ChkFindSym
      JR 4EAD                       ; shared tail with link_xfer_op
4EAD: RES 1,(IY+0x24)
      2800 (restore)
      JP 4F3E

Note 4EDD physically overlaps / shares the tail (4EAD) with link_xfer_op; they are two entry points into one routine body. _SendVarCmd is the “send by name from the running context” door; link_xfer_op is the “OP1 already set up, do the silent transfer” door.


APD, cleanup, and idle-line wait [confirmed]

  • 27DA (FUN_ram_27da) installs an error callback. link_xfer_op and _SendVarCmd install 3C:4F3E, which restores link state, the APD timer, and IY+0xC bit 2 after _JError:

    4F3E: POP AF
          BIT 2,A
          restore IY+0xC bit 2
          continue at 4F31
    4F31: RES 2,(IY+0x12)
          re-enable timers
          EI
    
  • Six other transfer paths install page-0 stub 2D51, which bjumps to 3C:6136. That callback dispatches on sndRecState; for the applicable non-DATA states it calls the raw/USB-aware abort cleanup at 3C:618D, then records ioErrState=1 through stub 2F3107:7AC3. The raw branch drives both port-0x00 lines low for an exact software delay before releasing them. See Two-wire link port hardware.

  • _ApdSetup (00:03AE) is called before any long blocking receive (6177, 6184) so the calc doesn’t auto-power-down mid-transfer.

  • 62B0/62BB clear the link error sub-state byte (8A0B, the low bits of IY+0x1B-area flags).


Flash-object dispatch and error handling

TriggerAddressError
send/receive line timeout, bad echo, unexpected reply cmd_JErrorNo 00:2799E_LnkErr 0x9F “ERR:LINK”
lnk_rec_status returned A=1 with C != 0xE0; header-send line never went idle_ErrLinkXmit 00:278D_JError(0x9F)E_LnkErr 0x9F
received checksum/length mismatch6356→ sends 0x5A NAK → 2799E_LnkErr 0x9F
peer sent SKIP/EXIT (0x36)link_xfer_op 4E80/4E83E_LnkErr 0x9F
incoming variable-header type at 0x867F equals 0x223C:463D_JError 00:2793raw error 0x22, displayed as ERR:LINK

The ordinary timeout, checksum, and unexpected-command paths collapse to E_LnkErr (0x9F). The error display masks bit 7, so this becomes table code 0x1F; pointer entry 07:6B08 selects 07:6C55, the string LINK. _JError(0x22) uses pointer entry 07:6B0E, which selects the same string. The two raw codes therefore produce the same visible ERR:LINK message. tools/ti84re/rom/error_table.py decodes this ROM table, and python3 -m ti84re.rom.describe_error 0x22 0x9F reproduces both lookups. [confirmed]

The include file labels 0x220x25 as E_LinkIOChkSum, E_LinkIOTimeOut, E_LinkIOBusy, and E_LinkIOVer, but the same block marks all four numbers obsolete. Those names do not describe the dispatcher at 3C:45D7: it reloads the variable-header type from 0x867F, not the packet command at 0x8675. [confirmed]

The independent tilibs type tables name 0x23 OS/AMS, 0x24 Flash application, and 0x25 certificate; they define no Z80 Flash-object type at 0x22. The ROM control flow agrees with those three names. [standard] for the host-library names; [confirmed] for the ROM branches.

Header typeROM behavior
0x223C:463D jumps to _JError with A=0x22, producing ERR:LINK.
0x23 — OS/AMS3C:45EA enters negotiation at 3C:45EE; its 3C:5735 branch checks the battery, initializes MD5 through _MD5Init = 808Dh, and calls _ReceiveOS = 8072h.
0x24 — Flash application3C:45DA requires sender machine ID 0x73, then jumps to the application-specific path at 3C:512C, whose first operation is _Chk_Batt_Low. A separate receive path at 3C:550D also selects type 0x24 and requires PC sender ID 0x23.
0x25 — certificate3C:462D requires sender machine ID 0x73, then jumps through 3C:5114 to the certificate path at 3C:566B; that path calls _FindFirstCertField = 8027h and uses 0x00E8-byte blocks at 3C:5659.

A linear scan of all 64 physical pages finds 32 direct references to _JError at 00:2793 and no rst 28h call with bcall ID 44D7h. Reviewing those direct sites finds the 0x22 path above, but no site that loads 0x23, 0x24, or 0x25 as a fixed _JError argument. The ROM bytes do not support the claim that a separate assembly-callable transfer API emits all four obsolete values. The 0x22 collision is the only one of these four header branches that passes its value directly to _JError. [confirmed]

The external cross-check uses tilibs commit 791d253 for the type table and its calc_73.cc DBus implementation for the OS, application, and certificate transfer shapes.


End-to-end program transfer [standard]

  1. Host (TI-Connect, machine-ID 0x95) opens the USB/DBUS link; calc detects it (IY+0x1B bit1).
  2. Host requests the directory or a specific var; calc’s receiver (4338) parses the request header, 6994/6298 classify it.
  3. To send a var: link_xfer_op/_SendVarCmd builds the VAR header (type byte + name from OP1, size) at 867F, sends it (41C3, cmd path), waits for CTS (0x09).
  4. 40DA streams the DATA (0x15) payload via _PagedGet_SendAByte (Flash-transparent), appends the 16-bit checksum, waits for ACK (0x56).
  5. _GetSysInfo (07:7345, id 0x50DD)-style metadata and an EOT (0x92) close the session.
  6. Receive direction is the mirror: header in → CTS out → DATA in (RAM direct, or Flash staged in 16-byte blocks through 3C:6AB1) → checksum verify (3C:6356, NAK 0x5A on error) → ACK out → VAT store.

Routine index

space:addrnamewhat
3C:420D_SendABytesend one byte: HW-assist (port 0x09/0x0D) or bit-bang (port 0)
3C:6BB2lnk_send_byte_hwHW-assist send: poll port 0x09 bit5, OUT (0x0D)
3C:443F_RecAByteIOreceive one byte (blocking)
3C:444Alnk_rec_statusdecode low-level status and returned C; C=0xE0 is the TI-Keyboard prefix and re-arms or joins its exceptional delimiter path
3C:6D5E_KeyboardGetKeydecode the 0xE0, deliberate-error, 0x01, data sequence and return a status byte
3C:439C_Rec1stBytewait for first byte of a packet (APD + start-bit)
3C:43A3_Rec1stByteNCas above, no line-clear
3C:41C3lnk_send_headersend 4-byte header (ID, cmd, len-lo, len-hi)
3C:419Blnk_send_ctrl_pktsend a 0-length control packet (cmd in H)
3C:4195lnk_send_eotsend EOT (cmd 0x92)
3C:4199lnk_send_ctssend CTS (cmd 0x09)
3C:4338lnk_recv_headerreceive + validate 4-byte header
3C:620Alnk_local_machine_idpick local machine-ID from IY+0x1B mode
3C:42FBlnk_send_ackbuild+send ACK (cmd 0x56, fresh local machine-ID), restoring the saved header
3C:4292lnk_recv_datareceive DATA payload, 16-byte Flash batching, checksum
3C:6356lnk_verify_cksumverify count vs len; NAK 0x5A on mismatch
3C:6AB1flush_paged_flash_blockprogram one 1–16-byte staged Flash block through _WriteFlash and port 0x14
3C:4DD2link_xfer_opsilent-link variable send orchestrator (OP1=name)
3C:4EDD_SendVarCmdbcall _SendVarCmd (4A14) body; DI-wrapped send-by-name
3C:4763lnk_resolve_varresolve var class/size/ptr for sending (archive-aware)
3C:40DAlnk_send_datasend DATA payload (_PagedGet_SendAByte) + checksum + ACK wait
3C:4167lnk_send_cksum_tailappend 16-bit checksum, recv reply, expect ACK 0x56
3C:4F3Elnk_cleanuperror/abort cleanup (restore APD/timers/flags)
3C:6136lnk_error_cleanupinstalled state-aware error callback; reaches raw/USB abort cleanup where applicable
3C:618Dlnk_abort_transportclear USB busy state or issue the raw both-low abort pulse
3C:62B0lnk_clear_substateclear link error sub-state (8A0B)
3C:6994lnk_recv_storereceive var + VAT store sequence (expects 0x09 then 0x15)
00:278D_ErrLinkXmit_JError(0x9F) E_LnkErr
00:2799_JErrorNoraise current pending error (link → 0x9F)
07:7345_GetSysInfo (id 0x50DD)system info reply (used in link sessions)
00:4A14_SendVarCmd (bcall id)→ 3C:4EDD

Ports: 0x00 = raw two-wire link; 0x080x0D = HW link-assist control/status/data FIFO (port 0x09 bit5 TX-ready, bit6 transmission-error, bit4 byte-received, bits 0x19 error); 0x02 bit7 = non-83+-Basic (assist-present gate on 84+; WikiTI’s “link-assist available” is bit6); 0x20 = CPU speed (timeout scaling); 0x4D bits5/6 = USB negotiation; 0x14 = Flash write/erase (received-to-archive path). See sub-usb-asic.md for the assist port state machine. RAM block: ioFlag 8670 … bakHeader 868B, staging pagedBuf 983A for Flash-window receive staging.

Command IDs: 0x06 VAR · 0x09 CTS · 0x15 DATA · 0x2D DEL · 0x36 SKIP/EXIT · 0x56 ACK · 0x5A ERR/NAK · 0x68 RTS · 0x92 EOT · 0xA2/0xB7 request. Machine IDs: 0x82/0x73 calc(84+/73), 0x95 computer (TI-Connect), 0x03 TI-83, plus the 0x02/0x12/0x23/0x74/0x83/0x13/0x08 set accepted.

Open items

  • Determine why the legacy backup normalizer chooses system-flags word 0x0063. Its RAM destination, replacement behavior, section bounds, and checksum coverage are confirmed. A complete direct indexed-bit audit shows that the word clears degree-mode, keyboard, editor, answer-scroll, and monitor state while setting donePrgm; the remaining gap is why active unnamed bits 0 and 1 and unreferenced bit 6 of 0x89F0 are set.
  • The prior USB target gap is now mapped in sub-usb-asic.md: link_xfer_op calls ram:2E0B, a cross_page_jump thunk to 35:4280, after sampling port 0x4D.