# Phase 1: Project Setup, Hardware Abstraction & RS485 Modbus Communication ## Objective Establish the PlatformIO project structure, configure the LILYGO TTGO T-Call v1.4 board, build the hardware abstraction layer, and implement RS485 Modbus RTU communication with the Acuvim II power meter. ## Prerequisites - LILYGO TTGO T-Call v1.4 board - Acuvim II power meter with RS485 interface - RS485-to-TTL transceiver module (e.g., MAX485 or SP3485) - Acuvim II Modbus register map documentation - USB-C cable for programming - PlatformIO IDE (VS Code extension) ## Deliverables 1. PlatformIO project with correct board and library configuration 2. Pin mapping header for TTGO T-Call v1.4 3. RS485 Modbus RTU driver for Acuvim II 4. Acuvim II register definitions and data structures 5. Polling task that reads all inverter parameters 6. Serial debug output confirming successful reads --- ## 1.1 PlatformIO Project Structure ``` Tau.Acuvim/ ├── firmware/ │ ├── platformio.ini │ ├── src/ │ │ └── main.cpp │ ├── include/ │ │ ├── pin_config.h │ │ ├── acuvim_registers.h │ │ ├── acuvim_reader.h │ │ └── config_manager.h │ ├── lib/ │ │ └── README.md │ ├── data/ # LittleFS web files (Phase 3) │ └── test/ ├── console/ # Console app (Phase 8-9) ├── docs/ │ └── acuvim-spec-xx.md └── README.md ``` ## 1.2 PlatformIO Configuration ### `platformio.ini` ```ini [env:ttgo-t-call] platform = espressif32 board = esp-wrover-kit framework = arduino monitor_speed = 115200 upload_speed = 921600 board_build.partitions = min_spiffs.csv board_build.f_cpu = 240000000L board_build.f_flash = 80000000L board_build.flash_mode = qio lib_deps = 4-20ma/ModbusMaster@^2.0.1 bblanchon/ArduinoJson@^7.0.0 build_flags = -DBOARD_HAS_PSRAM -DCORE_DEBUG_LEVEL=3 ``` **Notes:** - `esp-wrover-kit` is the closest PlatformIO board definition for the ESP32-WROVER-B module - Partition table will be changed in Phase 6 (OTA) to support two app partitions - Additional libraries will be added in subsequent phases ## 1.3 Pin Configuration ### `pin_config.h` Define all hardware pin assignments for the TTGO T-Call v1.4 in a single header. This is the hardware abstraction layer that will be updated if the board changes. ``` TTGO T-Call v1.4 Pin Assignments ================================ Modem (SIM800L) Pins: MODEM_RST = GPIO 5 MODEM_PWRKEY = GPIO 4 MODEM_POWER_ON = GPIO 23 MODEM_TX = GPIO 27 MODEM_RX = GPIO 26 LED: LED_PIN = GPIO 13 I2C (IP5306 Power Management): I2C_SDA = GPIO 21 I2C_SCL = GPIO 22 RS485 (Acuvim II) - User-assigned: RS485_RX = GPIO 32 RS485_TX = GPIO 33 RS485_DE_RE = GPIO 25 # Direction control (DE+RE tied together) SD Card (SPI) - User-assigned (Phase 5): SD_CS = GPIO 15 SD_MOSI = GPIO 2 SD_MISO = GPIO 14 SD_CLK = GPIO 12 Available GPIOs (unused): GPIO 0 (BOOT - avoid for general use) GPIO 34 (input only) GPIO 35 (input only) GPIO 36 (input only, VP) GPIO 39 (input only, VN) ``` **Important constraints:** - GPIO 6-11 are used by internal flash (do not use) - GPIO 34-39 are input-only (no pull-up/pull-down) - GPIO 2 must be LOW during boot (SD MOSI — acceptable since SPI is idle at boot) - GPIO 12 (MTDI) controls flash voltage on WROVER — if using SD on GPIO 12, set efuse or use `board_build.flash_mode = qio` to avoid boot issues **RS485 wiring to MAX485/SP3485:** ``` ESP32 GPIO 33 (TX) ───→ DI (Driver Input) ESP32 GPIO 32 (RX) ←─── RO (Receiver Output) ESP32 GPIO 25 (DE) ───→ DE + RE (tied together) MAX485 A ────────────→ Acuvim II RS485 A (+ / Data+) MAX485 B ────────────→ Acuvim II RS485 B (- / Data-) GND ─────────────────→ Acuvim II GND (common ground) ``` ## 1.4 Acuvim II Modbus Register Map The Acuvim II uses Modbus RTU over RS485 with the following defaults: - Baud rate: 9600 (configurable: 1200–19200) - Data bits: 8 - Parity: None - Stop bits: 1 - Slave address: 1 (configurable: 1–247) ### Key Register Groups Define these in `acuvim_registers.h`: ``` Register Address | Length | Parameter | Unit | Data Type | Scale ------------------|--------|------------------------|---------|--------------|------- 0x0000 | 2 | Phase A Voltage (Ua) | V | Float32 | - 0x0002 | 2 | Phase B Voltage (Ub) | V | Float32 | - 0x0004 | 2 | Phase C Voltage (Uc) | V | Float32 | - 0x0006 | 2 | Phase A Current (Ia) | A | Float32 | - 0x0008 | 2 | Phase B Current (Ib) | A | Float32 | - 0x000A | 2 | Phase C Current (Ic) | A | Float32 | - 0x000C | 2 | Active Power (P) | kW | Float32 | - 0x000E | 2 | Reactive Power (Q) | kVAR | Float32 | - 0x0010 | 2 | Apparent Power (S) | kVA | Float32 | - 0x0012 | 2 | Power Factor (PF) | - | Float32 | - 0x0014 | 2 | Frequency (F) | Hz | Float32 | - 0x0016 | 2 | Phase A Power (Pa) | kW | Float32 | - 0x0018 | 2 | Phase B Power (Pb) | kW | Float32 | - 0x001A | 2 | Phase C Power (Pc) | kW | Float32 | - 0x001C | 2 | Line Voltage AB (Uab) | V | Float32 | - 0x001E | 2 | Line Voltage BC (Ubc) | V | Float32 | - 0x0020 | 2 | Line Voltage CA (Uca) | V | Float32 | - Energy Registers (Accumulated): 0x0100 | 2 | Import Active Energy | kWh | Float32 | - 0x0102 | 2 | Export Active Energy | kWh | Float32 | - 0x0104 | 2 | Import Reactive Energy | kVARh | Float32 | - 0x0106 | 2 | Export Reactive Energy | kVARh | Float32 | - Demand Registers: 0x0200 | 2 | Active Power Demand | kW | Float32 | - 0x0202 | 2 | Max Active Demand | kW | Float32 | - 0x0204 | 2 | Reactive Power Demand | kVAR | Float32 | - THD Registers: 0x0300 | 2 | Voltage THD A | % | Float32 | - 0x0302 | 2 | Voltage THD B | % | Float32 | - 0x0304 | 2 | Voltage THD C | % | Float32 | - 0x0306 | 2 | Current THD A | % | Float32 | - 0x0308 | 2 | Current THD B | % | Float32 | - 0x030A | 2 | Current THD C | % | Float32 | - ``` **Note:** Register addresses and data types must be verified against the actual Acuvim II documentation for the specific model (Acuvim II, IIR, IIE, IIW). The above is representative — the actual register map from Accuenergy should be used as the source of truth. ### Data Structure ```cpp struct AcuvimData { // Phase Voltages float voltage_a; // V float voltage_b; // V float voltage_c; // V // Phase Currents float current_a; // A float current_b; // A float current_c; // A // Power float active_power; // kW (total) float reactive_power; // kVAR (total) float apparent_power; // kVA (total) float power_factor; // -1.0 to 1.0 // Frequency float frequency; // Hz // Per-Phase Power float power_a; // kW float power_b; // kW float power_c; // kW // Line Voltages float voltage_ab; // V float voltage_bc; // V float voltage_ca; // V // Energy (Accumulated) float import_active_energy; // kWh float export_active_energy; // kWh float import_reactive_energy; // kVARh float export_reactive_energy; // kVARh // Demand float active_demand; // kW float max_active_demand; // kW float reactive_demand; // kVAR // THD float thd_voltage_a; // % float thd_voltage_b; // % float thd_voltage_c; // % float thd_current_a; // % float thd_current_b; // % float thd_current_c; // % // Metadata uint32_t timestamp; // epoch seconds bool valid; // true if read succeeded uint8_t error_code; // 0 = success, else Modbus exception }; ``` ## 1.5 Modbus RTU Driver Implementation ### `acuvim_reader.h` / `acuvim_reader.cpp` **Class: `AcuvimReader`** Responsibilities: - Initialize HardwareSerial (Serial2) for RS485 at configured baud rate - Control RS485 direction pin (DE/RE) for half-duplex operation - Read register blocks using ModbusMaster library - Parse Float32 values from Modbus register pairs (big-endian IEEE 754) - Populate `AcuvimData` struct - Handle communication errors with retry logic - Report read success/failure statistics **Key methods:** ```cpp class AcuvimReader { public: bool begin(uint8_t slaveAddress = 1, uint32_t baudRate = 9600); bool readAll(AcuvimData& data); bool readVoltages(AcuvimData& data); bool readCurrents(AcuvimData& data); bool readPower(AcuvimData& data); bool readEnergy(AcuvimData& data); bool readDemand(AcuvimData& data); bool readTHD(AcuvimData& data); uint32_t getSuccessCount() const; uint32_t getErrorCount() const; uint8_t getLastError() const; private: ModbusMaster modbus; uint8_t slaveAddr; float readFloat32(uint16_t reg); bool readRegisterBlock(uint16_t startReg, uint16_t count, uint16_t* buffer); static void preTransmission(); static void postTransmission(); }; ``` **RS485 direction control callbacks:** ```cpp // Called before Modbus transmission — set DE/RE HIGH to enable transmit void AcuvimReader::preTransmission() { digitalWrite(RS485_DE_RE, HIGH); } // Called after Modbus transmission — set DE/RE LOW to enable receive void AcuvimReader::postTransmission() { digitalWrite(RS485_DE_RE, LOW); } ``` **Float32 parsing from Modbus registers:** The Acuvim II stores 32-bit floats across two consecutive 16-bit registers (big-endian). The ModbusMaster library returns registers in order, so: ```cpp float AcuvimReader::readFloat32(uint16_t reg) { uint32_t raw = ((uint32_t)modbus.getResponseBuffer(reg) << 16) | (uint32_t)modbus.getResponseBuffer(reg + 1); float value; memcpy(&value, &raw, sizeof(float)); return value; } ``` ### Error Handling - Max 3 retries per register block read with 50ms delay between retries - On persistent failure, mark `AcuvimData.valid = false` and set `error_code` - Track cumulative success/error counts for health reporting (Phase 7) - Log errors to Serial for debugging ## 1.6 Main Application (Phase 1 Scope) ### `main.cpp` Phase 1 main loop is minimal — it initializes the RS485 interface, polls the Acuvim II at a configurable interval (default: 5 seconds), and prints results to Serial for verification. ```cpp // Pseudocode for Phase 1 main.cpp void setup() { Serial.begin(115200); acuvimReader.begin(SLAVE_ADDRESS, BAUD_RATE); } void loop() { AcuvimData data; if (acuvimReader.readAll(data)) { printAcuvimData(data); // Serial debug output } else { Serial.printf("Read failed, error: %d\n", data.error_code); } delay(pollInterval); } ``` ## 1.7 Testing & Validation | Test | Method | Pass Criteria | |------|--------|---------------| | Build succeeds | `pio run` | Zero errors, zero warnings | | Upload to board | `pio run -t upload` | Firmware flashes successfully | | Serial output | `pio device monitor` | Prints structured data at poll interval | | Modbus read | Connect to Acuvim II | Valid voltage/current/power readings | | Error handling | Disconnect RS485 | Error reported, no crash, recovery on reconnect | | Baud rate mismatch | Set wrong baud | Error reported, no hang | | Slave address mismatch | Set wrong address | Timeout reported, no hang | | Continuous operation | Run for 1 hour | No memory leaks, stable read rate | ## 1.8 Dependencies | Library | Version | Purpose | |---------|---------|---------| | ModbusMaster | ^2.0.1 | Modbus RTU master protocol | | ArduinoJson | ^7.0.0 | JSON serialization (used from Phase 2 onward) | ## 1.9 Phase 1 Completion Criteria - [ ] PlatformIO project builds and uploads to TTGO T-Call v1.4 - [ ] RS485 communication established with Acuvim II - [ ] All register groups read successfully (voltages, currents, power, energy, demand, THD) - [ ] Float32 values parsed correctly from Modbus register pairs - [ ] Error handling tested (disconnection, timeout, wrong address) - [ ] Data printed to Serial in human-readable format - [ ] Pin configuration documented and verified against hardware --- **Next Phase:** [Phase 2 — WiFi, MQTT & NVS Configuration](acuvim-spec-02.md)