Gas station without pumps

2016 December 11

FET Miller plateau with Analog Discovery 2

Filed under: Data acquisition — gasstationwithoutpumps @ 21:49
Tags: , , , ,

I recently bought myself a birthday present: an Analog Discovery 2 USB oscilloscope. The device normally costs $279, but  I qualify for the academic discount, which brought the price down to $179—a very good deal.  This oscilloscope is better in every way than the Bitscope BS10 that I bought about 4 years ago: functions, resolution, bandwidth, software, … . The Analog Discovery 2 is also cheaper (at least with the academic discount).

I’ve been playing with it a little bit, and I decided to try to reproduce a few of the plots that I have done before.  This post is about creating the plot of the Miller plateau for an nFET (see, for example, More on nFET Miller plateau).  With the Bitscope, I had to filter the 5V power (which was just passed through the device, record many traces, process them with a program I wrote myself to remove the jitter in the triggering, average them, and plot with gnuplot).

With the Analog Discovery 2, I set the built-in power supply to 4V, set the function generator to a 1kHz square wave from 0V to 4V, and put the scope leads to measure Vgs and Vds for the following circuit:

The 100Ω gate resistor is to limit the current from the 1kHz square wave generator, so that the Miller plateau is stretched out in time.

This setup produced a very nice plot without any averaging—the Waveforms 2015 software that comes with the Analog Discovery 2 does the interpolation between samples to dejitter the waveform:

With a 100Ω gate resistance, the plateau is about 146ns long (easily measured with a pair of cursors not shown in this image).

With a 100Ω gate resistance, the plateau is about 146ns long (easily measured with a pair of cursors not shown in this image).

The average voltage is about 2.87V, so the current is (4V-2.87V)/100Ω=11.3mA, and the gate drain charge is 1.65nC, about half the 3.2nC on the data sheet (which was measured with different circuit parameters and is supposed to be a worst-case).  Note that this is a single trace, but multiple traces show almost no jitter, even though we are sampling at the full  100 Msample/second rate.  Averaging the traces would not make much difference in the signal.

If I replace the 100Ω gate resistor with a wire, I get a shorter Miller plateau:

With no gate resistor, the Miller plateau is only about 40.2ns long and averages 2.987V.

With no gate resistor, the Miller plateau is only about 40.2ns long and averages 2.987V.

If we assume 1.65nC in 40.2ns, we get a 41mA current, and (4V-2.987V)/41mA=24.7Ω for the output impedance of the function generator.  I may not have placed the cursors in exactly the same places on both curves, so this is a terrible way to estimate the output impedance of the function generator.

If I look at the square wave with nothing but the scope attached, then I see a voltage of about 4.005V.  With a 100Ω load, I see 3.44V, which gives an output impedance of 16.4Ω.

Of course, the square waves are not completely square, which may affect the Miller plateau measurement at high speed, but the edges are pretty sharp:

The falling edge takes about 50ns.

The falling edge takes about 50ns.

The rising edge takes about 50ns.

The rising edge takes about 50ns.

The edges are limited by the wiring, and I may be able to get better edges by using the BNC adapter board and 50Ω coax cable, rather than the wires provided, but I don’t happen to have any BNC coax cable handy.

2016 October 2

Pulse monitor using log amplifier

I am currently rewriting the optical pulse monitor lab for my Applied Electronics for Bioengineers book, because I’ve moved it earlier in the course, and because I need to remove the calculation of how big the signal should be, which never worked out very well.  There were way too many assumptions in the calculation, and it turned out to be much easier and more accurate to “try it and see” than to do the calculations.

I always do the labs (often several times in different ways) while writing the lab chapters, so I’ve been playing with pulse monitors again.  I’ve decided that I will definitely have the students do a logarithmic transformation of the photocurrent to voltage, as that eliminates the need to guess a resistance value for the first-stage amplifier.  The amplifier can be designed in two stages, with the first one doing the logarithmic conversion and the second one providing sufficient gain over the desired frequency range (where “sufficient” gain is determined by measuring the output of the first stage).

In Using 4¢ diode for log-transimpedance, I talked about using a 1N914 diode as the feedback element of a transimpedance amplifier to get an output voltage as (roughly) the log of the input current.  There are actually two circuits that are subtly different ways to convert the log of current to voltage:

Two circuits that convert the logarithm of light intensity to voltage.

Two circuits that convert the logarithm of light intensity to voltage.

One of the exercises in the book is to compare the circuits (particularly looking at the constraints on what Vref can be in each).

Because I’d not played with it before, I tried using the unity-gain buffer design this weekend, to make sure the circuit worked and to see whether a single-stage amplifier provided enough signal to record with PteroDAQ using the 16-bit ADCs in the Teensy boards. I was also interested in trying out a new design for holding the phototransistor, so that students could experiment with ambient-light pulse monitors and cisillumination, where the illuminating LED is on the same side of the finger as the phototransistor.  In previous years we have always used transillumination, shining the light through the finger, but most wearables use a cisillumination design, because it is mechanically simpler and much cheaper to make.

I played around with a couple of different ways to make a finger cradle and ended up with the following design:

This is cut from a scrap of some softwood (pine? fir?), with a 1" diameter hole for the finger, a ¼" hole at right angles for the wires, and two ⅛" holes between them for the 3mm LED and phototransistor.

This is cut from a scrap of some softwood (pine? fir?), with a 1″ diameter hole for the finger, a ¼” hole at right angles for the wires, and two ⅛” holes between them for the 3mm LED and phototransistor.

The finger cradle worked great for ambient light, but when I tried using it for cisillumination, I could not get it to work—I had plenty of photocurrent, and if I modulated the LED the photocurrent was modulated, but I never saw a fluctuation that corresponded to the pulse in my finger. It turned out that the problem was that the wood is too transparent—I was getting so much light through the wood, that the fluctuation in what was returned from my finger was a tiny fraction of the total light—too small to be visible above the noise floor.

I mentioned this problem to my wife, who suggested I use black electrical tape. I’m not sure she quite understood the problem, since the wood was solid between the LED and phototransistor, but her solution was a good one—I just needed to put the tape in the middle of the block of wood!

By cutting between the two 3mm holes I could put black electrical tape to block the short-circuiting light path.

By cutting between the two 3mm holes I could put black electrical tape to block the short-circuiting light path.

With this design I could get small but recordable signals with either ambient light or cisillumination:

Response of first stage amplifier with an LED desk lamp shining through my left thumb.

Response of first stage amplifier with an LED desk lamp shining through my left thumb.

Output of first stage using an IR emitter on the same side of the left thumb as the phototransistor.

Output of first stage using an IR emitter on the same side of the left thumb as the phototransistor.

Both the plots above are a little misleading, as they were sampled at 60Hz, to alias out any 60Hz or 120Hz interference. With steady bright light from my LED desk lamp, I got similar plots even at 600 Hz, but with compact fluorescent illumination, the signal was buried in 120 Hz interference:

With 600 Hz sampling, it is easy to see the effect of modulated light.

With 600 Hz sampling, it is easy to see the effect of modulated light.

I don’t particularly like the electrical tape and wood approach to making the finger cradle—it is a bit fragile, and the tape needs to be replaced frequently.  Furthermore, ambient light reaches the phototransistor through the sides and ends of the block, unless the whole thing is wrapped in electrical tape. I think that I’ll buy a chunk of black ABS plastic and try making the whole thing out of it. I can probably get a dozen finger cradles out of a 1′ length of plastic, if I don’t mess anything up (and if all my drills work with ABS—I’ve never worked with ABS as anything but Lego bricks).  Another alternative is to go to the hardware store and see how thick the black ABS couplers are—I might be able to make a finger cradle by cutting one in half lengthwise, if the wall thickness is at least 6mm.  I’d still have to add something to keep it from rolling around.

In the past, I’ve thought about using pulsed light to make pulse monitor measurements that are less sensitive to fluctuations in ambient light. The idea is that I would make a measurement with the LED off, turn the LED on, wait a little while for the sensor to settle, then make another measurement. The difference between the measurements would be due just to the LED light, if the ambient light is changing slowly enough not to be very different between the measurements. I’m not sure that this is a good idea (the large change in light level means that there would have to be less analog amplification than for steady illumination, for example), but I was curious how long the on-pulse would have to be to get good measurements.

I hooked the IR emitter up to a hysteresis oscillator, to get nice sharp square waves, then recorded the output of the amplifier with my Bitscope USB oscilloscope. I used the differential probe to get an extra gain of 5 and AC coupling. I recorded 1156 traces, then used my retriggering and averaging program to superimpose and average the pulses. I ended up with between 2300 and 3500 pulses being averaged (depending where on the waveform you look).

The circuit responds fairly quickly to a rising edge, roughly like having an RC time constant of 84.5µs.

The circuit responds fairly quickly to a rising edge, roughly like having an RC time constant of 84.5µs.

The response to a falling edge is slower, roughly like an RC time constant of 207.8µs.

The response to a falling edge is slower, roughly like an RC time constant of 207.8µs.

The response to rising and falling edges is quite different, because of the nonlinear nature of the diode. When we are charging the parasitic capacitance, we have a fairly large photocurrent to do it with, but when we are discharging, the current gets quite small as the voltage across the diode drops. With a linear system, response to a positive step and negative step would be identical, except for the sign.

Both response times are fast enough that the shape we were seeing for the pulse waveforms is due to changes in the opacity of the finger, not due to distortion or filtering in the amplifier.  There is a sudden increase in opacity, followed by a gradual recovery as the blood flows through the capillaries and veins out of the thumb.

If I were to try the difference between LED-on and LED-off measurement, I’d want to have the LED on for at least 400µs, then off for at least 1ms.  (I’d probably do on for 400µs, then off until the next 1/60s tick, making a 2.4% duty cycle for the LED.)

Note also that the 50mV swing here is much larger than the 3mV swing that I got from pulse measurements, so gain would have to be limited to avoid clipping.

2016 September 5

Improved spectral response for dehumidifier noise

Filed under: Data acquisition — gasstationwithoutpumps @ 14:29
Tags: , , , , , ,

In Dehumidifier, I provided spectral analysis of the sound from the 30-pint Whynter RPD-321EW Energy Star Portable Dehumidifier, using my BitScope USB oscilloscope to record the sound and averaging the absolute value of the FFT over 100 short traces. Today I decided to improve the FFT analysis by using the scipy.signal.welch() function to split a longer trace into windows, apply a Dolph-Chebyshev window-shaping function, and average the resulting power spectra. The main improvement here is the use of the window-shaping function.

I used PteroDAQ with a Teensy LC board to record at 15kHz, which was as fast as I could go with 4× averaging communicating with my old MacBook Pro laptop. Based on the Bitscope recordings, though, that should include all the interesting signal, which appears to be limited to about 5.4kHz. I could only record for about 330,000 samples to avoid overrunning the USB buffer and dropping samples, so I also tried 12kHz, which allowed me to run longer. The analysis looked much the same, with no apparent reduction in the noise floor, so I’ll only present the 15kHz results. (Note: when I get a new laptop this fall, I should be able to run at this sampling rate for much longer, which should allow smoothing out more of the noise.)

The dehumidifier looks very much like 1/f noise, with a big spike around 1050Hz. The spikes at 60Hz, 120Hz, 240Hz, and 360Hz may be electrical noise picked up by the sound amplifier, rather than acoustic noise.

The dehumidifier looks very much like 1/f noise, with a big spike around 1050Hz. The spikes at 60Hz, 120Hz, 240Hz, and 360Hz may be electrical noise picked up by the sound amplifier, rather than acoustic noise.

Using Welch’s method and a proper window function results in a much cleaner spectrum than before. I used a 216 FFT window for the analysis, since I had plenty of data, which gives a fairly fine resolution of about 0.23Hz. The 60Hz spike is appearing at 58.36Hz, which is a little troubling, as the PteroDAQ crystal and the power company both have better frequency control than a 2.7% error. The 120Hz, 240Hz, and 360Hz spikes are at most one bin off from where they should be, so I don’t understand the shift at 60Hz.

I checked my code by testing with a recording of a triangle wave, and found that Welch’s method with an 80dB Dolph-Chebyshev window did an excellent job of removing the spectral smear that I was getting by just averaging rectangular FFT windows (my first approach). The noise floor for Welch’s method on those measurements was pretty much flat across the entire spectrum (maybe rising at higher frequencies), so the 1/f plot here is not an artifact of the analysis, but a real measurement of the acoustic noise. Using 60dB window resulted in a higher noise floor, but 100dB and larger did not lower the noise floor, so I stuck with 80dB as a good parameter value. It might be useful to use other values if the FFT window width is changed or the noise in the data is different.

I’ve included the source code for the program, so that others can use it. It does require that SciPy be installed, in order to use the scipy.signal package.

#! /usr/bin/env python3

"""Sun Sep  4 21:40:28 PDT 2016 Kevin Karplus

Reads from stdin a white-space separated file 
whose first column is a timestamp (in seconds) and 
whose subsequent columns are floating-point data values.

May contain multiple traces, separated by blank lines.

Outputs a tab-separated file with two columns:
        the frequencies
        the Fourier transform

"""

from __future__ import print_function, division

import argparse
import sys
from math import sqrt
import numpy as np
try:
    from future_builtins import zip
except ImportError:     # either before Python2.6 or one of the Python 3.*
    try:
        from itertools import izip as zip
    except ImportError: # not an early Python, so must be Python 3.*
        pass

import scipy
import scipy.signal

class Trace:
    """One contiguous trace consisting of two 1D numpy arrays of the same length
        times:  timestamps in seconds
        values: arbitrary floats
      and sampling_freq: the sampling frequency in Hz
    """
    def __init__(self,times,values,sampling_freq=None):
        self.times=np.array(times)
        self.values=np.array(values)
        assert self.times.shape==self.values.shape
        if sampling_freq is None and len(times)>1:
            sampling_freq = (len(times)-1)/(times[-1]-times[0])
        self.sampling_freq=sampling_freq
    def __len__(self):
        return len(self.values)

def read_trace(file, column=2, sampling_freq=None, echo_comments_to=None):
    """Generator that yields traces from tab-separated rows of data.
    Each row contains a timestamp in the first column and any number of subsequent
    columns, one of which is selected to be the values for the trace.
    
    New traces are started whenever the timestamp in the first column decreases.
    
    If sampling_freq is None, the sampling_freq for the
    trace is deduced from the timestamps.
    
    Comments and blank lines are echoed to a file-like object,
    echo_comments_to, if it is not None.
    """
    times=[]    # time values (in seconds) from frst column
    values=[]   # values from the specified column
    
    for line in file:
        line=line.strip()
        if line.startswith('#') or len(line)==0:
            if echo_comments_to is not None:
                print(line, file=echo_comments_to)
            continue
        fields=line.split()
        if len(fields)>=column:
            time=float(fields[0])
            if times and time<times[-1]:
                yield Trace(times=times, values=values, sampling_freq=sampling_freq)
                times=[]
                values=[]
            times.append(time)
            values.append(float(fields[column-1]))
    if times:
        yield Trace(times=times, values=values, sampling_freq=sampling_freq)


def posint(opt):
    """returns int(opt) if that is positive,
            otherwise raises an argparse.ArgumentTypeError
    """
    try:
        x = int(opt)
    except:
        x=None
    if x is None or x<=0:
        raise argparse.ArgumentTypeError("{} is not a positive integer".format(opt))
    return x

def parse_options(argv):
    """Parse the options and return what argparse does:
        a structure whose fields are the possible options
    """
    parser = argparse.ArgumentParser( description= __doc__, formatter_class = argparse.ArgumentDefaultsHelpFormatter )
    parser.add_argument("--sampling_freq","-s", type=float,
            default=None,
            help="""Sampling frequency (in Hz). 
            If none specified, then estimate sampling frequency from the timestamps.
            """)
    parser.add_argument("--window", "-w", type=posint,
            default=(1<<16),
            help="""Max window size (in samples) for FFT.
            Longer traces will be split up into windows of this size
            """)
    parser.add_argument("--column" , "-c", type=posint, default=2,
           help="""What column should be Fourier transformed?""")
    parser.add_argument("--welch" , type=float, default=None,
           help="""Use Welch's method to produce averaged spectrum from windows. 
           Value is dB difference between main lobe and sidelobes for Dolph-Chebyshev window. 
           80 seems reasonable with 16-bit data (60 produces a higher noise floor, without noticeably narrowing the spikes).
           Note, sqrt is used to get amplitude spectrum, rather than power spectrum. """)
    options=parser.parse_args(argv[1:])
    return options


def main(argv):
    """parse the command line arguments and do FFTs
    """
    options = parse_options(argv)
    sampling_freq= options.sampling_freq

    # input file may contain separate time traces.
    # Each new trace stats when the time value is less than the previous time.
    
    for trace in read_trace(sys.stdin, column=options.column, 
                        sampling_freq=options.sampling_freq, 
                        echo_comments_to=sys.stdout):
        print("# using sampling frequency {:.6f} Hz for FFT of trace with {} samples".format(trace.sampling_freq,len(trace)))


        fft_len=64      # length of the fft to do
        while fft_len<len(trace) and fft_len<options.window:
            fft_len *= 2
        
        if options.welch is not None:
            welch_f,welch_spectrum = scipy.signal.welch(trace.values, trace.sampling_freq, nperseg=fft_len, scaling="spectrum", window=("chebwin",options.welch))
            print("")       # separate the traces
            for f,w in zip(welch_f,welch_spectrum):
                print("{0:.7f}\t{1:.6f}".format(f, sqrt(w)*(fft_len//2)))
        else:
            for start in range(0,len(trace),fft_len):
                window=trace.values[start:start+fft_len]

                # remove DC component, so that zero-padding makes sense
                # WARNING: This in-place change could introduce errors if
                # overlapping windows are allowed.
                dc = sum(window)/len(window)
                window -= dc

                fft = scipy.fftpack.fft(window, n=fft_len)
                fft[0] += dc

                print("")       # separate the traces
                print("# Window width {} = {:.3f}s".format(fft_len,fft_len/trace.sampling_freq))
                for i,f in enumerate(fft[0:fft_len//2]):
                    print("{0:.7f}\t{1:.6f}".format(i*trace.sampling_freq/fft_len, f))


if __name__ == "__main__" :
    sys.exit(main(sys.argv))

Dehumidifier

Filed under: Data acquisition — gasstationwithoutpumps @ 00:05
Tags: , , ,

In Average annual power use, I mentioned

I will be buying a dehumidifier for our house, to reduce the condensation on the walls. Current Energy Start rated dehumidifiers remove about 1.85 liters of water per kWh used, and I don’t think we need to remove 2775 liters of water a year (7.6 l/day) from our house, so the dehumidifier will add nothing to our electricity bill.  Based on reviews (in Consumer Reports and on Amazon), we’re looking at the 30-pint Whynter RPD-321EW Energy Star Portable Dehumidifier, as it has good performance in cool rooms (our house gets quite cool in winter, especially when we’re both at work) and is relatively easy to empty (we don’t have a convenient way to rig up a drain hose).

We bought the dehumidifier and it seems to work ok. The timer is a bit crude, as it does not have a time-of-day clock, but you can set up a delay of up to 24 hours for turning on and turning off.  This means that we’ll have to reset the timer daily. That’s not as much of an imposition as one might think, because we have to empty the water daily any way.  People running a dehumidifier with a drain hookup might find the need to reset the timer more of a nuisance.

Our biggest disappointment with the dehumidifier is that it is loud. They claim that it is only 53dB, but it seems louder than that to me—unfortunately, I don’t have a sound pressure meter to measure it with. The compressor is fairly quiet, but the fan is loud, which suggests a poorly designed air flow. We can run the dehumidifier in the living room, but not in the bedroom, because of the noise it makes.

I can’t measure the loudness of the dehumidifier in calibrated units, but I could record chunks of sound from it using an electret mic, an amplifier, and the BitScope USB oscilloscope. I then wrote a program using SciPy to take the FFT of each recorded trace, and averaged the absolute values of FFTs for a hundred or so traces:

The noise is "pink" noise, with more low-frequency components than high frequency ones. The 5kHz cutoff is not an artifact of my amplifier, which has fairly flat gain out to about 100kHz, nor of the microphone, which is fairly flat to at least 20 kHz.

The noise is “pink” noise, with more low-frequency components than high frequency ones. The 5kHz cutoff is not an artifact of my amplifier, which has fairly flat gain out to about 100kHz, nor of the microphone, which is fairly flat to at least 20 kHz.

We can use a lower sampling rate to zoom in on the lower frequency end of the spectrum:

The 60Hz peak is sharper here, and 120Hz is clearly missing, but the next several harmonics of 60Hz are present. I don’t have an explanation for the dip between 400Hz and 480Hz.

I don’t know whether the spike around 1067Hz (in both plots) is an artifact of my test setup or is present in the sound. I suspect it is present in the sound.

The dehumidifier takes 340 watts to run (about 40W for the fan and 300W for the compressor), and it extracts about 5 pints in 5 hours (somewhat slower than its claimed 30 pints/24 hours, but that is probably based on warmer, wetter air, which is easier to extract water from). We only run for 5 hours a night, because we don’t want to be woken up by the loud beeping that occurs when the 7-pint bucket fills up.  (There is no way mentioned in the manual to disable the alarm—my son and I might disassemble the dehumidifier to disconnect the irritating beep.)

We plan to run the dehumidifier only during off-peak hours (at night or weekend days), for about 45 hours a week, which will consume about 800kWh a year. At PG&E off-peak rates, that is around $120 a year, but we had $98 of unused Net-Electric Metering (NEM) credits, and our minimum bill is around $111, so the extra electricity use will make no change in our bill. We will probably want to pay for carbon offsets, though, since we are increasing our carbon footprint by about 0.23MT (so about $2 in carbon offsets).

2016 August 5

Digilent Analog Discovery 2 USB Oscilloscope

Filed under: Circuits course — gasstationwithoutpumps @ 14:12
Tags: , , , ,

I have recently learned about a new USB oscilloscope, Digilent’s Analog Discovery 2, which seems to be a step up from the BitScope BS10 USB oscilloscope that I currently own. Digilent’s offering has differential inputs, 14-bit ADC (instead of 8), 100MS/s (instead of 40MS/s), and much nicer-looking user interfaces (no more black background!).  It costs a little more ($279 vs. $245, both costing more to get BNC connectors for higher-speed oscilloscope probes), but Digilent has an academic program that reduces the cost to only $179, so that even with an extra $20 for the BNC adapter and $20 for scope probes, the price is still lower than the BitScope.

I’m considering getting the Analog Discovery 2 scope (if I qualify for the academic discount), but I’ll probably wait until I replace my laptop.  The free Waveforms 2015 software runs on a wide range of Windows versions, but only 10.9 or newer on Mac OS.  (It also runs under some versions of Linux).  I’m still running Mac OS 10.6.8 on my laptop, and I don’t want to “upgrade” to a newer OS on the old hardware—I’m planning to replace the laptop this year, but I’m waiting to see whether Apple comes out with a usable MacBook Pro in 2016, or whether they’ve gone all in for connector-less laptops, in which case I’ll probably have to switch to a cheaper, but clunkier Linux laptop.

One of the things I like about Digilent’s marketing is that they have a very thorough reference manual online, which goes through the design of the hardware, explaining the schematics and some of the design choices for what chips they used. The online reference manual for Waveforms 2015 seems decent, but not as thorough as the hardware manual.

I’m curious whether any of my readers have tried the Digilent USB oscilloscopes.

Next Page »

%d bloggers like this: