Wednesday, December 28, 2011

4pcb: √ Control Law + Teaser...

Yes, I am making another post about quadrotors. A made a few minor tweaks to the control of 4pcb. The most notable is a change to the output command for each motor, after all the P[I]D controller and motor mapping. The commands are now sent through a square root function. The force produced by the props is more related to rotational speed squared than to rotational speed. The speed is roughly proportional to the command. So, to produce a corrective a force, as the PID controller would like to do, the command generated in response to angle or rate error should follow a square root-type curve. Or so the story goes.

The remap sends the command through a square root function while normalizing to 1, to preserve the command range. Something like this:

// square root remap, command range is 0-255:
command_out = 255.0*sqrt(command_in/255.0);

This keeps the maximum command at 255 and the minimum at 0, but shapes the intermediate values to be a square root function. I don't actually do it like this because square root on a microcontroller is a horrible thing. Both the input and the output are 8-bit integers, and I have plenty of memory, so I use a look-up table instead:

// I'm a horrible person, generating my LUT at run-time:
unsigned char SQRT_LUT[256];
for(unsigned int i = 0; i <= 255; i++)
{
  SQRT_LUT[i] = (unsigned char)(255.0*sqrt((float)i/255.0));
}

...and later, in the loop...


command_out = SQRT_LUT[command_in];

Because it's an 8-bit integer table, there is some quantization near the extremes, but my upper and lower throttle limits (100 and 230) constrain it to a well-behaved part of the curve:


After implementing the remap, I noticed a significant improvement in the performance. For one, some of the high-frequency oscillation is gone. It doesn't seem to wiggle back and forth quickly when it's just hovering. It's also easier to hold it at a given altitude, probably due to the more linear stick-to-force relationship. I don't think it's just placebo effect, but who knows?

I also made one other ugly hack. One motor has been consistently and annoyingly slow compared to the other three, causing the roll axis to be hard to fly, especially as the battery drops below about 7.4V. I just multiplied this motor's rate gain by 1.3. I have no justification for this value, but it helps. Maybe I will replace that motor and ESC at some point. Here's some new video:


I can keep it in the air indefinitely in a small room, as long as the battery is above 7.4V. Below 7.4V, the roll axis still gets soft and it's hard to not crash into things. So for now I live with flying just a bit more than half the battery capacity. Even Charles can kinda fly it now. So it must be a little more stable.

The two hardest parts about flying it are maintaining altitude and remembering which way it's pointed. While I think you can learn to deal with both of these, I would like it to be easy enough for anyone to pick up and fly. I have, waiting to be installed, a sonar module that can be used for closed-loop altitude control. Then, the stick would control a climb rate or descent rate, but with no input it would hold altitude and you can focus on flying the rotational axes. I also have yet to implement the magnetometer on the new IMU, which can be useful for holding heading so you don't have to deal with yaw.

I really shouldn't be spending this much time on quadrotors, though. I'm pretty sure I had other things I was supposed to be working on. On that note...

......

Friday, December 23, 2011

More Sensorless

I'm still tweaking the sensorless routine for Pneu Scooter. The software framework for sensorless sinusoidal commutation and field-oriented current control is set, and now I am playing with the different parameters to see if they have the effects I think they should.

First, I hypothesized in the last post that the positive relationship between current and flux estimator offset (with respect to the Hall effect sensors), seen here, was due to underestimated inductance. At high current, the rotor electrical angle predicted by the flux estimator would lead the angle interpolated from the Hall effect sensors, which I trust to be more accurate. Based on this figure, if the inductance parameter is too low, the [-IL] vector will be too short and the estimated flux vector will lead the actual flux vector at high current. 

To test this, I changed only the L parameter in the sensorless code definitions, from 200μH to 300μH, leaving all other settings the same. After another 15-minute outdoor test drive, I plotted the flux estimator offset as a function of current once again. Here it is with the old data for comparison:


The effect is exactly what I thought it would be, flattening out the slope so that the offset is constant (near zero) though the entire range of load currents. I may have even gone a bit too far, creating a very slightly negative slope. But now I know that the inductance parameter has as predictable effect. The lead induced by using an inductance parameter that was 33% too low was about 20º electrical at maximum current. The error sensitivity is easy to predict using the vectors in this figure, but I will spare you the analysis in this post.

A closed-loop flux observer, which some day in the future I will certainly try out, would seek to minimize the effect of an over- or under-estimated inductance parameter. The ultimate goal would be plug-and-play sensorless that adapts to any motor on the fly. But for now, I can live with nicely-behaved dependencies like this. Though, without the parallel-processed Hall effect sensor data, I would be lost as to how to characterize any of it.

I also tried out the sensorless controller on a different motor:


The thing clamped to the table is Kitmotter, a demonstration motor with very similar characteristics to Pneu Scooter's wheel motor. They have virtually the same measured inductance. Kitmotter has a higher resistance, which I modified in the sensorless code definitions for this test. It also has a lower torque constant, which is not an explicit parameter in this sensorless algorithm. What it does mean, though, is that the flux magnitude should be lower:


And in fact it is, by a small amount. It's also interesting to see that the general form of the flux estimates is very similar for both motors. In particular, the gap between about 200º and 210º shows up in both cases. This eliminates the possibility that the gap is caused by some asymmetry in one motor's winding. It could be current sensor asymmetry, though.

Kitmotter also accidentally allowed me to test the fast overcurrent shutdown. The dangling mess of alligator clips seen above was not the best idea, and when two of the phase outputs shorted to each other, causing a large spark, the controller shut down properly and gave an overcurrent fault. No dead FET.

The next parameter I want to tweak is the low-pass filter time constant, which affects the low speed performance of the sensorless control. I mentioned that the low-pass filter causes the flux estimator to lead the true angle at low speeds, and showed the offset as a function of speed. Here it is again, but in pseudo-3D:

Is this nauseating?
Below 200rpm, the flux estimator angle is greater than the Hall effect sensor angle (trusted to be the true electrical angle). This causes the data, which should lie on the x = y plane, to slope off to the left at low speeds. Making the low-pass filter time constant longer should flatten out the plane and improve the low-speed torque. A lower "valid data" speed for switching to sensorless control will make start-up ramping, when I get around to that, just a bit easier.

I'm also worried about high-speed operation, which was the problem that magically disappeared when I made several code improvements. But, I'm still working with relatively slow motors, having commutation frequencies of 200Hz or less. I'm interested to see how things change (probably for the worse) when I try to run a non-direct-drive (indirect-drive?) motor like that of tinyKart, which can hit 750Hz or more.

More testing to come...

Sunday, December 18, 2011

Sensorless Pneu Scooter: Part 2

Pneu Scooter is now Sensorless Pneu Scooter! (Which means I can ride it again.)

This is the follow-up to Part 1: Where I make my code much fancier. Most of the work of the past two weeks went into setting up a new timing structure for the control code, which is summarized by this graphic. The controller has a fast loop, which runs at 15.625kHz, and a slow loop, which runs at just under 1kHz. The slow loop handles the computationally-burdensome Field-Oriented Control (FOC) calculations, and hasn't changed since I first implemented it on B.W.D. long ago. The concept of FOC is summarized in these two posts, and is shown in this, the first of many neon graphics I color-inverted for this post:


FOC seeks to keep stator current, I, in phase with back EMF, E, which is equivalent to saying that it keeps stator flux 90º ahead of rotor flux. This produces the most torque per amp. To keep the current and back EMF in phase, the voltage, V, is phase-advanced by the controller to counteract the effect of inductance. 

All these quantities are treated as vectors (really, the magnitude and relative phase of a set of balanced three-phase sine waves). The coordinate system is aligned with the rotor's magnetic field (hence, "field-oriented") and the axis on which the rotor flux is at a maximum is called the Direct (D) Axis. The Quadrature (Q) Axis leads the D-Axis by 90º electrical in the direction of rotation.

Because the coordinate system is locked to and spinning with the rotor, the position of the rotor must be known with good resolution for any of this to work. Previously, I used Hall effect sensors and time-based interpolation to derive the position. But, as the name would imply, Sensorless Pneu Scooter needs new tricks that do not rely on Hall effect sensors. To obtain the rotor position, the fast loop estimates flux on all three phases. The flux estimator I chose to start with is a very simple one:


It's an "open-loop" flux observer that derives the rotor flux linkage from measured currents, driven voltages, and a good estimate of the motor's resistance, R, and inductance, L. The vector version of this flux estimator is shown in the image above. Note that the integral of a given vector is another vector which lags by 90º and is scaled by the angular frequency:


Which brings up another good point: Unlike back EMF, flux does not vary with speed, so the flux estimator should produce a constant magnitude at any speed. As the V and E vectors increase in magnitude, the scaling by ω keeps the integral-form from growing.

The figure above is actually somewhat misleading. For one, the flux estimator doesn't run in the rotating D/Q coordinate system, but rather in the stationary frame, on each of the three phases independently. Also, the estimated flux on each phase is a scalar quantity; it's just the magnitude of the rotor flux seen by that particular motor phase at that particular instant. To derive position from this, I take an unusual approach of watching for flux zero-crossings (with hysteresis) and feeding these transitions to the same interpolation routine that my Hall effect sensors would have gone to.

In this way, the flux estimator is always running in parallel with the Hall effect sensors and control can be given to either one at any point in time. Additionally, they can be compared to each other. So, as I've been test-driving with the sensorless position estimate in control, I've been logging data from both. Cue the rest of the neon graph sequence:


All of the the data in this post was recorded during a five-minute outdoor test drive with the sensorless position estimate running the field-oriented control. The top plot above shows effective regulation of Q-Axis current to follow the throttle command, and the D-Axis current being held close to zero. The bottom plot shows the speed estimates produced by both the Hall effect sensors and the flux estimator. (Both calculate speed by measuring the time for one full period of rotor flux.) They are nearly identical at all speeds, including below 100rpm.

Speed is not sufficient for control, though; the flux estimator needs to be able to accurately derive the rotor position. Comparing the rotor position derived by the flux estimator to that of the Hall effect sensors reveals the following:


The ideal case would be a straight line with a slope of one, indicating perfect agreement between the flux estimator and the Hall effect sensors. There is a good deal of noise in the position estimate, but the general 1:1 correlation is there. The flux estimator angle leads the Hall effect sensor angle slightly, shifting the entire line upwards by about 15º. This is not necessarily the fault of the flux estimator; it could be interpreted as the Hall effect sensors being about 15º behind neutral timing.

To get more of a feel for the source of the noise around the 1:1 line, I plotted the relative phase offset between the flux estimator's angle and the Hall effect sensor angle as a function of a few other quantities. First, speed:


There are several interesting bits of information in this plot:
  1. At any speed, the band of uncertainty in the position estimate is still about 30º. Therefore, there must be factors other than speed which cause offset in the position estimate.
  2. The bulk shift of 15º (flux estimator leading Hall effect sensors) is clear in this plot too.
  3. At low speeds, the flux estimator leads the Hall effect sensors even more. This one is easy to explain. Instead of a pure integrator, I implemented a low-pass filter on (V-IR) as part of the flux esimator:
Pure integrator.
Low-pass filter.
The low-pass filter is used to keep the integral from drifting away. It's combined with a gain of τ, the filter time constant, so that at high frequencies it looks like a pure integrator. But at low frequencies, it has less than the -90º of phase a pure integrator would give:


At the corner frequency I chose for the low-pass filter, 0.053s, the low-pass filter causes the flux estimator to lead significantly at low speeds. The blue line in the offset vs. speed plot above is the theoretical lead induced by the low-pass filter. Setting the filter time constant to be longer would push the speed at which lead becomes significant down lower. Since the data seems to clearly show this effect, I would like to run some tests where all I change is the low-pass filter time constant. Obviously a longer time constant would be better for low speed operation. Integral drift might hurt the performance overall, though.

I also plotted the offset between the flux estimator angle and the Hall effect sensor angle as a function of load (Q-Axis current):


Here, another clear trend was revealed: the flux estimator lead increases with current. My hypothesis on this one is that the inductance estimate is low. In the first graphic of this post, the -IL vector would be too short if L were underestimated. This would cause the estimated flux to be somewhere in the upper-right quadrant, leading the true flux, which is on the D-Axis. Again, since the data shows a very clear trend, I would like to run some test where I vary only the inductance parameter to see if I can flatten out this line. 

This particular dependency (position error as a function of load) is one that I think can be avoided as I pursue more advanced sensorless algorithms. A "closed-loop" flux observer would be able to more readily handle misidentified motor parameters by using feedback to adapt the motor model or negate some of the effects of the misidentified parameter. Another valid option is to have the motor parameters self-calibrate, either at power-on or dynamically as it runs.

Lastly, my favorite graph of all:


I had the data spit back the actual flux estimate on Phase A along with all the phase currents. Plotting the Phase A flux as a function of the flux estimator's angle reveals a very nice sinusoidal flux that peaks at 180º and has an amplitude of about 15-17mWb. Not coincidentally, this is close to 1/7th of the motor's per-phase back EMF constant of 0.105V/(rad/s) = 0.105Wb. (The motor has seven pole pairs.)

The three phase currents are also plotted and are bounded by nice 40A sine waves, representing maximum positive throttle. Phase A's current (yellow) leads Phase A's flux (white) by about 90º, as it should if the field-oriented control is doing its job.There's a very interesting gap in the flux estimate angle just before 210º, which is also seen as a "staircase" effect in the flux estimator angle vs. Hall effect sensor angle plot. I still haven't tracked this one down or formed a theory about it yet, but this convinces me that it's an issue inherent to the flux estimate, since this plot has nothing to do with the Hall effect sensors.

Altogether, this "simple" sensorless routine has been working very well since I modified the controller timing. I can't say I understand exactly why (or even if) the changes I made to the loop timing caused it to start working, but I'm happy that it's now producing useful data and that I have new tests to try out. 

I'm even more happy that my Pneu Scooter riding ban can be lifted since it's now capable of propelling itself in 100% sensorless mode.

Saturday, December 17, 2011

Sensorless Pneu Scooter: Part 1

After just about one month, my voluntary moratorium on Pneu Scooter riding is officially over. Yes, that means it is now Sensorless Pneu Scooter. Even though I was distracted by flying things for a short period of time, I have made steady progress on working out the software kinks to get my first attempt at a sensorless field-oriented control strategy up and running. This is a two-week update, so I think I'll have to split it into two parts. The first part here explains most of the code tweaking I did. The second part, to come shortly, is when the sensorless control finally begins to work the way it should. How the two parts are related to each other, I don't exactly know.

Part 1: Where I make my code much fancier.

In the last update, I had been able to get Pneu Scooter running for a short indoor test drive on sensorless control. While it worked at low speed and low load, it was glitchy at anything above 20A and seemed to completely fail at 800rpm. These may have been related or unrelated problems, and I never got far enough into troubleshooting it to find out. Instead, while test-driving, one of the glitches took out the power stage, killing the FET, at least two gate drivers, and leaving a small crater where my DC current sensor used to be:


In a previous FET failure, the trace leading into the DC bus had blown, so I reinforced it with some copper braid. This moved the weakest link to the current sensor, I guess. It's worth noting that there is a 40A slow fuse in the system but it seems never to actually do anything during failures. Maybe time to go back to the magnetically-tripped circuit breaker switch. In any case, this board is toast. Even after I repair the power stage, it will only work with the DC current sensor bypassed completely. Possible motivation to design 3ph v4.0.

I switched to my back-up 3ph v3.1 controller and took some time to implement a few software improvements, including fast over-current shutdown designed to (possibly) save the FETs if (when) the sensorless control commutation glitches. I also implemented a better ADC sampling strategy to try to clean up the current measurements as much as possible. The goal was to sample phase currents at a time where no power switching is happening, since the switching transients can couple noise onto the current sensor signals.

First, the three PWM timers are synchronized (no easy task) and set to center-aligned mode, producing nested and symmetric voltage ouputs. In addition to reducing ripple on the DC bus capacitors, this has the benefit of creating a guaranteed point in time where all three PWMs are off. This is where I wanted to trigger the ADC to sample phase current. The STM32F103 has all sorts of interesting timer and ADC setup nuances that I discovered. After several days of messing around with timer and ADC configuration registers, I got to this:

Scopemas Tree.
I had to overlap the three voltage outputs to get all the traces to fit on the scope. Here, they are running with duty cycles of 25%, 50%, and 75%, just to illustrate what center-timed PWM looks like. Timer 1 is the master and the other two are synchronized to it. I set up an unused Timer 1 compare output as the trigger for the ADC, and I configured the dual ADC so I can convert two measurements simultaneously. I group and order the measurements as follows:
  1. Phase A Current & Phase C Current
  2. DC Current & DC Voltage
  3. Throttle & Nothing
The phase current measurements are most important, so they go first, triggered just before the center of the "off" portion of all three PWMs. The other measurements are more heavily filtered in hardware and software, so I don't care about protecting them from the switching transients. With my ADC configuration, it takes about 1.7μs to sample the two phase current analog signals. So, I set the ADC trigger to occur about 0.85μs before dead-center and expect it to finish 0.85μs after dead-center. After that, there is about another 2μs of conversion time, when the ADC translates the sampled voltage into a digital value. Then, an interrupt is triggered which stores the result in a variable. Zooming in on the center of the PWM, the timing of the first two sample-and-converts is something like this:


This scope plot also shows the motivation for sampling the phase currents away from the transitions. The tiny bits of ringing at each switching event show up everywhere: on other voltage outputs and on the digital line being used to illustrate processor timing. So, by leaving a window where all three PWMs are off and sampling the phase currents exactly in that window, the noise is hopefully reduced. The width of this "all-off" window has to be just a bit wider than the ADC sample time of 1.7μs, so perhaps 2μs. That limits the maximum duty cycle at 15.625kHz to 2μs/64μs, about 96.8%. Since the gate drivers are bootstrapped anyway and need some off-time to work at all, this seems like a reasonable limit.

After the three sets of ADC conversions, the fast loop is triggered and runs the sensorless field-oriented control routine using the freshly-converted current measurements. I also added some fast over-current shutdown code to kill the gate drive if any of the phase currents or the DC current exceed a maximum limit, positive or negative. Hopefully the combination of cleaner current measurments and shutdown within 64μs of an overcurrent fault will have some chance of saving the FETs if there is a controller glitch.

In case the last 1,000 words weren't very clear, here's a graphic showing the controller timing in its entirety:

(Click to enlarge.)
There are still some minor tweaks I want to make to the code. One way to save a ton of time is to use the Direct Memory Access (DMA) peripheral to automatically store ADC results in memory. Theoretically, this would cut the entire ADC block down to about 10μs. I'm also still running at only 32MHz. I don't need it yet, but the processor is capable of 72MHz in case I want to add more fanciness to my fast loop.

But for now, this setup seems fine. After loading the new version of the controller, Sensorless Pneu Scooter magically started working the way it should. I didn't think I had changed anything fundamental, but the performance is much better than it was before, almost indistinguishable from the previously-sensored Pneu Scooter. I've been riding it around for a few days now with no problems.

In Part 2, I'll get to the sensorless scheme itself and the data I've collected during my few days of test driving...

Saturday, December 10, 2011

Slightly More Quadrotor

I promise I will put it away soon...
After some reasonable success with the new digital IMU, I decided to play around with the digital filters built into the L3G4200D 3-axis gyro to see if I could improve the performance a little more. (The accelerometer is already heavily-filtered by the complementary filter, so no need to focus much attention on that.)

In previous testing, I had determined that the gyro rate signals were incredibly noisy and that the source of the noise was not electrical but rather mechanical frame vibrations. This is a common problem and, according to many people, it can most often be solved by mounting the sensors with foam tape. For some reason, foam tape alone is not enough on this frame, so I have to resort to heavier filtering:


From top to bottom, the graphs represent digital low-pass filters (built into the gyro) with cutoff frequencies of 50Hz, 25Hz, and 12.5Hz. The left plots are the rate signals when the quadrotor is held level and the motors are run at full throttle. The right plots are the angle computed by the complementary filter.

Noise in the rate signal can cause trouble in two ways: (1) directly, by being amplified through the derivative (D) term in the control loop, and (2) indirectly, by causing random drift in the angle calculated by the complementary filter. I actually think the second problem is the bigger one, since the angle drift occurs on a long enough time scale that it doesn't get averaged out by the mechanical system. I noticed while flying that the quadrotor would drift a lot and sometimes quickly dart off in one direction, something that could be caused by the sharp jump in angle seen in the right-middle graph.

With heavier digital low-pass filtering, the rate signal noise is significantly reduced. The angle estimate is also more stable. The trade-off is dynamic performance. With a 12.5Hz low-pass filter, command inputs faster than that cannot be tracked. A 12.5Hz low-pass filter has a rise time of about 20ms. I tried flying with this filter and the improvement in static hovering ability greatly outweighs any decrease in response time. Here's some new video:



It doesn't drift as much and the random darting is significantly reduced, at least to the point where it can be controlled. It still lacks altitude control, so maintaining height is difficult. But the improvement just by tweaking the filters is pretty neat.

I also finally compiled the documentation for this project. It's certainly not a fully-tested and user-friendly kit, but if you want to make your own, here is the documentation folder: 

4pcb_DOC.zip (1.38MB)

It's also on the project page. It contains:
  • Full bill of materials
  • EAGLE files
  • Gerber board files
  • Schematic
  • Arduino project
  • Executable control station GUI
  • Control station GUI source (VB .NET)
  • Picture showing mounting of IMU
The most important change from the original schematic is that the Sparkfun IMU, which has been discontinued, is not used. Instead, the Pololu minIMU-9 is externally wired into SDA, SCL, +5V and GND. Since the IMU needs to be creatively mounted on a ball of foam tape anyway, I did not include it in the board schematic. The power wiring to the motor control chips is also done externally, connecting to the pads near each chip. In a future version of the board I may clean all this up.

Monday, December 5, 2011

PCB Quadrotor: New Digital IMU

Over the weekend I took a break from my sensorless FOC work to upgrade 4pcb with a new digital IMU:


It's a Pololu MinIMU-9, featuring an ST L3G4200D 3-axis gyro and LSM303DLM 3-axis accelerometer and magnetometer. This replaces the now-discontinued Sparkfun 6DOF Razor IMU, which had six analog channels (3-axis gyro and 3-axis accelerometer). So, instead of reading each axis into the Arduino Mini's 10-bit ADC, the digital IMU streams 12-bit readings over a 2-wire serial interface (I2C).

The digital IMU has other nice features as well, such as programmable gains, sampling rates, and low-pass filters. No more fidgeting with RC filters on the board. It's compatible with the Arduino Two-Wire Interface (TWI/I2C) library and the sample code on the Pololu page is easy to work with. For once, I must admit that the Arduino I2C stuff does in fact make life easier.

Even with the nice new IMU, 4pcb still seems to suffer from the same vibration problems. I'm not sure why most people are able to get away with just mounting the sensors on some foam but that doesn't seem to be enough for this frame. In order for it to be even flyable, I need to add mass (aluminum) to the back of the IMU board and stick it to a very loose loop of foam tape:



Even the wires connecting the IMU to the main board need to be very flexible high-strand-count wire so as not to transmit vibrations. I should really film it in high speed and be horrified at the amount of high-frequency flexing happening in the frame. It would also be very easy to add stiffeners, though I would have to think about how much I want to compromise on the "entirely made out of a single PCB" goal.

In any case, with the foam loop mounting and carefully-chosen filters, it works:



It's certainly flyable with not much skill and it can stay aloft much longer than in previous testing. The roll axis seems to be more prone to oscillation than the pitch axis, which is something I'll have to look into. I'm not sure it will ever be the type of quadrotor that can sit and hover in one place for an extended period of time with little input from the user. Given its size and minimal sensor set (no GPS, no altimeter), it might always be a little twitchy. But at least it's robust; it's almost never damaged in a crash and even if it is, I have enough spare parts to last forever. And it poses no risk to human life...

Sunday, December 4, 2011

First Sensorless FOC Test Drive

Having done enough computational optimization of the flux observer math and tested the sensorless sine commutation with no load, the next obvious step was to test drive it on Pneu Scooter:


Well, no, the next obvious step would have been to run it under load with the Hall effect sensors but collect data from the flux observer in parallel to ensure that it is working. But I guess I skipped over that step.

As you can see in the video, the open-loop flux observer appears to work very well at low speeds. There is no start-up ramp in the code right now; I just nudge the scooter very slightly forward and it picks up commutation right away. It's able to find the position of the rotor at speeds down to 50rpm, which is less than 5% of the maximum speed. Here's a slice of data from one of these low-speed runs, showing the flux observer speed estimate matching up with the Hall effect sensor speed in the range of 50-400rpm:


Here's the comparison of estimated rotor electrical angles for the same data range:


The 1:1 correlation is not as tight as that of the no-load test. The additional noise is most likely caused by the current sensors, and is a target for improvement. The current sensors on this particular controller have always been noisy. When I was in Singapore last year, I confirmed that the source of noise was the PWM. I later added better filtering and that made the sensors much more usable.  But one thing I've wanted to try for a while is more intelligent ADC sampling, where the ADC is triggered in the middle of a PWM pulse, away from the noisy transitions:


In order to do this on 3ph v3.1, I will need to mess with the timers a bit. Each of the three phases of PWM are controlled by different timers (for very good reasons, I assure you). So before I can have them trigger the ADC, I will need to synchronize them and change the PWMs to be center-timed, all things the 1072-page STM32F103 reference manual ensures me are possible.

Current sensor noise isn't the only problem left to solve, though. Somehow, the flux observer works very well at low speed, where sensorless routines are supposed to struggle, but it fails at high speeds, where they should do well thanks to the large back EMF. The speed at which this happens, under no-load operation, is strongly dependent on the low-pass filter time constant I use for integrating V-IR. (I swear this low-pass filter is evil.)

