# Gas station without pumps

## 2015 July 21

### Measuring PteroDAQ KL25Z input impedance

In a series of posts (most recently Measuring BitScope BS-10 input impedance), I’ve been measuring the input impedance of my various ways of measuring AC voltage:

meter Z
DT-830B 0.42MΩ || 31.59pF
DT-9205A 13MΩ || 22pF
BitScope BS-10 oscilloscope 1.025MΩ || 9.8pF

I spent yesterday trying to add the PteroDAQ data acquisition system with the Freedom KL25Z board to that list.

One problem was that PteroDAQ was not designed to report an RMS voltage, but just a waveform, so I modified PteroDAQ to report the mean and standard deviation of a channel (not in the released version yet, as I still have some work to do on the user interface). Note that the mean of a channel is its DC bias, and the standard deviation is the RMS AC voltage. (I’ve never much cared for meters that report RMS AC+DC, which is the root-mean-square voltage without separating AC and DC components.)

A bigger problem is that PteroDAQ can only sample at fairly low frequencies, but the parallel capacitance is expected to be fairly small (pin capacitance for the pin is only about 7pF and the short wiring on the board should only add another couple of picofarads), so the RC time constants will be small. The result is that the low frequencies below the Nyquist frequency will not be much affected by the parallel capacitance, and all I would be able to estimate is the DC resistance of the inputs.

I can take advantage of a trick, however, to get effectively much higher sampling rates: aliasing. Because the input is a sine wave of stable frequency, f, I can sample it at every $\frac{n+\phi}{f}$ seconds and get a waveform that advances by phase $\phi$. I can pick the integer n to be large enough to get a feasible sampling rate while still seeing the whole waveform, especially if I pick the phase advance to be about $\phi=\pi/128$, so that I see all the 256 entries in the function generator’s table.

This trick has the further advantage of presenting the sample-and-hold with about the same value as it sampled on the previous sample, so that I don’t have to worry about the short sampling time not getting fully charged through a high-impedance input.  If I don’t do the aliasing trick, then the short sample time PteroDAQ uses (4 cycles of a 6MHz clock, or 667ns) is not enough to charge the sampling capacitor to the final voltage.

At higher frequencies, even this short sampling time is too long—at 1MHz the voltage changes substantially in 667ns, and the sampling capacitor ends up averaging the value over the sampling interval, which reduces the AC RMS voltage.

I made my measurements with the hardware averaging set to 1×, since averaging multiple readings is a digital low-pass filter that would hide the analog low-pass filter I’m trying to measure.  Because the measurements at 1× are so noisy, I took a large number of  measurements to determine the mean and standard deviation.  The results are still a bit noisy, as I did not realize the importance of having very precise sampling rates initially—if the $\phi$ value is too small, then I have to be careful to include an integer number of periods of the aliased waveform in the averaging to avoid bias, and if it is too large, then the short sample time is not long enough to charge fully and my waveform is not full scale.  A good compromise seems to be to pick n so that the sampling rate is around 5kHz and $\phi=\pi/128$ to get about a 19.5Hz aliased waveform. Only a few of my measurements were done with these settings, so I should probably redo the whole set at some point.

The aliasing trick is not a perfect one—at high frequencies there are a lot of glitches, where it is clear that the sampling did not happen at precisely the place in the waveform desired. This is probably due to jitter in the digital phase oscillators used in FG085, as the PteroDAQ interrupts should come at precise intervals (though the intervals may not be at exactly the frequency desired). The noise is much more of a problem with a high impedance source, as it may take several samples for the sampling capacitor to get back to the correct value.

I measured PTB0 with 1× sampling both directly driven by the FG085 (with 2.9Vpp and +1.8V offset) and through a 100.1kΩ resistor.

The dropoff in voltages at high frequencies with not series resistor is probably due to the averaging of the 667ns sampling time.

The impedance estimate derived from these measurements is pretty solid on the DC resistance, but the parallel capacitance estimate varies depending on how much of the high-frequency measurement I use in the fitting.

My estimate of C is 8pF±2pF, depending on how much of the high frequency data I include in the fit.

Estimating the input impedance of the single-ended pins of KL25Z at 2.5MΩ || 8pF seems pretty good. I’ll have to check the differential inputs separately, as there is no reason to suppose that they have the same input impedance.

I think that the 8pF I’m seeing is mainly the pin capacitance of the PTB0 pin, with a little extra board capacitance. The sampling capacitor is not really measurable here, since it is only connected to the input for very short intervals. To measure the RC time constant of the sample-and-hold circuit, we’d have to vary the sampling time (which is possible on the KL25Z, but which PteroDAQ is not set up to do).

## 2015 July 14

### PteroDAQ v0.2b1 released!

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 21:57
Tags: , , , ,

For the past couple of years, I’ve been using the PteroDAQ data acquisition system that my son wrote for the KL25Z board (a second-generation system, replacing the earlier Arduino Data Logger he wrote). Over the past week, he has been transferring the project to me, and together we did a lot of debugging and enhancements.

Today, we tested the code on the ancient Windows 7 machines in the circuits lab at UCSC (we only have Mac OS 10 and Linux at home), and decided it was ready for a beta release.

The new code is way better than the old code! Here are a few of the bigger changes:

• We now support all the ATMega-based Arduino boards, while still supporting the KL25Z.  The KL25Z board is a better choice for a new user, since it is cheaper and has a much better analog-to-digital converter than the Arduinos, but there are a lot of Arduinos and Arduino clones already in hobbyist hands, and PteroDAQ now works for them with no new hardware.
• Sampling rates have improved enormously, particularly for the KL25Z and the Leonardo Arduino boards, which have USB serial communication without the bottleneck of a UART, but even the UART-based Arduino boards have a decent throughput of 2600Hz for a single analog channel. Leonardo gets 5370Hz, and the KL25Z is limited by the Python program on the host—7.8kHz on the old Windows machines, almost 10kHz on my old MacBook Pro (buffer overflow in the operating system loses some packets after a million samples), and about 19kHz on my son’s Linux laptop (again starting to lose samples after about 1 million).  That’s not fast enough for high-quality audio, but it would do for speech-quality audio. It’s a lot better than the old v0.1 PteroDAQ, which was much more limited by the host, having trouble getting even 180Hz on the old Windows machines.
For short stretches, PteroDAQ can run somewhat faster—I can get 15kHz for about 400,000 samples on my MacBook Pro, which is long enough for a lot of lab experiments.
• We’ve removed the need for PySerial. My son reimplemented the USB serial interface (based heavily on the PySerial implementation), so that we could have everything in a single download with no dependencies outside the standard modules that come with Python.  The implementation may still be a bit inefficient (like the PySerial one), and we are considering working on it.
• Sparklines for the different signals now scroll smoothly even at the highest sampling rates, without taking up much of the host processor.
• Most recent data for each channel is shown numerically next to the channel (which is particularly useful when doing single samples).
• Resizing the window now works well, shrinking and stretching in the appropriate places.
• The GUI now reports errors when PteroDAQ can’t keep up with the requested sampling rate, which makes trading off the sampling rate and averaging easier.

The speed limitations are partly in how fast the Python program on the host machine can accept and process the data, and partly in how fast the KL25Z or Arduino board can do the analog-to-digital conversion. The Arduino boards hit the conversion limits before any of the host machines we used ran into Python limitations, but the KL25Z board with 1× averaging can produce data faster than any of our machines can accept it, so there is still work to be done on improving the efficiency of the Python code.

The software now needs a few users to test it out and find out what problems remain. Some things we won’t be able to do anything about—if Python crashes or the operating system messes up the communication link, there isn’t a lot we can do. Some things are not worth our time (like internationalizing the interface—though we do plan to get unicode characters properly handled in the Notes field—getting that to work in both Python2 and Python3 may be a bit tricky).

I encourage any one who has an Arduino or KL25Z board to try out the new system and tell what problems they have (other than the dire lack of documentation, which I will try to work on with my son over the summer). Ideas for new features are welcome also, though probably won’t come soon.

The software was a complete refactoring of the previous code, with much cleaner interfaces between the modules, which should help with maintenance and extension in future.  I have a huge wish list for new features to add to PteroDAQ, but my son needs to get back to work on the new product for Futuristic Lights, and I need get back to work on my book, so I’ll mainly be putting ideas onto the issue tracker, with the intent of getting back to them later.

## 2015 July 11

### Improving PteroDAQ

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 19:53
Tags: , , , , ,

For the past couple of years, I’ve been using the PteroDAQ data acquisition system that my son wrote for the KL25Z board (a second-generation system, replacing the earlier Arduino Data Logger he wrote).  He has been working on and off on a multi-platform version of PteroDAQ for over a year, and I finally asked him to hand the project over to me to complete, as I want it much more than he does (he’d prefer to spend his time working on new products for his start-up company, Futuristic Lights).

It has been a while since he worked on the code, and it was inadequately documented, so we’ve been spending some time together just digging into the code to figure out the interfaces, which I’ve been adding comments and docstrings to, so that I can debug the code.  Other than the lack of documentation, the code is fairly well written, so figuring out what is going on is not too bad (except in the GUI built using tkinter—like all GUI code, it is a complicated mess, because the APIs for building GUIs are a complicated mess).

The man goal of his multi-platform code was to support Arduino ATMega-based boards and the KL25Z board, while making it relatively easy to add more boards in future.  The Arduino code is compiled and downloaded by the standard Arduino IDE, while the KL25Z board code is compiled and downloaded with the MBED online compiler.  He has set up the software with appropriate #ifdef checks, so that the same code files can be compiled for either architecture.  The knowledge of what features are available on each board and how to request them is stored in one module of the python program that runs on the host computer.  As part of the cleaning up, we’ve been moving some of the code around to make sure that all the board-specific stuff is in that file, with a fairly clean interface.

He believed that he had gotten the new version working on the Arduino boards, but not on the KL25Z board.  It turned out that was almost true—I got the system working with the Leonardo board (which uses USB communication and has a weird way to get reset) almost immediately, but had to do a number of little bug fixes to get it working with other Arduino boards (which use UART protocol and a different way of resetting). It turned out that the system also worked with the KL25Z after those bug fixes, so he was very close to having it ready to turn over to me.

One of the first things I did was to time how fast the boards would run with the new code—the hope was that the cleaner implementation would have less overhead and so support a higher sampling rate.  Initial results on the Arduino boards were good, but quite disappointing on the KL25Z boards, which use a faster processor and so should be able to support much higher speeds.  We tracked the problem down to the very high per-packet overhead of the USB packets in the mbed-provided USBSerial code.  (He had tried writing his own USB stack for bare-metal ARM, but had never gotten it to work, so we were using the mbed code despite its high overhead.)

There was a simple fix for the speed problem: we stopped sending single-character packets and started using the ability of the MBED code (and the Arduino code for the Leonardo) to send multi-character packets.  With this change, we got much better sampling rates (approximate max rate):

channels Leonardo Uno/Duemilanove/Redboard KL25Z 32x avg KL25Z, 1x avg
0  13kHz  4.5kHz  8.2kHz
1 analog  5kHz  2.7kHz  5.5kHz  8.1kHz
2 analog  3kHz  1.9kHz  3kHz  8.1kHz
7 digital  6.5kHz  3.4kHz  8.2kHz

It is interesting that the Leonardo (a much slower processor) manages to get a higher data rate than the KL25Z when sending just time stamps.  I think that I can get another factor of 3 or 4 speed on the KL25Z by flushing the packets less often, though, so I’ll try that.

By flushing only when needed, I managed to improve the KL25Z performance to

channels KL25Z 32x avg KL25Z, 1x avg
0  17kHz
1 analog  6.3kHz  10kHz ??
2 analog  3.3kHz

Things get a bit hard to measure above 10kHz, because the board runs successfully for several hundred thousand samples, then I start losing characters and getting bad packets. The failure mode using my son’s faster Linux box is different: we lose full packets when going too fast—which is what PteroDAQ is supposed to do—and the speed at which the failure starts happening is much higher (maybe 23kHz). In other words, what I’m seeing now are the limitations of the Python program on my old MacBook Pro. It does bother me that the Mac seems to be quietly dropping characters when the Python program can’t clear the USB serial input fast enough.

The KL25Z slows down when doing the 32x hardware averaging, because the analog-to-digital conversion is slow—particularly when doing 32× hardware averaging.  I think that we’ve currently set things up for a 6MHz  ADC clock, with short sampling times, which means that a single-ended 32× 16-bit conversion takes around 134µs and the sampling rate is limited by the conversion times (differential measurements are slower, around 182µs).

There is a problem in the current version of the code, in that interrupts that take longer to service than the interrupt time result in PteroDAQ lying about its sampling rate.  I can fix this on the KL25Z by using a separate timer, but the Arduino boards have rather limited timer resources, and we may just have to live with it on them.  At least I should add an error flag that indicates when the sampling rate is higher than board can handle.

We had a lot of trouble yesterday with using the bandgap reference to set the voltage levels.  It turns out that on the Arduino boards, the bandgap channel is a very high impedance, and it takes many conversion times before the conversion settles to the final value (nominally 1.1V).  Switching channels and then reading the bandgap is nearly useless—the MUX has to be left on the bandgap for a long time before reading the value means anything.  If you read several bandgap values in quick succession, you can see the values decaying gradually from the value of the previously read channel to the 1.1V reference.

The bandgap on the KL25Z is not such a high-impedance source, but there is some strange behavior when reading it with only 1× averaging—some values seem not to occur and the distribution is far from gaussian.  I recorded several thousand measurements with 1×, 4×, 8×, 16×, and 32× averaging:

The unaveraged (1×) reading seems to be somewhat higher than any of the hardware-averaged ones.

I was curious about how the noise reduced on further averaging, and what the distribution was for each of the averaging levels. I plotted log histograms (using kernel-density estimates of the probability density function: gaussian_kde from the scipy python package) of the PteroDAQ-measured bandgap voltages.  The PteroDAQ is not really calibrated—the voltage reference is read 64 times with 32× averaging and the average of those 64 values taken to be 1V,  but the data sheet says that the  bandgap could be as much as 3% off (that’s better than the 10% error allowed on the ATMega chips).

Without averaging, there is a curious pattern of missing values, which may be even more visible in the rug plot at the bottom than in the log histogram.

The smoothed log-histogram doesn’t show the clumping of values that is more visible in the rug plot.

With eight averages, the distribution begins to look normal, but there is still clumping of values.

With 16 averages, things look pretty good, but mode is a bit offset from the mean still.

Averaging 32 values seems to have gotten an almost normal distribution.

Interestingly, though the range of values reduces with each successive averaging, the standard deviation does not drop as much as I would have expected (namely, that averaging 32 values would reduce the standard deviation to about 18% the standard deviation of a single value). Actually, I knew ahead of time that I wouldn’t see that much reduction, since the data sheet shows the effective number of bits only increasing by 0.75 bits from 4× t0 32×, while an 8-fold increase in independent reads would be an increase in effective number of bits of 1.5 bits.  The problem, of course, is that the hardware averaging is of reads one right after another, in which the noise is pretty highly correlated.

I think that the sweet spot for averaging is the 4× level—almost as clean as 32×, but 8 times faster.  More averaging improves the shape of the distribution a little, but doesn’t reduce the standard deviation by very much.  Of course, if one has a low-frequency signal with high-frequency noise, then heavier averaging might be worthwhile, but it would probably be better to sample faster with the 4× hardware averaging, and use a digital filter to remove the higher frequencies.

The weird distribution of values for the single read is not a property of the bandgap reference, but of the converter.  I made a voltage divider with a couple of resistors to get a voltage that was a fixed ratio of the supply voltage (so should give a constant reading), and saw a similar weird distribution of values:

