Minor update to improve documentation and add missing functions.
PowerMeasurement.cpp
- Committer:
- WiredHome
- Date:
- 2016-10-17
- Revision:
- 2:7f6a39ec5c01
- Parent:
- 1:d99a72afec32
File content as of revision 2:7f6a39ec5c01:
#include "PowerMeasurement.h" //#define DEBUG "POWR" // ... // INFO("Stuff to show %d", var); // new-line is automatically appended // #if (defined(DEBUG) && !defined(TARGET_LPC11U24)) #define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define ERR(x, ...) std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); static void HexDump(const char * title, const uint8_t * p, int count) { int i; char buf[100] = "0000: "; if (*title) INFO("%s", title); for (i=0; i<count; ) { sprintf(buf + strlen(buf), "%02X ", *(p+i)); if ((++i & 0x0F) == 0x00) { INFO("%s", buf); if (i < count) sprintf(buf, "%04X: ", i); else buf[0] = '\0'; } } if (strlen(buf)) INFO("%s", buf); } #else #define INFO(x, ...) #define WARN(x, ...) #define ERR(x, ...) #define HexDump(a, b, c) #endif DigitalOut mip(LED1); DigitalOut sam(LED2); PowerMeasurement::PowerMeasurement(AnalogIn * _AinList, BusOut * _MuxBus, DigitalOut * _Select, GetVoltage_T _GetVoltage, int _AinCount, int _MuxChannels) { if (_AinList) AinList = _AinList; if (_MuxBus) MuxBus = _MuxBus; if (_Select) { Select = _Select; } GetVoltage = &_GetVoltage; AinCount = _AinCount; NumMuxes = _AinCount; MuxChannels = _MuxChannels; totalChannels = (NumMuxes > 0) ? AinCount * (NumMuxes * MuxChannels) : AinCount; fullScaleCurrent = (float *)malloc(totalChannels * sizeof(float)); for (int i=0; i<totalChannels; i++) { fullScaleCurrent[i] = 1.0f; } fullScaleVoltage = 1.0f; if (AinCount > 0 && AinCount <= 6) { //INFO("Configure for %d A/D inputs", AinCount); } rawSamples = (RawPowerData_T *)malloc(SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE * sizeof(RawPowerData_T)); sampleNum = 0; frequency(60.0f); // default is pathetically slow, but good for basic testing... inProcess = false; isComplete = false; } /// The destructor. PowerMeasurement::~PowerMeasurement() { free(fullScaleCurrent); } /// Defines the overall frequency of the line voltage. /// /// Based on this line frequency, the sample-rate for the measurement is set. /// /// @param[in] Hz sets the line frequency. /// void PowerMeasurement::frequency(float _Hz) { uSecInterval = 1.0e6 / _Hz / SAMPLES_PER_CYCLE; } /// Defines the measuremenbt interval. /// /// Instead of defining the measurement interval by line frequency, the period /// can be directly set. /// /// @param uSec is the number of microseconds between samples. /// void PowerMeasurement::period_us(uint32_t uSec) { uSecInterval = uSec; } /// Set the voltage to current calibration value for a channel. /// /// Each analog input channel can be configured for the current sensor used on that channel. /// If the channel has a 30 A current sensor, that channel should be set to 30.0f. /// If the user calibrates the sensor more precisely, an improved calibration factor (e.g. 31.1) /// can be defined. /// /// @param[in] channel defines the channel to calibrate. /// @param[in] fullScaleCurrent is the calibration factor representing the full-scale current. /// @returns true if the value is accepted. /// @returns false if the channel was incorrect. /// bool PowerMeasurement::SetFullScaleCurrent(int channel, float fullScaleCurrentCalibration) { if (channel >= 0 && channel < totalChannels) { fullScaleCurrent[channel] = fullScaleCurrentCalibration; return true; } else { return false; } } /// Set the voltage value representing the full scale measurement. /// /// The GetVoltage callback is based on a uint16_t. This API sets the full scale voltage /// representing the value FFFF. Based on an A/C input, biased to the split supply, this /// represents an A/D value of 7FFF. When configured for a 120V circuit, which measures /// approximately 170v peak, the fullScaleVoltage value would be 170.0f. /// /// @param[in] fullScaleVoltage is the full-scale voltage value. /// @returns true if the value is accepted. /// bool PowerMeasurement::SetFullScaleVoltage(float fullScaleVoltageCalibration) { fullScaleVoltage = fullScaleVoltageCalibration; return true; } /// Starts a measurement on the specified channel. /// /// This starts the measurement on a specified channel. /// /// @param[in] channel defines the channel to measure. This is in the range of 0 to N-1, where N is /// AinCount * MuxChannels. /// @returns true if the measurement can be started. /// @returns false if the measurement cannot be started - likely because of an incorrect channel /// selection. /// bool PowerMeasurement::StartMeasurement(int channel) { if (channel >= 0 && channel < totalChannels) { int muxCh; inProcess = true; isComplete = false; sampleNum = 0; // ready for the first sample if (NumMuxes > 0) { muxCh = channel % MuxChannels; a2dChannel = channel / MuxChannels; *MuxBus = muxCh; Select->write(false); } else { a2dChannel = channel; } INFO("StartMeasurement(%2d) at %d usec, Mux Ch: %d, A/D ch: %d", channel, uSecInterval, (NumMuxes) ? muxCh : 0, a2dChannel); #if 1 // Directly calling the TakeSample works, but StartMeasurement is then is a blocking call while (inProcess) { TakeSample(); wait_us(uSecInterval); // this now has 'slippage' in time, based on TakeSample execution } mip = false; #else // Attaching it to a timer seems not to work, for reasons not yet understood sampleTimer.attach_us(this, &PowerMeasurement::TakeSample, uSecInterval); mip = true; #endif return true; } else { ERR("Cannot StartMeasurement(%d) of %d", channel, totalChannels); mip = false; return false; } } void PowerMeasurement::TakeSample(void) { rawSamples[sampleNum].current = AinList[a2dChannel].read_u16(); if (GetVoltage) rawSamples[sampleNum].voltage = (*GetVoltage)(); else rawSamples[sampleNum].voltage = 0 + 32768; // bias to mid-point sampleNum++; sam = !sam; if (sampleNum > (SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE)) { inProcess = false; isComplete = true; mip = false; } } /// Determines if the conversion is complete and the results are readable. /// /// @returns true if the measurement is complete (or if no measurement is in process). /// @returns false if the measurement is in process. /// bool PowerMeasurement::readable() { return isComplete; } /// Get the real power measurement. /// /// This retrieves the real power measurement for the channel which just completed measurement. /// This is the average of the instantaneous power. /// /// @returns the real power measurement. /// float PowerMeasurement::GetRealPower() { if (isComplete) { float sumInstantP = 0.0f; for (int i=0; i<(SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE); i++) { float voltage = fullScaleVoltage * ((float)rawSamples[i].voltage - PM_ZERO_OFFSET) / PM_FULL_SCALE; float current = fullScaleCurrent[a2dChannel] * ((float)rawSamples[i].current - PM_ZERO_OFFSET) / PM_FULL_SCALE; sumInstantP += (voltage * current); } float realP = sumInstantP / (SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE); return realP; } else { return 0.0f; } } /// Get the rms voltage measurement. /// /// This retrieves the rms voltage measurement for the channel which just completed measurement. /// /// @returns the rms voltage measurement. /// float PowerMeasurement::GetRMSVoltage() { if (isComplete) { float sumSquaredV = 0.0f; for (int i=0; i<(SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE); i++) { float voltage = fullScaleVoltage * ((float)rawSamples[i].voltage - PM_ZERO_OFFSET) / PM_FULL_SCALE; sumSquaredV += (voltage * voltage); } float rmsV = sqrt(sumSquaredV / (SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE)); return rmsV; } else { return 0.0f; } } /// Get the rms current measurement. /// /// This retrieves the rms current measurement for the channel which just completed measurement. /// /// @returns the rm current measurement. /// float PowerMeasurement::GetRMSCurrent() { if (isComplete) { float sumSquaredCurrent = 0.0f; for (int i=0; i<(SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE); i++) { float current = fullScaleCurrent[a2dChannel] * ((float)rawSamples[i].current - PM_ZERO_OFFSET) / PM_FULL_SCALE; sumSquaredCurrent += (current * current); } float meanSqC = sumSquaredCurrent / (SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE); return sqrt(meanSqC); } else { return 0.0f; } } bool PowerMeasurement::GetPeakCurrent(float * negPeak, float * posPeak) { if (isComplete) { for (int i=0; i<(SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE); i++) { if (i == 0) { if (negPeak) *negPeak = fullScaleCurrent[a2dChannel] * ((float)rawSamples[i].current - PM_ZERO_OFFSET) / PM_FULL_SCALE; if (posPeak) *posPeak = fullScaleCurrent[a2dChannel] * ((float)rawSamples[i].current - PM_ZERO_OFFSET) / PM_FULL_SCALE; } else { float testVal = fullScaleCurrent[a2dChannel] * ((float)rawSamples[i].current - PM_ZERO_OFFSET) / PM_FULL_SCALE; if (negPeak && testVal < *negPeak) *negPeak = testVal; if (posPeak && testVal > *posPeak) *posPeak = testVal; } } return true; } else { return false; } } /// Get the apparent power measurement. /// /// This retrieves the apparent power measurement for the channel which just completed measurement. /// /// @returns the apparent power measurement. /// float PowerMeasurement::GetApparentPower() { if (isComplete) { return GetRMSVoltage() * GetRMSCurrent(); } else { return 0.0f; } } float PowerMeasurement::GetPowerFactor() { if (isComplete) { return GetRealPower() / GetApparentPower(); } else { return 0.0f; } } bool PowerMeasurement::GetRawSample(int sample, RawPowerData_T * rawsample) { if ((isComplete || (inProcess && sampleNum > sample)) && sample >= 0) { if (rawsample) *rawsample = rawSamples[sample]; return true; } else { return false; } } int PowerMeasurement::GetRawSampleCount(void) { return SAMPLES_PER_CYCLE * CYCLES_PER_SAMPLE; }