KevsRobots Learning Platform
49% Percent Complete
By Kevin McAleer, 11 Minutes
Datasheets seem intimidating - pages of technical specifications, electrical diagrams, and confusing terminology. But you don’t need to understand everything! This lesson teaches you how to find the 20% of information you’ll use 80% of the time.
Think of datasheets like a reference manual for your car. You don’t read the entire manual cover-to-cover - you jump to the section about changing oil or fixing a flat tire. Same with datasheets.
A datasheet is the official technical documentation for an electronic component. It contains:
Important: Datasheets are written by engineers for engineers. They’re comprehensive but not always beginner-friendly. That’s okay - you’ll learn to extract what you need.
Search for: [component name] datasheet pdf
Examples:
Tip: Get datasheets directly from manufacturer websites (Bosch, ST Microelectronics, Adafruit, etc.) rather than third-party sites.
Most datasheets follow this structure:
Your roadmap to finding information quickly
High-level overview of what the component does
MOST IMPORTANT - Shows physical pins and their functions
Voltage ranges, current consumption, operating temperatures
How the component works internally
IMPORTANT FOR I2C/SPI - All internal configuration registers
Example circuits and usage notes
Physical dimensions and footprint
Let’s walk through the BME280 datasheet and extract useful information. You can follow along by downloading it from Bosch’s website.
Where to look: First few pages, usually a “Specifications” table
What to find:
BME280 example (from page 4):
Supply voltage (VDD): 1.71V to 3.6V ✓ (3.3V from Pico is fine)
Interface: I2C and SPI ✓ (We can use I2C)
I2C address: 0x76 or 0x77 ✓ (Noted for our code)
Operating range: -40 to +85°C ✓ (Covers all normal use)
Current consumption: 3.6 µA @ 1 Hz ✓ (Very low power)
Why this matters: Confirms the sensor will work with your Raspberry Pi Pico (3.3V) and tells you the I2C address to use in code.
Where to look: “Pin configuration” or “Pinout” section
What to find:
BME280 example (from page 8):
Pin 1: GND - Ground
Pin 2: CSB - Chip Select (SPI) / I2C address select
Pin 3: SDI - SPI Data In / I2C SDA
Pin 4: SCK - SPI Clock / I2C SCL
Pin 5: SDO - SPI Data Out / I2C address bit
Pin 6: VDDIO - Interface power supply
Pin 7: GND - Ground
Pin 8: VDD - Power supply
Simplified for I2C use:
Note: Most breakout boards simplify this to just 4 pins: VCC, GND, SCL, SDA
Where to look: “I2C interface” section
What to find:
BME280 example (from page 32):
The 7-bit device address is:
- 0x76 if SDO pin is connected to GND
- 0x77 if SDO pin is connected to VDDIO
In practice:
# Most BME280 boards default to 0x76
BME280_ADDR = 0x76
# Some boards use 0x77 - try both if unsure
devices = i2c.scan()
if 0x76 in devices:
BME280_ADDR = 0x76
elif 0x77 in devices:
BME280_ADDR = 0x77
Where to look: “Memory map” or “Register description”
What to find:
BME280 example (from page 26-27):
Key registers:
| Address | Name | Description | Access |
|---|---|---|---|
| 0xD0 | id | Chip ID (should be 0x60) | Read |
| 0xE0 | reset | Reset command | Write |
| 0xF2 | ctrl_hum | Humidity control | R/W |
| 0xF3 | status | Device status | Read |
| 0xF4 | ctrl_meas | Measurement control | R/W |
| 0xF5 | config | Configuration | R/W |
| 0xF7-0xFE | data | Measurement data | Read |
Using this in code:
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
# Read chip ID register (0xD0) to verify it's a BME280
chip_id = i2c.readfrom_mem(BME280_ADDR, 0xD0, 1)[0]
print(f"Chip ID: 0x{chip_id:02X}") # Should print: Chip ID: 0x60
# Read status register (0xF3) to check if measuring
status = i2c.readfrom_mem(BME280_ADDR, 0xF3, 1)[0]
measuring = (status >> 3) & 0x01
print(f"Measuring: {measuring}")
Registers contain multiple settings packed into single bytes.
Example: ctrl_meas register (0xF4) from BME280 datasheet:
Bits [7:5] - osrs_t (temperature oversampling)
Bits [4:2] - osrs_p (pressure oversampling)
Bits [1:0] - mode (sleep, forced, normal)
How to set specific bits:
# We want: temp x2, pressure x16, normal mode
# temp x2 = 010 (binary) in bits [7:5]
# press x16 = 101 (binary) in bits [4:2]
# normal = 11 (binary) in bits [1:0]
# Binary: 01010111
# Hex: 0x57
ctrl_meas_value = 0b01010111 # or 0x57
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([ctrl_meas_value]))
print("Configured: Temp x2, Pressure x16, Normal mode")
Breaking it down:
# Method 1: Build byte from components
TEMP_OVERSAMPLE_2 = 0b010 # x2
PRESS_OVERSAMPLE_16 = 0b101 # x16
MODE_NORMAL = 0b11 # Normal mode
ctrl_meas = (TEMP_OVERSAMPLE_2 << 5) | (PRESS_OVERSAMPLE_16 << 2) | MODE_NORMAL
# Result: 0b01010111 (0x57)
# Method 2: Use hex directly from datasheet calculations
ctrl_meas = 0x57
# Both are equivalent!
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([ctrl_meas]))
Where to look: “Data readout” or “Output data” section
What to find:
BME280 example (page 29):
Measurement data starts at address 0xF7:
0xF7: press_msb (pressure most significant byte)
0xF8: press_lsb (pressure least significant byte)
0xF9: press_xlsb (pressure extra bits [7:4])
0xFA: temp_msb
0xFB: temp_lsb
0xFC: temp_xlsb [7:4]
0xFD: hum_msb
0xFE: hum_lsb
Reading and combining:
# Read all 8 bytes at once
data = i2c.readfrom_mem(BME280_ADDR, 0xF7, 8)
# Combine bytes into 20-bit values
pressure_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
temperature_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
humidity_raw = (data[6] << 8) | data[7]
print(f"Raw values: Temp={temperature_raw}, Press={pressure_raw}, Hum={humidity_raw}")
Why this format? The BME280 uses 20-bit values for temperature and pressure (more precision) but only 16-bit for humidity.
Look for tables titled:
Example search terms in PDF: “register”, “address”, “0x”
Many registers pack multiple settings into one byte:
Register 0xF4 (8 bits total):
┌────────┬────────┬──────┐
│ [7:5] │ [4:2] │ [1:0]│
│ Temp │ Press │ Mode │
│ sample │ sample │ │
└────────┴────────┴──────┘
Reading bit fields:
# Read register
reg_value = i2c.readfrom_mem(addr, 0xF4, 1)[0]
# Extract temp oversampling (bits 7:5)
temp_os = (reg_value >> 5) & 0b111
# Extract pressure oversampling (bits 4:2)
press_os = (reg_value >> 2) & 0b111
# Extract mode (bits 1:0)
mode = reg_value & 0b11
print(f"Temp OS: {temp_os}, Press OS: {press_os}, Mode: {mode}")
Datasheets often provide startup sequences:
BME280 example (page 19):
1. Wait for sensor startup (2ms)
2. Read calibration data from registers 0x88-0xA1 and 0xE1-0xE7
3. Set humidity oversampling in register 0xF2
4. Set temp/pressure oversampling and mode in register 0xF4
5. Optionally set config register 0xF5 for standby and filter
In code:
from machine import I2C, Pin
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
# Step 1: Wait for startup
time.sleep_ms(2)
# Step 2: Read calibration (simplified - library handles this)
# cal_data = i2c.readfrom_mem(BME280_ADDR, 0x88, 24)
# Step 3: Set humidity oversampling x1
i2c.writeto_mem(BME280_ADDR, 0xF2, bytes([0x01]))
# Step 4: Set temp x2, pressure x16, normal mode
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([0x57]))
# Step 5: Set config - standby 1000ms, filter x16
i2c.writeto_mem(BME280_ADDR, 0xF5, bytes([0xB0]))
print("BME280 initialized according to datasheet sequence")
Let’s find everything needed to read temperature from the BME280:
Complete example:
from machine import I2C, Pin
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
# Use datasheet info
ADDR = 0x76 # From page 32
CTRL_MEAS = 0xF4 # From page 27
TEMP_MSB = 0xFA # From page 29
# Configure (from page 27 - temp x2, normal mode)
i2c.writeto_mem(ADDR, CTRL_MEAS, bytes([0x27]))
# Wait for measurement
time.sleep_ms(10)
# Read temperature (from page 29 - 3 bytes)
temp_data = i2c.readfrom_mem(ADDR, TEMP_MSB, 3)
temp_raw = (temp_data[0] << 12) | (temp_data[1] << 4) | (temp_data[2] >> 4)
print(f"Raw temperature: {temp_raw}")
# Note: Real temperature requires calibration compensation (page 23-25)
Practice datasheet skills:
Download the BME280 datasheet from Bosch’s website
Find the reset register - What address? What value to write?
# Answer: Register 0xE0, write 0xB6 to reset
i2c.writeto_mem(0x76, 0xE0, bytes([0xB6]))
# Answer: Register 0xD0, should return 0x60
chip_id = i2c.readfrom_mem(0x76, 0xD0, 1)[0]
assert chip_id == 0x60, "Not a BME280!"
As a beginner, you can usually skip:
Focus on:
Using libraries (recommended for most projects):
Understanding datasheets (recommended for learning):
Best approach: Use libraries for projects, but read datasheets to understand them. You’ll write better code and debug faster.
In this lesson, you mastered:
You can now approach any I2C or SPI device datasheet with confidence, finding the information you need without getting overwhelmed.
Armed with datasheet knowledge, let’s dive deeper into the BME280’s advanced features. In the next lesson, we’ll explore power modes, oversampling, filtering, and optimization techniques that aren’t covered in simple tutorials.
You can use the arrows ← → on your keyboard to navigate between lessons.
Comments