Skip to content

Instantly share code, notes, and snippets.

@brian-lc
Last active February 29, 2016 02:10

Revisions

  1. @BreakPointer BreakPointer revised this gist Feb 29, 2016. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion emf2tone.ino
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,6 @@
    #include "math.h"

    // Config Values
    #define ADC_BITS 12 // 12-bit ADC resolution for Particle Photon
    #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)
  2. @BreakPointer BreakPointer revised this gist Feb 29, 2016. 1 changed file with 23 additions and 7 deletions.
    30 changes: 23 additions & 7 deletions emf2tone.ino
    Original file line number Diff line number Diff line change
    @@ -12,9 +12,10 @@
    #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
    #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
    @@ -39,6 +40,8 @@ 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();
    @@ -70,12 +73,24 @@ void loop() {
    if ((prevIrms < ON_THRESHOLD) && (irms > ON_THRESHOLD)){
    active_on = true;
    } else {
    // it was on and is still on, or off and is stil off
    // or check if it was on and is now off
    // 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", "alert");
    Particle.publish("kettle_status", "ready");
    }
    }

    @@ -171,6 +186,7 @@ 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);
    }
    @@ -179,6 +195,7 @@ 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()
    }
    @@ -189,18 +206,17 @@ void reportUsage(String eventName, double irms){
    Particle.publish(eventName, msg);
    }

    // will flash on/off every time it is called
    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();

    }


  3. @BreakPointer BreakPointer revised this gist Feb 26, 2016. 1 changed file with 106 additions and 49 deletions.
    155 changes: 106 additions & 49 deletions emf2tone.ino
    Original file line number Diff line number Diff line change
    @@ -1,62 +1,67 @@
    #include "neopixel/neopixel.h"
    #include "SparkIntervalTimer/SparkIntervalTimer.h"
    #include "Adafruit_TPA2016.h"
    #include "wave_data.h"
    #include "math.h"

    // Config Values
    #define ADC_BITS 12 // 12-bit ADC resolution for Particle Photon
    #define SAMP_FEQ 260 // ~3.84kHz - 64 samples per 60hz cycle
    #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

    // Setting up IO Pins
    #define AUDIO_L DAC1
    #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;
    int sample_data[ADC_SAMPLES];

    Adafruit_TPA2016 audioamp = Adafruit_TPA2016();
    const int audioL = DAC1;
    const int audioR = DAC2;
    const int amp_active = D5;
    const int led = D7;
    const int iPin = A0;
    volatile int sample_data[ADC_SAMPLES];

    double irms = 0.0;
    double prevIrms = 0.0;
    bool active_on = false;
    String status = "";

    void setup() {
    // initialize the data array
    for (int i=0; i < ADC_SAMPLES; i++){
    sample_data[i] = 2048;
    }
    sample_clock.begin(grab_sample, SAMP_FEQ, uSec);

    pinMode(led, OUTPUT);
    pinMode(amp_active, OUTPUT);

    digitalWrite(amp_active, LOW);
    audioamp.begin();
    pinMode(audioL, OUTPUT);
    pinMode(audioR, OUTPUT);
    }

    status_pixel.begin();

    void grab_sample(void) {
    sample_data[sample_ix] = analogRead(iPin);
    // reset the sample location if = to the sample count
    if (sample_ix < ADC_SAMPLES){
    sample_ix ++;
    }
    else{
    sample_ix = 0;
    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);
    }
    }

    void stop_sample(void){
    sample_clock.end();
    sample_ix = 0;
    // 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() {
    @@ -69,24 +74,54 @@ void loop() {
    // or check if it was on and is now off
    if ((irms < ON_THRESHOLD) && active_on){
    active_on = false;
    digitalWrite(amp_active, HIGH);
    start_play();
    Particle.publish("kettle_status", "alert");
    }
    }

    if (active_on){
    status = "kettle_on";
    } else {
    status = "kettle_off";
    }
    reportUsage(status, irms);

    prevIrms = irms;
    delay(1000);
    }

    void reportUsage(String eventName, double irms){
    String msg = String::format("%.3f Amps", irms);
    Particle.publish(eventName, msg);
    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
    @@ -123,8 +158,8 @@ double calcIrms()
    void play_wave(void) {
    if (wave_ix < frame_count) {
    int v = wave_data[wave_ix];
    analogWrite(audioL, v);
    analogWrite(audioR, v);
    analogWrite(AUDIO_L, v);
    analogWrite(AUDIO_R, v);
    wave_ix++;
    }
    else {
    @@ -133,17 +168,39 @@ void play_wave(void) {
    }

    void start_play(void) {
    audioamp.enableChannel(true, true);
    pause_sampler(); // added to prevent audio slow down caused by analogRead()
    wave_ix = 0;
    digitalWrite(led, HIGH);
    audio_clock.begin(play_wave, 44, uSec); //44usec ~22,050hz
    digitalWrite(OB_LED, HIGH);
    audioamp.enableChannel(true, true);
    audio_clock.begin(play_wave, AUDIO_FREQ, uSec, AUD_TIMER);
    }

    void stop_play(void) {
    analogWrite(audioL, 0);
    analogWrite(audioR, 0);
    audioamp.enableChannel(false, false);
    audio_clock.end();
    digitalWrite(led, LOW);
    audioamp.enableChannel(false, false);
    digitalWrite(OB_LED, LOW);
    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);
    }

    // will flash on/off every time it is called
    void toggleLedStatus(bool device_active){

    if (device_active){
    status_pixel.setBrightness(150);
    status_pixel.setPixelColor(0, status_pixel.Color(0,255,0));
    } else {
    status_pixel.setBrightness(0);
    status_pixel.setPixelColor(0, status_pixel.Color(0,0,0));
    }
    status_pixel.show();

    }


  4. @BreakPointer BreakPointer created this gist Feb 18, 2016.
    149 changes: 149 additions & 0 deletions emf2tone.ino
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,149 @@
    #include "SparkIntervalTimer/SparkIntervalTimer.h"
    #include "Adafruit_TPA2016.h"
    #include "wave_data.h"
    #include "math.h"

    #define ADC_BITS 12 // 12-bit ADC resolution for Particle Photon
    #define SAMP_FEQ 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

    IntervalTimer sample_clock;
    IntervalTimer audio_clock;

    volatile int sample_ix = 0;
    volatile int wave_ix = 0;
    int sample_data[ADC_SAMPLES];

    Adafruit_TPA2016 audioamp = Adafruit_TPA2016();
    const int audioL = DAC1;
    const int audioR = DAC2;
    const int amp_active = D5;
    const int led = D7;
    const int iPin = A0;

    double irms = 0.0;
    double prevIrms = 0.0;
    bool active_on = false;
    String status = "";

    void setup() {
    // initialize the data array
    for (int i=0; i < ADC_SAMPLES; i++){
    sample_data[i] = 2048;
    }
    sample_clock.begin(grab_sample, SAMP_FEQ, uSec);

    pinMode(led, OUTPUT);
    pinMode(amp_active, OUTPUT);

    digitalWrite(amp_active, LOW);
    audioamp.begin();
    pinMode(audioL, OUTPUT);
    pinMode(audioR, OUTPUT);
    }

    void grab_sample(void) {
    sample_data[sample_ix] = analogRead(iPin);
    // reset the sample location if = to the sample count
    if (sample_ix < ADC_SAMPLES){
    sample_ix ++;
    }
    else{
    sample_ix = 0;
    }
    }

    void stop_sample(void){
    sample_clock.end();
    sample_ix = 0;
    }

    void loop() {
    irms = calcIrms();
    // if it was off and is now on
    if ((prevIrms < ON_THRESHOLD) && (irms > ON_THRESHOLD)){
    active_on = true;
    } else {
    // it was on and is still on, or off and is stil off
    // or check if it was on and is now off
    if ((irms < ON_THRESHOLD) && active_on){
    active_on = false;
    digitalWrite(amp_active, HIGH);
    start_play();
    Particle.publish("kettle_status", "alert");
    }
    }
    if (active_on){
    status = "kettle_on";
    } else {
    status = "kettle_off";
    }
    reportUsage(status, irms);
    prevIrms = irms;
    delay(1000);
    }

    void reportUsage(String eventName, double irms){
    String msg = String::format("%.3f Amps", irms);
    Particle.publish(eventName, msg);
    }

    // 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(audioL, v);
    analogWrite(audioR, v);
    wave_ix++;
    }
    else {
    stop_play();
    }
    }

    void start_play(void) {
    audioamp.enableChannel(true, true);
    wave_ix = 0;
    digitalWrite(led, HIGH);
    audio_clock.begin(play_wave, 44, uSec); //44usec ~22,050hz
    }

    void stop_play(void) {
    analogWrite(audioL, 0);
    analogWrite(audioR, 0);
    audioamp.enableChannel(false, false);
    audio_clock.end();
    digitalWrite(led, LOW);
    wave_ix = 0;
    }