# MC68705P3 ND-5000 External Panel Controller — Complete Firmware Analysis

**File:** `Code/68705/P3/ND-5000C-MC68705P3.BIN` (2048 bytes)  
**Datasheet:** `Code/68705/6805/MC68705P3.PDF` (Motorola Technical Summary, pages 3-568 to 3-585; page numbers below are the printed ones)  
**Chip:** Motorola MC68705P3 (28-pin DIP, 2KB ROM)  
**Language:** 6805:BE:16:default  
**Role:** ND-5000C external operator panel controller — HD44780 LCD display, physical buttons, bidirectional CY7C401 FIFO interface to ND-120 main CPU  
**Analysis date:** April 2026  

---

## Table of Contents

1. [Hardware Overview](#1-hardware-overview)
2. [Memory Map](#2-memory-map)
3. [Port Assignment](#3-port-assignment)
4. [RAM Variable Map](#4-ram-variable-map)
5. [Boot Sequence (RESET 0x00D6)](#5-boot-sequence)
6. [Main Loop (UpdateTimersAndWait 0x011F)](#6-main-loop)
7. [Command Processing (ProcessData 0x0142)](#7-command-processing)
8. [Serial Data Reception (WaitForData 0x025E)](#8-serial-data-reception)
9. [LCD Display System](#9-lcd-display-system)
10. [Character Decoding](#10-character-decoding)
11. [Display Modes](#11-display-modes)
12. [ROM Data Tables](#12-rom-data-tables)
13. [Interrupt Vectors](#13-interrupt-vectors)
14. [Function Reference](#14-function-reference)
15. [Mermaid Diagrams](#15-mermaid-diagrams)
16. [Key Differences from U3 Chip](#16-key-differences-from-u3-chip)
17. [Corrections to Existing Analysis Files](#17-corrections-to-existing-analysis-files)

---

## 1. Hardware Overview

The MC68705P3 is the controller for the **ND-5000C external operator panel** — a separate physical unit from the main ND-120 chassis. It differs fundamentally from the U3 chip (which drives the built-in front panel):

| Feature | MC68705P3 (external panel) | MC68705U3 (built-in panel) |
|---------|---------------------------|---------------------------|
| Package | 28-pin DIP | 40-pin DIP |
| ROM | 2KB (0x0000-0x07FF) | 4KB (0x0000-0x0FFF) |
| RAM | 112 bytes on chip (0x0010-0x007F, datasheet p.3-571); firmware uses 0x0010-0x004C | 128 bytes |
| Display | HD44780 LCD (2×40 chars) | 7-segment shift register chain |
| Input | Physical buttons via Port A | None (display-only U3 side) |
| Protocol | 192-bit serial + command poll | FIFO command only |
| Crystal | 2 MHz | ~8 MHz |

### System Connection

```
ND-120 CPU
    │
    ├──► PANC register ──► CY7C401 FIFO ──► PA[5:0] (commands)
    │                                         /INT pin 2 (serial frame ready, tested by BIH/BIL)
    │
    ├──► Serial transmitter ──► PA0 (CH1: time data)
    │                       ──► PA1 (CH2: display data)
    │                       ──► PA2 (CH3: status data)
    │                       PC1 (clock, generated by P3)
    │
    └──◄ PB[6:0] ◄── response byte (with STATUS_FLAGS ORed in)
                  PC0 = output strobe to ND-120 FIFO
```

### Physical Panel Hardware

- **HD44780 LCD**: 2-line × 40-character alphanumeric display
  - PB[7:0] → LCD data bus (D7-D0)
  - PC2 → RS (Register Select: 0=command, 1=data)
  - PC3 → E (Enable strobe)
- **Panel lock key**: PA3 — locks panel from software modification
- **Buttons**: PA[5:0] combined with command lines (multiplexed)
- **CY7C401 FIFO**: 512×9-bit — buffers commands from ND-120

---

## 2. Memory Map

### Registers (hardware, 0x0000-0x000B)

| Address | Name | Init Value | Description |
|---------|------|-----------|-------------|
| 0x0000 | PORT_A | — | **Input**: PA[5:0]=command, PA3=lock, PA6=button, PA7=read as data (see Section 3) |
| 0x0001 | PORT_B | — | **Output**: LCD data / ND-120 response byte |
| 0x0002 | PORT_C | — | **Output**: PC0=ND120_STROBE, PC1=SERIAL_CLK, PC2=LCD_RS, PC3=LCD_E (Port C has only 4 bits, datasheet p.3-570) |
| 0x0004 | DDRA | 0x00 | Direction A: all inputs |
| 0x0005 | DDRB | 0xFF | Direction B: all outputs |
| 0x0006 | DDRC | 0xFF | Direction C: all outputs |
| 0x0008 | TDR | 0x00 | Timer Data Register (8-bit down counter, datasheet p.3-575). Written 0 at 0x0100 (boot) and at 0x0264 (every WaitForData pass); read at 0x017B in ProcessData (0 → BSET STATUS_FLAGS.5, non-zero → BCLR STATUS_FLAGS.5) |
| 0x0009 | TCR | 0x78 | Timer Control. Decoded with datasheet p.3-575: TIR=0, TIM=1 (timer interrupt masked), TIN=1 (external clock), TIE=1 (TIMER pin enabled), PSC=1 (clear prescaler), PS2-0=000 (prescaler /1). That is "timer input mode 4": the TDR counts pulses on the TIMER pin (pin 7). This applies because MOR bit TOPT=0 (see 0x0784 below) |

### RAM (0x0010-0x004F, initialized to 0xFF at power-on)

See [Section 4](#4-ram-variable-map) for full variable map.

Datasheet memory map (p.3-571): on-chip RAM is 112 bytes at 0x0010-0x007F. The stack sits at the top of it: the stack pointer is reset to 0x007F and may grow down to 0x0061 (31 bytes, p.3-572). The firmware's own variables stop at 0x004C.

### Datasheet memory map for 0x0080-0x07FF (p.3-571)

| Range | Datasheet use | What the dump holds |
|-------|---------------|---------------------|
| 0x0080-0x00FF | Page-zero user EPROM (128 bytes) | tables + start of code |
| 0x0100-0x0783 | Main user EPROM (1668 bytes) | code + tables |
| 0x0784 | Mask Option Register (MOR) | 0x20 → CLK=0 (crystal oscillator), TOPT=0 (software timer), CLS=1 (timer clock from TIMER pin), TIE=0, P2-P0=000 (prescaler /1) — decoded with p.3-576 |
| 0x0785-0x07F7 | Bootstrap ROM (115 bytes) | 0xFF in this image |
| 0x07F8-0x07FF | Interrupt vectors | all 0x00D6 |

User EPROM total is 1804 bytes (0x0080-0x0783), not 2KB; the 2048-byte file is the whole address space.

### ROM (0x0050-0x07F7)

| Range | Contents |
|-------|---------|
| 0x0050-0x007F | (unused / padding) |
| 0x0080-0x008A | TABLE_CMD_LOOKUP_1 (5 entries + 0xFF) |
| 0x008B-0x0096 | TABLE_CMD_LOOKUP_2 (6 entries + 0xFF) |
| 0x0098-0x00D5 | ROM_DISPLAY_STRINGS (quote-terminated LCD strings) |
| 0x00D6-0x011E | RESET / Boot entry point |
| 0x011F-0x0141 | UpdateTimersAndWait (main loop head) |
| 0x0142-0x025D | ProcessData + command handlers |
| 0x025E-0x03CE | WaitForData + serial reception + decode entry |
| 0x03CF-0x03F3 | DisplayTimeData |
| 0x03F4-0x041C | ShowMessageAndTime |
| 0x041D-0x057B | ShowSystemStatusDisplay |
| 0x057C-0x0596 | DisplayBinaryBars |
| 0x0597-0x05C7 | DisplayBinaryDigits |
| 0x05C8-0x05D1 | DisplayDecimalPoint |
| 0x05D2-0x05FB | OutputCharacterToDisplay |
| 0x05FC-0x060B | SendDisplayCommand |
| 0x060C-0x0617 | DisplayStringUntilQuote |
| 0x0618-0x0622 | InitDisplayClearPulse |
| 0x0623-0x0640 | LookupCharacterCode |
| 0x0641-0x068F | DecodeCharacterFromTable |
| 0x0690-0x06AF | CalculateDisplayPosition |
| 0x06B0-0x06BB | SetDisplayAddressAndWrite |
| 0x06BC-0x06FC | TABLE_CHAR_DECODE (level-1) |
| 0x06FD-0x0758 | TABLE_CHAR_SUBKEYS (level-2) |
| 0x0759-0x077B | TABLE_7SEG_TO_ASCII (key 0xFF at 0x077B ends the search) |
| 0x077C-0x0783 | bytes CF 4E 09 80 00 C5 00 00 (after the table end marker; no code instruction names these addresses directly; use unknown) |
| 0x0784 | MOR = 0x20 (see datasheet map above) |
| 0x0785-0x07F7 | (bootstrap ROM area, 0xFF in this image) |
| 0x07F8-0x07FF | Interrupt vectors |

---

## 3. Port Assignment

### Pinout (28-pin DIP) — datasheet "Pin Assignments", p.3-585

The chip has 20 I/O lines: PA0-PA7, PB0-PB7 and PC0-PC3 (datasheet p.3-570). There is no PC4-PC7.

| Pin | Name | Use in this firmware |
|-----|------|----------------------|
| 1 | VSS | Ground |
| 2 | /INT | External interrupt input. Tested by BIH/BIL ("branch if interrupt line high/low", p.3-578) as the serial-frame signal; the INT vector goes to RESET |
| 3 | VCC | +5.25 V ±0.5 V (p.3-569) |
| 4 | EXTAL | Oscillator. MOR CLK=0 selects the crystal option (p.3-569, p.3-576) |
| 5 | XTAL | Oscillator |
| 6 | VPP | EPROM programming voltage; tied to VCC in normal use (p.3-569) |
| 7 | TIMER | External timer input. TCR=0x78 makes the TDR count pulses on this pin (p.3-575) |
| 8 | PC0 | ND120_STROBE (output) |
| 9 | PC1 | SERIAL_CLK (output) |
| 10 | PC2 | LCD_RS (output) |
| 11 | PC3 | LCD_E (output) |
| 12 | PB0 | LCD D0 / response bit 0 |
| 13 | PB1 | LCD D1 / response bit 1 |
| 14 | PB2 | LCD D2 / response bit 2 |
| 15 | PB3 | LCD D3 / response bit 3 |
| 16 | PB4 | LCD D4 / response bit 4 |
| 17 | PB5 | LCD D5 / response bit 5 |
| 18 | PB6 | LCD D6 / response bit 6 |
| 19 | PB7 | LCD D7 (always 0 in responses: AND #0x7F at 0x023A) |
| 20 | PA0 | CH1 serial data / command bit 0 |
| 21 | PA1 | CH2 serial data / command bit 1 |
| 22 | PA2 | CH3 serial data / command bit 2 |
| 23 | PA3 | Lock key / command bit 3 |
| 24 | PA4 | Command bit 4 |
| 25 | PA5 | Command bit 5 |
| 26 | PA6 | Button change bit |
| 27 | PA7 | Read as data (see Port A table) |
| 28 | /RESET | Reset input, Schmitt trigger with on-chip pull-up (p.3-570) |

Datasheet port notes (p.3-570): the DDRs are write-only and read back as 0xFF; reset clears all DDRs to 0 (all inputs), so the firmware's first job at 0x00D6 is writing DDRB/DDRC. Port output latches are not set by reset.

### Port A — ALL INPUTS (DDRA = 0x00)

| Bit | Signal | Direction | Description |
|-----|--------|-----------|-------------|
| PA0 | CH1_DATA | Input | Serial channel 1: time data (active-low, MSB first) |
| PA1 | CH2_DATA | Input | Serial channel 2: display data (active-low) |
| PA2 | CH3_DATA | Input | Serial channel 3: status data (active-low) |
| PA3 | /LOCK_KEY | Input | Panel lock key (1=locked). Read at boot and each command. |
| PA4 | (unused) | Input | — |
| PA5 | CMD[5] | Input | Command code bit 5 (MSB of 6-bit command) |
| PA6 | BTN_CHANGE | Input | Button state change flag |
| PA7 | (input) | Input | Read as data: cmd_display_update (0x01DB) tests PORT_A_SNAPSHOT bit 7 (BRSET 7,$12). It is NOT the interrupt line: BIH/BIL test the separate /INT pin (pin 2) |

**Note:** PA[5:0] together form the 6-bit command code sent from the ND-120 via PANC register and CY7C401 FIFO. The firmware polls PA rather than using the hardware interrupt mechanism. The serial-frame signal is the /INT pin, polled with BIH/BIL; the INT vector (0x07FA) points at RESET, and the I bit is never cleared (no CLI in the code), so it never causes an interrupt.

### Port B — ALL OUTPUTS (DDRB = 0xFF)

| Bits | Signal | Description |
|------|--------|-------------|
| PB[7:0] | LCD_D[7:0] | LCD data bus — shared for LCD data writes AND ND-120 response bytes |
| PB[6:0] | ND120_RESP | ND-120 response (bits[6:0] with STATUS_FLAGS ORed in, bit7 masked off) |

Port B is dual-use: the same 8 lines drive both the HD44780 LCD data bus and carry response data back to the ND-120. The PC signal strobes determine which function is active.

### Port C — ALL OUTPUTS (DDRC = 0xFF)

| Bit | Signal | Direction | Description |
|-----|--------|-----------|-------------|
| PC0 | ND120_STROBE | Output | Rising edge latches PB data into CY7C401 FIFO for ND-120 |
| PC1 | SERIAL_CLK | Output | Serial bit clock — toggled to clock each data bit from PA0/PA1/PA2 |
| PC2 | LCD_RS | Output | HD44780 RS: 0=command register, 1=data register |
| PC3 | LCD_E | Output | HD44780 Enable strobe (data valid on falling edge) |
| PC4-PC7 | (do not exist) | — | Port C is 4 bits; bits 7-4 of 0x0002 and 0x0006 read as 1 (datasheet p.3-570, p.3-571) |

**Important:** PC0 is for ND-120 output; PC1 is for serial input clock; PC2/PC3 are LCD control. These are mutually exclusive in timing.

---

## 4. RAM Variable Map

All RAM at 0x0010-0x004F. Initialized to 0xFF at power-on; zeroed selectively during RESET.

| Address | Name | Size | Initial | Description |
|---------|------|------|---------|-------------|
| 0x0010 | TIMER1_COUNT | 1 | 0xFF | Primary countdown timer — decremented each main loop |
| 0x0011 | TIMER2_COUNT | 1 | 0x06 | Secondary timer — display refresh control; reload=0x50 on command |
| 0x0012 | PORT_A_SNAPSHOT | 1 | 0x00 | Last sampled PA value for debounce comparison |
| 0x0013 | DEBOUNCE_COUNT | 1 | 0x05 | Counts down from 5; stable reading required to confirm input |
| 0x0014 | STATUS_FLAGS | 1 | 0x60 | System status (see bit map below) |
| 0x0015 | PA_PREV_HIGH_BITS | 1 | 0x00 | Previous PA[7:6] for edge detection via EOR |
| 0x0016 | CMD_CODE | 1 | — | Current 6-bit command code (PA[5:0] & 0x3F) |
| 0x0017 | PA_CHANGE_FLAGS | 1 | — | EOR result: bit6=state-change detected |
| 0x0018 | PREV_DISPLAY_STATUS | 1 | 0x00 | Previous display mode — compared to detect mode changes |
| 0x0019 | DISPLAY_STATUS_FLAGS | 1 | 0x00 | Current display state flags (see bit map below) |
| 0x001A | SERIAL_BIT_COUNT | 1 | 0x00 | Bit counter during serial reception (0-7) |
| 0x001B | SERIAL_TEMP_CH1 | 1 | — | Shift register CH1 / char decode low byte |
| 0x001C | SERIAL_TEMP_CH2 | 1 | — | Shift register CH2 / char decode high byte |
| 0x001D | CHAR_DECODE_RESULT | 1 | — | Result from DecodeCharacterFromTable |
| 0x001E | LOOP_COUNTER | 1 | — | General loop/timeout counter |
| 0x001F | X_SAVE | 1 | — | Saved X register across JSR calls |
| 0x0020 | PA_SAMPLE | 1 | — | Raw PA sample during serial reception |
| 0x0021 | SHIFT_REG_CH1 | 1 | — | MSB-first accumulator for time channel |
| 0x0022 | SHIFT_REG_CH2 | 1 | — | MSB-first accumulator for display channel |
| 0x0023 | SHIFT_REG_CH3 | 1 | — | MSB-first accumulator for status channel |
| 0x0024 | DECIMAL_PT_COUNT | 1 | 0x00 | Count of chars since last decimal point (0-3) |
| 0x0025-0x0028 | MSG_BUFFER[4] | 4 | — | 4-char decoded message buffer (current) |
| 0x0029-0x002C | MSG_BUFFER_PREV[4] | 4 | — | Previous message buffer (change detection) |
| 0x002D-0x0034 | TIME_DATA_BUF[8] | 8 | — | Serial CH1 bytes: 7-seg encoded time digits |
| 0x0035-0x003C | DISP_DATA_BUF[8] | 8 | — | Serial CH2 bytes: display mode + label chars |
| 0x003D-0x0044 | STAT_DATA_BUF[8] | 8 | — | Serial CH3 bytes: CPU/system status bits |
| 0x0045 | DISPLAY_LINE2_OFFSET | 1 | 0x00 | Line 2 display start position offset |
| 0x0046 | LCD_CURSOR_POS | 1 | — | Current HD44780 DDRAM address |
| 0x0047 | SCROLL_COUNTER | 1 | 0x00 | Message scroll position (reset on mode change) |
| 0x0048 | LCD_COLUMN_COUNT | 1 | — | Character counter (0x28=line1 end, 0x68=line2 end) |
| 0x0049 | CHAR_TEMP | 1 | — | Char temp in OutputCharacterToDisplay |
| 0x004A-0x004B | (unused) | 2 | — | |
| 0x004C | SERIAL_TIMEOUT | 1 | 0x30 | Countdown for serial frame timeout |

### STATUS_FLAGS (0x0014) Bit Map

| Bit | Meaning | Set/Clear |
|-----|---------|-----------|
| 7 | LOCK_KEY_STATE | 1=panel locked (PA3=1); 0=unlocked |
| 6 | DISPLAY_MODE_FLAG | Set/cleared by cmd_handler_6c |
| 5 | ON_FLAG | Tracks ON/OFF display state |
| 4 | REFRESH_INHIBIT | Cleared on timer expiry, inhibits LCD clear |
| 3-1 | (unused) | |
| 0 | (unused in flags) | |

**Initial value 0x60 = bits 6+5 set.**

### DISPLAY_STATUS_FLAGS (0x0019) Bit Map

| Bit | Meaning |
|-----|---------|
| 5 | MESSAGE_STABLE — current message matches previous (no redraw needed) |
| 4 | TIME_SHOWN — time section has been displayed this frame |
| 2 | BINARY_MODE — using binary digit display (not 7-seg decode) |
| 1 | LAYOUT_SET — display layout has been initialized |

### Control bits inside DISP_DATA_BUF (bit 7 of three bytes)

| Address | Byte | Read at | Effect of bit 7 |
|---------|------|---------|-----------------|
| 0x0038 | DISP_DATA_BUF[3] | 0x04DB, 0x04FD, 0x0520 | 1 → "DAY:" label (0x00A0) and then the TIME/UTC label; 0 → "YEAR:" label (0x00C7) and "  MONTH:" label (0x00CD) |
| 0x003A | DISP_DATA_BUF[5] | 0x0341, 0x0356, 0x039D | 1 → after the message is handled, jump to 0x04D6 (the date/time line + status bars); 0 → go to 0x03AB (ADDRESS:/P COUNT: path) |
| 0x003C | DISP_DATA_BUF[7] | 0x0502 | 1 → "  TIME:" label (0x00A5); 0 → "   UTC:" label (0x00AD). Only used when 0x0038 bit 7 = 1 |

---

## 5. Boot Sequence

**Entry:** 0x00D6 (RESET), called from VEC_RESET at 0x07FE.

```
RESET (0x00D6)
├── Configure ports
│   ├── DDRB = 0xFF  (Port B all outputs)
│   ├── DDRC = 0xFF  (Port C all outputs)
│   └── DDRA = 0x00  (Port A all inputs)
├── TCR = 0x78       (timer: prescaler /128, internal clock)
├── STATUS_FLAGS = 0x60
├── Read PA3 (panel lock key)
│   ├── PA3=0 → clear STATUS_FLAGS bit7 (unlocked)
│   └── PA3=1 → set STATUS_FLAGS bit7 (locked)
├── WriteToDisplayPort(STATUS_FLAGS) → output status to ND-120
├── Zero: DISPLAY_LINE2_OFFSET, PREV_DISPLAY_STATUS, PORT_A_SNAPSHOT, TDR (0x0008, timer data register; STA $08 at 0x0100)
├── cmd_handler_a4() → timing calibration delay
├── SendDisplayCommand(0x38) → LCD: Function Set (8-bit, 2-line, 5×7)
├── SendDisplayCommand(0x0C) → LCD: Display On, cursor off
├── SendDisplayCommand(0x06) → LCD: Entry Mode (increment, no shift)
├── InitDisplayClearPulse() → LCD Clear + 180-cycle delay
├── Wait for IRQ HIGH (BIH loop)
├── Wait for IRQ LOW  (BIL loop)   ← synchronize with ND-120 20ms cycle
└── Fall through to UpdateTimersAndWait (0x011F)
```

**Key boot facts:**
- PC1 is set HIGH during boot (serial clock idle state)
- All four interrupt vectors point to RESET — any interrupt or timer expiry re-initializes the chip
- The IRQ sync step waits for one complete rising+falling edge on the /IRQ pin before entering the main loop, ensuring frame alignment with the ND-120

---

## 6. Main Loop

**Entry:** 0x011F (UpdateTimersAndWait), tail-jumps to WaitForData (0x025E).

```
UpdateTimersAndWait (0x011F):
    PA_PREV_HIGH_BITS = PORT_A_SNAPSHOT & 0xC0
    PORT_A_SNAPSHOT = PA_PREV_HIGH_BITS
    DEBOUNCE_COUNT = 5
    DEC TIMER1_COUNT
    if TIMER1_COUNT != 0: goto WaitForData
    DEC TIMER2_COUNT
    if TIMER2_COUNT != 0: goto WaitForData
    BCLR STATUS_FLAGS.4    ← clear refresh inhibit
    if DISPLAY_STATUS_FLAGS.5 == 0:
        InitDisplayClearPulse()
        TIMER2_COUNT = 6
    goto WaitForData (0x025E)
```

The two-stage timer creates a hierarchical timebase. TIMER1 is decremented on every loop iteration (which occurs approximately every 20ms in sync with the ND-120). When TIMER1 reaches zero it decrements TIMER2. When TIMER2 also reaches zero the display is refreshed. The reload value of TIMER2 is normally 6, but extended to 0x50 when a command is being processed.

---

## 7. Command Processing

**Entry:** 0x0142 (ProcessData), reached via JMP from WaitForData.

### Debounce Logic

The firmware requires the Port A value to remain **stable for 5 consecutive reads** before accepting it as a valid command or button state:

```
ProcessData (0x0142):
    Update STATUS_FLAGS.7 from PA_SAMPLE.3 (lock key)
    PC1 = 0 (serial clock idle)
    Read PA
    if PA != PORT_A_SNAPSHOT: goto UpdateTimersAndWait.start (re-sample)
    DEC DEBOUNCE_COUNT
    if DEBOUNCE_COUNT != 0: goto timer decrement
    // stable reading confirmed
    ...
```

### Command Dispatch

Two lookup tables are used, selected by STATUS_FLAGS bit4:

```
TABLE_CMD_LOOKUP_1 (0x0080) — default mode:
    [0x01, 1]  → cmd_conditional_update
    [0x02, 0]  → cmd_display_update
    [0x10, 5]  → cmd_handler_5e
    [0x20, 6]  → cmd_handler_6c
    [0x05, 7]  → direct path
    [0xFF]     → end

TABLE_CMD_LOOKUP_2 (0x008B) — alternate mode (STATUS_FLAGS.4=1):
    [0x01, 1]  → cmd_conditional_update
    [0x02, 2]  → cmd_multi_stage_update
    [0x04, 3]  → cmd_handler_56
    [0x08, 4]  → cmd_handler_5a
    [0x10, 5]  → cmd_handler_5e
    [0x20, 6]  → cmd_handler_6c
    [0xFF]     → end
```

**Dispatch:** `func_index × 2` → offset into JUMP_TABLE_CMD_DISPATCH (0x01A7):

| func_index | Handler | Address | Description |
|-----------|---------|---------|-------------|
| 0 | cmd_display_update | 0x01DB | Check PA7 → conditional display + ND-120 response |
| 1 | cmd_conditional_update | 0x01D1 | Conditional update based on STATUS_FLAGS.4 |
| 2 | cmd_multi_stage_update | 0x01ED | Two-phase output + timing delay |
| 3 | cmd_handler_56 | 0x01FD | LDA #4 → CompleteCommandProcessing |
| 4 | cmd_handler_5a | 0x0201 | LDA #8 → CompleteCommandProcessing |
| 5 | cmd_handler_5e | 0x0205 | Toggle STATUS_FLAGS.5 if STATUS_FLAGS.6+PA_PREV bit6 set |
| 6 | cmd_handler_6c | 0x0213 | Wait for cmd 0x21 → toggle STATUS_FLAGS.4/6 |
| 7 | direct path | 0x01D7 | LDA #1 → CompleteCommandProcessing |

### CompleteCommandProcessing (0x01C0)

Sends two ND-120 response strobes (OutputToDisplayDriver twice), reads STATUS_FLAGS, calls WriteToDisplayPort, then waits for PA command to clear (WaitForCmdClear at 0x01B7).

The first strobe carries the handler's code in A (1, 2, 4 or 8) ORed with STATUS_FLAGS. The second carries A=0, because the delay loop in WriteToDisplayPort leaves A=0, so it sends STATUS_FLAGS & 0x7F alone. The entry at 0x01C6 skips the two strobes: it sends STATUS_FLAGS, waits for the command to clear, and JMPs to 0x011F.

### Handler detail (from the ROM, 0x01B7-0x0236)

| Handler | What it does |
|---------|--------------|
| 0x01B7 WaitForCmdClear | Loop: read PA, AND 0x3F, until 0. Returns with A=0 |
| 0x01D1 cmd_conditional_update | STATUS_FLAGS.4=1 → A=1, CompleteCommandProcessing. STATUS_FLAGS.4=0 and PA_SAMPLE bit 3=1 → 0x01C6 (status only). Otherwise A=1, CompleteCommandProcessing |
| 0x01DB cmd_display_update | PORT_A_SNAPSHOT bit 7=1 → 0x01C6. Otherwise: OutputToDisplayDriver(2), OutputToDisplayDriver(0), delay 0x024B, then A=4 → CompleteCommandProcessing |
| 0x01ED cmd_multi_stage_update | OutputToDisplayDriver(2), WaitForCmdClear, OutputToDisplayDriver(0), delay 0x024B, then 0x01C6 |
| 0x0205 cmd_handler_5e | PA_PREV_HIGH_BITS bit 6=0 → 0x01C6. Else toggle STATUS_FLAGS.5, then 0x01C6 |
| 0x0213 cmd_handler_6c | Poll PA & 0x3F: value 0x21 → toggle STATUS_FLAGS.4, then 0x01C6. Value 0 → if PA_PREV_HIGH_BITS bit 6=1 toggle STATUS_FLAGS.6; then 0x01C6 |
| 0x024B cmd_handler_a4 | Sets SERIAL_TIMEOUT (0x004C)=0x30, then a delay of 6 × nested DECX/DECA loops |

---

## 8. Serial Data Reception

**Entry:** 0x025E (WaitForData). The 192-bit serial protocol is the primary data path from the ND-120 to the panel.

### Protocol Overview

The ND-120 sends a **192-bit serial frame** consisting of 8 bytes × 3 parallel channels:
- **Channel 1 (PA0):** Time data — 8 bytes of 7-segment encoded time digits
- **Channel 2 (PA1):** Display data — 8 bytes of display mode + label characters
- **Channel 3 (PA2):** Status data — 8 bytes of CPU/system status bits

All three channels are clocked **simultaneously** by PC1 (generated by the P3 chip), with data arriving **MSB first** and **active-low** on the PA inputs.

### Trigger Mechanism

The P3 polls the /IRQ pin using BIH/BIL instructions (NOT as a hardware interrupt):
- **BIH (Branch if IRQ High):** Branches when /IRQ line = 1 (frame available)
- **BIL (Branch if IRQ Low):** Branches when /IRQ line = 0 (frame start confirmed)

This is different from the U3 chip which uses /EMP (PD7) for FIFO-ready detection.

### Reception Loop (0x0295-0x02DC)

```
WaitForData — Serial Path (0x0272):
    SERIAL_BIT_COUNT = 0
    PC1 = 1  (clock idle HIGH)
    pulse PC0 (ND-120 output strobe)
    LOOP_COUNTER = SERIAL_TIMEOUT (0x30)

    while /IRQ still HIGH:   ← wait for frame start
        delay...
        if timeout → reset SERIAL_TIMEOUT=1 → goto ProcessData

    // /IRQ went LOW: frame starting
    LDX #8  (8 bytes to receive)

    byte_loop:
        Read PA → PA_SAMPLE
        PC1 = 0  (clock LOW)
        PC1 = 1  (clock HIGH) ← data valid on rising edge

        Shift SHIFT_REG_CH1 right by 1
        Shift SHIFT_REG_CH2 right by 1
        Shift SHIFT_REG_CH3 right by 1

        // active-low inversion:
        if PA_SAMPLE.0 == 1: BCLR SHIFT_REG_CH1.7  (PA0=1 → bit=0)
        if PA_SAMPLE.0 == 0: BSET SHIFT_REG_CH1.7  (PA0=0 → bit=1)
        (same for CH2/CH3 from PA1/PA2)

        INC SERIAL_BIT_COUNT
        if SERIAL_BIT_COUNT != 8: goto byte_loop

        // 8 bits complete: store byte
        SERIAL_BIT_COUNT = 0
        DECX  (X goes 8→7→6→5→4→3→2→1→0)
        TIME_DATA_BUF[X] = SHIFT_REG_CH1  → 0x2D+X
        DISP_DATA_BUF[X] = SHIFT_REG_CH2  → 0x35+X
        STAT_DATA_BUF[X] = SHIFT_REG_CH3  → 0x3D+X
        if X != 0: goto byte_loop (receive next byte)

    // All 8 bytes received → decode and display
    ...
```

**Buffer fill order:** Bytes stored from high index down to 0 (most recent byte first).

| Buffer | Addresses | Content |
|--------|-----------|---------|
| TIME_DATA_BUF | 0x002D-0x0034 | Channel 1: 7-seg time digits (8 bytes) |
| DISP_DATA_BUF | 0x0035-0x003C | Channel 2: display mode + label (8 bytes) |
| STAT_DATA_BUF | 0x003D-0x0044 | Channel 3: CPU/system status flags (8 bytes) |

### Post-Reception Decode

After receiving all 8 bytes:

```
if DISPLAY_STATUS_FLAGS(0x0019) != PREV_DISPLAY_STATUS(0x0018):
    PREV_DISPLAY_STATUS = DISPLAY_STATUS_FLAGS
    InitDisplayClearPulse()      ← clear LCD on mode change
    SCROLL_COUNTER = 0           ← reset scroll position

DISPLAY_STATUS_FLAGS = 0

// Decode time_data[0:1] as display mode indicator
SERIAL_TEMP_CH1 = TIME_DATA_BUF[0]  (0x002D)
SERIAL_TEMP_CH2 = TIME_DATA_BUF[1]  (0x002E)
DecodeCharacterFromTable()
CHAR_DECODE_RESULT = A

if CHAR_DECODE_RESULT == 0: goto ShowSystemStatusDisplay (0x041D)
else: goto ShowMessageAndTime (0x03F4)   ← via message decode path
```

---

## 9. LCD Display System

The panel uses an **HD44780-compatible LCD controller** in 8-bit bus mode.

### Initialization Sequence (from RESET)

| Command | Value | Meaning |
|---------|-------|---------|
| Function Set | 0x38 | 8-bit bus, 2-line display, 5×7 font |
| Display On | 0x0C | Display on, cursor hidden, no blink |
| Entry Mode | 0x06 | Cursor increment, display no shift |
| Clear Display | 0x01 | Clear + return home (via InitDisplayClearPulse) |

Other HD44780 commands the firmware sends (every JSR 0x05FC in the ROM: 0x0107, 0x010C, 0x0111, 0x037C, 0x061A, 0x06B8):

| Command | Value | Where |
|---------|-------|-------|
| Cursor/display shift left | 0x18 | 0x037A — once per character step of the message scroll loop |
| Set DDRAM address | 0x80 \| addr | 0x06B8 (SetDisplayAddressAndWrite / CalculateDisplayPosition) |

No other command value (for example 0x02 Return Home) is ever sent.

### SendDisplayCommand (0x05FC) — RS=0 path

```
Port B ← command byte
PC2 = 0  (RS=0: command register)
PC3 = 1  (E=1: enable)
PC3 = 0  (E=0: latch command on falling edge)
PC2 = 1  (RS=1: return to data mode for subsequent character writes)
delay (5 DECA loops)
```

### OutputCharacterToDisplay (0x05D2) — RS=1 path (data mode)

```
CHAR_TEMP = character
if LCD_COLUMN_COUNT == 0x28 (40): SetDisplayAddress(0x00)  ← line 1 start
if LCD_COLUMN_COUNT == 0x68 (104): SetDisplayAddress(0x40) ← line 2 start
Port B ← CHAR_TEMP
PC3 = 1  (E=1)
PC3 = 0  (E=0: latch data)
INC LCD_COLUMN_COUNT
delay (5 DECA loops)
return 0x5F in A
```

Note: RS=1 (data mode) remains set from the previous SendDisplayCommand call, so characters are written directly without toggling RS.

### SetDisplayAddressAndWrite (0x06B0)

Sends HD44780 "Set DDRAM Address" command: `0x80 | address`. This positions the cursor before writing characters. Called automatically when LCD_COLUMN_COUNT wraps at 40 or 104.

### CalculateDisplayPosition (0x0690)

Computes the HD44780 DDRAM address from a logical position, adding the scroll offset (SCROLL_COUNTER) and wrapping to the 2-line layout:

```
Line 1: DDRAM 0x00-0x27 (positions 0-39)
Line 2: DDRAM 0x40-0x67 (positions 40-79)
```

### ND-120 Response Path — WriteToDisplayPort (0x023C)

When responding to ND-120 commands (NOT writing to LCD):
```
PC0 = 0  (strobe LOW)
Port B ← response byte
PC0 = 1  (strobe HIGH → rising edge latches data into CY7C401)
delay: A=0x20 outer count, DECX inner loop (about 32 × 256 passes; the first inner pass starts from whatever X holds)
return with A=0
```
OutputToDisplayDriver (0x0238) first ORs the byte with STATUS_FLAGS and masks to 7 bits before falling into WriteToDisplayPort. There is no ready/busy handshake: the strobe and the fixed delay are all the firmware does.

---

## 10. Character Decoding

The firmware uses two separate character lookup mechanisms:

### 7-Segment to ASCII: LookupCharacterCode (0x0623)

The ND-120 sends time data as **7-segment display patterns** (the same encoding used by the U3 built-in panel). The P3 reverses this to ASCII for the HD44780 LCD.

**TABLE_7SEG_TO_ASCII (0x0759)** — format: [seg_pattern, ascii_char] pairs, 0xFF terminated:

| 7-seg Pattern | ASCII | Digit |
|--------------|-------|-------|
| 0x00 | ' ' | blank |
| 0x77 | '0' | 0 |
| 0x11 | '1' | 1 |
| 0x6B | '2' | 2 |
| 0x3B | '3' | 3 |
| 0x1D | '4' | 4 |
| 0x3E | '5' | 5 |
| 0x7E | '6' | 6 |
| 0x13 | '7' | 7 |
| 0x7F | '8' | 8 |
| 0x3F | '9' | 9 |
| 0x50 | 0xFF | "show as binary" marker |
| 0x51 | 0xFF | "show as binary" marker |
| 0x54 | 0xFF | "show as binary" marker |
| 0x55 | 0xFF | "show as binary" marker |
| 0x4F | ' ' | blank variant |
| 0x66 | ' ' | blank variant |

(Table bytes 0x0759-0x077B in ROM order; key 0xFF at 0x077B ends it.) Before the search, LookupCharacterCode clears bit 7 of the pattern (BCLR 7,$1B at 0x0628), and it steps X by one (INCX at 0x0623, saved in X_SAVE) so the caller walks its buffer. A result of 0xFF makes DisplayTimeData (0x03E4) print the byte with DisplayBinaryDigits and set DISPLAY_STATUS_FLAGS.2.

If a pattern is not found, returns ' ' (0x20). This is the same 7-segment encoding as the U3 chip's TABLE_7SEG_PATTERNS, confirming the two chips use a compatible encoding system.

### Display Mode Decode: DecodeCharacterFromTable (0x0641)

The display channel bytes encode both the **display mode** (what type of information to show) and associated character data using a two-level lookup:

**Level 1 — TABLE_CHAR_DECODE (0x06BC):**
- Format: [key, value] pairs, terminated by 0xFE
- key matched against SERIAL_TEMP_CH2 (high byte of char pair)
- If value has bit7 set → strip bit7, return directly as character
- If value bit7=0 → use value as offset into level-2 table
- First entry (0x06BC-0x06BD) is [0x08, 0x80]: key 0x08 returns 0x00 directly (the status-display mode)

**Level 2 — TABLE_CHAR_SUBKEYS (0x06FD):**
- Format: subgroups of [key, result] pairs, 0xFF separates subgroups
- key matched against SERIAL_TEMP_CH1 (low byte of char pair)
- result = final decoded character

**Return value meanings:**
- 0x00 → ShowSystemStatusDisplay (binary status bars mode)
- Non-zero → ShowMessageAndTime (message + time display mode)

---

## 11. Display Modes

### Mode Selection

After each 192-bit serial packet, the first byte pair from TIME_DATA_BUF is decoded:

```
if DecodeCharacterFromTable(TIME_DATA_BUF[0:1]) == 0:
    → ShowSystemStatusDisplay (binary bar graph mode)
else:
    → decode 4-char message from DISP_DATA_BUF → ShowMessageAndTime
```

### Mode A: ShowMessageAndTime (0x03F4)

Displays a scrolling 4-character message on line 1, followed by time data on line 2.

```
Line 1: [MSG_BUFFER[0..3]]    ← 4 decoded ASCII chars, scrolling
         + status label string (from ROM_DISPLAY_STRINGS)
         + [DISP_DATA_BUF decoded chars]
Line 2: [TIME_DATA_BUF decoded time digits]
         + ':' separators
         + colon or space based on DISP_DATA_BUF[7].7
```

**Scroll logic:** SCROLL_COUNTER increments each frame. When a message character matches the previous frame, it is not redrawn. At SCROLL_COUNTER=0x28, counter resets to 0.

**Label selection (from ROM_DISPLAY_STRINGS):**
- MSG_BUFFER[0] != ' ': display "ADDRESS:" label (0x00B5)
- MSG_BUFFER[0] == ' ': display "P COUNT:" label (0x00BE)

**Time format:**  
Depending on bit 7 of 0x003C = DISP_DATA_BUF[7] (tested at 0x0502; see Section 4):
- Bit7=1: "  TIME:" prefix, then HH:MM:SS
- Bit7=0: "   UTC:" prefix, then HH:MM:SS

### Mode B: ShowSystemStatusDisplay (0x041D)

Displays binary status bar graph when the decoded display character is 0:

```
Line 1: [8 bar pairs from STAT_DATA_BUF]  ← '|' (0xDB) or '.' (0x2E) per bit
         + decimal status values (bit patterns 0-3)
         + DAY:/YEAR: and TIME:/UTC:/MONTH: labels based on bit 7 of 0x0038 and 0x003C (Section 4)
Line 2: [TIME_DATA_BUF decoded time]
```

Each status byte pair is displayed as a combination of bar characters:
- Bit set: '|' (0xDB — full block character on HD44780)
- Bit clear: '.' or '_' (0x5F)
- Decimal values for 4 pairs of status bytes
- Then "YEAR:" or "  MONTH:" label depending on display flag

**Ring digits (0x048C-0x04AB).** After the bars, four characters are printed from the byte pair in SERIAL_TEMP_CH1 (0x1B) / SERIAL_TEMP_CH2 (0x1C). Each is a digit if its bit is set, else '_' (0x5F):

| Position | Bit tested | Printed if set |
|----------|-----------|----------------|
| 1 | 0x1C bit 0 | '0' |
| 2 | 0x1B bit 1 | '1' |
| 3 | 0x1B bit 7 | '2' |
| 4 | 0x1C bit 6 | '3' |

Then the "ON "/"OFF" labels are printed twice (0x04BD, 0x04CC), chosen by 0x1B bit 7 and 0x1B bit 4 of the next byte pair (bit set → "ON ").

### DisplayTimeData (0x03CF)

Used by both display modes to render the time digits. Iterates 8 bytes from the specified buffer, converting each via LookupCharacterCode. If lookup returns 0xFF (no 7-seg match), falls back to DisplayBinaryDigits.

---

## 12. ROM Data Tables

### TABLE_CMD_LOOKUP_1 (0x0080) — 11 bytes

Command dispatch table for normal operation mode. See [Section 7](#7-command-processing).

### TABLE_CMD_LOOKUP_2 (0x008B) — 13 bytes

Command dispatch table for alternate mode. See [Section 7](#7-command-processing).

### ROM_DISPLAY_STRINGS (0x0098-0x00D5) — 62 bytes

Quote-terminated strings for LCD label output:

| Address | String | Purpose |
|---------|--------|---------|
| 0x0098 | `ON "` | ON status label |
| 0x009C | `OFF"` | OFF status label |
| 0x00A0 | `DAY:"` | Day label |
| 0x00A5 | `  TIME:"` | Time label |
| 0x00AD | `   UTC:"` | UTC label |
| 0x00B5 | `ADDRESS:"` | Address display label |
| 0x00BE | `P COUNT:"` | Performance counter label |
| 0x00C7 | `YEAR:"` | Year label |
| 0x00CD | `  MONTH:"` | Month label |

### JUMP_TABLE_CMD_DISPATCH (0x01A7) — 16 bytes

8-entry BRA jump table. See [Section 7](#7-command-processing).

### TABLE_CHAR_DECODE (0x06BC-0x06FC) — 65 bytes

Level-1 two-level character decode table. See [Section 10](#10-character-decoding).

### TABLE_CHAR_SUBKEYS (0x06FD-0x0758) — 92 bytes

Level-2 character decode subkeys. See [Section 10](#10-character-decoding).

### TABLE_7SEG_TO_ASCII (0x0759-0x077B) — 35 bytes (17 pairs + 0xFF end key)

7-segment pattern to ASCII reverse lookup. See [Section 10](#10-character-decoding).

---

## 13. Interrupt Vectors

Located at 0x07F8-0x07FF (end of ROM).

| Address | Name | Value | Notes |
|---------|------|-------|-------|
| 0x07F8 | VEC_TIMER | 0x00D6 | Timer ISR → RESET |
| 0x07FA | VEC_INT | 0x00D6 | External /IRQ → RESET |
| 0x07FC | VEC_SWI | 0x00D6 | Software interrupt → RESET |
| 0x07FE | VEC_RESET | 0x00D6 | Power-on reset → RESET |

**All four vectors point to RESET (0x00D6).** This is a deliberate design choice:
- The hardware timer is not used as a periodic ISR (TCR bit TIM=1 masks it; the TDR is only read as a pulse counter, see Section 2)
- The /INT pin (pin 2) is polled via BIH/BIL rather than used as an interrupt
- Vector order and addresses match the datasheet reset/interrupt flowchart (p.3-573): TIMER 0x07F8, INT 0x07FA, SWI 0x07FC, RESET 0x07FE
- Any unintended interrupt causes a full re-initialization (safe fallback)

This differs from the U3 chip which has a distinct Timer ISR at 0x08C8 for the software RTC.

---

## 14. Function Reference

| Address | Name | Description |
|---------|------|-------------|
| 0x00D6 | RESET | Boot: port config, LCD init, IRQ sync |
| 0x011F | UpdateTimersAndWait | Main loop head: timer chain, → WaitForData |
| 0x0142 | ProcessData | Command debounce + dispatch |
| 0x01A7 | JUMP_TABLE_CMD_DISPATCH | 8-entry BRA dispatch table |
| 0x01B7 | cmd_handler_10 | Wait for PA command=0 (WaitForCmdClear) |
| 0x01C0 | CompleteCommandProcessing | Send two ND-120 responses + wait clear |
| 0x01D1 | cmd_conditional_update | Conditional update: check STATUS_FLAGS.4 |
| 0x01DB | cmd_display_update | Check PA7 → update display + respond |
| 0x01ED | cmd_multi_stage_update | Two-phase output + timing delay |
| 0x01FD | cmd_handler_56 | LDA #4 → CompleteCommandProcessing |
| 0x0201 | cmd_handler_5a | LDA #8 → CompleteCommandProcessing |
| 0x0205 | cmd_handler_5e | Toggle STATUS_FLAGS.5 if conditions met |
| 0x0213 | cmd_handler_6c | Wait for cmd 0x21 → toggle STATUS_FLAGS bits |
| 0x0238 | OutputToDisplayDriver | OR with STATUS_FLAGS, AND 0x7F, → WriteToDisplayPort |
| 0x023C | WriteToDisplayPort | PC0 strobe: latch PB to ND-120 |
| 0x024B | cmd_handler_a4 | Multi-level delay / timing calibration |
| 0x025E | WaitForData | Serial reception + command poll |
| 0x03CF | DisplayTimeData | Display 8-byte time buffer via 7-seg lookup |
| 0x03F4 | ShowMessageAndTime | Display 4-char message + time (mode A) |
| 0x041D | ShowSystemStatusDisplay | Display binary status bars + time (mode B) |
| 0x057C | DisplayBinaryBars | Output 2 bar chars from status byte bits |
| 0x0597 | DisplayBinaryDigits | Output binary digit pair with decimal points |
| 0x05C8 | DisplayDecimalPoint | Output '.' and reset decimal point counter |
| 0x05D2 | OutputCharacterToDisplay | Write char to LCD with line-wrap |
| 0x05FC | SendDisplayCommand | HD44780 command (RS=0, E strobe) |
| 0x060C | DisplayStringUntilQuote | Output ROM string until '"' terminator |
| 0x0618 | InitDisplayClearPulse | LCD Clear (0x01) + delay |
| 0x0623 | LookupCharacterCode | 7-seg pattern → ASCII via TABLE_7SEG_TO_ASCII |
| 0x0641 | DecodeCharacterFromTable | Two-level display mode decode |
| 0x0690 | CalculateDisplayPosition | Logical pos + scroll → DDRAM address |
| 0x06B0 | SetDisplayAddressAndWrite | Send HD44780 Set DDRAM Address (0x80\|addr) |

---

## 15. Mermaid Diagrams

### Boot Flow

```mermaid
flowchart TD
    A[Power On / VEC_RESET 0x07FE] --> B[RESET 0x00D6]
    B --> C[DDRB=FF, DDRC=FF, DDRA=00]
    C --> D[TCR=0x78, STATUS_FLAGS=0x60]
    D --> E{PA3 = lock key?}
    E -- PA3=1 --> F[STATUS_FLAGS.7 = 1 locked]
    E -- PA3=0 --> G[STATUS_FLAGS.7 = 0 unlocked]
    F --> H[WriteToDisplayPort STATUS_FLAGS]
    G --> H
    H --> I[Zero RAM vars]
    I --> J[Timing delay cmd_handler_a4]
    J --> K[SendCmd 0x38: Function Set]
    K --> L[SendCmd 0x0C: Display On]
    L --> M[SendCmd 0x06: Entry Mode]
    M --> N[InitDisplayClearPulse: Clear + delay]
    N --> O[Wait for /IRQ HIGH]
    O --> P[Wait for /IRQ LOW]
    P --> Q[UpdateTimersAndWait 0x011F]
```

### Main Loop

```mermaid
flowchart TD
    A[UpdateTimersAndWait 0x011F] --> B[Sample PA hi-bits → PREV]
    B --> C[DEBOUNCE_COUNT = 5]
    C --> D[DEC TIMER1_COUNT]
    D --> E{== 0?}
    E -- No --> F[WaitForData 0x025E]
    E -- Yes --> G[DEC TIMER2_COUNT]
    G --> H{== 0?}
    H -- No --> F
    H -- Yes --> I[Clear STATUS_FLAGS.4]
    I --> J{DISPLAY_STATUS_FLAGS.5?}
    J -- Set --> F
    J -- Clear --> K[InitDisplayClearPulse]
    K --> L[TIMER2_COUNT = 6]
    L --> F

    F --> M{/IRQ HIGH?}
    M -- No --> N[Poll PA command]
    N --> O{cmd != 0?}
    O -- No --> F
    O -- Yes --> P[ProcessData 0x0142]
    M -- Yes --> Q[Serial reception path]
    Q --> R[Receive 192 bits / 3 channels]
    R --> S[Decode + Display]
    S --> A
    P --> A
```

### 192-bit Serial Reception

```mermaid
sequenceDiagram
    participant ND120 as ND-120 CPU
    participant IRQ as /IRQ Line
    participant P3 as MC68705P3
    participant LCD as HD44780 LCD

    ND120->>IRQ: Assert /IRQ HIGH (frame ready)
    P3->>P3: BIH detects HIGH
    P3->>P3: PC1=1 (clock idle), pulse PC0
    P3->>IRQ: Wait for /IRQ LOW (frame start)
    IRQ-->>P3: /IRQ goes LOW

    loop 8 bytes
        loop 8 bits
            P3->>ND120: PC1=0 (clock LOW)
            P3->>ND120: PC1=1 (clock HIGH)
            ND120->>P3: PA0=CH1 bit (active-low)
            ND120->>P3: PA1=CH2 bit (active-low)
            ND120->>P3: PA2=CH3 bit (active-low)
            P3->>P3: Invert + shift into SHIFT_REG_CH1/2/3
        end
        P3->>P3: Store byte: CH1→TIME_BUF, CH2→DISP_BUF, CH3→STAT_BUF
    end

    P3->>P3: DecodeCharacterFromTable(TIME_BUF[0:1])
    alt Result == 0
        P3->>LCD: ShowSystemStatusDisplay (binary bars)
    else Result != 0
        P3->>LCD: ShowMessageAndTime (message + time)
    end
```

### Command Dispatch

```mermaid
flowchart TD
    A[ProcessData 0x0142] --> B{PA stable 5×?}
    B -- No --> C[Back to loop start]
    B -- Yes --> D{Command PA5:0 != 0?}
    D -- No --> E[Update status, back to timer]
    D -- Yes --> F[CMD_CODE = PA & 0x3F]
    F --> G[TIMER2 = 0x50]
    G --> H{STATUS_FLAGS.4?}
    H -- 0 --> I[Walk TABLE_CMD_LOOKUP_1 at 0x0080]
    H -- 1 --> J[Walk TABLE_CMD_LOOKUP_2 at 0x008B]
    I --> K{Match found?}
    J --> K
    K -- No match / 0xFF --> L[JMP UpdateTimersAndWait]
    K -- Match --> M[func_index × 2 → X]
    M --> N[JMP JUMP_TABLE at 0x01A7 + X]
    N --> O[Command handler executes]
    O --> P[CompleteCommandProcessing 0x01C0]
    P --> Q[OutputToDisplayDriver × 2]
    Q --> R[WriteToDisplayPort STATUS_FLAGS]
    R --> S[Wait PA command clears]
    S --> L
```

### LCD Write Paths

```mermaid
sequenceDiagram
    participant FW as Firmware
    participant PB as Port B
    participant PC as Port C
    participant LCD as HD44780

    Note over FW,LCD: Command Write (SendDisplayCommand 0x05FC)
    FW->>PB: data byte
    FW->>PC: PC2=0 (RS=0 command)
    FW->>PC: PC3=1 (E=HIGH)
    FW->>PC: PC3=0 (E=LOW, latch command)
    FW->>PC: PC2=1 (RS=1 back to data)
    LCD-->>FW: (command processed)

    Note over FW,LCD: Character Write (OutputCharacterToDisplay 0x05D2)
    FW->>PB: ASCII character
    FW->>PC: PC3=1 (E=HIGH, RS already=1)
    FW->>PC: PC3=0 (E=LOW, latch data)
    LCD-->>FW: (character displayed, cursor advances)

    Note over FW,LCD: ND-120 Response (WriteToDisplayPort 0x023C)
    FW->>PC: PC0=0 (strobe LOW)
    FW->>PB: response byte
    FW->>PC: PC0=1 (strobe HIGH, latch to FIFO)
```

---

## 16. Key Differences from U3 Chip

The U3 (built-in panel) and P3 (external panel) serve different physical hardware and implement substantially different firmware despite both being 6805-family chips.

| Feature | U3 (built-in) | P3 (external) |
|---------|--------------|--------------|
| **ROM size** | 4KB | 2KB |
| **Display** | 7-segment shift register (PC3-PC7) | HD44780 LCD 2×40 |
| **Display output** | Bit-serial shift chain via Port C | Parallel 8-bit bus via Port B |
| **Display commands** | No — custom shift protocol | Yes — HD44780 command set (0x38, 0x0C, 0x06, 0x01) |
| **Input path** | FIFO (CY7C401) via Port A/B + PB3 gate | Dual: FIFO command (PA[5:0]) + serial 3-channel (PA0-PA2) |
| **Serial protocol** | None — PANC FIFO only | 192-bit × 3 channel serial reception |
| **IRQ usage** | /EMP (PD7) for FIFO-empty detection | /INT pin (pin 2, not a port bit) polled via BIH/BIL for serial frame |
| **Timer ISR** | Distinct ISR at 0x08C8 (400 Hz; drives the software RTC) | All vectors → RESET (no timer ISR) |
| **Software RTC** | Yes — full year/month/day/hour/min/sec | No — receives time from ND-120 via serial |
| **MM58274 RTC** | Yes — hardware RTC chip init + read | No hardware RTC |
| **Physical buttons** | None (display-only side) | Yes — PA3=lock key, PA[5:0]=button/command |
| **Character decode** | 7-seg → shift out directly | 7-seg → ASCII (TABLE_7SEG_TO_ASCII reverse lookup) |
| **Display strings** | None | ROM_DISPLAY_STRINGS (DAY:, TIME:, UTC:, etc.) |
| **Port B role** | IDB bus (ND-120 read/write) | LCD data + ND-120 response |
| **Port C role** | Display shift register serial out | LCD RS/E control signals |
| **Lock key** | Not present | PA3 hardware lock key; affects STATUS_FLAGS.7 |

### Why Different Serial Protocols?

The U3 chip **generates** 7-segment patterns and writes them to its own shift-register display chain. The ND-120 commands the U3 via the PANC FIFO.

The P3 chip **receives** 7-segment patterns sent by the ND-120 over a dedicated 3-channel serial link, converts them back to ASCII, and writes them to an HD44780 text LCD. The ND-120 sends the same data format it would send to a 7-segment display (compatible with the U3 protocol), and the P3 performs the necessary translation.

---

## 17. Corrections to Existing Analysis Files

The documents reviewed here have been removed from the repository. Their correct facts are merged into sections 1-16; the error lists below are kept as the record of what was wrong.

### `Code/68705/P3/C-code-p3.md` (and its copy `Code/68705/P3/p3_code/mc68705_readme.md`)

**Correct:**
- PA[5:0] = 6-bit command code ✓
- CY7C401 FIFO ✓
- 192-bit serial packet ✓
- PC1 = serial clock ✓
- HD44780 LCD commands (0x38, 0x0C, 0x06) ✓

**Incorrect or incomplete:**

1. **Serial channels described wrongly:** "Channel 1 (PA0): Time data, Channel 2 (PA1): Display data, Channel 3 (PA2): Status data" — the channel labels are correct but the document says "active low inputs with MSB-first reception." This is confirmed correct.

2. **"PC0 = Display strobe signals"** — WRONG. PC0 is the **ND-120 output strobe** (WriteToDisplayPort → CY7C401). The display strobe for LCD is PC3 (E). PC0 has nothing to do with the LCD.

3. **PORTA description "Serial sampling clock (PC1)"** — PC1 is on **Port C**, not Port A. Port A has no clock function; it is the data INPUT.

4. **"7-bit data with status integration" on PORTB** — the data is output on 8 bits of Port B; the upper bit is masked to 0x7F before sending to ND-120 (AND #0x7F in OutputToDisplayDriver). So effectively 7 data bits with STATUS_FLAGS[6:0] ORed in.

5. **Timer described as "1200Hz"** — There is **no timer ISR**. All interrupt vectors point to RESET. The timer is configured (TCR=0x78) but the firmware uses the /IRQ pin for serial frame detection via polled BIH/BIL, not a periodic interrupt.

6. **RAM layout "0x0080-0x008D: Command lookup tables"** — these are in ROM, not RAM.

7. **"128+ dispatch codes"** — the actual dispatch has **8 function indices** via the jump table; the lookup tables contain only 5-6 entries each. The "128+" figure from the C emulation is incorrect.

8. **Pin numbers and supply pins** (both copies) — "VBB pin 1, VSS pin 14, VDD pin 28, XTAL pin 15, PORTA pins 16-23, PORTB pins 6-13, PORTC PC0-PC7 pins 2-5, 24-27" — WRONG. The datasheet pinout (p.3-585) is in Section 3; the chip has no VBB pin and no PC4-PC7.

9. **"RAM 64 bytes (0x0010-0x004F)", "ROM 2KB"** — the chip has 112 bytes of RAM (0x0010-0x007F) and 1804 bytes of user EPROM (datasheet p.3-571); see Section 2.

10. **Display command 0x02 "Return Home"** — never sent by the firmware. 0x18 "Shift Left" is sent (0x037A).

11. **Command ranges "0x08-0x0C button polling", "0x48-0x4A direct output", "0x77-0x7F serial reception"** — not commands. They are artefacts of the decompiler's switch view; see item 7.

### Former `Code/68705/P3/P3/` folder (eight files)

These were an earlier pass, written as notes on a Ghidra project that is not part of this repository: `command_analysis.md`, `ghidra_hardware_docs.md`, `ghidra_updates.md`, `mc68705p3_pinout.md`, `panel_controller_analysis.md`, `port_analysis.md`, `technical_report.md`, `variable_renames.md`. What was wrong in them:

1. **Pinout** (`mc68705p3_pinout.md`, `ghidra_hardware_docs.md`) — every pin number was wrong (VBB on pin 1, XTAL/EXTAL as one pin 15, PA0 on pin 23, PC0-PC7). The datasheet pinout is in Section 3. The supply currents, power and "±0.01% crystal" figures had no source.
2. **Display hardware** — "HD44100H LCD driver", "CD4035 shift registers", "LD-H7919" and "SX 423 M4" modules, "PC0 = display strobe / CD4035 latch", "PC3-PC7 display control". The firmware drives an HD44780-type text LCD with PC2=RS and PC3=E (Section 9); PC0 is the ND-120 strobe; PC4-PC7 do not exist.
3. **"readIRQ() not found / external"** — it is the BIH/BIL instructions testing the /INT pin (Section 3).
4. **"PA7 status/control" and "PA4-PA7 button matrix"** — PA4/PA5 are command bits; PA7 is read only by cmd_display_update (0x01DB).
5. **Dispatch "0x00-0xFE, 128+ cases", "0x90-0x94 direct output", "0xA2 special return", "0xEE-0xFE serial reception"** — there are 8 handlers in the jump table at 0x01A7 (Section 7). The dual tables at 0x0080/0x008B mean the same command byte (e.g., 0x02) dispatches to **different handlers** depending on STATUS_FLAGS.4.
6. **"InitDisplayClearPulse signals the CPU" / "button events interrupt the CPU via the timer"** — InitDisplayClearPulse only sends LCD command 0x01 (0x0618). The only outputs toward the ND-120 are Port B and the PC0 strobe.
7. **"TimeDisplayBuffer" at 0x0035-0x003C** — this holds the raw channel-2 serial bytes (STA $35,X at 0x02D3), not formatted time.
8. **"DisplayTimeData shows 9 characters"** — the counter starts at 9 but is decremented before the first character, so 8 are shown (0x03D1-0x03DB).
9. **PANC/PANS bit formats, clock commands, button names, key-lock positions, UTIL/HIT/RING/MODE displays** (`panel_controller_analysis.md`) — the file gives no source for them, and none of it can be seen in this firmware. Not carried over.

---

*Analysis performed April 2026 in Ghidra on MC68705P3.BIN; datasheet and ROM checks added September 2026.*  
*All findings based on direct disassembly — no assumptions.*
