Gas station without pumps

2017 February 5

Units matter

Filed under: Circuits course — gasstationwithoutpumps @ 11:37
Tags: , , , , ,

I was a little surprised by how many students had trouble with the following homework question, which was intended to be an easy point for them:

Estimate C2(touching) − C2(not touching), the capacitance of a finger touch on the packing-tape and foil sensor, by estimating the area of your finger that comes in contact with the tape, and assume that the tape is 2mil tape (0.002” thick) made of polypropylene (look up the dielectric constant of polypropylene on line). Warning: an inch is not a meter, and the area of your finger tip touching a plate is not a square meter—watch your units in your calculations!

Remember that capacitance can be computed with the formula C = \frac{\epsilon_r\epsilon_0 A}{d}~,
where \epsilon_r is the dielectric constant,  \epsilon_0=8.854187817E-12 F/m is the permittivity of free space, A is the area, and d is the distance between the plates.

The problem is part of their preparation for making a capacitance touch sensor in lab—estimating about how much capacitance they are trying to sense.

There is a fairly wide range of different correct answers to this question, depending on how large an area is estimated for a finger touch. I considered any area from 0.5 (cm)2 to 4 (cm)2 reasonable, and might have accepted numbers outside that range with written justification from the students.  Some students have no notion of area, apparently, trying to use something like the length of their finger times the thickness of the tape for A.

People did not have trouble looking up the relative dielectric constant of polypropylene (about 2.2)—it might have helped that I mentioned that plastics were generally around 2.2 when we discussed capacitors a week or so ago.

What people had trouble with was the arithmetic with units, a subject that is supposed to have been covered repeatedly since pre-algebra in 7th grade. Students wanted to give me area in meters or cm (not square meters), or thought that inches, cm, and m could all be mixed in the same formula without any conversions.  Many students didn’t bother writing down the units in their formula, and just used raw numbers—this was a good way to forget to do the conversions into consistent units.  This despite the warning in the question to watch out for units!

A lot of students thought that 1 (cm)2 was 0.01 m2, rather than 1E-4 m2. Others made conversion errors from inches to meters (getting the thickness of the tape wrong by factors of 10 to 1000).

A number of students either left units entirely off their answer (no credit) or had the units way off (some students reported capacitances in the farad range, rather than a few tens of picofarads).

A couple of students forgot what the floating-point notation 8.854187817E-12 meant, even though we had covered that earlier in the quarter, and they could easily have looked up the constant on the web to figure out the meaning if they forgot.  I wish high-school teachers would cover this standard way of writing numbers, as most engineering and science faculty assume students already know how to read floating-point notation.

Many students left their answers in “scientific” notation (numbers like 3.3 10-11 F) instead of using more readable engineering notation (33pF). I didn’t take off anything for that, if the answer was correct, but I think that many students need a lot more practice with metric prefixes, so that they get in the habit of using them.

On the plus side, it seems that about a third of the class did get this question right, so there is some hope that students helping each other will spread the understanding to more students.  (Unfortunately, the collaborations that are naturally forming seem to be good students together and clueless students together, which doesn’t help the bottom half of the class much.)

2016 July 13

Autoranging capacitance meter using TeensyLC

Filed under: Circuits course — gasstationwithoutpumps @ 09:59
Tags: , , , ,

 

My earlier post, Capacitance meter using touchRead(), showed the beginnings of a capacitance meter, using the touchRead() routine in the Teensyduino environment.

Today I’ll share a slightly more complicated program that uses the TSI (touch sensing input) peripheral more directly to make an autoranging capacitance meter that can measure capacitors down to 1pF and up to 3µF.  The repeatability of the measurements is not great (about ±3%), but the linearity seems pretty good.

The program includes the ability to zero out the test fixture (important for measuring small capacitances) and to calibrate the meter to a known capacitor.  I don’t have a capacitor with a tight tolerance, so I had to do my calibration against a DT-9205A multimeter, which is not a very reliable standard.  Still, it seemed more consistent than the labeling on the cheap ceramic capacitors I have, some of which seem to be off by a factor of two!

The multimeter could not measure capacitances at the low end of the range, so only nominal values are used there.

The multimeter could not measure capacitances at the low end of the range, so only nominal values are used there.

A number of the capacitors in the 100pF–10nF range seemed to drift consistently upward for quite a while, both with the multimeter and my homebrew capacitance meter. Touching them with my finger brought them sharply back down again, but touching them with stainless steel tweezers did not. I suspect that the effect is a thermal one, with the capacitance dropping when my fingers warmed the capacitors and going up again as the capacitors cooled to room temperature. The change was several percent, which is consistent with a low-quality “Y” dielectric.

// Preliminary program for a capacitance meter
// Kevin Karplus
// 2016 Jul 13


// To use:
//	* Connect a serial monitor (like the Arduino IDE) to the USB port
//	* Connect capacitor to measure between pin 0 and ground
//	* Press "a" and <return> to make an autorange measurement.

// Capacitances in the range 1pF to 3uF can be measured, but large
// capacitors take a long time to measure, up to 9s for 3uF.
// The measurement time can be reduced by reducing NUM_READS, perhaps
//	replacing the constant by an array dependent on range.
//	A factor of about 10 reduction in measurement time is available, as
//	NUM_READS is currrently set to 20.

// Note: readings may drift by +- 5%.  I've not yet determined the
// cause of this drift.  It may be thermal (cooling from finger
// temperature to room temperature may raise the capacitance by 2%,
// depending on the additives to the ceramic used as the dielectric).

// To calibrate:
//	* Remove capacitors from pin 0
//	* Type 'z' to measure the counts for the empty test fixture
//	* Pick a known capacitor (around 1nF) and connect it between
//		pin 0 and ground.  The capacitor must be small enough
//		to be measurable on the highest-resolution range (<=3nF).
//		To calibrate with a larger capacitor, first calibrate with
//		a small one to get the highest-resolution range set, then
//		repeat the calibration with the larger capacitor, which
//		will only reset the larger ranges.
//	* Type 'k', followed by an integer known capacitance in pF,
//		followed by a separator (like ';')
//	* Type 'c', to measure the pF per count

// Note: the calibrations are reported to the USB serial port, so they
// can be saved and used to edit the source code, changing count_for_0pF
// and pf_per_count arrays.

// The measurement process can be followed in more detail by turning
// on debugging with the 'D' command.
// Debugging can be turned off with the 'd' command.

// Capacitance is measured on pin 0 (one of several pins with TSI),
// because pin 0 is immediately next to ground, making it easy to connect
// small capacitors between pin 0 and ground.
#define TOUCH_PIN (0)

// forward references to later routines
int32_t capRead(uint8_t pin, 
		uint8_t I_ext=3, uint8_t I_ref=4, 
		uint8_t Prescale=2, uint8_t N_scan=9);
float autoCapRead(uint8_t pin);
int readInt(void);
void print_calib(int count, int n_cycles=1);
float read_counts_per_cycle(uint8_t i_ext, uint8_t i_ref,float count_for_zero);

volatile uint8_t debug;

// The current for the external oscillator and the reference
// oscillator of the TSI interface determine the capacitance range, resolution,
// and speed of the measurement.
// The program uses the highest-resolution range that does not cause
// the 16-bit counter to overflow.

// Settings of the current paramters for the different ranges
// The highest-resolution range is first, the widest range is last.
#define NUM_RANGES (6)
const int8_t i_ext_choices[NUM_RANGES] = {2,3,4,5,6,7};
const int8_t i_ref_choices[NUM_RANGES] = {5,4,3,2,1,0};

float pF_per_count[NUM_RANGES]=
	{0.2126, 0.772, 2.897, 11.08, 47., 195.3};
float count_for_0pF[NUM_RANGES] =
	{48.7612, 13.82, 3.813, 1.0605, 0.273, 0.0517};

// parameters for calibration
int C_known=0;	// known capacitance value 

void setup() 
{
    pinMode(TOUCH_PIN, INPUT);
    Serial.begin(115200);
    debug=0;
}

void loop()
{
     if (Serial.available())
     {   char c=Serial.read();
         if (c=='D') {debug=1;}
	 else if (c=='d') {debug=0;}
	 else if (c=='a')
         {   // do one reading and print it
             Serial.print(autoCapRead(TOUCH_PIN));
             Serial.println(" pF");
        }
        else if (c=='k')
        {   // set known capacitance for calibration checks
	   C_known=readInt();
        }
	else if (c=='z')
	{   // Set the count_for_0pF array for each range.
	    // Print the current parameters and zero count for each range.
	    // Only issue this command if there is no capacitor connected
	    // to TOUCH_PIN
	    for (int range=0; range<NUM_RANGES; range++)
	    {   uint8_t i_ext=i_ext_choices[range];
	        uint8_t i_ref=i_ref_choices[range];
		count_for_0pF[range] =read_counts_per_cycle(i_ext,i_ref,0.0);
		Serial.print("# zero for ");
		Serial.print(i_ext); Serial.print("\t");
		Serial.print(i_ref); Serial.print("\t");
		Serial.println(count_for_0pF[range],4);
	    }	
	}
	else if (c=='c')
	{   // do autocalibration, setting pF_per_count
	    // printing known capacitance, 
	    //		external current setting,
	    //		reference current setting,
	    //		average pF_per_count for a single cycle
	    
	    if (C_known==0)
	    {   Serial.println("# use 'k<known C in pF>;' first");
	    	return;
	    }
	    for (int range=0; range<NUM_RANGES; range++) { uint8_t i_ext=i_ext_choices[range]; uint8_t i_ref=i_ref_choices[range]; float calib=read_counts_per_cycle(i_ext,i_ref,count_for_0pF[range]); if (!isnan(calib)) { calib = C_known/calib; pF_per_count[range]= calib; } else { Serial.print("# "); // comment out overflows } Serial.print(C_known); Serial.print("\t"); Serial.print(i_ext); Serial.print("\t"); Serial.print(i_ref); Serial.print("\t"); Serial.println(calib,4); } } } } // print a calibration line void print_calib(int count, uint8_t i_ext, uint8_t i_ref, int n_cycles) { float avg_count = (count+0.0)/n_cycles; Serial.print(C_known); Serial.print("\t"); Serial.print(i_ext); Serial.print("\t"); Serial.print(i_ref); Serial.print("\t"); Serial.print(count); Serial.print("\t"); Serial.print(n_cycles); Serial.print("\t"); Serial.println(C_known/avg_count,4); } float read_counts_per_cycle(uint8_t i_ext, uint8_t i_ref, float count_for_zero) { // Do one measurement and return average counts/cycle - count_for_zero. // If debug set, print C_known, count, i_ext, i_ref, n_cycles, cap/count // // Actually does NUM_READS+1 measurements: one with a single cycle, // then again NUM_READS times with as many cycles as can be fit without // overflowing counter. if (debug) { Serial.println("# C\ti_ext\ti_ref\tcount\tn_cycle\tC/avg_count"); } int count=capRead(TOUCH_PIN, i_ext, i_ref, 0, 0); if (count>=0xFFFF)
    {   if (debug)
    	{   Serial.print("# "); // comment out overflows
	    print_calib(count,i_ext,i_ref,1);
	}
	return NAN;	// abort rest of calibration check
    }

    // Determine max number of cycles that can fit
    int cycles= 0xFFFE/count; // how many cycles to use
    int prescale,n_scan;
    for (prescale=0; cycles>32 && prescale<7; prescale++) {cycles/=2;} if (cycles==0) {n_scan=0;} else if (cycles>32) {n_scan=31;}
    else {n_scan=cycles-1;}
    cycles = (n_scan+1)<<prescale;

#define NUM_READS	(20)
    // To do: replace NUM_READS with a const uint8_t num_reads[NUM_RANGES]
    // array, to speed up measurement at high ranges.

    int sum_count=0;
    for (int i=0; i<NUM_READS; i++) { count=capRead(TOUCH_PIN, i_ext, i_ref, prescale, n_scan); if (debug) { print_calib(count, i_ext, i_ref, cycles); } sum_count+=count; } return (sum_count+0.0)/ (NUM_READS*cycles) -count_for_zero; } // Read a non-negative integer from Serial as a series of digits, terminated // by any non-digit (recommend using something obvious like ';'). // The terminating character is discarded. int readInt(void) { int value=0; while (!Serial.available()) {} // wait for next char char c=Serial.read(); while (c>='0' && c<='9')
    {   value= 10*value + (c-'0');
	while (!Serial.available()) {}	// wait for next char
       	c=Serial.read();
    }
    return value;
}
   


float autoCapRead(uint8_t pin)
{
    // Return the capcitance in pF at the pin, using the highest-resolution
    // range that doesn't overflow the counter.
    
    // pick the lowest (highest resolution) range that doesn't overflow
    int32_t count;	// number of counts of ref oscillator
    int range;
    for (range=0, count=0xFFFF;  range<NUM_RANGES && count>=0xFFFF; range++)
    {	count=capRead(pin, i_ext_choices[range],i_ref_choices[range],0,0);
    }
    range--;
    
    if (count>=0xFFFF)
    {    return NAN;	// capacitance too big to measure with TSI
    }
    
    float count_per_cycle=read_counts_per_cycle(i_ext_choices[range],
		i_ref_choices[range],
		count_for_0pF[range]);
    return pF_per_count[range]*count_per_cycle;
}


/* Raw reading is based on the
 * Teensyduino Core Library touch.c (which implements touchRead)
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2013 PJRC.COM, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * 1. The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 *
 * 2. If the Software is incorporated into a build system that allows 
 * selection among a list of target devices, then similar target
 * devices manufactured by PJRC.COM must be included in the list of
 * target devices and selectable in the same manner.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// #include "core_pins.h"

// capacitance is supposed to be
//	Cref * (1<<I_ext) / ( (1<<I_ref) * (1<<Prescale) * (N_scan+1) ) * COUNT
// but use 
// Cref * ext_current[I_ext] / ( ref_current[I_ref] * (1<<Prescale) * (N_scan+1) ) * COUNT // because the current ratios are not a constant factor of 2 // with DVOLT==0 (slowest, but least noise sensitive), // I_ext = I_ref-1, // Prescale=2, // N_scan=9, // Capacitance is approx 0.01846 pF * COUNT, // so Cref approx 1.47694pF #if defined(__MK20DX128__) || defined(__MK20DX256__) // These settings give approx 0.02 pF sensitivity and 1200 pF range // Lower current, higher number of scans, and higher prescaler // increase sensitivity, but the trade-off is longer measurement // time and decreased range. static const uint8_t pin2tsi[] = { //0 1 2 3 4 5 6 7 8 9 9, 10, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0, 6, 8, 7, 255, 255, 14, 15, 255, 12, 255, 255, 255, 255, 255, 255, 11, 5 }; #elif defined(__MK66FX1M0__) static const uint8_t pin2tsi[] = { //0 1 2 3 4 5 6 7 8 9 9, 10, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0, 6, 8, 7, 255, 255, 14, 15, 255, 255, 255, 255, 255, 11, 12, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; #elif defined(__MKL26Z64__) static const uint8_t pin2tsi[] = { //0 1 2 3 4 5 6 7 8 9 9, 10, 255, 2, 3, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 13, 0, 6, 8, 7, 255, 255, 14, 15, 255, 255, 255 }; #endif // for I_ref=I_ext+1, N_scan=9, Prescale=2, output is approx pF * 50 // time to measure 33 pF is approx 0.25 ms // time to measure 1000 pF is approx 4.5 ms int32_t capRead(uint8_t pin, uint8_t I_ext, uint8_t I_ref, uint8_t Prescale, uint8_t N_scan) { uint32_t ch; int32_t count; if (pin >= NUM_DIGITAL_PINS) return 0;
    ch = pin2tsi[pin];
    if (ch == 255) return 0;

    *portConfigRegister(pin) = PORT_PCR_MUX(0);
    SIM_SCGC5 |= SIM_SCGC5_TSI;
#if defined(KINETISK)
    TSI0_GENCS = 0;
    TSI0_PEN = (1 << ch);
    TSI0_SCANC = TSI_SCANC_REFCHRG(I_ref) | TSI_SCANC_EXTCHRG(I_ext);
    TSI0_GENCS = TSI_GENCS_NSCN(N_scan) | TSI_GENCS_PS(Prescale) | TSI_GENCS_TSIEN | TSI_GENCS_SWTS;
    delayMicroseconds(10);
    while (TSI0_GENCS & TSI_GENCS_SCNIP) ; // wait
    delayMicroseconds(1);
    count= *((volatile uint16_t *)(&TSI0_CNTR1) + ch);
#elif defined(KINETISL)
    TSI0_GENCS = TSI_GENCS_REFCHRG(I_ref) | TSI_GENCS_EXTCHRG(I_ext) | TSI_GENCS_PS(Prescale)
	    | TSI_GENCS_NSCN(N_scan) | TSI_GENCS_TSIEN | TSI_GENCS_EOSF;
    TSI0_DATA = TSI_DATA_TSICH(ch) | TSI_DATA_SWTS;
    delayMicroseconds(10);
    while (TSI0_GENCS & TSI_GENCS_SCNIP) ; // wait
    delayMicroseconds(1);
    count= TSI0_DATA & 0xFFFF;
#endif
    if (debug)
    {
	Serial.print("  I_ext= ");  Serial.print(I_ext);
	Serial.print("  I_ref= ");  Serial.print(I_ref);
	Serial.print("  Prescale= ");  Serial.print(Prescale);
	Serial.print("  N_scan= ");  Serial.print(N_scan);
	Serial.print("  count= ");  Serial.print(count);
	Serial.println();
    }
    return count;
}

2016 July 11

Capacitance meter using touchRead()

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

I decided to see whether the TSI (touch sensing input) on the Teensy boards was good enough to use as a capacitance meter. My first attempt was to use the touchRead() interface from the Teensyduino environment.

The test code was very simple:

#define LED_PIN  (13)
#define TOUCH_PIN (0)

void setup() 
{
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, LOW); 
    pinMode(TOUCH_PIN, INPUT);
    Serial.begin(115200);
}

void loop() 
{
     if (Serial.available())
     {   char c=Serial.read();
         if (c=='1')
         {   int sum_touch=0;
             for(int i=100; i>0; i--)
             {   sum_touch+=touchRead(TOUCH_PIN);
                 digitalWrite(LED_PIN, HIGH);
                 delay(20);
                 digitalWrite(LED_PIN, LOW); 
             }
             Serial.println(sum_touch);
         }
     }
}

I just read the pin with touchRead 100 times, adding the results. I measured several different capacitors to find out the range of readings and to fit a function to the data.

The way the touch sensing works is that there are two hysteresis oscillators in the microcontroller, one using a fixed reference capacitance and the other using the capacitance of a pin.  The feedback in each oscillator is not a resistor, but switches between a positive and a negative constant-current source. This should give the input to the Schmitt trigger a clean triangle wave.  There are 8 different constant-current sources to choose from, and different sources can be chosen for the two oscillators.  The amount of hysteresis can also be chosen.  The pin-controlled oscillator output is divided down by a pair of counters (one that can be set to 1, 2, 4, 8, …, 128, the other to  1, 2, 3, 4, … , 32), and the number of pulses of the reference oscillator is counted for one tick of the divided-down pin-controlled oscillator.  The touchRead() function fixes which current sources, which hysteresis voltage, and which counter settings are used, so that the reading is directly proportional to the period of the pin-controlled oscillator, which in turn is directly proportional to the capacitance.

Therefore, the readings should be linear with the capacitance, but there is likely to be some parasitic capacitance that needs to be added (indeed, I get a reading of around 551.7 when the Teensy board is plugged into a bread board, with no deliberate capacitance is added).  So the model I want to fit is y= a (C + C_{0}).

The simple linear fit measures each capacitor within about 10% of its nominal value, which may be as accurate as the capacitors are (these were cheap ceramic assortments from China, with no specs).

The simple linear fit measures each capacitor within about 10% of its nominal value, which may be as accurate as the capacitors are (these were cheap ceramic assortments from China, with no specs).

To get good fits at the low end, I fit the C0 parameter only for the data points at 0pF, 1pF, and 5pF, but fit the scaling parameter over the whole range. It is not visible on the plot, but each capacitor was measured multiple times, and the variation in measurement was less than ±0.3%.  Of course, this is for averages of 100 readings, so the raw readings may vary up to ±3%—I’ve not tested for variation.  Somewhat surprisingly, the % variation seemed to be larger at the high end than at the low end—I would have expected the high end to have less variation (because of reduced quantization error).

I checked the capacitance of a small aluminum foil and packing tape touch pad, like we use in the applied electronics course.  It was about 7.05pF when not touched, and 60.8pF when touched with moderate pressure.

Because touchRead() reports 65535 for any capacitance that is too large, the maximum measurable capacitance is 65534/55.3942 – 10.1921 pF = 1173pF. If I want to measure large capacitors, I’ll need to rewrite the touchRead() code to use the TSI functions of the microcontroller more directly. I’ve started work on that, but I’ll save it for a later post. I expect to be able to measure up to at least 1µF. That code will also include translation to standard capacitance units and calibration options.

2016 January 27

Student projects selected for Winter 2016

Filed under: freshman design seminar — gasstationwithoutpumps @ 21:46
Tags: , , ,

The students today selected their projects and teams.  We ended up with three different projects:

  • Ultrasonic rangefinder.  This involves building a rangefinder from transducers, not just hooking up an already designed rangefinder (though rangefinder modules from China are cheaper than the pair of transducers that they include). There are 3 students on this team. I ordered TCT40-16R/T 40kHz transceiver pairs from two different sources on e-bay, hoping that one of the packages will arrive within two weeks, and that they don’t both get caught in the Chinese New Year holiday.  If both come, I’ll have huge numbers of transceivers (16 pairs).
  • Pulse monitor. This is almost identical to the pulse monitor project in the applied electronics class, though I’m hoping that the students will have time to try to extract the pulse rate from the signal—doing some programming as well as the electronics. There are 2 students on this team.
  • LED cube and capacitive touch keyboard.  This is a “fun” project, with little direct applicability to bioengineering.  The hard parts are figuring out how to do the 3D soldering and getting individual control over all the LEDs with the limited pinout of the Teensy board. There are either two teams of three or three teams of two—I wasn’t entirely clear how the six students divided up.

Now I have to figure out what needs to be taught for each of these projects.  I’m pretty clear on the pulse monitor, as I’ve done that one before, but rangefinder and the LED cube are new.

The Teensy boards have capacitive touch inputs built-in, so the capacitive-touch keyboard will be fairly trivial using the touchRead() function of Teensyduino, which reports approximate capacitance in units of about 50pF. I’ll try making a 5-key keyboard this weekend and seeing how well it works.  I’ll try both a large-plate sensor, like I’ve used for the applied electronics lab and a design alternating sense wires and ground wires.  I suspect that the alternating wire design will be less sensitive to 60Hz pickup, but also a bit less sensitive to touch.

I’ll have to look at some of the RGB LED cube designs, to see how they are multiplexing the signals. I suspect that they use either Charlieplexing or a 1-of-n decoder.  A 4×4×4 cube has 64 LEDs (192 if RGB), which needs 9 pins (or 15 for RGB) if Charlieplexing, and 8 (or 16 for RGB) with a 1-of-16 decoder. The decoder makes for much simpler mapping and coding, which is important for programming color patterns. There aren’t enough PWM channels to do a good job of getting arbitrary colors at each LED, but PWM could be used for brightness (and some patterned color changes).

I think that the ultrasonic rangefinder will mainly be software, if we do all the pulse generation and detection digitally. The speed of sound is about 340 m/s (more precisely 331.5 m/s+0.6 T m/s/°C), so a resolution of 1mm requires 2.9µs resolution for time (typical cheap range finders have a 1cm or even 1″ resolution). If we sample the incoming sound every 2.5µs and store the waveform for processing, the 8k bytes of RAM on the Teensy LC can store enough data for the round trip for an object up to 3.48m away (if storing only one byte per sample).  The Teensy 3.2, with 64k bytes of RAM could store enough data for an 13.9 m range with 16-bit samples, but the echo would probably be much too quiet at that distance (I haven’t checked whether the analog-to-digital converter can do a 16-bit conversion every 2.5µs).

The common approach for many of ultrasonic range finders is to rely entirely on the resonant receiver to filter out the desired signal, and just amplify and threshold it to detect the first returning echo, which makes for fairly simple programming. If we record and do digital processing of the returned waveform, we should be able to detect multiple echoes, and it may even be advantageous to use a non-resonant microphone as a detector to get less ringing.

I checked to see if a non-resonant microphone would work, using a cheap electret microphone (probably CMA-4544PF-W), my microphone preamp for the applied electronics class, and the BitScope oscilloscope. I seem to be getting about a 1µA peak-to-peak signal from the microphone when listening to the Maxbotix LV-MaxSonar-EZ rangefinder for the direct signal close by, and about 0.1µA  peak-to-peak for a bounce from about 25cm away. The pulse from the Maxbotix lasts far longer than one might expect (almost 1ms), which indicates substantial ringing in the transmit transducer, and explains why the Maxbotix can’t detect distances less than about 6″.

It looks like a non-resonant microphone will be able to pick up the signal, though substantial amplification and some filtering will be required before digitizing, and I’m not sure how difficult it will be for the students to do the programming for high-speed analog-to-digital conversion, as the Teensyduino software probably doesn’t support 400kHz sampling rates.

It looks like all three projects are doable, but I’m not sure whether to steer the rangefinder group towards electronics or digital filtering solutions (and they will need steering, as they know neither electronics nor programming currently).

2014 April 17

Hysteresis lab ended well

Today’s lab went well, with very little intervention on my part. Students finished up their RC calculations, picked their resistors and capacitors, and got their relaxation oscillators working.  They then adjusted their R or C values to bring the oscillator into spec, if needed. Most of the help I gave during all this was getting the students comfortable with using the Tektronix digital scopes, which have an extremely complicated and confusing menu system. The “autoset” feature on the scopes is almost essential, since they can have been left in any sort of weird state by the previous user, and finding and clearing all the weirdness takes a while.

Students then made their touch sensors (aluminum foil folded up to be sturdy, then wrapped with a layer of packing tape), and connected them to the oscillators. Most students got a substantial change in frequency, as expected, but one group had chosen a large C and small R, and so got almost no change. With only minimal prompting, they figured out why the frequency wasn’t changing, fixed their values and got it working.

The students did observe a change in frequency if they connected a scope probe to the input of the Schmitt trigger, and most eventually figured out that this meant that the scope probe was acting like a capacitor.  When I did it with my scope probe at home, I got a change from 60kHz to 35.22kHz, about a 70% increase in the RC time constant.  Since the capacitor I was using was 30pF, this looks like it implies a 21pF capacitance.   It doesn’t make much difference whether I connect the scope ground to the ground or the 3.3v lead—the change in frequency is the same either way, so we’re seeing an effect due to capacitance, not due to current through the oscilloscope input resistance. I looked up the specs for the input capacitance of my probes, and it is supposed to be 20pF in 10× mode and 130pF in 1× mode.  From that I worked out an approximate circuit for the probe:

Approximate circuit for my cheap 60MHz scope probes.

Approximate circuit for my cheap 60MHz scope probes.

With the 1× probe setting, the 1MΩ input resistance of the oscilloscope matters—connecting up the scope drops the oscillation frequency to 5kHz if the ground of the scope is grounded, and stops oscillation completely if the ground of the scope is connected to 3.3v.

The Bitscope DP01 differential probe, with no jumper plugs in place (so 2:1 setting on the Bitscope screen) reduces the frequency from 59.7kHz to 38.6kHz, implying about a 16.5pF input capacitance, while the spec claims only 2.5pF differential and 5pF common-mode. I don’t seem to be able to get a signal on the BitScope screen with the differential probe in high-gain mode, and I’m not sure why (the voltages shouldn’t be exceeding the voltage limits).  There may be some problem with powering both the BitScope and the device being tested from the same underlying USB power source, though it caused no problems in the low-gain mode.

Students soldered up the boards without problems. The only intermittent error that I had to help debug turned out to be a misuse of an alligator clip (the wire had not been screwed down, but only wrapped around the clip). No one soldered a chip in backwards and I did not need any of the spare boards or chips that I had brought along, just in case.

Luckily not everyone was ready to solder at the same time, as the lab support people had no board holders available, so only the two I brought from home were available.  I’ll have to ask them to get some PanaVise juniors (about $27 each) or, if they are too cheap to buy them, then some alligator-clip-based board holders for about $7 each.

Some students had enough time after soldering up their boards that I showed them how to get the frequency information that the KL25Z program was reporting to the SDA USB serial port (using the Arduino Serial Monitor).  Unfortunately, the old version of Windows running on the lab computers seems to have serious problems with cut-and-paste operations, and it was difficult to get more than a screenful of data that way.

Next Page »