Power Grid Monitor 2

This is a minor sequel of my power grid monitoring experiment. The first monitoring experiment excelled at visualizing the phase of the grid. Thus it allowed to indirectly infer the current frequency. However it did not provide any means to actually measure the frequency let alone transfer the results to a computer. This new experiment fixes this issue.

I use the same circuit as in the first experiment to decouple the Arduino from the power grid. But this time the pulses are fed into pin 8 (=input capture). That is I want to trigger the “input capture interrupt” whenever a pulse is detected. “Input capture” means that the processor will capture the exact value of the 16-bit hardware timer 1 in the “input capture register”. Thus the value will not depend on how fast my code reacts to interrupt (as long as it is fast enough to deal with the capture before the next interrupt is triggered).

Simple Driver

The code is not very complicated but there are some note worthy things about it.

//
//  www.blinkenlight.net
//
//  Copyright 2012 Udo Klein
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see http://www.gnu.org/licenses/


//  input capture = pin8 --> now how to exploit the display as good as possible 

// The following layout will be used ('X' indicates on, ' ' indicates off),
// * indicates IO pins

// Pins
// 01 00 08 --> used of serial connection and input capture IO
//  *  *  *

// Pins
// 07 06 05 04 03 | Unit of Deviation
//              X |     1 mHz         --> this is quite normal            
//           X  X |    10 mHz         --> this is also common 
//        X  X  X |   100 mHz         --> deviations >200mHz are not good and >800mHz are really really bad
//     X  X  X  X |  1000 mHz = 1 Hz  --> this is already in a black down
//  X  X  X  X  X | 10000 mHz = 10 Hz --> this is unheard of

const uint8_t pin_magnitude_low  = 3;
const uint8_t pin_magnitude_high = 7;

const uint8_t pin_l = 19;
const uint8_t pin_r = 9;

const uint8_t input_capture_pin = 8;

// Pins 
// 19 18 17 16 15 14 13 12 11 10 09 | Absolute Deviation   | Sign of Deviation
//  X                               | >  0 units, < 1 units | to slow
//  X  X                            | >= 1 units, < 2 units | to slow  
//  X  X  X                         | >= 2 units, < 3 units | to slow  
//  X  X  X  X                      | >= 3 units, < 4 units | to slow  
//  X     X  X  X                   | >= 4 units, < 5 units | to slow  
//  X        X  X  X                | >= 5 units, < 6 units | to slow  
//  X           X  X  X             | >= 6 units, < 7 units | to slow  
//  X              X  X  X          | >= 7 units, < 8 units | to slow  
//  X                 X  X  X       | >= 8 units, < 9 units | to slow  
//  X                    X  X  X    | >= 9 units, <10 units | to slow   
//  X                       X  X  X | >=10 units, <11 units | to slow
//  X                          X  X | >=11 units            | to slow

//                                X | >  0 units,< 1 units | to fast
//                             X  X | >= 1 unit, < 2 units | to fast
//                          X  X  X | >= 2 unit, < 3 units | to fast
//                       X  X  X  X | >= 3 unit, < 4 units | to fast
//                    X  X  X     X | >= 4 unit, < 5 units | to fast
//                 X  X  X        X | >= 5 unit, < 6 units | to fast
//              X  X  X           X | >= 6 unit, < 7 units | to fast
//           X  X  X              X | >= 7 unit, < 8 units | to fast
//        X  X  X                 X | >= 8 unit, < 9 units | to fast
//     X  X  X                    X | >= 9 unit, <10 units | to fast
//  X  X  X                       X | >=10 unit, <11 units | to fast
//  X  X                          X | >=11 unit            | to fast

        

// helper type for efficient access of the counter structure 
typedef union {
    uint32_t clock_ticks;
    struct {
        uint8_t  byte_0;
        uint8_t  byte_1;
        uint16_t word_1;
    };  
} converter;


// The timer values will be initialized nowhere.
// This works because we are intested in the differences only.
// The first differences will be meaningless due to lack
// of initialization. However they would be meaningless anyway
// because the very first capture has (by definition) no predecessor.
// So the lack of initialization semantically reflects this.
volatile converter input_capture_time;
volatile uint16_t timer1_overflow_count;

// 0 indicates "invalid"
volatile uint32_t period_length = 0;
volatile bool next_sample_ready = false;

ISR(TIMER1_OVF_vect) {
    ++timer1_overflow_count;
}

