再論TI TMP102
雖然TMP102只是一個温度感測器, 但因為它使用I2C溝通界面, 讓我們可以學習I2C的用法, 而且架構簡單, 用起來不難, 但又有許多功能可以設定, 玩起來很有意思, 最主要的是其温度感測算是很準, 所以我一直很喜歡這顆。加上網路上也有很多人在用, 討論的文章和驅動程式都很容易找到, 所以這裡我想再談一下這顆的一些用法。
1. 驅動程式的寫法:
其實可以不用自己寫驅動程式, 如果你使用Arduino, 那Sparkfun已經幫大家寫好了: SparkFun_TMP102_Arduino_Library 請直接拿去用就好了! 如果你使用MicroPython, 那khoulihan也寫好一個: khoulihan/micropython-tmp102, 他其實已經寫得很好。
但是我還是自己用MicroPython再寫了一次, 原因是khoulihan的驅動太完整了, TMP102所有的功能都實作了! 可惜的是我想用的一個開發板的Flash空間太小, 放了這個驅動後, 剩下的空間就太小了, 要寫其他的程式就很受限! 另外, 在自己改寫的過程中, 我發現自己又學到一些東西。雖然多花了幾天時間在嘗試, 還是收穫不少!
其實2017年在玩PyMata的時候, 就已經寫過TMP102的驅動, 參見python code for pymata tmp102, 但這個是架在PyMata之上的, 基本上是在PC上面執行, 稱之為驅動, 似乎有些奇怪。但程式邏輯和想法基本上相同。
操作TMP102的方法, 很簡單, 就是設定其暫存器的值就可以了, 如下圖 (取自TMP102的datasheet), TMP102主要有4個暫存器, (還有一個指位暫存器Pointer Register), 要取得温度值, 就讀取Temperature Register的內容值, 再做一些格式轉換即可。要讓TMP102進入省電的Shutdown模式, 就只要將Configuration Register內的某一個位元設成1就可以了。問題是如何指定要存取那個暫存器呢? 那就要在指位暫存器先指定就可以了, 例如, 將指位暫存器設成0x01, 就可以存取Configuration Register。
不過, 在讀或寫暫存器值時, 指位暫存器值的設定方式有點不太相同! 以下分別說明:
讀取暫存器內容值時, 先下一個I2C寫入的指令, 寫入SLA及指位暫存器的值; 然後再下I2C讀取的的指令, 這樣就可以讀到指位暫存器所指之暫存器之內容值了。
上圖(取自TMP102 datasheet), 就是讀取暫存器內容值時使用的I2C指令, 這其實是下2個指令, 一寫一讀。以邏輯分析儀來看, 如下圖, 可能清楚一些。第一個I2C指令寫入0x01的值給指位暫存器, 告訴它接下來要讀取那個暫存器(0x01表示Configuration Register), 第二個I2C指令去讀取時, 就會讀到剛才指定的暫存器的值了。即W[0x48] 0x01, 表示告訢slave address為0x48的TMP102, 後面的讀取是要讀0x01暫存器的值。因此, 後面的R[0x48] 0xE1 0xA0, 讀到的0xE1 0xA0就是暫存器0x01的內容值。寫入暫存器時, 如下圖(取自TMP102 datasheet), 則是一個I2C寫入指令而已, 但要先寫入的是要寫入那個暫存器(即要填入指位暫存器的數值), 接著是第一個位元組, 然後是第二個位元組。因為TMP102的4個主要暫存器都是二個位元組的大小, 所以寫入指令在指位暫存器後都是帶二個位元組。
同樣用邏輯分析儀來看一下, 可能清楚一點。如下圖, W[0x48] 0x01 0xE1 0xA0, 表示向slave address為0x48的TMP102下寫入指令, 將數值寫到0x01暫存器, 第一個位元組寫入0xE1, 第二個位元組寫入0xA0。
了解了如何用I2C和TMP102溝通以(讀取)寫入值到其暫存器之後, 要操作TMP102就很簡單了, 因為所有的功能都是操作其暫存器的值來進行的。例如, 可以讓TMP102進入Shutdown模式, 這個時候, 其耗電量就可以降到0.5uA左右, 非常省電。
The Shutdown-mode bit saves maximum power by shutting down all device circuitry other than the serial interface, reducing current consumption to typically less than 0.5 μA. Shutdown mode enables when the SD bit is 1; the device shuts down when current conversion is completed. When SD is equal to 0, the device maintains a continuous conversion state.
def sleep(self):
config = bytearray(self._get_config())
config[0] = _set_bit(config[0], SHUTDOWN_BIT)
self._set_config(config)
def wakeup(self):
config = bytearray(self._get_config())
config[0] = _clear_bit(config[0], SHUTDOWN_BIT)
self._set_config(config)
def _read_register(self, register):
self._write_register(register)
try:
val = self.bus.readfrom(self.address, 2)
except AttributeError:
val = self.bus.recv(2, addr=self.address)
return val
def _write_register(self, register, value=None):
bvals = bytearray()
bvals.append(register)
if value is not None:
for val in value:
bvals.append(val)
try:
self.bus.writeto(self.address, bvals)
except AttributeError:
self.bus.send(bvals, addr=self.address)
def _get_config(self):
return self._read_register(REGISTER_CONFIG)
def _set_config(self, config):
self._write_register(REGISTER_CONFIG, config)
REGISTER_TEMP = 0
REGISTER_CONFIG = 1
SHUTDOWN_BIT = 0x01
ONE_SHOT_BIT = 0x80
def _set_bit(b, mask):
return b | mask
def _clear_bit(b, mask):
return b & ~mask
class Tmp102(object):
def __init__(self, bus, address):
self.bus = bus
self.address = address
def get_TempC(self, data):
lo = data[1]
hi = data[0]
temp = ((hi * 256) + lo) >> 4
return temp * 0.0625
def _read_register(self, register):
self._write_register(register)
try:
val = self.bus.readfrom(self.address, 2)
except AttributeError:
val = self.bus.recv(2, addr=self.address)
return val
def _write_register(self, register, value=None):
bvals = bytearray()
bvals.append(register)
if value is not None:
for val in value:
bvals.append(val)
try:
self.bus.writeto(self.address, bvals)
except AttributeError:
self.bus.send(bvals, addr=self.address)
def _get_config(self):
return self._read_register(REGISTER_CONFIG)
def _set_config(self, config):
self._write_register(REGISTER_CONFIG, config)
def temperature(self):
return bytearray(self._read_register(REGISTER_TEMP))
def wakeup(self):
config = bytearray(self._get_config())
config[0] = _clear_bit(config[0], SHUTDOWN_BIT)
self._set_config(config)
def sleep(self):
config = bytearray(self._get_config())
config[0] = _set_bit(config[0], SHUTDOWN_BIT)
self._set_config(config)
def set_one_shot(self):
config = bytearray(self._get_config())
config[0] = _set_bit(config[0], ONE_SHOT_BIT)
self._set_config(config)
def get_one_shot(self):
while True:
config = bytearray(self._get_config())
data1 = (config[0] >> 7) & 0x01
if data1 != 0:
break
return bytearray(self._read_register(REGISTER_TEMP))
'''
from xxx import Tmp102
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(29), sda=Pin(31))
sensor = Tmp102(i2c, 0x48)
# periodic read
sensor.wakeup()
sensor.get_TempC(sensor.temperature())
# one-shot read
sensor.sleep()
sensor.set_one_shot()
sensor.get_TempC(sensor.get_one_shot())
'''
留言