Skip to content

Latest commit

 

History

History

06-serial

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Lab 6: I2C serial communication

Components list

  • ESP32 board, USB cable

  • Breadboard

  • DHT12 humidity/temperature sensor

    • Optional: RTC DS3231 and AT24C32 EEPROM memory module
    • Optional: GY-521 module with MPU-6050 microelectromechanical systems
  • SH1106 I2C OLED display 128x64

  • Logic analyzer

  • Jumper wires

    photo_oled

Learning objectives

After completing this lab you will be able to:

  • Understand the I2C communication
  • Perform data transfers between ESP32 and I2C devices
  • Use methods for OLED dispaly in MicroPython
  • Use logic analyzer

The main goal of this laboratory exercise is to gain a comprehensive understanding of serial synchronous communication via the I2C (Inter-Integrated Circuit) bus. This includes grasping the essential structure of address and data frames. Additionally, this exercise provides an opportunity to explore the utilization of classes and methods in MicroPython.

Pre-Lab preparation

  1. Use pinout of the FireBeetle ESP32 board and find out on which pins the SDA and SCL signals are located.

  2. Remind yourself, what the general structure of I2C address and data frame is.

Part 1: I2C bus

I2C (Inter-Integrated Circuit) is a serial communication protocol designed for a two-wire interface, enabling the connection of low-speed devices such as sensors, EEPROMs, A/D and D/A converters, I/O interfaces, and other similar peripherals within embedded systems. Originally developed by Philips, this protocol has gained widespread adoption and is now utilized by nearly all major IC manufacturers.

I2C utilizes just two wires: SCL (Serial Clock) and SDA (Serial Data). Both of these wires should be connected to a resistor and pulled up to +Vdd. Additionally, I2C level shifters are available for connecting two I2C buses with different voltage levels.

In an I2C bus configuration, there is always one Master device and one or more Slave devices. Each Slave device is identified by a unique address.

I2C bus

The initial I2C specifications defined maximum clock frequency of 100 kHz. This was later increased to 400 kHz as Fast mode. There is also a High speed mode which can go up to 3.4 MHz and there is also a 5 MHz ultra-fast mode.

In idle state both lines (SCL and SDA) are high. The communication is initiated by the master device. It generates the Start condition (S) followed by the address of the slave device (SLA). If the bit 0 of the address byte was set to 0 the master device will write to the slave device (SLA+W). Otherwise, the next byte will be read from the slave device (SLA+R). Each byte is supplemented by an ACK (low level) or NACK (high level) acknowledgment bit, which is always transmitted by the device receiving the previous byte.

The address byte is followed by one or more data bytes, where each contains 8 bits and is again terminated by ACK/NACK. Once all bytes are read or written the master device generates Stop condition (P). This means that the master device switches the SDA line from low voltage level to high voltage level before the SCL line switches from high to low.

I2C protocol

Note that, most I2C devices support repeated start condition. This means that before the communication ends with a stop condition, master device can repeat start condition with address byte and change the mode from writing to reading.

Example of I2C communication

Question: Let the following image shows several frames of I2C communication between ATmega328P and a slave device. What circuit is it and what information was sent over the bus?

  Temperature reception from DHT12 sensor

Answer: This communication example contains a total of five frames. After the start condition, which is initiated by the master, the address frame is always sent. It contains a 7-bit address of the slave device, supplemented by information on whether the data will be written to the slave or read from it to the master. The ninth bit of the address frame is an acknowledgment provided by the receiving side.

Here, the address is 184 (decimal), i.e. 101_1100-0 in binary including R/W=0. The slave address is therefore 101_1100 (0x5c) and master will write data to the slave. The slave has acknowledged the address reception, so that the communication can continue.

According to the list of I2C addresses the device could be humidity/temp or pressure sensor. The signals were really recorded when communicating with the humidity and temperature sensor.

The data frame always follows the address one and contains eight data bits from the MSB to the LSB and is again terminated by an acknowledgment from the receiving side. Here, number 2 was written to the sensor. According to the DHT12 sensor manual, this is the address of register, to which the integer part of measured temperature is stored. (The following register contains its decimal part.)

Memory location Description
0x00 Humidity integer part
0x01 Humidity decimal part
0x02 Temperature integer part
0x03 Temperature decimal part
0x04 Checksum

After the repeated start, the same circuit address is sent on the I2C bus, but this time with the read bit R/W=1 (185, 1011100_1). Subsequently, data frames are sent from the slave to the master until the last of them is confirmed by the NACK value. Then the master generates a stop condition on the bus and the communication is terminated.

The communication in the picture therefore records the temperature transfer from the sensor, when the measured temperature is 25.3 degrees Celsius.

Frame # Description
1 Address frame with SLA+W = 184 (0x5c<<1 + 0)
2 Data frame sent to the Slave represents the ID of internal register
3 Address frame with SLA+R = 185 (0x5c<<1 + 1)
4 Data frame with integer part of temperature read from Slave
5 Data frame with decimal part of temperature read from Slave

Part 2: I2C scanner

The goal of this task is to find all devices connected to the I2C bus.

  1. Use breadboard, jumper wires, and connect I2C devices to ESP32 GPIO pins as follows: SDA - GPIO 21, SCL - GPIO 22, VCC - 3.3V, GND - GND.

    Note: Connect the components on the breadboard only when the supply voltage/USB is disconnected! There is no need to connect external pull-up resistors on the SDA and SCL pins, because the internal ones is used.

    firebeetle_pinout

    • Humidity/temperature DHT12 digital sensor

    • SH1106 I2C OLED display 128x64

    • Optional: Humidity/temperature/pressure BME280 sensor

    • Optional: Combined module with RTC DS3231 (Real Time Clock) and AT24C32 EEPROM memory

    • Optional: GY-521 module (MPU-6050 Microelectromechanical systems that features a 3-axis gyroscope, a 3-axis accelerometer, a digital motion processor (DMP), and a temperature sensor).

  2. Within the Thonny IDE, create a new script named 01-i2c_scan.py and perform a scan to detect the slave addresses of connected I2C devices. Endeavor to determine the corresponding chip associated with each address.

    from machine import I2C
    from machine import Pin
    
    # Init I2C using pins GP22 & GP21 (default I2C0 pins)
    i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100_000)
    
    print("Scanning I2C... ", end="")
    addrs = i2c.scan()
    print(f"{len(addrs)} device(s) detected")
    
    for x in addrs:
        print(f"{x}\t{hex(x)}")

Part 3: Communication with I2C devices

The goal of this task is to communicate with the DHT12 temperature and humidity sensor assigned to the I2C slave address 0x5c.

  1. Create a new script named 02-i2c_sensor.py and read data from humidity/temperature DHT12 sensor. Note that, according to the DHT12 manual, the internal DHT12 memory has the following structure.

    Memory location Description
    0x00 Humidity integer part
    0x01 Humidity decimal part
    0x02 Temperature integer part
    0x03 Temperature decimal part
    0x04 Checksum
    from machine import I2C
    from machine import Pin
    
    SENSOR_ADDR = 0x5c
    SENSOR_HUMI_REG = 0
    SENSOR_TEMP_REG = 2
    SENSOR_CHECKSUM = 4
    
    # Init I2C using pins GP22 & GP21 (default I2C0 pins)
    i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400_000)
    # Display device address
    print(f"I2C address       : {hex(i2c.scan()[0])}")
    # Display I2C config
    print(f"I2C configuration : {str(i2c)}")
    
    # readfrom_mem(i2caddr, memaddr, nbytes)
    val = i2c.readfrom_mem(SENSOR_ADDR, SENSOR_TEMP_REG, 2)
    print(f"{val[0]}.{val[1]}°C")
  2. Extend the code and periodically read values from all DHT12 memory locations, print them, and verify the checksum byte.

  3. Use the MicroPython manual and find the description of the following methods from I2C class:

    • I2C.scan()
    • I2C.readfrom()
    • I2C.readfrom_into()
    • I2C.writeto()
    • I2C.writevto()
    • I2C.readfrom_mem()
    • I2C.readfrom_mem_into()
    • I2C.writeto_mem()
  4. Connect the logic analyzer to the I2C bus wires (SCL and SDA) between the microcontroller and the sensor. Launch the logic analyzer software Logic and Start the capture. Saleae Logic software offers a decoding feature to transform the captured signals into meaningful I2C messages. Click to + button in Analyzers part and setup I2C decoder.

    Note: To perform this analysis, you will need a logic analyzer such as Saleae or similar device. Additionally, you should download and install the Saleae Logic 1 software on your computer.

    You can find a comprehensive tutorial on utilizing a logic analyzer in this video.

  5. (Optional) Use BME280 sensor and read humidity, temperature and preassure values.

