From a3054251ef4bda0564e9c0c09b761c0059eca878 Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 20 May 2024 12:52:35 +0200 Subject: [PATCH] Button handling --- .../Arduino-DTSU666H_PowerMeter.ino | 99 +++++++++++-------- Arduino-DTSU666H_PowerMeter/LiquidCrystal.cpp | 25 ++++- DTSU666ModbusSniffer/LiquidCrystal_I2C.hpp | 33 ++++++- 3 files changed, 108 insertions(+), 49 deletions(-) diff --git a/Arduino-DTSU666H_PowerMeter/Arduino-DTSU666H_PowerMeter.ino b/Arduino-DTSU666H_PowerMeter/Arduino-DTSU666H_PowerMeter.ino index 022ff9f..22d0c9d 100644 --- a/Arduino-DTSU666H_PowerMeter/Arduino-DTSU666H_PowerMeter.ino +++ b/Arduino-DTSU666H_PowerMeter/Arduino-DTSU666H_PowerMeter.ino @@ -41,7 +41,7 @@ * 4. Wait 3.3 ms. * 5. After 20 + 6.666 ms do 10 ms phase B current measurement. Multiply values with voltage. * 6. Wait 3.3 ms - * 7. After 40 ms do 20 ms phase A current measurement, which will also cover negative current. Multiply values with voltage. + * 7. After 40 ms do 20 ms phase A current measurement, which will also cover negative current (which gives positive values at the last 10 ms). Multiply values with voltage. * 8. 20 ms for math and reply to RS485 or output to LCD until starting again at 80 ms - x ms. * * In short notation: @@ -185,8 +185,8 @@ uint16_t calculateCrc(uint8_t *aBuffer, uint16_t aBufferLength); #define DURATION_OF_ONE_MEASUREMENT_MILLIS 60 // Cannot be changed! From start of voltage measurement to end of L1 negative current measurement #define DURATION_OF_ONE_LOOP_MILLIS 80 // Cannot be changed! One measurement and 20 ms for synchronizing voltage used for communication and print #define LOOPS_PER_MINUTE (60000 / DURATION_OF_ONE_LOOP_MILLIS) -uint16_t sLastTCNT1; -uint16_t sDeltaTCNT1; // Difference between two synchronizing points, with 26 us resolution because of ADC period of 26 us e.g. 79911, 79885, 79859 +uint16_t sLastTCNT1; // Only for display on LCD +uint16_t sDeltaTCNT1; // Only for display on LCD. Difference between two synchronizing points, with 26 us resolution because of ADC period of 26 us /* * LCD stuff @@ -218,7 +218,7 @@ void printPaddedHexOnMyLCD(uint8_t aHexByteValue); */ #include "SoftwareSerialTX.h" /* - * Use a 115200 baud software serial for the short request frame. + * Use a 9600 baud software serial for the reply frames. * If available, we also can use a second hardware serial here :-). */ SoftwareSerialTX TxToModbusClient(MODBUS_TX_PIN); @@ -302,14 +302,19 @@ void handlePageButtonPress(bool aButtonToggleState __attribute__((unused))) { // Select phase to be plotted sIndexOfCurrentToPrint = ((sIndexOfCurrentToPrint + 1) & 0x03); // increment the buffer to print an wrap at 4 } else { - // switch LCD page. Long press handling for reset Energy page is in loop. - sLCDInfoPageCounter = 0; - sLCDDisplayPage++; - if (sLCDDisplayPage > POWER_METER_PAGE_MAX) { - sLCDDisplayPage = POWER_METER_PAGE_POWER; - // Clear watchdog flag position - myLCD.setCursor(0, 1); - myLCD.print(' '); + if (sCounterForDisplayFreeze != 0) { + // Do nothing, just reset display freeze + sCounterForDisplayFreeze = 0; + } else { + // switch LCD page. Long press handling for reset Energy page is in loop. + sLCDInfoPageCounter = 0; + sLCDDisplayPage++; + if (sLCDDisplayPage > POWER_METER_PAGE_MAX) { + sLCDDisplayPage = POWER_METER_PAGE_POWER; + // Clear watchdog flag position + myLCD.setCursor(0, 1); + myLCD.print(' '); + } } } } @@ -414,7 +419,7 @@ void setup() { // Timer 1 for sample timing runs continuously in Normal mode TCCR1A = 0; - TCCR1B = _BV(CS11); // clock divider is 8 + TCCR1B = _BV(CS11); // clock divider is 8 -> 2 MHz / 0.5 us. 32 ms / 30.51 Hz until overflow TIFR1 = 0; // Clear all compare flags TCNT1 = 0; @@ -458,7 +463,7 @@ void setup() { Serial.flush(); Serial.begin(MODBUS_BAUDRATE); while (Serial.available()) { - Serial.read(); + Serial.read(); // skip spurious input } /* * 9600 baud soft serial to Modbus client. For serial from client we use the hardware Serial RX. @@ -480,8 +485,8 @@ void loop() { * Read voltage of phase A */ readVoltage(true); - sWatchdogResetInfoCharacter = 'C'; // Hangup at current measurement code line C / 3 TIMING_PIN_LOW(); + sWatchdogResetInfoCharacter = 'C'; // Hangup at current measurement code line C / 3 /* * - Change ADC channel, set new timer compare value, and clear all timer compare flags. @@ -491,7 +496,7 @@ void loop() { ADMUX = LINE_WITH_13_MS_DELAY | (INTERNAL << SHIFT_VALUE_FOR_REFERENCE); TIFR1 = _BV(OCF1A); // Clear all timer compare flags - digitalWriteFast(LED_BUILTIN, LOW); + digitalWriteFast(LED_BUILTIN, LOW); // reset watt hour flash here uint8_t tIndexOfCurrentToPrint = 0xFF; // Disable store to array @@ -502,13 +507,13 @@ void loop() { loop_until_bit_is_set(TIFR1, OCF1A); // Wait for timer TIMING_PIN_HIGH(); - /* - * Read current values and compute power of phase C - */ sWatchdogResetInfoCharacter = 'c'; // Hangup at current measurement code line C / 3 + + /************************************************** + * Read current values and compute power of phase C + **************************************************/ int32_t tPowerRaw = readCurrentRaw(tIndexOfCurrentToPrint == LINE_WITH_13_MS_DELAY); // at 3.396 ms TIMING_PIN_LOW(); - sWatchdogResetInfoCharacter = 'B'; // Hangup at current measurement code line B / 2 digitalWriteFast(LED_BUILTIN, HIGH); // To signal, that loop is still running @@ -536,13 +541,13 @@ void loop() { loop_until_bit_is_set(TIFR1, OCF1A); TIMING_PIN_HIGH(); // 3.34 ms - /* - * Read current values and compute power of phase B - */ sWatchdogResetInfoCharacter = 'b'; // Hangup at current measurement code line B / 2 + + /************************************************** + * Read current values and compute power of phase B + **************************************************/ tPowerRaw = readCurrentRaw(tIndexOfCurrentToPrint == LINE_WITH_7_MS_DELAY); TIMING_PIN_LOW(); - sWatchdogResetInfoCharacter = 'A'; // Hangup at positive current measurement code line A / 1 // prepare for next line reading @@ -573,22 +578,25 @@ void loop() { sLCDLoopCounter++; checkPowerCorrectionPins(); + sWatchdogResetInfoCharacter = 'a'; // Hangup at wait before positive current measurement code line A / 1 loop_until_bit_is_set(TIFR1, OCF1A); TIMING_PIN_HIGH(); // 3.34 ms - /* - * Read current values and compute power of reference phase A - */ sWatchdogResetInfoCharacter = '+'; // Hangup at positive current measurement code line A / 1 - tPowerRaw = readCurrentRaw(tIndexOfCurrentToPrint == LINE_WHICH_CAN_BE_NEGATIVE); - /* - * Read negative half wave phase A, since this is the channel, where we may sell power - */ - sWatchdogResetInfoCharacter = '-'; // Hangup at negative current measurement code line A / 1 - tPowerRaw -= readCurrentRaw(tIndexOfCurrentToPrint == 0); // negative half wave of reference phase A is always stored in 0 - sWatchdogResetInfoCharacter = 'L'; // Hangup at loop() code + /************************************************************ + * Read current values and compute power of reference phase A + ************************************************************/ + tPowerRaw = readCurrentRaw(tIndexOfCurrentToPrint == LINE_WHICH_CAN_BE_NEGATIVE); // gives 0 for negative power + + sWatchdogResetInfoCharacter = '-'; // Hangup at negative current measurement code line A / 1 + /*************************************************** + * Read the half wave phase A with negative voltage. + * If we sell power, we have positive current here. + ***************************************************/ + tPowerRaw -= readCurrentRaw(tIndexOfCurrentToPrint == 0); // negative half wave of reference phase A is stored in 0 TIMING_PIN_LOW(); + sWatchdogResetInfoCharacter = 'L'; // Hangup at loop() code /* * fast actions @@ -597,7 +605,7 @@ void loop() { enableMillisInterrupt(DURATION_OF_ONE_MEASUREMENT_MILLIS); // compensate for 60 ms of ADC reading /* - * Reset watchdog here + * Reset 8 s watchdog here */ wdt_reset(); @@ -605,12 +613,16 @@ void loop() { * 60 ms of measurement are gone now, do computing and slow actions */ if (sPowerCorrectionPercentage == 100) { - tPower = (tPowerRaw + (POWER_SCALE_DIVISOR / 2)) / POWER_SCALE_DIVISOR; // shift by 15 -> 12 us + if (tPowerRaw >= 0) { + tPower = (tPowerRaw + (POWER_SCALE_DIVISOR / 2)) / POWER_SCALE_DIVISOR; // shift by 15 -> 12 us + } else { + tPower = (tPowerRaw - (POWER_SCALE_DIVISOR / 2)) / POWER_SCALE_DIVISOR; // shift by 15 -> 12 us + } } else { tPower = (((tPowerRaw / 100) * sPowerCorrectionPercentage) + (POWER_SCALE_DIVISOR / 2)) / POWER_SCALE_DIVISOR; // shift by 15 -> 12 us } - sPowerForLCDAccumulator[LINE_WHICH_CAN_BE_NEGATIVE - 1] += tPower; + sPowerForLCDAccumulator[LINE_WHICH_CAN_BE_NEGATIVE - 1] += tPower; // -1 for array index sPowerForModbusAccumulator[LINE_WHICH_CAN_BE_NEGATIVE - 1] += tPower; sEnergyAccumulator[LINE_WHICH_CAN_BE_NEGATIVE - 1] += tPower; sEnergyAccumulatorSumForFlash += tPower; @@ -621,7 +633,7 @@ void loop() { if (sWattHourFlashCounter > 0) { sWattHourFlashCounter--; if (sWattHourFlashCounter == 0) { - digitalWriteFast(LED_BUILTIN, HIGH); // Flash again for 30 ms + digitalWriteFast(LED_BUILTIN, HIGH); // Flash again for 30 ms, i.e. until voltage reading of next loop } } @@ -630,7 +642,7 @@ void loop() { */ if (sEnergyAccumulatorSumForFlash > ENERGY_DIVISOR) { sEnergyAccumulatorSumForFlash -= ENERGY_DIVISOR; - digitalWriteFast(LED_BUILTIN, HIGH); // one 30 ms flash + digitalWriteFast(LED_BUILTIN, HIGH); // One 30 ms flash, i.e. until voltage reading of next loop } else if (sEnergyAccumulatorSumForFlash < -ENERGY_DIVISOR) { sEnergyAccumulatorSumForFlash += ENERGY_DIVISOR; sWattHourFlashCounter = 2; // 2 30 ms flashes on negative energy @@ -645,7 +657,7 @@ void loop() { /* * Handle periodical print request. - * Must be before printDataOnLCD, because this resets the power value + * Must be before printDataOnLCD(), because this resets the power value */ if (tPeriodicallyPrintIsEnabled) { checkAndPrintInputSignalValuesForArduinoPlotter(); @@ -869,7 +881,7 @@ void printDataOnLCD() { */ // Mains period as measured by timer 1 myLCD.print(F("Period = ")); // micro sign - myLCD.print((uint32_t) (sDeltaTCNT1 / 2) + (256L * 256L)); // We have one overflow in timer each 64 ms. 5 digits. + myLCD.print((uint32_t) (sDeltaTCNT1 / 2) + (256L * 256L)); // We have one overflow in timer each 32 ms. 5 digits. myLCD.print(F("\xE4s")); // micro sign } } @@ -966,9 +978,10 @@ void readVoltage(bool aDoFindZeroCrossing) { */ if (tValue != 0) { if (tCounter >= 3) { - sDeltaTCNT1 = TCNT1 - sLastTCNT1; + sDeltaTCNT1 = TCNT1 - sLastTCNT1; // For display on LCD sLastTCNT1 = TCNT1; - OCR1A = TCNT1 + ((2 * 13333) - (3 * 26)); // set next compare to 13333 us after start of data. Timer has a resolution of 0.5 us. + + OCR1A = TCNT1 + ((13333 - (3 * 26)) * 2); // set next compare to 13333 us after start of non zero values. Timer has a resolution of 0.5 us. disableMillisInterrupt(); // disable it here to have it exact 60 ms disabled tDoSearchForStart = false; } diff --git a/Arduino-DTSU666H_PowerMeter/LiquidCrystal.cpp b/Arduino-DTSU666H_PowerMeter/LiquidCrystal.cpp index 68fa9b5..802c4d6 100644 --- a/Arduino-DTSU666H_PowerMeter/LiquidCrystal.cpp +++ b/Arduino-DTSU666H_PowerMeter/LiquidCrystal.cpp @@ -5,6 +5,20 @@ #include #include "Arduino.h" +#if defined(__AVR__) +/* + * The datasheet says: a command need > 37us to settle. + * Use a delay of 2 us instead of 100 us after each command, + * because the overhead of this library seems to be using the other 35 us. + * At least it works perfectly for all my LCD's connected to Uno, Nano etc. + * and it saves a lot of time in realtime applications using LCD as display, + * like https://github.com/ArminJo/Arduino-DTSU666H_PowerMeter + */ +# if !defined(DO_NOT_USE_FAST_LCD_TIMING) +#define USE_FAST_LCD_TIMING +# endif +#endif + // When the display powers up, it is configured as follows: // // 1. Display clear @@ -21,7 +35,7 @@ // S = 0; No shift // // Note, however, that resetting the Arduino doesn't reset the LCD, so we -// can't assume that its in that state when a sketch starts (and the +// can't assume that it is in that state when a sketch starts (and the // LiquidCrystal constructor is called). LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable, @@ -72,7 +86,7 @@ void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t en else _displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS; -// begin(16, 1); + begin(16, 1); // initializing for a 1601 LCD, but the user must call begin() with the right values at startup. Outcommenting increases program size } void LiquidCrystal::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) { @@ -262,12 +276,14 @@ void LiquidCrystal::noAutoscroll(void) { // Allows us to fill the first 8 CGRAM locations // with custom characters +// This also sets cursor to 0.0 void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) { location &= 0x7; // we only have 8 locations 0-7 command(LCD_SETCGRAMADDR | (location << 3)); for (int i=0; i<8; i++) { write(charmap[i]); } + command(LCD_SETDDRAMADDR); // set cursor to 0.0, this avoids overwriting CGRAM by next write() command. } /*********** mid level commands, for sending data/cmds */ @@ -306,8 +322,11 @@ void LiquidCrystal::pulseEnable(void) { digitalWrite(_enable_pin, HIGH); delayMicroseconds(1); // enable pulse must be >450ns digitalWrite(_enable_pin, LOW); -// delayMicroseconds(100); // commands need > 37us to settle +#if defined(USE_FAST_LCD_TIMING) delayMicroseconds(2); // commands need > 37us to settle +#else + delayMicroseconds(100); // commands need > 37us to settle +#endif } void LiquidCrystal::write4bits(uint8_t value) { diff --git a/DTSU666ModbusSniffer/LiquidCrystal_I2C.hpp b/DTSU666ModbusSniffer/LiquidCrystal_I2C.hpp index 2659ef4..3369306 100644 --- a/DTSU666ModbusSniffer/LiquidCrystal_I2C.hpp +++ b/DTSU666ModbusSniffer/LiquidCrystal_I2C.hpp @@ -1,4 +1,12 @@ // Based on the work by DFRobot +/* + * Extensions made by AJ 2023 + * Removed Arduino 0.x support + * Added SoftI2CMaste support, which drastically reduces program size. + * Added OLED stuff + * Added createChar() with PROGMEM input + * Added fast timing + */ #include "Arduino.h" @@ -23,6 +31,18 @@ inline size_t LiquidCrystal_I2C::write(uint8_t value) { #include "SoftWire.h" #endif +#if defined(__AVR__) +/* + * The datasheet says: a command need > 37us to settle. Enable pulse must be > 450ns. + * Use no delay for enable pulse after each command, + * because the overhead of this library seems to be using the 37 us and 450 ns. + * At least it works perfectly for all my LCD's connected to Uno, Nano etc. + * and it saves a lot of time in realtime applications using LCD as display, + * like https://github.com/ArminJo/Arduino-DTSU666H_PowerMeter + */ +#define USE_FAST_TIMING +#endif + // When the display powers up, it is configured as follows: // // 1. Display clear @@ -131,7 +151,11 @@ void LiquidCrystal_I2C::begin(uint8_t cols __attribute__((unused)), uint8_t line /********** high level commands, for the user! */ void LiquidCrystal_I2C::clear() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero +#if defined(USE_FAST_TIMING) delayMicroseconds(1500); // this command takes a long time! // AJ 20.9.23 1200 is too short for my 2004 LCD's, 1400 is OK +#else + delayMicroseconds(2000); // this command takes a long time! +#endif if (_oled) setCursor(0, 0); } @@ -274,10 +298,13 @@ void LiquidCrystal_I2C::expanderWrite(uint8_t _data) { void LiquidCrystal_I2C::pulseEnable(uint8_t _data) { expanderWrite(_data | En); // En high -// delayMicroseconds(1); // enable pulse must be >450ns // AJ 20.9.23 not required for my LCD's - +#if !defined(USE_FAST_TIMING) + delayMicroseconds(1); // enable pulse must be > 450ns // AJ 20.9.23 not required for my LCD's +#endif expanderWrite(_data & ~En); // En low -// delayMicroseconds(50); // commands need > 37us to settle // AJ 20.9.23 not required for my LCD's +#if !defined(USE_FAST_TIMING) + delayMicroseconds(50); // commands need > 37us to settle // AJ 20.9.23 not required for my LCD's +#endif } // Alias functions