Skip to content

ADB - Apple Desktop Bus

The ADB-related registers are:

• Keyboard Data register ($C000) • Modifier Key register ($C025) • Keyboard Strobe register ($C010)

• Mouse Data register ($C024)

• ADB Command/Data register ($C026) • ADB Status register ($C027)

C000/C010 are legacy registers to emulate Apple //e. The others are new and ADB-specific.

C000/C010 must be implemented using the underlying ADB logic.

Those registers are all managed by the KeyGloo, a 6502-based Microcontroller that interposes between GS and ADB bus. The GS host has access to about 20 commands it can send to the uC, and it is the uC that manages the actual ADB bus - likely chosen this way because the ADB bus is extremely timing sensitive.

In this emulator, we do not need to go to that level of detail - a real IIgs cannot do anything with ADB except via the uC. The one thing to be mindful about, is that GS software can read from and write to the ADB memory, and does so (address 0x51) in one case to determine if the machine was reset or just powered on. So we should allocate 3K for the ROM and 96 bytes for the RAM inside the keygloo object.

Interrupts

the ADB Controller can assert 3 interrupts:

  • keyboard data available
  • mouse data available
  • data available (when command/data register contains valid data)

"the keygloo chip can be setup to int whenever keylatch is written to by the uC". Cleared by reading status register followed by read of keyboard latch. If any s/w reads keyboard after status register says int occurred, then int condition will be cleared. The keylatch, the keymodifiers latch are both valid together until the strobe is cleared.

If mouse has moved or button pushed, returns two bytes data. uC writes x/y values into KEYGLOO which triggers the interrupt. When the second byte is read back by the cPU, the interrupt is cleared. The uC only writes mouse data when registers are empty (after mouse latch X has been read). "Only interrupted at VBL time". unclear how or why. uC won't do another FDB mouse poll until both bytes have been transferred.

Layers

There are essentially two layers here.

  1. the microcontroller-bus-device layer
  2. the GS-microcontroller layer

Generally the system polls. However, a device may signal a Service Request (SR or SRq) to tell the host it has data. All transactions are triggered by the host though.

"ADB Keyboards are prohibited by Cortland from issuing Service Requests". So only the Mouse will ever do this.

Microcontroller

The ADB microcontroller also implements the keyboard buffer - 16 keystrokes can be queued up / stored. Such that when we seek to refill the keyboard GLU registers, we fetch from that queue.

Mouse Interrupts can occur only during VBL.

So, I think the microcontroller is what is implementing ADB protocol for Mouse and Keyboard, usually, BUT, the CPU can also execute custom ADB commands. YES:

"Scans built-in (internal) keyboard and periodically polls FDB for keyboard and keypad data" "acts as FDB host for mouse by periodically polling the FDB mouse" "acts as a transceiver chip for other FDB devices".

https://www.brutaldeluxe.fr/documentation/cortland/v3_10_FDBERS.pdf

Also this has links to a dump of the ROM https://llx.com/Neil/a2/adb.html

There is a separate KEYGLOO chip "acts as holding register so that data written by uC can be read by system and data written by system can be read by uC". Also this is the chip that generates interrupts.

The keyboard auto-repeat rate is handled by the uC, and delay-before-auto-repeat. There is also "dual speed mode" where arrow key + control is 2x the repeat rate.

"All FDB keyboards and keypads will automatically be handled by the uC".

Key presses are returned by loading keylatch w/data. "The key-modifiers latch is updated when a key is pressed and the ASCII value is loaded into the keylatch". The values reflect the state of modifiers when the key was pressed - NOT the current state of the modifiers.

"Bit 5 update" is used to signify modifier latch was changed w/o any other keypresses - only occurs when KYSTB is clear. if only control or shift is pressed and KYSTB is clear, then uC sets Update bit and changes modifiers latch.

Appendix B shows keycodes generated by FDB keyboard.

"undefined codes for FDB keyboards are passed directly through keyboard latch with keypad bit set".

"Polling" by the uC will be simulated by calling the event handler. We could also, potentially, have a frame handler if we needed periodic processing not as a response to an event.

the IIgs Firmware Reference has thorough and much clearer write-up on the uC commands and responses, and esp. of the status byte. The status byte per Kent (in KEGS):

The Firmware Reference manual says in the C026 description: "Data at interrupt time in this register defined as follows:". I agree the Hardware reference manual gives a brief $C026 description that doesn't mention this, but the Firmware Reference is better. The ADB has 3 states for $C026, and returns different values in each of the states. KEGS calls them ADB_IDLE, ADB_IN_CMD, and ADB_SENDING_DATA. The documented bits are the ADB_IDLE definitions. In ADB_IN_CMD (entered by writing $C026 bits 3:0 to several different values), KEGS returns 0 on reads to $C026 (I don't know what the actual hardware does in this case, but nothing I've ever run seems to care about this). And in ADB_SENDING_DATA (the CMD is now returning data), you return the data requested, and when done, go to ADB_IDLE.

Keyboard Modifier Register - $C025

Bit Modifier Key Pressed
7 Command
6 Option
5 Modifier Latch updated, but no key pressed
4 Numeric keypad key
3 non-modifier key is being held down
2 Caps Lock
1 Control
0 Shift

Bit 5=1 means these are "live" values. I think this means if 5=0, then these values are latched and associated with the most recent key press? That is unclear.

These are pretty straightforward.

Mouse Data register - $C024

Bit Description
7 1=Mouse button is up, 0=Mouse button is down
6 Delta sign bit: =1, delta is negative; =0, delta is positive
5-0 Delta X/Y (with sign bit, up to +/- 31)

Read register twice in succession; first to get X, second to get Y. If you get out of sync, you will be reading X for Y and vice-versa. Is there a reset for this?

ADB Command / Data Register - $C026

On a write:

The command (as defined below) is written in these 8 bits. These are uC commands, not directly ADB commands.

On a read: Bits 0-7 are data from the keyboard micro

AT INTERRUPT TIME, on a read: (Per Cortland 2.01 Apple IIgs Firmware Reference Manual Appendix E)

Bit Name Description
7 Response/status When this bit is 1, the ADB microcontroller has received a response from an ADB device previously addressed. 0: No response.
6 Abort/CTRLSTB flush When this bit is 1, and only this bit in the register is 1, the ADB microcontroller has encountered an error and has reset itself. When this bit is 1 and bit 4 is also 1, the ADB microcontroller should clear the key strobe (bit 7 in the Keyboard Data register at $C000).
5 Reset key sequence When this bit is 1, the Control, Command, and Reset keys have been pressed simultaneously. This condition is usually used to initiate a cold start up
4 Buffer flush key sequence When this bit is 1, the Control, Command, and Delete keys have been pressed simultaneously. This condition wil result in the ADB microcontroller's flushing al internally buffered commands.
3 Service request valid When this bit is 1, a valid service request is pending. The ADB microcontroller will then poll the ADB devices and determine which has initiated the request
2 - 0 Number of data bytes returned The number of data bytes to be returned from the command is listed here.

Subsequent reads will pass data bytes per bits 2-0.

ADB Status Register - $C027

Bit Name Modifier Key Pressed
7 Mouse Data Reg Full =1, Mouse Data register at $C024 is full
6 Mouse int ena/dis = 1, mouse interrupt is enabled, int generated when mouse register contains valid data and in vbl period
5 Data register full =1, command/data register contains valid data
4 Data interrupt ena/dis =1, generate interrupt when command/data reg contains valid data
3 Keybd data register is full =1, kbd data ($C000) has valid data. =0, kbd register empty. is cleared when kbd data reg is read, or adb status is read
2 Keybd data register int ena/dis =1, interrupt thrown when kbd register contains valid data
1 Mouse X Available =1, a X-coord data is available; if =0, a Y-coord data is available
0 Command register full =1 when command/data register is written to; =0 cmd register empty, cleared when cmd/data register is read by uC

ok, a clearer description of this is in the Cortland manual: The Command Full flag is set to 1 when the system writes to the command register; and cleared to when the Keyboard micro reads the command register.

Commands / Signals

Command: message sent to a specific device Signal: broadcast to instruct all devices to perform a function.

Commands: * Listen (write to device) * Talk (read from device) * Device Reset * Flush

Signals: * Attention * Sync * Global Reset * Service Request

Transaction

Attention / Sync Address: 4 bits Cmd: 2 bits: LISTEN Register: 2 bits Then, 2 to 8 bytes of data

Address Command Code Register
7 6 5 4 3 2 1 0 Command
x x x x 0 0 0 0 Send Reset
A3 A2 A1 A0 0 0 0 1 Flush
x x x x 0 0 1 0 Reserved
x x x x 0 0 1 1 Reserved
x x x x 0 1 1 0 Reserved
A3 A2 A1 A0 1 0 r1 r0 Listen
A3 A2 A1 A0 1 1 r1 r0 Talk

Listen (Addressed)

Requests device to store data being transmitted in specified internal register (0-3). When the host addresses a device to listen, the device receives the next data packet from the host and places it in the appropriate register. (So, 'registers' can be multiple bytes). If the addressed device detects another command on the bus before it receives any data, the original transaction is immediately considered complete. (This may never happen in practice in an emulator).

Send Reset (Broadcast)

When a device receives this command, it clears all pending operations and data and inits to power-on state. This will reset ALL devices.

Flush (Addressed)

Clear all pending commands from the device.

Attention/Sync

start of every command. Does not need to be emulated.

Global Reset

If bus held low for 2.8ms, all devices reset. Only initiated by host.

Service Request

Sent by device, to inform host that a device requires service, for example, when there are data to send to the host. Following any command packet, a requesting device can signal an SR by holding the bus low. The devices hold this until they have been served. At that point, the Host polls all devices by sending a Talk Register 0 command beginning with last active device. Only the device with data to send will respond to this Talk command. The Mouse does NOT ever use this SR mechanism.

Device Registers

0-2 are device specific. Register 3 is Talk: status, and device address handler; Listen: status.

  • Keyboard Register 0
  • Bit 15: Key Released
  • Bit 14-8: Keycode 2
  • Bit 7: Key Released
  • Bit 6-0: Keycode 1

  • Mouse Register 0

  • Bit 15: Button Pressed (1 = button up, 0 = button down)
  • Bit 14: Moved Up
  • Bit 13-8: Y move value
  • Bit 7: always 1
  • Bit 6: Moved right
  • Bit 5-0: X move value

  • Registers 1 and 2

  • basically unused by keyboard/mouse

  • Register 3

  • Bit 15: reserved, must be 0.
  • Bit 14: exceptional event.
  • Bit 13: SR enable
  • Bit 12: Reserved, must be 0.
  • Bit 11-8: Device address.
  • Bit 7-0: Device handler.

  • Device Handlers. As Listen Register 3 data,

  • $FF - initiate self-test.
  • $FE - change address field to new address sent in this message.
  • $FD - change address to new address "if activator is pressed"
  • $00 - change address and enable fields to new values sent by host

  • Device addresses

  • $02 - Encoded Devices - Keyboard
  • $03 - Relative Devices - Mouse

The keycodes are scancodes, not ASCII: https://github.com/szymonlopaciuk/stm32-adb2usb/blob/main/src/keymap.h

https://github.com/gblargg/adb-usb/blob/master/keymap.h

So, we need to implement just like the ADB microcontroller. 1. Receive (and send) messages on an ADB Bus. Have a data structure for a queue of packets. 1. as packets come in, we present them to the ADB Command/Data registers. 1. But, we also process them to some degree, e.g. updating values for $C024, $C025, $C000, etc.

Commands to Microcontroller

Apparently you send commands to the uC in a format different from the ADB bus itself. That makes sense, the ADB bus timing is very particular.

See Page 13 on in the v3_10_FDBERS document.

Microcontroller Mode and Configuration

The MODES byte is:

Bit Function
7 Reset on RESET key only (control not needed)
6 Set XOR LOCK-SHIFT mode
5 Change FDB Keyboard Layout to //e Layout
4 Buffer Keyboard Mode
3 4x Repeat Enabled; instead of dual (2x) repeat
2 Include Spacebar, Delete key on Dual Repeat
1 Disable auto-poll of fdb mouse
0 Disable auto-poll of fdb keyboard

KeyGloo Dev notes

Even though today I only have two devices, and am unsure about ever supporting any other devices (graphics tablets? radio tuners? security keys?)

[x] Patch in event handling for keyboard. [x] Patch in event handling for Mouse. [x] test harness

Apparently the CDA panel is handled by the firmware during VBL interrupts. (NO)

Event handling. Put it into this object? Or, handle it at the device level and decouple the KeyGloo object from: timing, SDL, etc? Yeah, let's try that.

ok. So, events are sent in this way: main -> KeyGloo -> ADB_Host -> ADB_Device1, Device2, etc. then, based on this, the ADB devices should set some information in their registers. Then, KeyGloo should poll through the ADB Host to get some kind of status. E.g. was a key pressed. It would do this with a talk() command. KeyGloo does have special knowledge of Keyboards and Mice.

in the ADB Keyboard device, the keycodes are scan codes, not ascii, even though the GS manual says ascii. So if we're to maintain this fiction, there is a lot to do.

  1. Convert PC/SDL scancode to ADB keyboard scan code.
  2. convert ADB Scancode to ascii (in the key latch). or leave it as-is for direct reads from the adb key device.

now, this is ickyish because we already have to call an SDL routine to map from scancode to ascii, in order to take account of international keyboards. Each international keyboard in a GS is going to have a map. Would this imply we need a unique

KB register 0 just packs up to two events into one message.

https://www.brutaldeluxe.fr/documentation/cortland/v3/The%20ADB%20uC%20Tool%20Set%20-%20Peter%20Baum%20-%20Rev%201.1%20-%2019860515.pdf

https://gemini.google.com/app/c600196fa04cec55

OK, the buffer DOES live in the microcontroller, NOT in the keyboard device. Well, the keyboard device seems to be able to hold two keypresses.

Example Scan
If you press the 'A' key (ADB code 0x00):

Register 0 result: 0x00FF
00: High byte (Bit 15=0 for Down, Bits 14-8=0x00 for 'A').
FF: Low byte (No second key, idling).

If you release the 'A' key:

Register 0 result: 0x80FF
80: High byte (Bit 15=1 for Up, Bits 14-8=0x00 for 'A').
FF: Low byte (Idling).

So while we -could- use both bytes we really probably don't have to. It said "if no data, request ignored". not sure that's right.. if there's data, it goes in high byte, and 0xFF in low byte. 0xFFFF is reset / poweron.

Great convo on this on the slack: https://apple2infinitum.slack.com/archives/CPSGNGE05/p1767030235302959

I am motivated to run this PACMAN GAME on my emulated GS!!

Mouse

This doc goes into great detail on the Mouse on the GS.

https://www.brutaldeluxe.fr/documentation/cortland/v3/Mouse%20ERS%20-%20Fern%20Bachman%20-%20v00.10%20-%2019850715.pdf

key takeaways so far:

[ ] the mouse interrupts in sync with vbl, only 60 times per second. How does that work? Does ADB Micro get fed the vbl signal? yes, it and the keygloo do. [ ] +/- 63 counts max movement, is 0.8 inches of travel. [ ] there IS a button 1. [ ] At RESET, interrupts must be disabled
[ ] mouse data - 1st read gets X data and button 1 data; 2nd read gets Y and button 0

Control Panel (CDA) Ctrl-OA-ESC

hello! I’m struggling to figure out exactly which IRQ source is triggered on a control-openapple-ESC to open the CDA panel.

First, this seems to be signaled in the ADB Command/Status Register (“at interrupt time”) in bit 5. Cortland doc labels this “Desktop Manager Key Sequence.” but then contradicts itself halfway down the page calling it the “reset key sequence” indicator. But the FW Ref also calls it “Desktop Manager Key Sequence”.

KEGS has routines to set and clear C026[3]. This is "SRQ Valid If Set". That's Service Request. And C026[5] is "Desktop manager key sequence" pressed.

So when Bit 3 here is set we assert an ADB_SRQ interrupt to the cpu. After the 3 keys are hit, it sets the IRQ, but goes ahead and processes the data as a normal keypress.

But what clears it exactly?

in KEGS, the function adb_kbd_talk_reg0. And this is called only by uC commands 0xC0-0xCF. "Talk Dev x Reg 0" to the keyboard. And when the response data from that has been stored, we clear the IRQ. This doesn't feel right? That would clear it in time for then C026 to read the data from this request..

So when SRQ bit is set, and C026 is read, we present this status register.

Hmmm. Another possibility is that the keyboard is generating an ADB SRQ? and then the uC polls it to see which device did it. perhaps the uC expects the host to poll for the SRQ initiator, and KEGS just triggers it on the KB. This is mysterious.

this hinges on the "Data Register", which "at interrupt time" is ..

ok. If c027[54] = 11, then we are in "interrupt time". and reading the datareg C026 and/or the full response (based on number bytes noted in C026[2:0]).

we want an IRQ_ADB_DATAREG

c027[4] = data int enable; c027[5] = data reg full
1108 BE5A              @66      EQU   *
1109 BE5A AF C5 00 E1           LDA   >IRQ_KMSTATUS            ;Test for data register interrupts    -- C027 was copied here.
1110 BE5E 29 30                 AND   #$30                     ;Bits 4,5 are status/enable
1111 BE60 0A                    ASL   A                        ;Bit 5 to 'c'/bit 4 to bit 7
1112 BE61 0A                    ASL   A                        ;
1113 BE62 0A                    ASL   A                        ;
1114 BE63 10 61                 BPL   @55                      ;+ then not enabled                   -- data int not enabled
1115 BE65 90 5F                 BCC   @55                      ;'c'=0 then not data reg int          -- data int enabled, but this is not a data interrupt
1116 BE67
1117 BE67 AD 26 C0              LDA   DATAREG                  ;Pick up DATAREG for testing now and later
1118 BE6A 8F C4 00 E1           STA   >IRQ_DATAREG             ;Save for later
1119 BE6E 10 07                 BPL   @66_1                    ;Not response byte interrupt          -- c026[7] "response byte if set, otherwise status byte". response from what?
1120 BE70 22 40 00 E1           JSL   >IRQ_RESPONSE            ;Go do interrupt if possible          -- this is a "response byte". 
1121 BE74 20 83 BF              JSR   TSTSETFLG                ;Check 'c' and maybe set flag
1122 BE77
1123 BE77              @66_1    EQU   *
1124 BE77 AF C4 00 E1           LDA   >IRQ_DATAREG             ;Recall status byte
1125 BE7B 29 08                 AND   #$08                     ; and test for SRQ interrupt
1126 BE7D F0 07                 BEQ   @66_2                    ;=0 then not an SRQ
1127 BE7F 22 44 00 E1           JSL   >IRQ_SRQ                 ;Go do interrupt if possible
1128 BE83 20 83 BF              JSR   TSTSETFLG                ;Check 'c' and maybe set flag
1129 BE86
1130 BE86              @66_2    EQU   *
1131 BE86 AF C4 00 E1           LDA   >IRQ_DATAREG             ;Recall status byte
1132 BE8A 29 20                 AND   #$20                     ; and test for desktop mgr interrupt
1133 BE8C F0 13                 BEQ   @66_3                    ;=0 then not an desktop mgr interrupt
1134 BE8E AD 25 C0              LDA   KEYMODREG                ;Is Shift key pressed also ?
1135 BE91 6A                    ROR   A                        ;Rotate bit0 (shift) to 'c'
1136 BE92 90 06                 BCC   @66_2_1                  ;BRA if not pressed with Shift key
1137 BE94 22 08 00 FE           JSL   >TODESKMGR               ;Else use private vector to jmp to
1138 BE98 80 04                 BRA   @66_2_3                  ;Continue on  
1139 BE9A              @66_2_1  EQU   *
1140 BE9A 22 48 00 E1           JSL   >IRQ_DSKACC              ;Go do interrupt if possible
1141 BE9E              @66_2_3  EQU   *
1142 BE9E 20 83 BF              JSR   TSTSETFLG                ;Check 'c' and maybe set flag
1143 BEA1
1144 BEA1              @66_3    EQU   *
1145 BEA1 AF C4 00 E1           LDA   >IRQ_DATAREG             ;Recall status byte
1146 BEA5 29 50                 AND   #$50                     ; and test for flush buffer interrupt  -- OH, this is actually handled this way.
1147 BEA7 0A                    ASL   A                        ;Shift bit 6 to carry
1148 BEA8 0A                    ASL   A                        ;
1149 BEA9 F0 09                 BEQ   @66_4                    ;=0 then not flush buffer interrupt
1150 BEAB 90 03                 BCC   @66_2_2                  ;'c' clear then don't clear KBDSTRB
1151 BEAD 8D 10 C0              STA   KBDSTRB                  ;Remove key in buffer
1152 BEB0              @66_2_2  EQU   *
1153 BEB0 22 4C 00 E1           JSL   >IRQ_FLUSH               ;Go do interrupt if possible
1154 BEB4
1155 BEB4              @66_4    EQU   *
1156 BEB4 AF C4 00 E1           LDA   >IRQ_DATAREG             ;Recall status byte
1157 BEB8 C9 40                 CMP   #$40                     ; and test for keyboard micro interrupt
1158 BEBA D0 07                 BNE   @66_8                    ;= then abort interrupt                 -- probably never happen for us
1159 BEBC 22 50 00 E1           JSL   >IRQ_MICRO               ;Go do interrupt if possible
1160 BEC0              @66_6    EQU   *
1161 BEC0 20 83 BF              JSR   TSTSETFLG                ;Check 'c' and maybe set flag
1162 BEC3              @66_8    EQU   *
1163 BEC3 4C 8F BF              JMP   IRQDONE1_1               ;See if we exit or go to OS int handler

