📄 avr465.c
字号:
// stored in TC1ext. The extended counter has a maximum pulse period of:
//
// Presc
// Tmax = ------- x ( FFFF x FFFF + FFFF ) = >1Ms = >305h = >12d
// fCLK
//
// ...but, in practice active power is either zero or above I_MIN x Unom.
// Assuming I_MIN = 0.002 and Unom = 230V, and high meter constant:
//
// 4000000 3600
// Tmax = --------- x ------------------------------- = 3057s = <1h
// 1024 0.002A x 230V x 10000 imp/kWh
#define Tmax 0xFFFE0000
float T;
unsigned int CtrTemp,ExtCtrTemp;
char Mask;
Power=fabs(Power); // Pulse interval contains no sign information
if (Power<0.0001) // Divide by zero is not defined in this universe
T=Tmax;
else
T=Tnum/Power; // Precalculated numerator assumes power in watts
if (T<(2*OCR1B))
T=2*OCR1B; // Min interval = twice pulse high time
if (T>Tmax)
{
ExtCtrTemp=0xFFFF; // Max count
CtrTemp=0xFFFF; // Max count
}
else
{
ExtCtrTemp=T/0xFFFF; // Number of 16-bit timer cycles
CtrTemp=T-(ExtCtrTemp*0xFFFF); // Ticks in single/last cycle
}
Mask=TIMSK1;
TIMSK1=0x00; // Disable T/C1 interrupts for a moment
if (TC1extTOP>0)
{ // Currently timer is in the middle of an extended count
TC1extTOP=ExtCtrTemp; // Set new TOP for extended count
if (TC1ext>TC1extTOP)
TC1ext=TC1extTOP; // Fit counter value in new frame
if (TC1ext==TC1extTOP)
{ // Last extended count cycle
OCR1A=CtrTemp; // Set TOP value for last cycle
TCCR1A=0xC0; // Set mode: set OC1A on next compare match
DPCIncrement=1; // Do increment display counter on next match
}
else
{ // At least one more extended cycle to go
OCR1A=0xFFFF; // Set TOP value for last cycle
TCCR1A=0x80; // Set mode: clear OC1A on next match
DPCIncrement=0; // Do not increment displ cntr on next match
}
}
else
{ // Currently timer runs unextended
OCR1A=CtrTemp; // Set new TOP value
if (ExtCtrTemp>0)
{ // Next cycle must run extended
TC1extTOP=ExtCtrTemp; // Set new TOP for extended count
TC1ext=TC1extTOP; // Pretend we are at end of extended cycle
}
TCCR1A=0xC0; // Set mode: set OC1A on next compare match
DPCIncrement=1; // Do increment display counter on next match
}
TC1extRem=CtrTemp; // Set TOP value for future last cycles
if (TCNT1>=OCR1A)
TCNT1=(OCR1A-1); // Rewind, if counter is larger than TOP
TIMSK1=Mask; // Restore T/C1 interrupts
}
/////////////////////////////////////////////////////////////////////////////
// M A I N
/////////////////////////////////////////////////////////////////////////////
void main(void)
{
unsigned long TempUL;
// Check source of reset
if (MCUSR & 1) // Power-on Reset
{
// put POR handler here, if required
}
else if (MCUSR & 2) // External Reset
{
// put external reset handler here, if required
}
else if (MCUSR & 4) // Brown-Out Reset
{
// put BOR handler here, if required
}
else // Watchdog Reset
{
// put watchdog reset handler here, if required
};
MCUSR=0;
Initialise(); // set up I/O registers, flags & variables
ReadCalibration(); // interrupts must be disabled when accessing EEPROM
InitGainControl(); // configure variable gain
SetGain(ADC0,LOW); // start from low gain
SetGain(ADC1,LOW); // start from low gain
asm("wdr"); // reset WD counter
WDTCSR |= (1<<WDCE) | (1<<WDE); // enable watchdog interrupt
WDTCSR = (1<<WDE) | (1<<WDP2) | (1<<WDP1); // set 128K cycles (~1.0 s)
asm("sei"); // allow all enabled interrupts
printf("\n\r");
printf("AVR465\n\r");
printf("\n\r");
printf("PCC: 0x%04X 0x%04X 0x%04X\n\r",CalCoeff.PCC[0],CalCoeff.PCC[1],CalCoeff.PCC[2]);
printf("ILG: %8.6f %8.6f %8.6f\n\r",CalCoeff.ILG[0],CalCoeff.ILG[1],CalCoeff.ILG[2]);
printf("ING: %8.6f %8.6f %8.6f\n\r",CalCoeff.ING[0],CalCoeff.ING[1],CalCoeff.ING[2]);
printf("UG: %8.6f\n\r",CalCoeff.UG);
printf("MC: %8u\n\r",CalCoeff.MC);
printf("DPC: %8u\n\r",CalCoeff.DPC);
printf("\n\r");
while (1)
{
if (Flags&KEY_PRESSED)
{
// Insert key service functions here
switch (getchar())
{
case 'd':
case 'D': if (Flags&DISPLAY)
Flags=Flags&(0xFF-DISPLAY);
else
Flags=Flags|DISPLAY;
break;
}
}
// When a full cycle of data has been accumulated; make a copy of accumulated
// data before it is overwritten by next sampling instance. Process accumulated
// data as follows:
//
// Active power: P = (accumulated data) / (number of samples)
// Voltage: U = SQRT [ (accumulated data) / (number of samples) ]
// Current: I = SQRT [ (accumulated data) / (number of samples) ]
// Apparent power: S = U * I
//
// All data processing must be complete before next accumulation cycle is
// completed.
if (Flags&CYCLE_FULL)
{
Flags=Flags&(0xFF-CYCLE_FULL);
// Make a copy of accumulated data before it is overwritten
Sum=Accumulator;
// Clear accumulated data
Accumulator.PL=0;
Accumulator.PN=0;
Accumulator.U2=0;
Accumulator.IL2=0;
Accumulator.IN2=0;
// Save gain settings that was used with this data
Result.ADC0_Gain=ADC0_Gain;
Result.ADC1_Gain=ADC1_Gain;
// Set gain for next set of measurements
if (Flags&LESSGAIN0)
switch (ADC0_Gain)
{
case MEDIUM: SetGain(ADC0,LOW);
break;
case HIGH: SetGain(ADC0,MEDIUM);
break;
}
if (Flags&MOREGAIN0)
switch (ADC0_Gain)
{
case LOW: SetGain(ADC0,MEDIUM);
break;
case MEDIUM: SetGain(ADC0,HIGH);
break;
}
if (Flags&LESSGAIN1)
switch (ADC1_Gain)
{
case MEDIUM: SetGain(ADC1,LOW);
break;
case HIGH: SetGain(ADC1,MEDIUM);
break;
}
if (Flags&MOREGAIN1)
switch (ADC1_Gain)
{
case LOW: SetGain(ADC1,MEDIUM);
break;
case MEDIUM: SetGain(ADC1,HIGH);
break;
}
InitGainControl();
// Calculate. Use accumulated data to derive active power and current
// for live and neutral wires. Also derive voltage between live and
// neutral wire.
// Start by adding offset to accumulated data. This will improve non-
// linearities at small amplitudes. Provided that the offset is
// sufficiently small, that is!
if (Sum.PL>0)
Result.PL=Sum.PL*NORM+OFFSET;
else
Result.PL=Sum.PL*NORM-OFFSET;
if (Sum.PN>0)
Result.PN=Sum.PN*NORM+OFFSET;
else
Result.PN=Sum.PN*NORM-OFFSET;
TempUL=(Sum.U2+OFFSET)*NORM;
Result.U=sqrt(TempUL);
TempUL=(Sum.IL2+OFFSET)*NORM;
Result.IL=sqrt(TempUL);
TempUL=(Sum.IN2+OFFSET)*NORM;
Result.IN=sqrt(TempUL);
// Calibrate. Take measurement results and adjust them according to
// characteristics of this particular meter.
Result.PL=Result.PL*CalCoeff.ILG[Result.ADC0_Gain];
Result.IL=Result.IL*CalCoeff.ILG[Result.ADC0_Gain];
Result.PN=Result.PN*CalCoeff.ING[Result.ADC1_Gain];
Result.IN=Result.IN*CalCoeff.ING[Result.ADC1_Gain];
Result.PL=Result.PL*CalCoeff.UG;
Result.PN=Result.PN*CalCoeff.UG;
Result.U=Result.U*CalCoeff.UG;
// Anti-creep: reset power readings when current below threshold.
// This is to make sure no pulses are emitted when signal is too low.
// Don't want to bill the customer for noise in input channels...?
if (Result.IL<I_MIN)
Result.PL=0.0;
if (Result.IN<I_MIN)
Result.PN=0.0;
// Tampering logic activated when either active power above threshold.
// This is to prevent tamper indicators to go on and off at very low
// power readings.
if ((fabs(Result.PL)>P_MIN) | (fabs(Result.PN)>P_MIN))
{
// Check for earth fault, i.e. verify that all current which is going
// into the load via the live wire also returns via the neutral wire.
// Set tamper indicator as follows:
//
// EARTH | Condition
// -------+-------------------------------------------
// Low | P(L) and P(N) are almost same in magnitude
// High | P(L) and P(N) are not same in magnitude
//
// It doesn't matter if the user swappes live and neutral wires.
// Use the larger power reading for billing (energy pulse output).
if (fabs(Result.PL)>fabs(Result.PN))
{ // |P(L)| > |P(N)|
if (fabs(Result.PL)>(1.05*fabs(Result.PN)))
PORTC=(PINC&DIRC)|EARTH; // Magn. diff. > 5% => Set high
else
PORTC=(PINC&DIRC)&(0xFF-EARTH); // Magn. diff. < 5% => Set low
SetPulse(Result.PL); // Use Live wire measurements
}
else
{ // |P(L)| < |P(N)|
if (fabs(Result.PN)>(1.05*fabs(Result.PL)))
PORTC=(PINC&DIRC)|EARTH; // Magn. diff. > 5% => Set high
else
PORTC=(PINC&DIRC)&(0xFF-EARTH); // Magn. diff. < 5% => Set low
SetPulse(Result.PN); // Use Neutral wire measurements
}
// Verify that current flows in the expected direction. Both live and
// neutral current are positive if design is wired as expected. Set
// tamper indicator as follows:
//
// REVDIR | Condition
// -------+--------------------------------------
// Low | P(L) is positive AND P(N) is positive
// High | P(L) is negative OR P(N) is negative
//
// Measurement accuracy is not compromised by sign of current.
if ((Result.PL<0) | (Result.PN<0))
PORTC=(PINC&DIRC)|REVDIR; // Current flow NOK => Set high
else
PORTC=(PINC&DIRC)&(0xFF-REVDIR); // Current flow OK => Set low
}
else
{
// Tampering logic is disabled for now
PORTC=(PINC&DIRC)&(0xFF-EARTH);
PORTC=(PINC&DIRC)&(0xFF-REVDIR);
SetPulse(Result.PL); // Default
}
// Display measurement results. Connect TxD and RxD to any terminal
// software or application. If properly calibrated, the output will
// show active power in watts, current in amps and voltage in volts.
if (Flags&DISPLAY)
{
printf(" %12.3f, %12.3f",Result.PL,Result.PN);
printf(", %10.5f, %10.5f",Result.IL,Result.IN);
printf(", %8.3f",Result.U);
printf("\n\r");
}
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -