Last active
February 29, 2016 02:10
-
-
Save brian-lc/1eb9867eb261f669ca25 to your computer and use it in GitHub Desktop.
Prototype code for EMF sensing to tone based alert on Particle Photon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "neopixel/neopixel.h" | |
#include "SparkIntervalTimer/SparkIntervalTimer.h" | |
#include "Adafruit_TPA2016.h" | |
#include "wave_data.h" | |
#include "math.h" | |
// Config Values | |
#define AUDIO_FREQ 44 //44usec ~22,050hz | |
#define SAMP_FREQ 260 // ~3.84kHz - 64 samples per 60hz cycle | |
#define ADC_SAMPLES 256 // 4 full cycles captured (256 makes sample division a bit shift operation) | |
#define ON_THRESHOLD 1.0 // Device is on when current goes over 1 amp | |
#define SAMP_TIMER TIMER5 | |
#define AUD_TIMER TIMER6 | |
#define MAX_LONG 2147483647 | |
// Setting up IO Pins | |
#define AUDIO_L DAC1 // No stereo sound (yet) but we use both DAC pins in the circuit | |
#define AUDIO_R DAC2 | |
#define AMP_ACTIVE D5 | |
#define OB_LED D7 | |
#define I_SENSE A0 | |
#define PIXEL_PIN D3 | |
// NEO PIXELS Set pixel COUNT, PIN and TYPE | |
#define PIXEL_COUNT 1 | |
#define PIXEL_TYPE WS2812 | |
SYSTEM_MODE(AUTOMATIC); | |
Adafruit_NeoPixel status_pixel = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE); | |
Adafruit_TPA2016 audioamp = Adafruit_TPA2016(); | |
IntervalTimer sample_clock; | |
IntervalTimer audio_clock; | |
volatile int sample_ix = 0; | |
volatile int wave_ix = 0; | |
volatile int sample_data[ADC_SAMPLES]; | |
double irms = 0.0; | |
double prevIrms = 0.0; | |
bool active_on = false; | |
String status = ""; | |
unsigned long last_off_time = MAX_LONG; | |
long time_delta; | |
void setup() { | |
audioamp.begin(); | |
status_pixel.begin(); | |
pinMode(OB_LED, OUTPUT); | |
pinMode(AMP_ACTIVE, OUTPUT); | |
digitalWrite(AMP_ACTIVE, HIGH); | |
pinMode(AUDIO_L, OUTPUT); | |
pinMode(AUDIO_R, OUTPUT); | |
for(int i = 0; i < 8; i++){ | |
toggleLedStatus(true); | |
delay(100); | |
toggleLedStatus(false); | |
delay(50); | |
} | |
// can't start samples before we flash the LED's | |
// Not sure why but it causes the problems with the NeoPixels | |
start_samples(); | |
// boot sound | |
start_play(); | |
} | |
void loop() { | |
irms = calcIrms(); | |
// if it was off and is now on | |
if ((prevIrms < ON_THRESHOLD) && (irms > ON_THRESHOLD)){ | |
active_on = true; | |
} else { | |
// else it was on and is still on (or off and still off) | |
// Check if it was on and is now off | |
if ((irms < ON_THRESHOLD) && active_on){ | |
// Note: My tea kettle cycles the power on/off as it | |
// gets closer to the desired temperature. From analysis | |
// it seems to cycle on/off with about 5 seconds in between periods. | |
// Storing that time when it shut off | |
last_off_time = millis(); | |
active_on = false; | |
} | |
// Checking to see if it was off more than 10 seconds ago | |
// Note: If continually powered, and attached device is not used for ~24 days this will trigger continuously | |
time_delta = (long) (millis() - last_off_time); | |
if (time_delta >= 10000){ | |
last_off_time = MAX_LONG; // setting to max long value so that we don't continually trigger the sound after the tea is done | |
start_play(); | |
Particle.publish("kettle_status", "ready"); | |
} | |
} | |
if (active_on){ | |
status = "kettle_on"; | |
} else { | |
status = "kettle_off"; | |
} | |
reportUsage(status, irms); | |
prevIrms = irms; | |
delay(1000); | |
} | |
void start_samples(){ | |
// initialize the data array | |
for (int i=0; i < ADC_SAMPLES; i++){ | |
sample_data[i] = 2048; // 2048 is the mid-point voltage value | |
} | |
resume_sampler(); | |
} | |
void stop_sample(){ | |
pause_sampler(); | |
sample_ix = 0; | |
} | |
void resume_sampler(){ | |
sample_clock.begin(grab_sample, SAMP_FREQ, uSec, SAMP_TIMER); | |
} | |
void pause_sampler(){ | |
sample_clock.end(); | |
} | |
void grab_sample() { | |
// aquiring the analog reading slows down the timers for some reason. | |
// To work around this issue we'll just pause sampling while audio is playing | |
sample_data[sample_ix] = analogRead(I_SENSE); | |
// reset the sample location if = to the sample count | |
if (sample_ix < ADC_SAMPLES){ | |
sample_ix ++; | |
} | |
else{ | |
sample_ix = 0; | |
} | |
} | |
// Will calculate the RMS with whatever | |
// values are in the data sample buffer | |
double calcIrms() | |
{ | |
double Irms; | |
double Vrms; | |
uint32_t vSum = 0; | |
uint32_t vAvg = 0; | |
double vVal = 0; | |
int vAdj = 0; | |
for (int n = 0; n < ADC_SAMPLES; n++){ | |
vAvg += sample_data[n]; | |
} | |
// vAvg is the mid-point of the voltage over the samples | |
// subtracting the midpoint from the measured voltages | |
// gives us the +/- voltages to use for the Vrms | |
vAvg = vAvg >> 8; // int val offset. Divide by number of samples (256) | |
for (int n = 0; n < ADC_SAMPLES; n++) | |
{ | |
vAdj = sample_data[n] - vAvg; // int val adjusted measurment | |
vSum += (vAdj * vAdj); // Squaring the value, adding it to the accumulator | |
} | |
vVal = vSum >> 8; // divide by number of samples (256); | |
Vrms = sqrt(vVal) * 0.0008; // 12-bit ADC w/ 3.3 maxV gives 3.3V/4096 units | |
Irms = Vrms * 20.15; | |
return Irms; // Iprimary = Isecondary * CT ratio | |
} | |
void play_wave(void) { | |
if (wave_ix < frame_count) { | |
int v = wave_data[wave_ix]; | |
analogWrite(AUDIO_L, v); | |
analogWrite(AUDIO_R, v); | |
wave_ix++; | |
} | |
else { | |
stop_play(); | |
} | |
} | |
void start_play(void) { | |
pause_sampler(); // added to prevent audio slow down caused by analogRead() | |
wave_ix = 0; | |
digitalWrite(OB_LED, HIGH); | |
toggleLedStatus(true); // visual indicator | |
audioamp.enableChannel(true, true); | |
audio_clock.begin(play_wave, AUDIO_FREQ, uSec, AUD_TIMER); | |
} | |
void stop_play(void) { | |
audio_clock.end(); | |
audioamp.enableChannel(false, false); | |
digitalWrite(OB_LED, LOW); | |
toggleLedStatus(false); | |
wave_ix = 0; | |
resume_sampler(); // added to prevent audio slow down caused by analogRead() | |
} | |
void reportUsage(String eventName, double irms){ | |
String msg = String::format("%.3f Amps", irms); | |
Particle.publish(eventName, msg); | |
} | |
void toggleLedStatus(bool device_active){ | |
if (device_active){ | |
// On | |
status_pixel.setBrightness(150); | |
status_pixel.setPixelColor(0, status_pixel.Color(0,255,0)); | |
} else { | |
// Off | |
status_pixel.setBrightness(0); | |
status_pixel.setPixelColor(0, status_pixel.Color(0,0,0)); | |
} | |
status_pixel.show(); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment