Controlling ROB with Family Basic

Started by UglyJoe, July 16, 2013, 08:43:37 pm

Previous topic - Next topic

UglyJoe

So I wrote a Family Basic program to control ROB.  I'll jump straight to the code and a WAV file and tell my story after.

WAV file: robtest7.wav

Source:
Spoiler

` loads the assembly code for controlling ROB
1 POKE &H7700,&H2C,&H02,&H20,&H10,&HFB,&HAD,&H32,&H00,&H29,&H7F,&H8D,&H00,&H20,&HA0,&H08,&HA2,&H00,&H98,&HE0,&H05,&HD0,&H03,&HAD,&HD0,&H77,&H4A,&HA8,&HA9,&H3F,&H8D,&HD1,&H77,&H90,&H05,&HA9,&H2A,&H8D,&HD1,&H77,&HA9
2 POKE &H7728,&H80,&H2C,&H02,&H20,&HF0,&HF9,&HE0,&H0D,&HF0,&H26,&HAD,&H02,&H20,&HA9,&H3F,&H8D,&H06,&H20,&HA9,&H04,&H8D,&H06,&H20,&HAD,&HD1,&H77,&H8D,&H07,&H20,&HAD,&H02,&H20,&HA9,&H3F,&H8D,&H06,&H20,&HA9,&H04,&H8D
3 POKE &H7750,&H06,&H20,&H8D,&H01,&H20,&HE8,&HD0,&HB9,&HAD,&H33,&H00,&H8D,&H01,&H20,&H2C,&H05,&H20,&HAD,&H9D,&H00,&H8D,&H05,&H20,&HAD,&H69,&H00,&H8D,&H05,&H20,&HAD,&H32,&H00,&H09,&H80,&H8D,&H00,&H20,&H60

` prints out a simple menu
10 PRINT "H=LEFT"
12 PRINT "J=DOWN"
14 PRINT "K=UP"
16 PRINT "L=RIGHT"
17 PRINT
18 PRINT "M=DOWN 2"
20 PRINT "I=UP 2"
21 PRINT
22 PRINT "A=OPEN"
24 PRINT "S=CLOSE"
25 PRINT
26 PRINT "W=BLINK"
28 PRINT "Q=INIT"

` checks for user input
100 S$=INKEY$
120 IF(ASC(S$)=0) GOTO 100
123 RESTORE 3000
125 FOR I=1 TO 10
130 READ X$
140 READ R
150 IF (X$=S$) GOSUB 2000
160 NEXT
200 GOTO 100

` when called, will set the pattern for ROB and call the assembly routine
2000 POKE &H77D0,R
2010 CALL &H7700
2080 RETURN

` pairs of keyboard keys and ROB patterns
3000 DATA H,&H5D,J,&H75,K,&H5F,L,&H57,I,&HDD,M,&HDF,A,&H77,S,&H7D,Q,&HD5,W,&HD7
[close]


Story:

It's late here.  There are probably typos.

In another thread, P had mentioned the idea of controlling ROB with Family Basic.  I thought that was a pretty novel idea.  He told me that he didn't care if I went ahead and tried to do it (since he was going to try and figure it out, as well), so I decided to start tinkering with it in the evenings to see what I could come up with.

I actually already had a good idea of how ROB was controlled prior to this, since I started to write a ROB module for the FCEUX emulator a number of years ago.  I knew that Gyromite used a sequence of 13 black/green screens to send out a command to ROB.  I knew that it accomplished this by replacing the all of the palettes with either black or green during the vblank phase.  What I didn't know were all of the valid sequences -- ones that ROB would actually respond to.  I had kicked around the idea of creating a test ROM that would allow me to try all of the combinations.  Luckily, some one seems to have already done this by now, so I was able to acquire the list of valid commands pretty easily.

So I had the list of commands and I had a technique (thanks to Gyromite) for creating the sequences.  However, I wasn't too happy with Gyromite's technique.  To replace the palettes entirely with black or green would imply that I would need to restore the old palette values after I was done (assuming the player would actually like to be looking at something other than an all black/green screen!).  This would involve tracking down the palette colors currently being used (since Family Basic lets you set the palettes), store them, and then restore them afterwards.  I was trying to use as little memory as possible with this routine, so I wasn't keen on storing all that palette data.  (As it turns out, FB probably already has all of that backed up elsewhere in memory, but I didn't know that at the time).

I spent some time researching the NES PPU at Nesdev's wiki page.  I've been hacking away at ROMs since college so I'm not mystified by 6502 asm or the Famicom/NES hardware architecture (but I'm still very much a novice).  I noticed at the bottom of their PPU palettes article the background palette hack.  It sounded like I could use this hack instead of replacing the palettes.  Best of all, since I could store the color that I needed in normally unused palette locations, I wouldn't have to worry about backing up and restoring the original palette!

So now I was ready to start coding.  Although I've been hacking ROMs for fun for a while now, I've never created a ROM from scratch.  Enter the "Nerdy Nights" NES programming tutorials over on Nintendo Age.  I went through a few of the tutorials and eventually found one of the examples to be suitably hackable as to serve as a base for my ROB routine. 

I hacked away at this for about a week.  I would test my ROM in a couple of different emulators.  Nintendulator ended up being my most used, since it's accurate, has great Family Basic support, and has a debugger.  Once I was satisfied with a version of my routine, I would convert it to a series of POKE commands for Family Basic to use.  I wrote Java utility to handle this for me.  This would then go into Nestopia, since it has a "paste" command for Family Basic and I already knew how to convert its FB tape format into a wav file.  The wav file would go to my phone, me and my phone would go to the basement to my temporary Famicom setup, and I would test it.  And it wouldn't work.  Or it would kind of work, but not really.  Or only certain commands would work.  I'd think about the problem, tweak the code, and the start the process over again.  It took about seven real iterations to get it to where it is now.

There are still some more tweaks I could make to it to bring the size of the routine down a little bit.  I might do that at some point.  As a proof of concept, though, I'm pretty happy with it.  Here is the routine in non-POKE form for anyone who is interested.  (NESASM3  won't compile this on its own, though).

Spoiler

;;;;;;;;;;;;;; 

  .rsset $77D0

robPattern .rs 1  ; the pattern to flash to ROB (after preamble)
robPalette .rs 1  ; the color to flash to ROB next

FAMBASIC_PPUCTRL = $32 ; where Family Basic 2.1 stores its PPU ctrl
                       ; (it will always OR this with #$80 to keep NMI on)
FAMBASIC_PPUMASK = $33 ; where Family Basic 2.1 stores its PPU mask
FAMBASIC_SCROLLX = $9D ; where Family Basic 2.1 stores its X position for PPUSCROLL
FAMBASIC_SCROLLY = $69 ; where Family Basic 2.1 stores its Y position for PPUSCROLL

;;;;;;;;;;;;;;;   

  .org $E700   
vblankwait3:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait3


LoadROBPattern:
  LDA FAMBASIC_PPUCTRL ; load the PPUCTRL used by Family Basic
  AND #$7F     ; AND with %01111111 to disable NMI
  STA $2000    ; disable NMI
  LDY #$08     ; load preamble (%00001000)
  LDX #$00     ; rest step counter

NextROBBit:
  TYA          ; get the preamble/action into the Y register

  CPX #$05     ; is this the beginning of the action byte?
  BNE DoNextROBBit
  LDA robPattern ; beginning of action byte, load it up

DoNextROBBit:
  LSR A       ; push the next ROB bit into the carry flag
  TAY         ; store the preamble/action state into the Y register
  LDA #$3F    ; assume black
  STA robPalette
  BCC FlashROBBit ; if black, go flash it
  LDA #$2A    ; otherwise load green and go flash it
  STA robPalette

;
; Will load a palette byte (robPalette) into unused palette location
; $3F04.  Will then point the VRAM address to $3F04 and disable 
; sprite/bg rendering.  On real hardware and accurate emulators, this
; robPalette value at $3F04 will be used to fill the background.
;
FlashROBBit:
  LDA #$80
  BIT $2002
  BEQ FlashROBBit

  CPX #$0D     ; all bits loaded?
  BEQ DoneFlashing

  LDA $2002    ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006    ; write the high byte of $3F04 address
  LDA #$04
  STA $2006    ; write the low byte of $3F04 address
  LDA robPalette     ;load palette byte
  STA $2007             ;write to PPU
  LDA $2002    ; read PPU status to reset the high/low latch
  LDA #$3F
  STA $2006    ; write the high byte of $3F04 address
  LDA #$04
  STA $2006    ; write the low byte of $3F04 address
  STA $2001    ; disable bg/sprite rendering while VRAM address is pointing
               ; at the robPalette byte

  INX          ; increment the step counter
  BNE NextROBBit ; attempt to go do the next one

DoneFlashing:
  LDA FAMBASIC_PPUMASK ; otherwise, restore the original PPU mask
  STA $2001

  BIT $2005            ; read PPUSTATUS to reset the addres latch
  LDA FAMBASIC_SCROLLX ; load X position
  STA $2005            ; store X position
  LDA FAMBASIC_SCROLLY ; load Y position
  STA $2005            ; store Y position

  LDA FAMBASIC_PPUCTRL ; load the PPUCTRL used by Family Basic
  ORA #$80     ; ORA with %10000000 to enable NMI
  STA $2000    ; enable NMI

  RTS          ; and get out of here
[close]


Omake:

I got side-tracked at one point and wondered if asm was needed at all.  Could this just be done in a for loop?  Here is my attempt at this.  Some of the commands work some of the time.  By stepping through things in an emulator (Nintendulator this time, since it has frame stepping), I was able to determine that the pattern is sometimes interrupted (by either an NMI routine or the BASIC interpreter, I'm guessing) and, since every frame counts, that's enough to make it unreliable  Oddly, Left/Right always seem to function as Close/Open.  That's an interesting problem that I never really found a solution to.  Anyway, here's the code.  I can explain it later if anyone is interested.

Spoiler

10 PRINT "H=LEFT"
12 PRINT "J=DOWN"
14 PRINT "K=UP"
16 PRINT "L=RIGHT"
17 PRINT
18 PRINT "M=DOWN 2"
20 PRINT "I=UP 2"
21 PRINT
22 PRINT "A=OPEN"
24 PRINT "S=CLOSE"
25 PRINT
26 PRINT "W=BLINK"
28 PRINT "Q=INIT"

100 S$=INKEY$
120 IF(ASC(S$)=0) GOTO 100
123 RESTORE 3000
125 FOR I=1 TO 10
130 READ X$
140 READ R
150 IF (X$=S$) GOSUB 2000
160 NEXT
200 GOTO 100

2000 FOR I=1 TO 13
2010 X=R MOD 2
2020 R=R/2
2030 Y=&H3F
2040 IF (X) Y=&H2A
2050 PALETB 0,Y,Y,Y,Y
2060 NEXT
2070 PALETB 0,&HF,&H30,&H21,&H2
2080 RETURN

3000 DATA H,&HBA8,J,&HEA8,K,&HBE8,L,&HAE8,I,&H1BA8,M,&H1BE8,A,&HEE8,S,&HFA8,Q,&H1AA8,W,&H1AE8
[close]

nerdynebraskan

As someone who does not and will never have Family Basic, my question is: will this ever lead to a homebrew ROB game made available on a repro cart? And if so, when can I throw money at you to get one? In any event, it's super cool to see ROB getting some attention almost 30 years later.
Can Nintendo Age Beat Every NES Game in 2015?

http://nintendoage.com/forum/messageview.cfm?catid=31&threadid=140551

P

Great job UglyJoe! ;D It would have taken me much longer to figure this out even though I also went through the "Nerdy Nights" and learned the basics of programming the Famicom in 6502. I tried the code in Nestopia and it seems to work (I can't test it with ROB that way though). When I have some more time to spare I will dig out my Robot and test this on real hardware. :)

Quote from: nerdynebraskan on July 17, 2013, 03:22:09 am
As someone who does not and will never have Family Basic, my question is: will this ever lead to a homebrew ROB game made available on a repro cart? And if so, when can I throw money at you to get one? In any event, it's super cool to see ROB getting some attention almost 30 years later.

Of course! UglyJoe even made a NES ROM to test his code before he implemented it in his Family Basic code. With some modification you can take the subroutine posted here and put it in your ROM. I have a basic idea how to use it but we will see. You can also make your game in Family Basic and then use the program STTONES to convert it to a standalone NES file.

UglyJoe

Quote from: nerdynebraskan on July 17, 2013, 03:22:09 am
will this ever lead to a homebrew ROB game made available on a repro cart?


I have no short-term or long-term plans to make a full-blown game that utilizes ROB, but I guess anything is possible.

nerdynebraskan

Well, I suppose by publishing your code, you may be paving the way for an actual game designer to borrow it to make a game. In that case, I'll thank you again anyway.
Can Nintendo Age Beat Every NES Game in 2015?

http://nintendoage.com/forum/messageview.cfm?catid=31&threadid=140551

FamicomLover

Incredible! I Would also love to see some ROB homebrew!
I am the king of San Andreas

P

I tested this code on my Family Basic, and as I couldn't load the wav I had to type it in manually, ugh. But now I have it on tape at least. It works flawlessly with my Famicom Robot. :)

UglyJoe I noticed that Robot Block and Gyro uses the test command a bit differently, namely it can cause the LED to blink. When you start the test a window in the middle of the screen keeps blinking and you can adjust ROB's head until he looks at the screen. It's easy to tell because ROB's LED start blinking when he sees the window, and turns off if he stops seeing it. When you are done you press select and the screen will blink which will light up the LED permanently. Maybe it's doing this by keep sending the command over and over? I don't know.


Anyway, I made a working ROM that uses UglyJoe's routine that works on a real Famicom (I've tested it on the Everdrive at least). Only problem is that ROB doesn't respond 100% of the time and there's also something weird with the controller reading because my external controller doesn't work properly.

Download is in attachment of this post http://forums.nesdev.com/viewtopic.php?f=10&t=11445&p=131368#p131336 (Famicom World doesn't seem to allow this kind of file as an attachment). This shouldn't be used as a model for how to make proper homebrew, but it can be used if one wants to make a ROB homebrew, or if you just wants to test your ROB. :)

UglyJoe

Quote from: P on July 21, 2014, 05:53:40 am
UglyJoe I noticed that Robot Block and Gyro uses the test command a bit differently ... Maybe it's doing this by keep sending the command over and over? I don't know.


I'm pretty sure the game repeats the test pattern over and over again in order to get ROB to blink the LED.  I don't know what it does to make the LED stay on.  Are you sure the test command isn't just a toggle for the LED (turns it on the first time it reads, then off the second time, then on the third time, etc)?  It's been a while since I tested any of it, so I'm not sure.  I never really tested the "test" command, anyway.

Quote from: P on July 21, 2014, 05:53:40 am
Anyway, I made a working ROM that uses UglyJoe's routine that works on a real Famicom (I've tested it on the Everdrive at least). Only problem is that ROB doesn't respond 100% of the time and there's also something weird with the controller reading because my external controller doesn't work properly.


I'll try and check it out soon.  I can't test the external controller thing since I don't have a FC flashcart, though.  Did you add additional logic to read in the controller input from external controllers (bit 1 on $4016 and bit 1 on $4017)?

P

Quote from: UglyJoe on July 21, 2014, 06:16:33 am
Quote from: P on July 21, 2014, 05:53:40 am
UglyJoe I noticed that Robot Block and Gyro uses the test command a bit differently ... Maybe it's doing this by keep sending the command over and over? I don't know.


I'm pretty sure the game repeats the test pattern over and over again in order to get ROB to blink the LED.  I don't know what it does to make the LED stay on.  Are you sure the test command isn't just a toggle for the LED (turns it on the first time it reads, then off the second time, then on the third time, etc)?
Yes I'm sure it turns on permanently (like when you finished the test in Block/Gyro).

Quote from: UglyJoe on July 21, 2014, 06:16:33 am
Quote from: P on July 21, 2014, 05:53:40 am
Anyway, I made a working ROM that uses UglyJoe's routine that works on a real Famicom (I've tested it on the Everdrive at least). Only problem is that ROB doesn't respond 100% of the time and there's also something weird with the controller reading because my external controller doesn't work properly.


I'll try and check it out soon.  I can't test the external controller thing since I don't have a FC flashcart, though.  Did you add additional logic to read in the controller input from external controllers (bit 1 on $4016 and bit 1 on $4017)?

Yes I did, the problem is much more complex. I'm using pretty much the same logic in my Pong game and the external controller have no problems there. What's new in this program is that I keep the previous frame's button states in a variable and use that to see what is currently pressed (and stores that too in a variable). Then when my program checks for inputs from the user in order to move the cursor it checks the variable that what is currently pressed, and that way the cursor will only move one notch per press even if you hold the button down. But my ext controller acts up and moves the cursor several notches up or down. A and SELECT buttons works like normal but not UP/DOWN buttons.

Maybe my Capcom Power Stick Fighter doesn't like this currently-pressed-check for some reason. UP/DOWN on controller 3 (which is same as ext controller) works in Nestopia.

metalarmor19

This really starts opening doors for homebrewers to start making games using ROB. Now we an use it for something else other than just stack up and gyromite maybe. Great job  ;D

P

OK I made a new ROB test program some time ago. The mystery of the test LED is also solved.
It can be downloaded from here.

Long story short, ROB's firmware has been dumped, deassembled and analysed. And I made this program to do some tests with. The protocol allows for 16 possible commands, so I made a program that can do them all. Test results showed that none of the 6 extra commands do anything however (they are included but unnamed in my test program). I've renamed the command that previously was called "TEST" to "LED" because it turns out it isn't the command used to flash ROB's LED with at the test screen. To do that, you need to repeat a blinking pattern like so: GREEN,BLACK,GREEN,BLACK,GREEN,BLACK... forever, the preamble that goes before each command should not be used for this. This simply keeps ROB's LED blinking as long as his eyes are focused on the TV (so the player can adjust ROB's head). The games just flashes a green test window in TEST mode instead of flashing the whole screen like the commands do, so ROB's eyes must be within that window for the LED to blink. The LED command is used when ending the TEST mode by pressing SELECT (flashed to the whole screen like all other commands). This command turns on his LED forever (or until he is rebooted).
I included a TEST mode that behaves exactly the same as it does in the games. The size, position and even the color (which matters little) of the test window is the same, so it can be used to adjust ROB's head the same way as in the two games.

This program was written entierly from scratch, and since it was not made for Family BASIC it doesn't use UglyJoe's code this time (although it's very similar). I just change the whole palette to green when flashing a command like the two ROB games do instead of using the palette hack. I'm a much better programmer now and it seems to me that it works every time unlike my old test program.

UglyJoe

Quote from: P on March 23, 2020, 02:59:37 amLong story short, ROB's firmware has been dumped, deassembled and analysed.

Somehow I always thought ROB was all discrete logic.  I'll have to check out the commented disassembly sometime.

Quote from: P on March 23, 2020, 02:59:37 amTo do that, you need to repeat a blinking pattern like so: GREEN,BLACK,GREEN,BLACK,GREEN,BLACK... forever

Ah, so that's how it works ;D

P

ROB uses a custom 4-bit microcontroller that Nintendo made themselves. It is the same one used as the lockout chip. Since there are probably just 3 known programs for the architecture, I'm not even sure if all opcodes are figured out, and if nocash got everything right or not.