Test Suite

All commands return at least a single response byte

Value of bits 2-0 in response byte match number of bytes of response, and is 0 if there is only the response byte.

Except 0x0E and 0x0F, which should return bits 2-0 as "1", which is the # entries following.

Dec 31, 2025

happy new year!

ok, the ADB test harness is starting to come together.

SDL event -> keyglu -> adb_host -> adb_keyboard

the SDL scancode is getting converted to ADB scan codes. I need to fill the table in quite a bit.

the keyboard needs a concept of "what keys are down right now".

Jan 2, 2026

On Standard Keyboards left and right modifiers cannot be discriminated. Right Shift acts just as left Shift for example.

So, I think this means regardless of USB code, send an ADB_LEFT_SHIFT. We can try it, anyway.

Jan 3, 2026

the ADB uC should be the entity responsible for mapping from ADB Keyboard scancodes to ASCII, based on the language setting. However, it's not done with pure lookup tables. It seems there might be a partial lut somewhere but for compactness there is probably also some code somewhere to handle stuff like lowercase to uppercase letters when user is holding shift, etc.

This is what we need to be putting in the queue.

The KeyGLU might be a separate chip from the ADB uController. i.e. it might be the bit that is the four registers between the GS and the ADB uC. Consider renaming the classes? Currently, the functions are merged and the KeyGloo class is both the registers and the uC. Other than potential future reusability, there is no benefit to splitting these out and adding extra complexity.

What I implemented so far is: partial lookup table for 0x00 - 0x5F: and then a bunch of IF statements checking various modifier keys. E.g., shift and 'a' subs 32 from the char to get 'A'.

Jan 6, 2025

thinking about the auto-repeat. There is a GS setting to stop auto repeat. We can do that by ignoring repeats from the OS: events with: event.key.repeat == 1 are repeat keys.

But if I were to implement our own key repeat, how is that done..

Setting Repeats/sec Divisor 120 Ticks/sec ~59.9226 Ticks/sec
0 40 3 40 1
1 30 4 30 2
2 24 5 24 2
3 20 6 20 3
4 15 8 15 4
5 11 11 10.91 5
6 8 15 8 8
7 4 30 4 15
Setting Delay before repeat (sec) Ticks (of 120/sec) ticks (~59.9226)
0 1/4 sec 30 15
1 1/2 sec 60 30
2 3/4 sec 90 45
3 1 sec 120 60
4 no rpt -

My normal frame rate is 59.9226.. close to 60, which is 1/2 the 120 rate. Likely close enough for government work.

So: last_repeat_key; next_repeat_cnt; the start_repeat_in is reset on new key down; e.g. if you're repeating f but then hit and hold g, it waits the "delay before repeat".

So we use a frame handler here. decrement next_repeat_cnt (dncntr), when it's 0, call store_key_to_buffer with the saved "repeated key" info. Only do this when "keysdown">0.

divisor divisor_count repeat_active delay delay_count

on key up, clear divisor_count, repeat_active=0

