//1. Header and Includes /* Voltmetru/Ampermetru de precizie cu ADS1115(voltaj) și INA3221(curent) ESP8266 NodeMCU D1 mini v4.0 Afișare pe LCD 2004 și prin Bluetooth HC-05 Comutare între moduri cu switch pe GPIO14 (D5) */ #include // Biblioteca pentru comunicarea I2C #include // Biblioteca pentru ADS1115 #include // Biblioteca pentru LCD I2C #include // Biblioteca pentru INA3221 //2. Hardware Configuration // Configurare pini I2C #define SDA_PIN 4 // Pinul SDA (GPIO4/D2) #define SCL_PIN 5 // Pinul SCL (GPIO5/D1) // Pin pentru comutare mod afișare #define MODE_SWITCH_PIN 14 // GPIO14 (D5) // Adrese dispozitive #define LCD_ADDRESS 0x3F // Adresa I2C a LCD-ului #define ADS_ADDRESS 0x48 // Adresa I2C a ADS1115 #define INA_ADDRESS 0x40 // Adresa I2C a INA3221 //3. Constants and Settings // Constante #define BUFFER_SIZE 20 // Dimensiunea buffer-ului pentru mediere #define UV_PER_BIT 78125L // 7.8125 µV/bit (factor de conversie) #define MEASURE_INTERVAL 50 // Interval între măsurători (µs) #define DISPLAY_INTERVAL 333 // Interval între actualizări LCD (ms) #define BT_INTERVAL 1000 // Interval între trimiteri Bluetooth (ms) #define DEBOUNCE_DELAY 50 // Interval pentru debouncing (ms) //4. Object Declarations // Obiecte Adafruit_ADS1115 ads; // Obiect pentru ADS1115 Adafruit_INA3221 ina3221; // Obiect pentru INA3221 LiquidCrystal_I2C lcd(LCD_ADDRESS, 20, 4); // Obiect LCD 20x4 //5. Data Structures // Structuri de date pentru stocarea măsurătorilor struct { int32_t a0[BUFFER_SIZE]; // Buffer măsurători diferențiale 0-1 // int32_t a1[BUFFER_SIZE]; // Buffer măsurători diferențiale 1-0 (inverse) // int32_t a2[BUFFER_SIZE]; // Buffer măsurători single-ended A2 // int32_t a3[BUFFER_SIZE]; // Buffer măsurători single-ended A3 float current1[BUFFER_SIZE]; // Buffer curent canal 1 INA3221 (mA) float current2[BUFFER_SIZE]; // Buffer curent canal 2 INA3221 (mA) float current3[BUFFER_SIZE]; // Buffer curent canal 3 INA3221 (mA) uint8_t index = 0; // Index curent în buffer uint8_t count = 0; // Număr de măsurători în buffer } measurements; // Structură pentru stocarea valorilor medii struct { int32_t a0; // Valoare medie diferențială 0-1 // int32_t a1; // Valoare medie diferențială 1-0 // int32_t a2; // Valoare medie A2 // int32_t a3; // Valoare medie A3 float current1; // Valoare medie curent canal 1 (mA) float current2; // Valoare medie curent canal 2 (mA) float current3; // Valoare medie curent canal 3 (mA) } averages; uint8_t currentBTChannel = 0; // Canalul curent pentru transmisie Bluetooth unsigned long lastBTSent = 0; // Timpul ultimei transmisii Bluetooth bool voltmeterMode = true; // Mod implicit: voltmetru (true=voltmeter, false=ammeter) // Variabile pentru debouncing bool lastButtonState = HIGH; // Starea anterioară a butonului bool buttonState; // Starea curentă a butonului unsigned long lastDebounceTime = 0; // Timpul ultimei schimbări de stare //6. Setup Function void setup() { // Inițializare port serial pentru Bluetooth Serial.begin(9600); // Pornire serial la 9600 baud Serial.swap(); // Folosește GPIO13 (D7) și GPIO15 (D8) pentru serial // Inițializare pin comutare mod pinMode(MODE_SWITCH_PIN, INPUT_PULLUP); // Switch între GND și pin // Inițializare I2C Wire.begin(SDA_PIN, SCL_PIN); // Pornire comunicație I2C pe pinii definiți // Configurare LCD lcd.init(); // Inițializare LCD lcd.backlight(); // Pornire backlight lcd.setCursor(0, 0); lcd.print("Voltmetru ADS1115"); // Afișare titlu lcd.setCursor(0, 1); lcd.print("ESP8266 LCD2004 HC05"); lcd.setCursor(0, 2); lcd.print("Gama: +-256mV"); lcd.setCursor(0, 3); lcd.print("Rezol: 7.8125uV"); delay(2000); // Pauză pentru afișare mesaj inițial lcd.clear(); // Ștergere ecran // Inițializare ADS1115 if (!ads.begin(ADS_ADDRESS, &Wire)) { // Verificare inițializare ADS1115 lcd.print("Eroare ADS1115!"); // Mesaj de eroare dacă nu funcționează while(1); // Blocare(loop) infinită } // Configurare ADS1115 ads.setGain(GAIN_SIXTEEN); // Setare amplificare x16 (±256mV) ads.setDataRate(RATE_ADS1115_860SPS); // Setare rată de eșantionare 860SPS // Inițializare INA3221 if (!ina3221.begin()) { // Verificare inițializare INA3221 lcd.print("Eroare INA3221!"); // Mesaj de eroare dacă nu funcționează while(1); // Blocare(loop) infinită } Serial.println("HC-05 Ready"); // Mesaj de pornire Bluetooth } //7. Main Loop void loop() { static unsigned long lastMeasure = micros(); // Timp ultimă măsurătoare static unsigned long lastDisplay = millis(); // Timp ultimă afișare LCD // Citire starea butonului cu debouncing bool reading = digitalRead(MODE_SWITCH_PIN); // Dacă starea butonului s-a schimbat (din cauza zgomotului sau apăsării) if (reading != lastButtonState) { lastDebounceTime = millis(); // Resetare timer debouncing } // Dacă a trecut suficient timp de la ultima schimbare de stare if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) { // Dacă starea butonului s-a schimbat if (reading != buttonState) { buttonState = reading; // Dacă butonul a fost apăsat (tranziție HIGH->LOW) if (buttonState == LOW) { voltmeterMode = !voltmeterMode; // Comută între moduri lcd.clear(); // Curăță ecranul pentru noua afișare } } } lastButtonState = reading; // Salvează ultima stare a butonului // Citire periodică a senzorului if (micros() - lastMeasure >= MEASURE_INTERVAL) { lastMeasure = micros(); takeMeasurement(); // Efectuează o nouă măsurătoare } // Actualizare afișaj LCD if (millis() - lastDisplay >= DISPLAY_INTERVAL) { lastDisplay = millis(); calculateAverages(); // Calculează mediile if (voltmeterMode) { displayVoltmeter(); // Afișare mod voltmetru } else { displayAmmeter(); // Afișare mod ampermetru } } // Transmisie Bluetooth pe rând pe canale if (millis() - lastBTSent >= BT_INTERVAL) { lastBTSent = millis(); sendNextBTChannel(); // Trimite următoarea valoare pe Bluetooth } } //8. Measurement Functions void takeMeasurement() { // Citire valori de la ADS1115 și stocare în buffer measurements.a0[measurements.index] = ads.readADC_SingleEnded(0) * UV_PER_BIT; // measurements.a1[measurements.index] = ads.readADC_SingleEnded(1) * UV_PER_BIT; // measurements.a2[measurements.index] = ads.readADC_SingleEnded(2) * UV_PER_BIT; // measurements.a3[measurements.index] = ads.readADC_SingleEnded(3) * UV_PER_BIT; // Citire curenti de la INA3221 (în mA) measurements.current1[measurements.index] = ina3221.getCurrentAmps(0) * 490.16; // Convertire din Amperi în miliamperi measurements.current2[measurements.index] = ina3221.getCurrentAmps(1) * 114.94; measurements.current3[measurements.index] = ina3221.getCurrentAmps(2) * 492.85; // Actualizare index buffer circular measurements.index = (measurements.index + 1) % BUFFER_SIZE; if (measurements.count < BUFFER_SIZE) measurements.count++; } void calculateAverages() { int64_t sum_a0 = 0;//, sum_a1 = 0, sum_a2 = 0, sum_a3 = 0; float sum_current1 = 0, sum_current2 = 0, sum_current3 = 0; // Calcul sume pentru fiecare canal for (uint8_t i = 0; i < measurements.count; i++) { sum_a0 += measurements.a0[i]; // sum_a1 += measurements.a1[i]; // sum_a2 += measurements.a2[i]; // sum_a3 += measurements.a3[i]; sum_current1 += measurements.current1[i]; sum_current2 += measurements.current2[i]; sum_current3 += measurements.current3[i]; } // Calcul medii averages.a0 = sum_a0 / measurements.count; // averages.a1 = sum_a1 / measurements.count; // averages.a2 = sum_a2 / measurements.count; // averages.a3 = sum_a3 / measurements.count; averages.current1 = sum_current1 / measurements.count; averages.current2 = sum_current2 / measurements.count; averages.current3 = sum_current3 / measurements.count; } //9. Display Functions void displayVoltmeter() { lcd.setCursor(0, 0); displayVoltage(0, 0, "A0 ", averages.a0); // displayVoltage(0, 1, "A1 ", averages.a1); // displayVoltage(0, 2, "A2 ", averages.a2); // displayVoltage(0, 3, "A3 ", averages.a3); } void displayAmmeter() { lcd.setCursor(0, 0);lcd.print("Ampermetru CHx = GND"); displayCurrent(0, 1, "i1: ", averages.current1); displayCurrent(0, 2, "i2: ", averages.current2); displayCurrent(0, 3, "i3: ", averages.current3); } void displayVoltage(uint8_t col, uint8_t row, const char* label, int32_t value) { char buffer[20]; char unit[3] = "uV"; double voltage = value / 10000.0; if (abs(voltage) >= 1000000) { voltage /= 1000000.0; strcpy(unit, "V "); } else if (abs(voltage) >= 1000) { voltage /= 1000.0; strcpy(unit, "mV"); } snprintf(buffer, sizeof(buffer), "%s%+7.4f", label, voltage); lcd.setCursor(col, row); lcd.print(buffer); lcd.setCursor(18, row); lcd.print(unit); } void displayCurrent(uint8_t col, uint8_t row, const char* label, float value) { char buffer[20]; char unit[3] = "mA"; snprintf(buffer, sizeof(buffer), "%s%+7.3f", label, value); lcd.setCursor(col, row); lcd.print(buffer); lcd.setCursor(18, row); lcd.print(unit); } //10. Bluetooth Communication void sendNextBTChannel() { if (Serial.availableForWrite()) { // Trimite toate datele indiferent de mod Serial.println("Tensiuni"); if (abs(averages.a0) >= 1000000) { Serial.print("A0: "); Serial.print(averages.a0 / 10000000.0, 4); Serial.println(" V"); } else if (abs(averages.a0) >= 1000) { Serial.print("A0: "); Serial.print(averages.a0 / 10000.0, 4); Serial.println(" mV"); } else { Serial.print("A0: "); Serial.print(averages.a0 / 10.0, 1); Serial.println(" μV"); } /* if (abs(averages.a1) >= 1000000) { Serial.print("A1: "); Serial.print(averages.a1 / 10000000.0, 4); Serial.println(" V"); } else if (abs(averages.a1) >= 1000) { Serial.print("A1: "); Serial.print(averages.a1 / 10000.0, 4); Serial.println(" mV"); } else { Serial.print("A1: "); Serial.print(averages.a1 / 10.0, 1); Serial.println(" μV"); } if (abs(averages.a2) >= 1000000) { Serial.print("A2: "); Serial.print(averages.a2 / 10000000.0, 4); Serial.println(" V"); } else if (abs(averages.a2) >= 1000) { Serial.print("A2: "); Serial.print(averages.a2 / 10000.0, 4); Serial.println(" mV"); } else { Serial.print("A2: "); Serial.print(averages.a2 / 10.0, 1); Serial.println(" μV"); } if (abs(averages.a3) >= 1000000) { Serial.print("A3: "); Serial.print(averages.a3 / 10000000.0, 4); Serial.println(" V"); } else if (abs(averages.a3) >= 1000) { Serial.print("A3: "); Serial.print(averages.a3 / 10000.0, 4); Serial.println(" mV"); } else { Serial.print("A3: "); Serial.print(averages.a3 / 10.0, 1); Serial.println(" μV"); } */ // Curenți Serial.println("Curenti"); Serial.print("i1: "); Serial.print(averages.current1, 3); Serial.println(" mA"); Serial.print("i2: "); Serial.print(averages.current2, 3); Serial.println(" mA"); Serial.print("i3: "); Serial.print(averages.current3, 3); Serial.println(" mA"); Serial.println(" "); } }