Part 4: OLED display 128x64

An OLED I2C display, or OLED I2C screen, is a type of display technology that combines an OLED (Organic Light Emitting Diode) panel with an I2C (Inter-Integrated Circuit) interface for communication. The I2C interface simplifies the connection between the display and a microcontroller, making it easier to control and integrate into various electronic projects.

  1. Create a new file sh1106.py consinsting the class for OLED display with SH1106 driver and copy/paste the code to it. To import and use the class, the copy of file must be stored in the ESP32 device as well.

  2. Create a new file 04-i2c_oled.py and write a script to print text on the display.

    from machine import I2C
    from machine import Pin
    from sh1106 import SH1106_I2C
    
    WIDTH = 128  # OLED display width
    HEIGHT = 64  # OLED display height
    
    # Init I2C using pins GP22 & GP21 (default I2C0 pins)
    i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400_000)
    # Display device address
    print(f"I2C address       : {hex(i2c.scan()[0])}")
    # Display I2C config
    print(f"I2C configuration : {str(i2c)}")
    
    # Init OLED display
    oled = SH1106_I2C(WIDTH, HEIGHT, i2c, rotate=180)
    
    # Add some text
    oled.text("Using OLED and", x=0, y=40)
    oled.text("ESP32", x=50, y=50)
    
    
    # WRITE YOUR CODE HERE
    
    
    # Finally update the OLED display so the text is displayed
    oled.show()
  3. Use other methods from sh1106 class and draw lines and rectangles on the display.

    # https://docs.micropython.org/en/latest/esp8266/tutorial/ssd1306.html
    oled.fill_rect(x=0, y=0, w=32, h=32, color=1)
    oled.fill_rect(x=2, y=2, w=28, h=28, color=0)
    oled.vline(x=9, y=8, h=22, color=1)
    oled.vline(x=16, y=2, h=22, color=1)
    oled.vline(x=23, y=8, h=22, color=1)
    oled.fill_rect(x=26, y=24, w=2, h=4, color=1)
    oled.text("MicroPython", x=40, y=0)
    oled.text("Brno, CZ", x=40, y=12)
    oled.text("RadioElect.", x=40, y=24)

    Here is the list of availabe methods for basic graphics.

    Method name Description Example
    oled.text(text, x, y) Display text at position x, y oled.text("Using OLED...", x=0, y=0)
    oled.pixel(x, y, color) Display one pixel at position. Optional color: 1 - visible, 0 - background color oled.pixel(10, 20)
    oled.hline(x, y, w, color) Horizontal line with width w and color oled.hline(0, 64, 128, color=1)
    oled.vline(x, y, h, color) Vertical line with height h oled.vline(x=9, y=8, h=22, color=1)
    oled.line(x1, y1, x2, y2, color) Diagonal line oled.line(x1=0, y1=0, x2=128, y2=64, color=1)
    oled.rect(x, y, w, h, color) Rectangle oled.rect(0, 0, 128, 64, 1)
    oled.fill_rect(x, y, w, h, collor) Filled rectangle oled.fill_rect(x=0, y=0, w=32, h=32, color=1)
    oled.fill(color) Fill the whole screen (clear screen) oled.fill(0)
  4. Define a binary matrix, suggest your picture/icon, use the oled.pixel() method, and print it on the display.

    # Binary icon
    icon = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 1, 1, 0, 0, 0, 1, 1, 0],
        [1, 1, 1, 1, 0, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 1, 1, 1, 1, 1, 1, 1, 0],
        [0, 0, 1, 1, 1, 1, 1, 0, 0],
        [0, 0, 0, 1, 1, 1, 0, 0, 0],
        [0, 0, 0, 0, 1, 0, 0, 0, 0]]
    # Copy icon to OLED display position pixel-by-pixel
    pos_x, pos_y = 100, 50
    for j, row in enumerate(icon):
        for i, val in enumerate(row):
            oled.pixel(x=i+pos_x, y=j+pos_y, color=val) 
  5. Combine temperature and OLED examples and print DHT12 senzor values on OLED display.

    Create a new file dht12.py and copy/paste the class for DHT12 sensor. Save a copy of this file to the MicroPython device. Import the class to your script and use the methods according to the example:

    from machine import I2C
    from machine import Pin
    import time
    import dht12
    from sh1106 import SH1106_I2C
    
    WIDTH = 128  # OLED display width
    HEIGHT = 64  # OLED display height
    
    
    def read_sensor():
        sensor.measure()
        return sensor.temperature(), sensor.humidity()
    
    
    # Connect to the DHT12 sensor
    i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400_000)
    sensor = dht12.DHT12(i2c)
    
    # Init OLED display
    oled = SH1106_I2C(WIDTH, HEIGHT, i2c, rotate=180)
    
    try:
        while True:
            temp, humidity = read_sensor()
            print(f"Temperature: {temp}°C, Humidity: {humidity}%")
    
    
            # WRITE YOUR CODE HERE
    
    
            time.sleep(1)
    
    except KeyboardInterrupt:
        print("Ctrl+C pressed. Exiting...")
        oled.poweroff()

(Optional) Experiments on your own

  1. Transform the output of the I2C scanner application into a hexadecimal table format, as illustrated in the example below. Please be aware that the term RA signifies I2C addresses that are reserved and not available for use with slave circuits.

    Scanning I2C...
    
          .0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f
    0x0.: RA RA RA RA RA RA RA RA -- -- -- -- -- -- -- --
    0x1.: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    0x2.: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    0x3.: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
    0x4.: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    0x5.: -- -- -- -- -- -- -- -- -- -- -- -- 5c -- -- --
    0x6.: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    0x7.: -- -- -- -- -- -- -- -- RA RA RA RA RA RA RA RA
    
    2 device(s) detected
  2. Build a real-time clock using an ESP32 board, I2C communication, and an RTC DS3231. The goal is to set and display the current time, date, and perform basic time-related operations. Note that, according to the DS3231 manual, the RTC memory has the following structure.

    Address Bit 7 Bits 6:4 Bits 3:0
    0x00 0 10 Seconds Seconds
    0x01 0 10 Minutes Minutes
    0x02 0 12/24 AM/PM 10 Hour Hour
    ... ... ... ...
  3. Build an environmental monitoring system using an ESP32 board, I2C communication, and common sensors. The goal is to collect data on temperature, humidity, and air quality, and display this information on an OLED display.

References

  1. Ezoic. I2C Info - I2C Bus, Interface and Protocol

  2. Electronicshub.org. Basics of I2C Communication | Hardware, Data Transfer, Configuration

  3. MicroPython. class I2C - a two-wire serial protocol

  4. Adafruit. List of I2C addresses

  5. Aosong. Digital temperature DHT12

  6. NXP. I2C-bus specification and user manual

  7. Martin Fitzpatrick. Driving I2C OLED displays with MicroPython

  8. Maxim Integrated. DS3231, Extremely accurate I2C-Integrated RTC/TCXO/Crystal

  9. LastMinuteEngineers. Interface DS3231 Precision RTC Module with Arduino