Sunday, December 13, 2009

3ph Duo: Codefest, Part II

If nothing in my last post made any sense, this is what I was trying to say:



I used my good camera w/ directional microphone, and set up the scooter frame so that it would resonate. The difference is pretty clear here. Everything you hear translates to what you would feel while riding it, so the ride will be much smoother as well.

I finally worked out all the sign (+/-) kinks. The remaining task is to make it fault-tolerant and load test. The Hall effect sensors are not perfect, and they can occasionally throw in a false signal or miss a real signal. This becomes even more important when they are being relied upon for a speed estimate that drives the sine waves. The motor and MOSFETs should be able to tolerate a glitch like this, but at high speeds it can cause a draw-down on the main capacitor, which can sometimes cause the microcontroller to spontaneously jump out of the program loop. So it would be better if this didn't happen...

More to come.

Thursday, December 10, 2009

3ph Duo: Codefest

These days, most people are surprised when I tell them that my background is in mechanical engineering, not electrical, since more often then not I am troubleshooting some circuit or winding a giant inductor or something. But probably the thing that nobody knows is that my real background is in software. Okay, not in the educational sense, but I've been programming things since I was like...ten. I won't make any claims about my actual skill, but I will say that on more than one occasion I have been saved by a bit of software fidgeting that I probably couldn't have done if I hadn't been writing all those games (e.g. Pokemon Hunter and Pokemon Hunter II) when I was too little to use power tools.

In one of the saddest tragedies of my life, the original source code for Pokemon Hunter (written in QBASIC!) has been lost forever, but I assure you the graphics looked roughly like this.

Oh yeah, this is a post about my brushless motor controller. Or, the latest of them, anyway. If you've lost track, here is a side-by-side comparison of the three real iterations it has gone through:

Left-to-Right: Newest, Middle, Oldest


First was the double-stack IRFB3077-based controller (right), my very first shot at three-phase brushless motor control. While it has the distinction of MOSFETs so powerful they caused the aluminum bus bars to desolder themselves (Yes, you can solder aluminum.), it was impractically large for the scooter because each one only controlled one motor. Next was the greatly compacted 3ph Duo, so named because it controlled two motors from a single board using two IXYS GWM 100-01X1 six-pack MOSFET bridges as the inverter stages. And it works. But as I described in the last post, that won't stop me from making yet another one.

The most obvious visible change is the lack of LEM current sensors. These were large and expensive and have been replaced by the ACS714 surface-mount sensors, two each per motor. Two each so that I can effectively measure three-phase currents, instead of assuming that only two phases conduct current at a given time. If you want to know more about why I am concerned with this, this post sort-of sums it up. The goal is to implement full sine wave commutation with the possibility of phase adjustment, something that would separate this controller from other similarly sized and priced small vehicle brushless motor controllers on the market. Turns out from a hardware standpoint, this is very easy. The microcontroller I use, the TI MSP430F2274 already has six independent PWM output channels (three per motor). So the circuit board is essentially the same, with a few signals re-routed to make room for the six PWM pins.

The real challenge is the software. This is not a 32-bit processor with native floating-point. It is a regular old 16-bit mixed-signal processor that runs at a relatively poky 16Mhz and doesn't even have hardware multiply. Your cell phone probably has 10x more processing power. To roughly lay out the challenge: the PWMs are refreshed at a rate of 15kHz, setting the upper limit on the resolution of the sine waves generated. (You get 15,000 points per second with which to draw the sine function.) But to use all 15,000 points per second, six PWM values must be generated and scaled appropriately at every refresh. The clock speed is 16MHz, so all this has to happen in less than 1,000 clock cycles. To give you an idea of how hard this is, just multiplying two integer numbers together takes about 70 clock cycles on this chip. Forget about fractions and definitely forget about trigonometric functions. The only way to generate a sine wave is to use a look-up table, in this case 256 bytes in memory that store the value of the sine function for various angles. To set a baseline, this is how I originally implemented a sine-wave look-up on six channels:
aidx_int += aspeed;
aidx = aidx_int >> 8;
atemp = SIN8LUT[aidx];
btemp = SIN8LUT[(unsigned char)(aidx - TWOTHIRDSPI)];
ctemp = SIN8LUT[(unsigned char)(aidx + TWOTHIRDSPI)];
atemp = 65023 - ((atemp * amag) >> 6) + ((127 * amag) >> 6);
btemp = 65023 - ((btemp * amag) >> 6) + ((127 * amag) >> 6);
ctemp = 65023 - ((ctemp * amag) >> 6) + ((127 * amag) >> 6);

