NES Controller / GameBoy Button Translation

From what I gather that would depend on what you have wired up to what.

If it actually says that then the information is probably wrong.
The Arduino Leonardo board (which uses the same chip as the Arduboy - an ATmega32u4) has PORTF defined.

The Arduino Uno board (which uses an ATmega328P) does not have PORTF defined.

When porting code that references gpio from one target to another that doesn’t have the same pins just swap the instantiations and references to whatever pins you want to use on the new chip. So long as the original software isn’t using specific peripherals that the new chip doesn’t have it’ll be fine (and obviously you may have to rewrite some of the initialization code/transfer anything device specific register-wise).

Thanks for the response. As far as I understand it, Port manipulation allows for faster switching. So would it be feasible just to use a different port on the Uno that is defined, and then change the instantiations from this script to correspond to the pins used in the port I decide to use on the Uno?

I don’t have any experience with port manipulation so that’s what’s throwing me for a loop.

Of course you can use a different port or even a different pin number within any given port. As with all things the datasheet will have all the details you’ll ever need. For direct port manipulation you’ll need to do what is colloquially known as bit-twiddling ("and"ing with a zero to clear a bit (the rest of the port pins are anded with 1 to prevent changing them), "or"ing with a one to set a bit (the rest of the port pins are ored with 0 to prevent changing them)). There are tons of examples of this online. Alternatively you can create bit-masks/defines to make the code more human-readable.

The issue is that I chose PORTF because the code isn’t actually doing comparisons and separate bitwise operations to set some pins up and others down - it just sets the whole port to the current button values, which is fine for the 32u4…

A0 - A3 on the Uno are on PORTC though, and unfortunately so is the RESET pin :confounded:

Thought I found a solution using PORTB, but the crystal pins are on PORTB :grimacing:

Don’t have time to write this for you right now… sorry :frowning_face:

1 Like

Did you had to do anything special to get D-pad working too?

I’m trying to get the code you showed here working on Arduino Nano Every. I’ve got it set up to handle interrupts from DMG and use port manipulation to set the outputs. Everything works perfectly when I test it with some LEDs and buttons, but when I attach it to DMG - only start/select/A/B buttons work, and D-pad does nothing, except for rarely sending one of the other buttons.

I suspect that the issue is with timing, but I don’t have an oscilloscope to check, and Nano Every runs at 16MHz, same as Pro Micro, so it should’ve been just fine.

Do I need some extra electrical components to make it work?

My current code: Makefile · GitHub

And the wiring:

Black connector is where I plug in 5V, GND, and all button pins from the Gameboy. For testing without a Gameboy attached I just connect the LEDs to 5V, so they light up when a corresponding pin is pulled low.

…can you share the code you are using? :upside_down_face:

Sure, sorry :slight_smile: Updated my post above

1 Like

At the time, I was working on the assumption that only P14 or P15 would be low independently of each other - ‘either/or’… but it looks like P14 goes low first, and then P15 goes low as well.

Confirmed this by looking at the example button code (under ‘I/O Ports’) here:

So… maybe instead of using the falling edge(s) - you could try testing for these two conditions to decide which subset of buttons are being sent to the GameBoy at any one time:

  1. (P14 == 0 && P15 == 1)
  2. (P15 == 0)

Looking at that code, I don’t think triggering on the falling edge could cause any issues: I’m not resetting anything on the rising edge. Also, I borrowed an oscilloscope from a friend and can confirm that in the case of my ROM P14 and P15 are pulled down only one at a time:

However with the same oscilloscope I figured out that for the Arduino it takes 10 usec after the scan line goes low to set the buttons… which is enough time for Gameboy to execute about 10 instructions :frowning: On the pic above you can see that AB scan line (yellow) stays low for about 28 usec, while D-pad scan (cyan) - only for about 16 usec.

Given those timings, I think that Arduino is really not switching buttons fast enough.

The pic above also explains why my d-pad buttons would sometimes trigger a button from the other line: d-pad scan is performed first, and if Gameboy happens to be fast enough, AB scan can read the values that Arduino has set in d-pad scan interrupt handler.

As for why a 16MHz Arduino takes whole 10 usec to do its job - I’ve no idea, but a good next step might be to disassemble the compiled firmware to see what instructions its actually executing after an interrupt.

Might save yourself a lot of time and effort buy grabbing a SN54LS257B chip, connect Arduino D-Pad outputs to one set of the chip’s 4 inputs, Arduino A/B/Start/Select button outputs to the other set of the chip’s 4 inputs, and then use P14 or P15 (just one of them) as the chip’s select pin to toggle which set of buttons are sent to the chip’s 4 outputs (and on to the GameBoy)?

1 Like

Thanks, that sounds like a good backup plan, I didn’t realize such ICs already exists :slight_smile: That specific part is has restricted availability on Mouser, but I can probably find another one with similar functionality.

1 Like

Plenty on eBay, including some fascnitating soviet artifacts! :sweat_smile:

Are there any Setuns lying around? :P


Those are quite pricey though, SN74HCS157PWR looks quite similar from a quick glance at the datasheet, and costs only 0.40 EUR apiece.

Looks good - nice find, only comes in surface mount though :anguished:

Yeeeaaahh… Arduino stdlib does quite a lot of stuff before calling my interrupt handler :frowning:

Interrupt dispatch code disasm
000009b2 <__vector_20>:
 9b2:	1f 92       	push	r1
 9b4:	0f 92       	push	r0
 9b6:	0f b6       	in	r0, 0x3f	; 63
 9b8:	0f 92       	push	r0
 9ba:	11 24       	eor	r1, r1
 9bc:	2f 93       	push	r18
 9be:	3f 93       	push	r19
 9c0:	4f 93       	push	r20
 9c2:	5f 93       	push	r21
 9c4:	6f 93       	push	r22
 9c6:	7f 93       	push	r23
 9c8:	8f 93       	push	r24
 9ca:	9f 93       	push	r25
 9cc:	af 93       	push	r26
 9ce:	bf 93       	push	r27
 9d0:	ef 93       	push	r30
 9d2:	ff 93       	push	r31
 9d4:	83 e0       	ldi	r24, 0x03	; 3
 9d6:	0e 94 e5 01 	call	0x3ca	; 0x3ca <_ZL22port_interrupt_handlerh>
 9da:	ff 91       	pop	r31
 9dc:	ef 91       	pop	r30
 9de:	bf 91       	pop	r27
 9e0:	af 91       	pop	r26
 9e2:	9f 91       	pop	r25
 9e4:	8f 91       	pop	r24
 9e6:	7f 91       	pop	r23
 9e8:	6f 91       	pop	r22
 9ea:	5f 91       	pop	r21
 9ec:	4f 91       	pop	r20
 9ee:	3f 91       	pop	r19
 9f0:	2f 91       	pop	r18
 9f2:	0f 90       	pop	r0
 9f4:	0f be       	out	0x3f, r0	; 63
 9f6:	0f 90       	pop	r0
 9f8:	1f 90       	pop	r1
 9fa:	18 95       	reti

000003ca <_ZL22port_interrupt_handlerh>:
 3ca:	ef 92       	push	r14
 3cc:	ff 92       	push	r15
 3ce:	0f 93       	push	r16
 3d0:	1f 93       	push	r17
 3d2:	cf 93       	push	r28
 3d4:	df 93       	push	r29
 3d6:	c8 2f       	mov	r28, r24
 3d8:	d0 e0       	ldi	r29, 0x00	; 0
 3da:	a0 96       	adiw	r28, 0x20	; 32
 3dc:	95 e0       	ldi	r25, 0x05	; 5
 3de:	cc 0f       	add	r28, r28
 3e0:	dd 1f       	adc	r29, r29
 3e2:	9a 95       	dec	r25
 3e4:	e1 f7       	brne	.-8      	; 0x3de <_ZL22port_interrupt_handlerh+0x14>
 3e6:	e9 84       	ldd	r14, Y+9	; 0x09
 3e8:	f8 2e       	mov	r15, r24
 3ea:	ff 0c       	add	r15, r15
 3ec:	ff 0c       	add	r15, r15
 3ee:	ff 0c       	add	r15, r15
 3f0:	01 e0       	ldi	r16, 0x01	; 1
 3f2:	10 e0       	ldi	r17, 0x00	; 0
 3f4:	8e 2d       	mov	r24, r14
 3f6:	80 23       	and	r24, r16
 3f8:	a9 f0       	breq	.+42     	; 0x424 <__LOCK_REGION_LENGTH__+0x24>
 3fa:	2f 2d       	mov	r18, r15
 3fc:	21 0f       	add	r18, r17
 3fe:	30 e0       	ldi	r19, 0x00	; 0
 400:	22 0f       	add	r18, r18
 402:	33 1f       	adc	r19, r19
 404:	f9 01       	movw	r30, r18
 406:	e8 58       	subi	r30, 0x88	; 136
 408:	f7 4d       	sbci	r31, 0xD7	; 215
 40a:	80 81       	ld	r24, Z
 40c:	91 81       	ldd	r25, Z+1	; 0x01
 40e:	89 2b       	or	r24, r25
 410:	49 f0       	breq	.+18     	; 0x424 <__LOCK_REGION_LENGTH__+0x24>
 412:	01 90       	ld	r0, Z+
 414:	f0 81       	ld	r31, Z
 416:	e0 2d       	mov	r30, r0
 418:	28 5e       	subi	r18, 0xE8	; 232
 41a:	37 4d       	sbci	r19, 0xD7	; 215
 41c:	d9 01       	movw	r26, r18
 41e:	8d 91       	ld	r24, X+
 420:	9c 91       	ld	r25, X
 422:	09 95       	icall
 424:	1f 5f       	subi	r17, 0xFF	; 255
 426:	00 0f       	add	r16, r16
 428:	18 30       	cpi	r17, 0x08	; 8
 42a:	21 f7       	brne	.-56     	; 0x3f4 <_ZL22port_interrupt_handlerh+0x2a>
 42c:	e9 86       	std	Y+9, r14	; 0x09
 42e:	df 91       	pop	r29
 430:	cf 91       	pop	r28
 432:	1f 91       	pop	r17
 434:	0f 91       	pop	r16
 436:	ff 90       	pop	r15
 438:	ef 90       	pop	r14
 43a:	08 95       	ret

I’ve managed to override the ISR for the whole port - that brought down the reaction time to about 2.4 usec, and that’s fast enough for the Gameboy to detect the buttons :slight_smile:

Here’s the code: Makefile · GitHub
One caveat is that it requires commenting out the ISR for PORTD in the Arduino core code - I haven’t figured a way to override it without modifying the core, messing with linker flags didn’t work out :frowning:

Updated the code to not require core modification: added a tiny linker script that overwrites the ISR symbol in the vector jump table.

1 Like

It’s been awhile since I’ve done assembly but looks like it’s saving context of the registers to the stack. So long as the interrupt doesn’t … well interrupt during non-atomic calculations there shouldn’t be a problem doing away with that bit (though to be safe any important calculations should have interrupts disabled right before and then re-enabled right after).

Yeah, that register saving seems totally pointless, since ISR doesn’t use any of them, and the port_interrupt_handler function does the same for the registers it uses. However I don’t know how to tell gcc to not generate all those push/pop instruction.