← Back to Lectures
HalesAir Logo
HalesAir Β· Session 03

MicroPython
Fundamentals

Halesowen College Β· T Level Data Analytics

Setting up Thonny IDE Variables, loops & functions Reading from the BME680 Printing & formatting data
πŸ–₯️

Part 1
Setting Up Thonny

The IDE designed for exactly this

Thonny β€” Your Development Environment

What Thonny does

  • Text editor with Python syntax highlighting
  • Connects directly to Pico W over USB
  • Interactive REPL β€” type code, see results instantly
  • Upload and run scripts on the device
  • View errors clearly β€” great for beginners
  • Free, cross-platform β€” Windows Β· Mac Β· Linux

Initial setup

1

Open Thonny β†’ Tools β†’ Options β†’ Interpreter

2

Select MicroPython (Raspberry Pi Pico) from the dropdown

3

Select the correct COM port / USB serial device β€” the Pico W appears automatically when plugged in

The REPL shows >>> when connected. Type print("hello") to confirm.

REPL vs Scripts β€” Two Modes of Working

REPL (shell)

  • Read–Eval–Print Loop
  • Type one line β†’ runs immediately
  • Great for testing ideas, checking values
  • Lost when you disconnect or reset
>>> print("hello") hello >>> 2 + 2 4

Script file (.py)

  • Saved to the Pico as main.py
  • Runs automatically every time the Pico powers on
  • This is how our deployed sensor works
  • Save early, save often β€” click the green β–Ά to run

Key rule: The file must be named main.py for it to auto-run on boot. Everything else can have any name.

🐍

Part 2
MicroPython Fundamentals

Variables Β· Types Β· Loops Β· Functions

Variables & Data Types

# Integers count = 10 pin_number = 4 # Floats temperature = 22.5 humidity = 55.1 # Strings unit = "sensor_A" location = "car park" # Booleans wifi_connected = True logging = False

Key rules

  • No need to declare a type β€” Python infers it
  • Use type(x) to check: <class 'float'>
  • Snake_case for variable names: my_variable
  • Variable names are case-sensitive: Temp β‰  temp

Python convention: Comments start with #. Write them constantly β€” your future self will thank you.

Sensor readings come back as floats β€” temperatures like 22.453125. We'll round these for display.

Loops β€” The Sensor's Heartbeat

The while True loop

import time count = 0 while True: count += 1 print(f"Reading {count}") time.sleep(10) # ↑ Runs forever β€” every 10 seconds

Stop a running loop: Press Ctrl+C in the Thonny shell. This sends a KeyboardInterrupt.

Also useful: for loops

# Repeat a fixed number of times for i in range(5): print(f"Trial {i}") # Loop over a list of sites sites = ["car_park", "courtyard"] for site in sites: print(site)
  • Sensors use while True β€” run indefinitely
  • for loops are useful in data analysis scripts
  • Indentation is mandatory β€” 4 spaces, not tabs

Functions β€” Reusable Code Blocks

def read_sensor(sensor): """Read and return all four values.""" t = sensor.temperature h = sensor.humidity p = sensor.pressure g = sensor.gas return t, h, p, g # Call it temp, hum, pres, gas = read_sensor(sensor) print(f"Temp: {temp:.1f}Β°C")

Why bother?

  • Write once, use many times
  • Makes your main loop clean and readable
  • Easier to debug β€” test each function separately
  • def keyword + name + (parameters) + colon
  • Docstrings β€” the triple-quoted string β€” document what it does

{temp:.1f} β€” f-string formatting: .1f means 1 decimal place as a float. .0f = round to integer.

🌑️

Part 3
Reading the BME680

Live data from your sensor

Installing the BME680 Library

What we need

  • Bosch provides a driver, but we use a MicroPython port
  • Library: bme680.py by Robert Hammelrath
  • Wraps the raw I2C commands into simple Python properties
1

Download bme680.py from the shared project folder

2

In Thonny: File β†’ Open β†’ This Computer β†’ select bme680.py

3

File β†’ Save copy β†’ MicroPython device to upload it to the Pico's flash

Verify it's there

>>> import os >>> os.listdir() ['main.py', 'bme680.py']

If bme680.py appears in the list, the library is on the device and ready to import.

Alternative: Use Thonny's Manage packages with micropython-bme680 if your Pico has internet access.

Your First Sensor Reading Script

from machine import I2C, Pin import bme680, time # Set up I2C on bus 0 i2c = I2C(0, sda=Pin(4), scl=Pin(5)) sensor = bme680.BME680_I2C(i2c) # Read loop β€” every 10 seconds while True: temp = sensor.temperature hum = sensor.humidity pres = sensor.pressure gas = sensor.gas print(f"Temp: {temp:.1f}Β°C " f"Hum: {hum:.0f}% " f"Pres: {pres:.0f} hPa " f"IAQ: {gas}") time.sleep(10)

Line by line

  • from machine import β€” built-in Pico hardware library
  • I2C(0, sda=Pin(4), scl=Pin(5)) β€” creates the I2C bus on our wired pins
  • BME680_I2C(i2c) β€” wraps the sensor in a Python object
  • .temperature etc. β€” properties return live float values
  • f"..." β€” f-string formats numbers into readable text
  • time.sleep(10) β€” pause 10 seconds before next read

The gas reading is raw resistance in ohms β€” higher = cleaner air. We'll convert to IAQ index in Session 05.

Activity: Live Sensor Data

⏱ 25 minutes

Type, run, observe, modify β€” in that order.

1

(5 min) Type the sensor script above into a new Thonny file. Save it to the Pico as main.py. Click β–Ά and confirm you see readings in the shell.

2

(5 min) Change time.sleep(10) to time.sleep(2). Re-run. Breathe on the sensor β€” do you see humidity rise? Cup your hand over it β€” does temperature change?

3

(10 min) Add a reading_count variable that increments each loop. Print it alongside the sensor values: "Reading 1 | Temp: 22.1Β°C…"

4

(5 min) Wrap the sensor reading into a read_sensor() function. Call it from the main loop. Compare: is your code cleaner?

πŸ’‘ If you get a ValueError: No I2C device found, check your wiring β€” the sensor isn't responding. Re-run the I2C scan from Session 02.

πŸ“„

Part 4
Formatting Outputs

Making data readable for humans and machines

f-Strings & Number Formatting

temp = 22.453125 hum = 54.812 # Basic f-string print(f"Temp is {temp}") # β†’ Temp is 22.453125 # 1 decimal place print(f"Temp: {temp:.1f}Β°C") # β†’ Temp: 22.5Β°C # 0 decimal places (whole number) print(f"Hum: {hum:.0f}%") # β†’ Hum: 55% # Padding to align columns print(f"T:{temp:6.1f} H:{hum:5.1f}") # β†’ T: 22.5 H: 54.8

Format spec breakdown

:.1f

1 digit after decimal, as a float

:.0f

0 decimal places β€” rounds the float to integer display

:6.1f

Total width 6 characters, 1 decimal β€” pads with spaces for aligned columns

For CSV output in Session 04, we'll swap the human format for {temp:.2f} β€” more precision for data analysis.

Adding a Timestamp

Every data point needs to know when it was collected

import time # time.time() = seconds since 2000-01-01 # (UTC, no RTC β€” use for relative time) t0 = time.time() time.sleep(10) elapsed = time.time() - t0 print(f"Elapsed: {elapsed:.0f} s") # localtime() returns a tuple: # (year, month, day, hour, min, sec, …) now = time.localtime() print(f"{now[0]}-{now[1]:02d}-{now[2]:02d} " f"{now[3]:02d}:{now[4]:02d}:{now[5]:02d}")

Why this matters

  • Without a timestamp, data is just a list of numbers
  • Time enables trend analysis, peak detection, and correlation
  • The Pico W doesn't have a real-time clock (RTC) built in
  • When connected to Wi-Fi, we'll sync time using NTP (Session 04)
  • For local testing, elapsed seconds are sufficient

:02d pads a number to 2 digits with a leading zero β€” so 9 becomes 09. Essential for sortable timestamps.

Coming Up β€” Session 04

Date TBC Β· Data Logging & Transmission

What we'll cover

Writing sensor readings to a CSV file on the Pico, connecting to Wi-Fi, syncing time via NTP, and sending data to a remote server via HTTP POST β€” making your sensor a live data source.

For next time: Keep your main.py from today. We'll extend it to write to a CSV file first, then to the web.

Stretch task: Try adding a simple if condition β€” e.g. print a warning if temp > 27.

Questions? Get in touch:

jwilliams.science Β· HalesAir Project