I propose today to learn how to detect the devices connected to the bus i2c in MicroPython. For this tutorial, we will retrieve the measurements returned by a BME280 environment sensor that measures temperature, atmospheric pressure and humidity. The data will then be displayed on a very classic 128×64 pixel monochrome SSD1360 OLED display in Arduino and DIY projects. I advise you to use the uPiCraft code editor to develop in MicroPython if you are on Windows. On Linux / Raspberry Pi or macOS, follow this tutorial to learn how to use rshell.
Hardware and circuit for an ESP8266 or ESP32 project
For this tutorial, I used a Wemos d1 mini based ESP8266. The SCL pin is on pin D1 (GPIO5) and the pin SDA on D2 (GPIO4). The OLED screen and the BME280 must be powered by 3V3.
The i2c bus is on pins GPIO22 (SCL) and GPIO21 (SDA). However, pin 21 is missing on the Wemos LoLin32 Lite I am currently using. Fortunately, the library I2C allows to choose other pins as we will see later.
Scan devices connected to the i2c bus in MicroPython
The i2c bus is natively supported by MicroPython. All methods are detailed on the online documentation here. The following methods are available for connecting to a sensor and reading data in the registers. The methods are accessible from the machine class.
- I2C.init (scl, sda, freq = 400000), initializes the i2c bus. The sda and scl pins must be indicated. It is also possible to change the frequency of the bus.
- I2C.deinit (), stop the i2c bus
- I2C.scan (), scan the i2c bus and return the addresses of the devices found
- I2C.start (), generates a Start condition on the bus
- I2C.stop (), or a Stop
- I2C.readinto () reads the bytes on the bus and stores in a buffer
- I2C.write (), write the buffer on the bus
- I2C.readfrom (addr, nbytes, stop = True), reads nbytes on the slave. Return an object
- I2C.readfrom_into (addr, buf, stop = True), same but stores in a buffer the bytes returned
- I2C.writeto (addr, buf, stop = True), allows to write the contents of a buffer to the slave at the indicated address
- I2C.readfrom_mem (addr, memaddr, nbytes, *, addrsize = 8), read at the memory address of the slave nbytes
- I2C.readfrom_mem_into (addr, memaddr, buf, *, addrsize = 8), same but stores in a buffer the nbytes returned by the slave
- I2C.writeto_mem (addr, memaddr, buf, *, addrsize = 8), writes the buffer to the specified slave’s memory
By default, MicroPython automatically converts hex and decimal values. In general, manufacturers indicate the i2c address in hex. This little bit of code does the opposite conversion. You will be able to more easily identify connected devices. Then in the code, you can use either the address in hex or decimal, MicroPython does the automatic conversion.
Create a new script and paste the code below. Save the script as scanneri2c.py, for example, and start the execution by pressing the F5 key.
uPicraft will upload it and launch the script. If the wiring is correct, the scanner must detect the OLED screen (0x3c typically) and the BME280 sensor (0x76 in general). You can also use a BME180 that will not return the humidity level.
Now let’s take a closer look at how we use the i2c bus in MicroPython. First, we need to create an I2C object that will connect to the i2c bus and communicate with the connected devices. On an ESP8266, the SCL pin is on pin D1 (GPIO5) and the pin SDA on D2 (GPIO4). However, the library does not require the use of official pins. As with Arduino code, we can assign other pins. Here, I connected the i2c bus on board pins 22 and 18 the ESP32 Wemos Lolin32 Lite.
import machine i2c = machine.I2C(scl=machine.Pin(22), sda=machine.Pin(18)) # ESP8266 5/4
Then the i2c method. scan () retrieves device addresses as a hex array.
Read the measurements of a BMP180 / BME280 in MicroPython
There are some functional drivers on GitHub or internet. No need to re-invent the wheel. For this tutorial, I used the driver adapted by Catdog2. This driver is based on Adafruit Adafruit_I2C.py library. Go to GitHub to get the BME280 drivers code and paste it into a new script. Save the script as bme280.py. Do F5 to upload it (nothing will run on the board).
I have a weakness for the BME280 because it saves a DHT11 / DHT21 or DHT22 if you want to measure the temperature and humidity. We also gain in compactness and energy saving, essential conditions for projects of objects connected on battery.
Create a new script and paste the following code. The beginning is identical. We start by initializing an I2C object by indicating the bus pins and then create a bme280 object. It is passed in parameter the object i2c. It is also possible to pass the BME280 address as a parameter if it is different from the default address 0x76 (with the previous scanner, it is easy to find it). The values () method retrieves the measures directly formatted with the unit.
import machine, time, bme280 i2c = machine.I2C(scl=machine.Pin(22), sda=machine.Pin(18)) bme = bme280.BME280(i2c=i2c,address=0x76) while True: print("BME280 values:") temp,pa,hum = bme.values print(temp) print(pa) print(hum) time.sleep_ms(2000)
Embed an OLED SSD1306 display into MicroPython
Now that we have concrete measures, we will display them on a small monochrome OLED screen very classic and already seen in several tutorials. uPiCraft already embeds a driver for SSD1306 displays. It is in the Upi_lib section. Copy the ssd1306.py library to the board. If you do not use uPiCraft, you can recover the original drivers on GitHub.
You will need 6 lines of code to display text on an OLED screen in MicroPython. After creating an ssd1306 object that requires horizontal resolution in pixels, the vertical resolution and the i2c object. It can also be passed the screen address if it is different from 0x3c. Compared to the Arduino library, we can pass the screen resolution at initialization without having to go modify the library.
import machine, ssd1306 i2c = machine.I2C(scl=machine.Pin(22), sda=machine.Pin(18)) oled = ssd1306.SSD1306_I2C(128, 64, i2c, 0x3c) oled.fill(0) oled.text("Hello World", 0, 0) oled.show()
We then have some methods to manage the display:
- poweroff(), turns off the screen. Convenient for battery operation.
- contrast(), to adjust the contrast
- invert(), invert the colors of the screen (finally white and black!)
- show(), to refresh the view
- fill(), to fill the screen in black (1) or white (0)
- pixel(), to turn on a particular pixel
- scroll(), scroll the screen.
- text(), to display on text at the indicated x, y position
- Draw lines hline ), vline() or any line line()
- Draw a rect rect rectangle() or rectangle filled fill_rect()
It is a very powerful library that has nothing to envy to Arduino libraries (Adafruit GFX among others). It fully supports the Frame Buffer class of MicroPython. Whatever the resolution of the screen used, just indicate the horizontal and vertical resolution to have a correct display.
Full project code
Here we come to the end of this tutorial. We know how to do a lot of extra things in MicroPython now:
- Scan the i2c bus
- Retrieve data on an i2c sensor
- Display text or simple geometric shapes on a monochrome OLED display SSD1306
Create a new script and paste the following code. Modify the following parameters at the beginning of the code according to your configuration:
- pinScl = 22 #ESP8266 GPIO5 (D1)
- pinSda = 18 #ESP8266 GPIO4 (D2)
- addrOled = 60 #0x3c
- addrBME280 = 118 #0x76
- hSize = 64 # Hauteur ecran en pixels | display heigh in pixels
- wSize = 128 # Largeur ecran en pixels | display width in pixels
Save and send the code with the F5 key.
I have tested with several screens (124×64, 124×32, 64×48), everything works perfectly the first time!
I was really surprised how easy it is to manage an OLED display with MicroPython code. As the code is not compiled, we gain a lot of time for the development. In the next tutorial, we’ll see how to handle multiple Dallas DS18B20 probes.
- ESP32 MicroPython project with several DS18B20 probes, publish the measurements to Domoticz using the HTTP JSON API
- OLED display SSD1306 in MicroPython, example with a I2C digital barometer BME280 (or BMP180)
- MicroPython tutorial, manage WiFi connection at startup on ESP8266 / ESP32
- uPiCraft, a MicroPython IDE dedicated to the development of IoT based on ESP8266, ESP32, microbit, pyBoard
- How to reinstall the MicroPython firmware on an ESP8266 or ESP32 with the esptool.py script
- Unpacking the Wemos ESP32 Lolin32 Lite, testing the firmware MicroPython with a Raspberry Pi 3