During the process of tweaking the low-pass filter time constant, I was able to bump up the speed of the flux estimator. But this time when it failed at the higher speed, it took out one phase worth of MOSFETs with it. So I'll have to replace the whole IXYS GWM100-01X1 module and/or switch to the spare controller. That gives me an opportunity to implement a few other improvements:
  • Center-timed PWM with clean ADC triggering, as discussed above. I may even try to use the Direct
    Memory Access (DMA) to fully automate the ADC and save processor time.

  • Faster fast loop. I don't want the fast loop and the ADC sampling to run into each other, so I will put the entire fast loop at PWM frequency, as it was on 3ph v2.1. The timer interrupt will trigger the ADC, then the End-of-Conversion interrupt will trigger the fast loop.

  • Fast-loop current fault protection. If any phase current measurement exceeds some threshold, it will trigger gate driver shutdown in the fast loop, within 64μs. This might help protect the MOSFETs from destruction in the (likely/certain) event of commutation failure.

  • More data. I would like to transmit some of the intermediate variables in the flux estimator so I can maybe see more clearly why it fails at high speed.
Hopefully with all these changes, testing will go more smoothly this week.

Tuesday, November 29, 2011

First instance of sensorless sine commutation!

I won't quite call it sensorless field-oriented control yet, because it's still running open-loop sinusoidal commutation, but I've finally been able to yank the sensor cable:


Here's the proof in data:

(Cap Kart's fancy data logger has certainly come in handy.)
What you see there is a plot (in time) of the motor RPM as measured directly by the Hall effect sensors (yellow) and indirectly by the open-loop flux estimator (blue). At first, both RPM measurements are running simultaneously and they overlap. At the point where the sensor cable is yanked, the RPM measurement from the sensors freezes and then times out to zero, but the flux estimator RPM continues to read properly. The commutation is being controlled by the flux estimator only and the motor continues to run, even under load.

For this test, the motor is being driven by a 12.5% command, which means the peak-to-peak value of the sine waves is 12.5% of the DC voltage. The no-load speed is therefore about 12.5% of maximum. The loaded speed dips to as low as about 6% during the test. This is a good sign that the flux estimator is capable of working at low speeds where the back EMF is small. I haven't written the start-up ramp yet, but this test tells me that I should be able to exit the start-up mode at less than 10% speed.

The properly-functioning flux estimator did not come easy. As I mentioned last time, I converted all the floating-point math to fixed-point 32-bit integer math to get the estimator to run at 10kHz. This led to several instances of both loss-of-precision and overflow problems. The biggest culprit was a low-pass filter being used as a pseudo-integrator on (V-IR). Here's how it looks in floating-point:

vir_a = 0.9996 * vir_a + 0.0004 * vir_a_temp;

Every 100μs, the variable vir_a is decreased by 0.04% and the same percentage of a new value, vir_a_temp, is added in. Since this happens 10,000 times per second, the overall effect is that of a low-pass filter with a time constant of 0.25s. The simple conversion to fixed-point math would be:

vir_a = vir_a * 9996 / 10000 + vir_a_temp * 4 / 10000;

The constants are moved to the opposite side of the variables so that left-to-right order of operations doesn't yield zero all the time. (9996 / 10000 = 0 in integer math.) But that's not the only issue:

[Start of horrible multi-day debugging sequence:]

If vir_a_temp is less than about 10^6, the minimum tolerable precision (~1%) of the second term is lost. Since the physical unit of vir_a and vir_a_temp is Webers, this means working in μWb or even nWb depending on the expected flux.

But, if vir_a is greater than 10^6, then the first multiplication of the first term, vir_a * 9996, overflows a 32-bit signed integer.

Okay, no problem, let's just change the order of operations in the first term:

vir_a = vir_a / 10000 * 9996 + ...

Problem solved? Actually, no. Think about what happens on the first two iterations with vir_a_temp set to 10^6 and vir_a intially at zero.

(1) vir_a = 0 / 10000 * 9996 + 1000000 * 4 / 10000;

The result is 400.

(2) vir_a = 400 / 10000 * 9996 + 1000000 * 4 / 10000;

The results is...400 again. The first term is zero.You can probably see the problem. vir_a will never increase because the divide by 10000 kills off the small initial values completely. So, you can't divide by 10000 first because it kills the small initial values, and you can't multiply by 9996 first because it overflows the large steady-state values. My solution is so horrible that I'm actually ashamed to post it:

if((vir_a <= 214748) && (vir_a >= -214748))
{ vir_a = vir_a * 9996 / 10000; }
else
{ vir_a = vir_a / 10000 * 9996; }

(Note: 214748 is 2^31 / 10000)

There must be a more elegant way to do this...

[End of horrible multi-day debugging sequence.]


After sorting out that mess, I could finally get to the interesting part, which was actually testing the flux estimator to see if it works. Because I opted to paste the flux estimator directly in parallel with my existing sensor-based control code, the testing was greatly simplified:

At all times, the Hall effect sensors are being read and a rotor electrical angle is interpolated from them. Separately, the flux estimator is running on all three phases. At flux zero-crossings, a "virtual Hall effect sensor" transition is generated. The rotor electrical angle based on flux is interpolated from these virtual Hall effect sensor transitions, using the same method as the real sensors. This is where the two methods intersect, and either electrical angle can be fed into the field-oriented control algorithm (or, in this case, directly into the sine wave generator).

Both electrical angles (from flux and from sensors) are recorded and periodically transmitted to the data logger. Because the commutation frequency is much greater than the transmit frequency, plotting either angle against time would not be very informative. But, plotting them against each other tells the whole story:


The rotor electrical angle derived from Hall effect sensors, which were properly sequenced and timed, is nicely correlated to the rotor angle derived from the flux estimator. The "virtual Hall effect sensors" from the flux estimator do not require sequencing or timing, since they are linked to the correct phase variables already. (No more guessing wire combinations!) 

There is a bit of a staircase effect which I have yet to determine a cause for. There are also still a few bugs to track down, including something that is causing the field-oriented control to freak out. And I haven't even begun to write in the start-up and fault detection, but at least progress has finally been made.

Saturday, November 19, 2011

Ah, software optimization, my old friend. It's been a while.

In a somewhat drastic attempt to force myself to get work done, I have decided not to ride Pneu Scooter until I implement sensorless field-oriented control on its controller, a 3ph 3.1 board that's been happily commutating the motor off Hall effect sensors for something like 2-3 million electrical cycles. It's pretty much an ideal test platform since the motor is well-characterized (I built it.) and the controller is known to be reliable. It's also a relatively low-frequency system (175Hz at top speed), so the sensorless algorithm need not be absurdly fast to keep up with the commutation frequency. But, I do plan to use this algorithm on faster motors, so I'm designing for higher frequencies anyway.

The highest frequency the controller could run at is the PWM frequency, in this case 15.6kHz. Above this speed, it's not possible to update the commanded voltage to the motor fast enough, since the PWM duty cycle is not latched into a timer until the next PWM cycle. So, even if you could run a sensorless algorithm faster, there would be almost no point.

Back when I was using the MSP430F2274, I ran a "fast loop" at 14.4kHz (PWM frequency) to handle sensor polling, speed estimation, position interpolation, and updating the three phase motor PWMs from a sine look-up table. The "slow loop" ran at 122Hz and handled current sampling, coordinate transformation into the rotating reference frame, and feedback control of the q-axis and d-axis currents.

This.
On that processor, which doesn't have a hardware multiplier, getting the fast loop to run at 14.4kHz was a major challenge. I spent a large portion of the development time just optimizing the software using a number of tricks to get the processing time down to 53μs (for two motors). The largest single processor burden, accounting for 10μs, was the integer division required to get the speed of the motor, 1/(time per cycle). This was large enough that I couldn't compute both motor speeds in the same fast loop cycle; one always got priority.

High = processor in use. Low = processor free. The lighter portion of the trace is the single integer division.
Even totally optimized with all integer math (no floating-point), the dual FOC was just barely able to fit in the fast loop at 14.4kHz. The leftover processor time went to the slow loop, which could be run arbitrarily slow thanks to the coordinate transformation to the rotating frame. So, having floating-point controller math in the slow loop has never been an issue.

But now, I've moved on to the STM32F103 32-bit ARM processor, which has a hardware multiplier. Though so far I haven't done anything other than waste the extra processing power on silly things, one of my motivations for giving up my beloved MSP430 was to be able to implement sensorless field-oriented control. But first, for comparison, here's what single-motor FOC code looks like ported to the STM32F103:


The fast loop on this processor runs at 10kHz. It's no longer tied to the PWM frequency, so it's easy to change. I chose 10kHz for simple math. Using the same clock speed as the MSP430, single-motor FOC takes just under 7us. The integer operations, including multiplication, happen in one clock cycle instead of the 50-60 it took on the MSP430. Integer division is also fast, though to the best of my knowledge it isn't single-cycle. And these are 32-bit operands, so they inherently have more precision than the MSP430's 16-bit integers.

What about floating-point? I thought maybe the fast integer hardware would be leveraged somehow to make floating-point operations faster as well, even though the STM32F1-series does not have a hardware floating-point unit. So, I threw on my first attempt at a rotor flux observer, all implemented in floating-point math, to test this. 

Mathematical Tangent:
The rotor flux observer, used to estimate the position of the rotor magnets in lieu of Hall effect sensors, is a simple one that I mentioned before:


It's an open-loop rotor flux observer, meaning there is no feedback to correct the estimated flux. It relies on a reasonably accurate estimate of R and L (the motor resistance and inductance, respectively) to produce the flux estimate. I did a little more thinking and decided that this is a good place to start, instead of jumping right into closed-loop flux observers. The nice things about the open-loop rotor flux estimator, in my view, are:
  • It's very obvious what it's doing, in the context of the motor electrical model. In my experience, simple things tend to work more reliably.
  • It estimates flux, instead of back EMF. The value of flux is speed-independent, so the amplitude of the flux estimate should remain constant. The integrator also filters out noise in the current measurement. No derivative of current is required.
  • The effect of parameter offset is easy to analyze. More on this in a later post, but it's easy to show with simple geometrical arguments what the effect of an improperly-set R or L is.
I think in time I will move back toward the closed-loop observer, which can compensate for the parameter offset automatically, but for now this is what I'm starting with. So, the fast-loop code must sample the phase current sensors and the DC voltage. Phase voltage is computed as a duty cycle fraction of the DC voltage, based on the sine look-up table. The integration is implemented as a low-pass filter, to kill off DC offset. 

All three phase fluxes are estimated, and used to establish the motor position through what I will call "virtual Hall effect sensors" that detect flux zero-crossings. This method, though completely non-traditional and probably stupid, allows me to tape the flux observers to the back of my existing sensor-based FOC code and get up and running quickly.

Okay back to software:
Implementing the above open-loop flux estimator in all floating-point math was a terrible idea:


It looks like the processor utilization is 99%, but it's actually more like 120%. The 10kHz loop took so long that it actually took 120μs to finish each cycle...so I'm not even sure if it really finished or if the interrupt controller just gave up. The flux observer alone took about 80μs to run. I determined that each floating point multiply was taking about 7.44μs, or close to 120 clock cycles at 16MHz. So clearly the floating point math is still being done in software, and it's not really leveraging the hardware multiplier at all to speed things up.

So began a day or two of modifying the code to run faster without actually changing what it does. Software optimization is probably one of the most thankless tasks ever, since you make something work the same way it did before and nobody can see the difference. But I still find it somewhat fun to try to squeeze every bit of time out of a control loop.

First, I now have the ability to stick all my state variables into 32-bit integers and use the extra precision to make up for the lack of floating point capability. For example, instead of Phase A's current being represented in software as 38.7541723[A], it can be 38754[mA]. I don't care about sub-mA precision, and that still leaves me plenty of multiplication overhead. By that I mean I can still multiply the current, which fits in 17-bits of signed int, by up to 15 bits of precision without overflowing the 32-bit signed int.

For example, the floating-point current scaling would have been:

float raw_current, scaled_current;
raw_current = adc_read(CH) - offset;
scaled current = raw_current * 0.0561;

This scaled the raw ADC value to physical units of Amps. But it has more final precision than is really necessary and can be replaced with all integer math:

signed int raw_current, scaled_current;
raw_current = adc_read(CH) - offset;
scaled_current = raw_current * 561 / 10;

Now the scaled current is an integer value in mA. The intermediate precision required is about 22 bits. (12 bits for the raw ADC value plus 10 bits for the scaling operand 561.) The integer division by 10 is fast, and the precision is high enough that the truncated result is still perfectly fine.

After thus converting all the floating-point operations to integer math, the fast-loop consisting of FOC, ADC sampling, and flux observer was down to 50.5μs at 16Mhz:


This would already be good enough to run, but there are several other processing efficiency tricks I had ready to deploy. One obvious target for efficiency improvement is the ADC sampling. The STM32F103, like many other microcontrollers, uses a Successive Approximation Register (SAR) ADC, which is sort-of like a guess-and-check process for converting an analog value to digital representation. Each guess takes time, so many cycles are spent waiting for the conversion to complete. As implemented above, the processor would just sit there waiting for the sample to finish. 

With the ADC settings I have been using, each sample takes 4.55μs. The fast loop samples four analog channels, taking a total of 18.2μs. For most of that time, the processor is waiting for the ADC to finish. It doesn't need to be, though, since the ADC can run on its own and trigger an interrupt when it's done converting. Implementing this was straightforward: I have the fast loop code start the ADC and then have it cycle through five samples on its own. After each sample, the ADC interrupt retrieves the data and moves on to the next sample. While waiting for the ADC, the processor returns to the main loop.

This showed only marginal improvement. The total processor utilization is still about 50%, because the idle time saved by not waiting for the ADC is offset by extra processing time to decide what to do with the data. The result is that each sample now takes about 8μs, four of which is spent converting and four of which is spent decided what the data is and where to put it. The data manipulating part can be completely eliminated by using yet another hardware feature of this processor, the Direct Memory Access (DMA) peripheral. The ADC can tell the DMA to automatically transfer data to a specified memory location, with no processor involvement. This would completely automate the sampling, and bring the total processing time for the fast loop down to about 28μs.

But, there's also another way to squeeze even more horsepower out of the STM32F103. The clock speed can be multiplied by an on-board Phase-Locked Loop (PLL) to up to 72MHz. (Right now, the 16MHz oscillator sets the clock speed directly.) I've never even bothered to try turning on the PLL, but now seemed like a good time to see what it would do. For some yet-unknown reason, I was only able to multiply my 16MHz oscillator by as much as 3.5 (or rather, divide it by two and then multiply it by seven...don't ask). That gives me 56MHz. I'm not sure why it won't go higher than that, but I suspect some hidden clock speed limit on a peripheral. But I tracked down all the obvious ones, and none were overclocked. Anyway, here's what the fast loop processor utilization looks like at 56MHz:


The entire FOC and flux estimator now take only 8μs. The ADC samples still take about 4μs, but the amount of that time spent processing data is greatly reduced. (The sampling time itself is limited by the ADC's peripheral clock speed limit of 14MHz, but the data manipulating time is based on the system clock.) The total processor utilization is now about 20%, leaving room for increasing the fast-loop rate or doing more processing for a closed-loop observer.

For now, though, I'm satisfied that the flux observer will run happily at 10kHz and I'm merging it with the FOC code I already have. Hopefully I will get to test it before Thanksgiving.