Gas station without pumps

2016 April 16

Santa Cruz Mini Maker Faire went well

The first Santa Cruz Mini Maker Faire seemed to go well.  I did not get to see much of it, since I was busy at my booth most of the day, though I did get a break for lunch while my assistant Henry manned the booth, and I made a quick tour of the exhibits during that break, to see what was there, though with no time to chat with other exhibitors.

I understand that about 1800 people bought tickets to the Mini Maker Faire, which probably means there were over 2000 people on-site, including volunteers and makers.  I hope the food vendors did OK—I ate at the Ate3One truck, since I never have before, but my opinion afterwards was that CruzNGourmet and Zameen have better food (both of those trucks are frequently on campus, and I’ve eat at each several times).

My day went pretty well, though I had one annoying problem, having to do with my pulse monitor display. When I set up the booth Friday evening, the pulse monitor was not working, and I thought that the phototransistor had somehow been broken in the rough ride in the bike trailer, so I brought the pulse monitor home, replaced the phototransistor and tested in thoroughly.  Everything worked great, so I packed it more carefully for transport in the morning.

When I got everything set up Saturday morning, I found I had no electricity, though the electricity had worked fine the night before.  After I finally tracked down a staff member with the authority to do anything about it, he suggested unplugging the other stuff plugged in and switching outlets.  I turned out that the only problem was that the outlets were so old and worn out that they no longer gripped plugs properly—taping the extension cord to the outlet box so that the weight of the cord didn’t pull out the plug fixed the power problem.

Once I had power, I tested the pulse monitor, and it failed again!  I used the oscilloscope to debug the problem, and found that the first stage transimpedance amplifier was saturating—there was too much light in the room, and even shading the pulse monitor didn’t help. By then, my assistant for the day (and my group tutor for the class on campus), Henry, had arrived and gotten the parking permit on his car, so I raced home on my bike to get resistors, capacitors, op amp chips, multimeters, hookup wire,and clip leads to try to rebuild the pulse monitor from scratch on the bread board.

When I got back to Gateway School, I tried a simple fix before rebuilding everything—I added a pair of clip leads to the board so that I could add a smaller resistor in parallel with the feedback resistor in the transimpedance amplifier, reducing the gain by a factor of about 30.  This reduced gain kept the first stage from saturating, and the pulse monitor worked fine.  Rather than rebuild the amplifier, I just left the pair of clip leads and the resistor in place all day—they caused no problem despite many people trying out the pulse monitor.

I think that I want to redesign the pulse monitor with a logarithmic first stage, so that it will be insensitive to ambient light over several decades of light.  That should be an easy fix, but I’ll have to test it to make sure it works. I don’t think I’ll have time this weekend or next to do that, but I’ll add it to my to-do list.

I’ll need to think about whether to include having a logarithmic response in the textbook—that is certainly more advanced than what I currently include (just a transimpedance amplifier), which is already pushing students a bit.  A transimpedance amplifier is a pretty common component in bioelectronics, so I really want to leave one in the course.  I’m not sure a logarithmic amplifier is important enough or simple enough to include at this level (I don’t currently cover the non-linearity of diodes).

 

Here is the booth display with my assistant, Henry. I was permitted to use painter's tape to attach the banner to the whiteboard.

Here is the booth display with my assistant, Henry. I was permitted to use painter’s tape to attach the banner to the whiteboard.

The magenta laptop on right (which my family refers to as the “Barbie laptop”) was a used Windows laptop that I bought for testing out PteroDAQ installation on Windows. It was set up with PteroDAQ running all day, recording a voltage from a pressure sensor and a frequency from a hysteresis oscillator (as a capacitance touch center).

Just to the left of that was a fairly bright stroboscope, using 20 of my constant-current LED boards. To its left is my laptop, displaying the current draft of my book. Behind (and above) the laptop is my desk lamp, which uses the same electronic hardware as the stroboscope, though with only 6 LED boards, not 20.

In front of the laptop is the pulse monitor, which includes a TFT display in an improvised foamcore stand. I used just a half block for the pulse sensor, relying on ambient light (sunlight and the desk lamp) for illuminating the finger.

To the left of the pulse monitor was a stack of business cards for my book and sheets of paper with my email address and URLs for this blog and the book.  I should have included the PteroDAQ URL as well, but I had forgotten to do so. I did tell a lot of people how to find PteroDAQ from the navigation bar of my blog, but putting it on the handout would have been better. Ah well, something to fix next year (if Gateway is crazy enough to do another Mini Maker Faire, which I hope they are).

I also had all my bare PC boards that I had designed and not populated, plus my two Hexmotor H-bridge boards, behind the business cards. One of the amplifier prototyping boards was displaying in the Panavise that I use for soldering.

On the far left of the table is my Kikusui oscilloscope and two function generators, set up to generate Lissajous figures.  I let kids play with the frequencies of the function generators, take their pulse with the pulse monitor, and play with the pressure sensor and the capacitive touch sensor.

My booth was not the most popular of the Faire by any means (certainly the R2 Makers Club in the next booth was more popular), but I was kept busy all day and I talked with a lot of people who seemed genuinely interested in what I was doing, both with the UCSC course and as a hobbyist.

Advertisements

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)");
    
}

Create a free website or blog at WordPress.com.

%d bloggers like this: