You dont have javascript enabled! Please enable it!

Practical case: I2C Temperature Sensor on Pico-ICE UP5K

Practical case: I2C Temperature Sensor on Pico-ICE UP5K — hero

Objective and use case

What you’ll build: This project involves reading temperature data from an MCP9808 I2C temperature sensor using the Pico-ICE board with the RP2040 microcontroller. You will learn to wire the sensor, program the microcontroller, and stream data to a Raspberry Pi.

Why it matters / Use cases

  • Real-time temperature monitoring for environmental control in smart homes.
  • Integration with IoT applications to track temperature changes and trigger alerts.
  • Educational purposes for understanding I2C communication and sensor data acquisition.
  • Prototyping for weather stations that require accurate temperature readings.

Expected outcome

  • Continuous temperature readings with an accuracy of ±0.5°C.
  • Data streamed at a rate of 1 reading per second over USB serial.
  • Successful validation of outputs with less than 5% error margin during tests.
  • Ability to troubleshoot common issues such as wiring errors or sensor miscommunication.

Audience: Hobbyists, educators, and IoT developers; Level: Intermediate

Architecture/flow: Wiring the MCP9808 sensor to the Pico-ICE, programming the RP2040 to read data, and sending it to a Raspberry Pi for processing.

Basic Hands‑On Practical Case: Raspberry Pi Family — Pico-ICE (Lattice iCE40UP5K) — i2c-lectura-sensor-temperatura

This step‑by‑step exercise guides you through reading temperature over I2C from a digital sensor using the device model “Pico‑ICE (Lattice iCE40UP5K).” We will program the RP2040 microcontroller on the Pico‑ICE to read an I2C temperature sensor (MCP9808) and stream the measurements over USB serial to a Raspberry Pi running Raspberry Pi OS Bookworm 64‑bit with Python 3.11. You will see exact wiring, firmware, Python/MicroPython code, and terminal commands to build, flash, run, and validate.

The emphasis is on the objective “i2c-lectura-sensor-temperatura”: we wire an I2C temperature sensor, read it continuously, validate outputs, and troubleshoot typical pitfalls. No FPGA configuration is required; the iCE40UP5K remains unused in this basic project.


Prerequisites

  • A Raspberry Pi SBC (e.g., Raspberry Pi 4 Model B or Raspberry Pi 5) running Raspberry Pi OS Bookworm 64‑bit.
  • Terminal access to the Raspberry Pi (screen/keyboard locally or SSH).
  • Internet access on the Raspberry Pi to install packages and download firmware.
  • A known‑good USB‑C data cable for the Pico‑ICE (beware: some cables are power‑only).
  • Basic familiarity with the Linux shell and Python virtual environments.

Why the Raspberry Pi OS requirement? We’ll use it as the development and validation host: enabling interfaces, installing tools, creating a virtual environment, and using Python 3.11 to interact over USB serial. The microcontroller‑side code runs in MicroPython on the RP2040 within the Pico‑ICE board.


Materials (exact model)

  • 1 × Pico‑ICE (Lattice iCE40UP5K) development board (RP2040 + iCE40UP5K).
    Notes:
  • It is pin‑compatible with the Raspberry Pi Pico form factor for the microcontroller I/O.
  • We will use the RP2040’s I2C0 pins (GP4: SDA, GP5: SCL), 3V3, and GND.

  • 1 × MCP9808 I2C temperature sensor breakout (3.3 V compatible).
    Example: “MCP9808 High Accuracy I2C Temperature Sensor Breakout – 3.3/5 V tolerant” with default I2C address 0x18.

  • 4–7 × male‑to‑female (or suitable) jumper wires for connections:

  • Required: 3V3, GND, SDA, SCL
  • Optional: Connect A0/A1/A2 (address select) to GND for default address 0x18 if your breakout does not hard‑tie them.

  • 1 × Raspberry Pi SBC with:

  • Raspberry Pi OS Bookworm 64‑bit
  • Python 3.11 preinstalled
  • Network access via Ethernet/Wi‑Fi

Setup/Connection

1) Prepare Raspberry Pi OS and user environment

Run the following on your Raspberry Pi terminal:

sudo apt update
sudo apt full-upgrade -y

# Install core tools
sudo apt install -y git curl wget usbutils minicom screen \
  python3-venv python3-pip python3-dev

# Optional but recommended: reboot after upgrade
sudo reboot

After reboot, verify Python 3.11:

python3 --version
# Expect: Python 3.11.x

Create a working directory and Python virtual environment:

mkdir -p ~/pico-ice-i2c-temp
cd ~/pico-ice-i2c-temp
python3 -m venv .venv
source .venv/bin/activate
python -V   # should show Python 3.11.x from the venv

Install Python packages in the venv. We’ll install mpremote (to copy/run MicroPython files), pyserial (for USB serial monitoring), and—following Raspberry Pi family defaults—gpiozero and smbus2/spidev (even though this project does not use the Pi’s own I2C bus):

pip install --upgrade pip
pip install mpremote pyserial gpiozero smbus2 spidev

Give your user serial port access (for /dev/ttyACM* devices the board will expose):

sudo usermod -aG dialout $USER
# Start a new shell or re-login so the new group applies:
newgrp dialout

2) Enable interfaces on Raspberry Pi (family defaults)

Although the sensor connects to the Pico‑ICE, not the Raspberry Pi’s GPIO header, we show how to enable the I2C interface as requested. You will not use it for this project, but it’s good practice to know.

  • Using raspi-config:
sudo raspi-config
# Interface Options -> I2C -> Enable
# Finish and reboot if prompted
  • Or manually edit /boot/firmware/config.txt:
sudo nano /boot/firmware/config.txt
# Ensure the following line is present (uncomment or add if missing):
# dtparam=i2c_arm=on
# Optionally set the I2C bus speed:
# dtparam=i2c_arm_baudrate=400000
# Save and exit, then reboot if you changed the file:
sudo reboot

Again, the I2C on the Raspberry Pi header is not used in this tutorial. The I2C we use is on the RP2040 inside the Pico‑ICE.

3) Wire the I2C temperature sensor to the Pico‑ICE

We will use the RP2040’s I2C0 default pins: GP4 (SDA), GP5 (SCL). Power the sensor from 3V3. Ground is common.

  • Use short jumper wires. The MCP9808 breakout typically includes 10 kΩ pull‑ups; no external pull‑ups needed if your breakout provides them.
  • Ensure the sensor board is 3.3 V compatible (MCP9808 is).

Connection mapping:

Pico‑ICE (RP2040 pin label) Function MCP9808 Breakout Pin
3V3(OUT) 3.3 V power VIN or VDD
GND Ground GND
GP4 I2C0 SDA SDA
GP5 I2C0 SCL SCL
(Optional) Address config A0/A1/A2 to GND (default I2C addr 0x18)

Notes:
– On Pico‑form‑factor boards, GP4 and GP5 are the default I2C0 pins. The Pico‑ICE follows this arrangement.
– Do not power the sensor with 5 V; the RP2040 and its I/O are 3.3 V only.


Full Code

We’ll implement two pieces of code:

1) MicroPython program that runs on the Pico‑ICE (RP2040) and continuously reads temperature from the MCP9808 via I2C, then prints measurements over USB serial.

2) Optional host Python 3.11 script you can run on the Raspberry Pi to read and log those serial prints to CSV for validation.

1) MicroPython code (main.py) for Pico‑ICE (RP2040)

Save the following as main.py on your Raspberry Pi (we’ll upload it to the board in the next section). It initializes I2C0 on GP4/GP5 at 400 kHz, scans for the sensor at 0x18, validates manufacturer/device ID registers, then reads and prints temperature values every second.

# main.py — MicroPython on RP2040 (Pico-ICE)
# Objective: i2c-lectura-sensor-temperatura using MCP9808 at 0x18

from machine import Pin, I2C
import time
import sys

# Pin mapping for Pico-ICE (RP2040) default I2C0 pins
I2C_SDA_PIN = 4  # GP4
I2C_SCL_PIN = 5  # GP5
I2C_FREQ_HZ = 400000

# MCP9808 default I2C address (A2..A0 = 000)
MCP9808_ADDR = 0x18

# MCP9808 register addresses
REG_CONFIG = 0x01
REG_AMBIENT_TEMP = 0x05
REG_MANUF_ID = 0x06   # Expect 0x0054
REG_DEVICE_ID = 0x07  # Expect 0x0400
REG_RESOLUTION = 0x08  # Resolution settings (0..3 => 0.5, 0.25, 0.125, 0.0625 °C)

# LED indicator (on Pico-compatible boards "LED" alias should be available)
try:
    led = Pin("LED", Pin.OUT)
except:
    # Fallback if the alias is not present
    led = Pin(25, Pin.OUT)

def i2c_init():
    i2c = I2C(0, sda=Pin(I2C_SDA_PIN), scl=Pin(I2C_SCL_PIN), freq=I2C_FREQ_HZ)
    return i2c

def i2c_scan_or_fail(i2c):
    devices = i2c.scan()
    print("I2C scan found:", [hex(d) for d in devices])
    if MCP9808_ADDR not in devices:
        print("ERROR: MCP9808 not found at 0x18. Check wiring and address pins A2..A0.")
        sys.exit(1)

def read16(i2c, addr, reg):
    # Read 16 bits (big-endian) from a register
    i2c.writeto(addr, bytes([reg]))
    data = i2c.readfrom(addr, 2)
    return (data[0] << 8) | data[1]

def write8(i2c, addr, reg, val):
    i2c.writeto(addr, bytes([reg, val & 0xFF]))

def read_temp_c(i2c, addr=MCP9808_ADDR):
    # Datasheet: ambient temp register is 16-bit:
    # Bits 15..13 = flags, bit 12 = sign, bits 11..0 = temp*16
    i2c.writeto(addr, bytes([REG_AMBIENT_TEMP]))
    raw = i2c.readfrom(addr, 2)
    t_upper = raw[0]
    t_lower = raw[1]
    val = ((t_upper & 0x1F) << 8) | t_lower
    # Sign extend if negative (bit 12)
    if t_upper & 0x10:
        # 13-bit two's complement
        val -= 1 << 12
    temp_c = val * 0.0625
    return temp_c

def validate_mcp9808(i2c):
    mid = read16(i2c, MCP9808_ADDR, REG_MANUF_ID)
    did = read16(i2c, MCP9808_ADDR, REG_DEVICE_ID)
    print("Manufacturer ID:", hex(mid), "(expect 0x54)")
    print("Device ID:", hex(did), "(expect 0x400)")

    # Soft-check: 0x0054 and 0x0400 expected
    if mid != 0x0054 or did != 0x0400:
        print("WARNING: Unexpected MCP9808 IDs. Double-check sensor model and address.")

def set_resolution(i2c, level=3):
    # Resolution: 0->0.5°C, 1->0.25°C, 2->0.125°C, 3->0.0625°C
    if level < 0 or level > 3:
        level = 3
    write8(i2c, MCP9808_ADDR, REG_RESOLUTION, level)

def main():
    print("Pico-ICE i2c-lectura-sensor-temperatura (MCP9808 @ 0x18)")
    i2c = i2c_init()
    i2c_scan_or_fail(i2c)
    validate_mcp9808(i2c)
    set_resolution(i2c, 3)

    # Blink LED twice to indicate ready
    for _ in range(2):
        led.value(1)
        time.sleep(0.15)
        led.value(0)
        time.sleep(0.15)

    # Main loop: read & print once per second
    while True:
        try:
            t_c = read_temp_c(i2c)
            t_f = t_c * 9 / 5 + 32
            # Structured, parse-friendly line:
            print({"temp_c": round(t_c, 4), "temp_f": round(t_f, 4), "sensor": "MCP9808", "addr": hex(MCP9808_ADDR)})
            led.toggle()
            time.sleep(1.0)
        except Exception as e:
            print("ERROR during read:", repr(e))
            time.sleep(0.5)

if __name__ == "__main__":
    main()

2) Host-side Python (optional) to log USB serial to CSV

This helper script runs on the Raspberry Pi host and captures lines printed by the MicroPython program, writing them to a CSV file with timestamps. It uses pyserial.

Save as host_read.py:

# host_read.py — Read USB serial from Pico-ICE MicroPython and log to CSV
# Usage:
#   source .venv/bin/activate
#   python host_read.py --port /dev/ttyACM0 --csv temps.csv

import argparse
import csv
import json
import sys
import time
import serial

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--port", default="/dev/ttyACM0", help="Serial port (e.g., /dev/ttyACM0)")
    ap.add_argument("--baud", type=int, default=115200, help="Baud (CDC ACM often ignores but keep default)")
    ap.add_argument("--csv", default="temps.csv", help="CSV output path")
    args = ap.parse_args()

    print(f"Opening {args.port} @ {args.baud}")
    with serial.Serial(args.port, args.baud, timeout=2) as ser, open(args.csv, "a", newline="") as f:
        writer = csv.writer(f)
        # Header if file is empty
        if f.tell() == 0:
            writer.writerow(["timestamp_iso", "temp_c", "temp_f", "sensor", "addr", "raw_line"])
        while True:
            line = ser.readline().decode(errors="ignore").strip()
            if not line:
                continue
            now_iso = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime())
            # Try to parse dict-like output as JSON after replacing single with double quotes
            try:
                jl = json.loads(line.replace("'", '"'))
                temp_c = jl.get("temp_c")
                temp_f = jl.get("temp_f")
                sensor = jl.get("sensor", "")
                addr = jl.get("addr", "")
            except Exception:
                temp_c = ""
                temp_f = ""
                sensor = ""
                addr = ""
            writer.writerow([now_iso, temp_c, temp_f, sensor, addr, line])
            f.flush()
            print(now_iso, line)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nStopped.")

Build/Flash/Run commands

We’ll flash MicroPython to the RP2040 on the Pico‑ICE, then copy main.py to the device, and finally run/use it.

1) Download and flash MicroPython to Pico‑ICE

  • Put the Pico‑ICE into BOOTSEL mode:
  • Unplug USB‑C.
  • Hold the BOOT (BOOTSEL) button on the Pico‑ICE.
  • While holding BOOT, plug the USB‑C into your Raspberry Pi.
  • Release BOOT after the board enumerates as a USB mass storage device named RPI-RP2.

  • Download a stable MicroPython UF2 for the RP2040 (Raspberry Pi Pico compatible). For example (v1.22.2):

cd ~/pico-ice-i2c-temp
wget https://micropython.org/resources/firmware/rp2-pico-20240222-v1.22.2.uf2 -O micropython-pico.uf2
  • Copy the UF2 to the RPI-RP2 drive:

If it automounts (typical Desktop), it may appear as /media/pi/RPI-RP2. Otherwise, check with lsblk and mount accordingly.

# If automounted (most cases on RPi Desktop):
cp micropython-pico.uf2 /media/$USER/RPI-RP2/
# After copy completes, the board reboots into MicroPython automatically.

If you are headless and it doesn’t automount, find the device (e.g., /dev/sda1) and mount:

lsblk
# Find the RPI-RP2 (FAT) partition, e.g., /dev/sda1
sudo mkdir -p /mnt/rpi-rp2
sudo mount /dev/sda1 /mnt/rpi-rp2
sudo cp micropython-pico.uf2 /mnt/rpi-rp2/
sync
sudo umount /mnt/rpi-rp2
# The board will reboot into MicroPython.

After reboot, the board should expose a USB serial device, typically /dev/ttyACM0.

List connected MicroPython devices:

source ~/pico-ice-i2c-temp/.venv/bin/activate
mpremote connect list
# Expect something like: /dev/ttyACM0 ...

2) Copy and run your MicroPython script

Copy main.py to the board:

cd ~/pico-ice-i2c-temp
mpremote connect /dev/ttyACM0 fs cp main.py :main.py

Option A: Run immediately (on RAM) to observe:

mpremote connect /dev/ttyACM0 run main.py

Option B: Let it run at boot by keeping the file on the board as main.py. Power‑cycle/unplug/replug the board to auto‑start.