We know multiple keys can be down; but the micro should track and repeat the -most recent- one.

each iteration, 
  if key is down, 
    if repeat_active = 0,
      incr delay_count
      if delay_count == delay, 
        delay_count=0, repeat_active=1
      return

    if key down is same as last key, 
      increment divisor_count
      If divisor_count = divisor, then divisor_count=0, insert key into buffer again

    if key down different from last key,
      repeat_active=0; divisor_count=0

  if key up,
    repeat_active=0
    divisor_count=0
    delay_count=0

Jan 8, 2026

ok, the ROM is doing this:

   LDA C026
   LDA C027
   ROR A
   bcs 8501 - if bit 0 is set
   LDX #1D4C
   LDA #7
   STA C026 ; write cmd 7 / synch
8515: DEX
   BEQ 8521 - if X hits 0, exit this loop
   LDA C026
   LDA C027
   ROR A
   BCS 8515

This is a loop, to do a synch, then keep reading bit 0 of C027 - "cmd register full". As long as the cmd register is full, it's going to loop. Ah from Cortland manual: The Command Full flag is set to 1 when the system writes to the command register; and cleared to when the Keyboard micro reads the command register.

So after you write the cmd, this clears that bit when the uC has read it, a handshake saying "you can send the next cmd byte"? yeah I think so. And this handshake is for every byte, not the whole command. So this is working by accident since we are never setting or clearing the bit, it's just always 0.

In order, it's writing:

07 00 32 00 24

this might be mode =0, config = 32, 0, 24. Yes, 32/00/24 are the defaults I set in ADB_Micro. So this makes sense.

Then next is:

73

This appears to be disable SRQ on device 3 - the mouse. And I'm guess it's not waiting for a response from either of these commands..

Then:

09 51 00

this is READ uC MEM, address 0x0051!! We know what that is.

That is the kSYSPWRUP variable (as named by GuS).

then it's waiting in a big ole loop:

repeat: DEX
   LDA C027
   AND #$20
   BEQ repeat

that's data register full. It's probably the same thing, a handshake per byte. We're never setting this, and it's timing out, so that is why we get the 0911?

Got past the memory read, and it stores it into

now we're doing command

08 51 A5

write uC memory! This writes A5 to indicate not a power-on.

now: 01 - abort

--

I am now dumping the complete ADB Command and Response messages.

BUG FIX

ok, SO: The bits 2-0 being number of valid bytes thing, this ONLY applies to C026 "during interrupt time". I'm guessing, an IRQ is triggered, this value is set. If there is no interrupt then it's normal response.

Debugging

ok this is a routine from the rom03. This reads 1 byte. This seems to indicate KMSTATUS[5] = 1, the "data register full" bit, means a byte has been placed in the data register. It -waits- for KMSTATUS[5] to go high, then does the read. meaning, it expects it to take some time for data bytes to get there.

1474 DB22                       entry RCVDATA
1475 DB22              RCVDATA                                 ;
1476 DB22
1477 DB22                       longa off
1478 DB22                       longi on
1479 DB22
1480 DB22 18                    clc   
1481 DB23 A2 4C 1D              ldx   #7500                    ;TIMEOUT 7500*(2+2+4+2+3)/3 = 30 MS.
1482 DB26                       entry REGOK
1483 DB26 CA           REGOK    dex   
1484 DB27 F0 0B                 beq   FDBTIMEOUT
1485 DB29 AD 27 C0              lda   |KMSTATUS                ;GET DATA REG FULL BIT -> BIT 5
1486 DB2C 29 20                 and   #DATAFULL                ;AND TEST
1487 DB2E F0 F6                 beq   REGOK                    ;CLR IF EMPTY (READ)
1488 DB30              *  ;SET IF FULL (DON'T READ)
1489 DB30              *
1490 DB30 AD 26 C0              lda   |DATAREG
1491 DB33 60                    rts   
1492 DB34              *
1493 DB34                       entry FDBTIMEOUT
1494 DB34 38           FDBTIMEOUT sec   
1495 DB35 60                    rts   

another routine READRSPNS uses that - it reads 1 byte - if it's a status byte [7]=1, it stores it and loops to the top. else, it's a "response byte". clear hi bit, save in status. get the low 3 bits, that tells us how many bytes.