11. Networking and Communications¶
Group assignment:
- Send a message between two projects
- Document your work to the group work page and reflect on your individual page what you learned
Individual assignment:
- design, build and connect wired or wireless node(s) with network or bus addresses and a local input and/or output device(s)
Group assignment¶
This week’s group assignment was led by FabAcademy instructor Rudolf Igityan. Details of our group work can be found here.
For this group assignment, Ani and I sent a message between our two projects using the I2C protocol. My board, which carries an MQ-7 gas sensor and a piezo buzzer, acted as the Main. Ani’s board, which drives an LED strip through a MOSFET, acted as the Secondary, addressed at a fixed I2C address.
The Boards¶
My board — designed and milled in Week 9:

Ani’s board — designed and milled in her Week 9:

Pull-up Resistors¶
The I2C bus requires pull-up resistors on both SDA and SCL lines — 4.7 kΩ from each line to 3.3V. Without them the bus simply doesn’t work: the lines float and no communication happens. These resistors are needed once per bus, regardless of how many boards are connected. I soldered the two resistors together at their ends and connected them as a Y-shaped jumper between the 3.3V pin, the SDA line, and the SCL line.


The goal was for my board to read the MQ-7 sensor and decide, based on a threshold, whether the air should be considered safe or dangerous, then send this as a single byte to Ani’s board. Ani’s board, listening at its fixed address, would receive this byte and switch the LED strip from steady to a fast blink whenever the value indicated danger.
On the XIAO RP2040, SDA and SCL are broken out on pins D4/D5, but they aren’t fixed by default in code the way they are on classic Arduino boards — we had to set them explicitly with Wire.setSDA() / Wire.setSCL() before Wire.begin().
Code¶
I used ChatGPT to help write the firmware. The prompt I gave was:
“Write Arduino code for XIAO RP2040 as an I2C Main that reads an MQ-7 sensor on pin A0, compares it against a threshold of 600, and sends 0x01 if dangerous or 0x00 if safe to a Secondary device at address 0x10 every 200ms. SDA is on pin 6, SCL on pin 7.”
My board (Main):
#include <Wire.h>
#define MQ7_PIN A0
#define SECONDARY_ADDR 0x10
#define THRESHOLD 600
void setup() {
Wire.setSDA(6); // D4
Wire.setSCL(7); // D5
Wire.begin(); // join the I2C bus as Main
Serial.begin(115200);
}
void loop() {
int gasValue = analogRead(MQ7_PIN);
Serial.println(gasValue);
Wire.beginTransmission(SECONDARY_ADDR);
Wire.write(gasValue > THRESHOLD ? 0x01 : 0x00);
Wire.endTransmission();
delay(200);
}
Let’s explain the code step by step:
- Set SDA and SCL to D4/D5 explicitly, join the I2C bus as Main, and start Serial for debugging.
- In an endless loop, read the MQ-7 sensor on pin A0.
- Compare the reading against a fixed threshold to decide whether the air is safe or dangerous.
- Send the result as a single byte (
0x00or0x01) to Ani’s board. - The cycle repeats with a 200 ms delay.
For Ani’s board, the prompt was:
“Write Arduino code for XIAO RP2040 as an I2C Secondary at address 0x10 that receives a byte and blinks a MOSFET on pin D1 fast when the byte is 0x01, or keeps it off otherwise. SDA is on pin 6, SCL on pin 7.”
Ani’s board (Secondary):
#include <Wire.h>
#define SECONDARY_ADDR 0x10
#define MOSFET_PIN D1
volatile bool alertState = false;
void receiveEvent(int howMany) {
while (Wire.available()) {
byte cmd = Wire.read();
alertState = (cmd == 0x01);
}
}
void setup() {
pinMode(MOSFET_PIN, OUTPUT);
Wire.setSDA(6); // D4
Wire.setSCL(7); // D5
Wire.begin(SECONDARY_ADDR);
Wire.onReceive(receiveEvent);
}
void loop() {
if (alertState) {
digitalWrite(MOSFET_PIN, HIGH);
delay(100);
digitalWrite(MOSFET_PIN, LOW);
delay(100);
} else {
digitalWrite(MOSFET_PIN, LOW);
}
}
Let’s explain the code step by step:
- Set SDA and SCL to D4/D5, join the I2C bus at the fixed address
0x10, and register the receive handler. - Whenever a byte arrives from my board, store whether it equals
0x01(alert) or not. - In the main loop, if the alert flag is set, blink the MOSFET pin on and off quickly; otherwise keep it low.
Let’s look at the result:
This gave me a first concrete sense of what a bus address actually does — Ani’s board only reacts because it’s listening at a specific address, not because it happens to be on the wire. Getting to that point also took a fair bit of debugging: the bus didn’t respond at all until we added the pull-up resistors and set the SDA/SCL pins explicitly in code, which made it clear those two things aren’t optional details — without them, I2C just doesn’t run on this board.
Individual assignment¶
Data exchange between the boards will be carried out using the I2C protocol.
The I2C (Inter-Integrated Circuit) protocol is a two-wire communication protocol used to exchange data between devices. It allows a Main device and one or more Secondary devices to communicate over the same two lines, SDA and SCL. Each Secondary listens at its own address, so the Main can talk to a specific device without the others reacting.
On the XIAO RP2040, SDA and SCL are broken out on pins D4/D5, but they have to be set explicitly in code with Wire.setSDA() / Wire.setSCL() before Wire.begin() — they aren’t fixed by default the way they are on classic Arduino boards. The bus also needs pull-up resistors, 4.7 kΩ from SDA to 3.3V and from SCL to 3.3V, once for the whole bus regardless of how many boards are on it.
Basic Operation¶
My final project board reads the MQ-7 gas sensor and is meant to react in three stages: safe, warning, and dangerous. Instead of deciding everything on one board, I split this across two boards I already designed and fabricated — my sensor board acts as the Main, and my three-LED board (green, yellow, red) acts as the Secondary, addressed at 0x10.
My board — Week 9:

I used ChatGPT to write the Main board firmware. The prompt was:
“Write Arduino code for XIAO RP2040 as an I2C Main that reads an MQ-7 sensor on A0, compares against two thresholds (400 for warning, 700 for danger), and sends 0, 1, or 2 to a Secondary at address 0x10 every 200ms. SDA on pin 6, SCL on pin 7.”
Main board (MQ-7 sensor):
#include <Wire.h>
#define MQ7_PIN A0
#define LED_BOARD_ADDR 0x10
#define THRESHOLD_WARNING 400
#define THRESHOLD_DANGER 700
void setup() {
Wire.setSDA(6); // D4
Wire.setSCL(7); // D5
Wire.begin();
Serial.begin(115200);
}
void loop() {
int gasValue = analogRead(MQ7_PIN);
Serial.println(gasValue);
uint8_t status;
if (gasValue < THRESHOLD_WARNING) status = 0;
else if (gasValue < THRESHOLD_DANGER) status = 1;
else status = 2;
Wire.beginTransmission(LED_BOARD_ADDR);
Wire.write(status);
Wire.endTransmission();
delay(200);
}
Let’s explain the code step by step:
- Set SDA and SCL to D4/D5 explicitly, join the I2C bus as Main, and start Serial for debugging.
- Read the MQ-7 sensor on pin A0.
- Compare the reading against two thresholds to decide between SAFE, WARNING, and DANGER, and store the result as a single byte (0, 1, or 2).
- Send that byte to the three-LED board’s address.
- The cycle repeats with a 200 ms delay.
For the Secondary (LED) board, the prompt was:
“Write Arduino code for XIAO RP2040 as an I2C Secondary at address 0x10 that receives a byte (0, 1, or 2) and lights the corresponding LED: green on pin 2 for 0, yellow on pin 3 for 1, red on pin 4 for 2. SDA on pin 6, SCL on pin 7.”
Secondary board (three LEDs):
#include <Wire.h>
#define LED_BOARD_ADDR 0x10
#define LED_GREEN 2
#define LED_YELLOW 3
#define LED_RED 4
void setup() {
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_RED, OUTPUT);
Wire.setSDA(6); // D4
Wire.setSCL(7); // D5
Wire.begin(LED_BOARD_ADDR);
Wire.onReceive(receiveEvent);
}
void receiveEvent(int howMany) {
while (Wire.available()) {
uint8_t status = Wire.read();
digitalWrite(LED_GREEN, status == 0);
digitalWrite(LED_YELLOW, status == 1);
digitalWrite(LED_RED, status == 2);
}
}
void loop() {
}
Let’s explain the code step by step:
- Set SDA and SCL to D4/D5, join the I2C bus at the fixed address
0x10, and register the receive handler. - Whenever a byte arrives, read it as the current status (0, 1, or 2).
- Light only the LED that matches the received status — green for SAFE, yellow for WARNING, red for DANGER.
Connecting to a Third Board¶
I wanted my warning system to do more than just light up my own three LEDs, so I added a third node: my Main board now also sends the status to Ani’s board, addressed at 0x20. Her board already drives an outdoor LED strip through a MOSFET, so instead of adding any new hardware, I just had her code react to my status byte — blinking the strip whenever the status is DANGER.
Ani’s board — her Week 9:


For the extended Main code, the prompt was:
“Extend the previous XIAO RP2040 I2C Main code to also send the same status byte to a second Secondary at address 0x20 in the same loop.”
Main board, extended to address both Secondaries:
#include <Wire.h>
#define MQ7_PIN A0
#define LED_BOARD_ADDR 0x10
#define ANI_BOARD_ADDR 0x20
#define THRESHOLD_WARNING 400
#define THRESHOLD_DANGER 700
void setup() {
Wire.setSDA(6); // D4
Wire.setSCL(7); // D5
Wire.begin();
Serial.begin(115200);
}
void loop() {
int gasValue = analogRead(MQ7_PIN);
uint8_t status;
if (gasValue < THRESHOLD_WARNING) status = 0;
else if (gasValue < THRESHOLD_DANGER) status = 1;
else status = 2;
Wire.beginTransmission(LED_BOARD_ADDR);
Wire.write(status);
Wire.endTransmission();
Wire.beginTransmission(ANI_BOARD_ADDR);
Wire.write(status);
Wire.endTransmission();
delay(200);
}
Let’s explain the code step by step:
- The sensor reading and status calculation stay exactly the same as before.
- The status byte is now sent twice — once to my three-LED board at
0x10, and once to Ani’s board at0x20.
For Ani’s Secondary code, the prompt was:
“Write Arduino code for XIAO RP2040 as an I2C Secondary at address 0x20 that receives a status byte and blinks a MOSFET on pin D2 every 200ms when the byte equals 2, otherwise keeps it off. Use millis() for timing. SDA on pin 6, SCL on pin 7.”
Ani’s board, receiving the status and blinking the LED strip through the MOSFET whenever it’s DANGER:
#include <Wire.h>
#define ANI_BOARD_ADDR 0x20
#define MOSFET_PIN D2
volatile uint8_t status = 0;
bool blinkState = false;
unsigned long lastBlink = 0;
void receiveEvent(int howMany) {
while (Wire.available()) {
status = Wire.read();
}
}
void setup() {
pinMode(MOSFET_PIN, OUTPUT);
Wire.setSDA(6); // D4
Wire.setSCL(7); // D5
Wire.begin(ANI_BOARD_ADDR);
Wire.onReceive(receiveEvent);
}
void loop() {
if (status == 2) {
if (millis() - lastBlink > 200) {
blinkState = !blinkState;
digitalWrite(MOSFET_PIN, blinkState);
lastBlink = millis();
}
} else {
digitalWrite(MOSFET_PIN, LOW);
}
}
Let’s explain the code step by step:
- Set SDA and SCL to D4/D5, join the I2C bus at the fixed address
0x20, and register the receive handler. - Store whatever status byte arrives in a variable shared with the main loop.
- Whenever the status is DANGER, toggle the MOSFET pin on and off every 200 ms, using
millis()instead ofdelay()so the board stays responsive to new messages. - For any other status, the strip is forced off.
Conclusion¶
This week let me take the warning system from my final project and actually prove it as a network — not just one board reacting to its own sensor, but three boards, two of them from two different final projects, each addressed individually on the same I2C bus.