ISR(TIMER1_CAPT_vect) { 
    static uint32_t previous_capture_time = 0;
  
    // according to the datasheet the low byte must be read first
    input_capture_time.byte_0 = ICR1L; 
    input_capture_time.byte_1 = ICR1H;   
                            
    if ((TIFR1 & (1<<TOV1) && input_capture_time.byte_1 < 128)) { 
        // we have a timer1 overflow AND 
        // input capture time is so low that we must assume that it 
        // was wrapped around
      
        ++timer1_overflow_count;     
        
        // we clear the overflow bit in order to not trigger the 
        // overflow ISR, otherwise this overflow would be
        // counted twice
        TIFR1 = (1<<TOV1);
    }   
    input_capture_time.word_1 = timer1_overflow_count;
    
    period_length = input_capture_time.clock_ticks - previous_capture_time;
    previous_capture_time = input_capture_time.clock_ticks;
    
    next_sample_ready = true;
}                 
                  
void initialize_timer1() {
 
    // Timer1: "normal mode", no automatic toggle of output pins
    //                        wave form generation mode  with Top = 0xffff
    TCCR1A = 0;                

    // Timer 1: input capture noise canceler active
    //          input capture trigger on rising edge
    //          clock select: no prescaler, use system clock 
    TCCR1B = (1<<ICNC1) | (1<< ICES1) | (1<<CS10);
    
    // Timer1: enable input capture and overflow interrupts
    TIMSK1 = (1<<ICIE1) | (1<<TOIE1);

    // Timer1: clear input capture and overflow flags by writing a 1 into them    
    TIFR1 = (1<<ICF1) | (1<<TOV1) ;            
}    
                  
void visualize_frequency_deviation(const uint8_t target_frequency, const uint32_t period_length) {
  
    const int64_t deviation_1000 = 1000*(int64_t)F_CPU / period_length - 1000*(int64_t)target_frequency;
    
    //Serial.print(F("period length  "));
    //Serial.println(period_length);
    
    static int8_t sign = 1;
    if (deviation_1000 != 0) {
        // only compute new sign for frequency deviation != 0
        sign = deviation_1000 >= 0? 1: -1;
    }
    
    //Serial.print(F("deviation: "));
    Serial.println((int32_t)deviation_1000);
            
    const uint64_t value = abs(deviation_1000);     
        
    // magnitude is static in order to introduce some hysteresis into the computation
    static uint8_t magnitude = 1;
    magnitude = value >= 12000? 5:
                value >= 10000? (magnitude >= 5? 5: 4):
                value >=  1200? 4:
                value >=  1000? (magnitude >= 4? 4: 3):
                value >=   120? 3:
                value >=   100? (magnitude >= 3? 3: 2):
                value >=    12? 2:
                value >=    10? (magnitude >= 2? 2: 1):
                                1; 

    //Serial.print(F("magnitude: ")); Serial.println(magnitude);
    
    for (uint8_t pin = pin_magnitude_low; pin <= pin_magnitude_high; ++pin) {
        digitalWrite(pin, pin-pin_magnitude_low < magnitude);
    }
    
    
    uint32_t divider = 1;
    for (uint8_t power = 1; power < magnitude; ++power) {    
        divider *= 10;
    }
   
    const uint8_t output_value = value / divider;

    //Serial.print(F("sign: ")); Serial.println(sign);
    //Serial.print(F("value: ")); Serial.println(output_value, DEC);

    if (sign <= 0) {                
        digitalWrite(pin_r, HIGH);
        
        uint8_t pin = pin_r + 1;        
        while (pin <= pin_l && pin <= pin_r - 3 + output_value) {
            digitalWrite(pin++, LOW);
        }
        while (pin <= pin_l && pin <= pin_r + output_value ) {
            digitalWrite(pin++, HIGH);          
        }
        while (pin <= pin_l) {
            digitalWrite(pin++, LOW);
        }
    } else {
        digitalWrite(pin_l, HIGH);
        
        uint8_t pin = pin_l - 1;        
        while (pin >= pin_r && pin >= pin_l + 3 - output_value) {
            digitalWrite(pin--, LOW);
        }
        while (pin >= pin_r && pin >= pin_l - output_value) {
            digitalWrite(pin--, HIGH);          
        }
        while (pin >= pin_r) {
            digitalWrite(pin--, LOW);
        }      
    }    
}  
                  
void setup() {
  
    Serial.begin(115200);
  
    for (uint8_t pin = pin_magnitude_low; pin <= pin_magnitude_high; ++pin) {
        pinMode(pin, OUTPUT);
        digitalWrite(pin, HIGH);
    }
  
    for (uint8_t pin = pin_r; pin <= pin_l; ++pin) {
        pinMode(pin, OUTPUT);
        digitalWrite(pin, HIGH);
    }

    // just to indicate which pins will be the output pins
    // this "output test" makes it easier to wire the circuit
    delay(1000);

    pinMode(input_capture_pin, INPUT);
    digitalWrite(input_capture_pin, HIGH);

    initialize_timer1();
}

const uint8_t target_frequency = 50;
const uint8_t sample_buffer_size = 50;

uint32_t sample_buffer[sample_buffer_size];

void loop() {  
    static uint8_t sample_index = 0;
    
    if (next_sample_ready) {
        next_sample_ready = false;
        
        cli();
        sample_buffer[sample_index] = period_length;    
        sei();
        sample_index = sample_index > 0? sample_index - 1: sample_buffer_size - 1;
    
        uint32_t average_period_length = 0;
        for (uint8_t index = 0; index < sample_buffer_size; ++index) {
            average_period_length += sample_buffer[index];
        }
        average_period_length /= sample_buffer_size;        
        
        visualize_frequency_deviation(target_frequency, average_period_length);
    }    
}

The heavy lifting for this is done in the two interrupt service routines: ISR(TIMER1_CAPT_vect) and ISR(TIMER1_OVF_vect). In theory it would be sufficient to just read the input capture register and subtract the previous value to figure out the period length and thus the frequency. However there are some complications. First of all timer 1 is only a 16 bit timer. Since I run it at full clock speed (=maximum time resolution) it will wrap around several times per period. So I need to deal with the overflow of this timer as well. Pretty easy just add another interrupt to count the overflows.

This adds a new complication. What happens if input cature and overflow trigger at “the same time”? Or just half a cycle apart? Apparently it is unclear if the overflow or the input capture would be processed first. This implies that the overflow count may not reflect the proper value. So how can we figure out what the proper value of the overflow count actually should be?

Let’s see:
1) if the input capture is not triggered I do not need to deal with any special cases. Easy.
2) if input capture is triggered but no overflow is pending then there is nothing to do as well.
3) if input capture is triggered AND some overflow is pending, then there is something to do.

OK, so how do we know that we are in situation (3)? Well, inside the input capture ISR we check if the timer 1 overflow bit is set. If it is set then we know that the input capture and the overflow were triggered close to each other. Which now leaves the question which one was triggered first. If the overflow happened before the input capture we should increase the overflow counter. If the input capture happened before we should not increase it. Now here comes the crucial point. The overflow will always trigger when the timer wraps around. So if input capture is triggered shortly after the wrap around it will contain a value close to 0. If it was triggered shortly before the wrap around it will contain a value close to 0xffff. So looking at the size of the input capture value will tell us if it was captured before or after the wrap around.

In case we find that the input capture was triggered after the wrap around then we process the wrap around code in place. That is we increment the overflow counter AND clear the overflow bit. In case you may wonder why we need to clear this bit: otherwise the overflow ISR would be processed. Processing this ISR implicitly clears the overflow bit. This is a feature of the processor. Whenever it processes the overflow Interrupt then it will clear the overflow bit. Notice that directly calling the ISR does not constitute “processing the Interrupt”. Hence the bit must be cleared explicitly since we are in the input capture ISR.

In case input capture was triggered before the wrap around we just ignore the wrap around. This will leave the overflow bit set and thus trigger the overflow ISR after the input capture is handled.

Another cool feature is this declaration.

typedef union {
    uint32_t clock_ticks;
    struct {
        uint8_t  byte_0;
        uint8_t  byte_1;
        uint16_t word_1;
    };  
} converter;

The “union” declaration tells the compiler that the two data types of the union will share the same memory location. That is the structure will take up 4 bytes that can be addressed in two different ways. Either as clock_ticks or as byte_0, byte_1 and word_1. Thus the conversion of timer values and the additional 16 bit counter into a 32 bit integer is handled by just storing the bytes in the proper locations.

With this in place I can sample quite easily. However as it turns out there is a lot of phase jitter if I sample at maximum speed. Thus I compute the floating average of 50 periods to determine the “current” frequency.

Of course it is easily possible to render the values to the serial port. However I just put them into some kind of crude display. Nothing fancy but enough to get an idea of what is going on. See below for a picture of a deviation of 40 mHz. Notice that LED 3 and 4 are on thus indicating that the display will be for 10 mHz steps. Have a look at my code and the picture and you will get the encoding.

Power Grid Monitoring

In both the picture and the video I have the frequency counter as a reference. Of course the whole point is that this works just as well without a frequency counter. Notice that the grid was pretty stable while I was filming the video. So there is not a lot of action going on.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.