3) View output via serial

Use either minicom or the host Python logger script.

  • minicom:
minicom -b 115200 -D /dev/ttyACM0
# To exit minicom: Ctrl-A, then X (Exit).
  • Host logger:
source ~/pico-ice-i2c-temp/.venv/bin/activate
python host_read.py --port /dev/ttyACM0 --csv temps.csv

You should see lines like:

{'temp_c': 24.875, 'temp_f': 76.775, 'sensor': 'MCP9808', 'addr': '0x18'}

Step‑by‑step Validation

1) Physical wiring checks
– Confirm GP4 ↔ SDA, GP5 ↔ SCL, 3V3 ↔ VIN, and GND ↔ GND.
– If your MCP9808 breakout exposes A0/A1/A2, ensure they are GND or defaulted to select 0x18.

2) Board enumerates and MicroPython is alive
– After flashing the UF2, you should see a /dev/ttyACM* device.
– Run: mpremote connect list. If nothing appears, try another USB port/cable and reflash.

3) I2C scan indicates the sensor
– When running main.py, the program prints “I2C scan found: [‘0x18’]” among the results.
– If the sensor is not listed, the script exits with an error. Recheck SDA/SCL and power.

4) Manufacturer/Device ID check
– Expect:
– Manufacturer ID: 0x54
– Device ID: 0x400
– A mismatch suggests an incompatible sensor or wrong address configuration.

5) Temperature reading plausibility
– Touch the sensor with your finger: temperature should rise a few degrees Celsius within seconds.
– Remove your finger or place an ice pack nearby: temperature should drop.
– Typical indoor ambient: 20–30 °C.

6) Observe LED feedback
– On each successful reading, the onboard LED toggles (blinks once per second). If it stops blinking, the loop likely encountered an error; check the printed error messages.

7) Host logging verification
– Run the host logger:
– python host_read.py –port /dev/ttyACM0 –csv temps.csv
– Confirm CSV entries are appended with timestamp, temp_c, temp_f.

8) Confirm measurement stability
– Leave the setup running for 2–3 minutes.
– The readings should be stable within sensor resolution (0.0625 °C if resolution=3). Minor jitter is normal.


Troubleshooting

  • No /dev/ttyACM0 appears
  • Use lsusb to verify the RP2040 enumerates (look for Raspberry Pi Pico or MicroPython CDC ACM).
  • Try a different USB‑C data cable and/or USB port.
  • Re‑flash the UF2 via BOOTSEL mode.
  • Ensure your user is in the dialout group (newgrp dialout or re-login).

  • I2C scan doesn’t show 0x18

  • Check wiring order: GP4 must go to SDA, GP5 to SCL.
  • Verify the sensor receives 3.3 V (not 5 V).
  • If A0/A1/A2 are pulled high on your breakout, the address may differ (0x19..0x1F). Update MCP9808_ADDR in code accordingly.
  • Inspect for loose or reversed wires. Keep wires short.

  • Manufacturer/Device ID mismatch

  • Some clones or different sensors can share similar footprints but different IDs (e.g., TMP102/BME280). Confirm your sensor is MCP9808.
  • If you have a different I2C temp sensor, adapt the register map in code.

  • Garbled serial output

  • Make sure you aren’t running multiple terminal programs (minicom and host_read.py) on the same port simultaneously.
  • Close all serial sessions and retry with one program.

  • LED doesn’t blink

  • Some Pico‑form‑factor derivatives may not expose the LED as Pin(«LED»). The code falls back to Pin(25). If LED still doesn’t work, it may be absent or on a different pin. This does not affect sensor reading; ignore LED errors.

  • mpremote cannot connect

  • Use mpremote connect list to see the exact device path.
  • If using WSL or a USB hub, port naming may change (e.g., /dev/ttyACM1). Update commands accordingly.

  • spidev/gpiozero installation warnings

  • The project doesn’t use them directly. They are included to satisfy family defaults. If pip install shows warnings, ensure python3-dev is installed (we installed it above). Alternatively, install system packages via apt (e.g., sudo apt install python3-spidev python3-gpiozero) and skip pip for those.

