Cảm biến BME280 được sử dụng để đo các chỉ số liên quan đến nhiệt độ môi trường, áp suất khí quyển và độ ẩm tương đối. Nó chủ yếu được sử dụng trong các ứng dụng web và di động, nơi tiêu thụ điện năng thấp là chìa khóa. Cảm biến này sử dụng để giao tiếp dữ liệu với các bộ điều khiển vi mô. Mặc dù có một số phiên bản BME280 khác nhau có sẵn trên thị trường, nhưng phiên bản chúng tôi sẽ nghiên cứu sử dụng giao thức truyền thông I2C và SPI.
Việc kết nối BME280 với bo mạch ESP32 rất dễ dàng. Chúng ta phải kết nối thiết bị đầu cuối VCC với 3.3V, nối đất với mặt đất (mặt bằng chung), SCL của cảm biến với SCL của mô-đun và SDA của cảm biến với chân SDA của các mô-đun ESP.
Chân I2C trong ESP32 cho SDA là GPIO21 và cho SCL là GPIO22.
Các thành phần bắt buộc:
Chúng tôi sẽ cần các thành phần sau để kết nối bo mạch ESP32 với cảm biến BME280.
Bảng ESP32
Cảm biến BME280
Dây kết nối
Breadboard
Thực hiện theo sơ đồ bên dưới cho mô-đun ESP32 và kết nối chúng cho phù hợp.
Trong một số cảm biến BME280 như được thấy trong sơ đồ kết nối ở trên, thiết bị đầu cuối SCK có nghĩa là chân SCL và được kết nối với chân GPIO tương ứng trên bảng ESP. Tương tự như vậy, thiết bị đầu cuối SDI có nghĩa là chân SDA và được kết nối với chân GPIO tương ứng trên bo mạch. Vin được kết nối với chân 3.3V trên mô-đun và cả bo mạch ESP và cảm biến thường được nối đất.
Theo mặc định, MicroPython không có triển khai thư viện BME280. Tuy nhiên, MicroPyhon cung cấp API I2C của ESP32 và ESP8266 có thể được sử dụng để đọc các giá trị nhiệt độ, độ ẩm và áp suất từ cảm biến BME280. May mắn thay, có một thư viện có sẵn được phát triển bởi Robert và có thể được tải xuống từ liên kết này.
Chúng tôi sẽ sử dụng Thonny IDE để lập trình ESP32 của chúng tôi. Đảm bảo rằng bạn đã cài đặt phiên bản IDE mới nhất trong hệ thống của mình. Mở một tệp mới trong Thonny. Sao chép thư viện được cung cấp dưới đây. Lưu nó vào ESP32 với tên BME280.py trong thư mục lib.
from machine import I2C
import time
# BME280 default address.
BME280_I2CADDR = 0x76
# Operating Modes
BME280_OSAMPLE_1 = 1
BME280_OSAMPLE_2 = 2
BME280_OSAMPLE_4 = 3
BME280_OSAMPLE_8 = 4
BME280_OSAMPLE_16 = 5
# BME280 Registers
BME280_REGISTER_DIG_T1 = 0x88 # Trimming parameter registers
BME280_REGISTER_DIG_T2 = 0x8A
BME280_REGISTER_DIG_T3 = 0x8C
BME280_REGISTER_DIG_P1 = 0x8E
BME280_REGISTER_DIG_P2 = 0x90
BME280_REGISTER_DIG_P3 = 0x92
BME280_REGISTER_DIG_P4 = 0x94
BME280_REGISTER_DIG_P5 = 0x96
BME280_REGISTER_DIG_P6 = 0x98
BME280_REGISTER_DIG_P7 = 0x9A
BME280_REGISTER_DIG_P8 = 0x9C
BME280_REGISTER_DIG_P9 = 0x9E
BME280_REGISTER_DIG_H1 = 0xA1
BME280_REGISTER_DIG_H2 = 0xE1
BME280_REGISTER_DIG_H3 = 0xE3
BME280_REGISTER_DIG_H4 = 0xE4
BME280_REGISTER_DIG_H5 = 0xE5
BME280_REGISTER_DIG_H6 = 0xE6
BME280_REGISTER_DIG_H7 = 0xE7
BME280_REGISTER_CHIPID = 0xD0
BME280_REGISTER_VERSION = 0xD1
BME280_REGISTER_SOFTRESET = 0xE0
BME280_REGISTER_CONTROL_HUM = 0xF2
BME280_REGISTER_CONTROL = 0xF4
BME280_REGISTER_CONFIG = 0xF5
BME280_REGISTER_PRESSURE_DATA = 0xF7
BME280_REGISTER_TEMP_DATA = 0xFA
BME280_REGISTER_HUMIDITY_DATA = 0xFD
class Device:
"""Class for communicating with an I2C device.
Allows reading and writing 8-bit, 16-bit, and byte array values to
registers on the device."""
def __init__(self, address, i2c):
"""Create an instance of the I2C device at the specified address using
the specified I2C interface object."""
self._address = address
self._i2c = i2c
def writeRaw8(self, value):
"""Write an 8-bit value on the bus (without register)."""
value = value & 0xFF
self._i2c.writeto(self._address, value)
def write8(self, register, value):
"""Write an 8-bit value to the specified register."""
b=bytearray(1)
b[0]=value & 0xFF
self._i2c.writeto_mem(self._address, register, b)
def write16(self, register, value):
"""Write a 16-bit value to the specified register."""
value = value & 0xFFFF
b=bytearray(2)
b[0]= value & 0xFF
b[1]= (value>>8) & 0xFF
self.i2c.writeto_mem(self._address, register, value)
def readRaw8(self):
"""Read an 8-bit value on the bus (without register)."""
return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF
def readU8(self, register):
"""Read an unsigned byte from the specified register."""
return int.from_bytes(
self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF
def readS8(self, register):
"""Read a signed byte from the specified register."""
result = self.readU8(register)
if result > 127:
result -= 256
return result
def readU16(self, register, little_endian=True):
"""Read an unsigned 16-bit value from the specified register, with the
specified endianness (default little endian, or least significant byte
first)."""
result = int.from_bytes(
self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF
if not little_endian:
result = ((result << 8) & 0xFF00) + (result >> 8)
return result
def readS16(self, register, little_endian=True):
"""Read a signed 16-bit value from the specified register, with the
specified endianness (default little endian, or least significant byte
first)."""
result = self.readU16(register, little_endian)
if result > 32767:
result -= 65536
return result
def readU16LE(self, register):
"""Read an unsigned 16-bit value from the specified register, in little
endian byte order."""
return self.readU16(register, little_endian=True)
def readU16BE(self, register):
"""Read an unsigned 16-bit value from the specified register, in big
endian byte order."""
return self.readU16(register, little_endian=False)
def readS16LE(self, register):
"""Read a signed 16-bit value from the specified register, in little
endian byte order."""
return self.readS16(register, little_endian=True)
def readS16BE(self, register):
"""Read a signed 16-bit value from the specified register, in big
endian byte order."""
return self.readS16(register, little_endian=False)
class BME280:
def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,
**kwargs):
# Check that mode is valid.
if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,
BME280_OSAMPLE_8, BME280_OSAMPLE_16]:
raise ValueError(
'Unexpected mode value {0}. Set mode to one of '
'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '
'BME280_ULTRAHIGHRES'.format(mode))
self._mode = mode
# Create I2C device.
if i2c is None:
raise ValueError('An I2C object is required.')
self._device = Device(address, i2c)
# Load calibration values.
self._load_calibration()
self._device.write8(BME280_REGISTER_CONTROL, 0x3F)
self.t_fine = 0
def _load_calibration(self):
self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)
self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)
self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)
self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)
self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)
self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)
self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)
self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)
self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)
self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)
self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)
self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)
self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)
self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)
self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)
self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)
h4 = self._device.readS8(BME280_REGISTER_DIG_H4)
h4 = (h4 << 24) >> 20
self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F)
h5 = self._device.readS8(BME280_REGISTER_DIG_H6)
h5 = (h5 << 24) >> 20
self.dig_H5 = h5 | (
self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F)
def read_raw_temp(self):
"""Reads the raw (uncompensated) temperature from the sensor."""
meas = self._mode
self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)
meas = self._mode << 5 | self._mode << 2 | 1
self._device.write8(BME280_REGISTER_CONTROL, meas)
sleep_time = 1250 + 2300 * (1 << self._mode)
sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
time.sleep_us(sleep_time) # Wait the required time
msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)
lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)
xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)
raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
return raw
def read_raw_pressure(self):
"""Reads the raw (uncompensated) pressure level from the sensor."""
"""Assumes that the temperature has already been read """
"""i.e. that enough delay has been provided"""
msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)
lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)
xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)
raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
return raw
def read_raw_humidity(self):
"""Assumes that the temperature has already been read """
"""i.e. that enough delay has been provided"""
msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)
lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)
raw = (msb << 8) | lsb
return raw
def read_temperature(self):
"""Get the compensated temperature in 0.01 of a degree celsius."""
adc = self.read_raw_temp()
var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11)
var2 = ((
(((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) *
self.dig_T3) >> 14
self.t_fine = var1 + var2
return (self.t_fine * 5 + 128) >> 8
def read_pressure(self):
"""Gets the compensated pressure in Pascals."""
adc = self.read_raw_pressure()
var1 = self.t_fine - 128000
var2 = var1 * var1 * self.dig_P6
var2 = var2 + ((var1 * self.dig_P5) << 17)
var2 = var2 + (self.dig_P4 << 35)
var1 = (((var1 * var1 * self.dig_P3) >> 8) +
((var1 * self.dig_P2) >> 12))
var1 = (((1 << 47) + var1) * self.dig_P1) >> 33
if var1 == 0:
return 0
p = 1048576 - adc
p = (((p << 31) - var2) * 3125) // var1
var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
var2 = (self.dig_P8 * p) >> 19
return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)
def read_humidity(self):
adc = self.read_raw_humidity()
# print 'Raw humidity = {0:d}'.format (adc)
h = self.t_fine - 76800
h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) +
16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h *
self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) *
self.dig_H2 + 8192) >> 14))
h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4)
h = 0 if h < 0 else h
h = 419430400 if h > 419430400 else h
return h >> 12
@property
def temperature(self):
"Return the temperature in degrees."
t = self.read_temperature()
ti = t // 100
td = t - ti * 100
return "{}.{:02d}C".format(ti, td)
@property
def pressure(self):
"Return the temperature in hPa."
p = self.read_pressure() // 256
pi = p // 100
pd = p - pi * 100
return "{}.{:02d}hPa".format(pi, pd)
@property
def humidity(self):
"Return the humidity in percent."
h = self.read_humidity()
hi = h // 1024
hd = h * 100 // 1024 - hi * 100
return "{}.{:02d}%".format(hi, hd)
ThingSpeak là một API mã nguồn mở được sử dụng để lưu trữ hoặc truy xuất dữ liệu bằng giao thức HTTP hoặc MQTT. Điều này diễn ra qua Internet hoặc thông qua mạng LAN. Chúng tôi sẽ sử dụng API này để xuất bản các chỉ số cảm biến từ BME280 được tích hợp với bảng ESP32 của chúng tôi. Trong ThingSpeak, bạn có thể truy cập dữ liệu của mình từ mọi nơi trên thế giới. Bạn có thể hiển thị dữ liệu của mình theo biểu đồ và đồ thị.
ThingSpeak API được sử dụng miễn phí nhưng chúng ta sẽ phải tạo một Tài khoản MathWorks.
Đầu tiên, hãy truy cập trang web
Cửa sổ sau sẽ xuất hiện. Nhấp vào nút 'Bắt đầu miễn phí'.
Bây giờ bạn sẽ được chuyển hướng đến cửa sổ tài khoản. Nếu bạn đã có sẵn tài khoản MathWorks, bạn có thể sử dụng tài khoản đó để đăng nhập. Nếu không, bạn sẽ phải tạo một cái mới. Nhấp vào 'Tạo một tài khoản!' để tạo tài khoản MathWorks mới.
Khi bạn đã đăng nhập thành công, bạn sẽ nhận được thông báo sau:
Nhấp vào 'OK'.
Chúng tôi sẽ bắt đầu bằng cách tạo một kênh mới cho dự án của chúng tôi. Chuyển đến Kênh > Kênh của tôi. Sau đó nhấp vào 'Kênh mới'.
Bạn sẽ được nhắc đặt tên cho kênh của mình. Chúng tôi sẽ đưa ra một tên, một số mô tả và đánh dấu trường đầu tiên. Trong phần này, chúng tôi sẽ hướng dẫn bạn cách xuất bản nhiều dữ liệu. Bạn có thể sử dụng bất kỳ tên, mô tả và trường nào theo sở thích của mình. Có tổng cộng tám trường mà chúng tôi có thể thêm vào kênh của mình cùng một lúc. Chúng tôi sẽ đánh dấu vào ba trường đầu tiên và thêm tên. Nhấp vào 'Lưu kênh' để tiếp tục.
Bây giờ kênh của bạn sẽ được tạo.
Chuyển đến chế độ xem riêng tư và nhấp vào biểu tượng bút chì trên đầu mỗi Biểu đồ Trường. Điều này sẽ cho phép chúng tôi tùy chỉnh các biểu đồ theo sở thích của chúng tôi. Bạn có thể thêm chi tiết cho phù hợp. Sau khi bạn nhấn nút Lưu, biểu đồ sẽ được cập nhật về cài đặt bạn vừa đặt.
Sau đó, chuyển đến tab khóa API và nhấp vào nó. Bây giờ bạn sẽ có thể truy cập khóa API duy nhất của mình. Lưu nó và giữ nó an toàn vì bạn sẽ cần nó sau này trong mã chương trình.
Mở Thonny IDE của bạn và đi tới Tệp > Mới để mở tệp mới. Sao chép mã được cung cấp bên dưới trong tệp đó. Mã này sẽ hoạt động với bảng ESP32 của bạn. Bạn chỉ cần thay thế thông tin đăng nhập mạng và khóa API của mình.
import machine
import urequests
from machine import Pin, SoftI2C
import network, time
import BME280
i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=10000) #initializing the I2C method
HTTP_HEADERS = {'Content-Type': 'application/json'}
THINGSPEAK_WRITE_API_KEY = '****************'
UPDATE_TIME_INTERVAL = 5000 # in ms
last_update = time.ticks_ms()
ssid='YOUR_SSID'
password='YOUR_PASSWORD'
# Configure ESP32 as Station
sta_if=network.WLAN(network.STA_IF)
sta_if.active(True)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.connect(ssid, password)
while not sta_if.isconnected():
pass
print('network config:', sta_if.ifconfig())
while True:
if time.ticks_ms() - last_update >= UPDATE_TIME_INTERVAL:
bme = BME280.BME280(i2c=i2c) #BME280 object created
temperature = bme.temperature #reading the value of temperature
humidity = bme.humidity #reading the value of humidity
pressure = bme.pressure #reading the value of pressure
bme_readings = {'field1':temperature, 'field2':pressure, 'field3':humidity}
request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = bme_readings, headers = HTTP_HEADERS )
request.close()
print(bme_readings)
Đầu tiên, chúng tôi sẽ nhập tất cả các thư viện cần thiết cần thiết cho dự án này. Điều này bao gồm lớp Pin và lớp SoftI2C từ mô-đun máy. Điều này là do chúng tôi phải chỉ định chân cho giao tiếp I2C. Chúng tôi cũng nhập mô-đun thời gian để chúng tôi có thể thêm độ trễ giữa các lần đọc của chúng tôi. Ngoài ra, hãy nhập thư viện BME280 mà chúng tôi đã tải lên ESP32 trước đó. Hơn nữa, chúng tôi cũng sẽ yêu cầu urequests và mô-đun mạng.
import machine
import urequests
from machine import Pin, SoftI2C
import network, time
import BME280
Bây giờ, chúng ta khởi tạo phương thức I2C bằng cách đưa ra ba đối số cho nó. Đối số đầu tiên chỉ định chân GPIO cho SCL. Điều này được đưa ra dưới dạng GPIO22 cho ESP32. Tham số thứ hai chỉ định chân GPIO cho SDA. Điều này được đưa ra dưới dạng GPIO21 cho ESP32. Hãy nhớ rằng, đây là các chân I2C mặc định cho SCL và SDA mà chúng tôi đã sử dụng cho bảng ESP32. Tham số thứ ba chỉ định tần số tối đa cho SCL được sử dụng.
i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=10000) #initializing the I2C method
Chúng tôi sẽ chỉ định khóa API ghi ThingSpeak của chúng tôi trong biến 'THINGSPEAK_WRITE_API_KEY' sẽ giữ khóa API duy nhất của chúng tôi. Đây là chìa khóa mà chúng tôi đã lưu trước đó khi tạo kênh mới trong ThingSpeak. Hơn nữa, chúng ta sẽ tạo một biến 'HTTP_HEADERS' khác sẽ được sử dụng sau này trong khi kết nối với API.
HTTP_HEADERS = {'Content-Type': 'application/json'}
THINGSPEAK_WRITE_API_KEY = '****************'
Chúng tôi sẽ cập nhật số đọc của cảm biến sau mỗi 5 giây. Hơn nữa, biến 'last_update' sẽ theo dõi thời gian.
UPDATE_TIME_INTERVAL = 5000 # in ms
last_update = time.ticks_ms()
Tiếp theo, chúng ta sẽ tạo hai biến để lưu các giá trị SSID và mật khẩu. Bạn phải thay thế cả hai bằng thông tin đăng nhập mạng của mình để kết nối thành công với bộ định tuyến của mình.
ssid='YOUR_SSID'
password='YOUR_PASSWORD'
Các dòng mã sau đây sẽ định cấu hình bảng ESP32 làm trạm. ESP32 sẽ kết nối với mạng cục bộ và địa chỉ IP sẽ được in trong Thonny Shell. Điều này là cần thiết vì chúng tôi muốn gửi số đọc cảm biến đến ThingSpeak.
sta_if=network.WLAN(network.STA_IF)
sta_if.active(True)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.connect(ssid, password)
while not sta_if.isconnected():
pass
print('network config:', sta_if.ifconfig())
Bên trong vòng lặp while, trước tiên chúng tôi sẽ kiểm tra xem đã đến lúc cập nhật các chỉ số cảm biến hay chưa. Sau đó, chúng ta sẽ tạo một đối tượng của BME280 có tên bme và truy cập nhiệt độ, độ ẩm và áp suất thông qua nó. Các chỉ số cảm biến này sẽ được lưu trong các biến tương ứng của chúng: nhiệt độ, độ ẩm và áp suất.
while True:
if time.ticks_ms() - last_update >= UPDATE_TIME_INTERVAL:
bme = BME280.BME280(i2c=i2c) #BME280 object created
temperature = bme.temperature #reading the value of temperature
humidity = bme.humidity #reading the value of humidity
pressure = bme.pressure #reading the value of pressure
bme_readings = {'field1':temperature, 'field2':pressure, 'field3':humidity}
request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = bme_readings, headers = HTTP_HEADERS )
request.close()
print(bme_readings)
Tiếp theo, chúng ta sẽ tạo một biến 'bme_readings' để giữ các bài đọc sẽ được xuất bản vào các trường tương ứng của chúng. Trong trường hợp của chúng tôi, chúng tôi đã đặt Trường 1 cho số đọc nhiệt độ, Trường 2 cho chỉ số áp suất và Trường 3 cho số đọc độ ẩm tương ứng.
Sau đó, chúng tôi sẽ đăng ba số đọc cảm biến này vào các trường tương ứng của chúng bằng cách sử dụng phương thức urequests.post() và chỉ định Thông tin đăng nhập ThingSpeak mà chúng tôi đã xác định trước đó. Sau đó đóng yêu cầu.
Ngoài ra, cả ba bài đọc này sẽ được in trên thiết bị đầu cuối vỏ sau thời gian trễ 5 giây.
bme_readings = {'field1':temperature, 'field2':pressure, 'field3':humidity}
request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = bme_readings, headers = HTTP_HEADERS )
request.close()
print(bme_readings)
Sau khi bạn đã tải mã của mình lên bảng phát triển ESP32, hãy nhấn nút ENABLE của nó.
Trong thiết bị đầu cuối Thonny shell, bạn sẽ có thể xem bảng ESP32 được kết nối. Sau mỗi 5 giây, các kết quả đọc cảm biến mới sẽ tiếp tục xuất hiện khi chúng được xuất bản lên các trường tương ứng.
Tiếp theo, mở API ThingSpeak và bạn sẽ có thể thấy các chỉ số nhiệt độ, áp suất và độ ẩm cập nhật sau 5 giây trong biểu đồ của mình.