This is an old revision of the document!


Water refilling system

Introducere

Nume: Popovici Robert-Adrian
Grupa: 335CA

Obiectivul acestui proiect consta in realizarea unui sistem automat de umplere a diverselor recipiente cu lichide. Inregul sistem este centrat in jurul unei placute Arduino Mega 2560, aceasta fiind responsabila pentru controlul pieselor esentiale ale acestui proiect (pompa de apa, releul care alimenteaza pompa, etc.), dar si a componentelor care care servesc drept input si output pentru utilizatori (matricea de leduri, potentiomentru liniar, etc.).

Utilitatea proiectului este destul de evidenta si poate fi rezumata prin urmatoarele idei:

  • automatizarea unei sarcini uzuale si destul de frecvente
  • oferirea unei precizii mult mai bune si micsorarea duratei de asteptare
  • evitarea diverselor accidente care pot fi cauzate de erorile umane

Descriere generală

Interactiunea utilizatorului cu intregul sistem se realizeaza prin intermediul unui potentiomentru liniar, acesta avand rolul de a seta volumul de lichind care urmeaza sa fie turnat in recipient. In timp real aceast volum va fi afisata de catre Arduino pe matricea led 8×8 in format decimal, controlul acesteia fiind realizat pur software (fara a folosi niciun driver auxiliar de control).

Dupa ce volumul de lichid a fost stabilit utilizatorul va da comanda de start prin apasarea unui push-button. La apasarea butonului de start, modulul Arduino va activa releul care la randul lui va pune in functiune pompa de apa. Pentru a calcula timpul necesar de functionare al pompei vom presupune ca aceasta are un debit aproximativ constant, pe care il vom calcula in prealabil.

Dupa scurgerea timpului determinat anterior, pompa se va opri, iar sistemul este pregatit pentru a procesa noi comenzi.

Schema bloc

Hardware Design

Piesele folosite in cadrul proiectului sunt:

  • Arduino Mega 2560
  • Pompa de apa submersibila 12V
  • Matrice de leduri 8×8
  • Convertor AC-DC 12V
  • Releu 5V
  • Rezistente 330 ohm
  • Potentiometru liniar 10k ohm
  • Push button
  • Fire dupont

Software Design

Organizare

Codul este structurat in mai multe fisiere auxiliare grupate in functie de logica pe care o indeplinesc. Astfel vom prezenta pe scurt principalele module ale aplicatiei, urmand sa analizam in detaliu modul lor de functionare in sectiuniile urmatoare.

Button

Clasa care permite interfatarea unui buton. Aceasta are incorporat un modul de debouncer si poate fi configurata pentru a declansa diverse actiuni in functie de modul in care apasam butonul.

Componente:

  • button.h
  • buttonConfig.h
  • buttonConfig.cpp

Clock1ms

Timer cu frecventa de 1kHz, folosit de majoritatea modulelor aplicatiei.

Componente:

  • clock1ms.h
  • clock1ms.cpp
  • clockConfig.h
  • clockConfig.cpp

EventHandler

Clasa generica care permite declansare unor actiuni la un anumit moment de timp aleator, ales de apelant.

Componente:

  • eventHandler.h

LedMatrix

Principalul modul folosit pentru a afisa mesaje cu rol informativ legate de starea sistemului.

Componente:

  • ledMatrix.h
  • ledMatrixConfig.h
  • ledMatrixConfig.cpp

Logger

Driver folosit pentru a controla matricea de leduri. Acesta poate fi configurat la modul general pentru a controla orice matrice de leduri (indiferent de dimensiune) sau chiar o grupare de mai multe matrici.

Componente:

  • logger.h
  • logger.cpp

PinInfo

Wrapper peste un pin al Arduino. Controleaza un pin prin intermediul registrelor specifice (DDR, PIN, PORT) si expune o interfata simplu de utilizat pentru a configura pinul in diverse moduri.

Componente:

  • pinInfo.h

WaterPump

Clasa folosita pentru a controla pompa de apa.

Componente:

  • waterPump.h
  • waterPump.cpp
  • waterPumpConfig.h
  • waterPumpConfig.cpp

Intregul cod este disponibil aici:

Program Flow

Fisierul main.ino este responsabil pentru intializarea tuturor componentelor si actualizarea periodica a acestora.

#include "config.h"
 
static constexpr int BAUD_RATE = 9600;
 
void setup() {
	Logger::setup(BAUD_RATE);
 
	setupClock();
 
	setupLedMatrix();
 
	setupButton();
 
	setupWaterPump();
 
	setupEventHandler();
}
 
void loop() {	
	if (clock_1ms::periodElapsed()) {
 
		updatePotentiometer();
 
		updateEvents();
 
		updateButton();
	}
 
	updateLedMatrix();
}

Modulul clock1ms.cpp configureaza intern o intrerupere care va fi declansata o data la 1ms. In cadrul intreruperii vom incrementa valoarea tickCounter, folosita pentru a simula manual operatiile de delay().

#include "clock1ms.h"
 
static volatile long long tickCounter = 0LL;
 
ISR(TIMER1_COMPA_vect) {
	tickCounter++;
}
 
namespace clock_1ms {
	void startClock() {
		// disable all interrupts
		cli();
 
		// we want to check status every 1ms (f = 1KHz)
		// f_clk = 16MHz => N = 64 and OCR1A = 249
 
		TCNT1 = 0;
		TCCR1A = 0;
		TCCR1B = 0;
 
		OCR1A = 249;
		TCCR1B |= (1 << WGM12);   // CTC mode
		TCCR1B |= (1 << CS11);    // 64 prescaler 
		TCCR1B |= (1 << CS10);
 
		TIMSK1 |= (1 << OCIE1A);  // OCR1A compare match interrupt
 
		// enable back interrupts
		sei();
	}
 
	bool periodElapsed() {
		const long long elapsed = tickCounter - lastTickCounter;
		lastTickCounter = tickCounter;
		return elapsed > 0;
	}
 
	long long getTickCounter() {
		return tickCounter;
	}
}

Modulul button este alcatuit din 2 parti:

  • In button.h avem implementarea efectiva a butonului cu modul intern de debouncer. Aceasta primeste un parametru generic de tipul callback, care va fi apelat, in functie de configurarea aleasa, fie atunci cand apasam butonul, fie cand il eliberam. Pentru aplicatia vom folosi modul (FALLING_EDGE), deoarece ne intereseaza sa pornim pompa o data ce utilizatorul a apasat butonul.
#pragma once
 
#include "clock1ms.h"
#include "pinInfo.h"
 
class AbstractButton {
public:
	AbstractButton(PinInfo pinInfo) {
		this->pinInfo = pinInfo;
	}
 
protected:
	PinInfo pinInfo;
};
 
class AbstractCallableButton : public AbstractButton {
public:
	enum class CallbackActivation {
		RISING_EDGE,
		FALLING_EDGE
	};
 
	AbstractCallableButton(PinInfo pinInfo) : AbstractButton(pinInfo) {}
 
	static inline CallbackActivation getFallingEdge() {
		return CallbackActivation::FALLING_EDGE;
	}
 
	static inline CallbackActivation getRisingEdge() {
		return CallbackActivation::RISING_EDGE;
	}
 
	virtual void checkButton() = 0;
};
 
template<typename T>
class BaseCallableButton : public AbstractCallableButton {
public:
	BaseCallableButton(PinInfo pinInfo, const T& callback, CallbackActivation activationEdge)
		: AbstractCallableButton(pinInfo),
		  callback(callback) {
 
		this->activationEdge = activationEdge;
		if (activationEdge == CallbackActivation::RISING_EDGE) {
			pinInfo.configure(PinInfo::PinType::_INPUT_PULLDOWN);
		} else {
			pinInfo.configure(PinInfo::PinType::_INPUT_PULLUP);
		}
	}
 
	inline bool isActiveOnFallingEdge() {
		return activationEdge == CallbackActivation::FALLING_EDGE;
	}
 
	inline bool isActiveOnRisingEdge() {
		return activationEdge == CallbackActivation::RISING_EDGE;
	}
 
protected:
	CallbackActivation activationEdge;
	const T& callback;
};
 
template<typename T>
class CallableButton : public BaseCallableButton<T> {
public:
	using CallbackActivation = AbstractCallableButton::CallbackActivation;
 
	CallableButton(PinInfo pinInfo, const T& callback, CallbackActivation activationEdge) 
		: BaseCallableButton<T>(pinInfo, callback, activationEdge) {}
 
	CallableButton(PinInfo pin, const T& callback) 
		: CallableButton<T>(pin, callback, AbstractCallableButton::getFallingEdge()) {}
 
	void checkButton() override {
		const long long tickCounter = clock_1ms::getTickCounter();
 
		int currentButtonState = this->pinInfo.read();
		if (lastButtonState != currentButtonState) {
			lastDebounceTime = tickCounter;
		}
 
		if (tickCounter - lastDebounceTime > DEBOUNCE_TIME_MILLIS) {
			if (currentButtonState != debouncedButtonState) {
				debouncedButtonState = currentButtonState;
				if (debouncedButtonState == LOW && isActiveOnFallingEdge() ||
				    debouncedButtonState == HIGH && isActiveOnRisingEdge()) {
					callback();
				}
			}
		}
 
		lastButtonState = currentButtonState;
	}
 
private:
	static constexpr long long DEBOUNCE_TIME_MILLIS = 50;
 
	int lastButtonState;
	int debouncedButtonState;
	long long lastDebounceTime;
};
  • In buttonConfig.cpp vom instantia butonul si vom configura actiunile declansate atunci cand aceasta este apasat. Principala actiune va fi pornirea pompei de apa, daca aceasta nu era deja in starea RUNNING. Dupa aceea, vom calcula timpul necesar de umplere si vom insera un nou eveniment in event handler pentru a opri pompa dupa timpul calculat anterior.
#include "buttonConfig.h"
 
static constexpr PinInfo BUTTON_PIN_INFO = {&DDRD, &PIND, &PORTD, PD0};
AbstractCallableButton* button;
 
void setupButton() {
	auto buttonCallback = []() {
		if (waterPump.getState() == WaterPump::WorkingState::RUNNING)
			return;
 
		// multiply displayNumber with SCALE_FACTOR for better granularity
		static constexpr int SCALE_FACTOR = 10;
		long long fillTime = WaterPump::getFillTime(displayNumber * SCALE_FACTOR);
		long long currentTime = clock_1ms::getTickCounter();
		long long expireTime = fillTime + currentTime;
 
		auto& logger = Logger::getLogger();
		logger.debug().append("Current time: ").append(currentTime).commit();
		logger.debug().append("Fill time: ").append(fillTime).commit();
		logger.debug().append("Expire time: ").append(expireTime).commit();
 
		logger.info().append("Starting pump...").commit();
		waterPump.startPump();
 
		auto eventCallback = []() {
			auto& logger = Logger::getLogger();
			logger.info().append("Stopping pump: ").append(clock_1ms::getTickCounter()).commit();
			waterPump.stopPump(); 
		};
 
		using event_callback_t = decltype(eventCallback);
		eventHandler.addEvent<event_callback_t>(expireTime, eventCallback);
	};
 
 
	using button_callback_t = decltype(buttonCallback);
	button = new CallableButton<button_callback_t>(BUTTON_PIN_INFO, buttonCallback);
}
 
void updateButton() {
	button->checkButton();
}

Modulul ledmatrix este alcatui in mod simlar din 2 parti:

  • In ledMatrixConfig.cpp vom initializa driver-ul pentru matricea de leduri. Acesta primeste ca argumente in constructor doi vectori de tipul PinInfo. Primul vector reprezinta pinii care vor controla randurile matricei de leduri, iar al doilea vector reprezinta pinii de pe coloane. Dupa cum se poate vedea implementarea este destul de generica si nu tine cont de dimensiunea matricei.
#include "ledMatrixConfig.h"
 
static const std::array<PinInfo, LED_MATRIX_ROWS> ROW_PINS = 
	{{&DDRC, &PINC, &PORTC, PC7},
	 {&DDRC, &PINC, &PORTC, PC2},
	 {&DDRA, &PINA, &PORTA, PA0},
	 {&DDRC, &PINC, &PORTC, PC4},
	 {&DDRA, &PINA, &PORTA, PA7},
	 {&DDRA, &PINA, &PORTA, PA1},
	 {&DDRA, &PINA, &PORTA, PA6},
	 {&DDRA, &PINA, &PORTA, PA3}};
 
static const std::array<PinInfo, LED_MATRIX_COLS> COL_PINS = 
	{{&DDRC, &PINC, &PORTC, PC3},
	 {&DDRA, &PINA, &PORTA, PA5},
	 {&DDRA, &PINA, &PORTA, PA4},
	 {&DDRC, &PINC, &PORTC, PC6},
	 {&DDRA, &PINA, &PORTA, PA2},
	 {&DDRC, &PINC, &PORTC, PC5},
	 {&DDRC, &PINC, &PORTC, PC1},
	 {&DDRC, &PINC, &PORTC, PC0}};
 
 
LedMatrixDriver<LED_MATRIX_ROWS, LED_MATRIX_COLS> ledMatrix(ROW_PINS, COL_PINS);
 
void setupLedMatrix() {
	// ToDo: add setup if necessary
}
 
void updateLedMatrix() {
	displayNumber = map(potentiometerValue, 0, 1023, DISPLAY_RANGE_HIGH, DISPLAY_RANGE_LOW);
	ledMatrix.drawNumber(displayNumber, DISPLAY_OFFSET_ROW, DISPLAY_OFFSET_COL);
}

Rezultate Obţinute

Care au fost rezultatele obţinute în urma realizării proiectului vostru.

Concluzii

Download

O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-).

Fişierele se încarcă pe wiki folosind facilitatea Add Images or other files. Namespace-ul în care se încarcă fişierele este de tipul :pm:prj20??:c? sau :pm:prj20??:c?:nume_student (dacă este cazul). Exemplu: Dumitru Alin, 331CC → :pm:prj2009:cc:dumitru_alin.

Jurnal

Puteți avea și o secțiune de jurnal în care să poată urmări asistentul de proiect progresul proiectului.

Bibliografie/Resurse

Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe Resurse Software şi Resurse Hardware.

Export to PDF

pm/prj2023/alucaci/water-refilling-system.1685300609.txt.gz · Last modified: 2023/05/28 22:03 by robert.popovici
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0