1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// 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