/**
* "Разработка микропроцессорной системы управления процессом смешивания жидкостей"
* Выполнил: Петросян К.Г. ВМ-41
*/
/// Объявляем используемые пины, и даем им логические имена
#define PIN_UNKNOWN 0
// ЖКИ
#define PIN_LCD_RS 32
#define PIN_LCD_E 30
#define PIN_LCD_DB4 28
#define PIN_LCD_DB5 26
#define PIN_LCD_DB6 24
#define PIN_LCD_DB7 22
// Светодиоды
#define PIN_LED_POWER 8
#define PIN_LED_FILL 9
#define PIN_LED_MIX 10
// Кнопки
#define PIN_BUTTON_PLUS 5
#define PIN_BUTTON_MINUS 6
#define PIN_BUTTON_ACCEPT 7
// Сервоприводы
#define PIN_SERVO_1 11
#define PIN_SERVO_2 12
// Мотор
#define PIN_MIX 13
// Датчики уровня воды
#define PIN_WATER_LEVEL_1 23
#define PIN_WATER_LEVEL_2 25
#define PIN_WATER_LEVEL_3 27
#define PIN_WATER_LEVEL_4 29
#define PIN_WATER_LEVEL_5 31
#define PIN_WATER_LEVEL_6 33
#define PIN_WATER_LEVEL_7 35
#define PIN_WATER_LEVEL_8 37
#define PIN_WATER_LEVEL_9 39
#define PIN_WATER_LEVEL_10 41
#define PIN_WATER_LEVEL_DANGER 43
/// Основная часть программы
// ID кнопок для функции isClicked
#define BUTTON_PLUS 0
#define BUTTON_MINUS 1
#define BUTTON_ACCEPT 2
// Состояния системы
#define STATE_INIT 0
#define STATE_ENTER_FIRST_LEVEL 1
#define STATE_ENTER_SECOND_LEVEL 2
#define STATE_ENTER_TIME_MIX 3
#define STATE_FILL 6
#define STATE_MIX 4
#define STATE_DANGER 5
// Подключаем библиотеку для работы с жидкокристаллическим
// экраном (англ. Liquid Crystal Display или просто LCD)
#include <LiquidCrystal.h>
// Объявляем объект, для управления дисплеем. Для его создания
// необходимо указать номера пинов, к которым он подключен в
// порядке: RS E DB4 DB5 DB6 DB7
LiquidCrystal lcd(PIN_LCD_RS, PIN_LCD_E, PIN_LCD_DB4, PIN_LCD_DB5, PIN_LCD_DB6, PIN_LCD_DB7);
// Вспомогательные переменные
// - Состояние нажатий клавиш, по умолчанию не нажаты
boolean button_state[3] = { false, false, false };
// - Определение открытого резервуара
byte open_servo_id = 0;
// Состояние системы
int currentState = STATE_INIT;
boolean isFirstCall = true; // true если состояние проходит первый цикл
// Заданные величины
int levelOne = 0;
int levelRes = 0;
int timeSeconds = 0;
// Функция установки запускается один раз при нажатии reset или включения питания на плате
void setup() {
// Инициализация пинов как Input, Output
// Светодиоды
pinMode(PIN_LED_POWER, OUTPUT);
pinMode(PIN_LED_FILL, OUTPUT);
pinMode(PIN_LED_MIX, OUTPUT);
// Кнопки
pinMode(PIN_BUTTON_PLUS, INPUT);
pinMode(PIN_BUTTON_MINUS, INPUT);
pinMode(PIN_BUTTON_ACCEPT, INPUT);
// Сервоприводы
pinMode(PIN_SERVO_1, OUTPUT);
pinMode(PIN_SERVO_2, OUTPUT);
// Мотор
pinMode(PIN_MIX, OUTPUT);
// Датчики уровня воды
pinMode(PIN_WATER_LEVEL_1, INPUT);
pinMode(PIN_WATER_LEVEL_2, INPUT);
pinMode(PIN_WATER_LEVEL_3, INPUT);
pinMode(PIN_WATER_LEVEL_4, INPUT);
pinMode(PIN_WATER_LEVEL_5, INPUT);
pinMode(PIN_WATER_LEVEL_6, INPUT);
pinMode(PIN_WATER_LEVEL_7, INPUT);
pinMode(PIN_WATER_LEVEL_8, INPUT);
pinMode(PIN_WATER_LEVEL_9, INPUT);
pinMode(PIN_WATER_LEVEL_10, INPUT);
pinMode(PIN_WATER_LEVEL_DANGER, INPUT);
// начинаем работу с экраном. Сообщаем объекту количество
// строк и столбцов. Опять же, вызывать pinMode не требуется:
// функция begin сделает всё за нас
lcd.begin(16, 2);
}
// Функция loop выполняется снова и снова
void loop() {
// На всякий случай проверяем переполнение резервуара
if (isWaterLevel(PIN_WATER_LEVEL_DANGER)) {
changeStatus(STATE_DANGER); // устанавливаем соответствующее состояние
}
switch (currentState) {
case STATE_INIT: // инициализации
stateInit();
break;
case STATE_ENTER_FIRST_LEVEL: // ввод первого уровня жидкости
stateEnterFirstLevel();
break;
case STATE_ENTER_SECOND_LEVEL: // ввод результирующего уровня
stateEnterSecondLevel();
break;
case STATE_ENTER_TIME_MIX: // ввод времени перемешивания
stateEnterTimeMix();
break;
case STATE_FILL: // заливаем
stateFill();
break;
case STATE_MIX: // мешаем
stateMix();
break;
case STATE_DANGER: // жидкости слишком много
stateDanger();
break;
}
isFirstCall = false;
}
void stateInit() {
lcd.clear();
lcd.print("Загрузка...");
// включаем светодиод, что устройство работает
digitalWrite(PIN_LED_POWER, HIGH);
delay(2000); // 2 sec
changeStatus(STATE_ENTER_FIRST_LEVEL);
}
void stateEnterFirstLevel() {
if (isFirstCall) {
lcd.clear();
lcd.print("Ввод ур 1 ж");
}
if (isClicked(BUTTON_PLUS)) {
levelOne++;
} else if (isClicked(BUTTON_MINUS)) {
levelOne--;
}
if (levelOne < 1) levelOne = 1;
if (levelOne > 10) levelOne = 10;
lcd.setCursor(0, 1);
lcd.print(levelOne);
if (isClicked(BUTTON_ACCEPT)) {
changeStatus(STATE_ENTER_SECOND_LEVEL);
}
}
void stateEnterSecondLevel() {
if (isFirstCall) {
lcd.clear();
lcd.print("Ввод ур рез ж");
}
if (isClicked(BUTTON_PLUS)) {
levelRes++;
} else if (isClicked(BUTTON_MINUS)) {
levelRes--;
}
// уровень результирующей жидкости всегда
// должен быть больше уровня первой жидкости
if (levelRes < levelOne) levelRes = levelOne;
if (levelRes > 10) levelRes = 10;
lcd.setCursor(0, 1);
lcd.print(levelRes);
if (isClicked(BUTTON_ACCEPT)) {
changeStatus(STATE_ENTER_TIME_MIX);
}
}
void stateEnterTimeMix() {
if (isFirstCall) {
lcd.clear();
lcd.print("Ввод врем перем");
}
if (isClicked(BUTTON_PLUS)) {
timeSeconds++;
} else if (isClicked(BUTTON_MINUS)) {
timeSeconds--;
}
if (timeSeconds < 1) timeSeconds = 1;
lcd.setCursor(0, 1);
lcd.print(timeSeconds);
if (isClicked(BUTTON_ACCEPT)) {
changeStatus(STATE_FILL);
}
}
void stateFill() {
if (isFirstCall) {
lcd.clear();
lcd.print("Подача жидкости");
// включаем светодиод подачи воды
digitalWrite(PIN_LED_FILL, HIGH);
// открываем 1 клапон
digitalWrite(PIN_SERVO_1, HIGH);
open_servo_id = 1;
}
if (open_servo_id == 1 && isWaterLevel(getPinFromLevel(levelOne))) {
// закрываем 1 клапон
digitalWrite(PIN_SERVO_1, LOW);
// открываем 2 клапон
digitalWrite(PIN_SERVO_2, HIGH);
open_servo_id = 2;
} else if (open_servo_id == 2 && isWaterLevel(getPinFromLevel(levelOne))) {
// закрываем 2 клапон
digitalWrite(PIN_SERVO_2, LOW);
open_servo_id = 0;
// выключаем светодиод подачи воды
digitalWrite(PIN_LED_FILL, LOW);
changeStatus(STATE_MIX); // мешаем
}
}
void stateMix() {
if (isFirstCall) {
lcd.clear();
lcd.print("Перемешивание");
lcd.setCursor(0, 1);
// включаем светодиод перемешивания
digitalWrite(PIN_LED_MIX, HIGH);
// включаем мотор
digitalWrite(PIN_MIX, HIGH);
}
lcd.setCursor(0, 1);
lcd.print(timeSeconds);
delay(1000); // 1 sec
timeSeconds--;
if (timeSeconds <= 0) {
// выключаем мотор
digitalWrite(PIN_MIX, LOW);
// выключаем светодиод перемешивания
digitalWrite(PIN_LED_MIX, LOW);
changeStatus(STATE_INIT);
}
}
void stateDanger() {
lcd.clear();
lcd.print("Превышен уровень жидкости");
lcd.setCursor(0, 1);
lcd.print("Устройство будет переведено в начальное состояние");
// выключаем светодиоды и мотор, а также закрываем сервоприводы
digitalWrite(PIN_LED_POWER, LOW);
digitalWrite(PIN_LED_FILL, LOW);
digitalWrite(PIN_LED_MIX, LOW);
digitalWrite(PIN_SERVO_1, LOW);
digitalWrite(PIN_SERVO_2, LOW);
digitalWrite(PIN_MIX, LOW);
delay(3000); // 3 sec
changeStatus(STATE_INIT);
}
void changeStatus(int status) {
currentState = status;
isFirstCall = true;
}
// возвращает пин в соответствии с уровнем
int getPinFromLevel(int level) {
switch (level) {
case 1:
return PIN_WATER_LEVEL_1;
case 2:
return PIN_WATER_LEVEL_2;
case 3:
return PIN_WATER_LEVEL_3;
case 4:
return PIN_WATER_LEVEL_4;
case 5:
return PIN_WATER_LEVEL_5;
case 6:
return PIN_WATER_LEVEL_6;
case 7:
return PIN_WATER_LEVEL_7;
case 8:
return PIN_WATER_LEVEL_8;
case 9:
return PIN_WATER_LEVEL_9;
case 10:
return PIN_WATER_LEVEL_10;
default:
return PIN_UNKNOWN;
}
}
// Функция возвращает true, если уровень жидкости превысил заданный уровень
boolean isWaterLevel(int pinLevel) {
return digitalRead(pinLevel) == HIGH;
}
// Функция возвращает true, если пользователь отпустил кнопку
boolean isClicked(byte buttonId) {
int buttonPin;
// Определяем для какой кнопки проверка выполняется
switch (buttonId) {
case BUTTON_PLUS:
buttonPin = PIN_BUTTON_PLUS;
break;
case BUTTON_MINUS:
buttonPin = PIN_BUTTON_MINUS;
break;
case BUTTON_ACCEPT:
buttonPin = PIN_BUTTON_ACCEPT;
break;
default:
return false;
}
// определить момент «клика» несколько сложнее, чем факт того,
// что кнопка сейчас просто нажата. Для определения клика мы
// сначала понимаем, отпущена ли кнопка прямо сейчас...
boolean buttonIsUp = digitalRead(buttonPin) == HIGH;
boolean buttonWasUp = button_state[buttonId];
boolean ret = false;
// ...если «кнопка была отпущена и (&&) не отпущена сейчас»...
if (!buttonWasUp && buttonIsUp) {
// ...может это «клик», а может и ложный сигнал (дребезг),
// возникающий в момент замыкания/размыкания пластин кнопки,
// поэтому даём кнопке полностью «успокоиться»...
delay(10);
// ...и считываем сигнал снова
buttonIsUp = digitalRead(buttonPin) == HIGH;
if (buttonIsUp) { // если она всё ещё нажата...
// ...это клик!
ret = true;
}
}
// запоминаем последнее состояние кнопки
button_state[buttonId] = buttonIsUp;
return ret;
}