Ок, какой промышленный контроллер лучше взять для конкретных задач, без какого то огромного запаса.
Опять же тема с лифтом, это как минимум понятие общественной опасности, а тут всë будет ещё сотней концевиков будет дополнено.
Ок, какой промышленный контроллер лучше взять для конкретных задач, без какого то огромного запаса.
Опять же тема с лифтом, это как минимум понятие общественной опасности, а тут всë будет ещё сотней концевиков будет дополнено.
Я просто не могу пока это обкатать, потому что зима, а там реально всë надо взвешивать и месить. Куда мне сейчас десятки-сотни замесов бетона впустую. Просто хотелось доделать автоматику к сезону, и весной уже пробовать. Ну там ещё тарировать конечно надо всë.
Ну так то тут есть всë, что необходимо для работы с так скажем слаботочной стороны. Дальше реле на 220, большие пускатели, s образные тензодатчики, это же всë никакого отношения к этой стороне не имеет. А то что компоненты типо реле поменяются, это они на что не повлияет. Ну и плюс, само собой соберем это всë на макетной плате, такого вороха проводов не будет. Вопрос я просто не понимаю как вам правильнее озвучить. Достаточно ли “помехоустойчив” “Работоспособен” “Долговечен” Что бы работать в такой сфере ( задачу я в видео подробно разложил)
вы меня не слышите…
вы инженер разработчик или “доктор” диайвайщик с сознанием ардуино может все?
тут у нас есть такие
если первого у вас нет, никакой контроллер вам не поможет, если второе, ваш удел (условно безопасный), для личного применения.
// ==============================================
// АВТОМАТИЗИРОВАННАЯ СИСТЕМА ДОЗИРОВАНИЯ И СМЕШИВАНИЯ
// Управление бетономешалкой/дозатором
// Версия: 1.0
// ==============================================
// Библиотеки для работы с периферией
#include <Keypad.h> // Матричная клавиатура 4x4
#include <LiquidCrystal_I2C.h>// LCD дисплей по I2C
#include "HX711.h" // Тензодатчик (весы)
#include <SPI.h> // Шина SPI для SD карты
#include <SD.h> // Работа с SD картой
#include <Wire.h> // I2C шина
#include <RTClib.h> // Часы реального времени (RTC)
// ==============================================
// КОНФИГУРАЦИЯ (РЕДАКТИРОВАТЬ ПЕРЕД ЗАГРУЗКОЙ)
// ==============================================
// Пины для SD карты (SPI интерфейс)
const int SD_CS_PIN = 53; // Chip Select для SD карты
// Настройка индикаторной лампы ожидания
// 0 = Лампа горит постоянно (не мигает) в режиме ожидания
// 1000 = Мигает раз в 1 секунду, 500 = быстро, 2000 = медленно
const unsigned long IDLE_LAMP_SPEED = 1000;
// Параметры точного дозирования (импульсный режим)
const float DOSING_THRESHOLD = 0.70; // Порог включения импульсного режима (70%)
const int PULSE_ON_MS = 300; // Длительность импульса ВКЛ (мс)
const int PULSE_OFF_MS = 1000; // Длительность импульса ВЫКЛ (мс)
// Настройки конвейера
const int CONVEYOR_SMART_SHIFT_TIME = 2000; // Время сдвига конвейера (мс)
const int TIME_MOVE_AFTER_STONE = 20; // Время перемещения после щебня (сек)
const int TIME_MOVE_AFTER_SAND = 20; // Время перемещения после песка (сек)
// ==============================================
// ОБЪЯВЛЕНИЕ ОБЪЕКТОВ И ПЕРЕМЕННЫХ
// ==============================================
// LCD дисплей 20x4 символов с I2C адресом 0x27
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Часы реального времени
RTC_DS3231 rtc;
// Статусные переменные системы
int sdState = 0; // 0=Ошибка SD, 1=OK, 2=Ошибка записи
bool needScreenClear = true; // Флаг необходимости очистки экрана
int waterPercent = 100; // Процент воды (100% = по рецепту)
// ==============================================
// НАСТРОЙКА МАТРИЧНОЙ КЛАВИАТУРЫ 4x4
// ==============================================
const byte ROWS = 4; // 4 строки
const byte COLS = 4; // 4 столбца
// Раскладка клавиш
char keys[ROWS][COLS] = {
{'1','4','7','*'}, // Ряд 1: 1, 4, 7, *
{'2','5','8','0'}, // Ряд 2: 2, 5, 8, 0
{'3','6','9','#'}, // Ряд 3: 3, 6, 9, #
{'A','B','C','D'} // Ряд 4: A, B, C, D
};
// Пины подключения строк клавиатуры
byte rowPins[ROWS] = {11, 10, 9, 8};
// Пины подключения столбцов клавиатуры
byte colPins[COLS] = {7, 6, 5, 4};
// Объект клавиатуры
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// ==============================================
// ДАТЧИКИ И ИЗМЕРИТЕЛИ
// ==============================================
// Тензодатчик (весы) - HX711
const int LOADCELL_DOUT_PIN = A12; // Пин данных
const int LOADCELL_SCK_PIN = A13; // Пин тактовый
HX711 scale; // Объект весов
float SCALE_CALIBRATION = 420.0; // Калибровочный коэффициент
// Датчик расхода воды (турбинный)
const int FLOW_SENSOR_PIN = 2; // Пин прерывания для датчика потока
volatile unsigned long flowPulseCount = 0; // Счетчик импульсов (объявлен как volatile для ISR)
float FLOW_CALIBRATION = 4.5; // Импульсов на литр
// ==============================================
// УПРАВЛЯЮЩИЕ РЕЛЕ
// ==============================================
const int RELAY_STONE_1 = 22; // Реле щебня (группа 1)
const int RELAY_SAND = 23; // Реле песка
const int RELAY_WATER = 24; // Реле воды
const int RELAY_CEMENT = 25; // Реле цемента
const int RELAY_CONVEYOR = 26; // Реле конвейера
const int RELAY_CEM_VALVE = 27; // Реле клапана цемента
const int RELAY_IDLE_LAMP = 28; // Реле индикаторной лампы
const int RELAY_MIXER = 29; // Реле смесителя
// ==============================================
// ВНЕШНИЕ КНОПКИ
// ==============================================
const int BTN_EXT_START = 3; // Внешняя кнопка старта (с подтяжкой к +)
const int BTN_EMERGENCY = 30; // Аварийная кнопка STOP (с подтяжкой к +)
// ==============================================
// СИСТЕМНЫЕ ПЕРЕМЕННЫЕ И СОСТОЯНИЯ
// ==============================================
bool isSystemIdle = true; // Флаг режима ожидания
unsigned long lastLampTime = 0; // Таймер для мигания лампы
unsigned long lastScreenTime = 0; // Таймер для обновления экрана
bool lampState = false; // Текущее состояние лампы
unsigned long batchCounter = 0; // Счетчик выполненных партий
// База данных продуктов (меню)
// Структура: [категория][позиция]
String product[3][5] = {
{"Ring 1m", "Ring 1,5m", "Ring 2m", "Kryshka", "Dnishe"}, // Кольца
{"Block S", "Block M", "Block L", "", ""}, // Блоки
{"Beton M100", "Beton M150", "Beton M200", "Beton M250", "Beton M300"} // Бетон
};
// Категории продуктов
String productTypes = {"Kolca", "Bloki", "Beton"};
// Переменные меню
int mainMenu = -1; // Текущая выбранная категория (-1 = не выбрано)
int subMenu = -1; // Текущий выбранный продукт (-1 = не выбрано)
int lastMainMenu = -1; // Последняя использованная категория
int lastSubMenu = -1; // Последний использованный продукт
// Структура рецепта (компоненты в кг/литрах)
struct Recipe {
int stone1; // Щебень (кг)
int sand; // Песок (кг)
int water; // Вода (литры)
int cement; // Цемент (кг)
};
// Текущий загруженный рецепт
Recipe currentRecipe;
// ==============================================
// ПРОТОТИПЫ ФУНКЦИЙ (ДЛЯ КОРРЕКТНОЙ КОМПИЛЯЦИИ)
// ==============================================
void pulseCounterISR(); // Обработчик прерывания датчика потока
void runBatch(); // Основной цикл выполнения партии
float performStep(String stepName, int targetValue, int relayPin, bool isWater, bool useConveyor);
void runUnload(String name, int relayPin, int seconds);
void displayInfo(String info);
void updateIdleScreen();
void printTwoDigits(int number);
void checkExternalButton();
bool checkGlobalStop();
void logToSD_Full(String prodName, float s1_act, float sand_act, int w_plan, float w_act, float cem_act);
void viewLogs();
void buttonPress(char button);
void chooseProgram(int mainM, int subM);
// ==============================================
// ФУНКЦИЯ НАСТРОЙКИ (SETUP) - ВЫПОЛНЯЕТСЯ 1 РАЗ
// ==============================================
void setup() {
// Инициализация последовательного порта для отладки
Serial.begin(9600);
// Инициализация LCD дисплея
lcd.init();
lcd.backlight();
// Настройка пинов реле как ВЫХОДОВ
pinMode(RELAY_STONE_1, OUTPUT);
pinMode(RELAY_SAND, OUTPUT);
pinMode(RELAY_WATER, OUTPUT);
pinMode(RELAY_CEMENT, OUTPUT);
pinMode(RELAY_CONVEYOR, OUTPUT);
pinMode(RELAY_CEM_VALVE, OUTPUT);
pinMode(RELAY_IDLE_LAMP, OUTPUT);
pinMode(RELAY_MIXER, OUTPUT);
// Настройка пинов кнопок как ВХОДОВ с подтяжкой к питанию
pinMode(BTN_EXT_START, INPUT_PULLUP);
pinMode(BTN_EMERGENCY, INPUT_PULLUP);
// ИНИЦИАЛИЗАЦИЯ ВСЕХ РЕЛЕ В ВЫКЛЮЧЕННОМ СОСТОЯНИИ
// Примечание: Реле активируются сигналом LOW (0V)
digitalWrite(RELAY_STONE_1, HIGH);
digitalWrite(RELAY_SAND, HIGH);
digitalWrite(RELAY_WATER, HIGH);
digitalWrite(RELAY_CEMENT, HIGH);
digitalWrite(RELAY_CONVEYOR, HIGH);
digitalWrite(RELAY_CEM_VALVE, HIGH);
digitalWrite(RELAY_IDLE_LAMP, HIGH);
digitalWrite(RELAY_MIXER, HIGH);
// Инициализация часов реального времени (RTC)
if (!rtc.begin()) {
lcd.setCursor(0, 0);
lcd.print("RTC Error!");
delay(2000);
}
// Если RTC потерял питание - установить дату/время из компилятора
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(DATE), F(TIME)));
}
// Инициализация тензодатчика (весов)
lcd.setCursor(0, 0);
lcd.print("Init Scales...");
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
if (scale.wait_ready_timeout(2000)) {
scale.set_scale(SCALE_CALIBRATION);
scale.tare(); // Сброс весов (тарирование)
} else {
lcd.setCursor(0, 1);
lcd.print("Scale Error!");
delay(2000);
}
// Инициализация SD карты
lcd.setCursor(0, 1);
lcd.print("Init SD Card...");
if (!SD.begin(SD_CS_PIN)) {
lcd.setCursor(0, 2);
lcd.print("SD Fail/No Card");
sdState = 0; // Ошибка SD карты
delay(2000);
} else {
lcd.setCursor(0, 2);
lcd.print("SD Card OK!");
sdState = 1; // SD карта работает
// Создание файла лога, если он не существует
if (!SD.exists("log_full.csv")) {
File dataFile = SD.open("log_full.csv", FILE_WRITE);
if (dataFile) {
// Заголовок CSV файла
dataFile.println("Date;Time;ID;Product;Stone_Plan;Stone_Fact;Sand_Plan;Sand_Fact;Water_Plan;Water_Fact;Cement_Plan;Cement_Fact");
dataFile.close();
}
}
delay(1000);
}
// Настройка датчика расхода воды (прерывание)
pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), pulseCounterISR, RISING);
// Очистка экрана и переход в режим ожидания
lcd.clear();
isSystemIdle = true;
}
// ==============================================
// ФУНКЦИЯ ОБРАБОТКИ ПРЕРЫВАНИЯ ДАТЧИКА ПОТОКА
// ==============================================
void pulseCounterISR() {
flowPulseCount++; // Увеличиваем счетчик импульсов
// ВАЖНО: Функция должна быть максимально короткой!
// Не использовать delay(), Serial.print() и т.д.
}
// ==============================================
// ОСНОВНОЙ ЦИКЛ (LOOP) - ВЫПОЛНЯЕТСЯ ПОСТОЯННО
// ==============================================
void loop() {
// 1. Проверка аварийной остановки (высший приоритет)
if (checkGlobalStop()) return;
// 2. Обновление экрана в режиме ожидания
updateIdleScreen();
// 3. Проверка внешней кнопки старта
checkExternalButton();
// 4. Обработка нажатий на клавиатуре
char key = keypad.getKey();
if (key) {
buttonPress(key);
}
}
// ==============================================
// ОБНОВЛЕНИЕ ЭКРАНА В РЕЖИМЕ ОЖИДАНИЯ
// ==============================================
void updateIdleScreen() {
// Функция работает только в режиме ожидания
if (!isSystemIdle) return;
// 1. Очистка и отрисовка статического текста при входе
if (needScreenClear) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Vyberite regim:");
needScreenClear = false;
}
// Текущее время в миллисекундах
unsigned long currentMillis = millis();
// 2. УПРАВЛЕНИЕ ИНДИКАТОРНОЙ ЛАМПОЙ
if (IDLE_LAMP_SPEED == 0) {
// Режим 0: лампа горит постоянно
digitalWrite(RELAY_IDLE_LAMP, LOW); // LOW активирует реле
} else {
// Режим с миганием: проверяем таймер
if (currentMillis - lastLampTime > IDLE_LAMP_SPEED) {
lastLampTime = currentMillis;
lampState = !lampState; // Переключаем состояние
// Включаем/выключаем реле
digitalWrite(RELAY_IDLE_LAMP, lampState ? LOW : HIGH);
}
}
// 3. ОБНОВЛЕНИЕ ИНФОРМАЦИИ НА ЭКРАНЕ (раз в секунду)
if (currentMillis - lastScreenTime > 1000) {
lastScreenTime = currentMillis;
// Строка 2: Дата и время
DateTime now = rtc.now();
lcd.setCursor(0, 1);
printTwoDigits(now.day());
lcd.print('/');
printTwoDigits(now.month());
lcd.print('/');
lcd.print(now.year() % 100); // Только последние две цифры года
lcd.print(" ");
printTwoDigits(now.hour());
lcd.print(':');
printTwoDigits(now.minute());
lcd.print(':');
printTwoDigits(now.second());
// Строка 3: Статус SD карты
lcd.setCursor(0, 2);
lcd.print("SD:");
if (sdState == 1) lcd.print("OK ");
else if (sdState == 0) lcd.print("ERR ");
else if (sdState == 2) lcd.print("WR! ");
// Строка 4: Уровень воды и подсказки
lcd.setCursor(0, 3);
lcd.print("H2O:");
// Форматирование: добавляем пробел если число меньше 100
if (waterPercent < 100) lcd.print(" ");
lcd.print(waterPercent);
lcd.print("% A-Go D-Log");
}
}
// ==============================================
// ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ: ПЕЧАТЬ ДВУХ ЗНАКОВ
// ==============================================
void printTwoDigits(int number) {
// Добавляем ведущий ноль для чисел < 10
if (number < 10) lcd.print("0");
lcd.print(number);
}
// ==============================================
// ПРОСМОТР ЛОГОВ С SD КАРТЫ
// ==============================================
void viewLogs() {
// Выход из режима ожидания
isSystemIdle = false;
digitalWrite(RELAY_IDLE_LAMP, HIGH); // Выключаем лампу
// Информация на экране
lcd.clear();
lcd.print("Reading Logs...");
// Открытие файла логов
File dataFile = SD.open("log_full.csv");
if (!dataFile) {
lcd.setCursor(0, 1);
lcd.print("SD Error");
sdState = 0;
delay(2000);
needScreenClear = true;
isSystemIdle = true;
return;
} else {
sdState = 1; // SD карта доступна
}
// Подсчет общего количества строк в файле
unsigned long totalLines = 0;
while (dataFile.available()) {
if (dataFile.read() == '\n') totalLines++;
}
// Проверка на пустой файл (только заголовок)
if (totalLines < 2) {
lcd.setCursor(0, 1);
lcd.print("Log Empty");
dataFile.close();
delay(2000);
needScreenClear = true;
isSystemIdle = true;
return;
}
// Начинаем с последней записи
unsigned long currentLineIndex = totalLines - 1;
bool showDetails = false; // Флаг показа деталей
// Основной цикл просмотра логов
while (true) {
// Закрываем и открываем файл для каждого чтения
dataFile.close();
dataFile = SD.open("log_full.csv");
// Пропускаем ненужные строки
unsigned long linesToSkip = currentLineIndex;
String line = "";
unsigned long currentSeek = 0;
// Чтение нужной строки
while (dataFile.available()) {
char c = dataFile.read();
if (c == '\n') {
currentSeek++;
if (currentSeek > linesToSkip) break;
line = ""; // Сброс строки для нового чтения
} else {
if (currentSeek == linesToSkip) line += c;
}
}
// Разбор CSV строки на токены (поля)
String tokens[12];
int tokenIdx = 0;
int lastDelimiter = -1;
for (int i = 0; i < line.length(); i++) {
if (line[i] == ';') {
if (tokenIdx < 12) {
tokens[tokenIdx] = line.substring(lastDelimiter + 1, i);
tokenIdx++;
}
lastDelimiter = i;
}
}
// Последний токен (после последней ';')
if (tokenIdx < 12) tokens[tokenIdx] = line.substring(lastDelimiter + 1);
// Отрисовка информации на экране
lcd.clear();
if (tokens[2].length() > 0) {
if (!showDetails) {
// КОМПАКТНЫЙ ВИД (основная информация)
lcd.setCursor(0, 0);
lcd.print("#");
lcd.print(tokens[2]); // ID партии
lcd.print(" ");
lcd.print(tokens[1]); // Время
lcd.setCursor(0, 1);
lcd.print(tokens[0]); // Дата
lcd.setCursor(0, 2);
lcd.print(tokens[3]); // Название продукта
lcd.setCursor(0, 3);
lcd.print("<2 8> (5-More) C-Ex");
} else {
// ДЕТАЛЬНЫЙ ВИД (количества материалов)
lcd.setCursor(0, 0);
lcd.print("St:");
lcd.print(tokens[5]); // Щебень фактический
lcd.print(" Sd:");
lcd.print(tokens[7]); // Песок фактический
lcd.setCursor(0, 1);
lcd.print("Wt:");
lcd.print(tokens[9]); // Вода фактическая
lcd.print(" Cm:");
lcd.print(tokens[11]); // Цемент фактический
lcd.setCursor(0, 3);
lcd.print("Back: Press 5");
}
} else {
lcd.print("Read Error");
}
// Ожидание нажатия клавиши
char key = 0;
while (key == 0) {
key = keypad.getKey();
// Проверка аварийной остановки
if (checkGlobalStop()) {
dataFile.close();
return;
}
}
// Обработка нажатий
if (key == '2') {
// Листаем вверх (предыдущая запись)
if (currentLineIndex > 1) currentLineIndex--;
} else if (key == '8') {
// Листаем вниз (следующая запись)
if (currentLineIndex < totalLines - 1) currentLineIndex++;
} else if (key == '5') {
// Переключение между видами
showDetails = !showDetails;
} else if (key == 'C' || key == '#') {
// Выход из режима просмотра
dataFile.close();
needScreenClear = true;
isSystemIdle = true;
return;
}
}
}
// ==============================================
// ЗАПИСЬ ДАННЫХ ПАРТИИ В SD КАРТУ
// ==============================================
void logToSD_Full(String prodName, float s1_act, float sand_act, int w_plan, float w_act, float cem_act) {
// Открытие файла в режиме добавления
File dataFile = SD.open("log_full.csv", FILE_WRITE);
if (dataFile) {
sdState = 1; // SD карта доступна для записи
// Получение текущей даты и времени
DateTime now = rtc.now();
batchCounter++; // Увеличение счетчика партий
// Запись даты
dataFile.print(now.year(), DEC);
dataFile.print('/');
dataFile.print(now.month(), DEC);
dataFile.print('/');
dataFile.print(now.day(), DEC);
dataFile.print(";");
// Запись времени
dataFile.print(now.hour(), DEC);
dataFile.print(':');
dataFile.print(now.minute(), DEC);
dataFile.print(':');
dataFile.print(now.second(), DEC);
dataFile.print(";");
// Запись ID партии и названия продукта
dataFile.print(batchCounter);
dataFile.print(";");
dataFile.print(prodName);
dataFile.print(";");
// Запись данных по щебню (план/факт)
dataFile.print(currentRecipe.stone1);
dataFile.print(";");
dataFile.print(s1_act);
dataFile.print(";");
// Запись данных по песку (план/факт)
dataFile.print(currentRecipe.sand);
dataFile.print(";");
dataFile.print(sand_act);
dataFile.print(";");
// Запись данных по воде (план/факт)
dataFile.print(w_plan);
dataFile.print(";");
dataFile.print(w_act);
dataFile.print(";");
// Запись данных по цементу (план/факт)
dataFile.print(currentRecipe.cement);
dataFile.print(";");
dataFile.println(cem_act); // println добавляет перевод строки
dataFile.close(); // Закрытие файла
} else {
sdState = 2; // Ошибка записи
}
}
// ==============================================
// ПРОВЕРКА АВАРИЙНОЙ ОСТАНОВКИ (ВЫСШИЙ ПРИОРИТЕТ)
// ==============================================
bool checkGlobalStop() {
// Проверка нажатия аварийной кнопки
if (digitalRead(BTN_EMERGENCY) == LOW) {
// НЕМЕДЛЕННОЕ ОТКЛЮЧЕНИЕ ВСЕХ РЕЛЕ
digitalWrite(RELAY_STONE_1, HIGH);
digitalWrite(RELAY_SAND, HIGH);
digitalWrite(RELAY_WATER, HIGH);
digitalWrite(RELAY_CEMENT, HIGH);
digitalWrite(RELAY_CONVEYOR, HIGH);
digitalWrite(RELAY_CEM_VALVE, HIGH);
digitalWrite(RELAY_MIXER, HIGH);
digitalWrite(RELAY_IDLE_LAMP, HIGH);
// Отображение сообщения об аварии
lcd.clear();
lcd.print("!!! EMERGENCY !!!");
lcd.setCursor(0, 1);
lcd.print("STOP PRESSED");
// Ожидание отпускания аварийной кнопки
while (digitalRead(BTN_EMERGENCY) == LOW) {
delay(100); // Короткая задержка для стабильности
}
// Пауза перед восстановлением
delay(2000);
// Сброс всех состояний системы
needScreenClear = true;
mainMenu = -1;
subMenu = -1;
isSystemIdle = true;
return true; // Возвращаем true - была аварийная остановка
}
return false; // Аварийной остановки не было
}
// ==============================================
// ПРОВЕРКА ВНЕШНЕЙ КНОПКИ СТАРТА
// ==============================================
void checkExternalButton() {
// Проверка только в режиме ожидания
if (digitalRead(BTN_EXT_START) == LOW && isSystemIdle) {
delay(50); // Дебаунс (подавление дребезга)
// Повторная проверка после задержки
if (digitalRead(BTN_EXT_START) == LOW) {
// Эмуляция нажатия кнопки 'B' (повтор последней программы)
buttonPress('B');
// Ожидание отпускания кнопки
while (digitalRead(BTN_EXT_START) == LOW);
}
}
}
// ==============================================
// ОБРАБОТКА НАЖАТИЙ КЛАВИШ
// ==============================================
void buttonPress(char button) {
// Регулировка процента воды в режиме ожидания
if (isSystemIdle && mainMenu == -1) {
if (button == '*') {
// Уменьшение процента воды
waterPercent -= 5;
if (waterPercent < 10) waterPercent = 10; // Минимум 10%
needScreenClear = true;
updateIdleScreen();
return;
}
if (button == '#') {
// Увеличение процента воды
waterPercent += 5;
if (waterPercent > 200) waterPercent = 200; // Максимум 200%
needScreenClear = true;
updateIdleScreen();
return;
}
}
// Основная обработка функциональных клавиш
if (button == 'A') {
// Кнопка A: Запуск выбранной программы
if (mainMenu != -1 && subMenu != -1) {
lastMainMenu = mainMenu;
lastSubMenu = subMenu;
chooseProgram(mainMenu, subMenu);
} else {
displayInfo("Regim ne vybran");
delay(1000);
needScreenClear = true;
}
} else if (button == 'B') {
// Кнопка B: Повтор последней программы
if (lastMainMenu != -1 && lastSubMenu != -1) {
mainMenu = lastMainMenu;
subMenu = lastSubMenu;
chooseProgram(mainMenu, subMenu);
} else {
displayInfo("Istorii net");
delay(1000);
needScreenClear = true;
}
} else if (button == 'D') {
// Кнопка D: Просмотр логов
viewLogs();
} else if (button == 'C') {
// Кнопка C: Сброс выбора
mainMenu = -1;
subMenu = -1;
displayInfo("SBROS...");
delay(1000);
needScreenClear = true;
isSystemIdle = true;
} else {
// Обработка числовых клавиш (выбор меню)
if (button >= '0' && button <= '9') {
needScreenClear = false;
isSystemIdle = false;
digitalWrite(RELAY_IDLE_LAMP, HIGH); // Выключаем лампу
int num = button - '0'; // Преобразование символа в число
int index = num - 1; // Индекс в массиве (0-8)
if (index < 0 || index > 8) return;
if (mainMenu == -1) {
// Выбор категории (первый уровень меню)
if (index >= 0 && index < 3) {
mainMenu = index;
subMenu = -1;
lcd.clear();
lcd.print("Type: ");
lcd.print(productTypes[mainMenu]);
}
} else {
// Выбор продукта (второй уровень меню)
if (index >= 0 && index < 5 && product[mainMenu][index] != "") {
subMenu = index;
lcd.clear();
lcd.print("Prod: ");
lcd.print(product[mainMenu][subMenu]);
lcd.setCursor(0, 1);
lcd.print("A - Start");
}
}
}
}
}
// ==============================================
// ВЫБОР И ЗАГРУЗКА ПРОГРАММЫ (РЕЦЕПТА)
// ==============================================
void chooseProgram(int mainM, int subM) {
// Выход из режима ожидания
isSystemIdle = false;
digitalWrite(RELAY_IDLE_LAMP, HIGH);
// Загрузка рецепта в зависимости от выбранного продукта
if (mainM == 0 && subM == 0) {
// Кольцо 1м
currentRecipe = {500, 300, 30, 50}; // Щебень, Песок, Вода, Цемент
} else if (mainM == 0 && subM == 1) {
// Кольцо 1,5м
currentRecipe = {700, 400, 45, 70};
} else {
// Тестовый рецепт (по умолчанию)
currentRecipe = {5, 5, 2, 2};
}
// Отображение информации о запуске
lcd.clear();
lcd.print("Start Batch...");
delay(1000);
// Запуск основного цикла выполнения партии
runBatch();
}
// ==============================================
// ОСНОВНОЙ ЦИКЛ ВЫПОЛНЕНИЯ ПАРТИИ
// ==============================================
void runBatch() {
// Переменные для хранения фактических значений
float realStone = 0, realSand = 0, realWater = 0, realCement = 0;
// Шаг 1: Дозирование щебня
realStone = performStep("Scheben", currentRecipe.stone1, RELAY_STONE_1, false, true);
if (isSystemIdle) {
needScreenClear = true;
return; // Прервано аварийной остановкой
}
// Шаг 2: Перемещение щебня конвейером
runUnload("Move Stone ->", RELAY_CONVEYOR, TIME_MOVE_AFTER_STONE);
if (isSystemIdle) {
needScreenClear = true;
return;
}
// Шаг 3: Дозирование песка
realSand = performStep("Pesok", currentRecipe.sand, RELAY_SAND, false, true);
if (isSystemIdle) {
needScreenClear = true;
return;
}
// Шаг 4: Перемещение песка конвейером
runUnload("Move Sand ->", RELAY_CONVEYOR, TIME_MOVE_AFTER_SAND);
if (isSystemIdle) {
needScreenClear = true;
return;
}
// Шаг 5: Дозирование воды (с учетом процента)
int correctedWater = currentRecipe.water * (waterPercent / 100.0);
realWater = performStep("Voda", correctedWater, RELAY_WATER, true, false);
if (isSystemIdle) {
needScreenClear = true;
return;
}
// Шаг 6: Дозирование цемента
realCement = performStep("Cement", currentRecipe.cement, RELAY_CEMENT, false, false);
if (isSystemIdle) {
needScreenClear = true;
return;
}
// Шаг 7: Сохранение лога в SD карту
lcd.clear();
lcd.print("Saving Log...");
logToSD_Full(product[mainMenu][subMenu], realStone, realSand, correctedWater, realWater, realCement);
delay(500);
// Шаг 8: Выгрузка цемента
runUnload("Vygruzka Cem.", RELAY_CEM_VALVE, 10);
if (isSystemIdle) {
needScreenClear = true;
return;
}
// Шаг 9: Запуск смесителя
runUnload("Start Mixer!", RELAY_MIXER, 10);
// Шаг 10: Завершение партии
lcd.clear();
lcd.print("ZAMES GOTOV!");
delay(2000);
// Сброс состояний и возврат в режим ожидания
mainMenu = -1;
subMenu = -1;
needScreenClear = true;
isSystemIdle = true;
}
// ==============================================
// ФУНКЦИЯ ПРОСТОЙ ВЫГРУЗКИ (ВКЛ/ВЫКЛ НА ВРЕМЯ)
// ==============================================
void runUnload(String name, int relayPin, int seconds) {
// Отображение названия операции
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(name);
// Включение реле
digitalWrite(relayPin, LOW); // LOW активирует реле
// Обратный отсчет времени
for (int i = seconds; i > 0; i--) {
if (checkGlobalStop()) return; // Проверка аварийной остановки
// Отображение оставшегося времени
lcd.setCursor(0, 1);
lcd.print("Wait: ");
lcd.print(i);
lcd.print(" sec ");
delay(1000); // Задержка 1 секунда
}
// Выключение реле
digitalWrite(relayPin, HIGH);
}
// ==============================================
// ФУНКЦИЯ ТОЧНОГО ДОЗИРОВАНИЯ (ОСНОВНАЯ ЛОГИКА)
// ==============================================
float performStep(String stepName, int targetValue, int relayPin, bool isWater, bool useConveyor) {
// Проверка нулевого целевого значения
if (targetValue <= 0) return 0;
// Проверка аварийной остановки
if (checkGlobalStop()) return 0;
// Локальные переменные для дозирования
float currentVal = 0;
bool shifted1 = false; // Флаг первого сдвига конвейера
bool shifted2 = false; // Флаг второго сдвига конвейера
unsigned long lastPulseTime = 0; // Таймер импульсного режима
bool pulseState = false; // Текущее состояние импульса (ВКЛ/ВЫКЛ)
// Отображение информации на экране
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(stepName); // Название компонента
lcd.setCursor(12, 0);
lcd.print("T:"); // Целевое значение
lcd.print(targetValue);
// Подготовка к измерению
if (isWater) {
// Для воды: сброс счетчика импульсов
flowPulseCount = 0;
} else {
// Для сыпучих: тарирование весов
scale.tare();
}
// Включение соответствующего реле (начало дозирования)
digitalWrite(relayPin, LOW);
// ОСНОВНОЙ ЦИКЛ ДОЗИРОВАНИЯ
while (currentVal < targetValue) {
// Проверка аварийной остановки
if (checkGlobalStop()) {
digitalWrite(relayPin, HIGH); // Выключение реле
return currentVal; // Возврат текущего значения
}
// Получение текущего значения
if (isWater) {
// Для воды: расчет по импульсам
currentVal = flowPulseCount / FLOW_CALIBRATION;
} else {
// Для сыпучих: чтение с весов
currentVal = scale.get_units(3); // 3 измерения для усреднения
if (currentVal < 0) currentVal = 0; // Защита от отрицательных значений
}
// УМНЫЙ СДВИГ КОНВЕЙЕРА (только для сыпучих материалов)
if (useConveyor && !isWater) {
// Первый сдвиг на 33% от целевого значения
if (!shifted1 && currentVal > (targetValue * 0.33)) {
digitalWrite(relayPin, HIGH); // Временное выключение дозатора
lcd.setCursor(0, 1);
lcd.print("Smart Shift 1...");
digitalWrite(RELAY_CONVEYOR, LOW); // Включение конвейера
delay(CONVEYOR_SMART_SHIFT_TIME); // Время сдвига
digitalWrite(RELAY_CONVEYOR, HIGH); // Выключение конвейера
shifted1 = true; // Установка флага
digitalWrite(relayPin, LOW); // Продолжение дозирования
}
// Второй сдвиг на 66% от целевого значения
if (!shifted2 && currentVal > (targetValue * 0.66)) {
digitalWrite(relayPin, HIGH); // Временное выключение дозатора
lcd.setCursor(0, 1);
lcd.print("Smart Shift 2...");
digitalWrite(RELAY_CONVEYOR, LOW); // Включение конвейера
delay(CONVEYOR_SMART_SHIFT_TIME); // Время сдвига
digitalWrite(RELAY_CONVEYOR, HIGH); // Выключение конвейера
shifted2 = true; // Установка флага
digitalWrite(relayPin, LOW); // Продолжение дозирования
}
}
// ИМПУЛЬСНЫЙ РЕЖИМ ТОЧНОГО ДОЗИРОВАНИЯ (при достижении порога)
if (currentVal >= (targetValue * DOSING_THRESHOLD)) {
unsigned long now = millis();
if (pulseState) {
// Если реле ВКЛЮЧЕНО - проверяем время выключения
if (now - lastPulseTime >= PULSE_ON_MS) {
digitalWrite(relayPin, HIGH); // Выключение
pulseState = false;
lastPulseTime = now;
}
} else {
// Если реле ВЫКЛЮЧЕНО - проверяем время включения
if (now - lastPulseTime >= PULSE_OFF_MS) {
digitalWrite(relayPin, LOW); // Включение
pulseState = true;
lastPulseTime = now;
}
}
} else {
// ДО порога - непрерывный режим
if (digitalRead(relayPin) == HIGH) digitalWrite(relayPin, LOW);
}
// Отображение текущего значения
lcd.setCursor(0, 1);
lcd.print("Now: ");
lcd.print(currentVal, 1); // 1 знак после запятой
}
// Целевое значение достигнуто - выключение реле
digitalWrite(relayPin, HIGH);
// Краткая пауза для стабилизации
delay(1000);
// Возврат фактически измеренного значения
return currentVal;
}
// ==============================================
// ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ: ВЫВОД СООБЩЕНИЯ
// ==============================================
void displayInfo(String info) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(info);
}
вот с коментариями, может кому пригодится, и минимум потенциальные ошибки-проблемы вроде есть!
ну вот и ваш ассистент подтянулся))
пользуйтесь, а то он потом бабок попроси от запросов к ИИ.
Представьте что я инженер разработчик, так как вся остальная часть(механическая) уже нами разработана( совсем не как у всех). И как любой инженер разработчик, я не могу разбираться во всём вопросе( от сорта цемента, до версии необходимого контроллера) и поэтому я пытаюсь спросить у знающих людей. В моём окружении есть люди с автоматизации ГЭС, больших заводов, и производств, и всё смотря на это говорят что это очень даже неплохо выглядит))) потом естественно мне кировец соберёт провода, электрик всю силовую часть подключит, за это сомнений нет. Есть сомнения в контроллере.
еще и % с продаж! но времени нет, пойду в магазин за адреналином)))
а вы хоть бы ему про диодики на реле подсказали…. злой вы какой то)))
Какие например ошибки и проблемы? Я просто могу отдать, что бы проверил программист, но каждый раз не могу его просить, отдам уже финальную версию.
вы - манагер (перспективный менеджер).
вопроса про инженера недостаточно?
Ну мы хоть что то делаем, и это реально работает)) я не не проснулся сегодня утром и решил создать что то. Мы уже мешалки свои делаем, под себя, и много чего по мелочи, что работает. В этом моя эффективность).
для макета норм вроде, и не понятно что будет если sd карта не найдется, думаю не стоит исправлять, работает макет и ладно, а дальше при обнаружении ошибок поправите…
тока если не будет хватать, тоже поправите, или если сгорит ардуино, или зависнет, при пропускании большого тока через реле, тоже исправится позже если что… ардуино для любительских проектов, и его может хватить
но вообще я на форум больше по коду, на схемотехника 5 лет не учился, посмотреть на чужой код люблю, или получить вдохновение что собрать)))
ну хватит бровадства…
и по этому вы тут?
мешалки вы делали по какому форуму?
Работает норм, если карту не находит, просто в статус выводит фейл, уже попробовал. И без часов работает. Просто в колонке время выводит милисикунды от запуска ардуины. Если что то сгорит, я могу каждой мелочевки что тут есть штук по 5 купить, что бы всë было в наличии, сразу прошить все ардуино, что бы просто заменил и в работу сразу.
а переполнение милисс сер уже познал?
Блин, во правда мне уже интересно, почему вы так всё в штыки, по вашему мнению только человек с профильным образованием может сделать какую то задачу по его профилю? Так обычно рассуждают люди, которые типа отучились, но никому нафиг не пригодились, они у нас цемент в мешалки руками закидывают в основном, мы из-за высшего образования им не отказываем в приёме)). Я думаю Вы не такой человек. Ну или Вам сложно подсказать. Если да, скажите, я больше ничего спрашивать не буду. Я уже понял( коллега ваш написал) что код в целом норм, Вы сказали что и контроллер пойдёт, то есть что я хотел, я узнал. Не надо злиться на тех, кто что то не понимает, мы хорошие на самом деле))
Она будет выключаться примерно раз в 4 часа, ну край в 8, не думаю что возможно переполнить))
даже пока не дочитал до конца, ща почитаю, но и вы еще раз почитайте (в правилах) на какой форум вас занесло.
хорошо, не буду, но если ко мне приедет мешалка (теоретически от вас) и вместо куба, скинет мне 0.8 в фундамент, я вас … вспомню, без обид?
все, мир, и удачи вам в освоении микроконтролеров.