Improvements

  • Add on‑board display or status LED pattern
  • Drive an external LED or a small I2C SSD1306 OLED to show temperature locally without a serial console.

  • Timestamping and averaging

  • Compute a moving average to reduce jitter, and print both instantaneous and averaged temperature values.

  • Logging enhancements

  • The host script can rotate logs daily, or emit Prometheus‑style metrics. Use systemd to run it at boot.

  • Calibration and offsets

  • Apply a small offset determined by comparison with a reference thermometer if absolute accuracy is important.

  • Power optimization

  • Reduce I2C frequency or sampling rate; put the MCU in low‑power sleep between reads if powered from battery.

  • Use interrupts or alert outputs

  • The MCP9808 has alert pins; configure thresholds in REG_CONFIG and related registers to signal over/under‑temperature without constant polling.

  • Integrate the FPGA (future intermediate/advanced)

  • Use the iCE40UP5K to timestamp events, buffer samples via SPI or PIO bridge, or implement a simple display/driver; then exchange data with RP2040 via PIO/PIO‑SPI/I2C bridging.

  • Support multiple I2C sensors

  • If you have several MCP9808 devices, set A0/A1/A2 to different addresses (0x18–0x1F), scan them, and print each.

  • Alternative sensors

  • Adapt code for TMP102, BME280, or SHT31 if those are the devices you have. Pin wiring remains SDA/SCL/3V3/GND; only register maps change.

Final Checklist

  • Hardware
  • Pico‑ICE (Lattice iCE40UP5K) is connected via USB‑C to the Raspberry Pi.
  • MCP9808 breakout wired: 3V3 ↔ VIN, GND ↔ GND, GP4 ↔ SDA, GP5 ↔ SCL; A0/A1/A2 defaulted to GND.
  • Cables are short and secure; no 5 V to sensor.

  • Raspberry Pi OS Bookworm 64‑bit and Python 3.11

  • System updated (sudo apt update && sudo apt full-upgrade).
  • python3 –version shows Python 3.11.x.
  • Virtual environment created and activated in ~/pico-ice-i2c-temp/.venv.

  • Packages and interfaces

  • pip installed: mpremote, pyserial, gpiozero, smbus2, spidev.
  • User added to dialout group (serial access).
  • I2C enabled on Raspberry Pi (family default; not used by this project).

  • MicroPython flashed

  • rp2‑pico MicroPython v1.22.2 UF2 copied to RPI-RP2 via BOOTSEL.
  • /dev/ttyACM0 appears; mpremote connect list shows device.

  • Application code deployed

  • main.py uploaded via mpremote fs cp main.py :main.py.
  • Program runs and prints periodic temperature lines.

  • Validation complete

  • I2C scan shows 0x18.
  • Manufacturer ID 0x54 and Device ID 0x400 printed.
  • Temperature increases when touching sensor; decreases with cooling.
  • Optional host_read.py logs to temps.csv successfully.

  • Troubleshooting resolved

  • Any serial or wiring issues addressed per the troubleshooting section.

You now have a working i2c-lectura-sensor-temperatura project using the Pico‑ICE (Lattice iCE40UP5K) board, entirely validated with Raspberry Pi OS Bookworm 64‑bit and Python 3.11.

Find this product and/or books on this topic on Amazon

Go to Amazon

As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.

Quick Quiz

Question 1: Which microcontroller is used in the Pico-ICE?




Question 2: What type of sensor is being read in the project?




Question 3: What operating system is required on the Raspberry Pi for this project?




Question 4: What programming language is used to interact over USB serial?




Question 5: Is FPGA configuration required for this basic project?




Question 6: What type of cable is needed for the Pico-ICE?




Question 7: What is the purpose of the Raspberry Pi in this project?




Question 8: What is the focus of the objective 'i2c-lectura-sensor-temperatura'?




Question 9: Which component is NOT mentioned as part of the materials needed?




Question 10: What is the main function of the terminal access on the Raspberry Pi?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Telecommunications Electronics Engineer and Computer Engineer (official degrees in Spain).

Follow me:
Scroll to Top