Python Programming Tutorial: Getting Started with the Raspberry Pi
Experiment 4: I2C Temperature Sensor
In addition to analog sensors and SPI chips, you'll often find sensors (and other devices) that rely on the Inter-Integrated Chip (IIC or I2C) protocol. This is a 2-wire bus that contains a clock and data channel. The master (Raspberry Pi) and device (sensor) can communicate on the same data wire.
To see this protocol in action, we'll write a program to talk to a TMP102 Temperature Sensor. We'll use the smbus Python module to handle the low-level communication for us. Note that "SMBus" stands for "System Management Bus" and is another protocol layer built on top of the I2C protocol. By using smbus, we lose out on a few I2C abilities (e.g. clock stretching), but we can still talk to many I2C sensors.
Recommended Reading
- I2C - A detailed look at how the I2C protocol works
Hardware Connections
Refer back to Experiment 1 to look at the Pinout chart.
- Connect SDA1 (GPIO2, pin 3) to SDA on the TMP102
- Connect SCL1 (GPIO3, pin 5) to SCL on the TMP102
- Connect power (3.3 V) to VCC on the TMP102
- Connect ground (GND) to GND on the TMP102
Connecting through a Pi Wedge:
Connecting directly to the Raspberry Pi:
Code: Read and Calculate Temperature
Depending on your version of Raspbian, you may or may not have to install the smbus package (e.g. Raspbian Lite does not come with some Python packages pre-installed). In a terminal, enter the following:
language:bash
sudo apt-get install python3-smbus
In a new file, copy in the following code:
language:python
import time
import smbus
i2c_ch = 1
# TMP102 address on the I2C bus
i2c_address = 0x48
# Register addresses
reg_temp = 0x00
reg_config = 0x01
# Calculate the 2's complement of a number
def twos_comp(val, bits):
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
return val
# Read temperature registers and calculate Celsius
def read_temp():
# Read temperature registers
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
# NOTE: val[0] = MSB byte 1, val [1] = LSB byte 2
#print ("!shifted val[0] = ", bin(val[0]), "val[1] = ", bin(val[1]))
temp_c = (val[0] << 4) | (val[1] >> 4)
#print (" shifted val[0] = ", bin(val[0] << 4), "val[1] = ", bin(val[1] >> 4))
#print (bin(temp_c))
# Convert to 2s complement (temperatures can be negative)
temp_c = twos_comp(temp_c, 12)
# Convert registers value to temperature (C)
temp_c = temp_c * 0.0625
return temp_c
# Initialize I2C (SMBus)
bus = smbus.SMBus(i2c_ch)
# Read the CONFIG register (2 bytes)
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
print("Old CONFIG:", val)
# Set to 4 Hz sampling (CR1, CR0 = 0b10)
val[1] = val[1] & 0b00111111
val[1] = val[1] | (0b10 << 6)
# Write 4 Hz sampling back to CONFIG
bus.write_i2c_block_data(i2c_address, reg_config, val)
# Read CONFIG to verify that we changed it
val = bus.read_i2c_block_data(i2c_address, reg_config, 2)
print("New CONFIG:", val)
# Print out temperature every second
while True:
temperature = read_temp()
print(round(temperature, 2), "C")
time.sleep(1)
Save the file (e.g. tmp102.py), and run it with Python:
language:bash
python tmp102.py
You should see the 2 bytes in the CONFIG register be updated and then the temperature is printed to the screen every second.
Code to Note:
Unlike SPI, I2C relies on a set of addresses and registers. This is because SPI is configured to talk to one chip at a time while I2C can share many devices on one bus. To avoid conflicts, addresses are assigned to devices (by the manufacturer) so that each one knows when the host (the Pi) is trying to talk to it. The address for our TMP102 is 0x48
.
Each time we want to talk to the TMP102, we must send out its address (0x48) on the bus. Only then can we send the memory location (or address) of the register that we want to read from or write to on the TMP102. Note that for most I2C devices, a register is a location in the device's memory that stores 8 bits (1 byte) of data. Sometimes, this data controls the function of the device (as in the case of the CONFIG register). Other times, the registers hold the sensor reading data (as in the case of the temperature register).
We use the following command to read 2 bytes from the temperature register in the TMP102:
language:python
val = bus.read_i2c_block_data(i2c_address, reg_temp, 2)
These values are stored as a list [x, y]
in the val
variable. By looking at the TMP102 datasheet, we see that temperature is 12 bits. When we read the two bytes that contain this reading, we need to remove the last 4 bits from the second byte. We also move the first byte over 4 bits:
language:python
temp_c = (val[0] << 4) | (val[1] >> 4)
In order to display negative numbers for the temperature, values from the TMP102 can come in the form of Two's Complement. In this, the first bit of the 12-bit number determines if the value is positive or negative (0 for positive, 1 for negative). See this article to learn more about Two's Complement.
To convert a Two's Complement number to a negative number in Python, we check to see if the first bit is 0 or 1. If it is 0, then we just use the number as is (it's positive!). If it's a 1, we subtract the max negative number of the Two's Complement (212=4096 in this case) from our number.
language:python
if (val & (1 << (bits - 1))) != 0:
val = val - (1 << bits)
Challenge: Change the CONFIG register so that the TMP102 updates its temperature reading 8 times per second (instead of 8). Additionally, print out the temperature in degrees Fahrenheit. Hint: see page 7 of the TMP102 datasheet to see which bits need to be changed in the CONFIG register.