Netduino, BMP085 Driver in VB .NET
Il Sensore BMP085
Il sensore Bosch BMP085 è un sensore di temperatura e pressione barometrica ad alta precisione. Il sensore è basato sulla tecnologia piezo-resistiva che fornisce alla scheda grande accuratezza, robustezza e stabilità a lungo termine. .Nella modalià ad alta risoluzione il sensore di pressione raggiunge un’accuratezza di 0.003hPa in un range compreso tra i 300 e i 1100hPa. Il sensore viene fornito già calibrato dal produttore con i coefficienti di calibrazione precaricati nella ROM. Naturalmente è possibile modificare tali coefficienti andando a scrivere in alcune locazioni dimemoria interna tramite il bus I2C.
Normalmente viene fornito premontato in una mini-scheda fornita di piazzole prestagnate poste a distanza standard (passo 2,54mm). In questo modo si rende il sensore di pressione barometrica facilmente integrabile in sistemi preesistenti tramite l’utilizzo di connettore strip maschio facilmente utilizzabile nelle comunissime breadboard, millefori o schede di prototipazione. Normalmente le schede sono alimentate con una tensione di alimentazione tra i 3.0V e i 5.0V, anche se il sensore viene alimentato con una tensione regolata a 3.3V. Prima di alimentare la scheda, leggete sempre le istruzioni di utilizzo e state attenti a come la collegate al micro per non incorrere in spiacevoli conseguenze.
Come accennato precedentemente, la scheda si interfaccia con l’esterno tramite il bus I2C. Si ricorda che per far funzionare correttamente il bus I2C, i segnali SDA ed SCL devono essere collegati ad opportune resistenze di pull-up e che spesso queste sono già integrate sulle schede dei microcontrollori.
La matematica del sensore
Nel manuale del sensore si trova la lista delle operazioni da fare per calcolare il valore di temperatura e pressione reale dai dati grezzi provenienti dal sensore utilizzando i parametri di compensazione memorizzati all’interno. Infatti, i dati “grezzi” letti dal sensore sono dati “non compensati” cioè valori che non tengono conto delle derive termiche e del fatto che anche se i sensori sono costruiti in serie, questi comunque si comportano in modo leggermente diverso tra loro. Secondo il manuale le operazioni (in pseudocodice) da eseguire sono:
... 'Resolution Preset (0,1,2,3) ' 0 = Ultra Low Power ' 1 = Standard ' 2 = High Resolution ' 3 = Ultra High Resolution OSS = 1 'Read registers READ (0xAA, 0xAB) -> AC1 READ (0xAC, 0xAD) -> AC2 READ (0xAE, 0xAF) -> AC3 READ (0xB0, 0xB1) -> AC4 READ (0xB2, 0xB3) -> AC5 READ (0xB4, 0xB5) -> AC6 READ (0xB6, 0xB7) -> B1 READ (0xB8, 0xB9) -> B2 READ (0xBA, 0xBB) -> MB READ (0xBC, 0xBD) -> MC READ (0xBE, 0xBF) -> MD 'Read uncompensated Temperature WRITE 0x2E -> 0xF4 WAIT 4.5 ms READ (0xF6, 0xF7) -> UT 'Read uncompensated Pressure WRITE 0x34 + (OSS << 6) -> 0xF4 SELECT CASE OSS CASE 0: WAIT 4.5ms CASE 1: WAIT 7.5ms CASE 2: WAIT 13.5ms CASE 3: WAIT 25.5ms END SELECT READ (0xF6, 0xF7, 0xF8) >> (8-OSS) -> UP 'Calculate Temperature ( 0.1 Celsius ) X1 = (UT -AC6) * (AC5 / 2^15) X2 = MC * 2^ 11 / (X1 + MD) B5 = X1 + X2 T = (B5 + 8) / 2^4 'Calculate Pressure ( Pascal ) B6 = B5 - 4000 X1 = (B2 * (B6 * B6 / 2^12)) / 2^11 X2 = AC2 * B6 / 2^11 X3 = X1 + X2 B3 = ((AC1 * 4 + X3) << OSS + 2) / 4 X1 = AC3 * B6 / 2^13 X2 = (B1 * (B6 * B6 / 2^12)) / 2^16 X3 = ((X1 + X2) + 2) / 2^2 B4 = AC4 * (unsigned long)(X3 + 32768) / 2^15 B7 = ((unsigned long)UP - B3)*(50000 >> OSS) IF (B7 < 0x80000000) THEN P = (B7 * 2) / B4 ELSE P = (B7 / B4) * 2 END X1 = (P / 2^8) * (P / 2^8) X1 = (X1 * 3038) / 2^16 X2 = (-7357 * P) / 2^16 P = P + (X1 + X2 + 3791) / 2^4 ...
Tutti i calcoli nello script sono eseguiti utilizzando numeri interi (signed o unsigned) a diversa risoluzione (a 16, 32 o 64 bit). Anche se da un punto
di vista della precisione la cosa non avrebbe un gran chè senso, ho riscritto l’algoritmo utilizzando tutte operazioni in virgola mobile e, dove si
poteva, raggruppando le costanti.Utilizzando le variabili in virgola mobile, anche se il numero di calcoli sono diminuiti lievemente, ora ed è
possibile passare comodomante da un’unità di misura ad un’altra senza perdere risoluzione.
La classe BMP085
Passiamo ora alla stesura della classe BMP085 per la lettura della pressione e temperatura. Prima di tutto si eredita la classe I2CPlug (vedere il relativo articolo) e successivamente vengono definite alcune costanti per indirizzare due registri del sensore e il bit 7 che corrisponde al valore decimale 128 oppure
&H80 in esadecimale (nel byte i bit vengono indicizzati da 0 a 7).
... Public Class BMP085 Inherits I2CPlug 'Internal Single Values Private c4 As Single Private c5 As Single Private c6 As Single Private b1 As Single Private mb As Single Private mc As Single Private md As Single Private x0 As Single Private x1 As Single Private x2 As Single Private y0 As Single Private y1 As Single Private y2 As Single Private p0 As Single Private p1 As Single Private p2 As Single Private Buffer1 As Byte() = New Byte(0) {} Private Buffer2 As Byte() = New Byte(1) {} Private Buffer3 As Byte() = New Byte(2) {} .... ''' Sensor resolution Public Property Resolution() As Resolutions Get Return m_Resolution End Get Set(ByVal value As Resolutions) m_Resolution = value End Set End Property ... Public Sub New(ByVal Address As UShort, Optional ByVal ClockRateKHz As Integer = 100, Optional ByVal TimeOut As Integer = 100) 'Device creation MyBase.New(Address, ClockRateKHz, TimeOut) End Sub Public Sub Calibrate() 'Variable declaration Dim Buffer() As Byte = New Byte(21) {} 'Read the internal BOSH Registers MyBase.ReadRegister(&HAA, Buffer) 'Basic Single Coefficients Calculation c4 = Me.ToSingle(Buffer(6), Buffer(7), True) * 0.00000003051758F c5 = Me.ToSingle(Buffer(8), Buffer(9), True) * 0.000000190734866F c6 = Me.ToSingle(Buffer(10), Buffer(11), True) mb = Me.ToSingle(Buffer(16), Buffer(17)) b1 = Me.ToSingle(Buffer(12), Buffer(13)) * 0.0000238418579F mc = Me.ToSingle(Buffer(18), Buffer(19)) * 0.08F '/ 160 / 160 * 2048 md = Me.ToSingle(Buffer(20), Buffer(21)) * 0.00625F 'Derived Polinomial Values x0 = Me.ToSingle(Buffer(0), Buffer(1)) '* 1 x1 = Me.ToSingle(Buffer(2), Buffer(3)) * 0.01953125F '* 160 / 8192 x2 = Me.ToSingle(Buffer(14), Buffer(15)) * 0.000762939453F '* 160 * 160 / 33554432 y0 = c4 * 32768 y1 = c4 * Me.ToSingle(Buffer(4), Buffer(5)) * 0.0048828125F '* 160 / 32768 y2 = c4 * Me.ToSingle(Buffer(12), Buffer(13)) * 0.0000238418579F '* 160 * 160 / 1073741824 p0 = 236.4375F '= (3791 - 8) / 1600 * 100 p1 = 99.2983856F '= 1 - 7357 / 1048576 * 100 p2 = 0.000442087185F '= 303800 / 68719476736 * 100 'Offset Recalculation m_Offset = 0 Me.Calculate() m_Offset = m_Altitude - Altimetry 'Memory cleaning Erase Buffer Buffer = Nothing End Sub Private Sub Calculate() Dim Tmp As Single = 0 Dim Prs As Single = 0 Dim Alt As Single = 0 Dim aph As Single = 0 Dim s1 As Single = 0 Dim s2 As Single = 0 Dim z As Single = 0 'Select the temperature registry MyBase.Write(New Byte() {&HF4, &H2E}) 'Maximum Conversion Time for temperature Thread.Sleep(5) 'Read the temperature MyBase.ReadRegister(&HF6, Buffer2) 'TEMPERATURE CALCULATION Tmp = Me.ToSingle(Buffer2(0), Buffer2(1), CByte(0)) aph = c5 * (Tmp - c6) Tmp = aph + mc / (aph + md) 'Temperature Output (Celsius) m_Temperature = Tmp 'Select the pressure registry and correct conversion time Select Case m_Resolution Case Resolutions.Low MyBase.WriteRegister(&HF4, New Byte() {CType(&H34, Byte)}) Thread.Sleep(5) Case Resolutions.Standard MyBase.WriteRegister(&HF4, New Byte() {CType(&H74, Byte)}) Thread.Sleep(8) Case Resolutions.High MyBase.WriteRegister(&HF4, New Byte() {CType(&HB4, Byte)}) Thread.Sleep(14) Case Resolutions.Ultra MyBase.WriteRegister(&HF4, New Byte() {CType(&HF4, Byte)}) Thread.Sleep(26) End Select 'Preset the Pressure Registry MyBase.ReadRegister(&HF6, Buffer3) 'Uncompensated Pressure reading Prs = Me.ToSingle(Buffer3(0), Buffer3(1), Buffer3(2)) 'PRESSURE CALCULATION s1 = Tmp - 25 s2 = s1 * s1 z = (Prs - (x2 * s2 + x1 * s1 + x0)) / (y2 * s2 + y1 * s1 + y0) Prs = (p2 * z * z + p1 * z + p0) 'Pressure Output (Pascal) m_Pressure = Prs End Sub ... End Class ...
Viene ridefinito il metodo New() per la creazione della classe, ma solo per cambiare il valore di default del ClockRate per renderlo piú congeniale al dispositivo. I due metodi principali della classe sono Calibrate() e Calculate(). Con Calibrate() si leggono i coefficienti interni del sensore (c4, c5, c6, mb, mc, md e b1) trasformandoli in valori floating point (single). Poi si leggono e si calcolano i coefficienti dei tre polinomi di secondo grado che servono per calcolare la pressione compensando le variazioni dovute alla temperatura ( x0, x1, x2, y0,y1, y2 e p0, p1, p2).
Il metodo Calculate() legge il valore di temperatura e pressione (non compensata) e poi utilizzato i coefficienti pre-calcolati e memorizzati con il metodo precedente, determina il valore di temperatura, di pressione e altitudine. La legge di compensazione è data dalla seguente formula :
dove con la variabile Pc si è indicata la pressione compensata mentre con la variabile z un valore dipendente dalla temperatura e dalla pressione non compensata. Questa variabile si ottiene utilizzando le due formule:
dove la temperatura tu è quella effettiva mentre t è quella relativa alla temperatura standard di 25 °C.
Nel metodo Calculate() viene posto in Sleep il thread per un numero di millisecondi dipendente dalla risoluzione desiderata memorizzata nella variabile interna m_Resolution. Questa variabile è anche una proprietè della classe e può avere solo quattro valori: Low, Standard, High e Ultra. In generale, più $egrave; alta la risoluzione e più tempo ci vuole per la conversione.
La temperatura e la pressione sono espresse rispettivamente in gradi Celsius e Pascal. Per la lettura dei valori la classe ha le seguenti proprietà
ReadOnly:
... ''' Temperature Public ReadOnly Property Temperature(Optional ByVal Unit As TemperatureUnit = TemperatureUnit.Celsius) As Single Get Select Case Unit Case TemperatureUnit.Celsius Return m_Temperature Case TemperatureUnit.Kelvin Return (m_Temperature + 273.15F) Case TemperatureUnit.Fahrenheit Return (m_Temperature * 1.8F + 32.0F) Case TemperatureUnit.Rankine Return (m_Temperature * 1.8F + 491.67F) Case Else Return 0 End Select End Get End Property ''' Pressure Public ReadOnly Property Pressure(Optional ByVal Unit As PressureUnit = PressureUnit.Millibar) As Single Get Select Case Unit Case PressureUnit.Pascal Return m_Pressure Case PressureUnit.Bar Return m_Pressure / 100000.0F Case PressureUnit.Atmosphere Return m_Pressure / 1101325.0F Case PressureUnit.Torr Return m_Pressure / 133.322F Case PressureUnit.Psi Return m_Pressure / 6895.0F Case PressureUnit.Millibar Return m_Pressure / 100 Case Else Return 0 End Select End Get End Property ...
Queste due proprietà permettono di trasformare gli ultimi valori letti nell’unità di misura desiderata. Per le temperature abbiamo quattro scelte (Celsius, Kelvin, Fahrenheit, Rankine) mentre per le pressioni sei (Pascal, Bar, Atmosphere, Torr, Psi e Millibar). Vediamo ora come utilizzare la classe.
Utilizzo della Classe
Prima di tutto bisogna creare la classe BMP085 passando nel New() l’indirizzo del BUS I2C &H77, inserendola nella chiamata Sub Main e poi
eseguire una calibrazione del sensore chiamando il metodo Calibrate(). Se si desidera leggere i valori di Pressione e Temperaura in tempo Reale, bisogna chiamare il metodo Calculate() prima dell’utilizzo delle variabili in modo da aggiornare e ricalcolare i valori interni.
Public Sub Main() Dim BAR As BMP085 = New BMP085(&H77) Dim Temperature as Single = 0 Dim Pressure as Single = 0 'Calibrazione BAR.Calibrate 'Esegui per sempre ... While True 'Lettura dei dati BAR.Calculate ... 'Lettura della Temperatura Temperature = BAR.Temperature(TemperatureUnit.Celsius) 'Lettura della Pressione Pressure = BAR.Pressure(PressureUnit.Millibar) ... Debug.Print(Temperature.ToString & " Celsius : " & Pressure.ToString & " millibar") ... End While End Sub
Informazioni e Risorse
This work is licensed under a Creative Commons Attribution 4.0 International License |
|
BOSH BMP085 Sensor Data Sheet | |
Download the BMP085 Sources v1.0 (VB.NET) |