This commit is contained in:
2026-05-25 14:11:30 -04:00
parent 21938fb086
commit 3bd9cf9626
+250 -214
View File
@@ -1,4 +1,3 @@
#define RXD_PIN 5
#define TXD_PIN 6
#define CONTROL_PIN 4
@@ -17,33 +16,23 @@
#include <hd44780ioClass/hd44780_I2Cexp.h>
constexpr uint8_t LCD_I2C_ADDRESS = 0x27;
hd44780_I2Cexp lcd(LCD_I2C_ADDRESS, 20, 4);
constexpr uint8_t LCD_COLS = 20;
constexpr uint8_t LCD_ROWS = 4;
constexpr uint8_t WATER_ROW = LCD_ROWS - 1;
hd44780_I2Cexp lcd(LCD_I2C_ADDRESS, LCD_COLS, LCD_ROWS);
constexpr uint8_t RELAY_CONTROL_ADDRESS = 0x21;
constexpr uint8_t BUTTONS_CONTROL_ADDRESS = 0x20;
constexpr unsigned long COMMAND_INTERVAL_MS = 1000;
constexpr uint16_t MODBUS_RESPONSE_TIMEOUT_MS = 200;
uint8_t modbusReadCmd[] = {0x01, 0x03, 0x00, 0x04, 0x00, 0x01};
unsigned long lastCommandTime = 0;
const unsigned long COMMAND_INTERVAL = 1000;
volatile bool g_buttonInterruptFired = false;
int g_waterLevelCm = -1;
void addWaterLevel();
// String *descr[] = {"NA", "Pump", "Barrel -> Back Left", "Back Left -> Barrel", "Barrel to Hose", "CW to Hose", "NA", "NA"};
void printBin(byte aByte)
{
for (int8_t aBit = 7; aBit >= 0; aBit--)
{
// Serial.print(aBit);
// Serial.write(":");
Serial.write(bitRead(aByte, aBit) ? '1' : '0');
// Serial.write(" ");
}
Serial.println();
}
// NA, Pump, Barrel -> Back Left, Back Left -> Barrel, Barrel to Hose, CW to Hose, NA, NA
struct Action
{
const char *name;
@@ -70,130 +59,36 @@ inline Action g_actions[] = {
{"NotAssigned", 0b00000000, 0b00000000, false},
};
uint8_t current_stat = 0b00000000;
uint8_t current_mask = 0b00000000;
// Forward declarations
void updateLCDDisplay();
void addWaterLevel();
void applyRelayStateIfChanged();
void generateMasksByActive();
void updateStateByLastAction(ActionId lastAction);
void readAndPrintButtons();
void pollSerial2WaterLevel();
uint16_t calculateCRC16(const uint8_t *data, uint8_t length);
void sendCommand(const uint8_t *cmd, size_t len);
void sendModbusCommand();
void scanI2CDevices();
void buildTrimmedWaterLevelText(char *outBuffer, size_t outBufferSize);
void buildRightJustifiedWaterRow(char *rowBuffer, size_t rowBufferSize);
inline Action &getAction(ActionId id)
{
return g_actions[static_cast<size_t>(id)];
}
uint8_t current_stat = 0b00000000;
uint8_t current_mask = 0b00000000;
void updateLCDDisplay()
void printBin(byte aByte)
{
lcd.clear();
uint8_t c_row = 0;
for (size_t i = 0; i < static_cast<size_t>(ActionId::Count); ++i)
for (int8_t aBit = 7; aBit >= 0; --aBit)
{
if (g_actions[i].active)
{
lcd.setCursor(0, c_row);
lcd.print(g_actions[i].name);
c_row++;
}
}
addWaterLevel();
}
void generateMasksByActive()
{
current_stat = 0b00000000;
current_mask = 0b00000000;
for (size_t i = 0; i < static_cast<size_t>(ActionId::Count); ++i)
{
Action &c_action = g_actions[i];
if (c_action.active)
{
current_mask = current_mask | c_action.mask;
current_stat = (current_stat & ~c_action.mask) | (c_action.stat & c_action.mask);
}
}
}
void updateStateByLastAction(ActionId LastAction)
{
Action &c_action = getAction(LastAction);
if (c_action.active)
{
c_action.active = false;
generateMasksByActive();
updateLCDDisplay();
return;
}
uint8_t change_stat = c_action.stat;
uint8_t change_mask = c_action.mask;
uint8_t old_stat = current_stat;
uint8_t old_mask = current_mask;
uint8_t potential_new_stat = (current_stat & ~change_mask) | (change_stat & change_mask);
uint8_t potential_old_change = (potential_new_stat ^ old_stat) & old_mask;
if (potential_old_change > 0)
{
Serial.print(c_action.name);
Serial.print(" collides with existing. Setting new mask to this one");
Serial.println();
current_stat = change_stat;
current_mask = change_mask;
for (size_t i = 0; i < static_cast<size_t>(ActionId::Count); ++i)
{
g_actions[i].active = false;
}
c_action.active = true;
}
else
{
Serial.print(c_action.name);
Serial.print(" has no collisions with existing. Setting updated mask");
Serial.println();
current_stat = potential_new_stat;
current_mask = old_mask | change_mask;
c_action.active = true;
}
Serial.print("Stat: ");
printBin(current_stat);
Serial.print("Mask: ");
printBin(current_mask);
updateLCDDisplay();
}
void applyRelayStateIfChanged()
{
static uint8_t lastRelayValue = 0xFF;
// Serial.println(current_stat, BIN);
// Serial.println(current_mask, BIN);
// uint8_t relayValue = static_cast<uint8_t>(~((current_stat & current_mask) | static_cast<uint8_t>(~current_mask)));
uint8_t relayValue = ~current_stat;
for (int8_t bit = 7; bit >= 0; --bit)
{
Serial.print((relayValue >> bit) & 0x01);
Serial.write(bitRead(aByte, aBit) ? '1' : '0');
}
Serial.println();
Wire.beginTransmission(RELAY_CONTROL_ADDRESS);
Wire.write(relayValue);
uint8_t error = Wire.endTransmission();
if (error == 0)
{
lastRelayValue = relayValue;
Serial.printf(
"Relay state updated: value=0x%02X stat=0x%02X mask=0x%02X\n",
relayValue,
current_stat,
current_mask);
}
else
{
Serial.printf(
"Relay write failed: addr=0x%02X error=%u value=0x%02X\n",
RELAY_CONTROL_ADDRESS,
error,
relayValue);
}
}
void buildTrimmedWaterLevelText(char *outBuffer, size_t outBufferSize)
@@ -228,28 +123,157 @@ void buildTrimmedWaterLevelText(char *outBuffer, size_t outBufferSize)
outBuffer[len] = '\0';
}
void addWaterLevel()
void buildRightJustifiedWaterRow(char *rowBuffer, size_t rowBufferSize)
{
char text[24];
buildTrimmedWaterLevelText(text, sizeof(text)); // e.g. "123 cm"
size_t len = strlen(text);
if (len > 20)
if (rowBuffer == nullptr || rowBufferSize < (LCD_COLS + 1))
{
len = 20;
text[20] = '\0';
return;
}
const uint8_t row = 3; // last row on 20x4
const uint8_t startCol = static_cast<uint8_t>(20 - len); // right-justified
memset(rowBuffer, ' ', LCD_COLS);
rowBuffer[LCD_COLS] = '\0';
// Clear row first so old characters do not remain
// lcd.setCursor(0, row);
// lcd.print(" "); // 20 spaces
char text[24];
buildTrimmedWaterLevelText(text, sizeof(text));
// Print at exact right-justified cursor position
lcd.setCursor(startCol, row);
lcd.print(text);
size_t len = strlen(text);
if (len > LCD_COLS)
{
len = LCD_COLS;
}
const size_t startCol = LCD_COLS - len;
memcpy(rowBuffer + startCol, text, len);
}
void addWaterLevel()
{
char row[LCD_COLS + 1];
buildRightJustifiedWaterRow(row, sizeof(row));
lcd.setCursor(0, WATER_ROW);
lcd.print(row);
}
void updateLCDDisplay()
{
lcd.clear();
uint8_t c_row = 0;
for (size_t i = 0; i < static_cast<size_t>(ActionId::Count); ++i)
{
if (g_actions[i].active)
{
if (c_row >= WATER_ROW) // keep last row for water level
{
break;
}
lcd.setCursor(0, c_row);
lcd.print(g_actions[i].name);
++c_row;
}
}
addWaterLevel();
}
void generateMasksByActive()
{
current_stat = 0;
current_mask = 0;
for (size_t i = 0; i < static_cast<size_t>(ActionId::Count); ++i)
{
Action &c_action = g_actions[i];
if (c_action.active)
{
current_mask |= c_action.mask;
current_stat = (current_stat & ~c_action.mask) | (c_action.stat & c_action.mask);
}
}
}
void updateStateByLastAction(ActionId lastAction)
{
Action &c_action = getAction(lastAction);
if (c_action.active)
{
c_action.active = false;
generateMasksByActive();
applyRelayStateIfChanged();
updateLCDDisplay();
return;
}
const uint8_t change_stat = c_action.stat;
const uint8_t change_mask = c_action.mask;
const uint8_t old_stat = current_stat;
const uint8_t old_mask = current_mask;
const uint8_t potential_new_stat = (current_stat & ~change_mask) | (change_stat & change_mask);
const uint8_t potential_old_change = (potential_new_stat ^ old_stat) & old_mask;
if (potential_old_change != 0)
{
Serial.print(c_action.name);
Serial.println(" collides with existing. Setting only this action.");
current_stat = change_stat;
current_mask = change_mask;
for (size_t i = 0; i < static_cast<size_t>(ActionId::Count); ++i)
{
g_actions[i].active = false;
}
c_action.active = true;
}
else
{
Serial.print(c_action.name);
Serial.println(" has no collisions. Merging action.");
current_stat = potential_new_stat;
current_mask = old_mask | change_mask;
c_action.active = true;
}
Serial.print("Stat: ");
printBin(current_stat);
Serial.print("Mask: ");
printBin(current_mask);
applyRelayStateIfChanged();
updateLCDDisplay();
}
void applyRelayStateIfChanged()
{
static uint8_t lastRelayValue = 0xFF;
const uint8_t relayValue = static_cast<uint8_t>(~current_stat);
if (relayValue == lastRelayValue)
{
return;
}
Wire.beginTransmission(RELAY_CONTROL_ADDRESS);
Wire.write(relayValue);
uint8_t error = Wire.endTransmission();
if (error == 0)
{
lastRelayValue = relayValue;
Serial.printf(
"Relay state updated: value=0x%02X stat=0x%02X mask=0x%02X\n",
relayValue, current_stat, current_mask);
}
else
{
Serial.printf(
"Relay write failed: addr=0x%02X error=%u value=0x%02X\n",
RELAY_CONTROL_ADDRESS, error, relayValue);
}
}
void IRAM_ATTR onButtonInterrupt()
@@ -257,64 +281,67 @@ void IRAM_ATTR onButtonInterrupt()
g_buttonInterruptFired = true;
}
uint16_t calculateCRC16(uint8_t *data, uint8_t length)
uint16_t calculateCRC16(const uint8_t *data, uint8_t length)
{
uint16_t crc = 0xFFFF;
for (int i = 0; i < length; i++)
for (uint8_t i = 0; i < length; ++i)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
for (uint8_t j = 0; j < 8; ++j)
{
crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : crc >> 1;
crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : (crc >> 1);
}
}
return crc;
}
void sendCommand(uint8_t *cmd, size_t len)
void sendCommand(const uint8_t *cmd, size_t len)
{
uint16_t crc = calculateCRC16(cmd, len);
uint8_t crcL = crc & 0xFF;
uint8_t crcH = (crc >> 8) & 0xFF;
uint16_t crc = calculateCRC16(cmd, static_cast<uint8_t>(len));
uint8_t crcL = static_cast<uint8_t>(crc & 0xFF);
uint8_t crcH = static_cast<uint8_t>((crc >> 8) & 0xFF);
Serial2.write(cmd, len);
Serial2.write(crcL);
Serial2.write(crcH);
}
void sendModbusCommand() { sendCommand(modbusReadCmd, sizeof(modbusReadCmd)); }
void sendModbusCommand()
{
sendCommand(modbusReadCmd, sizeof(modbusReadCmd));
}
void scanI2CDevices()
{
uint8_t found = 0;
for (uint8_t address = 1; address < 127; address++)
for (uint8_t address = 1; address < 127; ++address)
{
Wire.beginTransmission(address);
uint8_t error = Wire.endTransmission();
if (error == 0)
{
Serial.printf("I2C device found at 0x%02X\n", address);
found++;
++found;
}
}
}
bool i2cAddressResponds(uint8_t address)
{
Wire.beginTransmission(address);
return (Wire.endTransmission() == 0);
if (found == 0)
{
Serial.println("No I2C devices found.");
}
}
void readAndPrintButtons()
{
Wire.beginTransmission(BUTTONS_CONTROL_ADDRESS);
Wire.write(0b11111110); // P0 LOW, all other pins released HIGH.
Wire.write(0b11111110); // P0 LOW, others HIGH
Wire.endTransmission();
Wire.requestFrom(BUTTONS_CONTROL_ADDRESS, (uint8_t)1);
if (Wire.available())
Wire.requestFrom(BUTTONS_CONTROL_ADDRESS, static_cast<uint8_t>(1));
if (!Wire.available())
{
return;
}
uint8_t portValue = Wire.read();
bool buttonP3Pressed = (portValue & (1 << 3)) == 0;
bool buttonP4Pressed = (portValue & (1 << 4)) == 0;
@@ -322,90 +349,106 @@ void readAndPrintButtons()
bool buttonP6Pressed = (portValue & (1 << 6)) == 0;
bool buttonP7Pressed = (portValue & (1 << 7)) == 0;
ActionId LastAction = ActionId::Count;
ActionId lastAction = ActionId::Count;
if (buttonP3Pressed)
{
LastAction = ActionId::CityWaterToHose;
lastAction = ActionId::CityWaterToHose;
}
else if (buttonP4Pressed)
{
LastAction = ActionId::BarrelToHose;
lastAction = ActionId::BarrelToHose;
}
else if (buttonP5Pressed)
{
LastAction = ActionId::BackLeftToBarrel;
lastAction = ActionId::BackLeftToBarrel;
}
else if (buttonP6Pressed)
{
LastAction = ActionId::BarrelToBackLeft;
lastAction = ActionId::BarrelToBackLeft;
}
else if (buttonP7Pressed)
{
// LastAction = ActionId::NotAssigned;
// reserved
}
Serial.printf(
"INT Buttons P3=%d P4=%d P5=%d P6=%d P7=%d (raw=0x%02X)\n",
buttonP3Pressed,
buttonP4Pressed,
buttonP5Pressed,
buttonP6Pressed,
buttonP7Pressed,
portValue);
buttonP3Pressed, buttonP4Pressed, buttonP5Pressed, buttonP6Pressed, buttonP7Pressed, portValue);
if (LastAction != ActionId::Count)
if (lastAction != ActionId::Count)
{
updateStateByLastAction(LastAction);
}
updateStateByLastAction(lastAction);
}
}
void pollSerial2WaterLevel()
{
static uint8_t responseBuffer[256];
static int responseIndex = 0;
static uint8_t responseBuffer[7];
static uint8_t responseIndex = 0;
static int lastPrintedWaterLevelCm = -1;
static unsigned long lastByteTimeMs = 0;
static unsigned long lastCommandTimeMs = 0;
sendModbusCommand();
unsigned long now = millis();
while (Serial2.available())
// Periodic request
if ((now - lastCommandTimeMs) >= COMMAND_INTERVAL_MS)
{
responseBuffer[responseIndex++] = Serial2.read();
if (responseIndex >= 256)
lastCommandTimeMs = now;
sendModbusCommand();
}
// Reset partial frame on timeout
if (responseIndex > 0 && (now - lastByteTimeMs) > MODBUS_RESPONSE_TIMEOUT_MS)
{
responseIndex = 0;
}
while (Serial2.available())
{
uint8_t b = static_cast<uint8_t>(Serial2.read());
lastByteTimeMs = millis();
if (responseIndex < sizeof(responseBuffer))
{
responseBuffer[responseIndex++] = b;
}
else
{
responseIndex = 0;
continue;
}
if (responseIndex == 7)
{
uint16_t data = ((uint16_t)responseBuffer[3] << 8) | responseBuffer[4];
uint16_t receivedCRC = ((uint16_t)responseBuffer[6] << 8) | responseBuffer[5];
uint16_t receivedCRC = static_cast<uint16_t>(responseBuffer[5]) |
(static_cast<uint16_t>(responseBuffer[6]) << 8);
uint16_t calculatedCRC = calculateCRC16(responseBuffer, 5);
if (receivedCRC == calculatedCRC)
if (responseBuffer[0] == 0x01 &&
responseBuffer[1] == 0x03 &&
responseBuffer[2] == 0x02 &&
receivedCRC == calculatedCRC)
{
int waterLevelCm = data / 10;
uint16_t dataMm = (static_cast<uint16_t>(responseBuffer[3]) << 8) | responseBuffer[4];
int waterLevelCm = static_cast<int>(dataMm / 10);
if (waterLevelCm != lastPrintedWaterLevelCm)
{
lastPrintedWaterLevelCm = waterLevelCm;
g_waterLevelCm = waterLevelCm;
addWaterLevel();
Serial.printf("Water level: %d cm (%d mm)\n", waterLevelCm, data);
Serial.printf("Water level: %d cm (%u mm)\n", waterLevelCm, dataMm);
}
}
else
{
Serial.printf("CRC mismatch: received=0x%04X calculated=0x%04X\n", receivedCRC, calculatedCRC);
Serial.println("Invalid Modbus frame or CRC mismatch.");
}
responseIndex = 0;
}
else if (responseIndex != 0)
{
Serial.printf("Unexpected response length: %d (expected 7)\n", responseIndex);
responseIndex = 0;
}
}
@@ -427,9 +470,8 @@ void setup()
scanI2CDevices();
int lcdStatus = lcd.begin(20, 4);
lcd.clear();
// updateLcdStatesIfChanged();
lcd.begin(LCD_COLS, LCD_ROWS);
updateLCDDisplay();
Serial2.begin(MODBUS_BAUD, SERIAL_8N1, RXD_PIN, TXD_PIN);
start = millis();
@@ -437,24 +479,18 @@ void setup()
{
delay(10);
}
applyRelayStateIfChanged();
}
void loop()
{
// NA, Pump, Barrel -> Back Left, Back Left -> Barrel, Barrel to Hose, CW to Hose, NA, NA
static unsigned long lastSerial2PollTime = 0;
if (g_buttonInterruptFired)
{
g_buttonInterruptFired = false;
readAndPrintButtons();
}
if (millis() - lastSerial2PollTime >= COMMAND_INTERVAL)
{
lastSerial2PollTime = millis();
pollSerial2WaterLevel();
}
// updateLcdStatesIfChanged();
delay(10);
}