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)

Tags: , , , ,

This entry was posted on Sunday, March 2nd, 2014 at 14:36 and is filed under Elettronica, Italiano, Programmazione. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

 
 
  • Chuck Norris Joke


  • a man, a myth.