Showing posts with label Rocketometer. Show all posts
Showing posts with label Rocketometer. Show all posts

Friday, April 24, 2015

Is this a spacecraft?

This is not a pipe. (It is a picture of a pipe)
Is this a picture of a spacecraft?
I have discussed my rocketometer project on this blog in depth before. That's it, the purple board in the picture above. I think I can lay claim to it being a full-blown spacecraft.

  • It has structure. 
  • It has command/data handling. 
  • It has a science payload with multiple instruments. 
  • It has independent power - it is charged from the rocket payload, but carries its own battery and is capable of operating for the whole mission from launch through recovery. In fact, it did operate on its own power during entry and descent. From the point of view of the Rocketometer, the external power system is ground support equipment.
  • It is capable of operating in the vacuum and freefall of space. If it were magically transported into orbit, it would operate for at least a little while (it lacks thermal control). 
  • It lacks propulsion and recover, but so do many spacecraft. 

The biggest strike against it is that it was bolted inside another spacecraft. How many other systems in the rocket would I count as a spacecraft under this same definition?

The other biggest strike is that it has no telecom system. I know of spacecraft which have been flown with transmitters but no receivers, but the only spacecraft I can think of that had neither were things like Echo, Lageos, and Starshine. Do those count as spacecraft?

Note (2025-04-05) The pictured Rocketometer has two solder connections and no other connections on the pin header populated. I have two nearly identical rocketometers, but I care which one actually went to space and is the trophy. One of them has all 8 pins soldered to a pin header, while the other one has never had any pins soldered to it -- all holes are still gold and shiny. Other details like particular capacitors shifted to one side or another (like the resistor to the left of the 32kiHz crystal) match the one with pins. I therefore conclude that the one with pins is the one that flew. This one is marked "2" in yellow ink on the "back" side with the SD card slot.

Monday, March 10, 2014

Rocketometer Flight Data Published

This documents the data produced by the Rocketometer during NASA Sounding Rocket Flight 36.290, 2013 Oct 21.

I was getting sick of looking at this data, unable to fully process it but holding it jealously. This has to change. The data wants to be free. Get it at https://drive.google.com/#folders/0B_jwoDhgAF3cNXo0OG5MS2RoM3c

Monday, October 21, 2013

IT WORKED!!!

I turned the Rocketometer over to the rocket guys back in July, and had no contact with it until after the flight today. It had been exposed to every environment it was going to see except for vacuum, but I still had no confidence that it would work.

Well, it did.

The first time I stuck the SD card into my computer after the flight, it said "what card?"

 That was disappointing.

 It took a few minutes for the computer to recognize the card, but when it did, I saw that it had recorded 419 MiB in 66 files. One of the last was also the longest, so I ran it through my quick extraction program, then through IDL, and saw the characteristic acceleration pulse during first and second stage.

The first thing I did after that was press the Victory button on my phone:

 No one else in the lab either heard that or got it, so I had to shout, "It Worked!!!".

Now I have to analyze the data...



Minimum mission success achieved!

At about 12:01:12MDT today, the Rocketometer achieved minimum mission success by riding above 100km and therefore reaching space.

It will be some time yet before I can recover the device to see that it worked, which will represent full mission success.

Saturday, October 12, 2013

The Curiously Recurring Template Pattern

Go look up on Wikipedia what it is. I am going to talk about how I am having to use it.

I was doing fine with the Rocketometer analysis code in C++, using the NewMat library to handle matrices, with a Quaternion layer on top that I wrote myself. After five days of doing battle with various things, I finally got something that worked, but I was curious if this was the "best" way to do it. The C++ Standard Template Library didn't have anything directly related to matrices. The Boost library had a section called uBLAS, but the documentation for it kind of de-recommended itself. It suggested several alternatives, and the one that looked best is called Eigen.

Eigen is interesting in that it is entirely header files, containing almost all of its code in C++ templates. Templates are cool, mostly because when they are instantiated, the compiler gets to see the code in the context that it is used, and gets to inline and optimize it there. Specifically, Eigen contains a dynamic-sized matrix, but also is a template for fixed-sized vectors and matrices. I want to use these as much as possible because all vector sizes used in Rocketometer data analysis are known at compile-time, so the compiler can unroll loops and so on to best optimize the code.

However, templates do not mix with virtual methods, so I had to figure out how to make that work, since I used virtual methods to implement the physics model.  I had code that looks like this with NewMat:

