Tuesday, May 12, 2009

The "dead fish" flight control algorithm may not be FAA approved

No photos this time, sorry, I was too busy keeping my finger perched on the "/" key (which shuts down the flight control system and all the motors) to take any photos or videos...

So I set the flight control parameters to some reasonable seeming values, held my breath, and slowly turned up the collective gain (overall thrust). I think the longest flight I had was a second long, maybe two, before I would panic and shut everything off. I definitely made a number of propeller gouges in the foam pads...

One-second hop-and-flop flights aren't the best way to tune the feedback parameters. I did get it into a fascinating mode where it really was flopping about like a dead fish -- technically this is oscillation from having the proportional gain on the pitch and roll too high -- but actually failed to crash entirely for quite a while, though it was bouncing on its floor pads continuously. Also, there's a lot of friction between the bits of foam strapped to its underbody and the foam pads on the ground, which means that whenever it comes into contact with the ground it tends to tip over. (I see why a lot of people's experimental rotorcraft have ping-pong balls on sticks for landing gear.)

Some rig that somehow held it safely fixed in place but let it pivot freely through a wide range of pitch, roll, and yaw would be fantastic. I'm not quite sure how to arrange that. Failing that, maybe I should take it to some larger space (the cafeteria at work, on a weekend?) and give it more room to flop about while I tune all the parameters?

I'm reminded of learning to fly the original Draganflyer, a quadrotor with some basic stabilization but mostly manual control. We would practice in the cafe of our New York office, which didn't have particularly high ceilings or open spaces, and mostly be frustrated with our total lack of ability to do anything, so we would keep it very close to the floor and constantly power down just before it ran into the wall. Only here I'm tweaking code, not control sticks.

Sunday, May 10, 2009

The Propeller chip: the good, the weird, and the really weird?

I've been using the Parallax Propeller chip as the controller for my quadrotor. Specifically, I've been using the PropStick USB:



The Propeller is an embedded microprocessor with some unusual features. Basically, it's a controller optimized for bit-banging. It has 8 symmetrical cores called "cogs", each running at (up to) 80MHz, each of which has access to read and write all 32 I/O pins. Instead of dedicated peripheral controllers such as UARTs, SPI drivers, or EEPROM controllers, you just dedicate one of the cores to driving the pins directly. You can even generate NTSC or VGA video output this way. There is literally no interrupt controller! The basic chip can be had for $8 in DIP, QFN, or QFP form factors.

(The PropStick is an expensive ($80) but super convenient option -- it has the same form factor as the DIP variant, but includes power regulators, clock crystal, EEPROM, and a USB connector right on the part, so you need basically no external parts to be up and running. I'm paying for convenience here.)

The Propeller's design is super great in many ways. In my application I need to interface with the XBee radio (115kbps serial), the GPS (57kbps serial), the IMU (115kbps serial), and the motor speed controllers (four 50Hz PWM lines). A conventional embedded CPU such as the LPC2138 might have one or two UARTs and a PWM driver, so I'd still have to bit-bang the remaining serial port -- quite challenging at 57kbps if you want to do anything else at the same time! -- or use an external UART. In any case, I would have to fuss with interrupt controllers and so on, which is always a pain. In the Propeller, I simply spin up three cogs running FullDuplexSerial and one running Servo32v3 (both of which are included in the standard library) and tell them which pins to use. I dedicate another cog to parsing the IMU and GPS data as it comes in, leaving the main cog to do the actual flight control.

There's a catch, though.

The Propeller is normally programmed in this proprietary language called "Spin", possibly with some assembly language mixed in. Spin is a BASIC-like interpreted language, and it's kind of cute, but its limitations get frustrating after a while. The syntax is idiosyncratic; for example, the greater-than-or-equal-to comparison operator is "x => y", and the conventional "x >= y" actually means "x := x > y", because they have assignment versions of every conceivable operator, so you get weird bugs just because you slipped and used normal syntax in an if statement. The language is untyped and unchecked; you're constantly passing around pointers as integer values. There are no data structures, only arrays of primitive types. There is no heap. You can define code modules and instantiate them multiple times, but they must be instantiated statically, and cannot be passed outside the calling module. The libraries tend to be buggy, and people make weird cargo-cult modifications to standard modules (the GPS module, for example, demands a custom serial port module that includes decimal parsing routines).

I could live with all that, but the real problem is that Spin is slow, because it's a bytecode interpreted language running on a processor that's about as powerful as an old 486 to begin with (and a 486SX at that -- no FPU!). For example, I mentioned that I dedicate a cog to parsing the GPS and IMU output; that's because keeping up with 100Hz IMU updates and 5Hz GPS updates in Spin requires a dedicated cog. You can write assembly code, which runs much more quickly, but then, well, you have to write assembly code.

Worse, each cog only has 2KB of local "cog RAM" (there's 32KB of shared "hub RAM"), and instructions can only be executed from cog RAM. The Spin bytecode interpreter takes up all 2K. That means if you want to call assembly code from Spin, you have to send it off to another cog to execute. This means those 8 cogs are used up quickly. In my case, I have three dedicated to the serial ports, one to servo control, one to parsing the GPS and IMU output, one for the main control logic, and because the software floating point library uses assembly code that uses up another cog. That's all but one of the cogs used up already.

I would love a Propeller-like architecture that I could program in compiled C. ICCV7 for Propeller costs $99 and runs on Windows only (yuck). Catalina, a free port of lcc, is in "beta release", and apparently generates rather poor code so far. In any case you're not going to get very far squeezing compiled code into the 2K of cog RAM, so both C compilers use the "Large Memory Model" which copies instructions from hub RAM into cog RAM, runs them, and repeats. That means that compiled code in hub RAM runs 5 times slower than native code in cog RAM. (This is still many times faster than Spin code.) Also, the instruction set is apparently designed for hand assembled code, not compiled code -- there is no explicit support for a stack, for example, nor are there indexed addressing modes. I'm guessing clever compiler designers could work around these things, but the Propeller is a niche product, and clever compiler designers have other things to do with their time.

The Propeller 2 will have 256KB of hub RAM but still only 2KB of cog RAM, so I'm not sure it will make things much better. Maybe things like Catalina will be more mature and optimized by then and we'll just live with variants of the LMM. Apparently the Propeller 2 will have an entire IDE *on the chip*, so if you connect a TV (!) and a keyboard you can write code without ever using a PC.

Saturday, May 9, 2009

Not so good vibrations

I'm back at work now, so progress will be slower.

Last weekend, on Sunday night after BANG 21 wrapped up, I pulled everything together and ported the simple pitch/roll tilt sensor Kalman filter code to the onboard Propeller CPU, hooked it up to a simple PID controller (actually a P controller for now), set the collective to a low value (so it wouldn't actually fly), and... it went crazy. When the motors were running, the orientation reading would vary by as much as 50 degrees, even though the device was sitting on a desk. It would try to compensate for the huge perceived tilt, and the motors would start and stop spastically. Fortunately I had ramped down the power so nothing actually happened, but...

I theorized that the vibration caused by the motors was throwing the IMU out of whack. Today I finally got around to looking into it, and indeed, if you watch my host-based IMU sensor readout graphs when the motor turns on, you can see quite a bit of noise in the accelerometer data:



The motors were off at first, and then turned on; you can see the transition about 40% of the way across the line charts (especially visible on the X axis accelerometer). I did several things to try to make this better. First, I mounted both the IMU and the motors on rubber washers to isolate them somewhat from the frame:





The neoprene washers were from Home Depot, which had a limited selection of sizes, but anyway I got them to fit. Running a similar test, you can see that the vibration is improved somewhat, but by no means gone. Maybe I reduced it by half:



Now, the real question is why this is a problem. The whole design of the Kalman filter is to ignore high frequency transients in the accelerometer data, using it only as a low frequency drift correction for the gyro data. (As you can see, the gyro data is basically unaffected by the vibration.) And in fact, in my host based computations the filtered readout is basically fine. Near the lower right of the test displays, you'll see pitch and roll indicators; the yellow triangle is unfiltered accelerometer orientation, the yellow line is unzeroed gyro orientation, and the white indicator is the filtered result. You can see they both indicate (correctly) zero, even though the accelerometer is reading a significantly nonzero value for roll at that moment.

Anyway, after some debugging, I found a bug in the onboard code. (I was adding two floating point numbers with integer addition.) After fixing that, I got much more sensible results -- still varying a bit, but only by a degree or two (which is about the same as the host-based filter).

I'm not yet brave enough to actually ramp up the power to takeoff, but I powered it up to 30% or so (it takes 50% power to take off) and held it in my hand and moved it around. Lo and behold, I could feel the motors counteracting my motion. There's quite a lag -- I tip it to the side, and maybe a half second later it pushes back against me -- which seems like it would be a real problem for flight stability. Not sure if that's sensor reading or the time to spin up the motors or what. Maybe (hopefully?) the lag will be a lot less for small corrections. In any case I look forward to a long process of tuning the flight controller's feedback loops. (How to do that without crashing a zillion times? Even with my foam pads, I'm sure it's quite capable of inflicting serious damage on itself, not to mention me and my apartment.) Maybe some sort of strapdown test harness?

Anyway, that's good progress for one day I think.

The tomato plants are growing madly, they're like two feet tall now:



I'm not sure quite what to do with them. They're still not flowering, I sort of hoped they would have by now, but I'll be patient. Meanwhile, the little booklet has a few pages of advice about pruning the plant if it gets too high or too wide, but from reading their directions you'd think you just have to clip the errant leaf every few days or something. In reality, the thing seems to grow several inches overnight. Do I really want to be cutting off entire branches? I'm not sure. I trimmed back some of the really excessive growth and some of the leaves that had gotten burned when they grew into the lights (I told you it grew several inches!), but otherwise I'm leaving well enough alone for now. The little beasties certainly suck up a lot of water, I have to refill it with an entire pitcher pretty much every day.