Undocumented Family BASIC keyboard I/O behaviour

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
User avatar
Vectrex2809
Posts: 102
Joined: Mon Jul 14, 2014 6:05 am
Location: Tokyo, Japan

Undocumented Family BASIC keyboard I/O behaviour

Post by Vectrex2809 »

I already posted about it in the Discord, but I might as well post it here as I believe it warrants a spot in the Wiki.

I was implementing Family BASIC keyboard functionalities in my Wordle homebrew project, when i noticed that the keyboard behaved differently on real hardware (Tested on black 505 Twin) than it did on MESEN.

After reverse-engineering the Family BASIC ROM, it turned out that the reason I was getting ghost presses is that when you write a value to $4016 on the expansion port, you need to burn some cycles in order not to get ghost presses when reading from $4017 later on. Family BASIC seems like it burns about 50 cycles between a $4016 write and a $4017 read, though the actual amount might be lower than that.

Fiskbit and Jekuthiel also helped me test the game out on real hardware and I can confirm this is not a fluke, rather an actual quirk of the keyboard. Anyone else try working with the keyboard/faced this issue?
lancuster
Posts: 235
Joined: Thu Feb 18, 2016 3:20 am
Contact:

Re: Undocumented Family BASIC keyboard I/O behaviour

Post by lancuster »

I haven't encountered such a problem, but I found another problem. On some emulators in Family BASIC, the keys responsible for special characters, such as ":", don't work.
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Undocumented Family BASIC keyboard I/O behaviour

Post by Pokun »

Yeah you need some delays, but it seems it's not mentioned on the wiki. It is mentioned on Enri's page (in Japanese) linked at the bottom in the wiki.

The process is basically like this:
1) Write $05 to $4016 (reset to row 0, column 0), then use 6 NOPs as a delay.
2) Write $04 to $4016 (select column 0, from 2nd lap this also selects the next row), wait to give time to scan all keys.
3) Read column 0 data from $4017. Shift right once to get key states in the low nibble, then AND with $0F to clear high nibble and save the value in RAM.
4) Write $06 to $4016 (select column 1), wait to give time to scan all keys.
5) Read column 1 data from $4017. Rotate left 3 times to get key states in the high nibble, then AND with $F0 to clear low nibble and save the value in RAM using ORA to merge the nibble with the column 0 data. Finally invert all bits (using EOR with $FF) if you want and store it back to RAM.
6) Repeat steps 2-5 eight more times to read both columns of all 9 rows.

This is how the code may look like, it's the same method as used in Family BASIC:

Code: Select all

@read:
;Reads the keyboard matrix state.
  lda #$05
  sta $4016          ;reset keyboard
  nop
  nop
  nop
  nop
  nop
  nop                ;wait for keyboard to get ready
  
;Read all 9 rows in column 0 and 1:
  ldx #$00           ;loop counter
@loop_r:

;Read column 0 keys:
  lda #$04           ;select colum 0 (from second lap: also select next row)
  sta $4016
  ldy #$0A           ;loop counter
@wait1:
  dey
  bne @wait1
  nop
  nop                ;wait to give time to scan all keys
  lda $4017          ;read key state of selected row and column
  lsr a              ;right shift to get key state into low nibble
  and #$0F           ;clear high nibble
  sta key_state+0,x  ;save column 0 key states in RAM

;Read column 1 keys:
  lda #$06           ;select colum 1
  sta $4016
  ldy #$0A           ;loop counter
@wait2:
  dey
  bne @wait2
  nop
  nop                ;wait to give time to scan all keys
  lda $4017          ;read key status of selected row and column
  rol a
  rol a
  rol a              ;rotate left to get key status to high nibble
  and #$F0           ;clear low nibble
  ora key_state+0,x  ;merge A with column 0 key status
  eor #$FF           ;invert key states so that 0=unpressed
  ldy #$08           ;loop counter for 8 bits
@store:
  asl a              ;shift bit 7 left into carry
  ror key_state+0,x  ;store key state bit to RAM from carry
  dey
  bne @store         ;loop for storing all 8 bits in RAM
  
  inx
  cpx #$09
  bne @loop_r        ;loop for all 9 rows
  
  rts


Lancuster, I suppose you talk about VirtualNES or Nestopia? I remember some of those older emulators missing one of the keys, and having no way to remap the keyboard either. FCEux might have some similar problem, I forgot. Mesen should support all keys and allow remapping them thankfully, but it's missing the paste feature of Nestopia.
Post Reply