uidx_int += uspeed;
uidx = uidx_int >> 8;
utemp = SIN8LUT[uidx];
vtemp = SIN8LUT[(unsigned char)(uidx - TWOTHIRDSPI)];
wtemp = SIN8LUT[(unsigned char)(uidx + TWOTHIRDSPI)];
utemp = 65023 - ((utemp * umag) >> 6) + ((127 * umag) >> 6);
vtemp = 65023 - ((vtemp * umag) >> 6) + ((127 * umag) >> 6);
wtemp = 65023 - ((wtemp * umag) >> 6) + ((127 * umag) >> 6);
One motor is ABC, the other is UVW. This steps through the sine table at some speed, looking up values with an independent index for each motor. Then, it scales those values by some magnitude and shifts them such that their PWM-average outputs are sine wave voltages centered at half the DC voltage:


The PWM outputs, normalized to the DC (battery) voltage, for full-command (top) and half-command (bottom). In both cases, they are centered at half the battery voltage.


Well, this almost worked. It worked with 1, 2, 3, and 4 channels. But with 5 or 6 channels, the interrupts starting running into each other because it took longer than 1,000 clock cycles to get through that block of code. Luckily, it's not a very efficient way of doing things, so the next few paragraphs will describe how it was trimmed down. First, the zero-sequence hack. I don't know of anyone who refers to it as such, but this is my favorite motor code hack of all time. A lot of the code above is being used simply to offset the PWM values so that they are always centered at 50% duty cycle. This is...stupid. (Maybe not, but I declare it to be so.) So I ditched that drive method in favor of one that always puts the peak of the sine waves at +Vbat, like this:


The modified drive signals at full-command (top) and half-command (bottom).


The difference is that, except at full command, the three sine waves are shifted up to higher voltages with respect to the battery. But what really matters, as far as the motor is concerned, is the relative voltage across the three phases, which is the same in either case. The same amount of current will come out of or go into each wire, and the bulk shift does not change the motor operation. It does, however, greatly simplify the code:

aidx_int += aspeed;
aidx = aidx_int >> 8;
atemp = SIN8LUT[aidx];
btemp = SIN8LUT[(unsigned char)(aidx - TWOTHIRDSPI)];
ctemp = SIN8LUT[(unsigned char)(aidx + TWOTHIRDSPI)];
atemp = -((atemp * amag) >> 6);
btemp = -((btemp * amag) >> 6);
ctemp = -((ctemp * amag) >> 6);

uidx_int += uspeed;
uidx = uidx_int >> 8;
utemp = SIN8LUT[uidx];
vtemp = SIN8LUT[(unsigned char)(uidx - TWOTHIRDSPI)];
wtemp = SIN8LUT[(unsigned char)(uidx + TWOTHIRDSPI)];
utemp = -((utemp * umag) >> 6);
vtemp = -((vtemp * umag) >> 6);
wtemp = -((wtemp * umag) >> 6);
The extra multiplication of the magnitude to get the sine waves in the right place is gone. So is the bulk offset. All that's left is a negative of the magnitude times the sine value. (Don't ask why it's negative...you don't want to know.) This manipulation gets the interrupt routine down to a size where it can actually run at 15kHz...barely. The processor utilization is up near 60%:


This signal is high when the processor is in a PWM interrupt executing the above block of code.

Of that, roughly half of the time is spent just doing the six multiplications. There are tricks for fast software multiplication, but only if one of the operands is known a priori (not the case here). The sine table look-ups, as fast as they are, also take up some time. The adds and shifts are relatively small contributions. Amazingly, though, this actually works. Try getting your computer to do anything when its processor is being utilized by background processes 60% of the time. (Okay, dual core...I know...) But this can still execute a slow loop with control and data acquisition functions and read in more interrupts from the hall-effect sensors. It's still a little quirky, but it didn't collapse in a mess of horrible interrupt stack Jenga blocks like I thought it might.

But...it can actually be even more efficient. The key observation is in the nature of the outputs, a balanced three-phase set of voltages. This type of output only really has two degrees of freedome...magnitude and phase. So you should really only have to look up two numbers, right? (Can you see it coming?) If you add the sine of any three angles separated by 120ยบ each, you get zero. Try it. Or have Wolfram Alpha try it. Here's my proposal: Look up and scale two sine values per motor. The third is the sum of the first two, negated. C = -(A+B). W = -(U+V). It's a bit more complicated because of magnitude offsets, but if this offset is calculated based on the magnitude in the slow loop, it can be simply added to the third value:
uidx_int += uspeed;
uidx = uidx_int >> 8;
utemp = SIN8LUT[uidx];
vtemp = SIN8LUT[(unsigned char)(uidx - TWOTHIRDSPI)];
utemp = -((utemp * umag) >> 6);
vtemp = -((vtemp * umag) >> 6);
wtemp = utemp + vtemp - woffset;

aidx_int += aspeed;
aidx = aidx_int >> 8;
atemp = SIN8LUT[aidx];
btemp = SIN8LUT[(unsigned char)(aidx - TWOTHIRDSPI)];
atemp = -((atemp * amag) >> 6);
btemp = -((btemp * amag) >> 6);
ctemp = atemp + btemp - coffset;
That's two fewer table look-ups and two fewer multiplies. The resulting interrupt should run 30% faster, giving back valuable clock time. Again, don't ask why everything is negated...

That's a far stretch in the name of efficient computation, but I assure you it makes a difference. Although everything seemed to be more-or-less working even with the bulkier interrupt. It's always good to write simple code, for many reasons. Which reminds me of another good one:



Yep, finally ran out of program memory. And I sure as hell am not going to buy the $N,000 full version of IAR Embedded Workbench. For the record, no compiler is worth that much money. It's not like Solidworks or some specialized simulation program that costs a lot of money to develop. It's a f*cking compiler. It takes C code and makes assembly code based on a set of well-understood standards. Even if I were using it for a commercially, it would be more worth my time to get a free GCC compiler and learn how to use Eclipse. Screw you, IAR. But I love your free version. :) So I will just have to keep my code size down.

Now might be a good time to step back and ask what the heck I am doing. Optimizing this one timy bit of code is a long way from making a new sine-commutated motor controller, and I have a long way to go before I can say I've finished the latter. But with the sine wave generator running at 15kHz in the background, all that's really left is to integrate the Hall effect sensors and some sort of master control loop.

The Hall effect sensors are really easy...dare I say easier than they are with the normal six-step commutation scheme where they drive a state machine. When a sensor hit comes in, you abandon whatever position you were at before and jump to the "correct" place in the sine table. "Correct" is tricky, and this is where a phase offset can be added in. But for now, I will rely on the scooter's moveable Hall sensors to make this work. The other piece of the puzzle is to set the speed of advance through the sine table, aspeed and uspeed in the code above. This is done by (carefully) estimating the time of one electrical cycle, which is six Hall effect transitions or in this case 1/7th of a revolution. I say carefully because this breaks down at low speed and also can be "glitchy". So there is more software work to be done here.

Lastly, the master control loop. This is where the high-level implementation happens. Eventually, this will hopefully measure and control both the quadrature- and the direct-axis current. The quadrature-axis current is the current that actually pulls on the magnets, creating torque. The direct-axis current can be used to change the torque-speed curve by countering the magnets, but I doubt this will make much difference for the scooter motors. Anyway, there is still some work to be done before this method of control is fully functional. For now, it is easy enough to create something that works by just controlling some semi-arbitrary current measurement, or even just running it open loop. This is where I am now...testing the subsystems to make sure they are working reliably before I integrate everything in the master loop.

But that didn't stop me from trying to ride it. The most noticeable difference (besides the fear that it might jump out of the program loop and short the motor at any moment) is the reduced torque ripple. You can actually hear the difference between DC (six-step) operation and AC (sine wave) operaiton. Okay, maybe you can't really hear the difference...but that's mostly because my microphone is terrible and the sensors were still not quite in the right place in either case. But there is definitely less high-frequency noise. If you don't believe me, believe MATLAB:

I promise I will do a better job capturing the results in a future update. That is, assuming I don't go crazy from debugging software. It's usually not my favorite thing to do. There's some fun in squeezing every last drop of computational power out of this thing, but I would still rather be making something tangible. Although in the end that's what this is for, so that's one plus. And if things go downhill, the old controller worked perfectly fine. Which...hrm...why am I doing this again?

