Week 13 - Networking and communication
Goals
- individual assignment: design, build, and connect wired or wireless node(s) with network or bus addresses
- group assignment: send a message between two projects
Week's explorations and achievments
- Group assignment : we achieved a serial communication between two Arduino Uno boards using the SoftwareSerial library
- I tested I²C communication between 3 arduino uno boards: a master board talked to secondary boards thanks to adresses defined in their script and lit up LEDs connected to one of their GPIO
- I implemented the same code on my own boards, as I had 3 functionnal boards providing I²C headers
- I integrated the neopixel protocol in the project to monitor neopixel strips instead of simple LEDs, connected to the secondary boards
- I integrated an RFID reader on my master board which uses the SPI protocol. As done during the week 'Embedded Programming', the RFID reader allows to monitor r,g,b values
- I programmed the I²C communication for the secondary boards to retrieve these r,g,b values from the master board
- I did a bit of soldering to clean my circuit aspect
- I modified my code to demonstrate the behavior I have in my mind for my final project (see video at the end)
Group assignment
We implemented serial communication between two arduino uno simple circuits with leds, using the Softwareserial.h
library. The boards were connected via USB to different computers,with the intention to use the real Serial monitor to observe the information exchanged. We managed to achieve the softwareserial communication between the two boards, but unfortunately information retrieved in the serial monitors weren't consistant (achracter format issue?).
It was my first time using serial communication and I think the Softwareserial can be pretty interesting!
I²C communication
Basics
I²C communication is a wire communication protocol which is really convenient because it relies on only 2 wires: SDA and SCL, common to all the network. Caution though, you also need to have a common ground, and apparently to have equivalent VCC power. For my custom boards I connected all 3.3V outputs together.
SDA and SCL must be connected to some pullout resistors to avoid floating values. I read that the value of the pullup resistors can be changed depending on the use case, but generally it should be around 3-5 KOhms. In my case I had already soldered beforehand 10K pullup resistors, and it happened to work just fine.
An important point of the I²C communication is that it works on the basis of a master / secondary roles paradigm. You can only have one master at a time, which sends instructions to the secondary boards connected to the network. In order to identify which board it is talking to, you have to use adresses (see below).
The library commonly used for I²C communication is Wire.h
.
Checking with the I²C scanner
If you want to monitor an I²C component such as a LCD screen, you can use the default adresses found on its documentation, but you can also retrieve its adresses with an I²C scanning script, which check if some devices are connected on the I²C bus and if there are some, what are their adresses.
When you want to do a network with several programmable boards, you can choose the adresses they will communicate with. It's optional for the master board. To specify a secondary's board adress, you have to use the instruction Wire.begin()
. For example, if you use Wire.begin(7)
in a secondary board program, its adress on the I²C bus will be 0x07, and the master board will be able to initiate a communication using the instruction Wire.beginTransmission(7)
.
I programmed secondary arduino uno boards with the adresses 8 and 9 (see below) and ran the I²C scanner to check my device were correctly wired to the I²C bus.
/*I²C_scanner
This sketch tests standard 7-bit addresses.
Devices with higher bit address might not be seen properly.*/
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial);
Serial.println("\nI2C Scanner");
}
void loop() {
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for (address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address < 16)
Serial.print("0");
Serial.print(address, HEX);
Serial.println(" !");
nDevices++;
}
else if (error == 4) {
Serial.print("Unknown error at address 0x");
if (address < 16)
Serial.print("0");
Serial.println(address, HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found\n");
else
Serial.println("done\n");
delay(5000);
}
Here's what's printed in the serial monitor:
Simple communication example
I used the example provided by Adrian Torres on its documentation (but with only two secondary boards), which consists in speaking alternatively to three secondary boards that will light up an LED when they recieve the message from the master board.
My connections:
- SDA together (don't forget the pullup resistors)
- SCL together (same)
- GND together
- 3.3V together (not sure at all this is the correct way to do it)
- simple LED with a 330Ohm resistor on the pin 4
Here is the master board script:
//original code by Luis Diaz_ Hola, as cited by Adrian Torres from Fab Academy 2022
#include <Wire.h>
int i = 0;
int j = 0;
int led = 4; //led pin
void setup() {
Serial.begin(115200); //speed of the communications
Wire.begin();
pinMode(led, OUTPUT); // led
}
void loop()
{
for (i = 0; i <= 3; i++) { // 0,1,2,3
Wire.beginTransmission(8); //secondary board number 8
if ( i == 1) {
Wire.write(1);
Serial.println("1");
} else {
Wire.write(0);
Serial.println("0");
}
Wire.endTransmission();
Wire.beginTransmission(9); //secondary board number 9
if ( i == 2) {
Wire.write(1);
Serial.println("1");
} else {
Wire.write(0);
Serial.println("0");
}
Wire.endTransmission();
for (j = 0; j <= i; j++) { // blink i times
digitalWrite(led, HIGH);
delay(200);
digitalWrite(led, LOW);
delay(200);
}
delay(1000);
}
}
And this is the script for one of the secondary board (for the other, just change the 8 with a 9)
//original code by Luis Diaz_ Hola, as cited by Adrian Torres from Fab Academy 2022
#include <Wire.h>
int d1 = 0;
#define led 4
void setup() {
pinMode(led, OUTPUT);
Serial.begin(115200);
Wire.begin(9); //change address for the nodes in my case I use 8 and 9.
Wire.onReceive(recieveEvent);
}
void loop() {
if (d1 == 1)
{
digitalWrite(led, HIGH);
Serial.println("led high");
}
else
{
digitalWrite(led, LOW);
Serial.println("led low");
}
delay(500);
}
void recieveEvent(int howMany)
{
while (Wire.available())
{
d1 = Wire.read();
}
}
Adding neopixels
I then worked with my homemade PCBs. I adapted the code adding Neopixel strips which use a different protocol to communicate to my secondary boards.
Circuit description
Master board
My master board is the development board based on an ATtiny3216 that I produced during the week Electronic production. It provides SPI and I²C pins (with two 10K pullup resistors). It is powered through USB.
Secondary boards
My secondary boards are two boards made during the week Input Device, based on ATtiny1614. I connected an LED strip to both of them and powered each of them with 3.7V LipoCell battery. It provides an I²C grove with two 10K pullup resistors.
What it does
The master board communicates alternatively to the secondary board with the adress 8 and the secondary board with the adress 9, based on a delay of 1000ms. When the secondary boards recieves the communication from the master, it turns on their LED strip to an r,g,b color which increments either in blue or in green (depending on which secondary board it is).
Code
Master code
#include <Wire.h>
int i = 0;
int led = 4; //led pin
void setup() {
Serial.begin(115200); //speed of the communications
Wire.begin();
pinMode(led, OUTPUT); // led
}
void loop()
{
for (i = 0; i <= 3; i++) { // 0,1,2,3
Wire.beginTransmission(8); //secondary board number 8
if ( i == 1) {
Wire.write(1);
Serial.println("1");
} else {
Wire.write(0);
Serial.println("0");
}
Wire.endTransmission();
Wire.beginTransmission(9); //secondary board number 9
if ( i == 2) {
Wire.write(1);
Serial.println("1");
} else {
Wire.write(0);
Serial.println("0");
}
Wire.endTransmission();
delay(1000);
}
}
Secondary's code
#include <Wire.h>
#include <tinyNeoPixel.h>
#define STRIP 1 // PA5 on lightshake
#define NUMPIXELS 10
tinyNeoPixel strip = tinyNeoPixel(NUMPIXELS, STRIP, NEO_GRBW + NEO_KHZ800);
int delayval = 50;
int r = 20; int g = 30; int b = 20;
int i = 0; //index of a pixel in the strip
int d1 = 0;
void setup() {
Serial.begin(115200);
strip.begin();
strip.clear();
Wire.begin(8); //change address for the secondary boards, here I use 8 and 9.
Wire.onReceive(receiveEvent);
}
void loop() {
if (d1 == 1)
{
strip.setPixelColor(i, strip.Color(r, g, b));
strip.show();
Serial.println("strip high");
if (b <=250){b = b+10;}
else (b = b - 10);
if ( i == NUMPIXELS-1){i=0;}
else i = i + 1;
}
delay(500);
}
void receiveEvent(int howMany)
{
while (Wire.available())
{
d1 = Wire.read();
}
}
For the board with the other adress I changed blue for green in the colors calculation, and Wire.begin(8);
for Wire.begin(9);
Final project demo: only one peripheral adress
For my final project I think of a slightly different process, where I'll use only one peripheral adress for my secondary boards and an I²C bus that will remain open until there is a contact, because I want my elements to be physically independent unless you make them physically interact. It doesn't change so much the code, except it's talking to only one peripheral board.
Also I started going in the direction of my final project in which it's the master role to send the r,g,b values to the secondary boards.
Master's code:
#include <Wire.h>
int t = 0;
int r = 20;
int g = 60;
int b = 30;
void setup() {
Serial.begin(115200); //speed of the communications
Wire.begin();
}
void loop()
{
for (t = 0; t <= 1; t ++) { // 0,1
Wire.beginTransmission(7);
if ( t == 1) {
Wire.write(1);
Wire.write(r);
Wire.write(g);
Wire.write(b);
} else {
Wire.write(0);
Wire.write(0);
Wire.write(0);
Wire.write(0);
}
Wire.endTransmission();
delay(100);
}
}
Secondary's code
#include <Wire.h>
#include <tinyNeoPixel.h>
#define STRIP 1 // PA5 on lightshake
#define NUMPIXELS 15
tinyNeoPixel strip = tinyNeoPixel(NUMPIXELS, STRIP, NEO_GRB + NEO_KHZ800);
int delayval = 300;
int r = 0; int g = 0; int b = 0;
int i = 0; //index of a pixel in the strip
int d1 = 0;
void setup() {
Serial.begin(115200);
strip.begin();
strip.clear();
Wire.begin(7); //change address for the nodes, here I use 8 and 9.
Wire.onReceive(receiveEvent);
}
void loop() {
if (d1 == 1)
{
strip.setPixelColor(i, strip.Color(r, g, b));
strip.show();
delay(delayval);
i = i+1;
}
}
void receiveEvent(int howMany)
{
while (Wire.available())
{
d1 = Wire.read();
r = Wire.read();
g = Wire.read();
b = Wire.read();
}
}
RFID reader and SPI protocol
Circuit description
Master board
My master board is the development board based on an ATtiny3216 that I produced during the week Electronic production.
I connected an RFID-RC522 reader to my master board, which communicates together thanks to SPI protocol. It needs a lot more cable than the I²C protocol! You indeed have to connect COPI/MOSI, CIPO/MISO, SCK, SS and RST, as well as 3.3V and GND. I already had tested this RFID reader during the Embedded Programming week, though it was on a commercial board.
Hopefully I had already provided the pins COPI, CIPO and SCK on my ATtiny3216 development board (using the ATtiny3216 pinout, see the week Electronics Production), and I had enough GPIO to connect SS and RST, which are configurable.
My connections between the ATtiny3216 master board and the RFID reader are thus:
- COPI/MOSI together
- CIPO/MISO together
- SCK together
- SS on pin 4
- RST on pin 5
- GND together
- 3.3 V together
In order to use the RFID reader, I used the SPI.h
library and the MFRC522.h
library.
I also added a single neopixel (a lilypad sewable mini board), that I connected to pin 3 (Data in). I could have used the neopixels that are directly soldered onto my boards, if only they had been correctly soldered! Now that I know how to use solder paste I would probably manage resoldering them correctly, but that was not my priority. Of course, I connected the + to Vcc and the - to ground.
Finally, I am powering the board with USB.
Secondary boards
My secondary boards are two boards made during the week Input Device, based on ATtiny1614. As in the previous example, I connected an LED strip to both of them and powered each of them with 3.7V LipoCell battery. It provides an I²C grove with two 10K pullup resistors.
What it does
This code emulates a gesture that would be important in my final project.
The master board with the RFID reader corresponds to the color palette where you will blend primary colors to obtain a certain tint. Then a secondary board corresponds to an independent light sources that you will dip in the palette to fill the element with the palette's color. Several light sources should be provided.
Technically, the primary colors blending could be achieved thanks to NFC tags, each corresponding to a certain primary color. You can read more about it in my assignment for week 4 - Embedded Programming. I used the same tags. Here are their adress and how i named them:
Name | Card UID |
---|---|
Red | 04 FB 32 22 DA 64 80 |
Green | 04 F3 30 22 DA 64 80 |
Blue | 04 87 31 22 DA 64 80 |
White | 04 96 31 22 DA 64 80 |
Black | 04 02 34 22 DA 64 81 |
The color blend is made visible thanks to the neopixel rgb led connected to the master board (could be changed for a neopixel ring).
The specificity of this "network" is that the I²C grove connector is by default not connected and I am connecting it only when I want to retrieve the R,G,B value from the master board to the led element
Issues encountered
I had no particular difficulty to integrate the RFID reading part to the master's code. Nevertheless finding the correct timing to retrieve the color in the led element was quite challenging. I wanted it to be able to fill the led element according to the duration of the contact: if you keep the led element on the palette, it will fill pixel by pixel until all are lit up, but it you interrupt the contact, only the first pixels will be lit up.
Also, I wanted to be able to fill the same strip with different colors, if you interrupt the filling, change the palette's color, then fill the remaining pixels.
The sensitive aspect of the code is then when do you increment the index of the pixel currently lighting up. The correct way to do it was in the receiveEvent function in the led element's code:
void receiveEvent(int howMany)
{
while (Wire.available())
{
d1 = Wire.read(); //d1 is
r = Wire.read();
g = Wire.read();
b = Wire.read();
if (d1==1)
{
i = i+1;
}
}
}
This is the corresponding I²C transmitting part of the master's code:
for (t = 0; t <= 1; t ++)
{
Wire.beginTransmission(7);
if (t == 1)
{
Wire.write(1);
Wire.write(r);
Wire.write(g);
Wire.write(b);
}
else {
}
Wire.endTransmission();
delay(100);
}
Video
Code
Master's code
Here is all the code of the master's board, including the RFID reading part.
// Clara Devanz
// Fabacademy2023
// Master board with RFID reader (color palette)
#include <Adafruit_NeoPixel.h>
#include <Wire.h> // for I2C communication
#include <MFRC522.h> //RFID reader library
#include <SPI.h> // used for the RFID reader
#define PIX 3 //PIN of the neopixe(s)
#define NUMPIXELS 1 //number of neopixels
Adafruit_NeoPixel pixels(NUMPIXELS, PIX, NEO_GRB + NEO_KHZ800);
int del = 50; //delay
int t = 0;
int r = 0;
int g = 0;
int b = 0;
int w = 0;
int k = 200;
#define RST_PIN 5 // Configurable
#define SS_1_PIN 4 // Configurable
#define NR_OF_READERS 1
byte ssPins[] = {SS_1_PIN};
MFRC522 mfrc522[NR_OF_READERS]; // Create MFRC522 instance.
/**
* Initialize.
*/
void setup() {
pinMode(PIX, OUTPUT);
Serial.begin(9600); // Initialize serial communications with the PC
Wire.begin();
while (!Serial); // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
SPI.begin(); // Init SPI bus
for (uint8_t reader = 0; reader < NR_OF_READERS; reader++) {
mfrc522[reader].PCD_Init(ssPins[reader], RST_PIN); // Init each MFRC522 card
}
}
/**
* Main loop.
*/
void loop() {
for (uint8_t reader = 0; reader < NR_OF_READERS; reader++) {
// Look for new cards
// String content[reader];
if (mfrc522[reader].PICC_IsNewCardPresent() && mfrc522[reader].PICC_ReadCardSerial()) {
Serial.print(F("Reader "));
Serial.print(reader);
// Show some details of the PICC (that is: the tag/card)
Serial.print(F(": Card UID:"));
Serial.println();
readTags(mfrc522[reader].uid.uidByte, mfrc522[reader].uid.size);
}
// Halt PICC
mfrc522[reader].PICC_HaltA();
// Stop encryption on PCD
mfrc522[reader].PCD_StopCrypto1();
} //end of the loop with the readers
for (t = 0; t <= 1; t ++) { // 0,1
Wire.beginTransmission(7);
if (t == 1){
Wire.write(1);
Wire.write(r);
Wire.write(g);
Wire.write(b);
}
else {
//Wire.write(0);
//Wire.write(0);
//Wire.write(0);
//Wire.write(0);
}
Wire.endTransmission();
delay(100);
}
}
/**
* Helper routine to dump a byte array as hex values to Serial.
*/
void readTags(byte *buffer, byte bufferSize) {
String adress="";
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
adress.concat(String(buffer[i] < 0x10 ? " 0" : " "));
adress.concat(String(buffer[i], HEX));
}
adress.toUpperCase();
if (adress.substring(1) == "04 FB 32 22 DA 64 80")
{
r += 10;
Serial.print(F(" red tag identified")); // String wrapper for printing strings in the serial monitor and using Flash memory instead of SRAM
pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
pixels.setBrightness(k);
pixels.show();
Serial.println();
Serial.println("Value of red color is " + String(r));
delay(del);
}
else if (adress.substring(1) == "04 F3 30 22 DA 64 80")
{
g += 10;
Serial.print(F(" green tag identified"));
delay(del);
Serial.println();
pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
pixels.setBrightness(k);
pixels.show();
Serial.println();
Serial.println("Value of green color is " + String(g));
delay(del);
}
else if (adress.substring(1) == "04 87 31 22 DA 64 80")
{
b += 10;
Serial.print(F(" blue tag identified"));
delay(del);
Serial.println();
pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
pixels.setBrightness(k);
pixels.show();
Serial.println();
Serial.println("Value of blue color is " + String(b));
}
else if (adress.substring(1) == "04 96 31 22 DA 64 80")
{
Serial.print(F(" white tag identified"));
delay(del);
Serial.println();
r=0;
g=0;
b=0;
k = 200;
pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
pixels.setBrightness(k);
pixels.show();
}
else if (adress.substring(1) == "04 02 34 22 DA 64 81")
{
Serial.print(F(" black tag identified"));
k -= 20;
Serial.println();
Serial.println("Value of brightness is " + String(k));
delay(del);
Serial.println();
pixels.setBrightness(k);
pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
pixels.show();
}
else
{
Serial.print("unknown tag");
delay(500);
}
}
Secondary's code
Here is all the code of the led element's board
//Clara Devanz, inspired by the code by Luis Diaz cited by Adriano Torres
//Fab Academy 2023
//ATtiny1614 secondary board (led element)
#include <Wire.h>
#include <tinyNeoPixel.h>
#define STRIP 1 // PA5 on lightshake
#define NUMPIXELS 15
tinyNeoPixel strip = tinyNeoPixel(NUMPIXELS, STRIP, NEO_GRB + NEO_KHZ800);
int delayval = 100;
int r = 0; int g = 0; int b = 0;
int i = -1; //index of a pixel in the strip
int d1 = 0;
void setup() {
Serial.begin(115200);
strip.begin();
strip.clear();
Wire.begin(7); //change address for the nodes
Wire.onReceive(receiveEvent);
}
void loop() {
if (d1 == 1)
{
if (i >= NUMPIXELS) {i=-1;}
strip.setPixelColor(i, strip.Color(r, g, b));
strip.show();
delay(delayval);
}
else {
delay(delayval);
}
}
void receiveEvent(int howMany)
{
while (Wire.available())
{
d1 = Wire.read();
r = Wire.read();
g = Wire.read();
b = Wire.read();
if (d1==1)
{i = i+1;}
}
}