Mon Dec 18, 2006
It's time to get working on the changes for the 2007 event since it appears we are going to have one.
Even more good news: I am hearing of more people wanting to enter! Prof. Chris MacNab's engineering classes appear to have two teams entering, Dan Gates of Solarbotics looks like he wants to compete (and Dave Hockeyrink might join up with him) and possibly more! This has inspired me to get started early.
First thing I did was swap out the motors for some faster ones. I changed from the B104 model of the B-series Beetle gearmotor to the B62 model. In 2006 the bot's course and detection were very good but it was only about 80% of the way through the course before the time ended.
Second I started to work on the encoders and PID stuff to drive the new motors.
I finally have an understanding of how PID works. All of the material out there describing it is far more complicated than it should be. However, making it work is still proving quite challenging.
The problem is that the readings from the encoder are noisy and difficult to nail down. Once in a while I will see spikes on the RPM even though I can clearly see and hear the motor is running fairly steadily. When I throw the encoder output at the oscilloscope it shows a fair amount of wobbling but nothing to indicate massive burps.
I needed to see what the robot was getting. I made use of the serial port I built in and had it send the readings to my computer.
Woah.
In order to see that spike the time from one interrupt to the next would have to be very short and, clearly, it would be wrong. So, what is the source of the bad data?
Besides making sure the motors are well filtered with three caps I tried a few other things out such as painting the inside of the gearbox with flat black paint and twisting the signal wires to help prevent them from picking up noise. That didn't seem to make much difference. I need to try something else.
By the way, the image above is a grab of a java program I wrote that displays telemetry sent by my bot to my computer's serial port. Man, is that handy for debugging.
The first obvious thing would be to see what happens when I send in a nice, simple square wave to the input to see what I get. I set up my avr experimenters board and had one of its timers send a 50% duty cycle square wave into the header where I normally plug in the encoder. If all goes well I should get a solid, flat line with no bumps anywhere.
This clearly shows that given such a signal the results are very good. Those little spikes concern me, though. They should not be appearing and yet they are. Extra interrupts?
Next is to try sending a nice sine wave at the input. Since the signal from the encoder is passed through a voltage comparator it's possible that the noise is being generated from that part of the circuit. To test it I figured I would generate a known clean sinewave into the circuit and graph the results. I couldn't be bothered to put together a 5 volt sine wave generator so I fed the above signal in through a single pole RC low pass filter. My scope showed the signal had very nice ramps up and down. But it didn't make any difference in the quality so I guess that part is ok.
I decided before going any further to find out what the heck is causing those spikes. I noticed that values of the spikes are almost exactly one millisecond short or long of the base time. This lead me to believe that the problem wasn't with the interrupts but how I was handling the timers. The fact that they are a millisecond off and are usually short and then immediately long told me everything I needed to know. First a little background on how I am handling it right now.
I keep track of the time since the robot started in milliseconds. Keeping consistent time is very important as accurate readings from the encoders and heading calculations from the gyro depend on it. However, millisecond timing is not enough resolution. For example, the encoders have 32 slots and rotate about 3 hz. At top speed it's only 5 or 6 milliseconds from one to another. That's not enough resolution to make meaningful calculations.
Increasing the timer resolution means that the interrupt timer is happening quite often. So often, in fact, that I wonder if anything else can get done. Don't forget that there is significant processor overhead to save state before an interrupt can be serviced. If this happens too often the usuable work the processor can do is affected.
So, I fake it out.
Since I am using timer 2 to handle time keeping I know that TCNT2's current contents can make up the difference. In this case the system clock is 16 MHz and I am using a prescale of 64 to get the speed down slow enough so that a value of 249 in OCR2 will roll the counter over once every millisecond. Timer 2 is only 8 bits so whatever I put in OCR2 can't be larger than 255.
Whenever the timer rolls over I add one to my variable, globaltimer, that keeps track of milliseconds since poweron/reset.
My encoder is also handled by an interrupt, in this case external interrupt 2. Whenever that happens I multiply globaltimer by 1000 and add TCNT2 shifted left twice to get an accurate reading of microseconds since poweron/reset. (16 MHZ clock with 64 prescale needs to get multiplied by 4 to get microseconds. Shifting left 2 bits is an efficient way to do this).
What was actually happening here is a classic example of synchronization. TCNT2 is getting incremented no matter what is happening. When a full wave from the encoder goes by interrupt2 is called. Since I prefer to use the SIGNAL macro I know that if TCNT2 were to roll over while the procesor was currently servicing interrupt 2 global timer would not get incremented until AFTER my routine was finished. When that happened it would usually get short 1 millisecond followed by getting long 1 millisecond for the next interrupt2.
This explanation fits the facts although I am surprised it would happen as often as it does.
The solution turned out to be to check if the OFC2 flag is set inside of my encoder interrupt. If set that tells me for sure that TCNT2 has rolled over but the global timer has not been incremented yet. What I will do in that case is just add 1000 microseconds to the timer and continue on as before. Here is what my output looks now:
That seems pretty solid.
Now let's hook the motors and encoder back up if that any difference in my output:
Not great but better than before and I am very glad to see that the spikes seem to have disappeared in this sample. I can probably live with this noise but those stupid spikes really concerned me. Before the change above the motor would suddenly change speed whenever one of thos spikes came by. If I left it at that then any thoughts of using PID were out the window.
By the way, this graph is somewhat misleading in that the data isn't quite as bad as it appears. I cut off the top and bottom parts of the graph to save space which has the effect of exaggerating the variance. The real range of data here is about 190 to about 210 out of 0 to 255. That's still quite a wide range but not as bad as the graph would indicate. For the sake of curiosity here is the above sample but plotted as a distribution. Since the motor was going constant speed I would prefer the tallest and thinnest possible looking results. In a perfect world it would be a very tall, thin spike over a single value.
Mon Jan 1, 2007
Ok, bored with PID now. I'll come back to it later.
I've decided to make a change to the routine that calculates the change in heading based on the rotation rate read from the gyro. The routine I have is complicated and uses floating point math which I don't like to have inside of an interrupt routine. What I am going to do is throw hardware at it: external eeprom.
To get a sample rate fast enough I use 14 bits from the i2c-based ADC
that reads from the gyro. That gives me an answer ranging from -8192 to
8191. I will just use that to index a pair of bytes in a 24FC1025 1
megabit serial EEPROM. These two bytes will be combined to form a signed
value that will be added to my overall heading each time an interrupt
happens.
Last modified: Mon Jan 1, 2007. Hmmmmmm, WRC Rally marathon.