I also promise a massive technical write-up for anyone who is interested in how this thing actually works. Ha...funny...nobody cares about controller. But there will be a write-up nonetheless.


Sunday, December 6, 2009

Failbot

In case you were wondering what this was all about, this might make more sense:


Introducing Failbot.


Recently, many of the projects I've been working on have been for credit, or for a class, or continuations of my never-ending quest to build a better motor controller. Speaking of which, the newest implementation is showing promising results. Except for the fact that I ordered some incorrect components, it went together perfectly and, without giving away the big reveal, is quietly proving that sine wave control of standard hall effect sensor brushless motors is ... easy.

Right, Failbot. I've been thinking that my projects have become a bit too large in scope and slow in execution. I say that being a grad student has basically slowed my pace down by a factor of two, since for every hour I spend actually doing something, I now have to spend another hour wondering if it's the right way to do it, what the potential problems are, and how to do it more efficiently in the future. So I am taking on this mini-project as an admission of my declining ability to just do something without thinking it through from beginning to end. In fact, every time I stop to think about Failbot, I realize how likely it is to not work at all. Solution: stop thinking about it!

Anyway, the idea is simple: linear tread motors. It's something I've had in mind since about the time when I realized you could actually build a motor. The operating principle and basic construction is the same as the 12-slot, 14-pole "LRK" scooter motors. Except, it is unwrapped into a linear motor that pulls magnets attached to a tank tread. I have no doubt in my mind that, conceptually, this works. The difference here is that I don't have the time or money to invest in a properly-designed linear bearing system. So bad idea #1: The Teflon Gap. It's like an air gap, except with Teflon. This is the part where I go back to high-school physics:


That is a Teflon-coated wooden stick, a nickel-plated NdFeB magnet, and a 1kg block of steel. This isn't the magnet size being used on the treads. But for the experiment, I just wanted a nice big surface. The objective here is to find the coefficient of friction between nickel-plated magnet and Teflon. So, an inclined plane is in order:


This dirt-simple experiment shows the static friction coefficient to be about 0.11 and the kinetic coefficient to be about 0.08. Which is good, because there will be approximately 75lbs of normal force holding the magnets to the Teflon! The good news is that, unlike my 2.007 robot, the treads will stay on. The bad news is that they may not move at all. This means that the linear motor needs to develop at least 10lbs of force to do anything, let alone move smoothly. The rear scooter motor puts out about 30lbf at the air gap...but it's 1" wide. These are only 3/8" wide. In order to work, it will need similar or higher amp-turns, which is no easy task given the small slots. And I guess the coefficient of friction might change a little when the Teflon gets scratched up and little iron filings stick to the tread magnets... Oh, did I mention that there is no commercially-available reversing brushless motor controller that runs off an RC signal? Well, except maybe this one. However, ordering it on eBay might be a problem:


So assuming I don't get arrested or have my account suspended, I get a $60 controller that might be capable of reversible speed control. If you wonder why I make controllers from scratch, this is why. But in this case I would rather try my luck with the RC stuff. If it works, it will save me a lot of time and will be 2.007 hardware-compatible. Like my last little robot...minus the high-speed acrylic-shattering collisions. Maybe some MechEs will look at it and be inspired...to use wheels instead.

Thursday, December 3, 2009

HSV: Arrows and Fruit



In case you've never seen what an arrow going through fruit looks like in 333x slow motion.

These videos were shot with a Phantom high speed video camera in the Edgerton Center Strobe Lab during a freshman seminar. The lighting came out very well, even at 10,000 frames per second. Everyone's favorite seems to be the one where the arrow misses, taking out the cups but leaving the fruits hanging in mid-air. Newton's first and second law in action.

An interesting side note: the Phantom capture software saved the video in raw 8-bit format at a resolution of 512x256 (exactly 512x256 bytes per frame, no compression). But it added a .mov header. Although Quicktime could play it, my custom video converter tried to extract the frames as raw data. It put the header in as dummy pixels on the first frame, then all subsequent frames were shifted by that amount (so that they looked off-center). The fix was some very simple math:

  1. Divide the file size in bytes by (512x256). Take the remainder.
  2. Subtract that number of bytes from the file using a hex editor. This is the header.
  3. Confirmation that the header ends after this number of bytes:

A wonderful little tag "mdat" (movie data?) at the exact address of the remainder. I love raw video! Compression is for losers.