Tuesday 19 June 2012

Latency and Timing

My experience with latency and timing is worth a blog post. I have now have a working Android app with acceptable timing issues, but not without some pain. The alpha Android app will be described in my next blog post. A few days ago a friend asked me about the project (alight - I offered an explanation). Her immediate question surrounded the latency and what issues I was having. Was the latency acceptable?

Latency

I am going to start by describing the complex pipeline that starts with air pressure, and ends with the Android device displaying something on the screen and going beep.

1. The air pressure and temperature exists at a point in time around the device. Below is a very close up picture of the MS5611 without the protective cap. This is one that I broke. On the left is the actual sensor, the right is the microprocessor circuitry that is part of the sensor. A particular air pressure does something funky to micro diaphragms in the MS5611. I am not quite sure what micro devices bend, or change resistance or capacitance or whatever, but what does happen is that the sensor part of the MS5611 is virtually instantaneously put into a particular state. (I am guessing in pico or nano seconds). 



2. The PIC Micro-controller (operating at 8 MHz) goes through the loop of:
  • 2a. Triggering a temperature sample on the MS5611.
  • 2b. Waiting up to about 9ms for the temperature to be measured internally in the MS5611's microprocessor 4096 times and averaged (at least I think it is averaged and not some other algorithm). It is possible to select a lower oversampling rate and hence be quicker, but the trade off is accuracy. I think it makes more sense to allow that math to be done in the MS5611 microprocessor rather than do it on the PIC.
  • 2c. Then reading the temperature (negligible)
  • 2d. Then triggering a pressure sample (negligible)
  • 2e. Waiting about 9ms again for the MS5611 to do a 4096 oversample of pressure reading for reading.
  • 2f. Then reading the pressure (negligible)
  • 2g. Then triggering a temperature sample (negligible)
  • 2h. Then calculating the pressure from the read temperature and pressure. To do this also takes bunch of  calibration constants which are read from the MS5611 at startup (negligible on the 8Mhz PIC)
  • 2i. Then formatting and sending the pressure characters to the RN42 bluetooth module over UART. I thought that this process would not take much time, but for some time I was using the microchip c++ function sprintf. This is really expensive on the PIC and took heaps of time so I changed to just pushing a sequence of chars to the UART (for those like 'P', 'R' and 'S') and doing a custom int (32 byte unsigned) to char sequence conversion for the pressure (representing the Hex value of the right five nibbles of the 4 byte calculated pressure). This was much quicker, but it still takes time as the PIC to RN42 bluetooth module connection is working at 58k. I guess that this whole print characters thing happens in a few ms. What is important is that I am pretty sure it happens in less than time it takes for the temp measurement on the MS5611. So when we loop back to reading the temperature from the device we will not be waiting for much of the rest of the 9ms.
  • 2j. Then looping back to 2b (I already trigged the temperature at 2g).

In total this takes just less then 20ms. I was able to get it to less than 19 ms (or about 52 Hz) with some tweeking. For reasons described below I slowed this loop down to exactly 20ms. Note that it is about 13 ms from the time of the pressure measurement 2f, to the time it is sent to the RN42 (2i).

3. The RN42 (when established in a bluetooth SPP connection with the Android device), does its thing in some mysterious way. There is this 'bluetooth stack' thing that each character needs to go through, then over the air via the 2.4GHz bluetooth signal, then back through the bluetooth stack on Android. I am not sure how long that takes. From others tests I am guessing about 20 ms. I might do some more tests on this in the future. [probably not – it seems to work ok]

4. The characters then pop out of the bluetooth stack on Android and are read by the Java application code. Here is where things get really unpredictable. I measured the interval between pressure measurements on Android (using System.nanotime(), which I don't really trust but there is nothing better). I found the time intervals to be very erratic. Overall the average time interval is 20ms after I changed it to 50Hz. All pressure measurements are getting through. What I am pretty sure is going on is that the Android threading means that other things, like all that stuff you have going on the background of your PC right now, takes up processor time. At GHz clock speeds the Android device eventually gets around to measuring the data from the bluetooth input stream, sometimes reading a few measurements in a row with less than 1ms in between. My guess is that the average latency here is about 10ms.

5. The vario averaging then adds quite a lot of latency depending on the degree of averaging (damping). With a damping factor of 0.05 at 50Hz it takes about 860ms to reflect 90% of a change. But that is not the latency I am really concerned about. I am more concerned about the latency in 1, 2, 3 and 4.

In total, my guess was that pressure change to pressure available to Java on android latency is about 13 + 20 + 10 ms. So less than 50ms. This should be barely perceptible providing the App can display visually and using audio quickly enough (that story is for the next post).

Timing

Initially I tried spitting out the pressure as quickly as possible from Bluetooth device. I planned to measure the time of the ‘read’ and use this time for the integration of a window of a bunch of pressure measurements. The inconsistency of timing of ‘reads’ of this measurement from Android meant that I was getting screwy results. I tried averaging the pressure time reads, but this was confounded by the few initial measurements that happened at startup. Initialising the vario values worked, but as I tweaked the code on the microchip the pressure intervals would change slightly, screwing up the initialisation.  At the same time I was coding up the display on the android device and it was tricky to relate windows of time to the number of measurements.

The solution to all my timing problems was simple. I implemented a timing loop on the PIC using an interrupt to execute a pressure read and send every 20 ms exactly (50 Hz). The android device just needs to make the same assumption that the pressure measurements are 50 Hz apart, which I now do. Everything with timing is mostly good.

I assume that the internal clock is running exactly at 8MHz. In practice the FRC clock on the PIC is up to 1% or 2% inaccurate. At a particular temperature and operating voltage the clock should be pretty much constant. At 3.0V and 25 degrees Celsius it is most accurate. In practice I do not think that this error is going to matter much. If the vario says 5m/s up when it is really 4.9 m/s I am not sure anyone will care. At some point in the future I could add a crystal to the device to fix this up. Or alternately implement some code on Android to properly measure pressure intervals and add a correction factor. At this stage I am going to do neither.