#include <Wire.h>
#include <EEPROM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Делитель
#define VOLTAGE_PIN A0
const float R1 = 100000.0;
const float R2 = 20000.0;
const float DIVIDER_RATIO = (R1 + R2) / R2;
// PWM
#define PWM_PIN 9
uint8_t pwmPercent = 0;
// Реле
#define RELAY_PIN 6
// ⚠️ АКТИВНАЯ ЛОГИКА (Active HIGH):
// HIGH = Включить реле (ACTIVE)
// LOW = Выключить реле (INACTIVE)
// Энкодер
#define ENC_A 2
#define ENC_B 3
#define ENC_BTN 4
int lastEncoded = 0;
long encoderValue = 0;
int lastEncoderValue = 0;
// EEPROM
#define EEPROM_ADDR 0
struct Calibration {
float factor = 1.0;
float vref = 5.00;
};
Calibration calib;
bool inCalibration = false;
uint32_t calibStartTime = 0;
void updateEncoder() {
int MSB = digitalRead(ENC_A);
int LSB = digitalRead(ENC_B);
int encoded = (MSB << 1) | LSB;
int sum = (lastEncoded << 2) | encoded;
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue++;
if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue--;
lastEncoded = encoded;
}
void saveCalibration() {
EEPROM.put(EEPROM_ADDR, calib);
}
void loadCalibration() {
EEPROM.get(EEPROM_ADDR, calib);
if (isnan(calib.factor) || calib.factor < 0.5 || calib.factor > 2.0) calib.factor = 1.0;
if (isnan(calib.vref) || calib.vref < 4.5 || calib.vref > 5.5) calib.vref = 5.00;
}
float readVoltage() {
int raw = analogRead(VOLTAGE_PIN);
float voltage = (raw * calib.vref / 1023.0) * DIVIDER_RATIO * calib.factor;
return voltage;
}
// ✅ Правильная автокалибровка
void autoCalibrateByVoltage(float realVoltage) {
delay(50); // Стабилизация
int raw = analogRead(VOLTAGE_PIN);
float measured = (raw * calib.vref / 1023.0) * DIVIDER_RATIO;
if (measured > 0.1 && realVoltage > 0.1) {
calib.factor = constrain(realVoltage / measured, 0.5, 2.0);
saveCalibration();
Serial.print("Calibration done. New factor: ");
Serial.println(calib.factor, 4);
} else {
Serial.println("Calibration failed.");
}
inCalibration = false;
}
void setup() {
Serial.begin(9600);
pinMode(PWM_PIN, OUTPUT);
analogWrite(PWM_PIN, 0);
// Инициализация пина реле
// 1. Принудительное выключение (LOW)
digitalWrite(RELAY_PIN, LOW);
pinMode(RELAY_PIN, OUTPUT);
// 💡 Эмуляционное исправление: кратковременный импульс HIGH/LOW
// для гарантированного "сброса" эмулируемого реле.
digitalWrite(RELAY_PIN, HIGH);
delay(10); // Очень короткая задержка для эмуляции переключения
digitalWrite(RELAY_PIN, LOW); // Финальное состояние: ВЫКЛЮЧЕНО
pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
pinMode(ENC_BTN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENC_A), updateEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENC_B), updateEncoder, CHANGE);
loadCalibration();
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("Display init error"));
while (1);
}
display.clearDisplay();
display.display();
Serial.println(F("Hold encoder button for 2s to calibrate..."));
Serial.println(F("RELAY (Active HIGH): HIGH at PWM >= 20%, LOW at PWM < 20%"));
}
void loop() {
static int lastBtnState = HIGH;
static uint32_t btnPressTime = 0;
static uint32_t lastUpdate = 0;
static uint8_t lastPwmPercent = 255;
static bool relayState = false; // false = OFF
int btnState = digitalRead(ENC_BTN);
// Удержание кнопки > 2 сек => автокалибровка
if (btnState == LOW && lastBtnState == HIGH) {
btnPressTime = millis();
}
if (btnState == LOW && !inCalibration && (millis() - btnPressTime > 2000)) {
inCalibration = true;
calibStartTime = millis();
autoCalibrateByVoltage(29.40); // Укажи реальное напряжение АКБ при калибровке!
}
if (btnState == HIGH && lastBtnState == LOW && inCalibration) {
inCalibration = false;
}
lastBtnState = btnState;
float voltage = readVoltage();
if (encoderValue != lastEncoderValue) {
int delta = encoderValue - lastEncoderValue;
lastEncoderValue = encoderValue;
pwmPercent = constrain(pwmPercent + delta, 0, 100);
analogWrite(PWM_PIN, map(pwmPercent, 0, 100, 0, 255));
}
// Управление реле - ВКЛЮЧАЕМ при PWM >= 20% (логика Active HIGH)
if (pwmPercent != lastPwmPercent) {
bool newRelayState = (pwmPercent >= 20); // true, если нужно ВКЛЮЧИТЬ (Active)
if (newRelayState != relayState) {
// ⚠️ АКТИВНАЯ ЛОГИКА:
// HIGH для ON (Active)
// LOW для OFF (Inactive)
digitalWrite(RELAY_PIN, newRelayState ? HIGH : LOW);
relayState = newRelayState;
Serial.print("PWM: ");
Serial.print(pwmPercent);
Serial.print("% | RELAY: ");
Serial.println(relayState ? "ON (HIGH)" : "OFF (LOW)");
}
lastPwmPercent = pwmPercent;
}
if (millis() - lastUpdate > 1000) {
lastUpdate = millis();
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(32, 0);
display.print("Bat: ");
display.print(voltage, 2);
display.println(" V");
// 🔋 Индикатор батареи
const int x = 14, y = 15, w = 100, h = 10, r = 4;
display.drawRoundRect(x, y, w, h, r, SSD1306_WHITE);
display.fillRect(x + w + 1, y + h / 3, 3, h / 3, SSD1306_WHITE);
float fillRatio = constrain((voltage - 20.5) / (29.4 - 20.5), 0.0, 1.0);
int fillWidth = fillRatio * w;
if (fillWidth > 0) {
display.fillRoundRect(x, y, fillWidth, h, (fillWidth < 2 * r ? fillWidth / 2 : r), SSD1306_WHITE);
}
// 📶 PWM-индикатор
display.setCursor(40, 30);
display.print("PWM: ");
display.print(pwmPercent);
display.println(" %");
// 🔌 Индикатор реле
display.setCursor(40, 55);
display.print("RELAY: ");
display.print(relayState ? "ON " : "OFF");
display.drawRoundRect(x, 45, w, h, r, SSD1306_WHITE);
int pwmWidth = pwmPercent * w / 100;
if (pwmWidth > 0) {
display.fillRoundRect(x, 45, pwmWidth, h, (pwmWidth < 2 * r ? pwmWidth / 2 : r), SSD1306_WHITE);
}
display.display();
}
}