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 peripherals on the same communication line. The end result is a communication with one input, an infrared temperature sensor, and one output, an OLED screen.
I then carried out the same operation with the PCB I’d made in week 8.
Assignments
Group Assignment
- Send a message between two projects.
Individual Assignments
- Design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or outputdevice(s).
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 peripheral devices on an I2C bus, adopting a networked approach can streamline communication effectively. Each peripheral device needs a unique address on the bus to ensure individual identification and communication by the controller device. In scenarios where multiple controller devices share the same I2C bus, coordination is crucial to avoid conflicts. Bus arbitration mechanisms facilitate this coordination, allowing only one controller to communicate at a time.
Organizing peripheral devices into a network topology enables the controller to interact with them individually, maintaining their unique addresses. In setups requiring multiple controller devices, a multi-controller configuration allows independent access to the bus while ensuring coordination to prevent collisions. An illustration of this communication is shown here.
Clock synchronization is essential to maintain proper timing during data transfer, with clock stretching accommodating any delays by peripheral 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 peripheral 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.
Controller & Peripherals
The elements that will be on my network are :
- Controller #1: A Raspberry Pi Pico with MicroPython firmware.
- Peripheral #1: SSD1306 OLED display.
- Peripheral #2: A MLX90614 temperature sensor.
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.
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.
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 Peripherals 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.
Final Result with my PCB
Now that the tests have been carried out, I can apply this to my PCB designed in week 8 by connecting the OLED display and the temperature sensor to the I2C inputs that were designed in parallel.
The setup can be seen below, along with a video of the result.