The distribution of single reads is far from a normal noise distribution, with fat tails on the distribution and clumping of values.

With 32× sampling, the mean is 1.31556 and the standard deviation 5.038E-04, with an excellent fit to a Gaussian distribution.

## 2015 June 17

### PteroDAQ bug fix

Now that my son is home from college, I’m getting him to do some bug fixes to the PteroDAQ data acquisition system he wrote for my class to use. The first fix that we’ve put back into the repository was for a problem that was noticed on the very old slow Windows machines in the lab—at high sampling rates, the recording got messed up.  The recording would start fine, then get all scrambled, then get scrambled in a different way, and eventually return to recording correctly, after which the cycle would repeat.  Looking at the recorded data, it was as if bytes were getting lost and the packets coming from the KL25Z were being read in the wrong frame.  As more bytes got lost the frameshift changed until eventually the packets were back in sync.  There seemed to be 5 changes in behavior for each cycle until things got back in sync.

This happened at a very low sampling rate on the old Windows machines, but even on faster machines still happened at a high enough sampling rate.

What the program was designed to do was to drop entire packets when the host couldn’t keep up with the data rate and the buffer on the KL25Z filled up, but that didn’t seem to be what was happening.  The checksums on the packets were not failing, so the packets were being received correctly on the host, which meant that the problem had to be before the checksums were added.  That in turn suggested a buffer overflow for the queuing on the KL25Z board.  More careful examination of the recordings indicated that when we got back into sync, exactly 4096 packets of 10 bytes each had been lost, which suggested that the 5 changes in behavior we saw during the cycle corresponded to 5 losses of the 8192-byte buffer.

We suspected a race condition between pushing data onto the queue and popping it off, so modified the code to turn off interrupts during queue_pop and queue_avail calls (we also made all the queue variables “volatile”, to make sure that the compiler wouldn’t optimize them out reads or writes, though I don’t think it was doing so).  This protection for the queue pop and availability calls changed the behavior to what was expected—at low sampling rates everything works fine, and at high sampling rates things start out well until the queue fills up, then complete packets are dropped when they won’t fit on the queue, and the average sampling rate is constant independent of the requested sampling rate, at the rate that the packets are taken out of the queue.

On my old MacBook Pro, the highest sampling rate that can be continued indefinitely for a single channel is 615Hz (about 6150 bytes/sec transferred).  On the household’s newer iMac, the highest sampling rate was 1572Hz (15720 bytes/sec). (Update, 2015 Jun 18: on my son’s System76 laptop, the highest sampling rate was 1576Hz.)

One can record for short bursts at much higher sampling rates—but only for $819.2 /(f_{s} - \max f_{s})$ for a single channel (8192 bytes at 10 bytes/packet is 819.2 packets in the queue).  At 700Hz, one should be able record for about 9.6376 seconds on my MacBook Pro (assuming a max sustained rate of 615 Hz).  Sure enough, the first missing packet is the 6748th one, at 9.6386 s.

I thought that 2 channels (12-byte packets) should be accepted on my MacBook Pro at (10bytes/12bytes)615Hz, or 512.5Hz, but the observed maximum rate is 533Hz, so it isn’t quite linear in the number of bytes in the packet.  Four channels (16-byte packets) run at 418Hz. There is some fixed overhead in addition to the per-byte cost on the host computer.

There is another, more fundamental limitation on the PteroDAQ sampling rate—how fast the code can read the analog-to-digital converter and push the bytes into the queue.  That seems to be 6928Hz, at which speed the longest burst we can get without dropping packets should be just under 130ms (it turned out to lose the 819th packet at 118.22ms, so I’m a little off in my estimate).  I determined the max sampling rate by asking for a faster one (10kHz) and seeing what the actual sampling rate was at the beginning of the run, then trying that sampling rate and again checking the achieved sampling rate. With two channels, the maximum sampling rate is only 3593Hz, suggesting that most of the speed limitation is in the conversion time for the analog-to-digital converter.

The current version of PteroDAQ uses long sample times (so that we can handle fairly high-impedance signal sources) and does hardware averaging of 32 readings (to reduce noise). By sacrificing quality (more noise), we could make the conversions much faster, but that is not a reasonable tradeoff currently, when we are mainly limited by how fast the Python program on the host reads and interprets the input stream from the USB port.  We’ll have to look into Python profiling, to see where the time is being spent and try to speed things up.

## 2015 May 23

### Interesting observation with PteroDAQ

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 19:35
Tags: , , ,

In the lab reports on the optical pulse monitor lab, one pair of students made an interesting observation: the DC offset of the signal they were observing with the PteroDAQ data acquisition system seemed to be periodic with a period of about 46 seconds. In the electronics course, they learn early on about aliasing, because we usually sample our signals at 10Hz, 15Hz, 20Hz, 30Hz, or 60Hz so that the AC noise picked up and amplified can be aliased to DC and result in a DC offset, rather than making a mess of our signals.  There is a large 60Hz interference in the pulse-monitor lab, as students are generally doing transimpedance amplifiers with a total gain of around 100MΩ, so 10nA of stray current at the input results in a 1V output signal.  The aliasing trick gets rid of that 60Hz signal in the output, as long as the amplifier is not clipping as a result of the interference.

They attributed the periodic offset to slight errors in the 20Hz or 30Hz sampling frequency, so that the 60Hz interference was not being completely canceled, but was being aliased to 0.022Hz.  They did not have a very long recording, so I was not sure whether their explanation was correct—it was plausible, but so was random drift, and I wanted more evidence.

So I set up my own amplifier picking up 60Hz line noise in my house, and I did my own recording at 30Hz:

This waveform looks like a time-reversed and slowed down version of the signal I saw on the oscilloscope.

Sure enough, there is clear evidence of aliasing. The period is about 93.7s. That would be an error of having 1 sample too few every 2811 samples, an error of 356 parts per million. That would be a huge error for the crystal-controlled timer on the KL25Z board (the ABM3B crystal on the FRDM KL25Z board has a ±50ppm error or better—the ABM3B F3C marking does not correspond to the part numbers on the ABM3B datasheet, so it is possible that the F3C codes for a tighter spec). I also considered a software error, setting the counts wrong for the interrupt timer. It is unlikely to be an off-by-one error in the programming, though, since the number of counts with the prescaling of the timer should be 8333 (not near 2811), and the separate timer that reports the intervals is not showing any drift in the timing of the interrupts. So I think that the PteroDAQ is doing 30Hz sampling too accurately to explain the aliasing, if the line frequency is really 60Hz.

The beat frequency is about 0.0107Hz, which would correspond to the 60Hz line frequency actually being around 59.9893Hz (a bit too slow, so that we sample slightly earlier in each period, to get the time reversal). Is that in spec for the power grid? According to “Legal and Technical Measurement Requirements for Time and Frequency” by Michael A. Lombardi  [Measure 1(3):60–69  Sept 2006, http://tf.nist.gov/timefreq/general/pdf/2125.pdf],

The 60 Hz frequency delivered to consumers is sometimes used as the resonator for low-priced electric clocks and timers that lack quartz oscillators. The legally allowable tolerance for the 60 Hz frequency is only ±0.02 Hz, or 0.033 % [17], but under normal operating conditions the actual tolerance is much tighter.

So being off by 0.01Hz is within spec for the line frequency, but the 46s period that the students saw was out of spec, unless the frequency spec is for a long-term average and not for a window of only a few minutes.

Note that the ±50ppm for the KL25Z crystal is good enough for a lot of purposes (like USB signalling), but is not nearly as tight a tolerance as a digital wristwatch, which is typically ±6 ppm (there is no legal requirement in the US for wristwatch accuracy).  Of course, people who really care about the time use GPS, which has an accuracy of 6 parts per 1014 (in the satellite—the accuracy is considerably less by the time it gets to the receiver).

Next Page »