Python Programming Tutorial: Getting Started with the Raspberry Pi

Pages
Contributors: Shawn Hymel
Favorited Favorite 30

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:

Fritzing diagram of using a Pi Wedge to connect to a TMP102 temperature sensor

Connecting directly to the Raspberry Pi:

Fritzing diagram of Raspberry Pi connected to a TMP102 temperature sensor

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.

Reading temperature using Python and I2C

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.