├── Examples ├── EmonTxV34CM_max │ └── EmonTxV34CM_max.ino ├── EmonTxV34CM_min │ └── EmonTxV34CM_min.ino └── EmonTxV34CM_min_RFM69 │ ├── EmonTxV34CM_min_RFM69.ino │ └── rfm.ino ├── emonLibCM User Doc.pdf ├── emonLibCM.cpp ├── emonLibCM.h ├── library.json └── readme.md /Examples/EmonTxV34CM_max/EmonTxV34CM_max.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | "Maximal" sketch to demonstrate emonLibCM 4 | 5 | This demonstrates the use of every API function. 6 | This sketch provides an example of every Application Interface function. 7 | Many in fact set the default value for the emonTx V3.4, and are therefore 8 | not needed in most cases. If you do need to change a value, the 9 | Application Interface section of the User Documentation gives full details. 10 | 11 | */ 12 | 13 | #include 14 | #include "emonLibCM.h" 15 | 16 | #define RF69_COMPAT 1 // Set to 1 if using RFM69CW, or 0 if using RFM12B 17 | #include // https://github.com/jcw/jeelib - Tested with JeeLib 10 April 2017 18 | // ISR(WDT_vect) { Sleepy::watchdogEvent(); } 19 | 20 | #define RF_freq RF12_433MHZ // Frequency of radio module can be RF12_433MHZ, RF12_868MHZ or RF12_915MHZ. 21 | // You must use the one matching the module you have. 22 | const int nodeID = 10; // node ID for this emonTx. This sketch does NOT interrogate the DIP switch. 23 | 24 | const int networkGroup = 210; // wireless network group 25 | // - needs to be same as emonBase / emonPi and emonGLCD. OEM default is 210 26 | bool recalibrate = false; // Do not demonstrate the recalibration functions 27 | /* 28 | 29 | emonhub.conf nodeid is 10 - switch is ignored) 30 | See: https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md 31 | 32 | [[10]] 33 | nodename = emontx1 34 | [[[rx]]] 35 | names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse 36 | datacode = h 37 | scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 38 | units =W,W,W,W,V,C,C,C,C,C,C,p 39 | 40 | */ 41 | 42 | typedef struct {int power1, power2, power3, power4, Vrms, T1, T2, T3, T4, T5, T6, P; } PayloadTX; // package the data for RF comms 43 | 44 | PayloadTX emontx; // create an instance 45 | 46 | 47 | DeviceAddress allAddresses[6]; // Array to receive temperature sensor addresses 48 | /* Example - how to define temperature sensors, prevents an automatic search 49 | 50 | DeviceAddress allAddresses[6] = { 51 | {0x28, 0x81, 0x43, 0x31, 0x7, 0x0, 0x0, 0xD9}, 52 | {0x28, 0x8D, 0xA5, 0xC7, 0x5, 0x0, 0x0, 0xD5}, // Use the actual addresses, as many as required 53 | {0x28, 0xC9, 0x58, 0x32, 0x7, 0x0, 0x0, 0x89} // up to a maximum of 6 54 | }; 55 | 56 | */ 57 | int allTemps[6]; // Array to receive temperature measurements 58 | 59 | void setup() 60 | { 61 | 62 | Serial.begin(9600); 63 | Serial.println("Set baud=115200"); 64 | Serial.end(); 65 | Serial.begin(115200); 66 | 67 | Serial.println("\nEmonTx v3.4 EmonLibCM Continuous Monitoring Maximal Demo"); 68 | Serial.print("\nAssumed voltage for apparent power calculations when no a.c. is detected: "); Serial.println(EmonLibCM_getAssumedVrms()); 69 | Serial.print("\nValues will be reported every "); Serial.print(EmonLibCM_getDatalog_period()); Serial.println(" seconds"); 70 | 71 | EmonLibCM_SetADC_VChannel(0, 268.97); // ADC Input channel, voltage calibration - for Ideal UK Adapter = 268.97 72 | EmonLibCM_SetADC_IChannel(1, 90.91, 4.2); // ADC Input channel, current calibration, phase calibration 73 | EmonLibCM_SetADC_IChannel(2, 90.91, 4.2); // The current channels will be read in this order 74 | EmonLibCM_SetADC_IChannel(3, 90.91, 4.2); // 90.91 for 100 A : 50 mA c.t. with 22R burden - v.t. leads c.t by ~4.2 degrees 75 | EmonLibCM_SetADC_IChannel(4, 16.67, 1.0); // 16.67 for 100 A : 50 mA c.t. with 120R burden - v.t. leads c.t by ~1 degree 76 | 77 | EmonLibCM_setADC(10, 104); // ADC Bits (10 for emonTx & Arduino except Due=12 bits, ADC Duration 104 us for 16 MHz operation) 78 | EmonLibCM_ADCCal(3.3); // ADC Reference voltage, (3.3 V for emonTx, 5.0 V for Arduino) 79 | 80 | EmonLibCM_setAssumedVrms(240.0); // Assumed voltage when no a.c. detected 81 | EmonLibCM_cycles_per_second(50); // mains frequency 50Hz, 60Hz 82 | EmonLibCM_datalog_period(10); // period of readings in seconds - normal value for emoncms.org 83 | 84 | EmonLibCM_min_startup_cycles(10); // number of cycles to let ADC run before starting first actual measurement 85 | 86 | EmonLibCM_setPulseEnable(true); // Enable pulse counting. See the documentation for 2-channel versions of these functions. 87 | EmonLibCM_setPulsePin(3, 1); 88 | EmonLibCM_setPulseMinPeriod(20, (byte)FALLING); // 20 ms debounce period, count on falling edge (e.g. switch closing) 89 | EmonLibCM_setPulseCount(0); // Initialise to pulse count to zero 90 | 91 | EmonLibCM_setWattHour(0, 0); // Wh counters set to zero 92 | EmonLibCM_setWattHour(1, 0); 93 | EmonLibCM_setWattHour(2, 0); 94 | EmonLibCM_setWattHour(3, 0); 95 | 96 | EmonLibCM_setTemperatureDataPin(5); // OneWire data pin (emonTx V3.4) 97 | EmonLibCM_setTemperaturePowerPin(19); // Temperature sensor Power Pin - 19 for emonTx V3.4 (-1 = Not used. No sensors, or sensor are permanently powered.) 98 | EmonLibCM_setTemperatureResolution(11); // Resolution in bits, allowed values 9 - 12. 11-bit resolution, reads to 0.125 degC 99 | EmonLibCM_setTemperatureAddresses(allAddresses); // Name of array of temperature sensors 100 | EmonLibCM_setTemperatureArray(allTemps); // Name of array to receive temperature measurements 101 | EmonLibCM_setTemperatureMaxCount(6); // Max number of sensors, limited by wiring and array size. 102 | 103 | EmonLibCM_TemperatureEnable(true); 104 | printTemperatureSensorAddresses(); // Show which sensors are connected 105 | 106 | 107 | rf12_initialize(nodeID, RF_freq, networkGroup); // initialize radio module 108 | 109 | EmonLibCM_Init(); // Start continuous monitoring. 110 | 111 | } 112 | 113 | void loop() 114 | { 115 | 116 | if (recalibrate) // recalibrate should be set when new calibration values become available 117 | { 118 | 119 | EmonLibCM_ReCalibrate_VChannel(268.97); // ADC Input channel, voltage calibration new value 120 | EmonLibCM_ReCalibrate_IChannel(1, 90.91, 4.2); // ADC Input channel, current calibration, phase calibration new values 121 | EmonLibCM_ReCalibrate_IChannel(2, 90.91, 4.2); // It is only necessary to use one of these functions if that calibration 122 | EmonLibCM_ReCalibrate_IChannel(3, 90.91, 4.2); // value needs to be changed. 123 | EmonLibCM_ReCalibrate_IChannel(4, 16.67, 1.0); // 124 | recalibrate = false; // Do it once only. 125 | } 126 | 127 | if (EmonLibCM_Ready()) 128 | { 129 | 130 | Serial.println(EmonLibCM_acPresent()?"AC present ":"AC missing "); 131 | delay(5); 132 | 133 | emontx.power1 = EmonLibCM_getRealPower(0); // Copy the desired variables ready for transmission 134 | emontx.power2 = EmonLibCM_getRealPower(1); 135 | emontx.power3 = EmonLibCM_getRealPower(2); 136 | emontx.power4 = EmonLibCM_getRealPower(3); 137 | emontx.Vrms = EmonLibCM_getVrms() * 100; 138 | 139 | emontx.T1 = allTemps[0]; 140 | emontx.T2 = allTemps[1]; 141 | emontx.T3 = allTemps[2]; 142 | emontx.T4 = allTemps[3]; 143 | emontx.T5 = allTemps[4]; 144 | emontx.T6 = allTemps[5]; 145 | 146 | emontx.P = EmonLibCM_getPulseCount(); 147 | 148 | rf12_sendNow(0, &emontx, sizeof emontx); //send data 149 | 150 | delay(50); 151 | 152 | Serial.print(" V=");Serial.print(EmonLibCM_getVrms()); 153 | Serial.print(" f=");Serial.println(EmonLibCM_getLineFrequency(),2); 154 | 155 | for (byte ch=0; ch<4; ch++) 156 | { 157 | Serial.print("Ch ");Serial.print(ch+1); 158 | Serial.print(" I=");Serial.print(EmonLibCM_getIrms(ch),3); 159 | Serial.print(" W=");Serial.print(EmonLibCM_getRealPower(ch)); 160 | Serial.print(" VA=");Serial.print(EmonLibCM_getApparentPower(ch)); 161 | Serial.print(" Wh=");Serial.print(EmonLibCM_getWattHour(ch)); 162 | Serial.print(" pf=");Serial.print(EmonLibCM_getPF(ch),4); 163 | Serial.println(); 164 | delay(10); 165 | } 166 | 167 | Serial.print(" pulses=");Serial.println(EmonLibCM_getPulseCount()); 168 | delay(10); 169 | 170 | if (EmonLibCM_getTemperatureEnabled()) 171 | { 172 | Serial.println("Temperatures:"); 173 | for (byte j=0; j 13 | #include "emonLibCM.h" 14 | 15 | #define RF69_COMPAT 1 // Set to 1 if using RFM69CW, or 0 if using RFM12B 16 | #include // https://github.com/jcw/jeelib - Tested with JeeLib 10 April 2017 17 | // ISR(WDT_vect) { Sleepy::watchdogEvent(); } 18 | 19 | #define RF_freq RF12_433MHZ // Frequency of radio module can be RF12_433MHZ, RF12_868MHZ or RF12_915MHZ. 20 | // - You must use the one matching the module you have. 21 | const int nodeID = 10; // node ID for this emonTx. This sketch does NOT interrogate the DIP switch. 22 | 23 | const int networkGroup = 210; // wireless network group 24 | // - needs to be same as emonBase / emonPi and emonGLCD. OEM default is 210 25 | 26 | /* 27 | 28 | emonhub.conf nodeid is 10 - switch is ignored) 29 | See: https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md 30 | 31 | [[10]] 32 | nodename = emontx1 33 | [[[rx]]] 34 | names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse 35 | datacode = h 36 | scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 37 | units =W,W,W,W,V,C,C,C,C,C,C,p 38 | 39 | */ 40 | 41 | typedef struct {int power1, power2, power3, power4, Vrms, T1, T2, T3, T4, T5, T6; unsigned long pulseCount; } PayloadTX; // package the data for RF comms 42 | 43 | PayloadTX emontx; // create an instance 44 | 45 | 46 | void setup() 47 | { 48 | 49 | Serial.begin(9600); 50 | Serial.println("Set baud=115200"); 51 | Serial.end(); 52 | Serial.begin(115200); 53 | 54 | Serial.println("\nEmonTx v3.4 EmonLibCM Continuous Monitoring Minimal Demo"); 55 | 56 | rf12_initialize(nodeID, RF_freq, networkGroup); // initialize radio module 57 | 58 | EmonLibCM_Init(); // Start continuous monitoring. 59 | 60 | } 61 | 62 | void loop() 63 | { 64 | 65 | if (EmonLibCM_Ready()) 66 | { 67 | 68 | Serial.println(EmonLibCM_acPresent()?"AC present ":"AC missing "); 69 | delay(5); 70 | 71 | emontx.power1 = EmonLibCM_getRealPower(0); // Copy the desired variables ready for transmission 72 | emontx.power2 = EmonLibCM_getRealPower(1); 73 | emontx.power3 = EmonLibCM_getRealPower(2); 74 | emontx.power4 = EmonLibCM_getRealPower(3); 75 | emontx.Vrms = EmonLibCM_getVrms() * 100; 76 | 77 | rf12_sendNow(0, &emontx, sizeof emontx); //send data 78 | 79 | delay(50); 80 | 81 | Serial.print(" V=");Serial.println(EmonLibCM_getVrms()); 82 | 83 | for (byte ch=0; ch<4; ch++) 84 | { 85 | Serial.print("Ch ");Serial.print(ch+1); 86 | Serial.print(" I=");Serial.print(EmonLibCM_getIrms(ch),3); 87 | Serial.print(" W=");Serial.print(EmonLibCM_getRealPower(ch)); 88 | Serial.print(" VA=");Serial.print(EmonLibCM_getApparentPower(ch)); 89 | Serial.print(" Wh=");Serial.print(EmonLibCM_getWattHour(ch)); 90 | Serial.print(" pf=");Serial.print(EmonLibCM_getPF(ch),4); 91 | Serial.println(); 92 | delay(10); 93 | } 94 | 95 | delay(10); 96 | 97 | } 98 | else 99 | rf12_recvDone(); 100 | 101 | } 102 | -------------------------------------------------------------------------------- /Examples/EmonTxV34CM_min_RFM69/EmonTxV34CM_min_RFM69.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | "Minimal" sketch to demonstrate emonLibCM - not using JeeLib 4 | 5 | ONLY the Hope RFM69CW radio is suitable for use with this sketch. 6 | 7 | This sketch assumes that the default values for the emonTx V3.4 are 8 | applicable, that no input calibration is required, mains frequency 9 | is 50 Hz and data logging period interval is 10 s, pulse counting 10 | and temperature monitoring are not required, and that 4 'standard' 11 | 100 A CTs and the UK a.c. adapter from the OEM Shop are being used. 12 | 13 | */ 14 | #include 15 | #include "emonLibCM.h" 16 | #include 17 | #include 18 | 19 | 20 | // ISR(WDT_vect) { Sleepy::watchdogEvent(); } 21 | 22 | 23 | enum rfband {RF12_433MHZ = 1, RF12_868MHZ, RF12_915MHZ}; // frequency bands. 24 | void rfm_init(byte RF_freq = RF12_433MHZ); 25 | bool rfm_send(const byte *data, const byte size, const byte group, const byte node, const byte rf_power = 0x99, const int threshold = -97, const byte timeout = 15); 26 | 27 | #define RFMSELPIN 10 // RFM pins 28 | #define RFMIRQPIN 2 // RFM pins 29 | 30 | const int nodeID = 10; // node ID for this emonTx. This sketch does NOT interrogate the DIP switch. 31 | 32 | const int networkGroup = 210; // wireless network group - OEM standard is 210 33 | 34 | /* 35 | 36 | emonhub.conf nodeid is 10 - switch is ignored) 37 | See: https://github.com/openenergymonitor/emonhub/blob/emon-pi/configuration.md 38 | 39 | [[10]] 40 | nodename = emontx1 41 | [[[rx]]] 42 | names = power1, power2, power3, power4, vrms, temp1, temp2, temp3, temp4, temp5, temp6, pulse 43 | datacode = h 44 | scales = 1,1,1,1,0.01,0.1,0.1,0.1,0.1,0.1,0.1,1 45 | units =W,W,W,W,V,C,C,C,C,C,C,p 46 | 47 | */ 48 | 49 | struct {int power1, power2, power3, power4, Vrms, T1, T2, T3, T4, T5, T6; unsigned long pulseCount; } emontx; // package the data for RF comms 50 | 51 | 52 | void setup() 53 | { 54 | 55 | Serial.begin(9600); 56 | Serial.println("Set baud=115200"); 57 | Serial.end(); 58 | Serial.begin(115200); 59 | 60 | Serial.println("\nEmonTx v3.4 EmonLibCM Continuous Monitoring Minimal Demo - not using JeeLib"); 61 | 62 | rfm_init(); // initialize radio module - Default = 433 MHz. You must use the frequency matching the module you have. 63 | 64 | EmonLibCM_Init(); // Start continuous monitoring. 65 | 66 | } 67 | 68 | void loop() 69 | { 70 | 71 | if (EmonLibCM_Ready()) 72 | { 73 | 74 | Serial.println(EmonLibCM_acPresent()?"AC present ":"AC missing "); 75 | delay(5); 76 | 77 | emontx.power1 = EmonLibCM_getRealPower(0); // Copy the desired variables ready for transmission 78 | emontx.power2 = EmonLibCM_getRealPower(1); 79 | emontx.power3 = EmonLibCM_getRealPower(2); 80 | emontx.power4 = EmonLibCM_getRealPower(3); 81 | emontx.Vrms = EmonLibCM_getVrms() * 100; 82 | 83 | rfm_send((byte *)&emontx, sizeof(emontx), networkGroup, nodeID); //send data: Defaults: power = 0x99, threshold = -97 dB, timeout = 15 ms 84 | delay(50); 85 | 86 | Serial.print(" V=");Serial.println(EmonLibCM_getVrms()); 87 | 88 | for (byte ch=0; ch<4; ch++) 89 | { 90 | Serial.print("Ch ");Serial.print(ch+1); 91 | Serial.print(" I=");Serial.print(EmonLibCM_getIrms(ch),3); 92 | Serial.print(" W=");Serial.print(EmonLibCM_getRealPower(ch)); 93 | Serial.print(" VA=");Serial.print(EmonLibCM_getApparentPower(ch)); 94 | Serial.print(" Wh=");Serial.print(EmonLibCM_getWattHour(ch)); 95 | Serial.print(" pf=");Serial.print(EmonLibCM_getPF(ch),4); 96 | Serial.println(); 97 | delay(10); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Examples/EmonTxV34CM_min_RFM69/rfm.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Interface for the RFM69CW Radio Module 3 | 4 | Routines for Transmit-only, with Channel Busy detection. 5 | ======================================================== 6 | 7 | Required definitions in main sketch: 8 | 9 | #define RFMSELPIN 10 // RFM pins 10 | #define RFMIRQPIN 2 // RFM pins 11 | // RFM Power setting - see below for more information 12 | 13 | #include 14 | #include 15 | 16 | enum rfband {RF12_433MHZ = 1, RF12_868MHZ, RF12_915MHZ }; // frequency band. 17 | 18 | 19 | Functions: 20 | 21 | void rfm_init(byte RF_freq); 22 | Initialises the radio module to JeeLib protocol standards 23 | RF_freq - must be either 1 (=433 MHz), 2 (=868 MHz) or 3 (=915 MHz) 24 | 25 | bool rfm_send(const byte *data, const byte size, const byte group, const byte node, const byte rf_power, const int threshold, const byte timeout) 26 | Transmits the data 27 | data - byte stream to be transmitted 28 | size - length of the data 29 | group - transmission group: 210 for OEM 30 | node - the unique ID of this node (1 - 30) 31 | rf_power - Min value is 0x80 (-18 dBm), max is 0x9F (+13 dBm) -- RFM12B equivalent: 0x99 32 | threshold - the RSSI level below which the radio channel is considered clear (suggested value: -97) 33 | timeout - the maximum time in milliseconds that the function will wait for the channel to become clear, 34 | after which it transmits regardless. Suggested value: 10 35 | returns: true if no timeout occurred, otherwise false. 36 | 37 | 38 | */ 39 | 40 | 41 | #include 42 | #define REG_FIFO 0x00 43 | #define REG_OPMODE 0x01 44 | #define MODE_SLEEP 0x00 45 | #define MODE_TRANSMITTER 0x0C 46 | #define MODE_RECEIVER 0x10 47 | #define REG_DIOMAPPING1 0x25 48 | #define REG_IRQFLAGS1 0x27 49 | #define MODE_READY 0x80 50 | #define REG_IRQFLAGS2 0x28 51 | #define IRQ2_FIFOFULL 0x80 52 | #define IRQ2_FIFONOTEMPTY 0x40 53 | #define IRQ2_PACKETSENT 0x08 54 | #define IRQ2_FIFOOVERRUN 0x10 55 | #define REG_PACKET_CONFIG2 0x3D 56 | #define RESTART_RX 0x04 57 | #define REG_RSSI_CONFIG 0x23 58 | #define RSSI_START 0x01 59 | #define RSSI_DONE 0x02 60 | #define REG_RSSI_VALUE 0x24 61 | 62 | 63 | void rfm_init(byte RF_freq) 64 | { 65 | // Set up to drive the Radio Module 66 | digitalWrite(RFMSELPIN, HIGH); 67 | pinMode(RFMSELPIN, OUTPUT); 68 | SPI.begin(); 69 | SPI.setBitOrder(MSBFIRST); 70 | SPI.setDataMode(0); 71 | SPI.setClockDivider(SPI_CLOCK_DIV4); // decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present 72 | 73 | // Initialise RFM69CW 74 | do 75 | writeReg(0x2F, 0xAA); // RegSyncValue1 76 | while (readReg(0x2F) != 0xAA) ; 77 | do 78 | writeReg(0x2F, 0x55); 79 | while (readReg(0x2F) != 0x55); 80 | 81 | writeReg(0x01, 0x04); // RegOpMode: RF_OPMODE_SEQUENCER_ON | RF_OPMODE_LISTEN_OFF | RF_OPMODE_STANDBY 82 | writeReg(0x02, 0x00); // RegDataModul: RF_DATAMODUL_DATAMODE_PACKET | RF_DATAMODUL_MODULATIONTYPE_FSK | RF_DATAMODUL_MODULATIONSHAPING_00 = no shaping 83 | writeReg(0x03, 0x02); // RegBitrateMsb ~49.23k BPS 84 | writeReg(0x04, 0x8A); // RegBitrateLsb 85 | writeReg(0x05, 0x05); // RegFdevMsb: ~90 kHz 86 | writeReg(0x06, 0xC3); // RegFdevLsb 87 | if (RF_freq == RF12_868MHZ) 88 | { 89 | writeReg(0x07, 0xD9); // RegFrfMsb: Frf = Rf Freq / 61.03515625 Hz = 0xD90000 = 868.00 MHz as used JeeLib 90 | writeReg(0x08, 0x00); // RegFrfMid 91 | writeReg(0x09, 0x00); // RegFrfLsb 92 | } 93 | else if (RF_freq == RF12_915MHZ) // JeeLib uses 912.00 MHz 94 | { 95 | writeReg(0x07, 0xE4); // RegFrfMsb: Frf = Rf Freq / 61.03515625 Hz = 0xE40000 = 912.00 MHz as used JeeLib 96 | writeReg(0x08, 0x00); // RegFrfMid 97 | writeReg(0x09, 0x00); // RegFrfLsb 98 | } 99 | else // default to 433 MHz band 100 | { 101 | writeReg(0x07, 0x6C); // RegFrfMsb: Frf = Rf Freq / 61.03515625 Hz = 0x6C8000 = 434.00 MHz as used JeeLib 102 | writeReg(0x08, 0x80); // RegFrfMid 103 | writeReg(0x09, 0x00); // RegFrfLsb 104 | } 105 | // writeReg(0x0B, 0x20); // RegAfcCtrl: 106 | // writeReg(0x11, 0x99); // Transmit Power - set at transmit time 107 | writeReg(0x1E, 0x2C); // 108 | writeReg(0x25, 0x80); // RegDioMapping1: DIO0 is used as IRQ 109 | writeReg(0x26, 0x03); // RegDioMapping2: ClkOut off 110 | writeReg(0x28, 0x00); // RegIrqFlags2: FifoOverrun 111 | 112 | // RegPreamble (0x2c, 0x2d): default 0x0003 113 | writeReg(0x2E, 0x88); // RegSyncConfig: SyncOn | FifoFillCondition | SyncSize = 2 bytes | SyncTol = 0 114 | writeReg(0x2F, 0x2D); // RegSyncValue1: Same as JeeLib 115 | writeReg(0x37, 0x00); // RegPacketConfig1: PacketFormat=fixed | !DcFree | !CrcOn | !CrcAutoClearOff | !AddressFiltering >> 0x00 116 | } 117 | 118 | 119 | // transmit data via the RFM69CW 120 | bool rfm_send(const byte *data, const byte size, const byte group, const byte node, const byte rf_power, const int threshold, const byte timeout) // *SEND RF DATA 121 | { 122 | // rf_power: RegPaLevel = 0x9F = PA0 on, +13 dBm -- RFM12B equivalent: 0x99 | 0x88 (-10dBm) appears to be the max before the AC power supply fails @ 230 V mains. Min value is 0x80 (-18 dBm) 123 | unsigned long t_start = millis(); 124 | bool success = false; // return false if timed out, else true 125 | 126 | writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | MODE_RECEIVER); // Receive mode - sniff for channel is busy 127 | while ((millis()-t_start)<(unsigned long)timeout) 128 | { 129 | while((readReg(REG_IRQFLAGS1) & MODE_READY) == 0) 130 | ; 131 | writeReg(REG_RSSI_CONFIG, RSSI_START); 132 | while((readReg(REG_RSSI_CONFIG) & RSSI_DONE) == 0x00) 133 | ; 134 | 135 | if (readReg(REG_RSSI_VALUE) > (threshold * -2)) // because REG_RSSI_VALUE is upside down! 136 | { 137 | success = true; 138 | break; // Nothing heard - go ahead and transmit 139 | } 140 | writeReg(REG_PACKET_CONFIG2, (readReg(REG_PACKET_CONFIG2) & 0xFB) | RESTART_RX); // Restart the receiver 141 | } 142 | 143 | // Either we can transmit now, or we have waited long enough - go ahead and transmit anyway 144 | 145 | writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | MODE_SLEEP); // Sleep 146 | 147 | while (readReg(REG_IRQFLAGS2) & (IRQ2_FIFONOTEMPTY | IRQ2_FIFOOVERRUN)) // Flush FIFO 148 | readReg(REG_FIFO); 149 | writeReg(0x30, group); // RegSyncValue2 150 | 151 | writeReg(REG_DIOMAPPING1, 0x00); // PacketSent 152 | 153 | volatile uint8_t txstate = 0; 154 | byte i = 0; 155 | uint16_t crc = _crc16_update(~0, group); 156 | 157 | while(txstate < 7) 158 | { 159 | if ((readReg(REG_IRQFLAGS2) & IRQ2_FIFOFULL) == 0) // FIFO !full 160 | { 161 | uint8_t next; 162 | switch(txstate) 163 | { 164 | case 0: next=node & 0x1F; txstate++; break; // Bits: CTL, DST, ACK, Node ID(5) 165 | case 1: next=size; txstate++; break; // No. of payload bytes 166 | case 2: next=data[i++]; if(i==size) txstate++; break; 167 | case 3: next=(byte)crc; txstate++; break; 168 | case 4: next=(byte)(crc>>8); txstate++; break; 169 | case 5: 170 | case 6: next=0xAA; txstate++; break; // dummy bytes (if < 2, locks up) 171 | } 172 | if(txstate<4) crc = _crc16_update(crc, next); 173 | writeReg(REG_FIFO, next); // RegFifo(next); 174 | } 175 | } 176 | //transmit buffer is now filled, transmit it 177 | writeReg(0x11, rf_power); 178 | writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | MODE_TRANSMITTER); // Transmit mode - 56 Bytes max payload 179 | 180 | rfm_sleep(); 181 | return success; 182 | 183 | } 184 | 185 | void rfm_sleep(void) 186 | { 187 | // Put into sleep mode when buffer is empty 188 | while (!(readReg(REG_IRQFLAGS2) & IRQ2_PACKETSENT)) // wait for transmission to complete (not present in JeeLib) 189 | delay(1); // 190 | 191 | writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | 0x01); // Standby Mode 192 | } 193 | 194 | 195 | 196 | void writeReg(uint8_t addr, uint8_t value) 197 | { 198 | select(); 199 | SPI.transfer(addr | 0x80); 200 | SPI.transfer(value); 201 | unselect(); 202 | } 203 | 204 | uint8_t readReg(uint8_t addr) 205 | { 206 | select(); 207 | SPI.transfer(addr & 0x7F); 208 | uint8_t regval = SPI.transfer(0); 209 | unselect(); 210 | return regval; 211 | } 212 | 213 | // select the transceiver 214 | void select() 215 | { 216 | noInterrupts(); 217 | SPI.setDataMode(SPI_MODE0); 218 | SPI.setBitOrder(MSBFIRST); 219 | SPI.setClockDivider(SPI_CLOCK_DIV4); // decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present 220 | digitalWrite(RFMSELPIN, LOW); 221 | } 222 | 223 | // UNselect the transceiver chip 224 | void unselect() 225 | { 226 | digitalWrite(RFMSELPIN, HIGH); 227 | interrupts(); 228 | } 229 | 230 | -------------------------------------------------------------------------------- /emonLibCM User Doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openenergymonitor/EmonLibCM/9895e6099bb99b02ce9352b9367fdd6e48614809/emonLibCM User Doc.pdf -------------------------------------------------------------------------------- /emonLibCM.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | emonLibCM.cpp - Library for openenergymonitor 3 | GNU GPL 4 | */ 5 | 6 | // This library provides continuous single-phase monitoring of real power on up to five CT channels. 7 | // All of the time-critical code is now contained within the ISR, only the slower activities 8 | // are done within the main code. These slower activities include RF transmissions, 9 | // and all Serial statements (not part of the library). 10 | // 11 | // This library is suitable for either 50 or 60 Hz operation. 12 | // 13 | // Original Author: Robin Emley (calypso_rae on Open Energy Monitor Forum) 14 | // Addition of Wh totals by: Trystan Lea 15 | // Heavily modified to improve performance and calibration; temperature measurement 16 | // and pulse counting incorporated into the library, by Robert Wall 17 | // Release for testing 4/1/2017 18 | // 19 | // Version 2.0 21/11/2018 20 | // Version 2.01 3/12/2018 Calculation error in phase error correction - const.'360' missing, 'x' & 'y' coefficients swapped. 21 | // Version 2.02 13/07/2019 Temperature measurement: Added "BAD_TEMPERATURE" return value when reporting period < 0.2 s, 22 | // getLogicalChannel( ), ReCalibrate_VChannel( ), ReCalibrate_IChannel( ) added, setPulsePin( ) interrupt no. was obligatory, 23 | // pulse & temperatures were set/enabled only at startup, setTemperatureDataPin was ineffective, preloaded sensor addresses 24 | // not handled properly. 25 | // 18/10/2019 Sketch using this became the default in emonTx V3.4 26 | // Version 2.03 25/10/2019 Mains Frequency reporting [getLineFrequency( )]added, 27 | // ADC reference source was AVcc and not selectable - ability to select [setADC_VRef( )] added, 28 | // sampleSetsDuringThisDatalogPeriod (and derivatives) was samplesDuringThisDatalogPeriod etc, 29 | // Energy calculation changed to use internal clock rather than mains time by addition of "frequencyDeviation". 30 | // Version 2.04 1/8/2020 In examples, 'else' added to "if (EmonLibCM_Ready())" to keep JeeLib alive. Example for RFM69CW only ('Classic' format) and 31 | // not using JeeLib added. 32 | // getDatalog_period( ) added. Temperature array is now ignored if first device is not a DS18B20. 'BAD_TEMPERATURE' now returned if 33 | // sensor address is made invalid during operation. Array above is sensor count is filled with UNUSED_TEMPERATURE. Superfluous 'if' 34 | // removed at end of retrieveTemperatures() - power pin is now set low regardless. 'else' added to 'if' in 35 | // (if (temperatureEnabled = _enable)) in TemperatureEnable( ) to ensure power is off if not required, delay after retrieving 36 | // a temperature was 5 ms. 37 | // unsigned long missing_VoltageSamples (was "missing_Voltage"), bool firstcycle were not volatile, unnecessary copies of 38 | // 'protected' variables removed, datalog period was set only at startup & minimum limit added. cycleCountForDatalogging, 39 | // min_startup_cycles were signed. 40 | // (Plus some cosmetic changes) 41 | // Version 2.1.0 9/7/2021 2nd pulse input added, array of structs was individual variables. N.B. The definition setPulsePin(byte channel, int _pin) 42 | // is incompatible with the old definition of setPulsePin(int _pin, int _interrupt). Solution for 85 °C problem added, 43 | // special print format for emonPi added. 'Setters' to initialise Wh counters & pulse count added. If no a.c. voltage, 44 | // now uses assumed Vrms to calculate power, VA & energy, p.f. and frequency both report zero. Error in phase shift calculation 45 | // meant wrong correction was applied when ct's were sampled out of sequence. 46 | // Version 2.1.1 26/7/2021 Typo in Include file name - was emonLibCM2P.h 47 | // Version 2.1.2 7/8/2021 'assumedACVoltage' was 'assumedVrms' (name conflict in some sketches). 48 | // Version 2.2.0 14/9/2021 Pulse counting could count double on switch bounce. Changed: PulseMinPeriod, default was 110 ms; 49 | // 'laststate', 'timing' & 'edge' added to track input state; 'edge' added to setters; in init(), interrupt was attached to RISING edge; 50 | // most of the code in the pulse ISR removed, replaced by countPulses( ) called from main loop. 51 | // Version 2.2.1 5/12/2012 Repackaged 30/10/2021 release: Debugging statements accidentally left in pulse ISR removed. 52 | // Version 2.2.2 15/9/2022 If temperature sensor pin had not been set but relied on default, no power was applied for initial search. 53 | 54 | // #include "WProgram.h" un-comment for use on older versions of Arduino IDE 55 | 56 | // #define SAMPPIN 5 // EmonTx: Preferred pin for testing. This MUST be commented out if the temperature sensor power is connected here. Only include for testing. 57 | // #define SAMPPIN 19 // EmonTx: Alternative pin for testing. This MUST be commented out if the temperature sensor power is connected here. Only include for testing. 58 | // #define INTPINS // Debugging print of interrupt allocations 59 | 60 | #include "emonLibCM.h" 61 | 62 | #if defined(ARDUINO) && ARDUINO >= 100 63 | 64 | #include "Arduino.h" 65 | 66 | #else 67 | 68 | #include "WProgram.h" 69 | 70 | #endif 71 | 72 | 73 | 74 | 75 | unsigned int cycles_per_second = 50; // mains frequency in Hz (i.e. 50 or 60) 76 | float datalog_period_in_seconds = 10.0; 77 | unsigned int min_startup_cycles = 10; 78 | 79 | // Maximum number of Current (I) channels used to create arrays 80 | static const int max_no_of_channels = 5; 81 | 82 | // User set number of Current (I) channels used by 'for' loops 83 | int no_of_channels = 4; 84 | // number of Current (I) channels that have been set 85 | byte no_of_Iinputs = 0; 86 | 87 | // for general interaction between the main code and the ISR 88 | volatile boolean datalogEventPending; 89 | volatile unsigned long missing_VoltageSamples = 0; // provides a timebase mechanism for current-only use 90 | // - uses the ADC free-running rate as a clock. 91 | double line_frequency; // Timed from sample rate & cycle count 92 | 93 | 94 | // Arrays for current channels (zero-based) 95 | int realPower_CT[max_no_of_channels]; 96 | int apparentPower_CT[max_no_of_channels]; 97 | double Irms_CT[max_no_of_channels]; 98 | long wh_CT[max_no_of_channels] = {0, 0, 0, 0, 0}; 99 | double pf[max_no_of_channels]; 100 | double Vrms; 101 | volatile boolean ChannelInUse[max_no_of_channels]; 102 | static byte lChannel[max_no_of_channels+1]; // logical current channel no. (0-based) 103 | 104 | // analogue ports 105 | static byte ADC_Sequence[max_no_of_channels+1] = {0,1,2,3,4,5}; // <-- Sequence in which the analogue ports are scanned, first is Voltage, remainder are currents 106 | // ADC data 107 | int ADCBits = 10; // 10 for the emonTx and most of the Arduino range, 12 for the Arduino Due. 108 | double Vref = 3.3; // ADC Reference Voltage = 3.3 for emonTX, 5.0 for most of the Arduino range. 109 | int ADCDuration = 104; // Time in microseconds for one ADC conversion = 104 for 16 MHz clock 110 | byte ADCRef = VREF_NORMAL << 6; // ADC Reference: VREF_EXTERNAL, VREF_NORMAL = AVcc, VREF_INTERNAL = Internal 1.1 V 111 | 112 | // Pulse Counting 113 | 114 | #define PULSEINPUTS 2 // No of available interrupts for pulse counting (2 is the maximum, add more "onPulse..." functions for more) 115 | 116 | struct pulse { 117 | byte PulsePin = 3; // default to DI3 for the emonTx V3 118 | byte PulseInterrupt = 1; // default to int1 for the emonTx V3 119 | unsigned long PulseMinPeriod = 20; // default to 20 ms 120 | byte edge = FALLING; // edge to increment count 121 | unsigned long pulseCount = 0; // Total number of pulses from switch-on 122 | unsigned long pulseIncrement = 0; // Incremental number of pulses between readings 123 | bool PulseEnabled = false; 124 | bool PulseChange = false; // track change of state of counting 125 | bool laststate = HIGH; // Last state of interrupt pin 126 | volatile bool timing = false; // 'debounce' period running 127 | volatile unsigned long pulseTime; // Instant of the last interrupt - used for debounce logic 128 | } pulses[PULSEINPUTS]; 129 | 130 | void onPulse(byte channel); // General pulse handler 131 | void onPulse0(), onPulse1(); // Individual pulse handlers - one per interrupt 132 | 133 | // Set-up values 134 | //-------------- 135 | // These set up the library for different hardware configurations 136 | // 137 | // setADC Sets the ADC resolution and the conversion time 138 | // cycles_per_second Defines the mains frequency 139 | // _min_startup_cycles The period of inactivity whilst the system settles at start-up 140 | // _datalog_period The rate at which data is reported for logging 141 | // SetADC_Channel Defines the channels input pin and calibration constants 142 | // 143 | // 144 | // Calibration values 145 | //------------------- 146 | // Many calibration values are used in this sketch: 147 | // 148 | // ADCCal This sets up the ADC reference voltage 149 | // voltageCal This is the principal calibration for the ac adapter. 150 | // currentCal A per-channel amplitude calibration for each current transformer. 151 | // phaseCal A per-channel calibration for the phase error correction 152 | 153 | 154 | // With most hardware, the default values are likely to work fine without 155 | // need for change. A compact explanation of each of these values now follows: 156 | 157 | // Voltage calibration constant. This is the mains voltage that would give 1 V 158 | // at the ADC input: 159 | 160 | // AC-AC Voltage adapter is designed to step down the voltage from 240V to 9V 161 | // but the AC Voltage adapter is running open circuit and so output voltage is 162 | // likely to be about 20% higher than 9V, actually 11.6 V for the UK Ideal adapter 163 | // (from the data sheet). 164 | // Open circuit step down = 240 / 11.6 = 20.69 165 | 166 | // The output voltage is then stepped down further with the voltage divider which has 167 | // values Rb = 10k, Rt = 120k which will reduce the voltage by 13 times. 168 | 169 | // The combined step down is therefore 20.69 x 13 = 268.97 which is the 170 | // theoretical calibration constant. The actual constant for a given 171 | // unit and ac adapter is likely to be different by a few percent. 172 | // Other adapters may be different by more. 173 | 174 | // Current calibration constant. This is the mains current that would give 1 V 175 | // at the ADC input: 176 | // Current calibration constant channels 1 - 3 = 100 A / 50 mA / 22 Ohms burden resistor = 90.9 177 | // (The default CT sensor is 100 A : 50 mA) 178 | // for channel 4 is 100 A / 50 mA / 120R burden resistor = 16.67 179 | // The actual constant for a given unit and CT is likely to be different by a few percent. 180 | 181 | // phaseCal is used to alter the phase of the voltage waveform relative to the 182 | // current waveform. The algorithm interpolates between the most recent pair 183 | // of voltage samples according to the value of phaseCal. 184 | // 185 | // The value of phaseCal entered is difference between the phase lead of the voltage transformer and 186 | // the phase lead of the current transformer, in degrees (changes of less than 0.1 deg are 187 | // unlikely to make a detectable difference). 188 | 189 | 190 | 191 | /************************************************************************************************** 192 | * 193 | * General variables 194 | * 195 | * 196 | ***************************************************************************************************/ 197 | 198 | 199 | // -------------- general global variables ----------------- 200 | // Some of these variables are used in multiple blocks so cannot be static. 201 | // For integer maths, many variables need to be 'long' or in extreme cases 'int64_t' 202 | 203 | double currentCal[max_no_of_channels] = {90.91, 90.91, 90.91, 16.67, 90.91}; 204 | double phaseCal_CT[max_no_of_channels] ={4.2, 4.2, 4.2, 1.0, 4.2}; 205 | 206 | double voltageCal = 268.97; 207 | 208 | unsigned int ADC_Counts = 1 << ADCBits; 209 | 210 | bool stop = false; 211 | volatile bool firstcycle = true; 212 | 213 | unsigned int samplesDuringThisCycle; 214 | bool acPresent = false; // true when ac voltage input is detected. 215 | unsigned int acDetectedThreshold = ADC_Counts >> 5; // ac voltage detection threshold, ~10% of nominal voltage (given large amount of ripple) 216 | double assumedACVoltage = 240.0; 217 | 218 | unsigned int datalogPeriodInMainsCycles; 219 | unsigned long ADCsamples_per_datalog_period; 220 | 221 | // accumulators & counters for use by the ISR 222 | long cumV_deltas; // <--- for offset removal (V) 223 | int64_t sumPA_CT[max_no_of_channels]; // 'partial' power for real power calculation 224 | int64_t sumPB_CT[max_no_of_channels]; // 'partial' power for real power calculation 225 | uint64_t sumIsquared_CT[max_no_of_channels]; 226 | long cumI_deltas_CT[max_no_of_channels]; // <--- for offset removal (I) 227 | uint64_t sum_Vsquared; // for Vrms datalogging 228 | long sampleSetsDuringThisDatalogPeriod; 229 | 230 | // Copies of ISR data for use by the main code 231 | // These are filled by the ADC helper routine at the end of the datalogging period 232 | 233 | volatile int64_t copyOf_sumPA_CT[max_no_of_channels]; 234 | volatile int64_t copyOf_sumPB_CT[max_no_of_channels]; 235 | volatile uint64_t copyOf_sumIsquared_CT[max_no_of_channels]; 236 | volatile uint64_t copyOf_sum_Vsquared; 237 | volatile long copyOf_sampleSetsDuringThisDatalogPeriod; 238 | volatile int64_t copyOf_cumI_deltas[max_no_of_channels]; 239 | volatile int64_t copyOf_cumV_deltas; 240 | 241 | // For mechanisms to check the integrity of this code structure 242 | #ifdef INTEGRITY 243 | int sampleSetsDuringThisMainsCycle; 244 | int lowestNoOfSampleSetsPerMainsCycle; 245 | volatile int copyOf_lowestNoOfSampleSetsPerMainsCycle; 246 | #endif 247 | 248 | enum polarities {NEGATIVE, POSITIVE}; 249 | // For an enhanced polarity detection mechanism, which includes a persistence check 250 | #define POLARITY_CHECK_MAXCOUNT 3 // 1 251 | polarities polarityUnconfirmed; 252 | polarities polarityConfirmed; // for improved zero-crossing detection 253 | polarities polarityConfirmedOfLastSampleV; // for zero-crossing detection 254 | 255 | float residualEnergy_CT[max_no_of_channels]; 256 | double x[max_no_of_channels], y[max_no_of_channels]; // coefficients for real power interpolation 257 | 258 | 259 | // Temperature measurement 260 | // 261 | // Hardware Configuration 262 | byte W1Pin = 5; // 1-Wire pin for temperature = 5 for emonTx V3, 4 for emonTx V2 & emonTx Shield 263 | char DS18B20_PWR = -1; // Power pin for DS18B20 temperature sensors. Default -1 - power off 264 | 265 | // Global variables used only inside the library 266 | OneWire oneWire(W1Pin); 267 | bool temperatureEnabled = false; 268 | byte numSensors = 0; 269 | byte temperatureResolution = TEMPRES_11; 270 | byte temperatureMaxCount = 1; 271 | DeviceAddress *temperatureSensors = NULL; 272 | bool keepAddresses = false; 273 | int *temperatures = NULL; 274 | unsigned int temperatureConversionDelayTime; 275 | unsigned long temperatureConversionDelaySamples; 276 | 277 | volatile bool startConvertTemperatures = false; 278 | volatile bool convertingTemperaturesNoAC = false; // Only used when not using mains for timing. 279 | 280 | /************************************************************************************************** 281 | * 282 | * APPLICATION INTERFACE - Getters & Setters 283 | * 284 | * 285 | ***************************************************************************************************/ 286 | 287 | void EmonLibCM_SetADC_VChannel(byte ADC_Input, double _amplitudeCal) 288 | { 289 | ADC_Sequence[0] = ADC_Input; 290 | voltageCal = _amplitudeCal; 291 | } 292 | 293 | void EmonLibCM_SetADC_IChannel(byte ADC_Input, double _amplitudeCal, double _phaseCal) 294 | { 295 | currentCal[no_of_Iinputs] = _amplitudeCal; 296 | phaseCal_CT[no_of_Iinputs] = _phaseCal; 297 | ChannelInUse[no_of_Iinputs] = true; 298 | lChannel[ADC_Input] = no_of_Iinputs; 299 | ADC_Sequence[++no_of_Iinputs] = ADC_Input; 300 | } 301 | 302 | void EmonLibCM_ReCalibrate_VChannel(double _amplitudeCal) 303 | { 304 | voltageCal = _amplitudeCal * Vref / ADC_Counts; 305 | } 306 | 307 | void EmonLibCM_ReCalibrate_IChannel(byte ADC_Input, double _amplitudeCal, double _phaseCal) 308 | { 309 | byte lChannel = EmonLibCM_getLogicalChannel(ADC_Input); 310 | currentCal[lChannel] = _amplitudeCal * Vref / ADC_Counts; 311 | phaseCal_CT[lChannel] = _phaseCal; 312 | calcPhaseShift(lChannel); 313 | } 314 | 315 | void EmonLibCM_cycles_per_second(unsigned int _cycles_per_second) 316 | { 317 | cycles_per_second = _cycles_per_second; 318 | datalogPeriodInMainsCycles = datalog_period_in_seconds * cycles_per_second; 319 | calcTemperatureLead(); 320 | for (byte i = 0; i 1.05 || pf[i] < -1.05 || isnan(pf[i])) 726 | pf[i] = 0.0; 727 | 728 | realPower_CT[i] = powerNow + 0.5; // rounded to nearest Watt 729 | apparentPower_CT[i] = VA + 0.5; // rounded to nearest VA 730 | energyNow = (powerNow * datalog_period_in_seconds / frequencyDeviation) // correct for mains time != clock time 731 | + residualEnergy_CT[i]; // fp for accuracy 732 | } 733 | else 734 | { 735 | VA = Irms_CT[i] * assumedACVoltage; 736 | 737 | pf[i] = 0.0; 738 | 739 | realPower_CT[i] = VA + 0.5; // rounded to nearest Watt 740 | apparentPower_CT[i] = VA + 0.5; // rounded to nearest VA 741 | energyNow = (VA * datalog_period_in_seconds / frequencyDeviation) // correct for mains time != clock time 742 | + residualEnergy_CT[i]; // fp for accuracy 743 | } 744 | wattHoursRecent = energyNow / 3600; // integer assignment to extract whole Wh 745 | wh_CT[i]+= wattHoursRecent; // accumulated WattHours since start-up 746 | residualEnergy_CT[i] = energyNow - (wattHoursRecent * 3600.0); // fp for accuracy 747 | } 748 | 749 | // Retrieve the temperatures, which should be stored inside each sensor 750 | if (temperatureEnabled) 751 | { 752 | retrieveTemperatures(); 753 | } 754 | 755 | } 756 | 757 | bool EmonLibCM_Ready() 758 | { 759 | 760 | countPulses(); 761 | 762 | if (startConvertTemperatures) 763 | { 764 | startConvertTemperatures = false; 765 | convertTemperatures(); 766 | } 767 | 768 | if (datalogEventPending) 769 | { 770 | datalogEventPending = false; 771 | EmonLibCM_get_readings(); 772 | return true; 773 | } 774 | return false; 775 | } 776 | 777 | 778 | void EmonLibCM_confirmPolarity() 779 | { 780 | /* This routine prevents a zero-crossing point from being declared until 781 | * a certain number of consecutive samples in the 'other' half of the 782 | * waveform have been encountered. It forms part of the ISR. 783 | */ 784 | static byte count = 0; 785 | 786 | if (polarityUnconfirmed != polarityConfirmedOfLastSampleV) 787 | { 788 | count++; 789 | } 790 | else 791 | { 792 | count = 0; 793 | } 794 | 795 | if (count >= POLARITY_CHECK_MAXCOUNT) { 796 | count = 0; 797 | polarityConfirmed = polarityUnconfirmed; 798 | } 799 | } 800 | 801 | void calcPhaseShift(byte lChannel) 802 | { 803 | /* Calculate the 'X' & 'Y' coefficients of phase shift for the c.t. 804 | * phaseCal value supplied is the difference between VT lead and CT lead in degrees 805 | * Add the delay due to the time taken by the ADC to convert one sample (ADCDuration), 806 | * knowing the position of the current sample with respect to 807 | * the voltage, then convert to radians. 808 | * x & y are the constants used in the power interpolation. (Sanity check: x + y ≈ 1) 809 | */ 810 | 811 | const double two_pi = 6.2831853; 812 | double sampleRate = ADCDuration * (no_of_channels + 1) * two_pi * cycles_per_second / MICROSPERSEC; // in radians 813 | double phase_shift = (phaseCal_CT[lChannel] / 360.0 + (lChannel+1) * ADCDuration 814 | * (double)cycles_per_second/MICROSPERSEC) * two_pi; // Total phase shift in radians 815 | // (lChannel+1) was ADC_Sequence[lChannel+1] 816 | y[lChannel] = sin(phase_shift) / sin(sampleRate); 817 | x[lChannel] = cos(phase_shift) - y[lChannel] * cos(sampleRate); 818 | } 819 | 820 | 821 | void calcTemperatureLead(void) 822 | { 823 | /* Set lead time to start temperature conversion 824 | * The temparature sensors are instructed to 'convert' the temperature just in time for the result 825 | * to be available to be retrieved and reported along with the other data. 826 | * The lead time is the number of cycles (or in the absence of ac, no. of samples) to allow after 827 | * datalogEventPending has been set to true, so that temperature conversion will complete just before 828 | * the next datalog event. Adjust the resolution if necessary so that conversion within one 829 | * datalogging period is possible. 830 | */ 831 | 832 | int conversionLeadTime = (CONVERSION_LEAD_TIME >> (3 - ((temperatureResolution & 0x70) >> 5))); 833 | // Should give 95 - 760 ms lead time, now convert to cycles (for a.c. present) or samples (for a.c. not present). 834 | temperatureConversionDelayTime = datalogPeriodInMainsCycles - (long)conversionLeadTime * cycles_per_second / 1000 - 1; 835 | // '-1' to counter the effect of integer truncation, and make sure there is some spare time 836 | temperatureConversionDelaySamples = ((unsigned long)(datalog_period_in_seconds * 1000.0) 837 | - (unsigned long)conversionLeadTime - 5) * 1000 / ADCDuration; 838 | // '- 5' extra 5 ms to make sure there is some spare time 839 | } 840 | 841 | 842 | /************************************************************************************************** 843 | * 844 | * ADC Interrupt Handling 845 | * 846 | * 847 | ***************************************************************************************************/ 848 | 849 | 850 | void EmonLibCM_allGeneralProcessing_withinISR() 851 | { 852 | /* This routine deals with activities that are only required at specific points 853 | * within each mains cycle. It forms part of the ISR. 854 | */ 855 | if (stop) 856 | EmonLibCM_StopADC(); 857 | static unsigned int cycleCountForDatalogging = 0; 858 | // a simple routine for checking the performance of this new ISR structure 859 | 860 | if (acPresent) 861 | { 862 | if (polarityConfirmed == POSITIVE) 863 | { 864 | if (polarityConfirmedOfLastSampleV != POSITIVE) 865 | { 866 | /* Instantaneous power contributions are summed in accumulators during each 867 | * datalogging period. At the end of each period, copies are made of their 868 | * content for use by the main code. The accumulators, and any associated 869 | * counters are then reset for use during the next period. 870 | */ 871 | cycleCountForDatalogging++; 872 | #ifdef INTEGRITY 873 | if (sampleSetsDuringThisMainsCycle < lowestNoOfSampleSetsPerMainsCycle) 874 | { 875 | lowestNoOfSampleSetsPerMainsCycle = sampleSetsDuringThisMainsCycle; 876 | } 877 | sampleSetsDuringThisMainsCycle = 0; 878 | #endif 879 | 880 | // Used in stop start operation, discards the first partial cycle 881 | if (firstcycle==true && cycleCountForDatalogging >= min_startup_cycles) 882 | { 883 | firstcycle = false; 884 | cycleCountForDatalogging = 0; 885 | for (int i=0; i= datalogPeriodInMainsCycles && firstcycle==false) 907 | { 908 | cycleCountForDatalogging = 0; 909 | for (int i=0; i temperatureConversionDelaySamples && convertingTemperaturesNoAC == false) 955 | { 956 | // Only do it once per report 957 | startConvertTemperatures = true; 958 | convertingTemperaturesNoAC = true; 959 | } 960 | 961 | if (missing_VoltageSamples > ADCsamples_per_datalog_period) 962 | { 963 | missing_VoltageSamples = 0; // reset the missing samples count here. 964 | 965 | firstcycle = true; // firstcycle reset to true so that next reading 966 | // with voltage signal starts from the right place 967 | #ifdef INTEGRITY 968 | lowestNoOfSampleSetsPerMainsCycle = 999; 969 | #endif 970 | 971 | cycleCountForDatalogging = 0; 972 | for (int i=0; ino_of_channels) // no_of_channels = count of Current channels in use. Voltage channel (0) is always read, so total is no_of_channels + 1 1034 | next -= no_of_channels+1; 1035 | 1036 | ADMUX = ADCRef + ADC_Sequence[next]; // set up the next-but-one conversion 1037 | #ifdef SAMPPIN 1038 | digitalWrite(SAMPPIN,LOW); 1039 | #endif 1040 | // Count ADC samples for timing when voltage is unavailable 1041 | missing_VoltageSamples++; 1042 | 1043 | 1044 | if (sample_index==0) // ADC_Sample 0 is always the voltage channel. 1045 | { 1046 | #ifdef SAMPPIN 1047 | digitalWrite(SAMPPIN,HIGH); 1048 | #endif 1049 | // Removing the d.c. offset - new method: 1050 | // Rather than use a filter, which takes time to settle and will always contain a residual ripple, and which can lock 1051 | // up under start-up conditions, it is possible to remove the effect of the offset at the final stage of measurement. 1052 | // First take off the theoretical (constant) offset to reduce the size of the numbers (as Robin's original method). 1053 | // Then accumulate the sum of the resulting values so as to be able at the end of the measurement period to 1054 | // recalculate the true rms based on the rms with the offset and the average remaining offset. The remaining offset 1055 | // should be only a few counts. 1056 | // 1057 | lastSampleV_minusDC = sampleV_minusDC; // required for phaseCal algorithm 1058 | sampleV_minusDC = rawSample - (ADC_Counts >> 1); // remove nominal offset (a small offset will remain) 1059 | // Detect the ac input voltage. This is a 'rough&ready" rectifier/filter. It only needs to be good enough to detect 1060 | // sufficient voltage to provide assurance that the crossing detector will function properly 1061 | acSense -= acSense >> 2; 1062 | acSense += sampleV_minusDC > 0 ? sampleV_minusDC : -sampleV_minusDC; 1063 | acPresent = acSense > acDetectedThreshold; 1064 | // 1065 | // deal with activities that are only needed at certain stages of each 1066 | // voltage cycle. 1067 | 1068 | if (sampleV_minusDC > 0) 1069 | { 1070 | polarityUnconfirmed = POSITIVE; 1071 | } 1072 | else 1073 | { 1074 | polarityUnconfirmed = NEGATIVE; 1075 | } 1076 | EmonLibCM_confirmPolarity(); 1077 | EmonLibCM_allGeneralProcessing_withinISR(); 1078 | // 1079 | // for real power calculations 1080 | #ifdef INTEGRITY 1081 | sampleSetsDuringThisMainsCycle++; 1082 | #endif 1083 | sampleSetsDuringThisDatalogPeriod++; 1084 | samplesDuringThisCycle++; 1085 | // 1086 | // for the Vrms calculation 1087 | sum_Vsquared += ((long)sampleV_minusDC * sampleV_minusDC); // cumulative V^2 (V_ADC x V_ADC) 1088 | // 1089 | // store items for later use 1090 | cumV_deltas += sampleV_minusDC; // for use with offset removal 1091 | 1092 | polarityConfirmedOfLastSampleV = polarityConfirmed; // for identification of half cycle boundaries 1093 | #ifdef SAMPPIN 1094 | digitalWrite(SAMPPIN,LOW); 1095 | #endif 1096 | 1097 | } 1098 | 1099 | if (sample_index>=1 && sample_index <= no_of_channels) 1100 | { 1101 | // Now do much the same for each current sample as it is read. 1102 | // N.B. The Current channels are zero-based but offset by 1 from the ADC sample_index. 1103 | // That means Current Channel 0 is read from ADC Sample 1. 1104 | // ADC_Sample 0 is always the voltage channel, handled in the section above. 1105 | // ADC_Sample 1 is always a current but not necessarily CT1. 1106 | // Save the current sample for one sample set, so that the calculation normally uses voltage samples 1107 | // from each side of the current sample for interpolation in the phase shift algorithm. 1108 | 1109 | #ifdef SAMPPIN 1110 | digitalWrite(SAMPPIN,HIGH); 1111 | #endif 1112 | 1113 | 1114 | static int lastRawSample[max_no_of_channels]; 1115 | if (rawSample > 5) // skip processing this sample if input is grounded 1116 | { 1117 | ChannelInUse[sample_index-1] = true; 1118 | 1119 | // Offset removal for current is the same as for the voltage. 1120 | 1121 | lastRawSample[sample_index-1] -= (ADC_Counts >> 1); // remove nominal offset (a small offset will remain) 1122 | 1123 | sampleI_minusDC = lastRawSample[sample_index-1]; 1124 | 1125 | // calculate the "partial real powers" in this sample pair and add to the accumulated sums - fine d.c. offsets are still present 1126 | sumPA_CT[sample_index-1] += (long)sampleI_minusDC * lastSampleV_minusDC; // cumulative power A 1127 | sumPB_CT[sample_index-1] += (long)sampleI_minusDC * sampleV_minusDC; // cumulative power B 1128 | 1129 | // for Irms calculation 1130 | sumIsquared_CT[sample_index-1] += (long)sampleI_minusDC * sampleI_minusDC; // this has the fine d.c. offset still present 1131 | cumI_deltas_CT[sample_index-1] += sampleI_minusDC; // for use with offset removal 1132 | 1133 | } 1134 | else 1135 | ChannelInUse[sample_index-1] = false; 1136 | 1137 | lastRawSample[sample_index-1] = rawSample; // Delay everything by 1 sample 1138 | #ifdef SAMPPIN 1139 | digitalWrite(SAMPPIN,LOW); 1140 | #endif 1141 | } 1142 | 1143 | sample_index++; // advance the control flag 1144 | if (sample_index>no_of_channels) 1145 | sample_index = 0; 1146 | } 1147 | 1148 | /************************************************************************************************** 1149 | * 1150 | * TEMPERATURES 1151 | * 1152 | * 1153 | ***************************************************************************************************/ 1154 | 1155 | 1156 | void EmonLibCM_setTemperatureDataPin(byte _dataPin) 1157 | { 1158 | W1Pin = _dataPin; 1159 | } 1160 | 1161 | 1162 | void EmonLibCM_setTemperaturePowerPin(char _powerPin) 1163 | { 1164 | DS18B20_PWR = _powerPin; 1165 | if (DS18B20_PWR >= 0) 1166 | { 1167 | pinMode(DS18B20_PWR, OUTPUT); 1168 | } 1169 | else 1170 | { 1171 | pinMode(DS18B20_PWR, INPUT); 1172 | } 1173 | } 1174 | 1175 | 1176 | void EmonLibCM_setTemperatureResolution(byte _resolution) 1177 | { 1178 | switch (_resolution) 1179 | { 1180 | case (9): temperatureResolution = TEMPRES_9; 1181 | break; 1182 | case (10): temperatureResolution = TEMPRES_10; 1183 | break; 1184 | case (12): temperatureResolution = TEMPRES_12; 1185 | break; 1186 | default: temperatureResolution = TEMPRES_11; 1187 | break; 1188 | } 1189 | } 1190 | 1191 | 1192 | void EmonLibCM_setTemperatureAddresses(DeviceAddress *addressArray) 1193 | { 1194 | temperatureSensors = addressArray; 1195 | temperatureSensors[0][0] = 0x00; // Used by "TemperatureEnable" to trigger search for sensors 1196 | } 1197 | 1198 | 1199 | void EmonLibCM_setTemperatureAddresses(DeviceAddress *addressArray, bool keep) 1200 | { 1201 | temperatureSensors = addressArray; 1202 | keepAddresses = keep; 1203 | if (!keep) 1204 | temperatureSensors[0][0] = 0x00; // Used by "TemperatureEnable" to trigger search for sensors 1205 | } 1206 | 1207 | 1208 | void EmonLibCM_setTemperatureArray(int *temperatureArray) 1209 | { 1210 | temperatures = temperatureArray; 1211 | } 1212 | 1213 | 1214 | void EmonLibCM_setTemperatureMaxCount(int _maxCount) 1215 | { 1216 | temperatureMaxCount = _maxCount; 1217 | } 1218 | 1219 | 1220 | void EmonLibCM_TemperatureEnable(bool _enable) 1221 | { 1222 | //Setup and test for presence of DS18B20s, fill address array, set device resolution & write to EEPROM 1223 | DeviceAddress deviceAddress; 1224 | 1225 | if (temperatureSensors == NULL || temperatures == NULL) 1226 | { 1227 | temperatureEnabled = false; // Could corrupt memory, try to limit damage 1228 | numSensors = 0; 1229 | return; // & quit. 1230 | } 1231 | 1232 | 1233 | if (temperatureEnabled = _enable) 1234 | { 1235 | if (DS18B20_PWR >= 0) 1236 | { 1237 | pinMode(DS18B20_PWR, OUTPUT); 1238 | digitalWrite(DS18B20_PWR, HIGH); 1239 | delay(10); 1240 | } 1241 | oneWire.begin(W1Pin); // In case W1Pin has changed. 1242 | if (datalog_period_in_seconds < 1.0) // Not enough time to convert between samples. 1243 | temperatureResolution = TEMPRES_9; 1244 | byte scratchpad[3]; 1245 | scratchpad[0] = 0; // low alarm 1246 | scratchpad[1] = 0; // high alarm 1247 | scratchpad[2] = temperatureResolution; // resolution 1248 | 1249 | for(byte j=0; j 0; numSensors--) 1260 | { 1261 | if (temperatureSensors[numSensors-1][0] == DS18B20SIG) // signature of a DS18B20, so a pre-existing sensor 1262 | break; 1263 | } 1264 | } 1265 | else 1266 | { 1267 | if (numSensors > temperatureMaxCount) 1268 | numSensors = temperatureMaxCount; 1269 | } 1270 | 1271 | byte j=0; // search for one wire devices and copy to device address array. 1272 | 1273 | if (temperatureSensors[0][0] != DS18B20SIG) // not a signature of a DS18B20, so not a pre-existing array - search for sensors 1274 | while ((j < numSensors) && (oneWire.search(temperatureSensors[j]))) 1275 | j++; 1276 | oneWire.reset(); // write resolution to scratchpad 1277 | oneWire.write(SKIP_ROM); 1278 | oneWire.write(WRITE_SCRATCHPAD); 1279 | for(int i=0; i<3; i++) 1280 | oneWire.write(scratchpad[i]); 1281 | oneWire.reset(); // copy to EEPROM 1282 | oneWire.write(SKIP_ROM); 1283 | oneWire.write(COPY_SCRATCHPAD, true); 1284 | delay(20); // required by DS18B20 1285 | } 1286 | else // make sure power is turned off 1287 | { 1288 | if (DS18B20_PWR >= 0) 1289 | { 1290 | pinMode(DS18B20_PWR, OUTPUT); 1291 | digitalWrite(DS18B20_PWR, LOW); 1292 | pinMode(DS18B20_PWR, INPUT); 1293 | } 1294 | } 1295 | // Calculate number of cycles (or in the absence of ac, no. of samples) to allow after datalogEventPending has been set 1296 | // to true, so that temperature conversion will complete just before the next datalog event. Adjust the resolution if necessary 1297 | // so that conversion within one datalogging period is possible. 1298 | 1299 | calcTemperatureLead(); 1300 | } 1301 | 1302 | void printTemperatureSensorAddresses(bool emonPi) 1303 | { 1304 | if (emonPi) 1305 | Serial.print(F("|")); 1306 | Serial.print(F("Temperature Sensors found = ")); 1307 | Serial.print(numSensors); 1308 | Serial.print(" of "); 1309 | Serial.print(temperatureMaxCount); 1310 | 1311 | if (numSensors) 1312 | { 1313 | Serial.println(F(", with addresses...")); 1314 | for (int j=0; j< numSensors; j++) 1315 | { 1316 | if (emonPi) 1317 | Serial.print(F("|")); 1318 | for (int i=0; i<8; i++) 1319 | { 1320 | if (temperatureSensors[j][i] < 0x10) 1321 | Serial.print(F("0")); 1322 | Serial.print(temperatureSensors[j][i], 16); 1323 | Serial.print(F(" ")); 1324 | } 1325 | if (temperatureSensors[j][6] == 0x03) 1326 | Serial.print(F("Sensor may not be reliable")); 1327 | Serial.println(); 1328 | delay(5); 1329 | } 1330 | if (emonPi) 1331 | Serial.print(F("|")); 1332 | } 1333 | Serial.println(); 1334 | if (emonPi) 1335 | Serial.print(F("|")); 1336 | Serial.print(F("Temperature measurement is")); 1337 | Serial.print(temperatureEnabled?"":" NOT"); 1338 | Serial.println(F(" enabled.")); 1339 | if (emonPi) 1340 | Serial.print(F("|")); 1341 | Serial.println(); 1342 | delay(5); 1343 | 1344 | } 1345 | 1346 | void convertTemperatures(void) 1347 | { 1348 | if (temperatureEnabled) 1349 | { 1350 | if (DS18B20_PWR >= 0) 1351 | { 1352 | pinMode(DS18B20_PWR, OUTPUT); 1353 | digitalWrite(DS18B20_PWR, HIGH); 1354 | delay(2); 1355 | } 1356 | oneWire.reset(); 1357 | oneWire.write(SKIP_ROM); 1358 | oneWire.write(CONVERT_TEMPERATURE, true); 1359 | } // start conversion - all sensors 1360 | } 1361 | 1362 | 1363 | void retrieveTemperatures(void) 1364 | { 1365 | if (temperatureEnabled) 1366 | { 1367 | for (byte j=0; j < numSensors; j++) 1368 | { 1369 | byte buf[9]; 1370 | int result; 1371 | if ((datalog_period_in_seconds < 0.2) // not enough time to get a reading 1372 | || !oneWire.reset() 1373 | || !temperatureSensors[j][0]) // invalid address 1374 | { 1375 | temperatures[j] = BAD_TEMPERATURE; 1376 | continue; 1377 | } 1378 | else 1379 | { 1380 | oneWire.write(MATCH_ROM); 1381 | for(int i=0; i<8; i++) 1382 | oneWire.write(temperatureSensors[j][i]); 1383 | oneWire.write(READ_SCRATCHPAD); 1384 | for(int i=0; i<9; i++) 1385 | buf[i] = oneWire.read(); 1386 | } 1387 | 1388 | if(oneWire.crc8(buf,8)==buf[8]) 1389 | { 1390 | result = (buf[1]<<8)|buf[0]; 1391 | // result is temperature x16, multiply by 6.25 to convert to temperature x100 1392 | result = (result*6)+(result>>2); 1393 | } 1394 | else result = BAD_TEMPERATURE; 1395 | if (buf[4] == 0) 1396 | result = BAD_TEMPERATURE; 1397 | if (buf[6] == 0x0C && result == 8500) // not genuine 85 °C 1398 | result = BAD_TEMPERATURE; 1399 | if (result != BAD_TEMPERATURE && (result < -5500 || result > 12500)) 1400 | result = OUTOFRANGE_TEMPERATURE; 1401 | temperatures[j] = result; 1402 | delay(1); 1403 | } 1404 | for (byte j=numSensors; j < temperatureMaxCount; j++) 1405 | temperatures[j] = UNUSED_TEMPERATURE; 1406 | 1407 | digitalWrite(DS18B20_PWR, LOW); 1408 | convertingTemperaturesNoAC = false; 1409 | } 1410 | } 1411 | 1412 | 1413 | int EmonLibCM_getTemperatureSensorCount(void) 1414 | { 1415 | if (temperatureEnabled) 1416 | return(numSensors); 1417 | else 1418 | return 0; 1419 | } 1420 | 1421 | bool EmonLibCM_getTemperatureEnabled(void) 1422 | { 1423 | return temperatureEnabled; 1424 | } 1425 | 1426 | 1427 | float EmonLibCM_getTemperature(char sensorNumber) 1428 | { 1429 | int temp = temperatures[(unsigned char)sensorNumber]; 1430 | 1431 | if (sensorNumber < 0 || sensorNumber >= temperatureMaxCount) 1432 | return UNUSED_TEMPERATURE/100.0; 1433 | if (temp >=30000) 1434 | return temp/100.0; // 300 series error codes are already set 1435 | return (temp/100.0); 1436 | } 1437 | 1438 | 1439 | /************************************************************************************************** 1440 | * 1441 | * 'ADC COMPLETE' ISR 1442 | * 1443 | * 1444 | ***************************************************************************************************/ 1445 | 1446 | ISR(ADC_vect) 1447 | { 1448 | EmonLibCM_interrupt(); 1449 | } 1450 | 1451 | /************************************************************************************************** 1452 | * 1453 | * 'PULSE INPUT' ISRs 1454 | * 1455 | * 1456 | ***************************************************************************************************/ 1457 | // The pulse interrupt routines - run each time an edge of a pulse is detected 1458 | 1459 | void onPulse0(void) 1460 | { 1461 | onPulse(0); 1462 | } 1463 | 1464 | void onPulse1(void) 1465 | { 1466 | onPulse(1); 1467 | } 1468 | 1469 | void onPulse(byte channel) 1470 | { 1471 | if (!pulses[channel].timing) 1472 | { 1473 | pulses[channel].timing = true; 1474 | pulses[channel].pulseTime = millis(); 1475 | } 1476 | } 1477 | 1478 | /************************************************************************************************** 1479 | * 1480 | * 'PULSE INPUT' handler (not part of ISR) 1481 | * 1482 | * 1483 | ***************************************************************************************************/ 1484 | // Handle pulse de-bounce and count on defined edge 1485 | 1486 | void countPulses(void) 1487 | { 1488 | 1489 | for (byte channel=0; channel pulses[channel].PulseMinPeriod) // Check that contact bounce has finished 1496 | { 1497 | bool newstate = digitalRead(pulses[channel].PulsePin); 1498 | if ((pulses[channel].laststate && !newstate && pulses[channel].edge == FALLING) // falling edge 1499 | || (!pulses[channel].laststate && newstate && pulses[channel].edge == RISING)) // rising edge 1500 | { 1501 | pulses[channel].pulseIncrement++; 1502 | } 1503 | pulses[channel].timing = false; 1504 | pulses[channel].laststate = newstate; 1505 | } 1506 | } 1507 | else // No 'debounce' required - electronic switch presumed 1508 | { 1509 | bool newstate = digitalRead(pulses[channel].PulsePin); 1510 | if ((pulses[channel].laststate && !newstate && pulses[channel].edge == FALLING) // falling edge 1511 | || (!pulses[channel].laststate && newstate && pulses[channel].edge == RISING)) // rising edge 1512 | { 1513 | pulses[channel].pulseIncrement++; 1514 | } 1515 | pulses[channel].timing = false; 1516 | pulses[channel].laststate = newstate; 1517 | } 1518 | } 1519 | } 1520 | } 1521 | -------------------------------------------------------------------------------- /emonLibCM.h: -------------------------------------------------------------------------------- 1 | /* 2 | emonLibCM.h - Library for openenergymonitor 3 | GNU GPL 4 | */ 5 | 6 | // This library provides continuous single-phase monitoring of real power on up to five CT channels. 7 | // All of the time-critical code is now contained within the ISR, only the slower activities 8 | // are done within the main code. These slower activities include RF transmissions, 9 | // and all Serial statements (not part of the library). 10 | // 11 | // This library is suitable for either 50 or 60 Hz operation. 12 | // 13 | // Original Author: Robin Emley (calypso_rae on Open Energy Monitor Forum) 14 | // Addition of Wh totals by: Trystan Lea 15 | // Heavily modified to improve performance and calibration; temperature measurement 16 | // and pulse counting incorporated into the library, by Robert Wall 17 | // Release for testing 4/1/2017 18 | // 19 | // Version 2.0 21/11/2018 20 | // Version 2.01 3/12/2018 No change. 21 | // Version 2.02 13/07/2019 getLogicalChannel( ), ReCalibrate_VChannel( ), ReCalibrate_IChannel( ) added, setPulsePin( ) interrupt no. was obligatory, 22 | // Version 2.03 25/10/2019 getLineFrequency( ), setADC_VRef( ) added. 23 | // Version 2.04 1/08/2020 getDatalog_period( ) added. 24 | // Version 2.1.0 9/7/2021 2nd pulse input added. Array of structs was individual variables. Set watthour and pulse count added. 25 | // Version 2.1.1 26/7/2021 Version 2.1.0 was dated 9/7/20, "Shield" define was EmonLibCM2P_h 26 | // Version 2.1.2 7/8/2021 'assumedACVoltage' was 'assumedVrms' (name conflict in some sketches). 27 | // Version 2.2.0 14/9/2021 Optional parameter 'edge' added to setPulseMinPeriod(). 28 | // Version 2.2.1 5/12/2021 Repackaged - no change. 29 | // Version 2.2.2 15/9/2022 No change. 30 | 31 | 32 | 33 | #ifndef EmonLibCM_h 34 | #define EmonLibCM_h 35 | 36 | #if defined(ARDUINO) && ARDUINO >= 100 37 | 38 | #include "Arduino.h" 39 | 40 | #else 41 | 42 | #include "WProgram.h" 43 | 44 | #endif 45 | 46 | #define MICROSPERSEC 1.0e6 47 | 48 | // Dallas DS18B20 libraries & commands 49 | 50 | #include 51 | #include 52 | #include 53 | 54 | #define DS18B20SIG 0x28 55 | #define SKIP_ROM 0xCC 56 | #define MATCH_ROM 0x55 57 | #define CONVERT_TEMPERATURE 0x44 58 | #define READ_SCRATCHPAD 0xBE 59 | #define WRITE_SCRATCHPAD 0x4E 60 | #define COPY_SCRATCHPAD 0x48 61 | #define UNUSED_TEMPERATURE 30000 // this value (300°C) is sent if no sensor has ever been detected 62 | #define OUTOFRANGE_TEMPERATURE 30200 // this value (302°C) is sent if the sensor reports < -55°C or > +125°C 63 | #define BAD_TEMPERATURE 30400 // this value (304°C) is sent if no sensor is present or the checksum is bad (corrupted data) 64 | // NOTE: The sensor might report 85°C if the temperature is retrieved but the sensor has not been commanded 65 | // to measure the temperature. 66 | 67 | #define TEMPRES_9 0x1F 68 | #define TEMPRES_10 0x3F 69 | #define TEMPRES_11 0x5F 70 | #define TEMPRES_12 0x7F 71 | #define CONVERSION_LEAD_TIME 752 // this is the conversion time of the DS18B20 in ms at 12-bits (rounded up to multiple of 8). 72 | #define SENSOR_READ_TIME 16 // this is the time to read one sensor in ms (plus the interval between readings) 73 | 74 | #define VREF_EXTERNAL 0 // ADC Reference is Externally supplied voltage 75 | #define VREF_NORMAL 1 // ADC Reference is Processor Supply (AVcc) 76 | #define VREF_INTERNAL 3 // ADC Reference is Internal 1.1V reference 77 | 78 | 79 | typedef uint8_t DeviceAddress[8]; 80 | 81 | void EmonLibCM_cycles_per_second(unsigned int _cycles_per_second); 82 | void EmonLibCM_min_startup_cycles(unsigned int _min_startup_cycles); 83 | void EmonLibCM_datalog_period(float _datalog_period_in_seconds); 84 | void EmonLibCM_setADC(int _ADCBits, int ADCDuration); 85 | void EmonLibCM_ADCCal(double _RefVoltage); 86 | void EmonLibCM_SetADC_VChannel(byte ADC_Input, double _amplitudeCal); 87 | void EmonLibCM_SetADC_IChannel(byte ADC_Input, double _amplitudeCal, double _phaseCal); 88 | void EmonLibCM_setADC_VRef(byte _ADCRef); 89 | void EmonLibCM_ReCalibrate_VChannel(double _amplitudeCal); 90 | void EmonLibCM_ReCalibrate_IChannel(byte ADC_Input, double _amplitudeCal, double _phaseCal); 91 | 92 | void EmonLibCM_setAssumedVrms(double _assumedVrms); 93 | void EmonLibCM_setWattHour(byte channel, long _wh); 94 | void EmonLibCM_setPulseCount(long _pulseCount); 95 | void EmonLibCM_setPulseCount(byte channel, long _pulseCount); 96 | void EmonLibCM_setPulseEnable(byte channel, bool _enable); 97 | void EmonLibCM_setPulseEnable(bool _enable); 98 | void EmonLibCM_setPulsePin(int _pin); 99 | void EmonLibCM_setPulsePin(byte channel, int _pin, int _interrupt); 100 | void EmonLibCM_setPulsePin(byte channel, int _pin); 101 | void EmonLibCM_setPulseMinPeriod(int _period, byte _edge=FALLING); 102 | void EmonLibCM_setPulseMinPeriod(byte channel, int _period, byte _edge=FALLING); 103 | 104 | bool EmonLibCM_acPresent(void); 105 | 106 | int EmonLibCM_getLogicalChannel(byte ADC_Input); 107 | int EmonLibCM_getRealPower(int channel); 108 | int EmonLibCM_getApparentPower(int channel); 109 | double EmonLibCM_getPF(int channel); 110 | double EmonLibCM_getIrms(int channel); 111 | double EmonLibCM_getVrms(void); 112 | double EmonLibCM_getAssumedVrms(void); 113 | double EmonLibCM_getDatalog_period(void); 114 | double EmonLibCM_getLineFrequency(void); 115 | long EmonLibCM_getWattHour(int channel); 116 | unsigned long EmonLibCM_getPulseCount(void); 117 | unsigned long EmonLibCM_getPulseCount(byte channel); 118 | 119 | 120 | void EmonLibCM_setTemperatureDataPin(byte _dataPin); 121 | void EmonLibCM_setTemperaturePowerPin(char _powerPin); 122 | void EmonLibCM_setTemperatureResolution(byte _resolution); 123 | void EmonLibCM_setTemperatureAddresses(DeviceAddress *addressArray); 124 | void EmonLibCM_setTemperatureAddresses(DeviceAddress *addressArray, bool keep); 125 | void EmonLibCM_setTemperatureArray(int *temperatureArray); 126 | void EmonLibCM_setTemperatureMaxCount(int _maxCount); 127 | void EmonLibCM_TemperatureEnable(bool _enable); 128 | bool EmonLibCM_getTemperatureEnabled(void); 129 | void printTemperatureSensorAddresses(bool emonPi=false); 130 | void printTemperatureSensorsEnabled(void); 131 | void convertTemperatures(void); 132 | void retrieveTemperatures(void); 133 | int EmonLibCM_getTemperatureSensorCount(void); 134 | float EmonLibCM_getTemperature(char sensorNumber); 135 | 136 | #ifdef INTEGRITY 137 | int EmonLibCM_minSampleSetsDuringThisMainsCycle(void); 138 | #endif 139 | 140 | 141 | void EmonLibCM_Init(); 142 | void EmonLibCM_Start(); 143 | bool EmonLibCM_Ready(); 144 | 145 | // for general interaction between the main code and the ISR 146 | extern volatile boolean datalogEventPending; 147 | 148 | // General calculations 149 | 150 | void calcPhaseShift(byte lChannel); 151 | void calcTemperatureLead(void); 152 | 153 | // Pulse debounce 154 | 155 | void countPulses(void); 156 | 157 | #endif 158 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EmonLibCM", 3 | "keywords": "electricity, energy, monitoring", 4 | "description": "Energy Monitoring Continuous Sampling Library", 5 | "version": "2.1.0", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/openenergymonitor/EmonLibCM.git" 10 | }, 11 | "frameworks": "arduino", 12 | "platforms": 13 | [ 14 | "atmelavr", 15 | "atmelsam" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # EmonLibCM 2 | 3 | The EmonLibCM library is maintained by @Robert.Wall and mirrored here for users of github. 4 | 5 | See forum release: [EmonLibCM](https://community.openenergymonitor.org/t/emonlibcm-version-2-03/9241/1) 6 | 7 | The following release notes are copied from the forum thread. The installation section is modified to reflect git command line installation. 8 | 9 | --- 10 | 11 | ## EmonLibCM Release notes 12 | 13 | EmonLibCM is a Continuous Monitoring alternative to EmonLib. Whereas emonLib repeats, every 5 or 10 s, a sequence of voltage and current measurements in each of the input channels for a short period, normally 200 ms, and then reports the measurements back to the sketch for onwards transmission to (for example) emonCMS; emonLibCM continuously measures in the background the voltage and all the current input channels in turn, calculates a true average quantity for each and then informs the sketch that the measurements are available and should be read and processed. 14 | 15 | Temperature measurement with up to 6 DS18B20 sensors, and pulse counting, is included in the library. Neither must be added separately in the sketch. 16 | 17 | The CM library will always give an accurate measurement of the average over the reporting period of the voltage, and for each of up to 5 channels the current, real and apparent power and power factor. A cumulative total of Watt-hours for each channel is also available. It is suitable for single phase or split phase operation at 50 Hz or 60 Hz. It will give more accurate values than the 'discrete sample' sketch where rapidly switched loads are in use, for example a burst mode energy diverter or an induction hob. 18 | 19 | The inputs can be calibrated for any realistic voltage and current, the default calibration is for an emonTx with a UK a.c. adapter and 100 A : 50 mA current transformers. 20 | 21 | The library is distributed as a compressed Zip file. This contains the library files themselves (emonLibCM.cpp & emonLibCM.h), two directories with example sketches and a PDF format User Documentation file that contains notes on using the library, a full description of each method, instructions both for setting the initial configuration and for calibration, and brief notes explaining the example sketches. EmonLibCM also depends on several other libraries, and these are listed. 22 | 23 | --- 24 | 25 | ## Installing The Library 26 | 27 | ### Arduino IDE 28 | 29 | #### From Zip file 30 | 31 | The directory emonLibCM together with its contents should be extracted from the zip file and copied into the "libraries" directory, alongside (in the same level of the hierarchy as) the emonLib directory. 32 | 33 | If you wish to use the example sketches, these (in their respective directories) should be moved or copied into your Sketchbook. 34 | 35 | The User Documentation PDF file can be moved or copied to a convenient location of your choosing. 36 | 37 | 38 | 39 | #### GitHub 40 | 41 | Navigate to your Arduino libraries directory and clone this repository: 42 | 43 | git clone https://github.com/openenergymonitor/EmonLibCM.git 44 | 45 | Reload Arduino to start using the library. 46 | 47 | --- 48 | 49 | ## Changelog 50 | 51 | Version 2.0 corrected some errors generated when converting from the original sketch, and incorporates improved handling of phase/timing compensation and improved removal of the d.c. bias. There are no other major changes from the version that has been tested by @TrystanLea since early 2017. 52 | 53 | - Changes for V2.01: Errors in phase error correction. 54 | 55 | - Changes for V2.02: Temperature measurement: Added "BAD_TEMPERATURE" return value when reporting period < 0.2 s, getLogicalChannel( ), ReCalibrate_VChannel( ), ReCalibrate_IChannel( ) added, setPulsePin( ) interrupt no. was obligatory, pulse & temperatures were set/enabled only at startup, setTemperatureDataPin was ineffective, preloaded sensor addresses were not handled properly. 56 | 57 | - Changes for V2.03: Mains Frequency reporting with getLineFrequency( ) added, energy calculation changed to use the internal clock rather than mains time. ADC reference source was AVCC and not selectable - ability to select using setADC_VRef( ) added (Note the warning in the documentation regarding the use of this function). 58 | 59 | - Changes for V2.04: getDatalog_period() has been added, the datalogging period now has a minimum value of 0.1 s. The list of temperature sensor addresses is now ignored if first device is not a DS18B20. 'BAD_TEMPERATURE' is now returned if sensor address is made invalid during operation, and 'UNUSED_TEMPERATURE' is returned for all addresses above the number of sensors detected. The sensor power pin (if used) is now always set low after a reading, and when temperature readings are disabled. The resolution of the temperature measurements is reduced when datalogging faster than once per second is requested, and temperature measurement is disabled when datalogging faster than 5 per second is set. In the example sketches, an 'else' clause has added to "if (EmonLibCM_Ready())" to keep JeeLib alive, and a third example sketch, only for emonTx's with a RFM69CW, using the JeeLib 'Classic' message format but not using JeeLib itself, has been added. There have also been some cosmetic changes. 60 | 61 | ## USING THE LIBRARY 62 | 63 | Three example sketches are provided as part of the distribution: 64 | **EmonTxV34CM_min** is the absolute minimum sketch required to exercise the library and produce meaningful values. 65 | **EmonTxV34CM_max** gives an example of every API call available. However, as distributed, it actually changes nothing as everything is again given the default value. If you need to change one of the defaults, then only the API call that sets that value is needed, and you can copy and add that call to the "minimum' demo sketch. 66 | **EmonTxV34CM_min_RFM69** is essentially the same as *EmonTxV34CM_min*, but uses the code in the file ``rfm.ino`` to drive the RFM69CW radio, so making better use of the RFM69's capabilities and minimising disturbance to the energy sampling. 67 | 68 | The EmonLibCM library is not a direct replacement for the 'discrete sample' library emonLib. Significant changes will be need to be made if emonLibCM is to replace emonLib in any particular sketch. 69 | The example sketches are intended only as a demonstration of the library. They do not (for example) take any account of the DIP switch settings of the emonTx V3.4. Great care must be taken if any significant additional load is to be put on the processor. 70 | 71 |   72 | 73 | ## Acknowledgements 74 | 75 | Jörg Becker (@joergbecker32) for his background work on interrupts and the ADC. 76 | Robin Emley (@calypso_rae) for his energy diverter software, from which the major part of the library was derived by @TrystanLea 77 | @ursi (Andries) and @mafheldt (Mike Afheldt) for suggestions made at https://community.openenergymonitor.org/t/emonlib-inaccurate-power-factor/3790 and https://community.openenergymonitor.org/t/rms-calculations-in-emonlib-and-learn-documentation/3749/3 78 | 79 | Please see [EmonLibCM - Version 2 (Support)](https://community.openenergymonitor.org/t/emonlibcm-version-2-support/9242/) to comment or request support. 80 | --------------------------------------------------------------------------------