progetto: erogatore automatico di croccantini per cani o gatti:
dunque il programma controlla : 4 pulsanti ed un potenziometro per far impostare all'utente quantità di cibo e muoversi all'interno del menu' definito dalle condizioni di stato di ck3.
un rtc()RealTimeClock per avere un orario di riferimento a cui impostare l'erogazione
un motore stepper che attua la forza di rotazione necessaria all'erogazione dei croccantini
una cella di carico che monitora il peso della ciotola normalmente ed in fase di riempimento
ed un display lcd per permettere la visualizzazione indipendentemente dal seriale di arduino.
#include "RTClib.h" //LIBRERIA PER MODULO REAL TIME CLOCK
RTC_DS3231 rtc; //GENERAZIONE ISTANZA REAL TIME CLOCK
//PIN:(CELLA DI CARICO)5V + GND+22DT+24SCK //(PULSANTE ROSSO )18//(PULSANTE VERDE)19//(PULSANTE GIALLO)2//(PULSANTE BLU)3
#define steP 8 //(POTENTIOMETER)A0 5V+GND//(LCD)5V+GND+21 SCL BLU.+20 SDA VERDE.//(REAL TIME CLOCK RTC DS3231)5V+GND+SCL+SDA.
#define dire 9
#define enable 10
#define greenB 19 //ASSEGNAZIONE PIN DI ARDUINO
#define redB 18 //
#define pot A0 //
#define giallo 2 //
#define blueB 3 //
#include <Wire.h>
#include <LiquidCrystal_I2C.h> //LIBRERIA COMANDI LCD
#define I2C_ADDR 0x27 //ASSEGNAZIONE DEI PIN DELL CHIP EXPANDER NELL'I2C
#define BACKLIGHT_PIN 3 //
#define En_pin 2 //
#define Rw_pin 1 //
#define Rs_pin 0 //
#define D4_pin 4 //
#define D5_pin 5 //
#define D6_pin 6 //
#define D7_pin 7 //
LiquidCrystal_I2C lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin); //COMUNICAZIONE PIN E I2c
#include "HX711.h" //libreria cella di carico
HX711 scale(22, 24);
unsigned long timer, intervallo = 0, intervallo2 = 0;
float calibration_factor = -463, units;
int load, statoPre = 1, previous = 1, tempO, tempM, statoO = 3, statoM = 3, statoLoop = 0, tempLoad, previousS = 1, pulse = LOW; //tempO=ore//tempM=minuti//statoO=salvaore//statoM=salvaminuti//ck3Temp=temporanea ck3 esce dalle 3 condizioni normali
volatile int ck3 = 1, ck2 = 1, ck1 = 0; //StatoLoop=condizione stato nel loop//tempLoad=valore carico croccantini//previousS=stato funzione tara
char *res = malloc(5); //PASSA PARAMETRO QUANTITA' DI MEMORIA (5BYTE)
void setup() {
Serial.begin(9600);
if (!rtc.begin()) { //CONTROLLO PER L'INIZIALIZZAZIONE RTC ALTRIMENTI AVVISA SU SERIALE
Serial.println("rtc scollegato");
while (true);
}
if (rtc.lostPower()) //SALVATAGGIO INFORMAZIONI INERENTI A DATA ED ORA NEL CASO VENGA A MANCARE L'ALIMENTAZIONE(RTD FORNITO DI BATTERIA)
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
lcd.begin(20, 4); //ATTIVAZIONE RIGHE E COLONNE
lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE); //ATTIVAZIONE RETROILLUMINAZIONE
lcd.setBacklight(HIGH); //
pinMode(giallo, INPUT); //ATTIVAZIONE RESISTENZE INTERNE PULL UP PER PULSANTI
pinMode(blueB, INPUT_PULLUP); //
pinMode(greenB, INPUT_PULLUP); //
pinMode(redB, INPUT_PULLUP); //
pinMode(pot, INPUT); //
pinMode(steP, OUTPUT);
pinMode(dire, OUTPUT);
pinMode(enable, OUTPUT);
digitalWrite(enable, HIGH);
digitalWrite(dire, HIGH); //pin dir A4988 5V IL NEMA 11 RUOTA IN SENSO ANTIORARIO
attachInterrupt(digitalPinToInterrupt(redB), menu, FALLING); //PULSANTE ROSSO SCORRE PAGINE MENU'
attachInterrupt(digitalPinToInterrupt(greenB), conferma, FALLING); //PULSANTE VERDE CONFERMA LE OPERAZIONI SCELTE DALL'UTENTE
attachInterrupt(digitalPinToInterrupt(giallo), togli, RISING); //PULSANTE GIALLO INCREMENTA IN FASE DI SELEZIONE(ORARIO)
attachInterrupt(digitalPinToInterrupt(blueB), aggiungi, FALLING); //PULSANTE BLU DECREMENTA IN FASE DI SELEZIONE(ORARIO)
}
void loop() {
Serial.println("tempO" + String(tempO) + " tempM" + String(tempM) + " ck3:" + String(ck3) + " statoLoop" + String(statoLoop) + " tempLoad:" + String(tempLoad) + " Peso:" + String(units, 0) + " ck2:" + String(ck2));
DateTime now = rtc.now(); //OTTENGO L'INFORMAZIONE DI DATA ED ORA NEL CORPO DI VOID LOOP
lcd.setCursor(0, 0); //SERIE DI COMANDI PER PRINT A SCHERMO
lcd.print(zeriData(now.day()) + "/" + zeriData(now.month()) + "/" + zeriData(now.year())); //
lcd.setCursor(15, 0); //
lcd.print(zeriData(now.hour()) + ":" + zeriData(now.minute())); //
timer = millis(); //ASSEGNAZIONE DI MILLIS AD UNA VARIABILE TIMER, CHE NE PERMETTERA' LA MANIPOLAZIONE
scale.set_scale(calibration_factor); //PASSA IL VALORE DI CALIBRAZIONE ALLA GESTIONE DELLA CELLA DI CARICO
units = scale.get_units(); //ASSOCIA LA VARIABILE UNITS AL PESO RILEVATO DALLA BILANCIA.(D'ORA IN POI G.)
load = analogRead(pot); //VALORE ANALOGICO DEL POTENZIOMETRO UTILIZZATO COME STRUMENTO DI INTERFACCIA PER L'UTENTE
if (statoPre != ck3) { // AD OGNI CAMBIO DI STATO DEL PULSANTE ROSSO RICHIAMO UN LCD CLEAR E RIPULISCO LO SCHERMO
lcd.clear(); //RIASSEGNO IN OLTRE ALLA VARIABILE CK2 IL VALORE DI 1 ONDEVITARE CHE L'UTENTE NEL NAVIGARE TRA LE PAGINE
statoPre = ck3; // DEL MENU' SCORRA LE FINESTRE PRINCIPALI SENZA RIPORTARE LA SCHERMATA NEL "LUOGO DI PARTENZA"
ck2 = 1;
}
if (previous != ck2) { // AD OGNI CAMBIO DI STATO DEL PULSANTE VERDE RICHIAMO UN LCD CLEAR E RIPULISCO LO SCHERMO
lcd.clear();
previous = ck2;
}
if (ck3 >= 5) //HO AVUTO NECESSITà DI CREARE UNA PAGINA DI MENU' NON RICHIAMATA DALL'UTENTE BENSI' CHE SI AUTOGENERASSE
ck3 = 1; //ALLA VERIFICA DELLA CONDIZIONE DI ORA E MINUTI IMPOSTATA DALL'UTENTE(CK3==4) DAL MOMENTO IN QUI 4 NON è UN VALORE
// ASSEGNATO AL NORMALE INCREMENDO DELLA VARIBILE CK3 TRAMITE PULSANTE,E' STATO NECESSARIO AVERE UN CONTROLLO AUTOMATICO
if (ck3 == 1) //CK3 DEFINISCE LA PRIMA PAGINA DEL MENU' RICHIAMA GRAMMI(STAMPA DEL PESO) CON POSSIBILITA' DA PARTE DELL'UTENTE
grammi(); //DI PORTARE A 0 LA BILANCIA CON O SENZA UN EVENTUALE PESO APPLICATO.
if (ck3 == 2) //RICHIAMA RIEMPIMENTO POSSIBILITA' DI VARIARE LA QUANTITA' DI CROCCANTINI DA EROGARE E UN ORA PRECISA IN CUI ESEGUIRLA
riempimento();
if (ck3 == 3) {
calibrazione();
if (ck3 == 4)
ck3 = 1;
} //FUNZIONE CHE PERMETTE ALL'UTENTE DI RICALIBRARE LA BILANCIA IN CASO NEL TEMPO DOVESSE MODIFICARSI IN QUALCHE MODO
//IL PARAMETRO DI SCALING.
if (tempO == now.hour() && tempM == now.minute()) { //CONDIZIONE CHE RISULTA VERA SOLAMENTE SE L'ORARIO IMPOSTATO DALL'UTENTE CORRISPONDE A QUELLO REALE
if (statoLoop == 0 && units < tempLoad) { // MODIFICARE CK3 PER FARE SI CHE IL LOOP PERSISTA ED ESEGUO UN SINGOLO CLEAR CHE RIPULISCA LO SCHERMO ADATTANDOLO AL SEGUENTE SET DI ISTRUZIONI, SALVO IL VECCHIO VALORE
lcd.clear();
statoLoop = 1;
digitalWrite(enable, LOW); // CHE CORRISPONDE ALL'ULTIMA PAGINA DI MENU CON CUI L'UTENTE SI INTERFACCIA PRIMA CHE L'EROGAZIONE PARTA,COSI' DA ESSERE RIPROPOSTA UNA VOLTA FINITA L'EROGAZIONE
}
}
if (statoLoop == 1) { //SE CK3 = 4 ALLORA LE 3 CONDIZIONI PRECEDENTI NON PERSISTONO, PERTANTO DOPO LCD.CLEAR() MI TROVO UNA NUOVA PAGINA DI MENU' STAMPABILE PER LA DURATA DI TUTTA L'EROGAZIONE
ck3 = 4;
motorino(); //IMPONENDO STATO LOOP =1 NON APPENA SMETTE DI VERIFICARSI LA CONDIZIONE ORARIO UTENTE E ORARIO ATTUALE, IL PROGRAMMA PROSEGUE NEL CORPO DI ISTRUZIONI SUCCESSIVO
lcd.setCursor(11, 0);
lcd.print("(4)" );
if (ck3 != 4)
ck3 = 4;
} //RICHIAMANDO QUESTA FUNZIONE IL PROGRAMMA ESEGUE UN CONTROLLO DELL'UNITA CORRISPONDENTE AL PESO DELLA CIOTOLA CON LE UNITA' IMPOSTATE TRAMITE INTERFACCIA DALL'UTENTE,RELATIVE AL PESO DESIDERATO DI RIEMPIMENTO.
//CONDIZIONI VERIFICATE UNA SOLA VOLTA DOPO CHE L'EROGAZIONE FINISCE: RIPORTA ALLA PAGINA DI MENU' PREMEMORIZZATA SU ck3Temp
if (statoLoop == 2)
{ digitalWrite(enable, HIGH); //IN CUI CK3 RIPRENDE IL VAOLRE LASCIATO DALL'ULTIMA NAVIGAZIONE DELL'UTENTE. //VARIABILE DI STATO IMPOSTA ALTRIMENTI LA CONDIZIONE ORA IMPOSTATA E ORA ATTUALE DIVERSE RISULTEREBBE SEMPRE VALIDA, IMPOSSIBILITANDO LA RIUSCITA DELL'UTILIZZO DEL NORMALE MENU'
statoLoop = 3;
ck3 = 1;
lcd.clear();
}
//PUO' ACCADERE CHELA CONDIZIONE CHE VERIFICA ORE E MINUTI RITORNI FALSA SE PER ERRORE L'UTENTE DOVESSE CONFERMARE L'ORARIO NEL MOMENTO STESSO //A QUEL PUNTO LA VARIABILE DI STATO NON SAREBBE IN LINEA CON I CHECK CHE DEVE GARANTIRE,INOLTRE IL SERVO A SEGUITO DELL'ERRORE UTETE RIMARREBBE ALIMENTATO //E' NECESSARIO INOLTRE IN TALE EVENTUALITA' CHE LA VARIABILE ASSEGNATA AL MENU' RITORNI ALLA POSIZIONE DI PARTENZA,ALTRIMENTI ESCE DAI PAREMETRI CHE
if (statoLoop == 3 && tempM != now.minute())
statoLoop = 0; //NE CONTROLLANO IL NORMALE SCORRIMENTO (1->3)
if (statoLoop == 0 && ck3 > 3) {
digitalWrite(enable, HIGH);
ck2 = 1;
previous = ck2;
}
//VERIFICATO QUINDI CHE NON SI E' NELLO STATO CORRISPONDENTE AL MOMENTO DI ATTIVAZIONE DEL MOTORE PER L'EROGAZIONE DEI CROCCANTINI, SI PROCEDE ALLA RICONVERSIONE DI CK3 NEL CASO SUPERI LA SOGLIA DELLA 3 PAGINA DI MENU'
}
void motorino() {
lcd.setCursor(11, 0);
lcd.print("(4)" );
if (units <= tempLoad * 8 / 10 )
{
if (timer - intervallo >= 10) {
Serial.println(timer);
pulse = !pulse;
intervallo = timer;
}
digitalWrite(steP, pulse);
lcd.setCursor(0, 2);
lcd.print("Impostato:" + String(tempLoad) + "g.");
lcd.setCursor(0, 3);
lcd.print("Riempendo:" + String(units, 0) + "g.");
pulisciSchermo();
}
if (units > tempLoad * 8 / 10 && units < tempLoad * 0.90 )
{
if (timer - intervallo >= 20) {
Serial.println(timer);
pulse = !pulse;
intervallo = timer;
}
digitalWrite(steP, pulse);
lcd.setCursor(0, 2);
lcd.print("Impostato:" + String(tempLoad) + "g.");
lcd.setCursor(0, 3);
lcd.print("Riempendo:" + String(units, 0) + "g.");
lcd.print(" ");
}
if (units >= tempLoad) {
statoLoop = 2;
lcd.clear();
digitalWrite(enable, HIGH);
}
}
String zeriData(int n) { //FORMATO DATA CON 0 DAVANTI ANCHE AD 1 SOLA CIFRA SIGNIFICATIVA
sprintf(res, "%02d", n);
return String(res);
}
void tara() { //AZZERA LA BILANCIA
if (ck2 != previousS) { // se nella prima pagina menu, il tasto verde viene premuto ck2 assume valore diverso da stato--> scale.tare puoò essere eseguito
scale.tare(); // immediatamente previous assume il nuovo valore di ck2, e la condizione smette di verificarsi.
previousS = ck2;
}
}
void togli() { //attach interrupt incremento(giallo) non viene richiamata al cambio di stato del pin 2[problema]
if (timer - intervallo2 >= 180)
{
intervallo2 = timer;
ck1--;
}
}
void aggiungi() {
if (timer - intervallo2 >= 180)
{
intervallo2 = timer;
ck1++;
}
}
void conferma() { //RICHIAMA LA CONFERMA DA PULSANTE VERDE
if (timer - intervallo2 >= 300)
{
ck2++;
intervallo2 = timer;
}
}
void menu() { // RICHIAMA VARIABILE MENU'
if (timer - intervallo2 >= 300)
{
ck3++;
intervallo2 = timer;
ck2 = 1;
}
}
void pulisciSchermo() { //RICHIAMA PULIZIA COORDINATE LCD
lcd.print(" ");
}
void grammi () { //RICHIAMA STAMPA DEL PESO MISURATO
previousS = ck2;
lcd.setCursor(11, 0);
lcd.print("(1)" );
lcd.setCursor(0, 1);
lcd.print("*Verde per Azzerare*" );
lcd.setCursor(0, 2);
lcd.print("");
lcd.setCursor(5, 3);
lcd.print("Peso:" + String(units, 0) + "g.");
pulisciSchermo();
tara(); //RICHIAMA TARA IN MODALITA' IMPULSO ALL'INTERNO DELLA FUNZIONE
}
void riempimento() { //RICHIAMA IMPOSTAZIONE RIEMPIMENTO CIOTOLA[problema]selezione orario
DateTime now = rtc.now();
lcd.setCursor(11, 0);
lcd.print("(2)" );
if (ck2 == 1) {
if (load < 24)
load = 0;
statoO = 3; // OGNI VOLTA CHE IL MENU TORNA IN PAGINA PRINCIPALE, INDIPENDENTEMENTE DAI VALORI ASSUNTI DALLE VARIABILI DI STATO NEL PROGRAMMA
statoM = 4; // TORNANO AL LORO VALORE DI ORIGINE
lcd.setCursor(2, 1);
lcd.print("**Rifornimento**");
pulisciSchermo();
lcd.setCursor(0, 3);
lcd.print("Quantita':");
lcd.setCursor(10, 3);
lcd.print(String(load / 2) + "g.");
lcd.print(" ");
ck1 = now.hour();
}
if (ck2 == 2) {
tempLoad = load / 2;
lcd.setCursor(2, 1);
lcd.print("**Rifornimento**");
lcd.setCursor(0, 3); //questa condizione dovrebbe essere nella funzione interrupt per evitare incongruenze durante l'esecuzione di istruzioni esterne, ma in tal caso
lcd.print("Orario:");
lcd.setCursor(8, 3);
if (timer - intervallo >= 100) {
intervallo = timer;
lcd.print(String(zeriData(ck1)) + ":" + String(zeriData(now.minute())));
}
pulisciSchermo();
if (ck1 < 0)
ck1 = 23; // il limite di ck1 per minuti != da limite di ck1 per ore.(non può più essere simultaneamente limitato a 24 (ore) e 60 (minuti) ) lcd.setCursor(14, 1);
if (ck1 > 23)
ck1 = 0;
}
if (ck2 == 3) {
if (statoO == 3) { //SALVATAGGIO VARIABILE ORE APPENA IMPOSTATA DALL'UTENTE TRAMITE TASTO VERDE DI CONFERMA CHE AVVIENE CON IL SUPPORTO DI UNA VARIABILE DI STATO
tempO = ck1; //ALTRIMENTI VERREBBE ESEGUITA L'OPERAZIONE DI SALVATAGGIO A LOOP, SOVVRESCRIVENDO CONTINUAMENTE IL VALORE VAR ck1 CHE A QUESTO PUNTO DEVE INVECE
ck1 = now.minute(); //DARE POSSIBILITA' ALL'UTENTE DI SCRORRERE I VALORI DA 0-60 PER SALVARE VARIABILE MINUTI
statoO = 4;
Serial.println("una volta");
Serial.println(ck2);
}
lcd.setCursor(2, 1);
lcd.print("**Rifornimento**");
lcd.setCursor(0, 3);
lcd.print("Orario:");
lcd.setCursor(8, 3);
lcd.print(String(zeriData(tempO)) + ":" );
if (timer - intervallo >= 100) {
intervallo = timer;
lcd.setCursor(11, 3);
lcd.print(zeriData(ck1));
}
pulisciSchermo();
if (ck1 < 0 )
ck1 = 59;
if (ck1 > 59)
ck1 = 0;
}
if (ck2 == 4)
{
if (statoM == 4) {
tempM = ck1;
ck1 = 0;
statoM = 5;
}
lcd.setCursor(2, 1);
lcd.print("**Ora Impostata**");
lcd.setCursor(0, 3);
lcd.print("Orario:");
lcd.setCursor(8, 3);
lcd.print(String(zeriData(tempO)) + ":" + String(zeriData(tempM)));
pulisciSchermo();
if (timer - intervallo2 >= 3000)
{
if (statoM == 5)
{ lcd.clear();
statoM = 6;
ck2 = 1;
}
intervallo2 = timer;
}
if (ck2 > 4)
ck2 = 1;
if (ck2 >= 2 && ck3 == 3)
ck2 = 1;
}
}
void calibrazione() { //RICHIAMA LA CALIBRAZIONE PER CALIBRARE LA BILANCIA[da impostare su lcd]
lcd.setCursor(11, 0);
lcd.print("(3)" );
lcd.setCursor(0, 2);
lcd.print("Peso:" + String(units, 0) + "g.");
pulisciSchermo();
if (load <= 511) {
load = load - 511;
lcd.setCursor(0, 1);
lcd.print("Togli: " );
pulisciSchermo();
lcd.setCursor(13, 1);
lcd.print(load / 4);
pulisciSchermo();
if (ck2 == 2) {
ck2 = 1;
calibration_factor = calibration_factor + load / 4;
}
}
if (load > 511)
{
load = load - 511;
lcd.setCursor(0, 1);
lcd.print("Aggiungi: ");
lcd.setCursor(13, 1);
lcd.print("+" + String(load / 4));
pulisciSchermo();
if (ck2 == 2)
{
ck2 = 1;
calibration_factor = calibration_factor + load / 4;
}
}
lcd.setCursor(0, 3);
lcd.print("Calibrazione:" + String(calibration_factor, 0));
pulisciSchermo();
if (ck2 != 1 && ck3 == 3)
ck2 = 1;
}