Skip to content

Commit

Permalink
Button handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ArminJo committed May 29, 2024
1 parent 7eabd2a commit a305425
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 49 deletions.
99 changes: 56 additions & 43 deletions Arduino-DTSU666H_PowerMeter/Arduino-DTSU666H_PowerMeter.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(' ');
}
}
}
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -597,20 +605,24 @@ 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();

/*
* 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;
Expand All @@ -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
}
}

Expand All @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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;
}
Expand Down
25 changes: 22 additions & 3 deletions Arduino-DTSU666H_PowerMeter/LiquidCrystal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@
#include <inttypes.h>
#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
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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) {
Expand Down
33 changes: 30 additions & 3 deletions DTSU666ModbusSniffer/LiquidCrystal_I2C.hpp
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit a305425

Please sign in to comment.