Uscita digitale dell’ATmega328P

I terminali di ingresso e uscita dell’ATmega328P sono programmabili. Prima di utilizzare un pin, il programma deve stabilire se deve funzionare come ingresso oppure come uscita.

Quando un pin viene configurato come uscita digitale, il microcontrollore può portarlo in uno dei due stati disponibili:

  • HIGH: 5 V;
  • LOW: 0 V.

Lo stato dell’uscita rimane invariato finché il programma non lo modifica. Se il programma imposta HIGH, il pin rimane a 5 V; se successivamente imposta LOW, il pin passa a 0 V e rimane in tale condizione fino a un nuovo comando.

Un’uscita digitale non genera valori intermedi. Non può essere impostata direttamente, per esempio, a 2 V oppure a 3,5 V: può assumere soltanto lo stato alto o lo stato basso.

La commutazione può avvenire una sola volta oppure essere ripetuta nel tempo. Alternando periodicamente HIGH e LOW si produce una sequenza come questa:

5 V → 0 V → 5 V → 0 V

La durata di ciascuno stato viene determinata dal programma. Se il livello alto dura un secondo e quello basso dura un secondo, l’intera sequenza si ripete ogni due secondi.

Questo principio verrà utilizzato prima per accendere e spegnere un LED e successivamente per comandare lo stadio elettronico collegato alla bobina di un relè.

Corrente dei pin e limiti elettrici

Un pin dell’ATmega328P può fornire o assorbire soltanto una corrente limitata.

Con alimentazione a 5 V, il datasheet specifica le caratteristiche dei livelli logici di uscita a una corrente di 20 mA. Questo valore è quindi un riferimento operativo, non il limite assoluto del pin.

Tra gli Absolute Maximum Ratings del microcontrollore sono indicati:

  • 40 mA per ciascun pin di I/O;
  • 200 mA complessivi attraverso i pin VCC e GND.

Questi valori sono limiti assoluti di stress e non devono essere utilizzati come normali valori di progetto. Il datasheet specifica inoltre limiti cumulativi per gruppi di pin, che devono essere rispettati quando più uscite erogano o assorbono corrente contemporaneamente.

In pratica è opportuno mantenere la corrente di ciascuna uscita ben al di sotto dei limiti assoluti. Un LED può essere pilotato direttamente attraverso una resistenza dimensionata correttamente; la bobina di un relè richiede invece uno stadio di pilotaggio, per esempio con transistor.

LED esterno con resistenza

Un LED non deve essere collegato direttamente a un’uscita dell’ATmega328P. Senza una resistenza, la corrente sarebbe limitata quasi soltanto dalle caratteristiche interne del LED e del pin, con rischio di danneggiare entrambi.

Il collegamento è composto da:

  • uscita digitale dell’ATmega328P;
  • resistenza in serie;
  • anodo del LED;
  • catodo del LED collegato a GND.

L’anodo è il terminale positivo del LED; il catodo è il terminale negativo. Nei LED tradizionali, il catodo è normalmente riconoscibile dal terminale più corto e dal lato piatto dell’involucro.

Quando l’uscita è HIGH, il pin assume un livello di tensione prossimo a VCC, il cui valore effettivo dipende anche dalla corrente erogata; il LED si accende. Quando l’uscita è LOW, il pin assume un livello prossimo a 0 V e il LED si spegne.

Il valore della resistenza si calcola con:

R = (Vuscita − VLED) / ILED

Per esempio, considerando:

  • tensione dell’uscita: 5 V;
  • caduta di tensione di un LED rosso: circa 2 V;
  • corrente desiderata: 10 mA, cioè 0,01 A;

si ottiene:

R = (5 − 2) / 0,01 = 300 Ω

Si sceglie il valore commerciale immediatamente superiore:

R = 330 Ω

Con una resistenza da 330 Ω, la corrente è circa:

I = (5 − 2) / 330 ≈ 9 mA

Questo valore è sufficiente per ottenere una buona luminosità e rimane inferiore alla corrente di 20 mA alla quale il datasheet specifica le caratteristiche del livello logico di uscita con alimentazione a 5 V.

La resistenza può essere collegata prima oppure dopo il LED, purché sia sempre in serie. Una resistenza da ¼ W è più che sufficiente per questo circuito.

Variabili e nomi utilizzati nel programma

Una variabile è uno spazio di memoria al quale viene assegnato un nome. Il programma utilizza quel nome per leggere, modificare e confrontare il valore conservato.

Una dichiarazione contiene almeno:

  • il tipo della variabile;
  • il nome della variabile.

Può inoltre contenere un valore iniziale.

Per esempio:

uint32_t millisecondi = 0;

In questa dichiarazione:

  • uint32_t è il tipo;
  • millisecondi è il nome;
  • 0 è il valore iniziale.

Il tipo stabilisce quali valori possono essere memorizzati e quanta memoria viene occupata. Nel programma verranno utilizzati principalmente:

  • uint8_t: numero intero senza segno da 8 bit;
  • uint32_t: numero intero senza segno da 32 bit.

Il nome può essere scelto dal programmatore, rispettando alcune regole:

  • può contenere lettere, numeri e il carattere _;
  • non può contenere spazi;
  • non può iniziare con un numero;
  • non può coincidere con una parola riservata del linguaggio, come int, while, if o return;
  • distingue le lettere maiuscole dalle minuscole.

Questi sono nomi validi:

millisecondi
ultimo_cambio_led
led_acceso
valore1

Questi nomi non sono validi:

ultimo cambio
1valore
int
stato-led

led_acceso, Led_acceso e LED_ACCESO sono tre nomi differenti.

Il nome dovrebbe descrivere chiaramente il contenuto o la funzione della variabile. Per esempio:

uint8_t led_acceso = 0;

indica immediatamente che la variabile rappresenta lo stato del LED.

Sarebbe sintatticamente valido scrivere:

uint8_t pippo = 0;

Un vezzo diffuso tra i programmatori italiani consiste infatti nell’utilizzare pippo come nome generico durante prove, esempi o verifiche rapide. In ambito internazionale vengono spesso usati allo stesso modo nomi come foo e bar.

pippo non spiega però che cosa contiene la variabile. In un programma reale è quindi preferibile utilizzare:

uint8_t led_acceso = 0;

anziché:

uint8_t pippo = 0;

Lo stesso criterio vale per tutte le variabili:

uint32_t ultimo_cambio_led = 0;
uint32_t tempo_attuale = 0;
uint8_t led_acceso = 0;

I nomi rendono riconoscibile il significato dei valori senza dover ricostruire ogni volta il funzionamento del programma.

Le definizioni introdotte con #define, invece, non sono variabili:

#define LED_BIT PB0
#define INTERVALLO_LED_MS 500UL

Sono sostituzioni definite prima della compilazione. Nel programma:

  • LED_BIT identifica il bit utilizzato per il LED;
  • INTERVALLO_LED_MS rappresenta l’intervallo di 500 millisecondi.

Anche per queste definizioni si utilizzano nomi descrittivi. La scrittura in lettere maiuscole permette di distinguerle visivamente dalle variabili.

Timer e gestione del tempo

Un timer è un circuito interno del microcontrollore che conta gli impulsi prodotti dall’orologio di sistema.

L’ATmega328P lavora, nel caso considerato, a una frequenza di:

16 MHz

Ciò significa che l’orologio genera:

16 000 000

impulsi ogni secondo.

Contare direttamente tutti questi impulsi non sarebbe pratico. Per questo il timer può utilizzare un prescaler, cioè un divisore della frequenza.

Con un prescaler pari a 64:

16 000 000 / 64 = 250 000

Il timer riceve quindi 250.000 impulsi al secondo.

La durata di ogni impulso ricevuto dal timer è:

1 / 250 000 = 0,000004 s

cioè:

4 µs

Per ottenere un intervallo di un millisecondo servono 250 impulsi:

250 × 4 µs = 1 ms

Il contatore parte da zero. Per eseguire 250 conteggi assume i valori da 0 a 249. Per questo il valore di confronto viene impostato a:

OCR1A = 249;

I timer dell’ATmega328P

L’ATmega328P dispone di tre timer:

  • Timer0, a 8 bit;
  • Timer1, a 16 bit;
  • Timer2, a 8 bit.

Un timer a 8 bit può contare da 0 a 255.

Un timer a 16 bit può contare da 0 a 65.535.

Nel programma viene utilizzato Timer1, perché è un timer a 16 bit e permette di gestire con maggiore flessibilità il conteggio e i valori di confronto.

Il contatore del timer

Timer1 possiede un registro chiamato:

TCNT1

Questo registro contiene il valore corrente del conteggio.

Il valore aumenta automaticamente ogni volta che il timer riceve un impulso. Il programma non deve incrementarlo manualmente.

Per iniziare il conteggio da zero si scrive:

TCNT1 = 0;

Il valore di confronto

Il registro:

OCR1A

contiene il valore con il quale viene confrontato il contatore.

Nel programma:

OCR1A = 249;

Quando TCNT1 raggiunge 249, si verifica un evento di confronto.

Il timer viene configurato in modalità CTC, abbreviazione di Clear Timer on Compare Match. In questa modalità, quando il contatore raggiunge il valore di confronto:

  1. viene generato l’evento di confronto;
  2. il contatore viene riportato automaticamente a zero;
  3. inizia un nuovo intervallo.

La sequenza è quindi:

0 → 1 → 2 → ... → 248 → 249 → 0

Questa sequenza dura un millisecondo e si ripete continuamente.

L’interruzione del timer

Quando Timer1 raggiunge il valore contenuto in OCR1A, può richiedere l’esecuzione immediata di una funzione chiamata routine di interruzione.

Nel programma la routine è:

ISR(TIMER1_COMPA_vect)
{
    millisecondi++;
}

TIMER1_COMPA_vect identifica l’interruzione prodotta dal confronto A di Timer1.

Ogni volta che trascorre un millisecondo, la routine aumenta di uno la variabile:

millisecondi

La variabile può quindi assumere valori come:

0
1
2
3
...
500
...
1000

Il valore rappresenta i millisecondi trascorsi dall’avvio del programma.

Dopo 500 millisecondi:

millisecondi = 500

Dopo un secondo:

millisecondi = 1000

Dopo cinque secondi:

millisecondi = 5000

Variabili utilizzate per controllare il tempo

Per far cambiare stato al LED ogni 500 millisecondi servono tre valori.

Il tempo corrente:

uint32_t tempo_attuale;

Il momento dell’ultimo cambiamento:

uint32_t ultimo_cambio_led;

L’intervallo richiesto:

#define INTERVALLO_LED_MS 500UL

I nomi descrivono direttamente il contenuto:

  • tempo_attuale: tempo trascorso dall’avvio;
  • ultimo_cambio_led: momento dell’ultima variazione del LED;
  • INTERVALLO_LED_MS: durata richiesta tra due variazioni.

Il tempo trascorso dall’ultima variazione viene calcolato con:

tempo_attuale - ultimo_cambio_led

Per esempio:

tempo_attuale = 1300 ms
ultimo_cambio_led = 1000 ms

Il tempo trascorso è:

1300 ms − 1000 ms = 300 ms

L’intervallo di 500 millisecondi non è ancora terminato.

Successivamente:

tempo_attuale = 1500 ms
ultimo_cambio_led = 1000 ms

Il tempo trascorso diventa:

1500 ms − 1000 ms = 500 ms

La condizione è quindi soddisfatta e il LED può cambiare stato.

Nel programma il controllo è:

if (tempo_attuale - ultimo_cambio_led >= INTERVALLO_LED_MS)
{
    ultimo_cambio_led = tempo_attuale;

    /* cambiamento dello stato del LED */
}

Dopo il cambiamento, la variabile:

ultimo_cambio_led

viene aggiornata con il tempo corrente. Inizia così il conteggio del nuovo intervallo.

Più intervalli con lo stesso timer

Un solo timer può fornire il riferimento temporale per più funzioni.

Per esempio:

uint32_t ultimo_cambio_led = 0;
uint32_t ultimo_cambio_rele = 0;

#define INTERVALLO_LED_MS 500UL
#define INTERVALLO_RELE_MS 5000UL

Il LED utilizza un intervallo di 500 millisecondi:

if (tempo_attuale - ultimo_cambio_led >= INTERVALLO_LED_MS)
{
    ultimo_cambio_led = tempo_attuale;

    /* cambiamento dello stato del LED */
}

Il relè utilizza un intervallo di 5000 millisecondi:

if (tempo_attuale - ultimo_cambio_rele >= INTERVALLO_RELE_MS)
{
    ultimo_cambio_rele = tempo_attuale;

    /* cambiamento dello stato del relè */
}

Le due funzioni utilizzano lo stesso valore:

tempo_attuale

ma conservano separatamente il momento dell’ultima variazione.

In questo modo:

  • il LED cambia stato ogni 500 millisecondi;
  • il relè cambia stato ogni 5000 millisecondi;
  • le due temporizzazioni rimangono indipendenti.

Programma in C

Nel programma il LED esterno è collegato all’uscita PB0 attraverso la resistenza da 330 Ω descritta nel pezzo precedente.

Il codice è scritto in C e utilizza direttamente i registri dell’ATmega328P. Il timer interno genera un riferimento temporale di un millisecondo; il LED cambia stato ogni 500 millisecondi.

#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <stdint.h>

#define LED_BIT PB0
#define INTERVALLO_LED_MS 500UL

volatile uint32_t millisecondi = 0;

ISR(TIMER1_COMPA_vect)
{
    millisecondi++;
}

uint32_t leggi_millisecondi(void)
{
    uint32_t valore;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        valore = millisecondi;
    }

    return valore;
}

void inizializza_timer1(void)
{
    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1 = 0;

    OCR1A = 249;

    TCCR1B |= (1 << WGM12);
    TCCR1B |= (1 << CS11) | (1 << CS10);

    TIMSK1 |= (1 << OCIE1A);
}

int main(void)
{
    uint32_t ultimo_cambio_led = 0;
    uint8_t led_acceso = 0;

    PORTB &= ~(1 << LED_BIT);
    DDRB |= (1 << LED_BIT);

    inizializza_timer1();
    sei();

    while (1)
    {
        uint32_t tempo_attuale = leggi_millisecondi();

        if (tempo_attuale - ultimo_cambio_led >= INTERVALLO_LED_MS)
        {
            ultimo_cambio_led = tempo_attuale;

            if (led_acceso == 0)
            {
                PORTB |= (1 << LED_BIT);
                led_acceso = 1;
            }
            else
            {
                PORTB &= ~(1 << LED_BIT);
                led_acceso = 0;
            }
        }
    }
}

Spiegazione completa del codice C

La prima istruzione definisce la frequenza di lavoro del microcontrollore:

#define F_CPU 16000000UL

F_CPU rappresenta la frequenza della CPU, pari a 16 MHz. Il suffisso UL indica che il valore è un intero senza segno di tipo unsigned long.

Le direttive successive includono le definizioni necessarie:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <stdint.h>

avr/io.h contiene i nomi dei registri e dei bit dell’ATmega328P, come DDRB, PORTB, TCCR1B e PB0.

avr/interrupt.h fornisce gli strumenti per gestire le interruzioni, compresi ISR() e sei().

util/atomic.h permette di leggere in modo sicuro una variabile condivisa tra il programma principale e una routine di interruzione.

stdint.h definisce tipi con dimensione precisa, come uint8_t e uint32_t.

Le due costanti successive definiscono il pin utilizzato e l’intervallo di lampeggio:

#define LED_BIT PB0
#define INTERVALLO_LED_MS 500UL

LED_BIT indica il bit PB0 della porta B.

INTERVALLO_LED_MS stabilisce che il LED cambi stato ogni 500 millisecondi. Rimane quindi acceso per mezzo secondo e spento per mezzo secondo.

La variabile:

volatile uint32_t millisecondi = 0;

contiene il numero di millisecondi trascorsi dall’avvio del programma.

uint32_t è un intero senza segno da 32 bit.

volatile informa il compilatore che il valore può cambiare in qualsiasi momento, perché viene modificato dalla routine di interruzione. Senza volatile, il compilatore potrebbe conservare una copia del valore e non rilevare correttamente gli aggiornamenti.

La routine:

ISR(TIMER1_COMPA_vect)
{
    millisecondi++;
}

viene eseguita automaticamente ogni volta che Timer1 raggiunge il valore impostato nel registro OCR1A.

TIMER1_COMPA_vect è il vettore associato all’interruzione di confronto A di Timer1.

A ogni interruzione:

millisecondi++;

incrementa di uno il contatore del tempo. Poiché l’interruzione avviene ogni millisecondo, il valore della variabile corrisponde al tempo trascorso espresso in millisecondi.

La funzione:

uint32_t leggi_millisecondi(void)

restituisce una copia sicura del contatore.

La dichiarazione:

uint32_t valore;

crea una variabile locale nella quale verrà copiato il contenuto di millisecondi.

La copia viene eseguita all’interno di:

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
    valore = millisecondi;
}

L’ATmega328P è un microcontrollore a 8 bit e non può leggere una variabile da 32 bit con una sola operazione. Durante la lettura dei quattro byte potrebbe verificarsi un’interruzione e modificare il valore prima che la copia sia completa.

Il blocco atomico impedisce temporaneamente l’esecuzione delle interruzioni, copia il valore e ripristina automaticamente lo stato precedente.

La funzione termina con:

return valore;

che restituisce la copia al punto del programma che ha chiamato la funzione.

La funzione:

void inizializza_timer1(void)

configura Timer1.

Le prime istruzioni azzerano i registri principali:

TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;

TCCR1A e TCCR1B sono i registri di controllo di Timer1.

TCNT1 contiene il valore corrente del conteggio. Impostandolo a zero, il timer parte dall’inizio.

Il registro:

OCR1A = 249;

contiene il valore con il quale viene confrontato il contatore.

La CPU lavora a 16 MHz. Con un prescaler pari a 64, Timer1 riceve:

16 000 000 / 64 = 250 000 impulsi al secondo

Ogni incremento del timer dura quindi:

1 / 250 000 = 0,000004 secondi

cioè 4 microsecondi.

Il timer conta da 0 a 249, quindi esegue 250 conteggi:

250 × 4 µs = 1 ms

La modalità CTC viene attivata con:

TCCR1B |= (1 << WGM12);

WGM12 è il bit che seleziona la modalità Clear Timer on Compare Match. Quando il contatore raggiunge OCR1A, viene automaticamente riportato a zero.

L’espressione:

1 << WGM12

crea un valore nel quale è impostato soltanto il bit corrispondente a WGM12.

L’operatore:

|=

imposta quel bit senza modificare gli altri bit del registro.

Il prescaler viene impostato a 64 con:

TCCR1B |= (1 << CS11) | (1 << CS10);

I bit CS11 e CS10 selezionano il divisore della frequenza del timer.

L’interruzione di confronto viene abilitata con:

TIMSK1 |= (1 << OCIE1A);

TIMSK1 è il registro che abilita le interruzioni di Timer1.

OCIE1A abilita specificamente l’interruzione associata al confronto tra TCNT1 e OCR1A.

Il programma principale inizia con:

int main(void)

main() è la funzione dalla quale parte l’esecuzione del programma.

All’interno vengono dichiarate due variabili:

uint32_t ultimo_cambio_led = 0;
uint8_t led_acceso = 0;

ultimo_cambio_led memorizza il momento dell’ultima variazione del LED.

led_acceso memorizza lo stato del LED:

  • 0: LED spento;
  • 1: LED acceso.

uint8_t è un intero senza segno da 8 bit ed è sufficiente per rappresentare i due stati.

Prima di configurare il pin come uscita viene impostato il livello basso:

PORTB &= ~(1 << LED_BIT);

1 << LED_BIT crea un valore con il solo bit PB0 impostato a uno.

L’operatore ~ inverte tutti i bit, ottenendo una maschera nella quale il bit PB0 vale zero e gli altri valgono uno.

L’operazione:

PORTB &=

azzera soltanto il bit PB0, lasciando invariati gli altri bit di PORTB.

Il pin viene quindi configurato come uscita:

DDRB |= (1 << LED_BIT);

DDRB è il registro che stabilisce la direzione dei pin della porta B.

Impostando a uno il bit corrispondente a PB0, il pin viene configurato come uscita.

Successivamente viene inizializzato Timer1:

inizializza_timer1();

Le interruzioni globali vengono abilitate con:

sei();

Senza questa istruzione, Timer1 potrebbe raggiungere il valore di confronto, ma la routine ISR() non verrebbe eseguita.

Il ciclo principale è:

while (1)

Il valore 1 rende la condizione sempre vera, quindi il ciclo continua per tutta la durata del funzionamento del microcontrollore.

All’inizio di ogni ripetizione viene letto il tempo corrente:

uint32_t tempo_attuale = leggi_millisecondi();

tempo_attuale contiene una copia del numero di millisecondi trascorsi.

La condizione:

if (tempo_attuale - ultimo_cambio_led >= INTERVALLO_LED_MS)

calcola il tempo trascorso dall’ultima variazione del LED.

Quando la differenza raggiunge almeno 500 millisecondi, il codice contenuto nel blocco viene eseguito.

L’istante della variazione viene aggiornato:

ultimo_cambio_led = tempo_attuale;

Successivamente viene controllato lo stato del LED:

if (led_acceso == 0)

Se il LED è spento, il bit PB0 viene portato a uno:

PORTB |= (1 << LED_BIT);

Il pin passa a HIGH e il LED si accende.

La variabile di stato viene aggiornata:

led_acceso = 1;

Se invece led_acceso non vale zero, viene eseguito il blocco else:

PORTB &= ~(1 << LED_BIT);
led_acceso = 0;

Il pin passa a LOW, il LED si spegne e la variabile torna a zero.

La sottrazione tra tempo_attuale e ultimo_cambio_led utilizza valori senza segno. In questo modo il controllo continua a funzionare correttamente anche quando il contatore da 32 bit raggiunge il proprio valore massimo e riparte da zero.

Il ciclo principale non rimane fermo durante i 500 millisecondi. Continua a essere eseguito e controlla ripetutamente se è trascorso l’intervallo stabilito.

Programma equivalente in C++

Il programma seguente produce lo stesso lampeggio del codice C: il LED esterno collegato a PB0 cambia stato ogni 500 millisecondi. Anche questa versione utilizza direttamente i registri dell’ATmega328P e Timer1.

#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <stdint.h>

constexpr uint8_t LED_BIT = PB0;
constexpr uint32_t INTERVALLO_LED_MS = 500UL;

volatile uint32_t millisecondi = 0;

ISR(TIMER1_COMPA_vect)
{
    ++millisecondi;
}

uint32_t leggi_millisecondi()
{
    uint32_t valore;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        valore = millisecondi;
    }

    return valore;
}

void inizializza_timer1()
{
    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1 = 0;

    OCR1A = 249;

    TCCR1B |= (1 << WGM12);
    TCCR1B |= (1 << CS11) | (1 << CS10);

    TIMSK1 |= (1 << OCIE1A);
}

void imposta_led(bool acceso)
{
    if (acceso)
    {
        PORTB |= (1 << LED_BIT);
    }
    else
    {
        PORTB &= ~(1 << LED_BIT);
    }
}

int main()
{
    uint32_t ultimo_cambio_led = 0;
    bool led_acceso = false;

    imposta_led(false);
    DDRB |= (1 << LED_BIT);

    inizializza_timer1();
    sei();

    while (true)
    {
        const uint32_t tempo_attuale = leggi_millisecondi();

        if (tempo_attuale - ultimo_cambio_led >= INTERVALLO_LED_MS)
        {
            ultimo_cambio_led = tempo_attuale;
            led_acceso = !led_acceso;
            imposta_led(led_acceso);
        }
    }
}

Programma equivalente con framework Wiring

Nel framework Wiring, il collegamento corrispondente a PB0 sulla UNO R3 viene indicato con il numero 8.

Il LED esterno cambia stato ogni 500 millisecondi.

const int pin_led = 8;
const unsigned long intervallo_ms = 500;

unsigned long ultimo_cambio_ms = 0;
bool led_acceso = false;

void setup()
{
    pinMode(pin_led, OUTPUT);
    digitalWrite(pin_led, LOW);
}

void loop()
{
    unsigned long tempo_attuale_ms = millis();

    if (tempo_attuale_ms - ultimo_cambio_ms >= intervallo_ms)
    {
        ultimo_cambio_ms = tempo_attuale_ms;

        if (led_acceso == false)
        {
            digitalWrite(pin_led, HIGH);
            led_acceso = true;
        }
        else
        {
            digitalWrite(pin_led, LOW);
            led_acceso = false;
        }
    }
}

Spiegazione di pinMode(), digitalWrite(), HIGH, LOW e millis()

La dichiarazione:

const int pin_led = 8;

assegna il nome pin_led al collegamento digitale 8 della scheda. Nel programma si utilizza quindi pin_led al posto del numero 8.

const indica che il valore non deve cambiare durante l’esecuzione.

La dichiarazione:

const unsigned long intervallo_ms = 500;

stabilisce che il LED deve cambiare stato ogni 500 millisecondi.

unsigned long è un tipo intero senza segno adatto a contenere i valori temporali restituiti da millis().

La variabile:

unsigned long ultimo_cambio_ms = 0;

memorizza il momento nel quale il LED ha cambiato stato l’ultima volta.

La variabile:

bool led_acceso = false;

memorizza lo stato del LED:

  • false: LED spento;
  • true: LED acceso.

pinMode()

Nel setup() viene eseguita:

pinMode(pin_led, OUTPUT);

pinMode() stabilisce il funzionamento del collegamento indicato.

Il primo argomento è il pin da configurare:

pin_led

Il secondo argomento è la modalità:

OUTPUT

OUTPUT configura il pin come uscita digitale. Dopo questa istruzione, il programma può portarlo a 5 V oppure a 0 V.

digitalWrite()

L’istruzione:

digitalWrite(pin_led, LOW);

porta inizialmente l’uscita a 0 V, mantenendo il LED spento.

digitalWrite() riceve due argomenti:

digitalWrite(pin, stato);

Il primo indica quale uscita deve essere modificata. Il secondo indica il livello da applicare.

Nel programma vengono utilizzate queste due forme:

digitalWrite(pin_led, HIGH);

e:

digitalWrite(pin_led, LOW);

HIGH e LOW

HIGH indica il livello logico alto dell’uscita. Con alimentazione a 5 V, la tensione del pin è prossima a VCC ma dipende dalla corrente erogata e dalle caratteristiche elettriche dell’uscita.

Con il collegamento utilizzato, il LED si accende.

LOW indica il livello logico basso dell’uscita. La tensione del pin è prossima a 0 V, ma il valore effettivo dipende dalla corrente assorbita dal pin.

Il LED si spegne.

HIGH e LOW non sono variabili create nel programma. Sono costanti definite dal framework Wiring.

millis()

All’interno di loop() viene eseguita:

unsigned long tempo_attuale_ms = millis();

millis() restituisce il numero di millisecondi trascorsi dall’avvio del programma.

Per esempio:

  • dopo un secondo restituisce circa 1000;
  • dopo cinque secondi restituisce circa 5000;
  • dopo un minuto restituisce circa 60000.

Il valore viene salvato nella variabile:

tempo_attuale_ms

Il programma confronta il tempo attuale con il momento dell’ultimo cambiamento:

if (tempo_attuale_ms - ultimo_cambio_ms >= intervallo_ms)

La sottrazione:

tempo_attuale_ms - ultimo_cambio_ms

calcola quanti millisecondi sono trascorsi dall’ultima variazione del LED.

Quando il risultato raggiunge almeno 500:

>= intervallo_ms

la condizione diventa vera e il LED cambia stato.

Subito dopo viene aggiornato il riferimento temporale:

ultimo_cambio_ms = tempo_attuale_ms;

Da quel momento inizia il conteggio dei successivi 500 millisecondi.

Cambiamento dello stato del LED

Il programma controlla la variabile:

if (led_acceso == false)

Se il LED risulta spento, esegue:

digitalWrite(pin_led, HIGH);
led_acceso = true;

L’uscita passa a 5 V e la variabile registra che il LED è acceso.

Se invece il LED è già acceso, viene eseguito il blocco else:

digitalWrite(pin_led, LOW);
led_acceso = false;

L’uscita passa a 0 V e la variabile registra che il LED è spento.

La sequenza si ripete ogni 500 millisecondi:

HIGH per 500 ms
LOW per 500 ms
HIGH per 500 ms
LOW per 500 ms

Il periodo completo del lampeggio è quindi di un secondo.

Relè e corrente della bobina

Un relè elettromeccanico contiene una bobina e uno o più contatti.

Quando la bobina viene alimentata, genera un campo magnetico che sposta il meccanismo interno e modifica la posizione dei contatti. Quando la bobina non è alimentata, il meccanismo ritorna nella posizione di riposo.

La tensione e la corrente della bobina non devono essere confuse con la tensione e la corrente che possono attraversare i contatti.

Per esempio, un relè può riportare:

  • bobina: 5 V;
  • corrente della bobina: 70 mA;
  • contatti: 10 A a 250 V AC.

I 10 A indicano la corrente massima commutabile dai contatti nelle condizioni specificate. Non indicano la corrente necessaria per eccitare la bobina.

La corrente richiesta dalla bobina deve essere ricavata dal datasheet del relè oppure calcolata conoscendone la resistenza:

I = V / R

Se una bobina da 5 V presenta una resistenza di 71 Ω:

I = 5 / 71 ≈ 0,070 A

La bobina assorbe quindi circa:

70 mA

Questa corrente è molto superiore ai 20 mA usati dal datasheet per specificare le caratteristiche del livello logico di uscita a 5 V e supera anche il limite assoluto di 40 mA per singolo pin. La bobina non può quindi essere collegata direttamente al pin.

L’uscita del microcontrollore deve fornire soltanto il segnale di comando. La corrente richiesta dalla bobina viene gestita da un transistor BJT NPN.

Pilotaggio del relè con BJT NPN

Per comandare la bobina del relè si utilizza come esempio un transistor BJT NPN come interruttore elettronico.

Il transistor dispone di tre terminali:

  • base;
  • collettore;
  • emettitore.

Nel circuito:

  • l’emettitore è collegato a GND;
  • il collettore è collegato a un terminale della bobina;
  • l’altro terminale della bobina è collegato a +5 V;
  • la base è comandata dall’uscita dell’ATmega328P attraverso una resistenza;
  • la massa dell’alimentazione del relè e quella del microcontrollore devono essere collegate insieme.

Quando l’uscita del microcontrollore è LOW, sulla base non circola corrente sufficiente. Il transistor è interdetto, la corrente non attraversa la bobina e il relè rimane diseccitato.

Quando l’uscita passa a HIGH, una piccola corrente entra nella base. Il transistor entra in saturazione e permette alla corrente della bobina di scorrere dal positivo dell’alimentazione verso massa:

+5 V → bobina → collettore → emettitore → GND

Il relè si eccita e i contatti cambiano posizione.

Il pin dell’ATmega328P fornisce soltanto la corrente di base. La corrente di 70 mA richiesta dalla bobina viene fornita dall’alimentazione esterna e attraversa il collettore e l’emettitore del transistor.

Il BJT viene quindi utilizzato in due soli stati:

  • interdizione: transistor spento, relè diseccitato;
  • saturazione: transistor acceso, relè eccitato.

La regione intermedia non è utile in questo circuito, perché il relè deve essere completamente eccitato oppure completamente diseccitato.

Resistenza di base, caratteristiche e calcoli del BC337

Per pilotare la bobina, come esempio, si utilizza un transistor BJT NPN BC337.

Per evitare di mescolare valori appartenenti a produttori o versioni differenti, nell’esempio si assumono le caratteristiche del BC337-25 prodotto da onsemi, in contenitore TO-92:

  • transistor bipolare NPN;
  • tensione massima collettore-emettitore VCEO: 45 V;
  • corrente continua massima di collettore IC: 800 mA;
  • potenza massima dissipabile: 625 mW, nelle condizioni termiche indicate dal produttore;
  • guadagno in corrente continuo minimo hFE: 160 per la versione BC337-25;
  • tensione massima emettitore-base VEBO: 5 V.

Il suffisso identifica la classe di guadagno. Nella famiglia onsemi:

  • BC337-16: hFE minimo 100;
  • BC337-25: hFE minimo 160;
  • BC337-40: hFE minimo 250.

Per il pilotaggio della bobina sono particolarmente importanti:

  • la corrente massima di collettore, che deve essere superiore alla corrente della bobina;
  • la tensione massima collettore-emettitore;
  • la potenza dissipabile;
  • la corrente di base necessaria per portare il transistor in saturazione.

Questi valori sono ricavati dalla documentazione tecnica ufficiale del produttore. Il calcolo della resistenza di base, invece, deriva dalla legge di Ohm e dall’impiego di un guadagno forzato prudenziale. Non viene utilizzato direttamente il valore nominale di hFE, perché varia con la corrente, la temperatura e il singolo esemplare.

Nel caso considerato, la bobina del relè funziona a 5 V e assorbe:

IC = 70 mA

Il BC337 viene utilizzato come interruttore e deve lavorare in saturazione. Per dimensionare la corrente di base si assume un guadagno forzato pari a 10:

IB = IC / 10

IB = 70 mA / 10

IB = 7 mA

Considerando:

  • uscita dell’ATmega328P a HIGH: 5 V;
  • caduta base-emettitore del BC337 in saturazione: circa 0,8 V;
  • corrente di base richiesta: 7 mA;

la resistenza di base teorica è:

RB = (5 − 0,8) / 0,007

RB = 600 Ω

Si sceglie il valore commerciale da:

RB = 560 Ω

La corrente di base effettiva diventa:

IB = (5 − 0,8) / 560

IB = 0,0075 A

IB = 7,5 mA

La corrente richiesta al pin dell’ATmega328P, pari a circa 7,5 mA, rimane ben al di sotto dei 20 mA usati dal datasheet per specificare le caratteristiche del livello logico di uscita a 5 V.

Con una corrente di collettore di 70 mA e una tensione collettore-emettitore in saturazione assunta pari a 0,2 V, la potenza dissipata dal BC337 è:

P = VCE(sat) × IC

P = 0,2 × 0,070

P = 0,014 W

Il transistor dissipa quindi circa 14 mW, valore molto inferiore alla potenza massima prevista per il componente nelle condizioni termiche indicate dal produttore.

Diodo di ricircolo

La bobina del relè è un componente induttivo. Quando il BC337 si spegne, la corrente nella bobina non può interrompersi immediatamente e viene generata una sovratensione che potrebbe danneggiare il transistor.

Per proteggere il BC337 si collega un diodo 1N4007 in parallelo alla bobina:

  • il catodo, riconoscibile dalla fascetta stampata sul corpo del diodo, deve essere collegato al terminale della bobina connesso a +5 V;
  • l’anodo, sul lato opposto alla fascetta, deve essere collegato al terminale della bobina connesso al collettore del BC337.

La fascetta del diodo deve quindi essere rivolta verso il +5 V.

Durante il normale funzionamento il diodo non conduce. Quando il BC337 si spegne, il diodo permette alla corrente residua della bobina di circolare fino alla dissipazione dell’energia immagazzinata.

Se il diodo viene montato al contrario, conduce quando il transistor si accende e provoca un collegamento diretto tra +5 V e massa.

Commutazione periodica del relè

Il relè viene collegato a una seconda uscita dell’ATmega328P attraverso il BC337 descritto nel pezzo precedente.

Si utilizza:

  • PB0 per il LED;
  • PB1 per la base del BC337 attraverso la resistenza da 560 Ω.

Nel framework Wiring, sulla UNO R3:

  • PB0 corrisponde al collegamento digitale 8;
  • PB1 corrisponde al collegamento digitale 9.

Il funzionamento è:

  • uscita PB1 a HIGH: il BC337 entra in saturazione e il relè si eccita;
  • uscita PB1 a LOW: il BC337 si interdice e il relè si diseccita.

Il relè viene fatto cambiare stato ogni 5 secondi. Il LED continua invece a cambiare stato ogni 500 millisecondi. Le due temporizzazioni sono indipendenti.

La commutazione periodica del relè serve a verificare il funzionamento del circuito di pilotaggio. La prova deve essere eseguita per un tempo limitato, perché il relè contiene parti meccaniche soggette a usura.

Aggiunta al programma in C

Si aggiungono le costanti:

#define RELE_BIT PB1
#define INTERVALLO_RELE_MS 5000UL

All’interno di main() si aggiungono:

uint32_t ultimo_cambio_rele = 0;
uint8_t rele_eccitato = 0;

Prima di configurare il pin come uscita, viene imposto il livello basso:

PORTB &= ~(1 << RELE_BIT);

Il pin PB1 viene configurato come uscita insieme a PB0:

DDRB |= (1 << LED_BIT) | (1 << RELE_BIT);

Nel ciclo while (1) si aggiunge:

if (tempo_attuale - ultimo_cambio_rele >= INTERVALLO_RELE_MS)
{
    ultimo_cambio_rele = tempo_attuale;

    if (rele_eccitato == 0)
    {
        PORTB |= (1 << RELE_BIT);
        rele_eccitato = 1;
    }
    else
    {
        PORTB &= ~(1 << RELE_BIT);
        rele_eccitato = 0;
    }
}

Il controllo del LED e quello del relè utilizzano lo stesso contatore dei millisecondi, ma intervalli differenti.

Aggiunta al programma in C++

Si aggiungono:

constexpr uint8_t RELE_BIT = PB1;
constexpr uint32_t INTERVALLO_RELE_MS = 5000UL;

La funzione che controlla il relè è:

void imposta_rele(bool eccitato)
{
    if (eccitato)
    {
        PORTB |= (1 << RELE_BIT);
    }
    else
    {
        PORTB &= ~(1 << RELE_BIT);
    }
}

All’interno di main() si aggiungono:

uint32_t ultimo_cambio_rele = 0;
bool rele_eccitato = false;

Lo stato iniziale viene impostato prima della configurazione del pin:

imposta_rele(false);
DDRB |= (1 << LED_BIT) | (1 << RELE_BIT);

Nel ciclo principale si aggiunge:

if (tempo_attuale - ultimo_cambio_rele >= INTERVALLO_RELE_MS)
{
    ultimo_cambio_rele = tempo_attuale;
    rele_eccitato = !rele_eccitato;
    imposta_rele(rele_eccitato);
}

Aggiunta al programma con framework Wiring

Si aggiungono le dichiarazioni:

const int pin_rele = 9;
const unsigned long intervallo_rele_ms = 5000;

unsigned long ultimo_cambio_rele_ms = 0;
bool rele_eccitato = false;

Nel setup():

pinMode(pin_rele, OUTPUT);
digitalWrite(pin_rele, LOW);

Nel loop(), dopo il controllo del LED:

if (tempo_attuale_ms - ultimo_cambio_rele_ms >= intervallo_rele_ms)
{
    ultimo_cambio_rele_ms = tempo_attuale_ms;

    if (rele_eccitato == false)
    {
        digitalWrite(pin_rele, HIGH);
        rele_eccitato = true;
    }
    else
    {
        digitalWrite(pin_rele, LOW);
        rele_eccitato = false;
    }
}

La sequenza risultante è:

  • il LED cambia stato ogni 500 ms;
  • il relè cambia stato ogni 5 s;
  • quando il relè è eccitato, rele_eccitato vale true;
  • quando il relè è diseccitato, rele_eccitato vale false;
  • il cambiamento dello stato del LED non modifica la temporizzazione del relè;
  • il cambiamento dello stato del relè non modifica la temporizzazione del LED.

Schemi elettrici

L’articolo deve contenere due schemi elettrici separati, realizzati con simboli elettronici standard e non con rappresentazioni testuali.

Schema elettrico del LED e del relè pilotato con BC337
Schema elettrico del LED e del relè. Fare clic sull’immagine per aprire il PDF ingrandibile.

Circuito del LED

Il primo schema mostra:

  • uscita PB0 dell’ATmega328P;
  • resistenza da 330 Ω collegata in serie;
  • anodo del LED collegato alla resistenza;
  • catodo del LED collegato a GND.

Il percorso della corrente è quindi:

PB0 → resistenza da 330 Ω → LED → GND

Quando PB0 è HIGH, il LED si accende. Quando PB0 è LOW, il LED si spegne.

Circuito del relè

Il secondo schema mostra:

  • uscita PB1 dell’ATmega328P;
  • resistenza da 560 Ω tra PB1 e la base del BC337;
  • emettitore del BC337 collegato a GND;
  • collettore del BC337 collegato a un terminale della bobina;
  • altro terminale della bobina collegato a +5 V;
  • diodo 1N4007 collegato in parallelo alla bobina;
  • catodo del diodo, riconoscibile dalla fascetta, collegato a +5 V;
  • anodo del diodo collegato al collettore del BC337;
  • massa dell’alimentazione del relè collegata alla massa dell’ATmega328P.

Lo schema deve riportare chiaramente i nomi dei terminali del transistor:

  • B: base;
  • C: collettore;
  • E: emettitore.

Deve inoltre essere indicata la fascetta del diodo sul lato collegato al positivo della bobina.

Quando PB1 è HIGH, il BC337 entra in saturazione e alimenta la bobina. Quando PB1 è LOW, il transistor si interdice e il relè si diseccita.

Collegamenti completi e programma Wiring

Pin utilizzati

Nel programma con framework Wiring si utilizzano:

  • D8, corrispondente a PB0, per il LED;
  • D9, corrispondente a PB1, per il comando del relè tramite BC337.

Collegamento del LED

Il LED esterno deve essere collegato in questo modo:

  • D8 collegato a una resistenza da 330 Ω;
  • altro terminale della resistenza collegato all’anodo del LED;
  • catodo del LED collegato a GND.

La resistenza e il LED sono collegati in serie.

Quando D8 è HIGH, il LED si accende. Quando D8 è LOW, il LED si spegne.

Collegamento del relè

Il relè non deve essere collegato direttamente a D9.

I collegamenti sono:

  • D9 collegato a una resistenza da 560 Ω;
  • altro terminale della resistenza collegato alla base del BC337;
  • emettitore del BC337 collegato a GND;
  • collettore del BC337 collegato a un terminale della bobina del relè;
  • altro terminale della bobina collegato a +5 V;
  • massa dell’alimentazione del relè collegata al GND della UNO R3.

I terminali base, collettore ed emettitore devono essere identificati consultando il datasheet del BC337 effettivamente utilizzato. Non bisogna ricavare la piedinatura soltanto dalla forma del contenitore.

Il diodo 1N4007 deve essere collegato in parallelo alla bobina:

  • catodo, riconoscibile dalla fascetta, collegato al terminale della bobina connesso a +5 V;
  • anodo collegato al terminale della bobina connesso al collettore del BC337.

La fascetta del diodo deve quindi essere rivolta verso il positivo.

I contatti COM, NO e NC del relè sono elettricamente separati dalla bobina:

  • con relè diseccitato, COM è collegato a NC;
  • con relè eccitato, COM è collegato a NO.

Per la prova del circuito i contatti possono essere lasciati scollegati oppure controllati con un multimetro in modalità continuità.

Programma completo con framework Wiring

const int pin_led = 8;
const int pin_rele = 9;

const unsigned long intervallo_led_ms = 500;
const unsigned long intervallo_rele_ms = 5000;

unsigned long ultimo_cambio_led_ms = 0;
unsigned long ultimo_cambio_rele_ms = 0;

bool led_acceso = false;
bool rele_eccitato = false;

void setup()
{
    pinMode(pin_led, OUTPUT);
    pinMode(pin_rele, OUTPUT);

    digitalWrite(pin_led, LOW);
    digitalWrite(pin_rele, LOW);
}

void loop()
{
    unsigned long tempo_attuale_ms = millis();

    if (tempo_attuale_ms - ultimo_cambio_led_ms >= intervallo_led_ms)
    {
        ultimo_cambio_led_ms = tempo_attuale_ms;

        if (led_acceso == false)
        {
            digitalWrite(pin_led, HIGH);
            led_acceso = true;
        }
        else
        {
            digitalWrite(pin_led, LOW);
            led_acceso = false;
        }
    }

    if (tempo_attuale_ms - ultimo_cambio_rele_ms >= intervallo_rele_ms)
    {
        ultimo_cambio_rele_ms = tempo_attuale_ms;

        if (rele_eccitato == false)
        {
            digitalWrite(pin_rele, HIGH);
            rele_eccitato = true;
        }
        else
        {
            digitalWrite(pin_rele, LOW);
            rele_eccitato = false;
        }
    }
}

Il LED cambia stato ogni 500 millisecondi. Rimane quindi acceso per mezzo secondo e spento per mezzo secondo.

Il relè cambia stato ogni 5 secondi. Rimane quindi eccitato per cinque secondi e diseccitato per cinque secondi.

Le due temporizzazioni utilizzano lo stesso valore restituito da millis(), ma conservano separatamente il momento dell’ultima commutazione. Il lampeggio del LED e la commutazione del relè rimangono quindi indipendenti.

Schede relè già pronte e logica HIGH/LOW

In commercio sono disponibili schede relè già assemblate, con uno o più relè e il relativo circuito di pilotaggio.

Una tipica scheda può comprendere:

  • relè elettromeccanico;
  • transistor di pilotaggio;
  • resistenza di base;
  • diodo di ricircolo;
  • LED di segnalazione;
  • morsetti COM, NO e NC;
  • connettori di alimentazione e comando;
  • eventuale optoisolatore.

I collegamenti di ingresso sono normalmente indicati con:

  • VCC: alimentazione del circuito;
  • GND: massa;
  • IN: ingresso di comando.

Nelle schede con più relè sono presenti ingressi come IN1, IN2, IN3 e IN4.

Alcuni moduli presentano anche i terminali:

  • JD-VCC: alimentazione delle bobine;
  • VCC: alimentazione della parte logica;
  • GND: riferimento comune.

Un ponticello può collegare VCC e JD-VCC. La disposizione cambia tra i diversi moduli e deve essere verificata leggendo le indicazioni stampate sulla scheda.

Moduli attivi con livello alto

In un modulo attivo HIGH il relè si eccita quando l’ingresso viene portato a livello alto:

IN = HIGH → relè eccitato
IN = LOW  → relè diseccitato

Il programma può utilizzare direttamente:

digitalWrite(pin_rele, HIGH);

per eccitare il relè e:

digitalWrite(pin_rele, LOW);

per diseccitarlo.

Moduli attivi con livello basso

Molte schede relè già pronte sono invece attive LOW.

In questo caso il funzionamento è invertito:

IN = LOW  → relè eccitato
IN = HIGH → relè diseccitato

Il livello basso attiva il transistor o l’optoisolatore presente sulla scheda. Di conseguenza, un programma scritto per il circuito con BC337 descritto in precedenza farebbe funzionare il relè al contrario.

Con un modulo attivo LOW, il comando corretto è:

digitalWrite(pin_rele, LOW);

per eccitare il relè e:

digitalWrite(pin_rele, HIGH);

per diseccitarlo.

Anche lo stato iniziale deve essere modificato. Nel setup() si deve scrivere:

pinMode(pin_rele, OUTPUT);
digitalWrite(pin_rele, HIGH);

In questo modo il relè rimane diseccitato all’avvio.

Programma adattato a un modulo attivo LOW

La funzione di comando può essere scritta in modo esplicito:

void imposta_rele(bool eccitato)
{
    if (eccitato)
    {
        digitalWrite(pin_rele, LOW);
    }
    else
    {
        digitalWrite(pin_rele, HIGH);
    }
}

Il resto del programma può continuare a utilizzare una variabile descrittiva:

bool rele_eccitato = false;

Quando la variabile vale true, la funzione applica LOW al pin. Quando vale false, applica HIGH.

Il programma continua quindi a ragionare sullo stato fisico del relè, senza dover ricordare ogni volta che la logica elettrica del modulo è invertita.

Come verificare la logica del modulo

Prima di utilizzare una scheda relè occorre verificare se l’ingresso è attivo HIGH oppure attivo LOW.

La prova può essere eseguita senza collegare alcun carico ai contatti:

  1. alimentare correttamente il modulo;
  2. collegare GND del modulo al GND della scheda;
  3. impostare l’ingresso a HIGH;
  4. osservare se il relè si eccita;
  5. impostare l’ingresso a LOW;
  6. osservare nuovamente lo stato del relè.

Il LED presente sul modulo può aiutare, ma non sempre indica direttamente lo stato meccanico dei contatti. La verifica più sicura consiste nell’ascoltare lo scatto del relè oppure misurare la continuità tra COM, NO e NC.

La logica non deve essere dedotta soltanto dal colore del modulo o dal numero dei componenti. Due schede esteticamente simili possono utilizzare circuiti di ingresso differenti.

Compatibilità della tensione di comando

Una scheda con relè da 5 V non è automaticamente compatibile con qualsiasi segnale logico.

Occorre distinguere:

  • tensione della bobina;
  • tensione di alimentazione del modulo;
  • tensione riconosciuta dall’ingresso IN.

Un modulo alimentato a 5 V può accettare correttamente un comando a 5 V, ma potrebbe non riconoscere in modo affidabile un livello alto di 3,3 V. Altri moduli funzionano invece correttamente sia a 3,3 V sia a 5 V.

La compatibilità deve essere verificata prima del collegamento. Il fatto che il relè riporti la scritta “5 V” indica normalmente la tensione della bobina, non necessariamente la tensione minima riconosciuta dall’ingresso di comando.

Fonti principali

Microchip Technology — ATmega328P: 8-bit AVR Microcontroller with 32K Bytes In-System Programmable Flash, datasheet.

Arduino — Arduino UNO R3: scheda tecnica, schema elettrico e corrispondenza dei collegamenti con l’ATmega328P.

onsemi — BC337: NPN Bipolar Transistor, datasheet.

Vishay — 1N4001–1N4007: General Purpose Plastic Rectifier, datasheet.