A fun hack: iPad to FPGA data-passing
Nothing ground-breaking today, but it is a fun little hack that I've been using lately.
One of my personal hobbies is developing random stuff on FPGAs. I find hardware development (as much as FPGA is that) a fun diversion from game development. I'm a strong believer that a huge number of software issues (especially related to concurrency) are well-solved problems in the hardware space and a huge variety of common hardware solutions can be applied to software. Not to mention that a better understanding of what software actually, ultimately does informs good software and data design choices in general.
But a while back I realized the same was true in reverse. While developing some more useless gadgetry, I noticed that I was constantly rebuilding and reloading the ROM when I was just updating a few tweak variables. It occurred to me (obvious in retrospect) that this is exactly the same problem we face every day in game engine development. So why can't I solve it the same way? But then I didn't really do anything about it for a long time.
Last year though, something changed. I was sitting on my couch with my iPad, playing with iCircuit (which is an awesome app, BTW - you should totally check it out) and I thought, "It would be really awesome if I could just update some of my variables while I'm lazing around here on the couch instead of walking way over there to my desk." [Note: One can not over-value general laziness as a motivating factor. My desk was literally 5 feet from the couch.] Plus, you know, it would just be cool.
My first thought was that I could write a custom communication app for the iPad and add some kind of wifi module to the FPGA and get something like this:
But then I realized something:
- Apple makes it obnoxiously difficult to write an app "just for fun" on one of their devices. The hoops you need to jump through with certificates, etc. just to run something on your own device are ridiculous. I've written one complete iOS app previously and really, I don't feel compelled to do it again.
Briefly I considered picking up one of those knock-off Android tablets (this was before the Galaxy came out) for like $150 bucks. That momentarily seemed like a good idea, but then I realized something else:
- More than 99% of the time, my FPGAs are directly hooked up to my PC when I'm working on them. Which is obvious, since I'm always pushing updates to the device.
So, I decided to go down a simpler path. (Well, technically way more complicated, but simpler to implement anyway.)
Which basically exactly mirrors the kind of practice we use in engine development for live editing data from the game tools. So it's a pretty familiar setup. Here's the plan:
- On the PC, create an HTTP server which provides a REST(-like) interface to talk the FPGA over USB and can specify values to update. (Registers, memory or whatever you like.)
- Create a web client (i.e. web page) that talks to the local server through the REST interface and can adjust variables as desired.
- Launch web browser on iPad, load up web page on the local server and make adjustments.
The first thing I needed was for the PC to be able to speak to the FPGA over USB. This was a non-problem. In this case, I'm using a Digilent Nexys2 board and Digilent already provides an API to piggy-back on their USB interface - Adept SDK 2.
Next, I needed to set up an HTTP server and link to the USB communication functions. For a server, I chose to go with mongoose. For three reasons:
- It's very simple. Only a single file to build.
- It's totally hackable. I can do pretty much whatever I want and I don't need to figure out a bunch of complex configuration to do something very simple.
- I was already familiar with it.
I wrote I little project that linked in mongoose, linked in the Adept SDK library, and provided a couple of example REST methods that I could use to test. Here are the functions that I set up:
mg_set_uri_callback( s_MongooseContext, "/SetDeviceRegister", &VGATestServer::HttpSetDeviceRegister, NULL); mg_set_uri_callback( s_MongooseContext, "/GetDeviceRegister", &VGATestServer::HttpGetDeviceRegister, NULL); mg_set_uri_callback( s_MongooseContext, "/Ping", &VGATestServer::HttpPing, NULL); mg_set_uri_callback( s_MongooseContext, "/Shutdown", &VGATestServer::HttpShutdown, NULL); mg_set_uri_callback( s_MongooseContext, "/Bin/*", &VGATestServer::HttpBin, NULL); mg_set_uri_callback( s_MongooseContext, "/EnumDevices", &VGATestServer::HttpEnumDevices, NULL); mg_set_uri_callback( s_MongooseContext, "/OpenDevice", &VGATestServer::HttpOpenDevice, NULL); mg_set_uri_callback( s_MongooseContext, "/CloseDevice", &VGATestServer::HttpCloseDevice, NULL); mg_set_uri_callback( s_MongooseContext, "/GetActiveDevice", &VGATestServer::HttpGetActiveDevice, NULL);
A few things to note:
- It's not truly REST since I have to manage a certain amount of state with multiple devices connected. But who cares?
- There's a Ping and a Shutdown method. Both I find invaluable for debugging purposes. To be able to test that the server is still running from the client page, and to shut it down remotely.
- The SetDeviceRegister and GetDeviceRegister map directly to a register file on the FPGA in a straightforward way. Registers are referenced by index. It's up to the device to determine what they mean.
Other things I'm using here:
- jQuery - Initially I had a couple of headaches between Sencha Touch and jQuery. But overall it wasn't too bad since I wasn't trying to do anything spectacular here.
- jGrowl - A Growl-like plugin for jQuery. Super-handy for debugging and notifications.
- The control widget there (the boxes represent bits) is using HTML canvas. Something else I wanted to mess around with while I was in there anyway.
- And a really simple jQuery color picker by Lakshan Perera
One cool thing about Safari on the iPad is that I can add any webpage to the Home screen:
...and have it appear like a native app:
...and when viewed this way, the address bar is removed and it does look pretty native. At least enough for my purposes. Note: This is the reason I added the "Refresh" button on the bottom. I wanted to be able to run the app, but without the address bar there was no (obvious?) way to refresh the page.
As an added bonus, since the clients really just reflect the values that are stored on the device (and synchronize to any device changes) that means all clients stay in-sync in real-time. (Well, pretty quick, anyway.) So if I'm also running the web page on the PC, it will show the identical settings and if I change those settings from either client, the other will update immediately. Here's the page running in Chrome on the PC:
That's pretty much it.
Client talks to HTTP server. HTTP server talks to FPGA. I can now edit some register values in this sample app, see them updated live on the device and all the changes (even from the device itself) reflected live in the client views. Very handy.
In this test, the actual device isn't doing much. It really just outputting a VGA signal and not much else. The background color is set to the specified color and there's a test pattern that uses the bits set in the control. The batteries in my camera are dead or I'd take a picture for you. Maybe I'll put one up later. (Not much to see though.)
- VGATest.zip This is the VHDL source for the Digilent Nexys2. Project file included for Xilinx ISE 12.2
- VGATestServer.zip This is the source for the HTTP server and the client web code (found in the Distro sub-directory) Solution file included for Visual Studio 2008.
In some future posts I think I'll talk about some of the even cooler fun things that you can do with this kind of setup and perhaps walk through the code in detail. For now, poke around. And have fun.