Using I2C Sensors With Badgeware

Our range of Badgeware devices are much more than conference badges, and in this tutorial we'll use a Tufty 2350 with a Multi-Sensor Stick to show live sensor data from the world around us.

The Multi-Sensor Stick contains three different sensors in one package - BME280, LTR559, and LSM6DS3. But what does each sensor do?

BME280 measures:

  • Temperature
  • Humidity
  • Air Pressure

LTR-559ALS-01 measures:

  • Proximity
  • Light

TLSM6DS3TR-C measures:

  • Orientation
  • Motion

These three sensors, packaged into the Multi-Sensor Stick, all connect to Tufty 2350 via the super-simple Qw/ST connector (find out more about Qw/ST here) and the data they collect is displayed in real-time on Tufty 2350's 2.8 inch IPS display.

So, let's get building!

What You'll Need

If you already have Tufty 2350, but not the STEM kit, then you will also need these.

Optionally, you can 3D print a clip to attach the Multi-Sensor Stick to your Badgeware board.

Building The Project

Connecting up the Multi-Sensor Stick to Tufty 2350 is our first priority, then we will write the code that brings this project together.

  1. Connect the Multi-Sensor Stick to Tufty 2350 using the Qw/ST cable. The port is marked I2C on Tufty. This would also be a good time to attach the 3D printed clip.
  2. Ensure that Tufty 2350 is running the latest firmware.
  3. Connect Tufty 2350 to your computer using a USB Type C data cable.
  4. Double press RESET on Tufty 2350 and a new drive, "Tufty" will appear in the File Manager.
  5. Open the drive and go to the apps folder.
  6. Create a new folder called sensor_app.
  7. Place this file icon.png into the sensor_app folder.
  8. Create a folder called assets inside the sensor_app folder. We won't be using it for this project, but it is a good practice to create the folder should we wish to add images to our apps.
  9. Open Thonny (or your preferred text editor).
  10. Save the blank file as __init__.py to the sensor_app folder on Tufty 2350.
  11. Import a series of modules that will make up the project.
    1. Time: Used to control the speed at which the app will run and check sensor readings.
    2. Machine, I2C: The Machine module enables us to use the GPIO pins of the RP2350 and in this case we specifically use I2C (Inter-Integrated Circuit) I2C is a data protocol used to communicate between the sensors and Tufty 2350. The Qw/ST cable connects the sensors and Tufty 2350 over an I2C interface.
    3. BreakoutBME280: Used to read sensor data from the Multi-Sensor Stick's BME280 temperature and humidity sensor.
    4. LSM6DS3: Used to read sensor data from the LSM6DS3 accelerometer and orientation sensor.
    5. BreakoutLTR559: Used to read light and proximity data from the LTR559 sensor.
      import time
      from machine import I2C
      from breakout_bme280 import BreakoutBME280
      from lsm6ds3 import LSM6DS3, NORMAL_MODE_104HZ
      from breakout_ltr559 import BreakoutLTR559
      
  12. Create three objects to interface the code with the temperature / humidity (BME280), accelerometer / gyroscope (LSM6DS3) and light / proximity (LTR559) sensors. All of these sensors are present on the Multi-Sensor Stick, and using I2C we can address each one individually.
    bme = BreakoutBME280(I2C())
    gyro = LSM6DS3(I2C(), mode=NORMAL_MODE_104HZ)
    ltr = BreakoutLTR559(I2C())
    
  13. Create a variable, last_time and store the current number of "ticks" (milliseconds, ms) since Tufty 2350 was powered on. This will be used as part of a timing mechanism.
    last_time = time.ticks_ms()
    
  14. Create a variable, interval and store a value between 100 and 500. This translates to 100ms, a tenth of a second, to 500ms, half a second. This will ultimately control how fast the updates happen, so we are leaving the duration to your discretion.
    interval = 100
    
  15. Create temporary variables for temperature, air pressure, humidity, accelerometer (x,y,z), gyroscope (x,y,z), light and proximity. We create these now so that we can use them in the update function later. If they are not created, then the code will error.
    temp = 0
    press = 0
    humid = 0
    ax = 0
    ay = 0
    az = 0
    gx = 0
    gy = 0
    gz = 0
    lux = 0
    prox = 0
    
  16. Set the font to "sins", a 7px tall font that is clear and legible, despite its small size.
    screen.font = rom_font.sins
    
  17. Create a function called update. This will run the code contained within every time Tufty 2350 updates the screen.
    def update():
    
  18. Create a series of global variables (variables that can be used inside and outside the update function) that reference timing and sensor data.
    global last_time, interval, temp, press, humid,ax,ay,az,gx,gy,gz,lux,prox
    
  19. Store the current time in ticks (ms) inside an object.
    current_time = time.ticks_ms()
    
  20. Set the screen pen colour to latte (light grey, brown mix) and the use the clear screen function to set the screen to that color. We chose this colour, and the other colours for this app, from the Badgeware colour palette.
    screen.pen = color.latte
    screen.clear()
    
  21. Set the pen colour to lime, then draw rectangle, start a x 10, y 10. Stretch the rectangle 140 pixels across the screen, and then 30 pixels down the screen. To section the various sensor data, we will use coloured rectangles. This rectangle will form the background for the temperature data.
    screen.pen = color.lime
    rect0 = shape.rectangle(10,10,140,30)
    screen.shape(rect0)
    
  22. Create a yellow rectangle underneath the temperature data. This yellow rectangle will be the background for the humidity data.
    screen.pen = color.yellow
    rect1 = shape.rectangle(10,40,140,40)
    screen.shape(rect1)
    
  23. Create a final, orange rectangle that will be the background for the accelerometer / gyroscope sensor data.
    screen.pen = color.orange
    rect2 = shape.rectangle(10,80,140,20)
    screen.shape(rect2)
    
  24. Reset the pen colour to black. This ensures that black text is written over the coloured rectangles.
    screen.pen = color.black
    
  25. Write the current temperature, air pressure and humidity values to the display. At this exact moment in the code, there are no values, hence the 0 values earlier. But in 100 - 500ms, and for every 100 - 500ms thereafter there will be real sensor data. We write the data 10 pixels in (x) and then at 10, 20 and 30 pixels down from the top (y). We use str() to format the numerical data (float values) into text strings.
    screen.text(str(temp), 10,10)
    screen.text(str(press), 10,20)
    screen.text(str(humid), 10,30)
    
  26. Create section headings for "Accelerometer" and "Gyroscope" and place them inside the yellow rectangle which is under the temperature data section. Add the accelerometer (ax,ay,az) and Gyroscope (gz,gy,gz) data inside this section.
    screen.text("Accelerometer",10,40)
    screen.text(str(ax), 10,50)
    screen.text(str(ay), 50,50)
    screen.text(str(az), 90,50)
    screen.text("Gyroscope", 10,60)
    screen.text(str(gx), 10,70)
    screen.text(str(gy), 50,70)
    screen.text(str(gz), 90,70)
    
  27. Inside the orange rectangle, add text and data for the LTR559 sensor. Note that the "Lux" and "Proximity" text are on the same line, becoming headers for the data underneath.
    screen.text("Lux", 10,80)
    screen.text(str(lux), 10,90)
    screen.text("Proximity", 80,80)
    screen.text(str(prox), 80,90)
    
  28. Using an if conditional test, check if the timer difference between when the app started and the current time is greater than or equal to the interval. If true, then the code within the conditional test will run.
    if time.ticks_diff(current_time, last_time) >= interval:
    
  29. Take a reading from the BME280 and store the data inside an object called readings. The returned data is a list that comprises of temperature, pressure and humidity data.
        readings = bme.read()
    
  30. From readings extract the temperature data and store it inside a variable called temp. The temperature data is the first item in the list, so we use [0] to access it. You will also notice that we are using round() to round the returned value to one decimal place.
        temp = round(readings[0],1)
    
  31. Update the temp variable so that we create a sentence to format the temperature data into Celsius. This will be the text that is displayed in the green rectangle.
        temp = "Temperature: {}C".format(temp)
    
  32. From the readings object, extract the air pressure data and store it in a variable called press. Then update the variable to format the data into a sentence for display inside the green rectangle.
        press = readings[1]
        press = "Pressure: {}hpa".format(press)
    
  33. Grab the humidity data from the readings objects and round the value to one decimal place before storing it in an object called humid. Then place the data in a sentence and save it to the humid object.
        humid = round(readings[2],1)
        humid = "Humidity: {}%".format(humid)
    
  34. Grab the gyroscope and accelerometer data for all axes and store it inside objects that are named accordingly. A refers to accelerometer, then it is x,y,z. G refers to gyroscope and the x,y,z format is repeated.
        ax, ay, az, gx, gy, gz = gyro.get_readings()
    
  35. Take a reading from the lux / proximity sensor and save to an object called ltr_sensor then breakout the data for lux and proximity and save to variables of the same name.
        ltr_sensor = ltr.get_reading()
        lux = ltr_sensor[BreakoutLTR559.LUX]
        prox = ltr_sensor[BreakoutLTR559.PROXIMITY]
    
  36. Update the last_time variable with the current time.
        last_time = current_time
    
  37. Outside of any loops or functions we now called the update function using run(). This will run the code every time the screen updates.
  38. Save the code into the sensor_app folder as __init__.py.
  39. Press RESET on Tufty 2350 and then navigate to the app in the launcher.
  40. Press B to launch the app and you will see live sensor data on Tufty 2350's display.

Complete Code Listing

import time
from machine import I2C
from breakout_bme280 import BreakoutBME280
from lsm6ds3 import LSM6DS3, NORMAL_MODE_104HZ
from breakout_ltr559 import BreakoutLTR559

bme = BreakoutBME280(I2C())
gyro = LSM6DS3(I2C(), mode=NORMAL_MODE_104HZ)
ltr = BreakoutLTR559(I2C())

last_time = time.ticks_ms()
interval = 100
temp = 0
press = 0
humid = 0
ax = 0
ay = 0
az = 0
gx = 0
gy = 0
gz = 0
lux = 0
prox = 0
screen.font = rom_font.sins

def update():
    global last_time, interval, temp, press, humid,ax,ay,az,gx,gy,gz,lux,prox
    current_time = time.ticks_ms()
    screen.pen = color.latte
    screen.clear()
    screen.pen = color.lime
    rect0 = shape.rectangle(10,10,140,30)
    screen.shape(rect0)
    screen.pen = color.yellow
    rect1 = shape.rectangle(10,40,140,40)
    screen.shape(rect1)
    screen.pen = color.orange
    rect2 = shape.rectangle(10,80,140,20)
    screen.shape(rect2)
    screen.pen = color.black
    screen.text(str(temp), 10,10)
    screen.text(str(press), 10,20)
    screen.text(str(humid), 10,30)
    screen.text("Accelerometer",10,40)
    screen.text(str(ax), 10,50)
    screen.text(str(ay), 50,50)
    screen.text(str(az), 90,50)
    screen.text("Gyroscope", 10,60)
    screen.text(str(gx), 10,70)
    screen.text(str(gy), 50,70)
    screen.text(str(gz), 90,70)
    screen.text("Lux", 10,80)
    screen.text(str(lux), 10,90)
    screen.text("Proximity", 80,80)
    screen.text(str(prox), 80,90)

    if time.ticks_diff(current_time, last_time) >= interval:
        # Do the thing: read and print the temperature
        readings = bme.read()
        temp = round(readings[0],1)
        temp = "Temperature: {}C".format(temp)
        press = readings[1]
        press = "Pressure: {}hpa".format(press)
        humid = round(readings[2],1)
        humid = "Humidity: {}%".format(humid)
        ax, ay, az, gx, gy, gz = gyro.get_readings()
        ltr_sensor = ltr.get_reading()
        lux = ltr_sensor[BreakoutLTR559.LUX]
        prox = ltr_sensor[BreakoutLTR559.PROXIMITY]
        last_time = current_time

run(update)

What Have We Learnt?

In this tutorial we have learnt:

  • How to connect a Qw/ST device to Badgeware.
  • How to read sensor data.
  • How to display text and vector shapes on Tufty 2350's screen.
  • How to time updates on Tufty 2350.

If you would like to take your skills further, we have an excellent graphical sensor example from one of our in-house software developers here.

That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.