In this tutorial, we will learn how to publish sensor readings to ThingSpeak using Raspberry Pi Pico W with MicroPython. ThingSpeak provides an API to send data to their analytics and we can make an HTTP POST request from Pico W to send data. Hence, we will also learn how to perform HTTP requests from Raspberry Pi Pico W.
For demonstration, we will transmit BME280 sensor readings to ThingSpeak easily and interactively show them on graphs. Any suitable sensor can be used such as DS18B20, BME680, LM35, and MPU6050 but for this article, we will use a BME280 sensor which is used to gauge temperature, pressure, and humidity.
We will cover the subsequent content in this article:
- Introduction to BME280 sensor
- Connecting BME280 sensor with the Raspberry Pi Pico W development board
- Getting ThingSpeak API Ready
- Publish multiple fields of sensor readings to ThingSpeak (Temperature, humidity, and pressure)
Prerequisites
Before we start this tutorial, make sure you are familiar with and have the latest version Python 3 in your system, have set up MicoPython in Raspberry Pi Pico, and have a running Integrated Development Environment(IDE) in which we will be doing the programming. We will be using the same Thonny IDE as we have done previously in the getting started guide. If you have not followed our previous tutorial, you check here:
BME280 Introduction
The BME280 sensor is used to measure readings regarding ambient temperature, barometric pressure, and relative humidity. It is mostly used in web and mobile applications where low power consumption is key. This sensor uses I2C or SPI to communicate data with the micro-controllers. Although there are several different versions of BME280 available in the market, the one we will be studying uses I2C communication protocol.
I2C means Inter-Integrated Circuit and works on the principle of the synchronous, multi-master multi-slave system. With BME280 and the microcontroller, the Raspberry Pi Pico W acts as a master, and the BME280 sensor as a slave because it is an external device, acts as a slave. The Raspberry Pi Pico W communicates with the BME280 sensor through the I2C protocol to give temperature, barometric pressure, and relative humidity readings.
Pinout Diagram
The figure below shows the BME280 sensor and its pinout.
- VCC: connected with 3.3V
- SCL: used to generate the clock signal
- SDA: used in sending and receiving data
Interfacing Raspberry Pi Pico W with BME280
This section shows how to connect Raspberry Pi Pico with BME280 sensor.
We will require the following components:
- Raspberry Pi Pico
- BME280 Sensor
- Connecting Wires
- Breadboard
The connection of BME280 with the Raspberry Pi Pico W is very simple. We have to connect the VCC terminal with 3.3V, ground with the ground (common ground), SCL of the sensor with SCL of the board, and SDA of the sensor with the SDA pin of the board.
Raspberry Pi Pico W I2C Pins
Raspberry Pi Pico W has two I2C controllers. Both I2C controllers are accessible through GPIO pins of Raspberry Pi Pico. The following table shows the connection of GPIO pins with both I2C controllers. Each connection of the controller can be configured through multiple GPIO pins as shown in the figure. But before using an I2C controller, you should configure in software which GPIO pins you want to use with a specific I2C controller.
I2C Controller | GPIO Pins |
I2C0 – SDA | GP0/GP4/GP8/GP12/GP16/GP20 |
I2C0 – SCL | GP1/GP5/GP9/GP13/GP17/GP21 |
I2C1 – SDA | GP2/GP6/GP10/GP14/GP18/GP26 |
I2C1 – SCL | GP3/GP7/GP11/GP15/GP19/GP27 |
The connections between the two devices which we are using can be seen below.
BME280 | Raspberry Pi Pico W |
VCC | 3.3V |
SDA | GP20 (I2C0 SDA) |
SCL | GP21 (I2C0 SCL) |
GND | GND |
We have used the same connections as specified in the table above. However, you can use other combinations of SDA/SCL pins as well but remember to change them in the MicroPython script.
Connection Diagram Raspberry Pi Pico W with BME280
We have used the same connections as given in the tables above. All devices will be commonly grounded and will be powered with the same 3.3V pin of Raspberry Pi Pico W.
The diagram below shows the connection diagram of Raspberry Pi Pico W with BME280 sensor.
BME280 MicroPython Library
Create a file with name of BME280.py in Thonny IDE and copy following code to that file. This is a MicroPython library to get temperature, pressure and humidity readings from BME280.
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)
Getting ThingSpeak API Ready
ThingSpeak is an open-source API that is used to store or retrieve data using HTTP or MQTT protocol. This takes place over the Internet or through the LAN. We will use this API to publish sensor readings from BME280 with Raspberry Pi Pico W . In ThingSpeak you can access your data from anywhere in the world. You can display your data in plots and graphs.
For more information about ThingSpeak API, you can have a look at our tutorials given below:
- ESP8266 Wi-Fi Module interfacing with Arduino: Send data to server (ThingSpeak)
- ESP32 HTTP POST using Arduino IDE (ThingSpeak and IFTTT)
- HTTP GET using ESP32 and Arduino IDE (OpenWeatherMap.org and ThingSpeak)
Creating Account
ThingSpeak API is free to use but we will have to create a MathWorks Account.
First go to the following website: https://thingspeak.com/
The following window will appear. Click on the ‘Get Started for Free’ button.
Now you will be redirected to the account window. If you already have an existing MathWorks account you can use that to log in. Otherwise, you will have to create a new one. Click ‘Create One!’ to make a new MathWorks account.
When you have successfully signed in you will receive the following notification:
Click ‘OK’.
Publish to multiple fields of sensor readings to ThingSpeak (Temperature, Humidity and Pressure)
We will start by creating a new channel for our project. Go to Channels > My Channels. Then click ‘New Channel’.
You will be prompted to give a name to your channel. We will give a name, some description and mark the first field. In this section, we will show you how to publish multiple data. You can use any name, description, and field according to your preference. There are a total of eight fields that we can add to our channel at the same time. We will tick the first three fields and add the names. Click ‘Save Channel’ to proceed.
Your channel will now be created.
Go to private view and click the pencil icon on top of each Field Chart. This will let us customize the graphs according to our preference. You can add details accordingly. After you press the Save button, the chart will get updated to the settings you just set.
After that go to the API key tab and click it. You will now be able to access your unique API key. Save it and keep it secure as you will need it later in the program code.
Raspberry Pi Pico W ThingSpeak MicroPython Script
Open your Thonny IDE and go to File > New to open a new file. Copy the code given below in that file. This code will work with your Raspberry Pi Pico W. You just have to replace the network credentials and your API key.
import machine
import urequests
from machine import Pin, I2C
import network, time
import BME280
i2c = I2C(0, sda=Pin(20), scl=Pin(21), freq=400000) # initializing the I2C method
HTTP_HEADERS = {"Content-Type": "application/json"}
THINGSPEAK_WRITE_API_KEY = "Enter_Your_Write_API_KEY"
UPDATE_TIME_INTERVAL = 5000 # in ms
last_update = time.ticks_ms()
ssid = "Replace_with_wifi_ssid"
password = "Replace_with_wifi_pswd"
# Configure Raspberry Pi Pico W 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)
How does the Code Works?
Firstly, we will be importing all the necessary libraries required for this project. This includes the Pin class and SoftI2C class from the machine module. This is because we have to specify the pin for I2C communication. We also import the time module so that we will be able to add a delay in between our readings. Also, import the BME280 library which we have previously uploaded to Raspberry Pi Pico W. Moreover, we will require urequests and network modules as well.
import machine
import urequests
from machine import Pin, I2C
import network, time
import BME280
Now, we initialize the I2C method by giving it three arguments. The first argument specifies the GPIO pin for SCL. This is given as GPIO20 for Raspberry Pi Pico W. The second parameter specifies the GPIO pin for the SDA. This is given as GPIO21 for Raspberry Pi Pico W. Keep in mind, these are the default I2C pins for SCL and SDA which we have used for the Raspberry Pi Pico W. The third parameter specifies the maximum frequency for SCL to be used.
i2c = I2C(0, sda=Pin(20), scl=Pin(21), freq=400000) # initializing the I2C method
We will specify our ThingSpeak write API key in the ‘THINGSPEAK_WRITE_API_KEY’ variable that will hold our unique API key. This is the key that we saved previously when creating a new channel in ThingSpeak. Moreover, we will create another variable ‘HTTP_HEADERS’ that will be used later on while connecting with the API.
HTTP_HEADERS = {'Content-Type': 'application/json'}
THINGSPEAK_WRITE_API_KEY = '****************'
We will update the sensor readings after every 5 seconds. Moreover the ‘last_update’ variable will keep track of the time.
UPDATE_TIME_INTERVAL = 5000 # in ms
last_update = time.ticks_ms()
Connect Raspberry Pi Pico W to Local Network
Next, we will create two variables to save the SSID and the password values. You have to replace both of them with your network credentials to successfully connect with your router.
ssid='YOUR_SSID'
password='YOUR_PASSWORD'
The following lines of code will configure the Raspberry Pi Pico W board as station. The Raspberry Pi Pico W will connect with the local network and the IP address will get printed in the Thonny Shell. This is necessary as we want to send sensor readings to 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())
Get Sensor Readings and Publish to ThingSpeak
Inside the while loop we will first check if it time to update the sensor readings. Then we will create an object of BME280 named bme and access the temperature, humidity and pressure through it. These sensor readings will be saved in their respective variables: temperature, humidity, and pressure.
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)
Next, we will create a variable ‘bme_readings’ to hold the readings that will get published to their respective fields. In our case, we had set Field 1 for temperature readings, Field 2 for pressure readings and Field 3 for humidity readings respectively.
After that we will post these three sensor readings to their respective fields by using urequests.post() method and specifying the ThingSpeak Credentials that we defined previously. After that close the request.
Additionally, all these three readings will get printed on the shell terminal after a delay of 5 seconds.
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)
Demonstration
After you have copied the above codes onto a BME280.py and main.py files, click the ‘Save’ icon to save your program code on your PC. Firstly save BME280.py and then save main.py. After that click on run button.
After you have saved the code, press the Run button to upload the code to your board. Before uploading code make sure the correct board is selected.
In the Thonny shell terminal you will be able to view the Raspberry Pi Pico W board get connected. After every 5 seconds, new sensor readings will keep appearing as they get published to their respective fields.
Next, open the ThingSpeak API and you will be able to see temperature, pressure and humidity readings updating after 5 seconds in your charts.
Video demo:
Conclusion
In conclusion, we were able to learn how to publish sensor readings to ThingSpeak using MicroPython and Raspberry Pi Pico W in a fairly easy way. Likewise, you can publish single or multiple data to ThingSpeak API which you will be able to access from anywhere around the world.
If you find this Raspberry Pi Pico W project useful, you may also like to read: