Skip to content

Instantly share code, notes, and snippets.

@brian-lc
Last active February 29, 2016 02:10
Show Gist options
  • Save brian-lc/1eb9867eb261f669ca25 to your computer and use it in GitHub Desktop.
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
#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