This is an old revision of the document!
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:
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.
Piesele folosite in cadrul proiectului sunt:
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.
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:
Timer cu frecventa de 1kHz, folosit de majoritatea modulelor aplicatiei.
Componente:
Clasa generica care permite declansare unor actiuni la un anumit moment de timp aleator, ales de apelant.
Componente:
Principalul modul folosit pentru a afisa mesaje cu rol informativ legate de starea sistemului.
Componente:
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:
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:
Clasa folosita pentru a controla pompa de apa.
Componente:
Intregul cod este disponibil aici:
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:
#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; };
#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:
#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); }
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.