Gas station without pumps

2016 March 31

Fifteenth weight progress report

Filed under: Uncategorized — gasstationwithoutpumps @ 22:12
Tags: , , , , , ,

This post is yet another weight progress report, continuing the previous one. I’m doing a little better this month, ging above my target range for only 6 days:

I've been fluctuating around the top of my target range, when I'd rather be in the middle of it (about 3–4 lbs less than my current weight).

I’ve been fluctuating around the top of my target range, when I’d rather be in the middle of it (about 3–4 lbs less than my current weight).

 

My exercise for March was a bit low (averaging just under 4.1 miles/day bicycling), but I was good about my raw-fruits-and-vegetables-for-lunch diet during the week, and did not indulge too much in sweets (except for the week of Spring break, when my son was home so we had ice cream and sorbetto in the freezer!).

PteroDAQ bug fixes

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

I spent much of my lunch break today using a laptop borrowed from a student in the Applied Electronics course to debug a problem in PteroDAQ (one that I thought I had fixed on 2016 Feb 6 and again on 2016 March 30).

The problem was that on newer versions of Mac OS X, our original way for finding the serial ports and listing them in PteroDAQ (with good names) was failing, and the serial ports weren’t appearing. I fixed it for Mac 10.11.3, but that fix was breaking for most of the students in the course.  Yesterday, a student who is a friend of a student in the class suggested that the problem was just that I was only looking for USB 2 ports, and that the new USB stack used different data types for USB 3 ports.

Since I did not have any hardware with USB 3 ports (yes, I hang onto my computers for a while—the machine I’m typing this on is a MacBook Pro bought in mid-2009), I was unable to test the fix, so I released it to the class and asked them to test it.  A few students reported it working, but in the lab this morning, several students with Mac OS X computers were still unable to use PteroDAQ.  It was clearly a USB 2 vs. USB 3 problem still, since they were able to use PteroDAQ if they connected using my cable that has a USB 2 hub in it. One other student had gotten PteroDAQ to work for her by using a USB 2 hub that she had bought just for the purpose (probably a $7 or $8 one from Staples).

So instead of eating my lunch of raw vegetables, I sat with one of the laptops that was failing, uncommented some debug print statements that I had left in the code (I don’t delete debugging statements—I just comment them out, precisely for this sort of later bug fixing), and looked at the data structure for that Mac.  Apparently there are many different USB stacks for Mac OS X, with all sorts of differences. I put together ways of finding the appropriate name for the serial port using as many different methods as I could think of, based on either the child or the parent of the object in the chain that had the name I wanted.

After it was working on his computer, I handed it back to him and redid the bug fixes on my laptop and pushed them back to the bitbucket site, asking students to test the new code.  So far, everyone who has tested it has gotten it working (except for one student who seems to have a fried Teensy LC board—we were not able to get even the Arduino environment on my Mac to see his board). We don’t have any spare Teensy boards, so I suggested he order one from PJRC with the male headers already soldered on.  That makes two students (out of 48) who have had to order replacement boards (the other one soldered the male headers on the wrong side of the board, and delaminated some of the pads in attempting to correct the problem).  Perhaps next year we should order an extra 5% of Teensy LC boards, to cover for this sort of problem and have a local source for replacement boards.

I managed to finally finish my lunch after lab ended (around 5:30pm), but I’m glad to have gotten PteroDAQ working for (almost) everyone.  It is now 10pm, and time for me to prepare for tomorrow’s lecture.

Pep talk for students frustrated at the end of the first week

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 20:48
Tags: , , ,

Some of the students in my Applied Electronics for Bioengineers course are feeling frustrated at the end of the first week (often due to imposter syndrome, not any real inability to do the work).  I sent them the following e-mail this evening:

To the class—a number of people are feeling overwhelmed, because of the wide range of preparation that people in the class have had.  This is supposed to be a first course in electronics, but a number of people are taking it after having had other electronics courses.  If the advanced students are allowed to dominate the questions in class, I’ll never know what help the students with less preparation (that is, the students the course is intended for) need.  If you are feeling overwhelmed or out-matched in class, please ask questions!  I know that there are people feeling like they need more help, but I don’t know exactly what help they need.
I could guess at what is causing people problems, but I’m likely to guess wrong, and I don’t want to waste a lot of time on reviewing stuff that everyone in the class gets, while not spending any time on the stuff that is really needed.
In short, I’m saying that I need a lot of questions from people in the bottom quarter of the class, and I don’t think I’ve been getting them.
Going to [the group tutor]’s sections is another way to catch up to those you perceive as being ahead of you.
It looked to me like everyone pretty much got labs 1 and 2 done, and that most of the class (though perhaps not everyone) had a decent grasp of aliasing.  A bigger fraction of the class had PteroDAQ and gnuplot installed and working by Lab 2 than in any previous offering of the course—so this looks to me like a very promising start to the quarter—it may have seemed chaotic to you with not all the parts arriving on time and last-minute patches to PteroDAQ to compensate for changes in laptop operating systems, but these startup pains are normal—I expect to have them every time the course is offered.
Lab 2 was much harder than intended this year, because of the resistor assortments not including 470kΩ resistors, and I was impressed by how the class rose to the challenge, despite not having had the lectures yet that would really support the design work done (those are scheduled for week 3, I believe). I’m going to have to rewrite parts of Lab 2 to allow for the possibility of not having the right parts available.
The deal with Lab 2 was this: I had given them in the book a circuit to build that consisted of a function generator, a capacitor, a pair of resistors, and the Teensy board with the PteroDAQ software. The idea in terms of skills was for them to learn how to lay things out on bread board, collect data with PteroDAQ and do some minimal plotting with gnuplot.  The concept they were supposed to be learning about was aliasing, which I was planning to cover in lecture yesterday, but I got diverted to other equally important topics.
The problem was that the design I gave them could not be implemented, because the resistor assortments (which only arrived yesterday, so I had no idea exactly what resistors would be in the kit) did not have the specified 470kΩ resistors!  I probably should have redesigned the circuit for them and had them build a different circuit which would have worked equivalently (like using 1MΩ and 4.7µF instead of 470kΩ and 10µF), but I did not know what resistor values they did have in their kits.
Instead, on the spur of the moment, I chose to have the students come up with a design themselves that has the same (or nearly the same) RC time constant as the circuit in the book.  If I’d had an hour to think about how to handle the challenge, I might have chosen a different approach. The assignment I gave them tied in well with yesterday’s unplanned lecture—without that lecture, I would not have considered them capable of redesigning the circuit.
 I think that everyone in the class did come up with a design that let them do at least a few recordings with PteroDAQ, though they did not get as much time to explore aliasing as I had originally intended. There were several different designs students came up with, including the 1MΩ and 4.7µF design, 10MΩ and 0.47µF, putting two 1MΩ in parallel to make 500kΩ, and building the 470kΩ out of a series chain of resistors.
Having a real design challenge for this first lab was in one way a good one (it had bothered me that there was no design element in the first week of lab), but this design challenge was too much for the first week.  After lab some students were feeling overwhelmed and wanting to drop the course—even though this year’s class is well ahead of previous year’s classes (even the students who are struggling are further along than their counterparts in previous years).
Now my challenge is to convince the students who are feeling stretched to stick with the class for another week or two, so that the lectures can catch up to what they need to know and they can have a more confident base to work from.

2016 March 30

Class topic not what was planned

Filed under: Circuits course — gasstationwithoutpumps @ 21:27
Tags: , , , ,

In my Applied Electronics for Bioengineers course, I had planned to spend the lecture time today talking about sampling and aliasing, but that is not what ended up happening.

I am making it a point to answer student questions (unless they are irrelevant) first, before doing whatever I prepared. The point of the lectures is to help students understand the reading and do the design work for the labs, and anything I have prepared is just a best guess at what the students need. Their questions address more directly what they perceive as their need.  Most of the prepared lecture material is in the book (I wrote the book based on what I have covered in lectures), so answering questions from students who have read the book and are still confused is going to be better than my repeating what is in the book.

Today students had some logistic questions about what to write up for Lab 1 (not much, it was just soldering headers onto the Teensy boards and setting up PteroDAQ—I just asked for a description of what they did, whether anything went wrong, and what they did to fix the problem) and about prelab homework for Lab 2 (do it, but don’t turn it in, it is just setting up gnuplot so that they can use it for the lab).  Those only took a couple of minutes.

The big question that diverted the entire flow of the lecture was a request for an explanation of the high-pass filter in Lab 2 that is used for recentering the function generator output at 1.65V. This lead to several things:

  • Description of block diagrams as functional blocks connected by interfaces, and why this was an important concept in engineering. Frequency and voltage information was put on the block diagram  connections.
  • Capacitor symbol and DC-blocking property of capacitors.
  • Resistor to Vref and why that would cause the output to become Vref, if there was no current through the output.
  • Back to the block diagram to add the constraint that the analog-to-digital converter on the Teensy board couldn’t take any current from its input.
  • Definition of “gain” as \frac{dV_{out}}{dV_{in}}.
  • Showing the high-pass filter Bode plot as two lines meeting at the corner frequency, and giving the corner frequency as \frac{1}{2\pi R C}, without derivation.  I promised the students that we would derive that result in a few weeks, once we’ve had complex impedance.
  • Replacing the resistor to Vref with a pair of resistors to 3.3V and Gnd.
  • Introduction of the triangular ground symbol, and rejection of the chassis ground and earth ground symbols as not relevant for the class.
  • Derivation of the voltage-divider formula from Ohm’s Law, using the important constraint that no current is taken from the output node of the voltage divider, so that the two resistors have identical currents. I had the students help with this, in order to elicit the most common mistake
  • Assertion, without derivation or explanation, that the RC time constant for the high-pass filter should treat the two resistors as being “2R” rather than “R”.

For the last couple of minutes of class, I finally got to do the demo with the homemade stroboscope and pendulum of aliasing, but it was not very effective. Even with the lights off in the classroom, there was enough light through the windows to wash out the strobe. I could not easily keep the pendulum swinging with one hand and adjust the strobe with the other.  If I do this again next year, I should make a panel with about 20 of the LED boards, for around 2.35A during the flash.  At 1.64ms for the longest flash, that’s 3.85mC, which would drain 8.2V from the 470µF capacitor, if the power supply weren’t capable of delivering that much current (but I have a 6A 9V supply, so there should be no problem delivering full power).  Hmm, maybe I should make up that panel for the Mini Maker Faire, instead of the wimpy 4-LED strobe I now have.

I’m actually pleased that I didn’t give the lecture I had planned—my book, which was based on my lectures, already covers the material adequately, and I’d much rather spend precious class time explaining the things that aren’t clear in the book.  The only way I can know what the students need to hear is for them to ask for clarification where they are confused.

2016 March 27

Pulse monitor with display

Filed under: Uncategorized — gasstationwithoutpumps @ 15:10
Tags: , , ,

For the Mini Maker Faire, I put together the pulse monitor board with a 240×320 full-color TFT display, to make a self-contained pulse monitor (no laptop for display or power):

The display showing my pulse (which was a bit higher than my usual resting pulse). The trace is drawn left-to-right taking 5.33 seconds for each pass. The gap in the middle shows where the new trace is currently being drawn. The heart blinks with the pulse.

The display showing my pulse (which was a bit higher than my usual resting pulse). The trace is drawn left-to-right taking 5.33 seconds for each pass. The gap in the middle shows where the new trace is currently being drawn. The heart blinks with the pulse.

The block diagram shows the components I assembled for the monitor. I deliberately am not showing the pulse-monitor amplifier board, since that is a design exercise in my applied electronic course.

The block diagram shows the components I assembled for the monitor. I deliberately am not showing the pulse-monitor amplifier board, since that is a design exercise in my applied electronic course.

I need to redesign the finger block to be easier for kids to use—perhaps a more open design with the phototransistor not so deep.  I tried doing a back-scattering design (with the LED and phototransistor adjacent on a board), and I got a usable signal, but it seemed to be even touchier and more sensitive to motion artifacts than this block.  The motion artifacts mainly come from varying the amount of pressure with which the finger is pressing against the hole in the block for the phototransistors.

Real pulse monitors clip onto a fingertip or earlobe, so that the person does not have to keep their hand relaxed and still, but I’ve not yet come up with an easy-to-make design that works (mechanical design had never been my strength).

 

Here is the rather crude source code for the pulse monitor:

// code for pulse monitor on Teensy 3./3.2 board
// Using ILI9341 TFT 240x320 TFT display
// Sun 20 March 2016 Kevin Karplus

#define MONITOR_PIN	(A0)

#include "SPI.h"
#include "ILI9341_t3.h"
#include "font_GeorgiaBold.h"

// Use pins 9 and 10 for the DC and chip-select inputs of the TFT SPI interface
#define TFT_DC  9
#define TFT_CS 10

// Use hardware SPI (#13=SCK, #12=MISO, #11=MOSI) and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

static const unsigned char heart_data[] = {
/* Glyph 0: size=25x22, offset=0,-2, delta=25
	      **         **      
	  ********     *******   
	 *********** *********** 
	 *********************** 
	*************************
	*************************
	*************************
	*************************
	*************************
	 *********************** 
	 *********************** 
	  *********************  
	   *******************   
	    *****************    
	     ***************     
	      *************      
	       ***********       
	        *********        
	         *******         
	          *****          
	           ***           
	            *            
*/
0x19,0xB0,0xEC,0x80,0xC0,0x18,0x03,0xFC,0x1F,0xC1,
0xFF,0xDF,0xFC,0x7F,0xFF,0xFF,0x5F,0xFF,0xFF,0xFE,
0x1F,0xFF,0xFF,0xC3,0xFF,0xFF,0xE0,0x7F,0xFF,0xF0,
0x0F,0xFF,0xF8,0x01,0xFF,0xFC,0x00,0x3F,0xFE,0x00,
0x07,0xFF,0x00,0x00,0xFF,0x80,0x00,0x1F,0xC0,0x00,
0x03,0xE0,0x00,0x00,0x70,0x00,0x00,0x08,0x00,0x00,
};

static const unsigned char heart_index[] = {
0x00,0x00,
};
/* font index size: 2 bytes */

/*
typedef struct {
	const unsigned char *index;
	const unsigned char *unicode;
	const unsigned char *data;
	unsigned char version;
	unsigned char reserved;
	unsigned char index1_first;
	unsigned char index1_last;
	unsigned char index2_first;
	unsigned char index2_last;
	unsigned char bits_index;
	unsigned char bits_width;
	unsigned char bits_height;
	unsigned char bits_xoffset;
	unsigned char bits_yoffset;
	unsigned char bits_delta;
	unsigned char line_space;
	unsigned char cap_height;
} ILI9341_t3_font_t;
*/
const ILI9341_t3_font_t heart_font = {
	heart_index,
	0,
	heart_data,
	1,
	0,
	0,
	0,
	0,
	0,
	13,
	5,
	5,
	3,
	4,
	5,
	24,
	22
};

#define HYSTERESIS_THRESH	(1000)
	// filtered signal is turned to square wave with 
	// hysteresis with thresholds +-HYSTERESIS_THRESH

#define DEFAULT_PERIOD	((int32_t) (60e6/70.)) // period for 70bpm

static volatile     int32_t x_0, x_1, x_2;
static volatile     int32_t y_0, y_1, y_2;

// filter parameters for biquad bandpass filter

// selected for approx 0.66--6Hz with 60Hz sampling
#define SAMPLE_FREQ	(60)	// sampling frequency in Hz
#define a0  (256)
#define a1  (-388)  
#define a2  (141) 


#define SAMPLE_PERIOD_USEC (1.e6/SAMPLE_FREQ)



#define gain (1)
 // b0= - b2= gain*a0
  // b1=0

#define DELAY_XY (x_2=x_1, x_1=x_0, y_2=y_1, y_1=y_0)
#define GENERAL_BANDPASS (y_0 =  ((gain*a0)*(x_0-x_2) -a1*y_1 -a2*y_2)/a0, DELAY_XY)

#define NUM_TIMESTAMPS (8)
volatile int32_t time_falling[NUM_TIMESTAMPS];	
	// time (from micros()) of last NUM_TIMESTAMPS falling edges
	// (may want to replace with circular buffer)

volatile int32_t num_edges_since_pulse_found=0;

volatile bool reported_edge=0;	// most recent edge has been reported in loop()

IntervalTimer sampler;

volatile uint16_t xloc;	// location of trace in x dimension
uint16_t old_xloc;    // old value of xloc, to detect change outside interrupt routine

volatile bool squared_pulse=0;	// square wave made from pulse signal
bool heart_displayed;  // current state of heart display

void one_sample(void)
{
    x_0=analogRead(MONITOR_PIN);
    GENERAL_BANDPASS;
    xloc++;
    if (xloc>=320) {xloc=0;}
    if (squared_pulse && y_0< -HYSTERESIS_THRESH) { squared_pulse =0; for (int i=NUM_TIMESTAMPS-1; i>0; i--)
	    {    time_falling[i]= time_falling[i-1];
	    }
	    time_falling[0]=micros();
	    reported_edge=0;  // new edge not reported yet
    }
    else if (!squared_pulse && y_0 > HYSTERESIS_THRESH)
    {   
        squared_pulse =1;
    }
}

// convert the timestamps in time_falling 
//	to periods and report the median of the
//	NUM_TIMESTAMPS-1 periods
int32_t median_period(void)
{
    int32_t periods[NUM_TIMESTAMPS-1];	// sorted array of periods 
    	// (increasing)
    
    for (int i=0; i<NUM_TIMESTAMPS-1; i++) { int32_t p=time_falling[i]-time_falling[i+1]; int j; // do a crude insertion sort, since list is so short for (j=i; j>0 && periods[j-1]>p; j--)
	{   periods[j] = periods[j-1];
	}
	periods[j] = p;
    }
    return (NUM_TIMESTAMPS%2)? 
    	(periods[NUM_TIMESTAMPS/2-1] +periods[NUM_TIMESTAMPS/2])/2:
	periods[NUM_TIMESTAMPS/2-1] ;
}

void draw_heart(bool red)
{   tft.setFont(heart_font);
    tft.setTextColor(red? ILI9341_RED: ILI9341_WHITE);
    tft.setCursor(5,5);
    tft.drawFontChar(0);
}

void clear_text(uint16_t x_start=40, uint16_t y_start=0, 
                uint16_t x_stop=319, uint16_t y_stop=60)
{
    tft.fillRect(x_start,y_start,x_stop,y_stop,ILI9341_WHITE);
    tft.setCursor(x_start,y_start+5);
    tft.setFont(Georgia_14_Bold);
    tft.setTextColor(ILI9341_BLACK);
}

void setup(void)
{
    tft.begin();
    tft.fillScreen(ILI9341_WHITE);
    tft.setTextSize(2);
    tft.setRotation(1); // Header pins are on the right.

    pinMode(MONITOR_PIN, INPUT);
    analogReadRes(16);
    analogReadAveraging(32);
    Serial.begin(115200);
    squared_pulse=0;
    for (int i=NUM_TIMESTAMPS-1; i>0; i--)
    {    time_falling[i]= 0;
    }
    num_edges_since_pulse_found=0;
    sampler.begin(one_sample, SAMPLE_PERIOD_USEC);
    
    heart_displayed=0;
    draw_heart(heart_displayed);
    old_xloc=xloc=0; 
}

void loop(void)
{
    if (Serial.available())
    {   char c= Serial.read();
    	if (c=='r')
	    {    setup();
	    }
    }

#define scale (512)

    if (xloc!=old_xloc)
    {   tft.drawFastVLine(xloc,75,240,ILI9341_WHITE);
    	int32_t low_y,height;
        if (y_0<y_2)
        {   low_y=(y_0/scale)+170;
            height = (y_2-y_0)/scale; 
        }
        else
        {   low_y=(y_2/scale)+170;
            height = (y_0-y_2)/scale; 
        }
        if (low_y<80) { height -= (80-low_y); low_y=80; } if (height>=0)
        {   tft.fillRect(xloc-1,low_y-1, 3, height+3, ILI9341_BLACK);
        }
        old_xloc=xloc;
        if (squared_pulse==heart_displayed)
        {   // update heart display
            heart_displayed = !squared_pulse;
        	draw_heart(heart_displayed);
        }
    }

    if (reported_edge) return;	// nothing new to report
    reported_edge=1;
    int32_t period = time_falling[0]-time_falling[1];
    
    if (period < 250000 || period > 3000000)
    {   clear_text();
        tft.setTextColor(ILI9341_RED);
        tft.println("PULSE LOST");
        tft.setCursor(40,25);
        tft.print("period=");
		tft.println(period);
	    num_edges_since_pulse_found = 0;
	    return;	// bogus short or long pulse (maybe should adjust hysteresis?)
    }

    num_edges_since_pulse_found ++;
    if (num_edges_since_pulse_found<=0)
    	num_edges_since_pulse_found=NUM_TIMESTAMPS;	// handle rare overflow
    if (num_edges_since_pulse_found<NUM_TIMESTAMPS)
    {	// report number more pulses needed
        clear_text();
        tft.setTextColor(ILI9341_RED);
        tft.println("PULSE LOST");
        tft.setCursor(40,25);
        tft.print(NUM_TIMESTAMPS-num_edges_since_pulse_found);
        tft.println(" pulses needed");
    	return;
    }
	
    int32_t mid_period = median_period();
    clear_text();
    tft.print(60e6/period,1); tft.println(" bpm");
    tft.setCursor(40,25);
    tft.print(60e6/mid_period,1); tft.println(" bpm (median)");
    
}
Next Page »