/*
Программа для управления лабораторным блоком питания 0-25В 0-2.5А
с защитой от перескока значений через 0
*/
#define KALL_U 214 // Коэффициент калибровки напряжения (ШИМ)
#define KALL_I 1325 // Коэффициент калибровки тока (ШИМ)
#define KALL_I_IZ 0.820 // Коэффициент калибровки измерения тока
#define KALL_U_IZ 1.090 // Коэффициент калибровки измерения напряжения
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <EncButton.h>
// Инициализация энкодера с кнопкой (CLK=5, DT=6, SW=7)
EncButton enc(5, 6, 7);
// Инициализация LCD (адрес 0x27, 16 символов, 2 строки)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Глобальные переменные
int16_t u = 0, i = 0; // Установленные значения (u: 0-2500=0.0-25.0V, i: 0-2500=0.00-2.50A)
float u_iz, i_iz; // Измеренные значения
unsigned long settingsTimer; // Таймер автосохранения
uint32_t i_sum = 0, u_sum = 0;// Сумматоры для усреднения
bool iu = 0, displayUpdate = 0, saveFlag = 0;
bool power = 0; // Состояние выхода
uint16_t n = 0, m = 0; // Счетчики усреднения
int i_dig, u_dig; // Сырые значения АЦП
// Для частичного обновления дисплея
char last_u_iz_str[6] = "";
char last_i_iz_str[6] = "";
char last_u_set_str[6] = "";
char last_i_set_str[6] = "";
char last_state_str[4] = "";
bool last_iu = false;
bool last_power = false;
// Для ускоренного вращения энкодера
unsigned long lastTurnTime = 0;
uint8_t acceleration = 1; // Текущий множитель скорости
void updateDisplay();
// ================================= НАСТРОЙКА УСТРОЙСТВА =================================
void setup() {
delay(200);
Wire.begin();
Serial.begin(9600);
lcd.init();
lcd.backlight();
// Настройка энкодера
enc.setHoldTimeout(800); // Длительное нажатие 800 мс
// Инициализация EEPROM
if (EEPROM.read(100) != 0) {
for (int addr = 0; addr < 101; addr++) EEPROM.update(addr, 0);
}
// Загрузка с повышенной точностью и защитой от перескока
u = constrain(EEPROM.read(0) * 10, 0, 2500); // 0-2500 = 0.0-25.0V
i = constrain(EEPROM.read(1) * 100, 0, 2500); // 0-2500 = 0.00-2.50A
// Настройка АЦП
analogReference(DEFAULT);
pinMode(A0, INPUT);
pinMode(A1, INPUT);
// Настройка ШИМ (Timer1, два канала)
cli();
DDRB |= (1 << PB1) | (1 << PB2); // D9 и D10
TCCR1A = 0;
TCCR1B = 0;
TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
ICR1 = 10000;
OCR1A = 0; // Напряжение
OCR1B = 0; // Ток
sei();
// Приветствие
lcd.setCursor(2, 0);
lcd.print("POWER SUPPLY");
lcd.setCursor(2, 1);
lcd.print("0-25V 0-2.5A");
delay(2000);
lcd.clear();
// Применение настроек
OCR1A = KALL_U * u / 100; // Напряжение
OCR1B = KALL_I * i / 10000.0; // Ток
settingsTimer = millis();
displayUpdate = true;
}
// ================================= ОСНОВНОЙ ЦИКЛ =================================
void loop() {
static uint32_t btn_timer = 0;
enc.tick();
// Обработка кнопки энкодера (с антидребезгом)
if (enc.click() && millis() - btn_timer > 200) {
iu = !iu;
btn_timer = millis();
settingsTimer = millis();
displayUpdate = true;
saveFlag = true;
}
// Длительное нажатие - вкл/выкл выхода
if (enc.hold() && millis() - btn_timer > 200) {
power = !power;
btn_timer = millis();
if (power) {
OCR1A = 0; // Сброс напряжения
OCR1B = 0; // Сброс тока
} else {
OCR1A = KALL_U * u / 100;
OCR1B = KALL_I * i / 10000.0;
}
settingsTimer = millis();
displayUpdate = true;
saveFlag = true;
}
// Обработка вращения энкодера с ускорением
if (enc.turn()) {
// Определение ускорения
unsigned long currentTime = millis();
if (currentTime - lastTurnTime < 100) {
acceleration = 5; // Быстрое вращение
} else {
acceleration = 1; // Обычное вращение
}
lastTurnTime = currentTime;
int8_t direction = enc.dir();
// Защита от перескока через 0 и максимум
if (!iu) {
// Регулировка напряжения
int16_t newU = u + direction * acceleration;
// Защита от перехода через границы
if (newU < 0) newU = 0;
else if (newU > 2500) newU = 2500;
u = newU;
if (!power) OCR1A = KALL_U * u / 100;
} else {
// Регулировка тока
int16_t newI = i + direction * acceleration;
// Защита от перехода через границы
if (newI < 0) newI = 0;
else if (newI > 2500) newI = 2500;
i = newI;
if (!power) OCR1B = KALL_I * i / 10000.0;
}
settingsTimer = millis();
displayUpdate = true;
saveFlag = true;
}
// Измерение тока (усреднение 100 значений)
i_dig = analogRead(A0);
i_sum += i_dig;
n++;
if (n >= 100) {
i_iz = i_sum / 100.0;
i_sum = 0;
n = 0;
displayUpdate = true;
}
// Измерение напряжения (усреднение 100 значений)
u_dig = analogRead(A1);
u_sum += u_dig;
m++;
if (m >= 100) {
u_iz = u_sum / 100.0;
u_sum = 0;
m = 0;
displayUpdate = true;
}
// Обновление дисплея при необходимости
if (displayUpdate) {
updateDisplay();
displayUpdate = false;
}
// Автосохранение через 5 секунд бездействия
if (saveFlag && (millis() - settingsTimer > 5000)) {
EEPROM.update(0, u / 10); // Сохранение напряжения
EEPROM.update(1, i / 100); // Сохранение тока
saveFlag = false;
iu = false;
displayUpdate = true;
}
}
// ================================= ОБНОВЛЕНИЕ ДИСПЛЕЯ =================================
void updateDisplay() {
// Форматирование значений
char u_iz_str[6], i_iz_str[6], u_set_str[6], i_set_str[6], state_str[4];
// Форматирование с правильным количеством знаков после запятой
dtostrf(u_iz * KALL_U_IZ / 100.0, 4, 1, u_iz_str); // "12.3"
dtostrf(i_iz * KALL_I_IZ / 1000.0, 4, 2, i_iz_str); // "1.23"
dtostrf(u / 10.0, 4, 1, u_set_str); // "12.3"
dtostrf(i / 100.0, 4, 2, i_set_str); // "1.23"
strcpy(state_str, power ? "OFF" : "ON ");
// Обновление только измененных частей
if (strcmp(u_iz_str, last_u_iz_str) != 0) {
lcd.setCursor(3, 0);
lcd.print(u_iz_str);
strcpy(last_u_iz_str, u_iz_str);
}
if (strcmp(i_iz_str, last_i_iz_str) != 0) {
lcd.setCursor(3, 1);
lcd.print(i_iz_str);
strcpy(last_i_iz_str, i_iz_str);
}
if (strcmp(u_set_str, last_u_set_str) != 0) {
lcd.setCursor(8, 0);
lcd.print(u_set_str);
strcpy(last_u_set_str, u_set_str);
}
if (strcmp(i_set_str, last_i_set_str) != 0) {
lcd.setCursor(8, 1);
lcd.print(i_set_str);
strcpy(last_i_set_str, i_set_str);
}
if (iu != last_iu) {
lcd.setCursor(0, 0);
lcd.print(iu ? "U " : "U> ");
lcd.setCursor(0, 1);
lcd.print(iu ? "I> " : "I ");
last_iu = iu;
}
if (strcmp(state_str, last_state_str) != 0 || power != last_power) {
lcd.setCursor(12, 1);
lcd.print(state_str);
strcpy(last_state_str, state_str);
last_power = power;
}
// Статичные элементы (только при первом обновлении)
static bool first_update = true;
if (first_update) {
lcd.setCursor(7, 0);
lcd.print("V");
lcd.setCursor(7, 1);
lcd.print("A");
lcd.setCursor(12, 0);
lcd.print("OUT");
first_update = false;
}
}