Nel precedente tutorial vi ho mostrato l’utilizzo del driver I2C incluso nel framework esp-idf per far comunicare il chip esp32 con dispositivi I2C. Oggi vedremo un esempio pratico: l’utilizzo di un sensore temperatura/umidità.
Il sensore
Per questo tutorial ho scelto di utilizzare il sensore HTU21D di Te Connectivity. Questo sensore offre una buona accuratezza ed è disponibile già saldato su una breakout board ad un costo di pochi euro:
Per collegarlo alla scheda di sviluppo esp32 dobbiamo per prima cosa scegliere quali pin del chip esp32 utilizzare per i segnali SDA e SCL. Oltre a tali segnali, dobbiamo collegare VDD (3.3V) e GND:
Datasheet e comandi
Una volta realizzati i collegamenti fisici tra la scheda di sviluppo esp32 e il sensore, dobbiamo capire come scrivere un programma che interagisce con esso. La prima cosa da fare è sicuramente leggere il datasheet del sensore (qui il documento in PDF). Non spaventatevi se aprendo il documento vedete 21 pagine di specifiche tecniche, nei prossimi paragrafi vi spiegherò quali sono le informazioni fondamentali che ci servono!
A pagina 10 inizia il capitolo relativo al protocollo di comunicazione (COMMUNICATION PROTOCOL WITH HTU21D(F) SENSOR). Subito leggiamo che il sensore offre una interfaccia I2C slave con indirizzo 0x40; appena sotto è riportata anche la tabella con i comandi disponibili:
Vi sono due diverse modalità per misurare temperatura e umidità:
[checklist]
- hold master
- no hold master
[/checklist]
Nella prima modalità, il sensore blocca il segnale di clock (SCK) durante la misurazione: in questo modo il master può inviare il comando di lettura solo quando la misurazione è effettivamente terminata. Nella seconda modalità invece il master può effettuare altre operazioni sul bus (es. interrogare un altro sensore) durante la fase di misurazione.
In modalità no hold il master, dopo aver inviato il comando di “trigger maesurement”, deve attendere che il sensore termini la misurazione prima di leggere il valore. E’ possibile verificare se la misurazione è completata inviando al sensore un comando di read e attendendo l’ACK: se questo viene ricevuto, significa che il dato è disponibile.
Il sensore restituisce il dato in forma grezza (raw) come valore a 16bit (= 2 bytes). In aggiunta al dato, viene anche restituito un valore (1 byte) di checksum; tale valore consente al master di verificare che non vi siano stati errori di trasmissione.
Nel precedente articolo abbiamo già imparato come interrogare un dispositivo slave; per leggere la temperatura dal sensore HTU21D in modalità no hold le istruzioni da utilizzare sono quindi:
// constants #define HTU21D_ADDR 0x40 #define TRIGGER_TEMP_MEASURE_NOHOLD 0xF3 // send the command i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (HTU21D_ADDR << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, TRIGGER_TEMP_MEASURE_NOHOLD, true); i2c_master_stop(cmd); ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_RATE_MS); i2c_cmd_link_delete(cmd); // wait for the sensor (50ms) vTaskDelay(50 / portTICK_RATE_MS); // receive the answer uint8_t msb, lsb, crc; cmd = i2c_cmd_link_create(); i2c_master_start(cmd); i2c_master_write_byte(cmd, (HTU21D_ADDR << 1) | I2C_MASTER_READ, true); i2c_master_read_byte(cmd, &msb, 0x00); i2c_master_read_byte(cmd, &lsb, 0x00); i2c_master_read_byte(cmd, &crc, 0x01); i2c_master_stop(cmd); ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_RATE_MS); i2c_cmd_link_delete(cmd); |
Il codice di esempio attende 50ms prima di leggere il dato: dal datasheet si legge infatti che il tempo massimo di misurazione è proprio 50ms per il valore di temperatura alla massima risoluzione:
Per convertire il valore raw nel valore reale di temperatura (in °C) o di umidità (in %) è possibile utilizzare le formule presenti nel datasheet:
uint16_t raw_value = ((uint16_t) msb << 8) | (uint16_t) lsb; float temperature = (raw_value * 175.72 / 65536.0) - 46.85; float humidity = (raw_value * 125.0 / 65536.0) - 6.0; |
Risoluzione
Il sensore offre 4 diverse combinazioni di risoluzione per temperatura e umidità:
- umidità 12bit, temperatura 14bit
- umidità 8bit, temperatura 12bit
- umidità 10bit, temperatura 13bit
- umidità 11bit, temperatura 11bit
E’ possibile cambiare la risoluzione modificando il valore di un registro di configurazione, con il comando write user register. In particolare i bit del registro da modificare sono il bit 0 e il bit 7:
CRC
L’algoritmo per la verifica del valore di CRC è illustrato nel datasheet.
Il polinomio utilizzato è x^8 + x^5 + x^4 + 1 che in binario diventa:
A tale polinomio vanno aggiunti degli zeri in base al numero di bit del CRC:
Il valore ottenuto, in esadecimale, è 0x98800.
Al dato da verificare, viene aggiunto in coda il valore di CRC ricevuto come terzo byte dal sensore:
uint32_t row = (uint32_t)value << 8; row |= crc; |
Quindi si verifica il valore del bit che corrisponde alla posizione più a sinistra del polinomio divisore: se tale bit è uguale a 1, il dato da verificare viene XORato con il divisore. Infine il divisore è spostato a destra di una posizione:
for (int i = 0 ; i < 16 ; i++) { if (row & (uint32_t)1 << (23 - i)) row ^= divisor; divisor >>= 1; } |
Se il valore residuo di row è uguale a zero, il CRC è validato.
Componente
Ho sviluppato un componente per il framework esp-idf che implementa quando spiegato in questo articolo. Il componente è disponibile su Github e la documentazione sul suo utilizzo è pubblicata in una pagina ad esso dedicata.
Ecco un filmato che mostra il suo funzionamento (sono disponibili i sottotitoli in italiano):
In the function htu21d_init(), you create an i2c_cmd_handle_t, but once you’ve verified a slave device on the bus, you do not call i2c_cmd_link_delete(cmd) to remove the link. Is this omission intentional?
Hi Alan, that’s because the handler
i2c_cmd_handle_t cmd
is in scope of that function and therefore it’s automatically removed by the GC when the function ends. It’s anyway a good practice to delete the unused objects/pointers, thanks for the comment!Hello Luca.
Both this tutorial and the previous one were incredibly useful for me but I was wondering if you could explain or give an example of how to configure the ESP32 as a slave device, rather than the master.
I’m trying to connect the ESP32 to a PSOC5 and i’m having a hard time understanding what I have to do in the loop function of the esp32 configuration in order to be able to receive the data being written by the master (the psoc).
Thank you