class SimModel {
protected:
  /** Physics function. Calculates the derivative of the state with respect to time
  virtual ColumnVector fd_only(double t, const ColumnVector& x)=0;
  /** Measurement function. Calculates the measurement from the given state and time   */
  virtual ColumnVector g_only (double t, const ColumnVector& x, int k)=0;
  /** Physics function with process noise. Uses fd virtual function to calculate physics, then adds process noise.*/
  ColumnVector fd(double t, const ColumnVector& x,        const ColumnVector* v);
public:
  /** Measurement function with measurement noise. Uses g virtual function to calculate measurement, then adds measurement noise.   */
  ColumnVector g (double t, const ColumnVector& x, int k, const ColumnVector* w) {ColumnVector result=g_only (t,x,k);if(w)result+=*w;return result;};
};
 
But I wanted to adapt that to use Eigen, specifically with the fixed-length vectors, since the size of the state vector is determined by the problem and known at compile time. That means that ColumnVector has to go, to be replaced by Matrix<double,n,1> where n is a template parameter determining the size of the state vector. But what about the measurement? The purpose of the k parameter to g_only is to tell which of several kinds of measurements to use. For instance, in the Rocketometer problem, we have a measurement vector coming from the inertial and magnetic sensors, treated as a single 9-element vector. We also have measurements coming from radar or equivalent, treated as a 3-element vector. So, we need a template function g_only, which generates either a 9-element vector or a 3-element vector. You can't do that and have it be virtual, too. Basically, virtual functions are a runtime binding issue, while templates are a compile-time binding. So, I can't have a virtual g_only function, callable by the base class g function.

Enter the Curiously Repeating Template Pattern (CRTP). As it happens, this is something that I read about just a few days ago, just reading up on the C++ language in general. For us, the pattern goes something like this:

template<int n, class Derived> class SimModel {
public:
  template<int m> Matrix<double,m,1> g (double t, const Matrix<double,n,1>& x, int k, const Matrix<double,m,1>* w) {
    Matrix<double,m,1> result=static_cast<Derived*>(this)->template g_only<m>(t,x,k);
    if(w)result+=*w;
    return result;
  };
};
 
Note that g_only isn't even defined in this class template, only used. In fact, one of the weaknesses of CRTP is that it implies definitions without expressing them, so it is hard to document. Also note that extra template keyword there after the arrow operator. See here for details.

The derived class then looks like this:


template<int n> class RocketometerModel: public SimModel<n,class RocketometerModel<n>> {
  template<int m> Matrix<double,m,1> g_only(double t, const Matrix<double,n,1>& x, int k);
};
 So what happens is that the compiler
  1. Parses the template for SimModel, but doesn't compile it, because it's a template, not actual code yet. Therefore it doesn't matter that g_only is undefined yet.
  2. Parses the template for RocketometerModel, and again doesn't compile it.
  3. Parses the main code, compiling as it goes along until it hits RocketometerModel. It instantiates and compiles RocketometerModel, in the process instantiating and compiling SimModel.
  4. When SimModel is being instantiated and compiled, it has a call to RocketometerModel g_only, but this is ok, since that is available already, since step 2 has already happened.
Now the derived class might not be a template, it might be an actual class. In this case, the base class is instantiated and compiled with the derived class. In either case, everything is available before it is used, even though the code might look otherwise.

Now this part I will write in bold, so that Google can see it. The other curiously repeating template pattern is having to use the word .template when using (not defining) a template member function. This solves the error invalid operands of types ‘<unresolved overloaded function type>’ and ‘int’ to binary ‘operator<’ .

It appears that there is a weakness in the C++ operator precedence table, such that in certain cases it can't distinguish the opening angle bracket of a template parameter set from a normal compare-for-less-than. In order to disambiguate, you throw in the word template after the dot (it will also work after a pointer arrow -> if you are using one of those). I don't understand it completely myself, but Eigen uses this itself, which is how I found out about how it works in the first place.

I am gradually coming to the conclusion that Java did it right, with generics which are real compiled code. Java generics are enabled by the "everything is an object" model in which all objects descend from a common class. I am also beginning to think that Java did it right with operator overloading. Operator overloading, even when fully appropriate, like defining * to mean matrix or quaternion multiplication, is fun to use but a nightmare to implement. And, if it is implemented wrong, it might be left to the user to find out when he tries to do something the implementer did not foresee.

All in all, I give Eigen 4/5, especially recommended for new projects, but not for converting old projects. The biggest advantage is speed. What took IDL over an hour, took Java and C++ with NewMat about 4 minutes, but takes Eigen only 20 seconds. Also, templated matrix and vector sizes are nice, because they resolve matrix size mismatches at compile-time. Finally, zero-based component indexing is what I expect, and the reason I don't suggest converting old projects from NewMat. Also be aware that the Eigen quaternion library uses the convention \(\vec{v}_{to}=\mathbf{p}\vec{v}_{from}\mathbf{p}'\), which is fine and internally consistent, but not consistent with the math I had seen for quaternion derivatives. As a consequence, my code is liberally festooned with q.conjugate() and in some places q.conjugate().conjugate(). It's almost a case of two wrongs making a right, but not quite.

Thursday, October 10, 2013

Calibration

It's kinda weird, but it turns out that C++ is the best language for processing Rocketometer data. There is a cool library NewMat which creates appropriate operator overloads to do matrices in C++, and I have extended it to include quaternions. C++ was doing in minutes what it was taking IDL hours. However, it took me 5 days to translate from IDL to C++, so I had better process a lot of data to ever get that time back.

The time that the Rocketometer spends in zero gravity may be the most valuable calibration time I ever get. Zero acceleration, zero rotation, and I expect a wide range of temperatures.

Remember that the goal of this is to get something into space, but that goal requires no further effort on my part to achieve. The secondary goal is to be able to calibrate the data and report something useful to Tom. The long-term goal though is to measure the track of Space Mountain.

So I am putting something in Space to get it ready for a roller coaster.

That made me laugh.

Wednesday, July 31, 2013

Rocketometer has been integrated

The rocketometer is now an integral part of the rocket payload
Attached to the CCD heater card

In the control section card cage



And here we have a short video demonstrating the size of the Rocketometer:

Wednesday, June 26, 2013

Unattended running

The Rocketometer has been running unattended now for several hours. For instance, last I checked it was just over 7 hours, and had generated 77 files of 4MiB each. This represents a data rate of about 12kiB/second. At this rate, the device will fill up in about 350 hours, or 14 straight days.

So, I asked about which direction would be vertical when the Rocketometer is installed in the rocket. It is vertical when accelerometer reads most strongly in the  +Z direction. Coincidentally, this means that the interface is on the bottom, and the lights will be hidden.

I put in a piece of code which detects when the Z axis is stronger than the others. Mindful of the Genesis reentry failure, I put in code such that if the board is either +Z or -Z, it will decide that it is vertical. I can't test that Z is correct, but I can observe the board in place in the control section and visually confirm that Z is correct.

Now this piece of code will just reduce the rate of readout from once every 3 ms to once every 30ms, still quite quick, but only 1/10 the data rate. Even if it is wrong, we will still get some data.

I will need a log of when the CCD board is powered in order to match up non-flight measurements with anything, but the flight should be either the last or second-to-last record on the board.

For the unattended run, the system will be set to chunk its data into 1GiB pieces, and use only the last digit of the 4-digit number for chunks in a run, reserving 3 digits for runs. I do not expect the Rocketometer to be power-cycled more than 1000 times.

Monday, June 24, 2013

Bugs crossing Boundaries

I am getting the Rocketometer ready for delivery. The hardware appears to be fine, but the software had one particularly weird bug -- the name of the FAT volume on the SD card was changing. This meant that something was poking into the root directory block where it did not belong, which meant that it could be anything.

As it turned out, it was the packet buffer becoming full. Now how in the world did that corrupt the file system buffers? As it turned out, the file system by design wasn't using buffers, in order to save memory.

An SD card is addressed a sector at a time. Data can only be read and written in full sectors. So, if you want to change a single entry in the root directory or file allocation table, you have to read the whole sector into memory, change the bits you want, then write the whole sector back. Now the root directory and file allocation table only needs to be changed when the file is written, and in this case there is already a buffer which has just been used, so I foolishly decided to have the code use this.

In the mean time, this same buffer was part of the circular buffer accumulating packets. If the buffer ever filled up, new bytes would be rejected, and the head pointer would not move. If you start to write a packet to an already full buffer, nothing gets written, and when you finish, the packet length seems to be zero. Now the fun begins.

Upon finishing a CCSDS packet, the code counts how many bytes have been written, subtracts 7 because that's according to the protocol, then goes back and changes the buffer. In the current case, since no data had been written, it actually changed marked data.

Now we combine this with the fact that the interrupt task was filling the buffer. Here then is the final sequence:

  1. The buffer is full. No more bytes can be written.
  2. The main loop detects this, and starts to drain the buffer and write it to the SD card. It writes the packet, then reads the root directory sector, changes it, then writes it back.
  3. During the write, an interrupt fires.
  4. The interrupt task writes another packet. The bytes are dropped on the floor.
  5. The interrupt task finishes the packet. The calculated length is zero, since no new bytes were written
  6. The length to be written is -7, 0xFFF9
  7. The length is written to the buffer, but because of timing, it writes it over the top of the root directory sector in memory. The root directory is now corrupted.
  8. The interrupt task finishes
  9. The SD writing code writes the corrupted root directory sector back to the card.
  10. The card is now named RKT\FF\F9 instead of RKTO3.
It would be pretty easy for this same process to shoot holes anywhere in the filesystem metadata. This bug is hazardous!

So, to fix the bug, we do the following:

  1. Give the file system its own scratch buffer, and never let it do scratch work with the user buffer. This doesn't actually fix anything, but it does make the bug, and any similar undiscovered bug, less dangerous.
  2. Handle the full buffer case better. When the buffer is full, it throws away the new incoming bytes. Further, it backs the head pointer to the mid pointer, in effect throwing away the fractional packet accumulated before the buffer became full. Even further, since the buffer is now no longer full, to prevent getting the last half of a packet, we keep a flag that says that the buffer was full, and don't accept any more bytes until the flag is cleared by a drain operation.
  3. The interrupt task checks if the buffer is already full. If it is, we don't bother to read the sensors, a time-consuming chore which will just discard the result and take time away from the SD writing routines.
This clears a major bug, perhaps one causing all the observed halts. A bug which punches random holes in the metadata can do anything. It only triggered recently when I cranked the data recording rate to 200Hz, which appears to be just beyond the equipment capability. Previous rocket flights must have run at lower rate and not triggered the bug, or not triggered it as hard.

So how did I find it? Lots of print statements and blinklocks. First, I had the SD driver print out the buffer it was going to write. Then I had the direntry driver (the thing which actually handles the root directory) print out its scratch buffer after it had been read, as it changed each byte in turn, and before it wrote it out. From that I found out that the buffer was changing in between lines. That meant that an interrupt task was doing it. Further puzzling out and seeing 0xFFF9, then counting on my fingers to see what number that is in signed decimal, revealed that it was -7, a key number in calculating CCSDS packet sizes. However, it wasn't writing bytes 4 and 5, where it would be if the packet started at byte 0 in the buffer. Instead it wrote one byte before that, implying that the phantom packet started at -1, a key number in the circular buffer pointers. Once the head pointer is one less than the tail pointer, the buffer is full.

For a while I thought that the code was starting a new packet before it finished an old one, which would result in weird numbers. So, I put in some code which blinklocked the device if a packet had not been finished before the next one was started. That never hit. So, I put in some code which blinklocked when the circular buffer filled up, and that finally hit and proved it all. This interacted weirdly with the overlapping packet checker, so I had to fix a bug there. 

Now the only remaining known weakness is what we do if the first cluster of the root directory is full. We can make a cluster as big as 64kiB, we can handle multiple sectors in a cluster in the root directory, and we can fit 2ki entries into a cluster that big. But, what if we fill it? The file search code will need to know how to go to the next cluster, and the file creation code will need to know how to allocate a new cluster. Or, the format process can write 10000 files then delete them all.

The flight version of the code will not blinklock, but reset, when blinklock() is called. Between that and gathering enough runtime, I can gain some confidence that the device won't lock up.

I set the circular buffer size back to two sectors, down from the 6 I had given it. Next version will put the circular buffer into USB ram to save space for the stack, and allow me to have up to 16 sectors. USB memory doesn't work for some reason... maybe the USB system needs to be on for it? Then we are exchanging memory for power consumption. I know that the system is dropping packets now, more so than it was at 6 sectors.

Tuesday, June 18, 2013

Not another Rocketometer

I got an Arduino Leonardo and a Sparkfun Pro Micro as breadboard models of the Arduino-based Rocketometer. Based on my experience with those, I conclude that the ATMega32U4 and the current bootloader are Not Ready for Prime Time, and Not Ready for Spaceflight. Further, I would have had to write my own USB Mass Storage driver anyway.


Wednesday, May 29, 2013

Yet another Rocketometer

The Arduino Leonardo can effectively be thought of as an Arduino on a chip, except that an Arduino pretty much already is one chip. What makes a Leonardo special is that its core is an ATMega32U4, which has a USB port. The chip can be programmed as normal over ISP, and once it is programmed with the appropriate Arduino bootloader, it can be programmed over the USB port. The Arduino library for Leonardo also supports using the USB as a CDC serial port, and has much other USB support.

The chip only runs at 8MHz. It can run at 16MHz at 5V, but I am planning on running it in the Rocketometer, which only has 3.3V available, which limits the chip to the aforementioned 8MHz.

Now how do I think I can get away with this? Simple: Even with the LPC2148 running at 60MHz, it was spending most of its time waiting for the bits to crawl across the I2C and SPI buses. And how much can we optimize a busy wait loop?

I bought a Leonardo to practice with, and a Sparkfun Pro Micro to practice some more, especially programming the bootloader. I will not be using the 6-pin ISP header on the Rocketometer, but I got the great idea to use the MicroSD sniffer to get at the SPI signals through the Rocketometer MicroSD slot, and I can just hold down the reset button, or solder one wire to the contact of the Reset switch - which reminds me, I should put the solder bridge on the top side of the reset switch. In that case, I can use the solder bridge itself as a temporary terminal, solder a wire to it long enough to program, connect the rest of the SPI signals to the microSD sniffer, then program it with an ArduinoISP.

While I was at it, I also designed a Precision clock around this new chip. It doesn't need a hardware UART to program or talk to the host computer, so no FT232 and no multiplexer is needed. The chip has more pins, so this design doesn't use literally every single possible pin (those two ADC-only pins don't count, since they are not possible to use in this project).

Sunday, April 28, 2013

DirectTask and nested interrupts

One problem with the old rocketometer code is that the sensor readout and SD card writing code were in the same thread, meaning that when the SD card took its endless milliseconds to write the data, the sensors were not being read, leaving an irregular gap in the record. My brilliant idea was to read the sensors in a task at interrupt priority, effectively creating another thread. First effort was with the task manager I described below, which was a dismal failure.

For whatever reason, and perhaps the same reason (see below), the task was not able to read the sensors. I came up with a much simpler task manager with which I am getting incredible accuracy.

I call it the DirectTask manager. Its concept: Rather than using one match channel of the timer and a heap priority queue, we just use all three available match channels (there are four, but the zeroth channel resets the timer every 1 minute). This limits the flexibility enormously, but I only need two tasks. I set up one task to reschedule itself on a regular basis (5ms in my first test) and I use the other task to read the BMP sensor.

However, the sensor readout runs an I2C read, which itself is interrupt driven. The code does not currently support nested interrupts, which means all interrupts are delayed until the current one returns. The I2C state machine was interrupt drive, and its interrupts were getting delayed, including the one which makes the state machine stop waiting forever, so the state machine waited forever.

So, we put in an option to I2C to run without interrupts, instead using a busy loop to check the I2C interrupt status bit, then calling the same state machine driver. We weren't getting anything by being interrupt driven anyway, since we had to wait for the read to finish.

With that, the DirectTask manager worked fine. Maybe the heap task manager would have worked fine too, but this one is simpler.

It takes 641 microseconds to read the sensors. We could probably easily bump the read rate to 500Hz, and maybe to 1000Hz, but this doesn't count anything but reading the MPU and HighAcc sensors.

Wednesday, March 20, 2013

Rocketometer Flight Test 1 and photos

3/16/2013 Flight 1

Site: Alpine Elementary field
Weather: Overcast, calm winds during flight, weather at KCOLONGM64 51.5f 29.97 in 6.7mph 49%RH
Hardware: Rocketometer6050 #2, Estes Star Stryker (modified)

Forgot to pull cap from launch rod before flight. Flew straight, seemed to turn level to ground at apogee, I could see a yellow tinge to the tracking smoke. Parachute ejected but never inflated. Rocket fell free from apogee. Rocketometer was still running after flight. Engine hook unzipped about 1/8 inch to the aft, probably due to ejection. One fin has a cracked glue joint. Minor ding to rocket nose cone due to striking launch rod cap. Rocket could probably be repaired, but not reflown in its current condition.

No video of the flight was captured. I had the camera, forgot to press record.

Rocketometer has one file as expected, RKTO0500.SDS 35,369,984 bytes. Indicates that Rocketometer was running through whole flight.

Switch and card not glued into place. Reset solder bridge in place. Rocketometer ran through flight anyway, indicating that nothing was jarred loose.

Rocketometer on internal power ~11:50am

Nautilus reports weird file sizes for files. fsck.vfat does not report any filesystem errors.

Total packets retrieved: 1,572,103
AD377           STRUCT    = -> <Anonymous> Array[124694]
BMP2            STRUCT    = -> <Anonymous> Array[6234]
DEF             STRUCT    = -> <Anonymous> Array[15]
DUMPDATA        BYTE      = Array[65640]
F               STRING    = 'RKTO0500.SDS'
HMC             STRUCT    = -> <Anonymous> Array[124694]
INF             LONG      =          100
MPU             STRUCT    = -> <Anonymous> Array[1246947]
N_EXTEND        LONG      =       -59604
N_PACKETS       ULONG     = Array[2048]
OUF             LONG      =          100
PKT             STRUCT    = -> <Anonymous> Array[1]
STATUS          INT       =        0
TCC             STRUCT    = -> <Anonymous> Array[68932]
T_PACKETS       ULONG     =      1572103

Greater than 26 minutes of data recorded. Processing took about 14 minutes.
Time stamp difference between first MPU record and last: 1601.1205s (26m41.1205s)
Range zero is 1058s (last top of second before first acceleration)
MPU did saturate during boost


Rocketometer loaded and ready for flight
Flight battery is similar to this, not visible in rocket pictures since it is packed in foam behind the board. Photo from Sparkfun Electronics published under CC BY-NC-SA 3.0


Rocketometer rendering, showing sensor side

Rocketometer rendering, showing interface side

Friday, January 25, 2013

Gem of the Week - the Parallax Propeller

I write this before ever using one, so consider this a review of the concept rather than the implementation. The Parallax Propeller is a microcontroller with a couple of interesting features, and perhaps more interestingly, a couple of intentionally missing features.

First off, it has no peripherals other than 32 GPIO pins.

Secondly, it has no such concept as interrupt.

Thirdly, it has eight independent processors, called "cogs", each with its own memory. Each one can run on its own resources without interfering with any other processor. Each cog is a 32-bit processor, and gets access to 2kiB of ram, shared between code and data.

Fourth, it has a set of resources that all processors can access, called a "hub". This basically consists of more memory and a round-robin memory controller which each cog can access in turn. The hub has 32kiB of ROM with the bootloader, Spin interpreter, and a couple of tables, and 32kiB of RAM.

The missing features are what make the controller interesting. Want an I2C? Write a program for a cog which can bit-bang it. Same for SPI, UART serial, etc. Presumably it could bit-bang low-speed USB, but high-speed would be difficult due to limited processor speed.

Further -- want an interrupt? Too bad. Instead, assign a cog to sleep-wait for the appropriate signal.

Programming such a beast is clearly a different problem than programming an ARM of any flavor. ARMs are all about peripherals, registers, interrupts, etc. Propeller is about bit-banging. Effectively you can use a cog as a soft-peripheral to do basically any digital process.

As I mentioned above, the processor comes with a Spin interpreter. Spin is a custom language for programming Propeller, which gets compiled into byte code and interpreted with the Spin virtual machine. Of course you give up performance, but they used an interpreter in the Apollo Guidance Computer. There, it was for memory saving - a single interpretive instruction could take the place of many instructions in AGC machine language. They gave up time to gain space. Spin could have similar benefits, but it seems like the main purpose is providing a language which natively handles the very different concepts needed to handle a processor as weird as the Propeller.

A Propeller program then consists of a bunch of Spin and machine language routines, all stored in hub RAM and copied from some external source whenever the chip resets. Aside from self-modification or using a cog to bit-bang a memory bus, this is all the space you get. This is a rather tight restriction, in fact less memory than in the AGC. But, they fit a full-blown Kalman filter into that.

Of course I have already designed it into a Rocketometer. It's what I do. One of the great things about the GPIO and bit-banging style of the Propeller is that I can put the SPI bus on the pins which are closest to their targets outside the chip. This makes routing the board MUCH easier.

So I will say for now that Propeller shows a lot of promise. The design is a gem. We will see about the implementation.

Sunday, December 16, 2012

Putting my hardware where my mouth is...

I had been keeping this quiet, but it is the natural culmination of all that I have written on this blog.


At my day job, I work with space projects, but I have only been in the same room as flight hardware once, and that was purely as a tourist, to get my picture taken with it. I have written code that has gone into space, but never touched the hardware that carried it.

In October 2013, that will change. I am building space hardware. In a sense, that has already changed, since I have touched the hardware, but it's not space hardware yet. I have arranged for a version of the Rocketometer to fly on a rocket all the way into space.

The people I work for at my day job run an instrument in space that needs to be calibrated every so often. Every year or so, we fly a sounding rocket with a copy of our instrument, pop it up above the atmosphere for a few minutes, then let it fall back down into the atmosphere and descend on a parachute. It goes into space (well over 100km) but not into orbit.

On the campaign building up the rocket for the last flight this past summer, I was tinkering with my rocketometer (actually the 11DoF) and got to talking to my scientists about it. I told them about my daydream to actually get this thing on the rocket, and they said it was a great idea. Naturally it was far too late to get on board that last rocket, but with this next one I have plenty of time, especially considering that the hardware is finished, and I potentially could fly it now. The baseline mission is just to collect the data as fast as possible, and not bother with any on-board processing. That code was demonstrated with the speed test I did a few weeks ago.

My test plan and to-do list then looks like this:

  1. Adapt the old code to the new hardware. A couple of the I/O lines were reassigned to simplify the board design.
  2. Write an offline Kalman filter to process the data. IDL will work fine for that. Mostly I just need a set of equations of motion that allow the compass, gyro, and accelerometer to calibrate each other.
  3. Calibrate the sensors. I have an old record player with no needle which will be perfect for this. I may be able to use some stuff in the labs at work to help with this.
  4. Do a test flight in a model rocket. These generate a similar scale of forces and rotation rates, just for much shorter durations, seconds rather than minutes. The Rocketometer was designed to be carried in any rocket with a payload section 1" or larger in diameter.
  5. Get USB Bootloader++ working. This is low priority, as I can program the part over serial as I have been doing for a while.
  6. Consider on-board processing of the data.
I will be using the Rocketometer2148 with an MPU6050 6DoF sensor, an ADXL377 analog high-g accelerometer, an AD7991 12 bit ADC to read it, an HMC5883 compass, and a BMP180 pressure/temperature sensor.

With a couple of changes to main.cpp and gpio.cpp to tell it where the sensors and lights are on this board, the thing works! It may also be working at my goal rate of 1000Hz, attributable to a faster SD card, reading the compass only 1 of 10 times that the 6DoF is read, and not reading the HighAcc.

Monday, November 19, 2012

Things that there are never enough of, part 1

There is never enough bandwidth.

I can get an MPU6000 from Newark for about $39, or I can get an MPU6050 from Sparkfun for about $20. The only issue with the 6050 is that it runs on I2C, and maxes out at 400kHz. In burst mode, I can get a byte across in 9 clock cycles. An MPU6050 readout has three 16-bit gyro registers, three 16-bit acc registers, and a 16-bit temperature readout, a total of 14 bytes, plus addressing the part, for 15 bytes. Nine cycles each gives 135 cycles, plus a couple more for starts and stops, call it 140 cycles.

Is this enough? Of course not. There is never enough bandwidth. But is it enough? In 1 millisecond, there are 400 cycles, so in theory the MPU6050 being read at 1000Hz will use up 33% of the available bandwidth.

Now there is also the high-accelerometer, read out using an I2C ADC. The ADC has three 16-bit data registers, so 7 bytes counting address. 7 bytes at 1000Hz, call it 70 more cycles. Now we are at 52% bus usage. We need to throw in a compass read and pressure sensor read every once in a while, but it looks like enough to run the sensor at whatever rate I want.

So maybe there is enough. One way to get more bandwidth is to use the other I2C bus on the chip, but this involves a fairly heavy redesign of the board. We would put the MPU and compass on one bus, and the HighAcc and pressure sensor on the other bus.

Onto the measurements. I put together a program which reads the sensors with no delay, in effect as fast as possible with blocking. The program records the tick count (TC), which increments at 60MHz, before each reading, then reads the ADXL345, HMC5883, MPU6050, and L3G4200D in that order. So, the time spent doing the ADXL345 read is the TC of each HMC packet minus the TC of the corresponding ADXL packet, and so forth. The ADXL345 consistently takes 131 microseconds with a variation of less than 1 microsecond. The HMC takes 483 microseconds. And for the moment of truth, the MPU6050 takes 539 microseconds. All is fine and good, right? Nope. We spend most of our time waiting for the card to write out. On a good write, it takes 9 milliseconds, 18 times longer than an MPU read, to write a sector to the SD card. A lot of that could be gotten around by not busy-waiting for the card to finish.

In any case, there is plenty of bandwidth for the sensors, so there is no point in splitting up the traffic to two I2C buses. Reading twice as many sensors as necessary and producing more data than necessary (the real rocketometer will only carry one set of gyros), it still is reading out at about 300Hz.

Dual (or dueling) gyroscopes

The experiment that I wrote about yesterday was actually carrying two gyroscopes: The one in the MPU6050 discussed then, and the L3G4200D on board the 11DoF. This gyro was connected to the Loginator by SPI running at 1MHz. The 11DoF tab was oriented such that during the South integration, +X was East, +Y was Up, and +Z was South. In the same integration, the MPU tab was oriented such that +X was West, +Y was South, and +Z was Up (not Down as marked on the board -- the silkscreen is wrong, proven by the positive signal on the +Z MPU signal.)

So, we match up axes as follows:

MPU605011DoF
+X-X
+Y+Z
+Z+Y
Therefore we just perform the calculation using +Z on the 11DoF just like yesterday we used +Y.

The result doesn't look so good. First, data from yesterday, showing what a detection looks like:

This shows 1 minute of data from before and after the rotation. The noisy part on both ends is 10 seconds of raw data, and the smoothed bit in the middle shows a 2000-sample (roughly 20 second) boxcar smooth. The white data was with +Y pointing north, and the red data was with +Y pointing south. In that middle region, the difference is due to the rotation of the Earth.

Now, the data from the L3G, also taken yesterday, but not reduced until today.

Since the signals cross, there is no clear detection, even with a 20 second boxcar average. Why not? As it turns out, this sensor was set to 2000°/s, its least sensitive setting, with 1/8 the resolution of the MPU. So,  it's not a fair test.

Sunday, November 18, 2012

Detection of the Rotation of the Earth

Abstract: An MPU6050 6DoF MEMS sensor is used to measure the rotation of the Earth. The device is run pointed north for 1 minute, then south for 1 minute, taking 98samples/s during the runs. Actual rotation difference between north and south is 0.0063837°/s, taking into account the cosine(latitude) effect. Measurement is 0.0064822°/s±0.0015299°/s, representing a clear detection of the rotation.  The MPU6050 gyroscope is extremely accurate, probably sufficient for the Rocketometer mission.

This is also a review of the MPU6050 6DoF sensor. This part has a three-axis accelerometer with ranges ±2g, ±4g, ±8g, and ±16g, and a three-axis gyroscope with ranges ±250°/s, ±500°/s, ±1000°/s, and ±2000°/s. Since the intended mission is flying on a rocket, and certain rockets that may fly with this have been observed to accelerate at around 25g, it will need to be supplemented with a high-accelerometer, but the gyroscope will handle the 4.5Hz rotation expected.

The earth rotates 360° in 24h*60m*60s=86400s, or 1° in 240 seconds, or 0.00416666°/s. The gyroscope's most sensitive setting is ±250°/s and reads out at 16 bit resolution, resulting in 500°/s/65536DN=0.0076294°/s/DN, not quite enough to distinguish earth rotation from a standing stop, but enough to distinguish when one axis is pointing north, then south. Unfortunately, that presumes that the gyro is noise-free, which we will soon see is false.

The current experimental setup is on a breadboard connected to a version 1.1 Loginator by I2C at 400kHz, carefully aligned to true north by the following extremely accurate procedure: Since the walls of the secret underground laboratory are not aligned with true north, I pulled up the Google map of the lab and oriented the map such that the walls on the map were parallel to the real walls. The case of the phone was then aligned to true north. I put down a line of tape to mark this orientation. In this setup, the MPU6050 axes were aligned with +Z pointing down (thus we expect to see the 1g field read negative), the +Y axis pointing north first then south, and the +X axis pointing east first then west.

Of course, all measurements are contaminated with noise. This can often be beaten back by taking many measurements of the same thing. If you take into account a number of simplifying assumptions, the noise of the average of \(N\) measurements of the same quantity is \(\sigma/\sqrt{N}\). In short, if you take 100 measurements of the same thing, the average of them is expected to have 1/10 the noise of the original measurements. My previous efforts have taken data over very long stretches of time, hundreds of samples per second over hours. This time I decided to take data for 1 minute at a time. I sampled at 98samples/s (2samples/s were spent reading the pressure sensor, a topic for another day) for 1 minute with the Y axis pointing north, then 1 minute with the Y axis pointing south.

The estimated noise on each measurement, calculated with the standard deviation of all the measurements, was 11.02DN, or 0.084°/s, about 20 times that of the rotation of the earth, so it is obviously impossible to measure with one sample. But, I took 5880 samples, giving a predicted noise on the average of 0.14DN or 0.001°/s, plenty small enough to measure the rotation of the Earth. But did I? And if I did, why did I fail before?

Data:
Y north: -0.2217195°/s±0.0010972°/s (1σ)
Y south: -0.2282107°/s±0.0010972°/s (1σ)

Difference: 0.0064822°/s±0.0015299°/s (North is greater than South by this much)

Now, what is the expected value? First, is it positive or negative? Relative to inertial space, the device is rotating according to the right-hand rule around the Earth's axis. The device measures a right-handed rotation around an axis as positive, so the device should read more positive when pointing north than when pointing south, as it does.

Also, as mentioned above, the earth rotates at 0.00416°/s, but my Y axis is inclined 40° relative to the earth's rotation axis, so I should only expect to see \(\cos 40^\circ\) as much. Also, I would expect to see twice as much as that, because I am not comparing a standing stop to rotation, but rotation one way to rotation the other way.

Taking all this into account, the predicted measurement is....

0.0063837°/s.

My measurement clearly brackets this. In fact, the measurement is much better than I have any right to expect, being only 0.06σ above the expected value. I would have accepted any measurement with the proper sign and an error of less than 1σ.

Why did I fail before, with measurements taken over hours and hours? Gyro drift. Ideally, the zero point of the measurement would be zero DN, but we are happy if the zero point is just constant. But it's not. Temperature changes and other unmeasured effects cause the zero point to drift, and when measuring for hours and hours, this gyro drift swamps the signal I am trying to measure. To get a good rotation measurement, you want as many samples as possible, but taken over a relatively short time such that gyro drift is small.

So, back to the review. The MPU sensor is kind of noisy, with 11DN noise per sample, but can be read out sufficiently quickly and has sufficiently small gyro drift that it can measure the rotation of the Earth.