I'll just dive right in and show you what I've got so far.
/* Main Loop */
while(1) {
if (_adcCounter >= NUM_SAMPLES){
_adcCounter=0;
CalcPower(instData,powerData); //tally up power data
UpdateLcd(powerData,display_state); //Display on LCD
}
Sleep();
}
This is the main loop. Currently it does the power calculations and updates the LCD. Eventually it will also transmit data over the nRF24. Outside of this loop I've set up a timer that will trigger every (60Hz*64) 3840Hz. Inside of that timer interrupt I call the GatherData() method shown below:
//Read each voltage/current pair
void GatherData(struct datapoint *dp, uint16_t midPtV, uint16_t midPtA[3]) {
uint16_t tempV[3],tempA[3];
for (uint8_t i=0; i < 3; i++) {//read ADCs
MySetChanADC(VIN_ADC_CHAN); //read voltage
ConvertADC(); //starts ADC capture
while(BusyADC()){NOP();}
tempV[i] = ReadADC();
MySetChanADC(i+1); //read current transformer
ConvertADC();
while(BusyADC()){NOP();}
tempA[i] = ReadADC();
}
for (uint8_t i=0; i < 3; i++) {//Scale and store data
//scale voltage and current readings
tempV[i] = tempV[i] >= midPtV ? tempV[i]-midPtV : midPtV-tempV[i]; //calculate difference about midpoint value
tempV[i] *= V_SCALE_FACTOR;
tempA[i] = tempA[i] >= midPtA[i] ? tempA[i]-midPtA[i] : midPtA[i]-tempA[i];
tempA[i] *= A_SCALE_FACTOR;
//accumulate values for inst power and RMS volts/amps
dp[i].instVolts += (uint32_t)tempV[i] * tempV[i];
dp[i].instAmps += (uint32_t)tempA[i] * tempA[i];
dp[i].instPower += (uint32_t)tempV[i] * tempA[i];
}
}
All this function does is read in a voltage and current for each power channel, scales it, then accumulates for the RMS and power calculations. The ADC values are scaled about the alternating voltage/current midpoint, which would be the value read by the ADC when no voltage or current sensor is present. I'll determine those midpoint values by a yet unimplemented calibration routine that runs on startup. It'll average the ADC values over some power of 2 number of 60Hz cycles.
In case you're wondering why the datapoint structure uses uint32 types, it's because it needs to store 3840 squared ADC values. That adds up to a larger than uint24 value. I tried looking for a way to save that data without squaring it and without a large array, but couldn't come up with anything else.
void CalcPower(struct datapoint *dp, struct power_data *pwr){
struct datapoint lastDp[3];
for (uint8_t i=0; i < 3; i++) {//transfer data to temps first
lastDp[i].instAmps = dp[i].instAmps;
lastDp[i].instPower = dp[i].instPower;
lastDp[i].instVolts = dp[i].instVolts;
dp[i].instAmps = 0; //clear data to prepare for next wave
dp[i].instPower = 0;
dp[i].instVolts = 0;
}
for (uint8_t i=0; i < 3; i++) {
lastDp[i].instAmps *= A_SCALE_FACTOR_SQ; //scale ADC values
lastDp[i].instVolts *= V_SCALE_FACTOR_SQ;
lastDp[i].instPower *= VA_SCALE_FACTOR;
pwr[i].voltsRMS = sqrt(lastDp[i].instVolts / NUM_SAMPLES); //RMS calc
pwr[i].ampsRMS = sqrt(lastDp[i].instAmps / NUM_SAMPLES);
pwr[i].realPower = lastDp[i].instPower / NUM_SAMPLES;
pwr[i].apparentPower = pwr[i].voltsRMS * pwr[i].ampsRMS;
pwr[i].powerFactor = pwr[i].realPower / pwr[i].apparentPower;
}
}
Lastly is the actual power calculation. This is called once a second from the main loop. The first loop in this function tries to quickly transfer the instantaneous voltage/current/power data into temporary variables, then clear those accumulations before the next timer interrupt. The next loop is the meat of it. First it scales the data from ADC values to actual units (power_data structure is all floats). The scaling factors are squared since the data is squared before it's accumulated. The rest is just the standard RMS and power calculationsIf you're looking at this and thinking it's going to take a lot of instructions to accomplish not only the math but the conversions between 16-bit, 32-bit, and float data types then you are right! It actually took 10 times more cycles than I allotted for in the GatherData() function. I'll get into all of that in the next post talking about simulation and optimizations. I actually discovered something pretty interesting (or infuriating depending on your point of view), which is why I rushed through this post.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.