Gestire i registri dell’MCP9803

Nella prima parte abbiamo visto come comunicare con il sensore di temperatura MCP9803 utilizzando il bus I2C per leggere il valore di temperatura contenuto nel Temperature Register.
Proveremmo ora a fare qualcosa di più interessante variando alcuni parametri del sensore. Il circuito elettrico è sempre quello impiegato nella prima parte dell’articolo.

Il register pointer permette di selezionare 4 registri differenti a seconda di come vengono impostati i bit P0 e P1.

Per cambiare la risoluzione del convertitore dobbiamo accedere al registro di configurazione. Come mostra la tabella della figura precedente bisogna impostare il bit P1 a 0 ed il bit P0 a 1.

La figura seguente Riporto in figura il registro Configuration register  come illustrato nel datasheet:

Registro di configurazione del pic

Come è possibile osservare questo registro permette di eseguire diverse operazioni. Quella che a noi ci interessa è relativa alla sezione Resolution (Bit5 e Bit6). Quando il sensore di temperatura viene alimentato i bit di questo registro sono impostati tutti a 0 (Power-up default).

Le operazioni che dobbiamo eseguire per impostare la risoluzione del convertitore a 12bit sono le seguenti:

  1. Selezionare il sensore tramite l’indirizzo
  2. Selezionare il registro di configurazione
  3. Impostare i bit5 e il bit6 a 1
  4. Selezionare il registro della temperatura
  5. Leggere dal registro della temperatura

Queste sono le illustrazioni presenti nel datasheet:

Scrivere un byte sul registro di configurazione

Il master invia un comando di Start, invia un byte contenente l’indirizzo del sensore (7bit) e il flag di lettura/scrittura, nel nostro esempio 1001000 (0x90 in esadecimale). Il sensore risponde con un acknowledges (ACK), il master invia il secondo un byte che sarà scritto sul Pointer Register. Questo valore è 00000001 (0x01 in esadecimale) che seleziona il Configuration Register. Il sensore risponde con un acknowledges. Il master invia un terzo byte 01100000 (0x60 in esadecimale) che sarà scritto nel configuration register. Il sensore risponde con un acknowledges. Il master termina la comunicazione con un comando di Stop.

Ora possiamo eseguire la procedura di lettura come abbiamo visto nella prima parte:

Diagramma di lettura di due Byte dal registro

Riassumiamo la procedura:

  • Il master invia un comando di Start
  • Il master invia un byte contenente l’indirizzo dello slave e il flag di lettura/scrittura impostato su lettura (1).
  • Il sensore risponde con un acknowledges.
  • Il master legge il primo byte dallo slave e  invia un acknowledges.
  • Il master legge il secondo Byte e invia un not acknowledges(NAK) per indicare allo slave la fine della lettura.
  • Il master invia il comando di stop.

Utilizzando la tabella di esempio possiamo apprendere come i dati verranno scritti nel Temperature Register:

Registro di temperatura a 12 bit

Prendiamo per esempio la colonna 12bit ed una temperatura ambiente di 25.4375°C, il valore binario presente nel Temperature Register è 000110010111. Il primo byte letto dal pic è 00011001 mentre il secondo byte letto è 01110000.
Il primo byte letto dal pic è quello più significativo mentre il secondo è quello meno significativo. Per ottenere il valore della temperatura ambiente, campionata dal sensore, dobbiamo prendere il valore binario del secondo byte e unirlo al valore binario del primo byte in questo modo

00011001 + 0111 => 000110010111

il valore ottenuto è 000110010111 che convertito in decimale diviene 407. La temperatura viene calcolata utilizzando la formula seguente:

n nel nostro caso vale -4 perché la risoluzione del convertitore è settata a 12bit, di conseguenza la nostra formula per calcolare la temperatura diventa:

407 * 2^-4 => 407 * 0.0625 => 25.4375°C

Il codice di esempio seguente è compilato e caricato sul pic16f876, sono presenti numerosi commenti che illustrano il funzionamento del programma:

unsigned short Primo_Byte = 0;
unsigned short Secondo_Byte = 0;

void main()
{
//inizializzo la porta UART
//a 9600bps
UART1_Init(9600);
//Inizializzo la porta I2C
//con una velocità di 400Khz
I2C1_Init(400000);

//Attendo 100 millisecondi
Delay_ms(100);

//procedura per variare la risoluzione
//del convertitore digitale
//invio uno Start sul bus I2C
I2C1_Start();
//Invio l'indirizzo 1001000
//setto il bit per la scrittura
I2C1_Wr(0x90); //10010000
//Seleziono il registro di configurazione
I2C1_Wr(0x01); //00000001
//scrivo sul Configuration Register il valore 96
//per impostare 12bit di risoluzione
I2C1_Wr(0x60);
//termino la comunicazione
I2C1_Stop();

//invio uno Start sul bus I2C
I2C1_Start();
//Invio l'indirizzo 1001000
//setto il bit per la scrittura
I2C1_Wr(0x90); //10010000
//scrivo sul Register point il valore 0
//per accedere al Temperature Register
I2C1_Wr(0x00);

//ciclo infinito per leggere
//continuamente la temperatura
while (1)
{
//invio uno Start sul bus I2C
I2C1_Start();

//Invio l'indirizzo 1001000
//setto il bit per la scrittura
I2C1_Wr(0x91); //10010001
//leggo il primo Byte ed invio un acknowledge
Primo_Byte = I2C1_Rd(1);
//leggo il primo Byte ed invio un NOT acknowledge
Secondo_Byte = I2C1_Rd(0);
//Invio uno stop sul bus I2C
I2C1_Stop();

//Invio sulla seriale i dati letti dal sensore
UART1_Write(Primo_Byte);
UART1_Write(Secondo_Byte);

//attendo 2 secondi tra una lettura e la
//sucessiva
Delay_ms(2000);

}
}

 

Quello che adesso rimane da fare è il software lato pc che acquisisce i dati dalla seriale. Ho utilizzato Visual C# per creare un’applicazione windows form.
Anche in questo caso il codice seguente è ricco di commenti che permettono di capire meglio il programma:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Collections;
using System.IO.Ports;

namespace SensoreTemperaturaMCP9803
{
public partial class frmMain : Form
{
//variabili per la lettura dalla seriale
Double temperaturaRX = 0;
Int16 Temperatura = 0;
Byte[] bytesRicevuti = new Byte[2];

//creo un delegate per accedere dal thread secondario a quello principale
public delegate void AggiornaTXT(string _str);

/// <summary>
/// questa funzione permette di accedere alla textbox da un thread secondario
/// </summary>
/// <param name="str">Testo da inserire nella textbox</param>
public void FormTxtUpdate(string str)
{
if (this.txtTemperatura.InvokeRequired)
{
AggiornaTXT updaterdelegate = new AggiornaTXT(FormTxtUpdate);
this.Invoke(updaterdelegate, new object[] { str });
}
else
{
txtTemperatura.Text = str;
}
}

/// <summary>
/// Questa funzione converte un valore binario in un valore a 16 bit
/// </summary>
/// <param name="ValoreBinario">Valore in bit</param>
/// <param name="Start">Indice di partenza del BitArray</param>
/// <param name="NumBit">Numero di bit da convertire</param>
/// <returns></returns>
private Int16 ConverToDecimal(BitArray ValoreBinario, Int32 Start, Int32 NumBit)
{
Int32 Ciclo = 0;
Int16 Valore = 0;

for (Ciclo = 0; Ciclo < NumBit; Ciclo++)
{
if (ValoreBinario[Ciclo + Start])
Valore |= (Int16)(1 << Ciclo);
}

return (Valore);
}

public frmMain()
{
InitializeComponent();
}

/// <summary>
/// Questo evento viene generato ala ricezione di un byte sulla seriale
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RS232_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//questa funzione viene eseguita in un thread secondario

//quando sono disponibili almeno 2 byte inizia
//l'elaborazione
if (RS232.BytesToRead >= 2)
{
//leggi 2 byte e memorizzali nell'array bytesRicevuti
RS232.Read(bytesRicevuti, 0, 2);

//inverto l'array
Array.Reverse(bytesRicevuti);
//converto l'array di 2 byte in un array di bit
BitArray ba = new BitArray(bytesRicevuti);

//ottengo il valore della temperatura utilizzando solo i bit validi
//versione con risoluzione a 9bit
//Temperatura = ConverToDecimal(ba, 7, 8);
//calcolo il valore della temperatura
//se il valore del convertiore è a 9 bit devo moltiplicare per 0.5
//temperaturaRX = Temperatura * 0.5;

//ottengo il valore della temperatura utilizzando solo i bit validi
//versione con risoluzione a 12bit
Temperatura = ConverToDecimal(ba, 4, 12);
//calcolo il valore della temperatura
//se il valore del convertiore è a 12 bit devo moltiplicare per 0.0625
temperaturaRX = Temperatura * 0.0625;

//rappresento il valore della temperatura nella textbox
FormTxtUpdate(Convert.ToString(temperaturaRX) + " °C");
}
}

private void frmMain_Load(object sender, EventArgs e)
{
//apro la seriale per iniziare a leggere i dati
RS232.Open();
}

private void frmMain_FormClosed(object sender, FormClosedEventArgs e)
{
//alla chiusura del form chiudo la comunicazione seriale
RS232.Close();
}

private void btnChiudi_Click(object sender, EventArgs e)
{
//chiudi
Close();
}
}
}

 

Le operazioni che esegue il programma sono riassunte in questo modo:

  • La porta seriale viene aperta per poter acquisire i dati
  • Un gestore di evento viene scatenato quando sulla seriale arrivano dei dati
  • Il gestore di evento leggere due byte alla volta.
  • Il gestore di evento elabora i due byte in modo da ottenere il valore decimale del dato della temperatura
  • Il valore della temperatura è visualizzato su un controllo textbox

Adesso potete divertirvi con il codice per creare un datalogger della temperatura e magari creare un sistema con più sensori di temperatura.

Come sempre suggerimenti e critiche sono sempre ben accette.

Buon divertimento!