// Measure mains frequency (nominal 50 Hz) // and throw it out a PWM pin onto an analogue // meter movement. // // Similar to the "time for tea" artwork. // // RCS 2012 // // RA4 is used for the mains signal as it is a schmitt input // The counter is run at 1 MHz so full scale is 65 ms. // // #include "16F872.H" #pragma cdata [0x2007]=0b11.11.11.01.11.00.01 //config data, disable LV prog, no code prot, use XT osc. /* Oscillator (see datasheet p91) Last two bits: 11 = RC oscillator 10 = HS oscillator 4MHz-20MHz 01 = XT oscillator 200kHz-4MHz 00 = LP oscillator <= 200kHz */ void seconds(uns8 delay) // Seconds may be 1-15 only. delay in 10s of seconds { uns16 counter; counter=1; delay = delay << 4; // Multiply by 16, (10*2^20)us ~= (10*10^6)us = 10s while (delay != 0) // 10 instructions in this loop, so 0.65 seconds. { while (counter != 0) counter++; delay--; counter=1; } } void startup_cal(void) { CCPR1L=160; // Set PWM to max (640=160*4) CCP1CON.4=0; CCP1CON.5=0; PORTC.1=1; // and overrange LED on seconds(1); CCPR1L=80; // Set PWM to half (60*4) PORTC.1=0; // and overrange LED off seconds(1); CCPR1L=0; // Set PWM to zero PORTC.0=1; // and underrange LED on seconds(1); PORTC.0=0; // Underrange LED on } void main(void) { // Initial setup - I/O TRISA = 0b.1111.1111; // All of port A as inputs TRISB = 0b.0011.0111; // All non pgm bits input TRISC = 0b.1110.1000; // Make RC2 an ouput (will be used for PWM), RC4 will blink an LED at 1/64 mains freq. // RC0 and RC1 will be out-of-range indicators // Initial setup - Timer T1CON=0b00000000; // Divider set to 1 - correct for 4 MHz crystal TMR1H=0; TMR1L=0; // Initial setup - PWM CCP1CON=0b00001100; PR2=0xFF; T2CON=0b00000100; // With 4 MHz crystal this should give 3.9 kHz PWM uns16 count,count_old,pwm; uns8 blink_counter; bit pwm_lsb; count=20000; count_old=20000; // Perfect 50Hz blink_counter=0; PORTC.4=1; // LED on startup_cal(); while (1) // Infinite loop. Measures every mains cycle and keeps a rolling average. { if ((count>=19920)&&(count<=20080)) // If the count is in range { PORTC.0=0; // Both out-of-range outputs off nop(); PORTC.1=0; // Do all the calculations, etc. count=count+count_old; count=count/2; // Rolling average count_old=count; /* 49.5 Hz: count = 20202 49.8 Hz: count = 20080 50.0 Hz: count = 20000 50.2 Hz: count = 19920 50.5 Hz: count = 19802 */ pwm=20080-count; // PWM from 0 to 160, 49.8Hz=0, 50.2Hz=160, 50.5Hz=80 pwm=160-pwm; // Flip scale right to left because the meter is physically upside-down CCP1CON.4=0; CCP1CON.5=0; //pwm = pwm >> 2; CCPR1L=pwm; // Output from 0/1024 to 640/1024 in steps of 4 - to do // this frequency range with better resolution use faster crystal } else { if (count<20000) // Slower than 49.8 Hz ********* Note changed > to < as part of left-right flip { CCPR1L=0; // Set PWM output to zero CCP1CON.4=0; CCP1CON.5=0; PORTC.0=1; // Signal underrange nop(); PORTC.1=0; } else // Faster than 50.2 Hz { CCPR1L=160; // Set PWM to max (640=160*4) CCP1CON.4=0; CCP1CON.5=0; PORTC.0=0; // Signal overrange nop(); PORTC.1=1; } } if (blink_counter.6==1) PORTC.4=1; else PORTC.4=0; blink_counter++; // Blink an LED at 1/64th the mains frequency while (PORTA.4); // Loop doing sod-all until input goes low while (!PORTA.4); // Loop doing sod-all until input goes high T1CON.0=0; // Stop timer1 count=256*TMR1H+TMR1L; // Read timer1 TMR1H=0; TMR1L=0; // Clear timer1 T1CON.0=1; // Start timer1 /* Note that this stopping, reading and resetting of the timer adds a small delay and introduces an error into the count. If it is known how many clock cycles these operations take then these can be added to the count */ count=count+13; // Check this in the assembly! } // End infinite loop } // End main