Here is my best idea yet: Go out and drive the robot manually around a course that can be seen from Google Earth. Record the GPS and compass data with the robot controller in "passenger" mode. Then take that data and run it through a program written on a desktop computer. It is able to run the filter and navigate in faster than real-time. It can calculate what guidance it should have, and what control it would have used. You can then see if it is working, and do corrections and bug fixes off-line. Only when it can drive perfectly offline, do you load the code onto the robot controller and give it the keys.
We have program passenger.cpp, written in C++ just like the robot controller firmware, with much of its code symlinked directly from the YukariII codebase. The only part of passenger.cpp that is different is that the code reads a recording rather than reading live data. When it encounters each packet, it loads it, then calls the YukariII code to act on it. It then prints out the results.
Thursday, June 19, 2014
Tuesday, June 17, 2014
It's really going to work!
I'm worried about it being all on a breadboard and held on with foam tape, but I have some solder breadboards in case it shakes apart in testing. I am still going to use sockets for the Loginator and for the GPS interface, because I am going to want those back.
I still need to think about the "Go" button and the bumper. Watching the replays from last year, it looks like I should do the average G before the start of the race. I turn the controller on, step away from it for a few seconds for it to collect average G, then push the green button when they say go.
Tomorrow early morning I will take the bot to the mall parking lot and drive it manually there, collecting data all the while. I can then use that data to test the guide routine as I develop it. Doing the compass writing on a desktop machine, then porting it to the emulator, worked out well. I am going to do that again with guide().
The current long-range weather forecast is good. I don't want to put a hat on the car if I don't have to.
Sunday, June 15, 2014
Closing the Loop
It is going to work.
My latest great idea is processing the data off-line on my main computer. I can do this in a way such that I can re-analyze it repeatedly, pull out what I want from the data, and do it all without having to re-run the in-motion test.
I took Yukari out again, this time just driving it up and down the street. I ran it down the bike lane line, then over to the center line when the bike lane line ended. I did about 6 laps of this, and got good GPS data the whole time.
Once back, I ran the data through the Passenger program. This code is written in C++, just like the main robot firmware. It reads and parses the recorded data one packet at a time, but does so by reconstructing the variables which the robot originally had on-the-fly. The robot will be able to use this same code to do the same things. And what it is doing is integrating the gyro data, producing a full quaternion of orientation data. Then I transform the nose vector of the robot (it happens to be -Z), then take the atan2 of the z and x components of that vector. This is the gyro heading.
Of course it lacks any absolute reference, and that is what we will use the GPS heading for. If I walk up to the starting line with the robot in GPS lock, in the direction of the first waypoint, the GPS will remember the heading when it stops. I have tested this today.
There was one sick moment when I thought it wasn't going to work. The curve of GPS and gyro headings weren't anything alike. Then I found a bug dealing with going around the corner with TC, which made all the difference. As I was explaining to one of my friends, its like a speedometer. If you know that you are going at 50mph, then in 1 hour you will have gone 50 miles. If your clock calculation says instead that you have travelled for -12 hours, you will calculate that you have gone -600 miles.
I have the gyro turned down intentionally to 100Hz, with a bandwidth of 25Hz. The whole point of the low-pass filter is that the gyro is sampling itself as fast as it can (probably at 800Hz or more), then is taking the average to produce what the gyro would read if it were sampled at a much higher rate and integrated over a much longer time. This smooths out the noise and gives my robot brain less work to do.
Next steps:
My latest great idea is processing the data off-line on my main computer. I can do this in a way such that I can re-analyze it repeatedly, pull out what I want from the data, and do it all without having to re-run the in-motion test.
I took Yukari out again, this time just driving it up and down the street. I ran it down the bike lane line, then over to the center line when the bike lane line ended. I did about 6 laps of this, and got good GPS data the whole time.
Once back, I ran the data through the Passenger program. This code is written in C++, just like the main robot firmware. It reads and parses the recorded data one packet at a time, but does so by reconstructing the variables which the robot originally had on-the-fly. The robot will be able to use this same code to do the same things. And what it is doing is integrating the gyro data, producing a full quaternion of orientation data. Then I transform the nose vector of the robot (it happens to be -Z), then take the atan2 of the z and x components of that vector. This is the gyro heading.
Of course it lacks any absolute reference, and that is what we will use the GPS heading for. If I walk up to the starting line with the robot in GPS lock, in the direction of the first waypoint, the GPS will remember the heading when it stops. I have tested this today.
There was one sick moment when I thought it wasn't going to work. The curve of GPS and gyro headings weren't anything alike. Then I found a bug dealing with going around the corner with TC, which made all the difference. As I was explaining to one of my friends, its like a speedometer. If you know that you are going at 50mph, then in 1 hour you will have gone 50 miles. If your clock calculation says instead that you have travelled for -12 hours, you will calculate that you have gone -600 miles.
I have the gyro turned down intentionally to 100Hz, with a bandwidth of 25Hz. The whole point of the low-pass filter is that the gyro is sampling itself as fast as it can (probably at 800Hz or more), then is taking the average to produce what the gyro would read if it were sampled at a much higher rate and integrated over a much longer time. This smooths out the noise and gives my robot brain less work to do.
Next steps:
- Figure out how to relate the GPS and Gyro headings. I figure something like a Kalman filter, but somehow have the accuracy of the GPS heading be related to the distance travelled since the last turn.
- Close the loop. Use the calculated heading to drive a heading control loop.
- Turntable test. Tell the thing to steer to the north, then while the thing is on a turntable (or in my hands) rotate it back and forth and see how it steers.
- Road test. Write code which drives forward for 10 seconds, while steering to the heading of the bike lane line. After 10 seconds, change the commanded heading to 90deg right for 1 second, then 90deg right again, then let it run for 10 more seconds.
Wednesday, June 11, 2014
Capacitance Loss
I didn't learn this one myself the hard way, but that's only luck. I very well could have.
I use 100nF (why doesn't the nanofarad get any respect?) capacitors all over the place on my boards, mostly as "filter" or "bypass" caps. They sit next to each Vcc pin of each digital IC, and the way I have heard it, they act like a little bitty power supply right next to where you need it. If the part needs a bit more power than average, it will suck charge off that capacitor before it sucks current off the Vcc rail. The part is protected from its own variations in power, and the rail (and therefore the other parts) are protected from this part.
I also use much larger caps as specified for regulators and other such power supply and sensor analog devices. The sensors want a much smoother power supply, and the regulators and such use them for feedback. These tend to be 1μF or even larger. Some parts call for as much as 10μF. The USB spec says no more than 10μF equivalent on a device across Vcc to ground, since too large of a value will draw a large inrush current as those capacitors are initially charged.
I tend to use ceramic caps for all of these, because I am obsessed with board space. Besides, a ceramic cap is pretty close to ideal. No polarization to worry about, etc.
Our good friend over on the EEVBlog demonstrates an issue which does bite ceramic caps -- capacitance change with applied voltage. I had heard of this before, but effectively ignored it as something I couldn't do anything about. Ignore the accent and presentation quirks, I am sure I would sound even worse.
He takes a 10μF 6.3V 0805 ceramic capacitor and demonstrates how to measure it with an oscilloscope and an RC circuit. With a 0-5V square wave, he demonstrates that the cap has its rated 10μF. But, when he changes the signal to 5V-6V, the capacitance drops to less than 5μF.
At first I breathed a sigh of relief when I saw the first demo. No capacitance loss even with a 5V signal. But then I thought about how the second demo applies to my circuits. A bypass cap is run with a large DC bias -- the Vcc voltage. This is directly relevant to my interests.
I guess all I can say is that the designs I am following call for a certain size capacitor. When these were tested by their original manufacturer, they either took these effects into account, or didn't, and just put in a larger cap than they needed when the circuit didn't originally work. If the design turns out to call for only 3μF, they specify a 10μF cap knowing that it will still have at least 3μF under the prevailing conditions.
More details at www.murataamericas.com/murata/murata.nsf/promo_dcbias.pdf .
I use 100nF (why doesn't the nanofarad get any respect?) capacitors all over the place on my boards, mostly as "filter" or "bypass" caps. They sit next to each Vcc pin of each digital IC, and the way I have heard it, they act like a little bitty power supply right next to where you need it. If the part needs a bit more power than average, it will suck charge off that capacitor before it sucks current off the Vcc rail. The part is protected from its own variations in power, and the rail (and therefore the other parts) are protected from this part.
I also use much larger caps as specified for regulators and other such power supply and sensor analog devices. The sensors want a much smoother power supply, and the regulators and such use them for feedback. These tend to be 1μF or even larger. Some parts call for as much as 10μF. The USB spec says no more than 10μF equivalent on a device across Vcc to ground, since too large of a value will draw a large inrush current as those capacitors are initially charged.
I tend to use ceramic caps for all of these, because I am obsessed with board space. Besides, a ceramic cap is pretty close to ideal. No polarization to worry about, etc.
Our good friend over on the EEVBlog demonstrates an issue which does bite ceramic caps -- capacitance change with applied voltage. I had heard of this before, but effectively ignored it as something I couldn't do anything about. Ignore the accent and presentation quirks, I am sure I would sound even worse.
At first I breathed a sigh of relief when I saw the first demo. No capacitance loss even with a 5V signal. But then I thought about how the second demo applies to my circuits. A bypass cap is run with a large DC bias -- the Vcc voltage. This is directly relevant to my interests.
I guess all I can say is that the designs I am following call for a certain size capacitor. When these were tested by their original manufacturer, they either took these effects into account, or didn't, and just put in a larger cap than they needed when the circuit didn't originally work. If the design turns out to call for only 3μF, they specify a 10μF cap knowing that it will still have at least 3μF under the prevailing conditions.
More details at www.murataamericas.com/murata/murata.nsf/promo_dcbias.pdf .
Sunday, June 1, 2014
Worse is Better
The thesis "Worse is Better" states that Simplicity of implementation is the overriding design goal of quality software, to the expense of everything else, even Correctness. I don't know if I agree with it in all cases, and I'm not even sure the author agrees with it in all cases. Be that as it may, I am applying this principle to Project Yukari. The main result of this is that if it is simpler (by which I mean takes less time to implement) then do it that way. If I run into trouble using some advanced language or hardware feature, I will see if I can get around it.
Will it be elegant? No.
Will it be extensible? No.
Will it be an example of how to code, a work of art? No.
Will it work? That is the goal above all else. If the robot navigates the course in three weeks, that is Mission Accomplished, nothing else matters.
Will it be elegant? No.
Will it be extensible? No.
Will it be an example of how to code, a work of art? No.
Will it work? That is the goal above all else. If the robot navigates the course in three weeks, that is Mission Accomplished, nothing else matters.
- The hardware has a great USB interface. I know that the part is capable of simultaneously acting as a Mass Storage Class and a serial port. However, I haven't learned how to code it. I can't use the Sparkfun bootloader since it doesn't work with SDHC cards. I can't use my upgraded Bootloader_SDHC since it is too slow. Therefore I don't use the USB at all, except for power. I load the software with the LPC2148 monitor. This means that since the PWM is using serial port 1, the GPS is forced to use the same port 0 as the bootloader.
- The GPS Rx line doesn't seem to be working on the part that I have. I know it has worked in the past, but I can't get it to work now. As a result, I will let the part speak its native NMEA 4800 baud, 1Hz update.
- The Rocketometer used a timer-interrupt driven task to read the sensors at a precise given cadence. Yukari will instead read the sensors at whatever rate it can, record the timestamp at which it did read, then go with that.
- I am having trouble with reading the serial port, and I suspect it has to do with the interrupt-driven nature. Somehow I am not acknowledging an interrupt, and as a consequence no new interrupts are being generated and no new data is being processed. I will make a new NoIntSerial class, as a replacement for HardwareSerial, which will have blocking write and will use the Rx FIFO as its one and only buffer. This will be fine, as long as the main loop is called sufficiently often. In this case the low bitrate from the first item above works to our advantage. There are only 480 characters per second, about one every 2ms. If we run the main loop at least once every 32ms, we won't drop any serial data. This is only 30Hz.
- There will only be a major loop, with no interrupts. In order, in the main loop:
- The inertial sensors are read
- An inertial packet is written to the card
- Every 10th time, the magnetic sensors are read, and a magnetic packet is written to the card
- The serial port is checked, and while there is a byte present, the port is read and parsed as NMEA. If there is a complete sentence, update the 2D state and GPS heading.
- The heading Kalman filter is run based on the inertial sensors and GPS heading, if new. This is the navigation portion of Navigate, Guide, Control.
- The waypoints are consulted and updated as necessary, and the heading to the correct waypoint is caluclated. This is the Guide portion of Navigate, Guide, Control.
- The difference between the navigation heading and guidance heading is used to calculate the steering. This is the control part of Navigate, Guide, Control.
- The control value is written to the steering PWM.
Remind me why I am doing this again?
By the third day his eyes ached unbearably and his spectacles needed wiping every few minutes. It was like struggling with some crushing physical task, something which one had the right to refuse and which one was nevertheless neurotically anxious to accomplish.-- George Orwell, 1984
Here we are, three weeks until the contest. I have no motivation to do this, but at the same time no desire to call it off. As I hate making decisions by default, that means I have to do it. Since I am unlikely to win the contest, nothing tangible is gained by finishing. Last time, I was tremendously motivated, to the point of using every waking hour, even to the point of taking vacation time to work on the robot. This time I can barely motivate myself to take a weekend to work on it. I just can't get into it.
Yesterday I went to the course. As the newspaper says, "'Tis a privilege to live in Colorado". One benefit is that with 20 minutes of driving and $6.25 to get in the gate, I can visit the course whenever I want. I did so yesterday, with the robot brain on a breadboard. I had the GPS on and locked, connected through the Arduino Nano used as a pure passthrough for its FT232 chip. This was piped through the SiRFDemo program running on Natsumi, and recorded.
Yesterday, however, they were setting up for the Boulder Triathlon. I guess I take a certain amount of inspiration from the runners in that race. Thousands of people join up, most of them with no thought of winning, just finishing. Similarly, I do not plan on winning. I just want to do this so that I can say that I finished something. I am not doing this again by myself. Next time, I plan on being part of a team, preferably as a sponsor/adviser.
In any case, here is the plan. I am going to do this by waypoint navigation. The robot will continuously estimate its 2D position and heading (speed doesn't matter). The position estimate will come straight from the GPS. The heading will be a Kalman filtered result from the GPS heading (to remove gyro biasing) and the gyro (for precision and response time). Perhaps GPS position will work in as well, if I can figure out how.
The hardest part is deciding what to do when we are close to the waypoint. I can easily imagine the waypoint getting inside of the robot's turning circle and the thing chasing its tail forever in vain.
Imagine we have three consecutive waypoints, one at the start line (point 0), one at the first turn (point 1), and one at the second turn (point 2). Also imagine we have just left the start line and are heading towards the first turn. How do we know when to turn? Also, what course do we steer heading towards the point? I think we want to set an imaginary steer-to point (point 1') about 20 meters beyond the actual waypoint 1, on the line from point 0 through point 1. We steer towards 1', while keeping track of the dot product between the vector from point 1 to point 0 and the vector from point 1 to the robot. When this dot-product becomes negative, we have crossed a line perpendicular to the line from 0 to 1. At this time, we set the target point to point 2', 20m beyond waypoint 2 on the line from 1 to 2. We then change all the indexes and steer towards point 2'.
The 20m is arbitrary, but intended to be larger than the GPS error.
Friday, May 16, 2014
June 1 Test
When I last entered the AVC in earnest, I remember there being an April 1 test, where you had to demonstrate the vehicle moving and steering under its own power and control. As it happened, I burned out my helicopter and shifted to the car design after this date, but I got a waiver when I demonstrated the car a mere 1 week before the contest. And we all know how that wound up.
So, I was expecting something like this, this time around. I was watching the Sparkfun web page, expecting to see something there. My phone automatically checks my gmail, so I was watching that. As it turns out, their email got stuck in a spam trap, and I didn't see it until today. So without further ado, I present the June 1 test, completed on February 22, 2014.
Steering is the easy part. It is half of the Control function in the Guidance, Navigation, and Control triad. All the mechanical parts work and if the robot knew where to go, it could control itself and get itself there. The latter is the hard part. I anticipate using a Kalman filter with inputs from the GPS, magnetic compass, and inertial rotation rate sensor. I also am going to add a bumper, so in case it hits anything, it can back up and try again. This is to get around the barrels. I am not currently planning on using the line for line-following robots.
The parts breakdown is about as follows:
Robot chassis - RC car. Includes drive motor, steering servo, battery, ESC, and radio receiver (still carried in robot but disconnected). about $150. Maybe $100 if you count only the parts in the chassis that I actually use, not the receiver, transmitter, or battery charger.
Controller - Kwan Systems Loginator (Logomatic clone) - about $60
Inertial Sensors - Kwan Systems 11DoF - about $40
GPS - GP2106 - about $50
Miscelaneous wires and breadboards - scattered on my workbench.
After last time's debacle, I decided to not go crazy with parts this time. All parts except the chassis are from other projects. I have all these parts already. This is all the hardware I anticipate needing. I use lots of other parts for bench support, such as Arduinos, FTDI chips, oscilliscopes, logic analyzers, computers, etc. None of these are actually part of the robot.
As such, I believe it is a small enough budget that it might fit in the micro class.
As noted in the parts list above, I have chosen to go with the more powerful LPC2148, in the form of the Kwan Systems Loginator. You can read about the design of the Loginator and 11DoF further down this blog. I have repeated all these tests with that controller, but I didn't record them, and they would just look the same as these tests anyway. Video from those tests when it does something more advanced.
April 1 test from last time, completed on April 17
So, I was expecting something like this, this time around. I was watching the Sparkfun web page, expecting to see something there. My phone automatically checks my gmail, so I was watching that. As it turns out, their email got stuck in a spam trap, and I didn't see it until today. So without further ado, I present the June 1 test, completed on February 22, 2014.
This test shows the steering servo connected to an Arduino Nano, powered by the BEC from the ESC, just like the real controller will be used.
Maya wants to help. Some tests are more successful than others...
This test shows the Arduino controlling both the steering and throttle.
The parts breakdown is about as follows:
Robot chassis - RC car. Includes drive motor, steering servo, battery, ESC, and radio receiver (still carried in robot but disconnected). about $150. Maybe $100 if you count only the parts in the chassis that I actually use, not the receiver, transmitter, or battery charger.
Controller - Kwan Systems Loginator (Logomatic clone) - about $60
Inertial Sensors - Kwan Systems 11DoF - about $40
GPS - GP2106 - about $50
Miscelaneous wires and breadboards - scattered on my workbench.
After last time's debacle, I decided to not go crazy with parts this time. All parts except the chassis are from other projects. I have all these parts already. This is all the hardware I anticipate needing. I use lots of other parts for bench support, such as Arduinos, FTDI chips, oscilliscopes, logic analyzers, computers, etc. None of these are actually part of the robot.
As such, I believe it is a small enough budget that it might fit in the micro class.
As noted in the parts list above, I have chosen to go with the more powerful LPC2148, in the form of the Kwan Systems Loginator. You can read about the design of the Loginator and 11DoF further down this blog. I have repeated all these tests with that controller, but I didn't record them, and they would just look the same as these tests anyway. Video from those tests when it does something more advanced.
Tuesday, April 8, 2014
Music Structure and Program Structure
I am a programmer by nature. I learned on my father's knee when I was single-digits old. I passed him in skill when I was a teenager. But, I am not only a programmer. "Specialization is for insects."
One of the other things that I am is an amateur musician. I may not have ever been a good, or even an average, tuba player, but I had fun doing it. I had fun marching, and I had fun watching football and basketball games. I realized early on that I was there not really so much as a musician, but as the carrier of a big metal flag. I was there for visual impact as much as anything. I even achieved a certain amount of fame as the "spinning tuba guy" in the early 2000's. One of my proudest moments was when I was featured in the opening montage of SportsCenter for one whole second.
As I said, I was never very good, but to be even a below-average tuba player, you must acquire certain skills. You have to know how to read music, and I did. I could convert note positions into fingerings and play approximately the right note. I could count or clap out rhythms, and with some practice, I could play the music well enough to fit in with the rest of the band.
One of the things I noticed being both a programmer and musician is that there are some similarities between program flow and music flow. In both cases, the most common flow is from one line or measure to the next, just sequential. Programming has loops, music has repeat signs. Programming has if/then, music has first and second endings. Music is somewhat limited, in that it is deterministic. It doesn't have to deal with input. Therefore, in music, some things have to go together, like multiple endings and repeat signs. Otherwise, the second ending would never be played, and would effectively get optimized out.
I have often wondered if this mapping could be made more complete. There are certain concepts we use in programming that tend not to get used in music, but maybe could, like subroutines. That got me wondering if there were concepts in music that could be mapped to programming, but aren't, and I finally came up with one today: the coda.
In music, you will see markings such as D.S. al Coda, indicating that the flow jumps from here, back (never forward) to a special marking, and then continues from there to another mark coda. This mark is ignored the first time through, but the second time, indicates a jump forward (never back) to the marked coda section.
It first occurred to me today that this is similar to exception handling. When a handled exception is thrown, the flow jumps to the handler. In a sense, this is like taking the coda branch in music.
You could use this in normal flow, throwing an exception when you want to make an early exit from a loop or function. Most functions and loops should have one entry point (enforced by the language, except for Fortran), and one exit point. However, sometimes it is convenient to do an early return from a routine, an early break from a loop body, or an early re-loop in a loop body. Most languages I use support these things with the return, break, and continue statements, respectively. However, there is a good reason for the 'only one exit' rule. Often the routine needs to do some cleanup on exit, closing files, calculating final results from accumulated variables, etc. If you do an early exit, you have to make sure that the cleanup is done appropriately each time you have an exit. If you want to change the cleanup, you have to do that in multiple places. The alternative is that rather than having multiple cleanup-and-exit blocks, you have something like goto end, and then at the end label, you do the cleanup.
However, gotos are to be avoided, for good reason. While the code will work just fine, source code is for communication with humans, not machines. If it were otherwise, we would write code once, keep the binary code, and delete the source. In this case, the humans we are communicating with are most likely our future selves.
Since source code is for humans, semantics matter more than they do if it were just for machines. Machine language doesn't normally have many advanced control structures, just jumps. The code is effectively strewn with goto statements, and the more optimized the code is, the worse this tends to get. Even disassembled code from a modern compiler is hard to reconcile with its source code. Optimizations make everything dramatically out-of-order.
Source code on the other hand, has structured statements rather than a spaghetti nest of goto statements, because they mean something to us. To a machine, a for loop means set up an induction variable, do something to it at the end of each loop, check the loop condition, and go back and do it all again as necessary. To a human, it means run the loop a predetermined number of times. This is why we have the rule to not monkey with the induction variable, as it disrupts the loop semantics. The human reading it will think that this block will run a predetermined number of times, but monkeying with the induction variable disrupts this expectation. In a sense, this is why break and continue are frowned upon, because they also disrupt the expectation.
Random goto statements are just that, random. They have no semantics. They can be used to do anything, so they mean nothing. All structured programming constructs can be done with goto (in fact they have to be in machine language) but not all goto constructs can be represented by structured programming. Sure, there is a proof that you can do it, but that's just because structured programming and goto statements are both Turing complete. I was once dealing with a program, SDP4, which was originally written in Fortran with no structured programming constructs, but pure goto spaghetti. The code arrived to me translated into (Turbo) Pascal, and I translated it further into Java. The previous programmer did a pretty good job of translating most of the gotos into structured constructs, but there is one part where he left in the gotos because the original code was so tangled he couldn't figure it out. He had the luxury of leaving in the goto statements in that case, because (Turbo) Pascal supports it, but I in Java did not. I ended up using a case statement inside a while loop and depending on fall-through. It was technically a structured construct, but just emulated the original goto nest. Vallado points to a solution that involved jettisoning that entire block of code and re-writing it from the original math.
So, while you can write a goto end, it doesn't carry the appropriate semantics. What I want is a statement that means to coda and coda. This carries the semantic that we will shortly be exiting the routine, but that there is a certain amount of cleanup to do. It would be added to the arsenal along with the early return, break, and contiune. One way to implement it in Java is to put the coda in a finally block of a try/catch/finally statement. Then when you want an early cleanup, you throw an exception. However, this violates the semantics of an exception, which is supposed to only be used for an error condition. I once wrote a program which used exceptions in this sense, asked the guys on TheDailyWTF forum what is a better way to do it, and basically got laughed off the forum.
Therefore, I think that in order to capture this semantic, a new statement is needed. Coda is a fine word for it for me, since I am a musician, but maybe there is a better term. Until this construct is added to our arsenal of structured constructs, we are stuck with goto coda, which is better than nothing, because it does capture the appropriate semantics.
One of the other things that I am is an amateur musician. I may not have ever been a good, or even an average, tuba player, but I had fun doing it. I had fun marching, and I had fun watching football and basketball games. I realized early on that I was there not really so much as a musician, but as the carrier of a big metal flag. I was there for visual impact as much as anything. I even achieved a certain amount of fame as the "spinning tuba guy" in the early 2000's. One of my proudest moments was when I was featured in the opening montage of SportsCenter for one whole second.
As I said, I was never very good, but to be even a below-average tuba player, you must acquire certain skills. You have to know how to read music, and I did. I could convert note positions into fingerings and play approximately the right note. I could count or clap out rhythms, and with some practice, I could play the music well enough to fit in with the rest of the band.
One of the things I noticed being both a programmer and musician is that there are some similarities between program flow and music flow. In both cases, the most common flow is from one line or measure to the next, just sequential. Programming has loops, music has repeat signs. Programming has if/then, music has first and second endings. Music is somewhat limited, in that it is deterministic. It doesn't have to deal with input. Therefore, in music, some things have to go together, like multiple endings and repeat signs. Otherwise, the second ending would never be played, and would effectively get optimized out.
I have often wondered if this mapping could be made more complete. There are certain concepts we use in programming that tend not to get used in music, but maybe could, like subroutines. That got me wondering if there were concepts in music that could be mapped to programming, but aren't, and I finally came up with one today: the coda.
In music, you will see markings such as D.S. al Coda, indicating that the flow jumps from here, back (never forward) to a special marking, and then continues from there to another mark coda. This mark is ignored the first time through, but the second time, indicates a jump forward (never back) to the marked coda section.
It first occurred to me today that this is similar to exception handling. When a handled exception is thrown, the flow jumps to the handler. In a sense, this is like taking the coda branch in music.
You could use this in normal flow, throwing an exception when you want to make an early exit from a loop or function. Most functions and loops should have one entry point (enforced by the language, except for Fortran), and one exit point. However, sometimes it is convenient to do an early return from a routine, an early break from a loop body, or an early re-loop in a loop body. Most languages I use support these things with the return, break, and continue statements, respectively. However, there is a good reason for the 'only one exit' rule. Often the routine needs to do some cleanup on exit, closing files, calculating final results from accumulated variables, etc. If you do an early exit, you have to make sure that the cleanup is done appropriately each time you have an exit. If you want to change the cleanup, you have to do that in multiple places. The alternative is that rather than having multiple cleanup-and-exit blocks, you have something like goto end, and then at the end label, you do the cleanup.
However, gotos are to be avoided, for good reason. While the code will work just fine, source code is for communication with humans, not machines. If it were otherwise, we would write code once, keep the binary code, and delete the source. In this case, the humans we are communicating with are most likely our future selves.
Since source code is for humans, semantics matter more than they do if it were just for machines. Machine language doesn't normally have many advanced control structures, just jumps. The code is effectively strewn with goto statements, and the more optimized the code is, the worse this tends to get. Even disassembled code from a modern compiler is hard to reconcile with its source code. Optimizations make everything dramatically out-of-order.
Source code on the other hand, has structured statements rather than a spaghetti nest of goto statements, because they mean something to us. To a machine, a for loop means set up an induction variable, do something to it at the end of each loop, check the loop condition, and go back and do it all again as necessary. To a human, it means run the loop a predetermined number of times. This is why we have the rule to not monkey with the induction variable, as it disrupts the loop semantics. The human reading it will think that this block will run a predetermined number of times, but monkeying with the induction variable disrupts this expectation. In a sense, this is why break and continue are frowned upon, because they also disrupt the expectation.
Random goto statements are just that, random. They have no semantics. They can be used to do anything, so they mean nothing. All structured programming constructs can be done with goto (in fact they have to be in machine language) but not all goto constructs can be represented by structured programming. Sure, there is a proof that you can do it, but that's just because structured programming and goto statements are both Turing complete. I was once dealing with a program, SDP4, which was originally written in Fortran with no structured programming constructs, but pure goto spaghetti. The code arrived to me translated into (Turbo) Pascal, and I translated it further into Java. The previous programmer did a pretty good job of translating most of the gotos into structured constructs, but there is one part where he left in the gotos because the original code was so tangled he couldn't figure it out. He had the luxury of leaving in the goto statements in that case, because (Turbo) Pascal supports it, but I in Java did not. I ended up using a case statement inside a while loop and depending on fall-through. It was technically a structured construct, but just emulated the original goto nest. Vallado points to a solution that involved jettisoning that entire block of code and re-writing it from the original math.
So, while you can write a goto end, it doesn't carry the appropriate semantics. What I want is a statement that means to coda and coda. This carries the semantic that we will shortly be exiting the routine, but that there is a certain amount of cleanup to do. It would be added to the arsenal along with the early return, break, and contiune. One way to implement it in Java is to put the coda in a finally block of a try/catch/finally statement. Then when you want an early cleanup, you throw an exception. However, this violates the semantics of an exception, which is supposed to only be used for an error condition. I once wrote a program which used exceptions in this sense, asked the guys on TheDailyWTF forum what is a better way to do it, and basically got laughed off the forum.
Therefore, I think that in order to capture this semantic, a new statement is needed. Coda is a fine word for it for me, since I am a musician, but maybe there is a better term. Until this construct is added to our arsenal of structured constructs, we are stuck with goto coda, which is better than nothing, because it does capture the appropriate semantics.
Wednesday, April 2, 2014
Regulator
Once again this is hooked up as indicated in the MIC5319 datasheet. I finally am using the switch right, no more useless machine (and useless transistor) for me. As it turns out, that transistor was effectively built-in to the regulator, that's what the EN input is there for. This circuit supplies a nice 3.3V output on VCC to everything else. Previous versions had a current sensor, but this one doesn't, as I haven't gotten one of those to work yet.
It isn't visible in this image, but the bottom rail is GND.
It isn't visible in this image, but the bottom rail is GND.
Charging Circuit
This one is a pretty straightforward hookup of the MCP73831 charging circuit, with one addition: D302 is there so that the charging circuit does not have to treat the whole device as a battery to be charged. If USB is connected, VIN will get the full 5V (minus the voltage drop of a Schottky diode). If not, the battery is used (again minus the voltage drop). Finally, both diodes need to be there so that the battery doesn't feed back to the charger input and try to charge itself. The programming resistor tells the resistor to charge the battery at a maximum of 100mA. This is 1C for a 100mAh battery, the smallest one I have and the one that flies with the Rocketometer.
The status light uses a resistor from the 1.5k pack in the USB connection.
Other versions of this circuit have had a voltage divider between VLIPO and GND so as to allow the MCU to measure the battery voltage. I haven't used it in a while, and it does draw some current, so it is gone from this circuit.
The status light uses a resistor from the 1.5k pack in the USB connection.
Other versions of this circuit have had a voltage divider between VLIPO and GND so as to allow the MCU to measure the battery voltage. I haven't used it in a while, and it does draw some current, so it is gone from this circuit.
Doing USB right
AN11392 - Guidelines for full-speed USB on NXP's LPC microcontrollers (19 Feb 2014). This finally answers all my questions about what all the USB parts are. Based on it, let's review the Loginator USB/charge/power supply section.
We are now using a Micro USB connector with through-hole posts for better mechanical security and easier alignment during soldering. Micro USB takes up less board space and is compatible with the cords used for Android phone charging.
The first thing the app note says is that there must be a 33 ohm resistor on each of D+ and D-. This, plus the internal resistance of the LPC itself, add up to the 90 ohm total impedance required. It implies that there is 12 ohms on each of the pins inside the LPC. This is what I have been carrying all along from the Logomatic design.
Secondly, USB_Softconnect is required if the device is self-powered, or if it takes longer than a certain amount of time to boot up. Since my device can be self-powered and might never boot up and connect, I intend to use this as intended. However, I still like a PMOS rather than PNP transistor. The 1.5k resistor required for softconnect to work is also well-sized for the LED, so I use an array.
The signal lines have capacitors to ground, for exactly the purpose I deduced - shorting high-frequency signals to ground. The app note says that they are not strictly necessary but that it has been reported to improve certain noise issues. I have always built circuits with these included, so I shall continue. These are one of the rare cases where it makes sense to use a capacitor array.
Next, we have R011 and R015, which I completely whiffed on. In my defense, I am not the only one. My design came from the Logomatic, which came from the microbuilder.eu LPC2148 Reference Design which appears to be a mistranscription of the Keil MCB2140 schematic. Even then, the Keil board does not seem to be what was intended.
The idea is that P0.23, USB_ON, is 5V tolerant if and only if the rest of the circuit is powered. So, if it is not possible for the MCU to be off while the USB voltage is present, then you can just plug VBUS into P0.23. However, if the MCU is not powered, the voltage on that pin is supposed to be limited to less than 3.6V. The app note recommends a voltage divider, with 24k on the high side and 39k on the low side, resulting in about 3.09V on the input.
The way both the microbuilder circuit and Logomatic circuits are arranged, that isn't a voltage divider at all, and the pin eats a full 5V. Since the MCU can be turned off (the power switch disables the 3.3V VCC line), this is technically out of spec.
R011 should be connected to the right pin of R015B, and should be closer to 20k. This divider will draw three times as much current as the recommended value, but that is still less than 1mA.
There is supposed to be between 1 and 10 uF between VBUS and ground, visible through regulators and other parts. The real spec is that the inrush current should be limited, but I don't intend to ever submit my device to USB compliance testing, so as long as it works for me, it's fine. There is 4.7uF on the input to the voltage regulator, so I do not inculde any intentional capacitance in this section.
We are now using a Micro USB connector with through-hole posts for better mechanical security and easier alignment during soldering. Micro USB takes up less board space and is compatible with the cords used for Android phone charging.
The first thing the app note says is that there must be a 33 ohm resistor on each of D+ and D-. This, plus the internal resistance of the LPC itself, add up to the 90 ohm total impedance required. It implies that there is 12 ohms on each of the pins inside the LPC. This is what I have been carrying all along from the Logomatic design.
Secondly, USB_Softconnect is required if the device is self-powered, or if it takes longer than a certain amount of time to boot up. Since my device can be self-powered and might never boot up and connect, I intend to use this as intended. However, I still like a PMOS rather than PNP transistor. The 1.5k resistor required for softconnect to work is also well-sized for the LED, so I use an array.
The signal lines have capacitors to ground, for exactly the purpose I deduced - shorting high-frequency signals to ground. The app note says that they are not strictly necessary but that it has been reported to improve certain noise issues. I have always built circuits with these included, so I shall continue. These are one of the rare cases where it makes sense to use a capacitor array.
Next, we have R011 and R015, which I completely whiffed on. In my defense, I am not the only one. My design came from the Logomatic, which came from the microbuilder.eu LPC2148 Reference Design which appears to be a mistranscription of the Keil MCB2140 schematic. Even then, the Keil board does not seem to be what was intended.
The idea is that P0.23, USB_ON, is 5V tolerant if and only if the rest of the circuit is powered. So, if it is not possible for the MCU to be off while the USB voltage is present, then you can just plug VBUS into P0.23. However, if the MCU is not powered, the voltage on that pin is supposed to be limited to less than 3.6V. The app note recommends a voltage divider, with 24k on the high side and 39k on the low side, resulting in about 3.09V on the input.
The way both the microbuilder circuit and Logomatic circuits are arranged, that isn't a voltage divider at all, and the pin eats a full 5V. Since the MCU can be turned off (the power switch disables the 3.3V VCC line), this is technically out of spec.
R011 should be connected to the right pin of R015B, and should be closer to 20k. This divider will draw three times as much current as the recommended value, but that is still less than 1mA.
There is supposed to be between 1 and 10 uF between VBUS and ground, visible through regulators and other parts. The real spec is that the inrush current should be limited, but I don't intend to ever submit my device to USB compliance testing, so as long as it works for me, it's fine. There is 4.7uF on the input to the voltage regulator, so I do not inculde any intentional capacitance in this section.
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
Thursday, February 20, 2014
Once More Unto the Breach...
It's official, St Kwan's has re-entered the robot business. Project Yukari Mk2 will be racing on June 21, 2014.
Friday, October 25, 2013
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...
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:
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.
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
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 {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.
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;};
};
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 {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.
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;
};
};
The derived class then looks like this:
template<int n> class RocketometerModel: public SimModel<n,class RocketometerModel<n>> {So what happens is that the compiler
template<int m> Matrix<double,m,1> g_only(double t, const Matrix<double,n,1>& x, int k);
};
- 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.
- Parses the template for RocketometerModel, and again doesn't compile it.
- 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.
- 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 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.
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.
Saturday, August 3, 2013
Code A113
I was thinking about this at work, explaining it to one of the students I work with. He had never seen Wall-E, so I explained it in a generic and non-spoilery way. Since no one but me reads this anyway, I am going to write it the spoilery way here. You have been warned.
In that story, the systems on Axiom are programmed to look for habitability on Earth, and when it is proven, the ship is to return to Earth. It's an interesting but very expensive backup for just a normal recall message.
So, the pseudocode looks something like this:
while in_space do begin
if(check_earth_habitability()) {
return_to_earth();
}
sleep(1 year);
end
The function check_earth_habitability() is incredibly expensive to evaluate, as it involves sending a spacecraft with several EVE probes all the way back to Earth. It's like us making an implementation of go_to_saturn().
Directive A113 instructed the autopilot to not return to Earth under any conditions. It could have been implemented by cancelling the entire loop (including never sending EVE back to Earth), but instead the implementation looks like this:
while in_space do begin
if(check_earth_habitability()) {
//return_to_earth();
}
sleep(1 year);
end
A normal person would ask "Why does Auto send Eve back to Earth year after year if it has been ordered to not return?" I ask a similar question "Why did they do the code that way?" The thing is, I have done things similar to this: Evaluate an incredibly expensive function, just to ignore almost all of its output.
An example from work is sim_sample. A lot of times I only care about a small part of the result of sim_sample, or I have already calculated the result and could just run viusalize_1c manually. I don't because it is easier to run sim_sample and wait for it to finish than it is to do it efficiently.
In that story, the systems on Axiom are programmed to look for habitability on Earth, and when it is proven, the ship is to return to Earth. It's an interesting but very expensive backup for just a normal recall message.
So, the pseudocode looks something like this:
while in_space do begin
if(check_earth_habitability()) {
return_to_earth();
}
sleep(1 year);
end
The function check_earth_habitability() is incredibly expensive to evaluate, as it involves sending a spacecraft with several EVE probes all the way back to Earth. It's like us making an implementation of go_to_saturn().
Directive A113 instructed the autopilot to not return to Earth under any conditions. It could have been implemented by cancelling the entire loop (including never sending EVE back to Earth), but instead the implementation looks like this:
while in_space do begin
if(check_earth_habitability()) {
//return_to_earth();
}
sleep(1 year);
end
A normal person would ask "Why does Auto send Eve back to Earth year after year if it has been ordered to not return?" I ask a similar question "Why did they do the code that way?" The thing is, I have done things similar to this: Evaluate an incredibly expensive function, just to ignore almost all of its output.
An example from work is sim_sample. A lot of times I only care about a small part of the result of sim_sample, or I have already calculated the result and could just run viusalize_1c manually. I don't because it is easier to run sim_sample and wait for it to finish than it is to do it efficiently.
Wednesday, July 31, 2013
Rocketometer has been integrated
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.
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:
So, to fix the bug, we do the following:
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.
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:
- The buffer is full. No more bytes can be written.
- 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.
- During the write, an interrupt fires.
- The interrupt task writes another packet. The bytes are dropped on the floor.
- The interrupt task finishes the packet. The calculated length is zero, since no new bytes were written
- The length to be written is -7, 0xFFF9
- 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.
- The interrupt task finishes
- The SD writing code writes the corrupted root directory sector back to the card.
- The card is now named RKT\FF\F9 instead of RKTO3.
So, to fix the bug, we do the following:
- 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.
- 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.
- 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.
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.
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).
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).
Monday, May 13, 2013
NTRS is back!
I am happy to say that I was wrong about NTRS. Plus, the EDL kernel has been released (at least is present on NAIF).
I know what we're doing tonight.
I know what we're doing tonight.
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.
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, April 24, 2013
Flight Level 390 and autopilots
Started reading an interesting blog - Flight Level 390 by an airliner pilot who obviously wishes to remain anonymous.
One of the interesting things he pointed to was an incident where the ground crew turned off the pressurization system of a 737, failed to turn it back on, and the flight crew failed three times during the preflight check to turn it back on. As a result, the plane never pressurized on the way up on its flight from Cyprus to Athens, Greece. The oxygen masks in back dropped at 18000 feet, and no communications from the crew happened after the plane flew through 28000 feet. Presumably the crew lost consciousness at this point.
The interesting thing to me is what happened next. The autopilot leveled off at its flight plan altitude of 34000 feet, flew to Athens automatically, and then entered the holding pattern. The program did exactly what it was supposed to do at that point, wait for further instructions, which never came. After flying for almost 3 hours, the plane ran out of fuel and plummeted to the ground.
I find it amazing that the autopilot could be programmed to do that -- not that it is technically difficult, but that they bothered to do it. I always think of an autopilot as more of just a wing leveller and maybe a beam follower. I would have supposed that the plane would get to Athens or wherever the end of its beam was, then keep flying straight and level in the same direction.
If you can program an autopilot to do that, it's not that big of a jump to complete auto control of the whole flight, with the human crew there just to be able to think on their feet if there is trouble. And here then is the interesting part. In my previous essay on the interaction between pilots and automation, I advocated a system where each part of the system was assigned to the component that could do it best. In that case it was lunar landing, where the computer calculated the best course to the landing spot while the human used his vision and judgement to select that spot. In this case, we assign most of the actual flying to the autopilot and assign oversight and handling of emergencies to the human crew. But, here is where the human and autopilot system are anti-complimentary. The human crew will have very little to do during the 999,999 flights in 1,000,000 where nothing happens. A crew member might go his entire career without ever seeing a true emergency, although I have been on enough flights to know that a good chunk of them try to fly over, under, or around turbulence. Is the latter just a matter of dialing a different number into the altitude hold?
Maybe the autopilot could have been programmed to descend to 18000 feet if it detects a pressure loss (the masks dropping) and gets no command inputs for several minutes. Will this incident type ever happen again? Maybe, ever is a long time. It turns out that the system did have a pressurization alarm, which worked, but the alarm was the same sound as one which can only occur on the ground. The crew thought it was the alarm for the thing that can only happen on the ground, and therefore ignored it as a bad alarm.
The point is, you become good at what you practice, and lose ability at what you don't. If a crew is mind-numbed by 10,000 hours of babysitting an autopilot, what makes us think that the humans will actually be able to handle an emergency? Air France 447 seems to have run into just that sort of difficulty. They crew was so used to the autopilot and fly-by-wire handling things, that when those programs shut down, the human pilot didn't remember how to fly that high up, and stalled the plane for 35000 feet while believing that the fly-by-wire would keep the plane from stalling.
But here's the thing. When autopilots work, they work better than human pilots. They don't make the same kinds of mistakes as human pilots. And, even if the system was fully manual, being mind-numbed by holding the yoke in place for 10,000 hours is no better training for an emergency.
I look at how astronauts trained for the Apollo missions and I see how football teams practice. They spend at least ten times as much time practicing as they do in the event. I look at how airliners are flown and I see how baseball players and basketball players and basketball band musicians practice -- by playing the game. By performing. By actually doing it every single day.
The airlines are doing something right. They fly 10 million operations per year, and there have been no fatal airline crashes in the United States since 2009. Maybe this is just as good as it gets.
One of the interesting things he pointed to was an incident where the ground crew turned off the pressurization system of a 737, failed to turn it back on, and the flight crew failed three times during the preflight check to turn it back on. As a result, the plane never pressurized on the way up on its flight from Cyprus to Athens, Greece. The oxygen masks in back dropped at 18000 feet, and no communications from the crew happened after the plane flew through 28000 feet. Presumably the crew lost consciousness at this point.
The interesting thing to me is what happened next. The autopilot leveled off at its flight plan altitude of 34000 feet, flew to Athens automatically, and then entered the holding pattern. The program did exactly what it was supposed to do at that point, wait for further instructions, which never came. After flying for almost 3 hours, the plane ran out of fuel and plummeted to the ground.
I find it amazing that the autopilot could be programmed to do that -- not that it is technically difficult, but that they bothered to do it. I always think of an autopilot as more of just a wing leveller and maybe a beam follower. I would have supposed that the plane would get to Athens or wherever the end of its beam was, then keep flying straight and level in the same direction.
If you can program an autopilot to do that, it's not that big of a jump to complete auto control of the whole flight, with the human crew there just to be able to think on their feet if there is trouble. And here then is the interesting part. In my previous essay on the interaction between pilots and automation, I advocated a system where each part of the system was assigned to the component that could do it best. In that case it was lunar landing, where the computer calculated the best course to the landing spot while the human used his vision and judgement to select that spot. In this case, we assign most of the actual flying to the autopilot and assign oversight and handling of emergencies to the human crew. But, here is where the human and autopilot system are anti-complimentary. The human crew will have very little to do during the 999,999 flights in 1,000,000 where nothing happens. A crew member might go his entire career without ever seeing a true emergency, although I have been on enough flights to know that a good chunk of them try to fly over, under, or around turbulence. Is the latter just a matter of dialing a different number into the altitude hold?
Maybe the autopilot could have been programmed to descend to 18000 feet if it detects a pressure loss (the masks dropping) and gets no command inputs for several minutes. Will this incident type ever happen again? Maybe, ever is a long time. It turns out that the system did have a pressurization alarm, which worked, but the alarm was the same sound as one which can only occur on the ground. The crew thought it was the alarm for the thing that can only happen on the ground, and therefore ignored it as a bad alarm.
The point is, you become good at what you practice, and lose ability at what you don't. If a crew is mind-numbed by 10,000 hours of babysitting an autopilot, what makes us think that the humans will actually be able to handle an emergency? Air France 447 seems to have run into just that sort of difficulty. They crew was so used to the autopilot and fly-by-wire handling things, that when those programs shut down, the human pilot didn't remember how to fly that high up, and stalled the plane for 35000 feet while believing that the fly-by-wire would keep the plane from stalling.
But here's the thing. When autopilots work, they work better than human pilots. They don't make the same kinds of mistakes as human pilots. And, even if the system was fully manual, being mind-numbed by holding the yoke in place for 10,000 hours is no better training for an emergency.
I look at how astronauts trained for the Apollo missions and I see how football teams practice. They spend at least ten times as much time practicing as they do in the event. I look at how airliners are flown and I see how baseball players and basketball players and basketball band musicians practice -- by playing the game. By performing. By actually doing it every single day.
The airlines are doing something right. They fly 10 million operations per year, and there have been no fatal airline crashes in the United States since 2009. Maybe this is just as good as it gets.
Wednesday, April 3, 2013
They took down NTRS
Back when NASA was testing the X-43, they said something that has stuck in my mind ever since. The X-43 is a scramjet test vehicle. The thing is boosted on a Pegasus to near mach 10, then the scramjet is fired for 10 seconds. Afterwards, the vehicle is not recovered. "The only thing we get back from this mission is data."
When you think about it that way, all the robotic space missions are like that. We just get back data. No artifacts, no samples, just bits. We then assemble those bits back into knowledge back here on the ground. When we talk about the $800M SDO mission, we are spending the $800M to get those bits, so they better be valuable. Furthermore, I paid for those bits so I should have access to them.
The NASA Technical Report Server is where the knowledge collected from those bits go. It is now closed.
They claim that it is due to an ITAR review, and that it will be reopened when that is complete. There are over a million papers on NTRS. That is never going to get the kind of review they are implying.
So: For all those mission that only send back data, the only evidence that we have that they even happened is now gone. We spent $XB a year on NASA, and what we paid for is now locked up. This is a bad precedent. Furthermore, the NASA library is now untrustworthy. We build libraries to hold knowledge, and if they close at random without notice, they fail to serve their purpose.
Similarly, I now don't think that the kernels for reconstructed entry of MSL will ever be released.
This is not a good way to start the day.
When you think about it that way, all the robotic space missions are like that. We just get back data. No artifacts, no samples, just bits. We then assemble those bits back into knowledge back here on the ground. When we talk about the $800M SDO mission, we are spending the $800M to get those bits, so they better be valuable. Furthermore, I paid for those bits so I should have access to them.
The NASA Technical Report Server is where the knowledge collected from those bits go. It is now closed.
They claim that it is due to an ITAR review, and that it will be reopened when that is complete. There are over a million papers on NTRS. That is never going to get the kind of review they are implying.
So: For all those mission that only send back data, the only evidence that we have that they even happened is now gone. We spent $XB a year on NASA, and what we paid for is now locked up. This is a bad precedent. Furthermore, the NASA library is now untrustworthy. We build libraries to hold knowledge, and if they close at random without notice, they fail to serve their purpose.
Similarly, I now don't think that the kernels for reconstructed entry of MSL will ever be released.
This is not a good way to start the day.
Tuesday, March 26, 2013
Thursday, March 21, 2013
Pinewood Derby and single-track Gray codes
It's time for the anuual Pinewood Derby at the cub scout pack I serve. Last year I was out of town during the Derby so I didn't participate. This year I am here, so I am making a car. I am not competing, of course, so I am free of certain constraints. Also, I never do any project like this in some ordinary way. As Emeril would say, I always try to kick it up a notch.
The design of the car is a pretty simple one, inspired by my first car back when I was in Cub Scouts. That year, we (me and my parents) thought aerodynamics was the most important thing, and so we designed the car accordingly. And finished dead last. Anyway, I still have fond memories of that car, like trying to microwave the car to dry the paint and charring the center of the block, nailing AA batteries to the back, putting my Battlestar Galactica 2" pilot action figure as a pilot. The general car design had a duckbill front and a turtle back. It was also solid red (what color do we hate?).
This time I have nicely polished the block to the point that it shines. I am going to leave the block completely unmarked.
But, that's not kicking it up a notch. Kicking it up a notch is installing a rocket engine in it. It's installing a camera. It's installing an electric motor. It's putting in a sensor for the car to time itself. I'm going to do the latter.
I have painted the back right wheel half white, including the tread, but with the hub very carefully masked off so as to not interfere with spinning. Over that wheel, I have installed two QRD1114 line-follower sensors. Each includes a near-infrared LED and a phototransistor. The sensor is close enough to visible light that things like white paint are still white and black is still black. The two sensors are placed roughly 90deg apart around the wheel, and about 1-2mm away from the tread. The idea is that the sensors use the half-white wheel as a single-track Gray code encoder wheel. Thinking about it this way: Suppose the car is rolling, the back sensor is well over the white part, but the front sensor just transitioned from black to white. Which way did the wheel turn? It must have been forward. If the back sensor were white but the front sensor changed from white to black, that means the wheel turned backward. Using this, we can construct a directional odometer, and measure the distance the car has moved. By timing the transitions, we can measure the speed of the car as well. By properly using both sensors, we can always know the direction of wheel spin and the orientation of the wheel to within a quarter turn.
So, the car will measure its own speed. We start the car with the wheel just past ticking over, so it has to turn one full time to count a rotation. The wheels are 15mm in radius, so we can measure the distance traveled. The microcontroller reading these sensors has a clock with microsecond precision and 4-microsecond resolution, so plenty enough to measure the exact time of each wheel rotation. Putting these together gives the car's speed, which it will print on an LCD display.
The gem this week is Gray code encoders, in particular single-track encoders. These have one track of code, and several sensors over that track at different angular positions. You need one sensor for every bit of code (two in my case for a 2-bit, 4 position code), but with careful design of the code track, you can use the same track for different bits, by shifting the sensors around the wheel a bit. This can be continued until you use the same track for all the sensors.
The design of the car is a pretty simple one, inspired by my first car back when I was in Cub Scouts. That year, we (me and my parents) thought aerodynamics was the most important thing, and so we designed the car accordingly. And finished dead last. Anyway, I still have fond memories of that car, like trying to microwave the car to dry the paint and charring the center of the block, nailing AA batteries to the back, putting my Battlestar Galactica 2" pilot action figure as a pilot. The general car design had a duckbill front and a turtle back. It was also solid red (what color do we hate?).
This time I have nicely polished the block to the point that it shines. I am going to leave the block completely unmarked.
But, that's not kicking it up a notch. Kicking it up a notch is installing a rocket engine in it. It's installing a camera. It's installing an electric motor. It's putting in a sensor for the car to time itself. I'm going to do the latter.
I have painted the back right wheel half white, including the tread, but with the hub very carefully masked off so as to not interfere with spinning. Over that wheel, I have installed two QRD1114 line-follower sensors. Each includes a near-infrared LED and a phototransistor. The sensor is close enough to visible light that things like white paint are still white and black is still black. The two sensors are placed roughly 90deg apart around the wheel, and about 1-2mm away from the tread. The idea is that the sensors use the half-white wheel as a single-track Gray code encoder wheel. Thinking about it this way: Suppose the car is rolling, the back sensor is well over the white part, but the front sensor just transitioned from black to white. Which way did the wheel turn? It must have been forward. If the back sensor were white but the front sensor changed from white to black, that means the wheel turned backward. Using this, we can construct a directional odometer, and measure the distance the car has moved. By timing the transitions, we can measure the speed of the car as well. By properly using both sensors, we can always know the direction of wheel spin and the orientation of the wheel to within a quarter turn.
![]() |
| I was disappointed to see that Bele and Lokai wear gray gloves. Bele doesn't wear gloves here! |
So, the car will measure its own speed. We start the car with the wheel just past ticking over, so it has to turn one full time to count a rotation. The wheels are 15mm in radius, so we can measure the distance traveled. The microcontroller reading these sensors has a clock with microsecond precision and 4-microsecond resolution, so plenty enough to measure the exact time of each wheel rotation. Putting these together gives the car's speed, which it will print on an LCD display.
The gem this week is Gray code encoders, in particular single-track encoders. These have one track of code, and several sensors over that track at different angular positions. You need one sensor for every bit of code (two in my case for a 2-bit, 4 position code), but with careful design of the code track, you can use the same track for different bits, by shifting the sensors around the wheel a bit. This can be continued until you use the same track for all the 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
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 |
Sunday, March 10, 2013
More on Siding Spring
Subscribe to:
Posts (Atom)










