Week 14 : Networking and Communications



Summary

This week, I took inspiration from previous weeks and continued my exploration of I2C by trying to communicate with several slaves on the same communication line. The end result is a communication with one input, an infrared temperature sensor, and one output, an OLED screen.

WireNoPullup

Assignments

Group Assignment

Individual Assignments


Communication Between Two Projects

This part was done in group and is accessible on the group page.

Wired Network With Input & Output

I2C Network

When connecting multiple slave devices on an I2C bus, adopting a networked approach can streamline communication effectively. Each slave device needs a unique address on the bus to ensure individual identification and communication by the master device. In scenarios where multiple master devices share the same I2C bus, coordination is crucial to avoid conflicts. Bus arbitration mechanisms facilitate this coordination, allowing only one master to communicate at a time.

Organizing slave devices into a network topology enables the master to interact with them individually, maintaining their unique addresses. In setups requiring multiple master devices, a multi-master configuration allows independent access to the bus while ensuring coordination to prevent collisions. An illustration of this communication is shown here.

i2cbus

Clock synchronization is essential to maintain proper timing during data transfer, with clock stretching accommodating any delays by slave devices. Including error detection and handling mechanisms enhances reliability, addressing issues like bus collisions or data corruption.

Thorough testing and validation of the networked I2C communication setup confirm its functionality and address any issues. This comprehensive approach ensures efficient data exchange and coordination among interconnected devices in embedded systems or IoT projects.

Pull-Up Resistor

Integrating pull-up resistors is essential for reliable communication in setups with multiple slave devices on an I2C bus. These resistors ensure that the bus lines return to a logic high state when not actively driven by a device, preventing bus contention and ensuring effective communication. The value of the pull-up resistors depends on factors like bus capacitance and desired signal rise time, typically ranging from 2.2 kΩ to 10 kΩ. By incorporating pull-up resistors, you enhance the overall reliability and performance of your embedded systems or IoT projects.

Master & Slaves

The elements that will be on my network are :

RP2Pico
Master #1
SSD1306
Slave #1
MLX90614
Slave #2

Wiring

As described above, wiring is fairly straightforward, since all elements must be connected in parallel to the SDA and SCL lines. Pull-up resistors are also required.

It should be noted that I forgot to add the pull-up resistors at the start of my tests, and then fixed the problem, although this didn’t solve the problems I encountered and which will be presented later.

The wiring is shown here.

WireNoPullup
Wiring without Pull-up Resistor
WirePullup
Wiring with Pull-up Resistor

I used the Pins GP8 and GP9 of the raspberry Pi Pico for i2c communication. A diagram of the raspberry’s pins can be seen below.

RaspPico_Scheme

Network Testing

To test the network, after plugging it in, I tested the two escalves separately, and then tried to make them work together.

Testing the OLED

To test the screen, I took an old code from week 12, which displayed the analog measurement of a temperature sensor (measured this time on a different pin).

The code is available on the Week 12 page.

Don’t forget to add the ssd1306 library to the raspberry pico’s internal memory.

It worked perfectly!

Testing the Temperature Sensor

To test the temperature sensor, I used the test code in the mlx90614 library.

I also need to add the mlx90614 library to the raspberry pico’s internal memory.

import mlx90614
from machine import I2C, Pin

i2c = I2C(scl=Pin(5), sda=Pin(4))
sensor = mlx90614.MLX90614(i2c)

print(sensor.read_ambient_temp())
print(sensor.read_object_temp())
if sensor.dual_zone:
    print(sensor.object2_temp)
Troubleshooting the Sensor

The sensor does not initialize correctly and returns an error when creating the “sensor” object. After some research, it seems that this is due to the Raspberry Pico’s default communication frequency, which is too high.

So I add the freq argument to the I2C and set it to 100 kHz as indicated in the datasheet (maximum frequency).

import mlx90614
from machine import I2C, Pin

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100_000)
sensor = mlx90614.MLX90614(i2c)

print(sensor.read_ambient_temp())
print(sensor.read_object_temp())
if sensor.dual_zone:
    print(sensor.object2_temp)

Now it’s working!

Testing the Two Slaves Together

To test both I need to write new code. In theory, the idea is quite similar to what’s been done before, i.e. create an i2c object with the machine library and assign it to the two objects, oled and sensor, created with the ssd1306 and mlx90614 libraries.

A bit of formatting and I get this code, which, spoiler, won’t work…

from machine import Pin, I2C
import ssd1306
import time
import mlx90614

# Initialize I2C
i2c = SoftI2C(scl=Pin(9), sda=Pin(8), freq=100_000)

# Initialize MLX90614
mlx = mlx90614.MLX90614(i2c)

# OLED Display setup
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

# Main loop
while True:
    ambient_temp = mlx.read_ambient_temp()  # Ambient temperature
    object_temp = mlx.read_object_temp()    # Temperature of the object in front of the sensor
    oled.fill(0)  # Clear the display
    oled.text('Ambient Temp:', 0, 0)
    oled.text(f'{ambient_temp:.2f} C', 0, 10)
    oled.text('Object Temp:', 0, 20)
    oled.text(f'{object_temp:.2f} C', 0, 30)
    oled.show()
    time.sleep(1)

Troubleshooting the Network

This time, it’s the ssd1306 library that’s having trouble, generating a “time_out” error when initializing the oled object. After a bit of research, I realize that I forgot to add the pull-up resistors, what a shame. As a reminder, they prevent floating pins on my SDA and SCL channels. However, this didn’t solve the problem…

However, the two elements appear to be clearly identified and distinct when “i2c.scan()” is run. In fact, this function returns the addresses of the two elements on the i2c line: [60, 90], in decimal.

So for me it must be a bug in the library when initializing the oled, it must be trying to write something that doesn’t exist.

I decided to work around the problem, as suggested on a forum, by using “Software I2C”.

Software I2C

Software I2C, also known as bit-banging I2C, is a flexible method of implementing the I2C protocol using software routines instead of dedicated hardware. It directly controls the SDA and SCL lines of an I2C bus through GPIO pins, offering versatility across various microcontrollers and hardware platforms. While it provides flexibility, it may not match the performance of hardware-based solutions, and developers should consider factors like timing accuracy and resource utilization when choosing to use software I2C in their projects.

Final Solution & Result

So I solved my problem using the SoftI2C library from machine, the code is shown below.

Pull-up resistors are not required.

from machine import Pin, SoftI2C
import ssd1306
import time
import mlx90614

# Initialize I2C
i2c = SoftI2C(scl=Pin(9), sda=Pin(8), freq=100_000)

# Initialize MLX90614
mlx = mlx90614.MLX90614(i2c)

# OLED Display setup
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

# Main loop
while True:
    ambient_temp = mlx.read_ambient_temp()  # Ambient temperature
    object_temp = mlx.read_object_temp()    # Temperature of the object in front of the sensor
    oled.fill(0)  # Clear the display
    oled.text('Ambient Temp:', 0, 0)
    oled.text(f'{ambient_temp:.2f} C', 0, 10)
    oled.text('Object Temp:', 0, 20)
    oled.text(f'{object_temp:.2f} C', 0, 30)
    oled.show()
    time.sleep(1)

The operation of the oled and the sensor on the same i2c line can be seen below.

WireNoPullup