// ------------------------- VARIABILI TIMER0 DEL SISTEMA -------------------------
volatile uint16_t mytick = 0;
static uint16_t tick_start = 0;
static uint16_t tick_startCaldaia = 0;
static uint16_t tick_startServo = 0;
static uint8_t TCNT0_start, elapsedTime;
static uint8_t TCNT0_start2, elapsedTime2;
// ------------------------- VARIABILI GESTIONE POTENZIOMETRO -------------------------
struct TypeFloatCustomizedPositive {
uint8_t integerPart;
uint8_t decimalPart;
}; TypeFloatCustomizedPositive Tb;
volatile uint8_t adc_value = 0;
static uint8_t PV_adc_value = 1; // lo Inizializzo ad 1 almeno anche appena accendo il Sistema so che Tb viene Settato
volatile bool adc_ready = false;
// ------------------------- VARIABILI GESTIONE DHT22_1 -------------------------
struct TypeFloatCustomized {
uint8_t sign; // 0 se negativo, 1 se positivo
uint8_t integerPart;
uint8_t decimalPart;
}; TypeFloatCustomized Tr1;
#define DHT_PIN1 2 // Rilevatore Stanza 1
static uint8_t data[5];
static uint16_t rawTemperature;
static bool negative, error_checksum = false;
static uint8_t checksum;
volatile bool pinSignalChanged = false;
static enum { WAKE_UP_SENSOR, REQUEST, RESPONSE1, RESPONSE2, RESPONSE3, RESPONSE4, READ_BIT_START, READ_BIT_END } state = WAKE_UP_SENSOR;
static uint8_t bitIndex = 0;
static uint8_t byteIndex = 0;
// ------------------------- VARIABILI GESTIONE DHT22_2 -------------------------
TypeFloatCustomized Tr2;
#define DHT_PIN2 3 // Rilevatore Stanza 2
static uint8_t data2[5];
static uint16_t rawTemperature2;
static bool negative2, error_checksum2 = false;
static uint8_t checksum2;
volatile bool pinSignalChanged2 = false;
static enum { WAKE_UP_SENSOR2, REQUEST2, RESPONSE1_2, RESPONSE2_2, RESPONSE3_2, RESPONSE4_2, READ_BIT_START2, READ_BIT_END2 } state2 = WAKE_UP_SENSOR2;
static uint8_t bitIndex2 = 0;
static uint8_t byteIndex2 = 0;
// ------------------------- VARIABILE STATO CALDAIA -------------------------
static bool Caldaia_ON = false;
// ---------------------- VARIABILI SERVOMOTORI ----------------------
#define SERVO_PIN1 9 // ServoMotore 1
#define SERVO_PIN2 10 // ServoMotore 2
static uint8_t gradi1 = 0, gradi2 = 0;
void setup()
{
// Inizializzo la comunicazione seriale
Serial.begin(9600);
// Imposto il Timer 0 in modalità CTC e il Prescaler a 64
TCCR0A = (1 << WGM01);
TCCR0B = (1 << CS00) | (1 << CS01);
// Imposto l'Interrupt quando TCNT0 raggiunge OCR0A
TIMSK0 |= (1 << OCIE0A);
// Imposto OCR0A
OCR0A = 249; // Questo valore insieme al prescaler 64 darà un interrupt ogni 1ms
// POTENZIOMETRO
ADMUX = (1 << REFS0) | (1 << ADLAR); // Riferimento AVcc e left adjust (8 bit)
ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1); // Abilito l'ADC, l'interrupt ADC e imposto il prescaler a 64
// DHT22_1
// Configuro INT0 per attivarsi su qualsiasi cambiamento di stato del DHT_PIN1
EICRA |= (1 << ISC00); // Setto ISC00
EIMSK |= (1 << INT0); // Abilito l'interrupt per INT0
// DHT22_2
// Configuro INT1 per attivarsi su qualsiasi cambiamento di stato del DHT_PIN2
EICRA |= (1 << ISC10); // Setto ISC10
EIMSK |= (1 << INT1); // Abilito l'interrupt per INT1
// SERVO MOTORI
// Imposto i pin 9 (PB1) e 10 (PB2) come uscite
DDRB |= (1 << PB1) | (1 << PB2); // PB1 è il pin 9, PB2 è il 10 su Arduino Nano
// Configuro il Timer1
TCCR1A = 0; // Resetta TCCR1A
TCCR1B = 0; // Resetta TCCR1B
TCNT1 = 0; // Resetta il contatore
// Imposto il valore del registro ICR1 per ottenere una frequenza di 50Hz
ICR1 = 39999; // (16*10^6) / (50*8) - 1 = 39999
// Imposto i registri per il PWM su OC1A (Pin 9) e OC1B (Pin 10)
TCCR1A |= (1 << WGM11); // Modalità di Fast PWM con ICR1 come top
TCCR1B |= (1 << WGM13) | (1 << WGM12); // Modalità di Fast PWM con ICR1 come top
TCCR1A |= (1 << COM1A1) | (1 << COM1B1); // Non-inverting mode su OC1A e OC1B
// Avvio il timer1 con prescaler di 8
TCCR1B |= (1 << CS11);
// Inizializzo i Servo Motori per avere una apertura di 0°
OCR1A = 912;
OCR1B = 912;
// Abilito gli interrupt globali
sei();
}
void loop()
{
// OGNI DUE SECONDI GESTISCO POTENZIOMETRO (Tb) E I DHT22 (Tr1 e Tr2)
if (mytick - tick_start >= 2000) {
tick_start = mytick;
// Potenziometro
// Avvio una conversione ADC
ADCSRA |= (1 << ADSC);
adc_ready = false;
// DHT22_1
// Richiesta di lettura al DHT22_1
DDRD |= (1 << DHT_PIN1); // Configuro il pin come output
PORTD &= ~(1 << DHT_PIN1); // Porto il segnale a LOW
state = REQUEST;
// DHT22_2
// Richiesta di lettura al DHT22_2
DDRD |= (1 << DHT_PIN2); // Configuro il pin come output
PORTD &= ~(1 << DHT_PIN2); // Porto il segnale a LOW
state2 = REQUEST2;
}
// SE LA CONVERSIONE ADC è CONCLUSA SFRUTTO IL VALORE ADC e controllo se è cambiato rispetto a prima;
// se è effettivamente stato mosso il potenziometro, ricalcolo Tb
if (adc_ready) {
adc_ready = false; // Resetto il flag dopo aver utilizzato il valore
if (adc_value != PV_adc_value) { // Se il Potenziometro è stato mosso dall'utente ricalcolo Tb
PV_adc_value = adc_value;
Tb.integerPart = (10 * adc_value) / 255 + 16; // Parte intera della conversione
Tb.decimalPart = (((10 * adc_value) % 255) * 10) / 255; // Parte decimale della conversione scalata su 10
// in questo modo Tb può avere solo una cifra decimale, proprio come Tr1 e Tr2, ottimo per i confronti
}
}
// GESTIONE DHT22_1
switch (state)
{
case REQUEST:
// Attendo 20ms per poi fare la richiesta
if (mytick - tick_start >= 20) {
DDRD &= ~(1 << DHT_PIN1); // Configuro il pin come input
state = RESPONSE1;
}
break;
// Alternarsi di Segnali Alti e Bassi da parte del Sensore per iniziare la Trasmissione
case RESPONSE1:
if (pinSignalChanged) {
pinSignalChanged = false;
state = RESPONSE2;
}
break;
case RESPONSE2:
if (pinSignalChanged) {
pinSignalChanged = false;
state = RESPONSE3;
}
break;
case RESPONSE3:
if (pinSignalChanged) {
pinSignalChanged = false;
state = RESPONSE4;
}
break;
case RESPONSE4:
if (pinSignalChanged) {
pinSignalChanged = false;
byteIndex = 0;
bitIndex = 0;
memset(data, 0, sizeof(data)); // Setto a 0 il vettore data
state = READ_BIT_START;
}
break;
// Fine dell'alternarsi di Segnali Alti e Bassi da parte del Sensore per iniziare la Trasmissione
// Inizio della Trasmissione dei 40 bit di Informazione
case READ_BIT_START:
// Inizia la lettura del bit
if (PIND & (1 << DHT_PIN1)) {
TCNT0_start = TCNT0; // Inizio la misurazione del tempo di segnale Alto
state = READ_BIT_END;
}
break;
case READ_BIT_END:
// Completa la lettura del bit
if (!(PIND & (1 << DHT_PIN1))) {
data[byteIndex] <<= 1; // Sposto i bit esistenti a sinistra per fare spazio al nuovo bit
// Gestione dell'Overflow di TCNT0
if (TCNT0 >= TCNT0_start) {
elapsedTime = TCNT0 - TCNT0_start;
} else {
elapsedTime = (256 - TCNT0_start) + TCNT0;
}
if (elapsedTime > 14) { // Se HIGH è durato più di circa 56us
data[byteIndex] |= 1; // metto il nuovo bit ad 1 (è 0 di default)
}
bitIndex++;
if (bitIndex >= 8) {
bitIndex = 0;
byteIndex++;
}
if (byteIndex >= 5) { // se il sensore ha trasmesso 40 bit
checksum = data[0]+data[1]+data[2]+data[3];
if (checksum == data[4]) {
rawTemperature = (data[2] << 8) | data[3];
negative = rawTemperature & 0x8000;
if (negative) {
rawTemperature &= 0x7FFF; // Rimuovo il bit di segno
}
Tr1.sign = negative ? 0 : 1;
Tr1.integerPart = rawTemperature / 10;
Tr1.decimalPart = rawTemperature % 10;
state = WAKE_UP_SENSOR;
} else {
error_checksum = true;
state = WAKE_UP_SENSOR;
}
} else {
state = READ_BIT_START;
}
}
break;
}
// GESTIONE DHT22_2
switch (state2) {
case REQUEST2:
if (mytick - tick_start >= 20) {
DDRD &= ~(1 << DHT_PIN2); // Configuro il pin come input
state2 = RESPONSE1_2;
}
break;
// Alternarsi di Segnali Alti e Bassi da parte del Sensore per iniziare la Trasmissione
case RESPONSE1_2:
if (pinSignalChanged2) {
pinSignalChanged2 = false;
state2 = RESPONSE2_2;
}
break;
case RESPONSE2_2:
if (pinSignalChanged2) {
pinSignalChanged2 = false;
state2 = RESPONSE3_2;
}
break;
case RESPONSE3_2:
if (pinSignalChanged2) {
pinSignalChanged2 = false;
state2 = RESPONSE4_2;
}
break;
case RESPONSE4_2:
if (pinSignalChanged2) {
pinSignalChanged2 = false;
byteIndex2 = 0;
bitIndex2 = 0;
memset(data2, 0, sizeof(data2)); // Setto a 0 il vettore data
state2 = READ_BIT_START2;
}
break;
// Fine dell'alternarsi di Segnali Alti e Bassi da parte del Sensore per iniziare la Trasmissione
// Inizio della Trasmissione dei 40 bit di Informazione
case READ_BIT_START2:
if (PIND & (1 << DHT_PIN2)) {
TCNT0_start2 = TCNT0; // Inizio la misurazione del tempo di segnale Alto
state2 = READ_BIT_END2;
}
break;
case READ_BIT_END2:
if (!(PIND & (1 << DHT_PIN2))) {
data2[byteIndex2] <<= 1; // Sposto i bit esistenti a sinistra per fare spazio al nuovo bit
// Gestione dell'Overflow di TCNT0
if (TCNT0 >= TCNT0_start2) {
elapsedTime2 = TCNT0 - TCNT0_start2;
} else {
elapsedTime2 = (256 - TCNT0_start2) + TCNT0;
}
if (elapsedTime2 > 14) { // Se HIGH è durato più di circa 56us
data2[byteIndex2] |= 1; // metto il nuovo bit ad 1 (è 0 di default)
}
bitIndex2++;
if (bitIndex2 >= 8) {
bitIndex2 = 0;
byteIndex2++;
}
if (byteIndex2 >= 5) { // se il sensore ha trasmesso 40 bit
checksum2 = data2[0] + data2[1] + data2[2] + data2[3];
if (checksum2 == data2[4]) {
rawTemperature2 = (data2[2] << 8) | data2[3];
negative2 = rawTemperature2 & 0x8000;
if (negative2) {
rawTemperature2 &= 0x7FFF; // Rimuovo il bit di segno
}
Tr2.sign = negative2 ? 0 : 1;
Tr2.integerPart = rawTemperature2 / 10;
Tr2.decimalPart = rawTemperature2 % 10;
state2 = WAKE_UP_SENSOR2;
} else {
error_checksum2 = true;
state2 = WAKE_UP_SENSOR2;
}
} else {
state2 = READ_BIT_START2;
}
}
break;
}
// MODULO ACCENSIONE CALDAIA
if (mytick - tick_startCaldaia >= 2500) { // faccio passare un pelo di più di 2 secondi almeno sono sicuro che Tb, Tr1 e Tr2 siano aggiornate
tick_startCaldaia = tick_start; // faccio tick_startCaldaia = tick_start in modo da non aumentare sempre di più la distanza tra l'aggiornamento di Tr1, 2 e Tb e il controllo della Caldaia
// LOGICA ACCENSIONE CALDAIA
if (Caldaia_ON == false) {
// Controllo se Tr1 < Tb * 0.9 o Tr2 < Tb * 0.9 senza usare float
if ((Tr1.integerPart * 10 + Tr1.decimalPart) < ((Tb.integerPart * 10 + Tb.decimalPart) * 9 / 10) ||
(Tr2.integerPart * 10 + Tr2.decimalPart) < ((Tb.integerPart * 10 + Tb.decimalPart) * 9 / 10)) {
Caldaia_ON = true;
}
} else {
// Controllo se Tr1 >= Tb e Tr2 >= Tb
if ((Tr1.integerPart * 10 + Tr1.decimalPart) >= (Tb.integerPart * 10 + Tb.decimalPart) &&
(Tr2.integerPart * 10 + Tr2.decimalPart) >= (Tb.integerPart * 10 + Tb.decimalPart)) {
Caldaia_ON = false;
}
}
}
// MODULO GESTIONE SERVI
if (mytick - tick_startServo >= 2800) { // faccio passare un pelo di più di 2.5 secondi almeno sono sicuro che Stato Caldaia, Tb, Tr1 e Tr2 siano aggiornate
tick_startServo = tick_start; // faccio tick_startCaldaia = tick_start in modo da non aumentare sempre di più la distanza tra l'esecuzione di questo modulo e gli altri
if (Caldaia_ON == false) { // Se la Caldaia è spenta entrambe le Valvole sono aperte a 0°
OCR1A = 912;
OCR1B = 912;
}
if (Caldaia_ON) {
gradi1 = 0;
if ((Tb.integerPart * 10 + Tb.decimalPart) - (Tr1.integerPart * 10 + Tr1.decimalPart) > 0) { // se diff <= 0 i gradi di apertura sono 0
if (((Tb.integerPart * 10 + Tb.decimalPart) - (Tr1.integerPart * 10 + Tr1.decimalPart)) >= ((Tb.integerPart * 10 + Tb.decimalPart) * 2 / 10)) {
gradi1 = 180;
} else {
gradi1 = (((Tb.integerPart * 10 + Tb.decimalPart) - (Tr1.integerPart * 10 + Tr1.decimalPart)) * 180) / ((Tb.integerPart * 10 + Tb.decimalPart) * 2 / 10);
}
}
gradi2 = 0;
if ((Tb.integerPart * 10 + Tb.decimalPart) - (Tr2.integerPart * 10 + Tr2.decimalPart) > 0) { // se diff <= 0 i gradi di apertura sono 0
if (((Tb.integerPart * 10 + Tb.decimalPart) - (Tr2.integerPart * 10 + Tr2.decimalPart)) >= ((Tb.integerPart * 10 + Tb.decimalPart) * 2 / 10)) {
gradi2 = 180;
} else {
gradi2 = (((Tb.integerPart * 10 + Tb.decimalPart) - (Tr2.integerPart * 10 + Tr2.decimalPart)) * 180) / ((Tb.integerPart * 10 + Tb.decimalPart) * 2 / 10);
}
}
// Valore di OCR1A per 0 gradi di apertura = 912
// Valore di OCR1A per 180 gradi di apertura = 4976
OCR1A = (9120000 + 225778 * gradi1) / 10000;
OCR1B = (9120000 + 225778 * gradi2) / 10000;
// Debug print che viene stampato solo se la Caldaia è Accesa
Serial.print("Gradi1: ");
Serial.println(gradi1);
Serial.print("OCR1A: ");
Serial.println(OCR1A);
Serial.print("Gradi2: ");
Serial.println(gradi2);
Serial.print("OCR1B: ");
Serial.println(OCR1B);
}
// STAMPO I VALORI ALLA FINE DEL CICLO TOTALE
Serial.print("Tb: ");
Serial.print(Tb.integerPart);
Serial.print(".");
Serial.print(Tb.decimalPart);
Serial.println(" °C");
if(error_checksum) {
error_checksum = false;
Serial.println("Tr1: ERRORE DI CHECKSUM");
} else {
Serial.print("Tr1: ");
if (Tr1.sign == 0) {
Serial.print("-");
}
Serial.print(Tr1.integerPart);
Serial.print(".");
Serial.print(Tr1.decimalPart);
Serial.println(" °C");
}
if(error_checksum2) {
error_checksum2 = false;
Serial.println("Tr2: ERRORE DI CHECKSUM");
} else {
Serial.print("Tr2: ");
if (Tr2.sign == 0) {
Serial.print("-");
}
Serial.print(Tr2.integerPart);
Serial.print(".");
Serial.print(Tr2.decimalPart);
Serial.println(" °C");
}
// Stampo lo stato della caldaia
Serial.print("Caldaia ON: ");
Serial.println(Caldaia_ON ? "True" : "False");
Serial.print("tick_start: ");
Serial.println(tick_start);
Serial.println("");
}
}
ISR(TIMER0_COMPA_vect)
{
mytick++;
}
ISR(INT0_vect) {
pinSignalChanged = true;
}
ISR(INT1_vect) {
pinSignalChanged2 = true;
}
ISR(ADC_vect) {
adc_value = ADCH; // Leggo il valore dell'ADC (8 bit)
adc_ready = true; // Setto il flag per indicare che il valore è pronto
}