Tuesday, May 26, 2015

Generating tiles for Google Maps API

I use Google Maps API to render the maps on my Zelda Parallel Worlds walkthrough, and as a result I needed to generate the necessary tiles for the Maps API to use.  My source image was a 4,096x4,096 image, and I needed to generate 256x256 tiles at various zoom levels, starting at fully zoomed out, where the entire map was contained in a single tile, up to however large I could reasonable render (which ended up being a whopping 16,384x16,384).  GIMP's script-fu functionality was perfect for the task, but I couldn't find a script that quite did what I wanted, including scaling the map to the various zoom levels, so I made my own.  I used the tiles-to-files plugin as my starting point and went from there.  The end result gives the following options


Tile size is adjustable (though I've only tested powers of 2, such as 64, 128, and 256)
Max zoom level determines how many zoom levels should be generated.  The lowest zoom level is 0, which is a single tile in size.

Output file type is selectable between PNG and JPEG

Interpolation mode can be chosen separately for shrinking and growing.  In my case, I didn't want any interpolation when growing, since I was growing a pixel-perfect image by a factor of 2 each zoom level, so I wanted to retain the pixel-perfect aspect and just create "big pixels".

Here's the script:

; Google Maps Tiles, V1.0
;
; Based on the tiles-to-files script by theilr
; http://registry.gimp.org/node/20868
;
; http://www.qwertymodo.com
;
(define (script-fu-google-maps-tiles inImage inDrawable inTileSize inMaxZoom
                                     inFileType inShrinkMethod inGrowMethod outDir)
  (gimp-image-undo-group-start inImage)
  (let* (
          (fullWidth  (car (gimp-image-width  inImage)))
          (fullHeight (car (gimp-image-height inImage)))
          (tileWidth  inTileSize)
          (tileHeight inTileSize)
          (zoomWidth  tileWidth)
          (zoomHeight tileHeight)
          (newImage (car (gimp-image-new tileWidth tileHeight RGB)))
          (tmpImage)
          (tmpLayer)
          (selLayer)
          (outfname)
          (hcnt 0)
          (vcnt 0)
          (zcnt 0)
    )
   
    (set! zcnt 0)
    (while (<= zcnt inMaxZoom)
      (set! zoomWidth (* tileWidth (expt 2 zcnt)))
      (set! zoomHeight (* tileHeight (expt 2 zcnt)))
     
      (gimp-rect-select inImage
                        0
                        0
                        fullWidth
                        fullHeight
                        CHANNEL-OP-ADD FALSE 0)
                       
      (gimp-edit-copy-visible inImage)
      (gimp-selection-none inImage)
     
      (set! tmpImage
        (car (gimp-image-new zoomWidth zoomHeight RGB)))
         
      (set! tmpLayer
        (car (gimp-layer-new tmpImage fullWidth fullHeight
                     RGB-IMAGE "Background" 100
                     NORMAL-MODE)))                    
      (gimp-image-add-layer tmpImage tmpLayer -1)
      (set! selLayer
        (car (gimp-edit-paste tmpLayer FALSE)))
      (gimp-floating-sel-anchor selLayer)
     
      (if (< zoomWidth fullWidth)
        (begin
          (gimp-context-set-interpolation inShrinkMethod)
          (gimp-layer-scale tmpLayer zoomWidth zoomHeight FALSE)
          (gimp-image-resize-to-layers tmpImage)
        )
      )
     
      (if (> zoomWidth fullWidth)
        (begin
          (gimp-context-set-interpolation inGrowMethod)
          (gimp-layer-scale tmpLayer zoomWidth zoomHeight FALSE)
          (gimp-image-resize-to-layers tmpImage)
        )
      )
   
      (set! hcnt 0)
      (while (< (* hcnt tileWidth) zoomWidth)
        (set! vcnt 0)
        (while (< (* vcnt tileHeight) zoomHeight)
          (gimp-rect-select tmpImage
                            (* tileWidth hcnt)
                            (* tileHeight vcnt)
                            tileWidth
                            tileHeight
                            CHANNEL-OP-ADD FALSE 0)
          (gimp-edit-copy-visible tmpImage)
          (gimp-selection-none tmpImage)
         
          (set! tmpLayer
            (car (gimp-layer-new newImage tileWidth tileHeight
                         RGB-IMAGE "Background" 100
                         NORMAL-MODE)))
          (gimp-image-add-layer newImage tmpLayer -1)
          (set! selLayer
            (car (gimp-edit-paste tmpLayer FALSE)))
          (gimp-floating-sel-anchor selLayer)
         
          (if (= inFileType 0)
            (begin
              (set! outfname (string-append outDir
                                            "/"
                                            (number->string zcnt)
                                            "-"
                                            (number->string hcnt)
                                            "-"
                                            (number->string vcnt)
                                            ".png"))
         
              (file-png-save  RUN-NONINTERACTIVE
                              newImage
                              tmpLayer
                              outfname
                              outfname
                              0 9 1 0 0 1 1 )
              )
            )
         
          (if (= inFileType 1)
            (begin
              (set! outfname (string-append outDir
                                            "/"
                                            (number->string zcnt)
                                            "-"
                                            (number->string hcnt)
                                            "-"
                                            (number->string vcnt)
                                            ".jpg"))
         
              (file-jpeg-save RUN-NONINTERACTIVE
                              newImage
                              tmpLayer
                              outfname
                              outfname
                              0.95 ; JPEG compression level
                              0    ; Smoothing
                              1    ; Optimize
                              1    ; Progressive
                              ""   ; Comment
                              0    ; Subsampling (0-4)
                              1    ; Baseline
                              0    ; Restart
                              0    ; DCT
                              )
              )
            )
     
          (set! vcnt (+ vcnt 1))
          )
        (set! hcnt (+ hcnt 1))
        )
       
      (gimp-image-delete tmpImage)
     
      (set! zcnt (+ zcnt 1))
      )
     
    (gimp-image-delete newImage)
    (gimp-image-undo-group-end inImage)
    (gimp-displays-flush)
  )
)
(script-fu-register
  "script-fu-google-maps-tiles"            ; function name
  "<Image>/Filters/Tiles/_Google Maps"     ; menu label
  "Split an image into tiles suitable\     ; description
   for use with Google Maps API"
  "qwertymodo"                             ; author
  "(c) 2015, qwertymodo"                   ; copyright notice
  "25 May 2015"                            ; date created
  "RGB*"                                   ; image type
  SF-IMAGE      "Image"   0
  SF-DRAWABLE   "Drawable" 0
  SF-ADJUSTMENT "Tile Size (px)"           '(128 8 1024 1 8 0 SF-SPINNER)
  SF-ADJUSTMENT "Max Zoom Level"           '(4 0 10 1 2 0 SF-SPINNER)
  SF-OPTION     "Output File Type"         '("png" "jpg")
  SF-ENUM       "Interpolation (Shrink)"   '("InterpolationType" "cubic")
  SF-ENUM       "Interpolation (Grow)"     '("InterpolationType" "cubic")
  SF-DIRNAME    "Output Folder"            "tiles"
)

Tuesday, August 27, 2013

Roll-Your-Own EEPROM Burner

It's been awhile since I've written anything, mostly because I've been busy actually making stuff, but I figured I'd take some time out to do some writing instead.  Something I've been working with lately is building SNES reproduction carts.  I see a lot of people buying and using Willem-based programmers to burn their EEPROMs, and from what I can tell, they're way more trouble than they're worth, and more expensive too. I chose to go a different route and build my own programmer.  EEPROM programming is a pretty straightforward microcontroller exercise.  The main trouble is finding a microcontroller with enough I/O pins. Personally, I used a Teensy++ 2.0 development board.  One of the most common chips used for SNES reproduction is the AM29F032B, used with a TSOP-to-DIP36 breakout board, usually the one made and sold by BuyICNow.com.  So, for lack of anything better to do, I'm going to explain how to go about creating a programmer for the AM29F032B, though much of the information can be adapted to any EEPROM.

First of all, you'll need to build yourself an adapter in order to connect the microcontroller to the ROM.  For the sake of cleaner code, logically contiguous pins on the ROM, such as A0-A22, D0-D7, should be connected to logically contiguous pins on the microcontroller (an 8-bit microcontroller will only have 8-bit ports, so a good compromise is to utilize full ports as much as possible, e.g. A0->PORTX0, A1->PORTX1 . . . A7->PORTX7, A8->PORTY0, A9->PORTY1, etc.). For my Teensy++ adapter, it looked like this:

(I'm trying to figure out how to create a custom part in Fritzing so I can post a nice image representation of this circuit, but for now you'll have to live with a pin table)

ROM    Teensy
Vcc    Vcc
Gnd    Gnd
A0-7   D0-7
A8-15  C0-7
A16-22 F0-6
D0-7   B0-7
/CE    E7
/OE    E6
/WE    E1

Ok, so now that we have everything wired up (or, in my case, I created a socketed PCB), we can start writing code. The most basic functions are reading and writing a single byte. The function prototypes will look something like this.


uint8_t ReadByte(uint32_t address);
void WriteByte(uint32_t address, uint8_t value);


In order to understand how to implement these functions, we first need to look at the function waveforms in the datasheet.  Here's the read function:


We can ignore the actual timings right now, all we really care about is the sequence.  From the diagram, we can see that the sequence goes like this:

Set CE# high, OE# high, and WE# high (in any order, or simultaneously)
Set up the address
Set CE# low
Set OE# low
Wait for a short time
Read the data
Set CE# high and OE# high (in any order, or simultaneously)


In code, it looks like this:


uint8_t ReadByte(uint32_t address)
{
  // Set data line as input, pulled high
  DATA_DDR   = 0x00;
  DATA_PORT  = 0xFF;
 
  // Pull all control lines high
  CS_PORT |= CS_BIT;
  OE_PORT |= OE_BIT;
  WE_PORT |= WE_BIT;
 
  // Set up address
  ADDR_PORT_0 = address & 0xFF;
  ADDR_PORT_1 = (address >> 8) & 0xFF;
  ADDR_PORT_2 = ((address >> 16) & 0x7F);
 
  // Pull CS low, then OE low
  CS_PORT &= ~CS_BIT;
  OE_PORT &= ~OE_BIT;

  delayMicroseconds(1);
 
  // Read data
  uint8_t data = DATA_PIN;
 
  // Pull all control lines high
  OE_PORT |= OE_BIT;
  CS_PORT |= CS_BIT;
 
  return data;
}


As you can see, I've #defined a few values here to make the code cleaner. That's all done based on the pinout specified above.  For instance:


#define DATA_PORT    PORTB
#define DATA_PIN     PINB
#define DATA_DDR     DDRB

#define CS_PORT      PORTE
#define CS_DDR       DDRE
#define CS_BIT       (1<<7)


...and so on.  Writing is almost identical, though you wouldn't think it, looking at the waveform in the datasheet.


The reason that this looks so complicated is that Flash ROMs actually require you to write several command bytes for every byte of data you actually want to program.  However, we want to first write the code to write a single byte, then it's easy to write multiple bytes in a row by making consecutive calls to that function. To make it easier, the sequence is:

Set CE# high, OE# high, and WE# high (in any order, or simultaneously)
Set up the address
Set CE# low
Set WE# low
Set up the data
Wait for a short time
Set CE# high and WE# high (in any order, or simultaneously)

The write operation actually occurs when CE# or WE# is pulled high, which latches the data lines and then performs the write.

No code this time, it should be trivial.  Copy and paste the read function and make the necessary changes.

Next, we want to be able to program information to the chip.  As mentioned before, this is done by writing several command bytes, followed by the actual data byte.  This varies from chip to chip, but for the AM29F032B, the sequence is:


Addr   Data
0x555  0xAA
0x2AA  0x55
0x555  0xA0
addr   data


where the final "addr" and "data" are the actual address and data that you want to program on the chip.  All you have to do is call your WriteByte 4 times in a row with those addresses and data values.

Now, the final function we need to write is to erase the chip.  EEPROMs, including Flash ROMs, must be erased before they can be written to.  This is because the program function can only change a 1 to a 0, it can't change a 0 to a 1.  Because of that, in order to change a 0 to a 1, you have to change EVERYTHING to 1's by erasing the chip, then you can go back and program the 0's.  It's just how it is.  Anyway, erasing a Flash ROM is achieved by a command sequence.  Again, this varies from chip to chip, but for the AM29F032B, the sequence is:


Addr   Data
0x555  0xAA
0x2AA  0x55
0x555  0x80
0x555  0xAA
0x2AA  0x55
0x555  0x10

One last thing is that we need to know when the erase function has completed. There are several ways to do so, as described in the data sheet. The lazy way to do it is to continuously read any address (I usually pick 0x000) until the data returned is 0xFF. The reason for this is that during an erase procedure, the result of any read, instead of being the data at that address, is actually a status register. The status register will never equal 0xFF, but once the chip is erased, the whole chip will be all 1's, so any read should return 0xFF. Like I said, it's the lazy way to do it.

So now, we have 4 functions that pretty much handle everything that we need in order to burn code to our Flash ROM:


uint8_t ReadByte(uint32_t address);
void WriteByte(uint32_t address, uint8_t value);
void ProgramByte(uint32_t address, uint8_t value);
void EraseChip();

Now, you have to figure out how to actually transfer data between the PC and the microcontroller. I use RealTerm, because it has the ability to transmit binary files over a serial connection. I then set up my Teensy++ main loop with a simple serial interface that resembles a command-line application, with various commands and flags, then I use RealTerm's send function for programming, and its capture function for reading. Once I've programmed the ROM, I read it back to a file, and compare the file against the original ROM file to make sure that they match (be sure you've padded your ROM file or else trim the file you read back to match the original file size or you may get an incorrect mis-match). Because I'm using the Teensy++'s CDC virtual-serial-port-over-USB interface, it would be entirely possible to write a full PC-side host application tailor-made for this device, but there really isn't much point, seeing as all it would be doing is sending a file to a serial port, or capturing data from that serial port. Better to just use an existing application, if it fits our needs, and RealTerm does just that.

[Minor Edit]
RealTerm apparently does a really terrible job of packet utilization for USB-CDC virtual serial devices, an issue which I've submitted to their bug tracker, though it hasn't generated any response, so it's unlikely to be fixed.  For that reason, I've switched to Tera Term, as it speeds up write speeds by a factor of about 20-30, which makes the difference between taking 45 minutes to burn a chip with RealTerm vs about 90 seconds with Tera Term.  This doesn't change the fact that I'm still using the same code on the microcontroller side.

Anyway, I'll probably throw up some more pictures at some point, but for the most part, I wanted to describe the process, rather than just handing out schematics and code.  This is a relatively simple feat to accomplish, and from what I've seen of a lot of the SNES reproduction makers, I feel it should be something of a rite of passage.  If you want to just go out and buy yourself a Willem, go ahead.  But be warned that nobody really wants to help repro makers with their crappy Willems.  Be a man, roll your own burner.

Here's mine:

An original SNES MaskROM, used for my initial read-only testing

The double-sided PCB design cut down on PCB size, and as a result, cost

Friday, June 21, 2013

Year in review

Well, school really caught up with me, and although I've had a few pretty neat projects over the last several months, I haven't had the time to post any of them here.  For one, I finally got around to trying reflow soldering, and created a second revision of my SNES Wii Classic Controller mod which turned out really nicely.  I'm actually working on a third revision now, which may not actually be possible, but if I can manage to get it working, it'll be REALLY nice, so fingers crossed there.


Another project I put a lot of time into was the Altera EMP7064S development board I built for my CST231 class.  A large part of this class was devoted to building a wire-wrap board around the Altera CPLD for the purposes of simultaneously building the board and learning to program it in Verilog.  I chose to put my PCB manufacturing experience to good use and go ahead and build a PCB version of the wire-wrap board we built in class.  Here's the board we built in class (the wiring wasn't *quite* complete in the second photo, but it gives a pretty good indication of how much wire wrapping was involved)




And here's the PCB that I made from the same schematic:



As you can see, I made good use of surface-mount parts for the LED current-limiting resistors, as well as the traffic light LEDs.  I also added a DC power jack and a 7805 regulator so I can just hook up a power plug without the need for a bench power supply.  I also made a nice little clock generator from a dual 555-timer IC that plugs into the 4-pin header directly above the main chip socket, so I don't need a function generator for most stuff either.  I'm pretty happy with this board.  My professor really liked it too, and he bought one of the extra boards from me.


In other news, I'm also still working on the Zelda: Parallel Worlds mapping/walkthrough site.  I finally managed to build my first SNES reproduction cart, of ZPW, of course (I built one of Metroid: Super Zero Mission as well...), and have managed to play through the entire game on the actual console hardware, so yay :)  The one major change coming to the website is that I hope to replace WorldKit with Google Maps API.  I currently have a test page up and running and properly displaying the overworld map, but I'm having a few issues, most of which probably stem from my lack of understanding of the Projection class.  I also have yet to try messing around with markers to see if they'll work for my purposes, but I suspect they'll do fine.  So, be watching for that to roll out site-wide soon (I hope...).


I have a bunch of other small projects I've been working on that aren't really in any shape for a write-up yet, lots of fun with the Super Nintendo and other stuff, but I'll get around to them eventually...

Tuesday, January 8, 2013

Super Nintendo Classic Controller for Wii

Awhile back, Nintendo released an awesome Wii controller as a Club Nintendo Japan exclusive, the Wii Super Famicom Classic Controller.


This thing is beautiful.  The only problem is, not only is it only available through Club Nintendo, it's a Japanese exclusive.  It's been out long enough to have found its way onto Ebay and other resellers, but it typically goes for about $100.  Not cool, Nintendo.  Not cool.  There are 3rd party versions of this controller, but like the cheap Ebay knockoff SNES controllers, they're crap.  So what's a guy to do?  Let's build one!

So, obviously we have to start with an official SNES controller (or, just because I want to, a Super Famicom controller).  I found this project implementing a classic controller adapter in a cheap AVR microcontroller and figured I could improve the hardware design.  The existing design was incredibly simple, and the AVR came in a surface-mount package, so I figured I could shrink the board sufficiently to fit it inside the controller itself.  Actually, there's a ton of room inside the controller, so I could have even fit the DIP version inside if I'd wanted to.  But I wanted to do better.  So I did.  The PCB did shrink down nicely, and here's the result:

With a minimum of external parts, the final
PCB shrunk down quite nicely
The board fits perfectly over the pin header from the original cable

The placement of the PCB avoids all of the spacer posts, meaning no
cutting or other modification to the controller shell is necessary
Voila!


You can find more info, including source and PCB design files, at the project page on my site.

Wednesday, November 7, 2012

Arduino HID Gamepad, Part 2

Well, I'm finally getting around to moving forward with my Arduino HID Gamepad project (part 1 here).  I finished the wiring on the gamepad shield, and started trying to get the gamepad device report descriptor and event handlers added to the Arduino libraries.  First of all, you need to add the device report descriptor to HID.cpp, located in hardware\arduino\cores\arduino.  Sorry, I can't explain this very well, I barely understand it myself, but this page helped me stumble through.  Add the following to const u8 _hidReportDescriptor[] (near the top of the .cpp file), above the #if RAWHID_ENABLED directive:


    // Gamepad
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x85, 0x03,                    //     REPORT_ID (3)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x08,                    //     USAGE_MAXIMUM (Button 8)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x04,                    //     REPORT_COUNT (4)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0xff,                    //     LOGICAL_MINIMUM (-1)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x75, 0x02,                    //     REPORT_SIZE (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION


This defines a 2-axis, 4-button gamepad.  Next, you need to add the Gamepad class and event handlers to USBAPI.h:


class Gamepad_
{
private:
uint8_t _buttons;
public:
Gamepad_(void);
void begin(void);
void end(void);
void press(uint8_t b);
void release(uint8_t b);
bool isPressed(uint8_t b);
};
extern Gamepad_ Gamepad;


Now you need to put the class definitions in HID.cpp:


//================================================================================
//================================================================================
// Gamepad

Gamepad_::Gamepad_(void) : _buttons(0)
{
}

void Gamepad_::begin(void)
{
}

void Gamepad_::end(void)
{
}

void Gamepad_::press(uint8_t b)
{
_buttons |= b;
HID_SendReport(3,&_buttons,1);
}

void Gamepad_::release(uint8_t b)
{
_buttons &= ~b;
HID_SendReport(3,&_buttons,1);
}

bool Gamepad_::isPressed(uint8_t b)
{
if ((b & _buttons) > 0) 
return true;
return false;
}


Finally, you need to instantiate the Gamepad singleton.  At the top of HID.cpp, add the following line under the Mouse and Keyboard instantiations:

Gamepad_ Gamepad;

Now, you should be able to call Gamepad.begin() in your sketch setup() function, and then call Gamepad.press() and Gamepad.release() to send button press and release reports.  As you can see, Windows now recognizes my new 2-axis, 4-button gamepad :)





Tuesday, October 23, 2012

I have a domain :)

I just finished registering my very own domain :)  This blog can now be found at blog.qwertymodo.com, and my Zelda: Parallel Worlds mapping site can be found at zelda.qwertymodo.com.

Tuesday, October 16, 2012

Non-volatile N64 Controller Pak

Ever since I read this post on the BenHeck forums about modding an N64 Controller Pak to eliminate the need for a battery to maintain the save data.  I decided to take it one step further and build one from scratch.  The first step was to draw up the schematic for the existing Controller Pak, and modify it from there.  For that, I needed a pinout for the card edge.  I found one with a few errors, used it to fill out the schematic, and determined the function of the remaining contacts from there.  Here's the working pinout:

  Pin   Name
--------------
  1     GND
  2     A14
  3     A12
  4     A7
  5     A6
  6     A5
  7     A4
  8     A3
  9     A2
  10    A1
  11    A0
  12    D0
  13    D1
  14    Detect*
  15    3V3
  16    D2

  17    GND
  18    CE1
  19    /CE2
  20    /WE
  21    A13
  22    A8
  23    A9
  24    A11
  25    /OE
  26    A10
  27    D7
  28    D6
  29    D5
  30    D4
  31    3V3
  32    D3

The Detect line is pulled low inside the controller through a pull-down resistor.  The Controller Pak connects Detect to 3V3 to indicate the presence of a cart in the slot (this is true for both the Controller Pak and the Rumble Pak;  the controller determines which type of cart it is by strobing a specific address and reading the response).

From this, it's fairly trivial to connect an FRAM chip in place of the original SRAM.  I chose to use the Ramtron FM28V020, though the any of the FM18*08 chips should work as well.  If we don't really care about the dual CE lines, we can ignore CE1 and connect /CE2 directly to the RAM.  After that, all that is needed is a pull-up resistor on the /CE line (10Kohm is a good value) and a smoothing cap between 3V3 and GND (100nF is good).

I'll be working on a 4x version soon, using a 1Mbit FRAM chip (Ramtron FM28V100).  The basic wiring for this is the same as above, but add pull-up resistors on the A15 and A16 lines, then connect those lines to a 4-position switch that connects to GND.  Use diodes to isolate the connections between the address lines and the switch, since one position on the switch will pull both lines to GND, but you don't want *every* position to do that.

If you'd like to buy a pre-assembled, nonvolatile N64 Controller Pak (1x or 4x), shoot me an email.  I have a lot of extras sitting around...