Fab Academy 2020
14. Networking and Communications

Assignment :

Jump to Group Assignment section below

Week 14 learning journey :

So far we had been building boards with a single processor, and this week is on how we can work with and integrating multiple boards (or projects) together and have them communicating (or transmitting data) with one another. In order to accomplish this, each divice is being connected to a common bus (or network) and need to have its own unique ID or addresses.

We have exprience in using UART (Tx, Rx) to send data from the target board to the screen on the PC/notebook. In addition, we been frequetly using SPI (Serial Peripheral Interface)(MISO, MOSI, SCK) to upload our program code to the target board via our 'fabisp'.

This week, I will be exploring more on I2C communication which is a 'two-wire interface'.


Implementation of I²C Protocol

I2C (Inter-Integrated Circuit), sometimes also pronounced as "I-squared-C" or "I-two-C" or "I-I-C" (written as IIC), combines the best features of SPI and Serial UARTs.

I2C is a serial communication protocol, so data is transferred bit by bit along a single wire (the SDA line). But unlike Serial UART Ports which are asynchronous (meaning devices need to agree on a data rate beforehand), I2C is synchronous; synchronized to the sampling of bits by a clock signal shared between the master and the slave. In I2C protocol, the master controls the clock speed.

I2C only uses two wires to transmit data between devices. The common I2C bus speeds are 100 kbit/s which is the standard mode and the 400 kbit/s Fast mode. It allow connection up to 128 devices using 7-bit addressing or up to 1024 devices using 10-bit addressing. 10-bit address space is rarely used.


Illustration extracted from Circuit Basic


In I2C bus, both the SCL and SDA lines are refer to "open drain" drivers, meaning that they can pull the corresponding signal line low, but cannot drive it high. Thus, there can be no bus contention where one device is trying to drive the line high while another tries to pull it low, eliminating the potential for damage to the drivers or excessive power dissipation in the system.
Reference : Circuit Basic : I2C


For the line to be able to go high you must provide pull-up resistor(s) to the 5V supply. You only need at least one pull-up resistor for the SDA as well as the SCL line in the whole I2C bus. You are not require to put resistor for each I2C boards on the I2C bus. If the resistor is missing, the SCL and SDA lines will always be low - nearly 0 volts - and the I2C bus will not work. A pair of 4.7k ohm Resistors are typically used. The resistors "pull-up" the voltage of SDA and SCL to the Vdd line (to 5V level).
Reference : JLabsTech : What is I2C? (Simplified)




SDA (Serial Data) – The line for the master and slave to send and receive data. I2C is a serial communication protocol, so data is transferred bit by bit along a single wire (the SDA line).

SCL (Serial Clock) – The line that carries the clock signal. The clock signal is always generated by the current bus master. I2C is synchronous, so the output of data bits is synchronized to the sampling of bits by a clock signal shared between the master and the slave(s).
Reference : Circuit Basic : I2C


Each node connected on the I2C bus has its own individual I2C address.

1. To initiate a transaction (or start of a data transfer). The Master node pull the SDA line from high voltage level to a low voltage level before the SCL line switches from high to low. All the slaves as such will start to listen to the SDA line.

2. The master then send a 7-bit address of the slave it wants to communicate with, along with the read/write bit. The single read/write bit specifying whether the master is sending data to the slave (low voltage level) or requesting data from it (high voltage level).

3. The addressed slave node (which the Master wish to communicate with) will replies with an ACK bit to the MASTER by pulling the SDA line low for one bit.

4. After the master detects the ACK bit from the slave, it will start the transaction and send the data frame(s).

5. The Slave after receive of each data frame will send a ACK bit back tot he MASTER.

6. Once the MASTER finished with the transaction, it will send a STOP condition to the slave by switching SCL high before switching SDA high.

Reference : Circuit Basic : I2C



I2C communication between Master and multiple Slaves and Software Serial between Master and laptop

What will I design?
I will be designing and making three boards. One Master and two Slaves (namely Slave 1 and Slave 2). I be using ATTiny45, as in my output device week I having issue with ATTiny44A board running I2C and did not have time to resolve the issue...

How do I connect the boards?
The I2C bus require a MASTER that will initialise and control the clock for the whole I2C bus.

The MASTER board will be connected to the host PC/laptop via a FTDI (usb/serial) adaptor. This allows me to send command to MASTER.

The MASTER, SLAVE 1 and SLAVE 2 are physically connected by an I2C ribbon cable which represent the I2C bus.

The I2C ribbon cable consist of four wires (SDA, SCL, VCC and GND).

I am using ATTiny45 which is pin 5 (PB0) for SDA pin and pin7 (PB2) for SCL pin.

Microcontroller SDA SCL
Arduino Uno A4 A5
ATMega328P 20 21
ATtiny45 5 (PB0) 7 (PB2)
ATtiny44A 7 (PA6) 9 (PA4)


What will I test?
1) User send a command (data) from serial monitor on the host PC /laptop to MASTER.

2) MASTER evaluate Command send :
     '0' : which refer to the MASTER node, the red LED on the MASTER will blink three times.
     '1' : which refer to 'Slave 1', MASTER tells SLAVE 1 to blink led 3 times.
     '2' : which refer to 'SLAVE 2', MASTER tells SLAVE 2 to blink led 3 times.

3) The MASTER reply to the User what message the User had typed.


Schematics and Board Design

I used a pull-up resistor of 10k ohm for the I2C bus (on the SDA and SCL lines) on the Master board. Only one sets of pull-up resistors is required for the whole I2C bus. Note: If your boards are far away from each other, you might reduce the resistor value.


Components required for stuffing my boards

Components require for the Master board :

Components require for two Slaves boards :

Components require for I2C ribbon :

Note : Preferably to use ATTiny85 if your Fab Lab has stock, as the ATTiny85 provide more Program Memory Space if you are writing Arduino codes.


Milling and stuffing my boards

In EAGLE, I rotated the boards to be in vertical orientation, so I can print two boards together in one 2"x3" FR1 copper-clad board. Soldering three boards at one go took some time and effort. Laster I used a glue gun and apply glue to all the vertical as well as horizontal connector to make sure it is firmly secured in position.

    


My completed boards



Programming for I2C as well as Software Serial

1) Burn the bootloader :
After finished milling the three boards, I uploaded the bootloader using Arduino IDE. Ensure that the clock speed is set to run at 'Internal 8Mhz', if you set to Internal 1Mhz, you will see 'funny' text on the Serial Monitor.

Upload a simple blink LED Arduino sketch to see if the LEDs are working fine.

2) The 'TinyWire' library
As I am using the 'ATTiny' microntroller, I downloaded TinyWireM.h library (for MASTER) and a modified TinyWireS.h library by 'rambo' (for SLAVE)

If you are using the ATMega328P microntroller (or Arduino Uno v3), you can use the built-in 'Wire.h' Arduino library. Take note that the TinyWire library only work on ATTiny MCU as it is built upon 'TWI' which is a sub-set of the implementation of I2C on the ATTiny MCUs family. The Wire.h does not work on ATTiny MCU and the TinyWire libray does not work on ATMega328p MCU. The TinyWire libray also does not work with ATTiny44A MCU, one would need to edit the C source file and change the name as well as the pins assignment first.

3) Coding : Initilaizing I2C
Addressing : There is no need to specify the address for the single MASTER in the I2C bus arrangement. If the board is initialised as a slave, then you would need to specify an address, between 0 to 127 (7-bit address), as its parameter to uniquely identifying it on the I2C bus. (Simon Monk, 2019, pp157)

Setup of I2C nodes :
   MASTER : TinyWireM.begin();
   SLAVE : TinyWireS.begin(x); // where x is the node number

Master sending data :
The 'TinyWireM.beginTransmission(slave address);' function enable the MASTER to start sending data to the designated SLAVE address which is on the I2C bus.

The TinyWireM.send(data) buffer up bytes to send, this function can be called multiple times

TinyWireM.endTransmission() function actually send the bytes in the buffer

4) Upload the code to the Master :
Note : I added in a few more global variable and this quickly filled up the 'dynamic memory' space. So I will have to stop my testing here and not adding any more codes...





My Code for the MASTER :

/*
  attiny_i2c_master.ino
  by Lucas Lim
  created on 6/5/2020

  Purpose: MASTER receive 'instruction' from PC via serial port (Tx, Rx)
           MASTER reply acknoledge message back to PC
           If instruction is to Slave 1/2, MASTER re-route to addressed
              SLAVE 1/2 via I2C bus
           Instruction :
             '0' MASTER will Blink LED
             '1' SLAVE 1 will blink LED
             '2' SLAVE 2 will blink LED
  NOTE! - It is important to use pullups on the SDA & SCL lines on the I2C bus!

  The work is provided for academic purpose for Fab Academy 2020.
  Users accept all as is, no warranty is provided, no call/email
  from users will get a response. Users accept all liability.
*/

#include <SoftwareSerial.h>
#include <TinyWireM.h>

#define slave1 1
#define slave2 2

const int ledPin = 1;   //PB1 - Arduino Pin No. 1
const int tx = 3;       //PB3 - Arduino Pin No. 3
const int rx = 4;       //PB4 - Arduino Pin No. 4
int times = 3;          //Number of times that LED will blink

SoftwareSerial mySerial(rx, tx);

void setup() {
  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  mySerial.println("< Serial Communication is ready>");

  // initialize the led pin as an output.
  pinMode(ledPin, OUTPUT);

  // join I2C bus as MASTER
  TinyWireM.begin();
}



void process_incoming_command(char cmd)
// Received user command from host PC via Software Serial
{
  switch (cmd)
  {
    case '0':                     // MASTER to blink led
      blink_led(times);
      break;
    case '1':                     // Slave 1 to blink led
      TinyWireM.beginTransmission(slave1);
      TinyWireM.send(times);
      TinyWireM.endTransmission();
      break;
    case '2':                     // Slave 2 to blink led
      TinyWireM.beginTransmission(slave2);
      TinyWireM.send(times);
      TinyWireM.endTransmission();
      break;
  }
}


void blink_led(int times)
{
    while(times--)
    {
        digitalWrite(ledPin, HIGH);
        delay(500);
        digitalWrite(ledPin, LOW);
        delay(500);
    }
}

void loop() {
  // don't read unless there is data, Serial.available() > 0
  if(mySerial.available())
  {
    char cmd = mySerial.read();  //User to type '0' for MASTER, '1' for SLAVE 1 and '2' for SLAVE 2"
    mySerial.print("You typed : ");
    mySerial.println(cmd);
    process_incoming_command(cmd);
  }

  delay(50);   //limit how fast the serial check/update
}


My Code for Slave 1 :
Note : The code for Slave 2 is the same as Slave 1 except the following line at the top :
  "#define i2c_slave2 2"
which set the I2C address for Slave 2 board

/*
  attiny_i2c_slave1.ino
  by Lucas Lim
  created on 6/5/2020

  Purpose: Receive data from MASTER via the I2C bus
           Blink the LED

  The work is provided for academic purpose for Fab Academy 2020.
  Users accept all as is, no warranty is provided, no call/email
  from users will get a response. Users accept all liability.
*/

#include <TinyWireS.h>

#define i2c_slave1 1

const int ledPin = 4;       //PB4 - Arduino Pin No. 4
//int times = 0;            //Number of times that LED will blink
byte received = 0;          //Received data(on number of times that LED will blink) from MASTER


void setup()
{
    TinyWireS.begin(i2c_slave1);    // join I2C bus as slave

}


void blink_led(int times)
{
    while(times--)
    {
        digitalWrite(ledPin, HIGH);
        delay(500);
        digitalWrite(ledPin, LOW);
        delay(500);
    }
}


void loop()
{

    if (TinyWireS.available()) {
      received = TinyWireS.receive();
      blink_led(received);
    }
    received = 0;


    // This needs to be here
    TinyWireS_stop_check();
}



Video on I2C implementation

Duration : 57 secs
Audio : No




Group Assignment : Send a message between two projects

The expectation for this week group assignment is to send a message between any combination of boards, computers and/or mobile devices.

Therefore I going to explore furthur into the posibility of incorporating I2C protocol to my final project or how I can better use of I2C protocol.

I am using two of my fabricated boards, a ATMega328P board (fabricated in output device week) as well as ATTiny45 (fabricated this week). Each board is running it's own process and serving a different function. I have added an ultrasonic sensor and a I2C LCD display module. I came up with the following plan.

Setup :
Photo
Here I have :


I2C Bus Wiring :
For the wiring, I using back my fabricated i2c ribbbon wire as well as used a number of jumper wires to branch out from I2C ribbon wire to connect to the two boards and LCD. This allows me to easily space the devices out, better cable managemeent and better handling(moving around).

White color jumper wire for VCC,
Black color jumper wire for GND,
Blue color jumper wire for SDA and
Yellow color jumper wire for SCL.
Photo

Steps :
1. I layout the boards and I2C LCD module as shown in picture above.
Photo

2. I setup the I2C MASTER as well as the I2C address for the Slave with the following codes.

MASTER :
Wire.begin();     // join i2c bus as master, no address is stated

SLAVE :
TinyWireS.begin(i2c_slave);     // join i2c network as slave

Note :
I am using the ATMega328P board as the MASTER, as earlier on I found that my ATTiny45 will quickly run out of memory space.
The MASTER generates the clock and has no address. For both boards, I had set to run at 8Mhz Internal Clock just to be sure it will work without any issue.

3. I ran my modified I2C scanner code on the MASTER, and is able to detect the I2C Slave node as well as the I2C LCD. This show that the addressing is working ok.
Photo

4. I have writen my code using Arduino IDE, with two seperate windows open side-by-side displaying onto my 28" LCD monitor. Testing and programming the board one at a time, one for MASTER With the ATMega328P, the other for the Slave with ATTiny45.
Photo

5. Scanning for LCD : The I2C MASTER board when powered up, will first scan for the I2C LCD and if found, it will display "LCD Ready" on the LCD. This show that LCD is working ok.

#define i2c_lcd 63
...
Wire.beginTransmission (i2c_lcd);
    if (Wire.endTransmission() == 0) {     // check if lcd is available
lcd.setCursor(2, 0);
lcd.print("LCD ready"); ...


Photo

6. Scanning for Slave : The MASTER will then scan for the I2C Slave and if found, it will display "Slave detected" on the LCD display, otherwise, it will show "Slave is not found!".

#define i2c_slave 2
...
Wire.beginTransmission (i2c_slave);
// if return = 2 : received NACK on transmit of address
// if return = 4 : other error
if (Wire.endTransmission() ==2 || Wire.endTransmission() ==4) {        // check if slave is available
    lcd.setCursor(1, 0);
    lcd.print("Slave detected");
    ...

Photo

If one of the wires is loose or not connected to the Slave board, Slave will not be able to response. This is to check the connection.
Photo

7. After detecting the Slave node, the MASTER will talk to the Slave and request data from the Slave. The Slave will send a message ("2") back to MASTER. MASTER will verify the message, if matches the expected response, MASTER will blink the LED 3 times on the MASTER board and display the message received on the LCD.

MASTER :
Wire.requestFrom(i2c_slave, 1);
while(Wire.available())
{
    c1 = Wire.read();    // receive a byte from Slave # 2
}

lcd.print("slave # ");
lcd.print(i2c_slave);
lcd.print("received : ");
lcd.print(int(c1));
if (c1 == i2c_slave) {
    delay(2000);    // to facilitate the recording of video, this can be deleted
    blink_led(3);    // blink led to indicate receive of acknowledgement (or data) from Slave
}
delay(2000);
lcd.clear();

SLAVE :
TinyWireS.onRequest(requestEvent);;    // listen to request from master node
...
// Gets call when receive a request from MASTER and response with a message
void requestEvent() {
    TinyWireS.send(2);
}

Photo

8. The MASTER is able to calculate and display the reading to the LCD.
Photo

9. Slave is able to print out distance reading to serial monitor on the notebook via a FTDI adaptor. With this, an application can be added on later to do data visualisation or provide a GUI interface.
Photo

My Code for the MASTER connected with the sensor :

/*
  M328P_Master_ultrasonic_and_i2c_scanner.ino
  by Lucas Lim
  created on 10/5/2020

  Purpose: 1) To detect i2c nodes
           2) To receive, calculate the distance in cm and
              send reading to slave via I2C bus
           3) To display reading to I2C LCD display module
              via I2C bus too

  The work is provided for academic purpose for Fab Academy 2020.
  Users accept all as is, no warranty is provided, no call/email
  from users will get a response. Users accept all liability.
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// i2c nodes address on the network
#define i2c_lcd 63
#define i2c_slave 2

LiquidCrystal_I2C lcd(i2c_lcd, 16, 2);     // set the LCD address, for a 16 chars by 2 rows display

int trigPin = 7;             // Trigger (PD7 - arduino pin no. 7)
int echoPin = 8;             // Echo    (PB5 - arduino pin no. 8)
long duration, cm;
const int ledPin = 2;        // PD2 - Arduino pin no. 2
byte c1 = 0;                 // for requestFrom()

void blink_led(int times)
{
    while(times--) {
        digitalWrite(ledPin, HIGH);
        delay(350);
        digitalWrite(ledPin, LOW);
        delay(350);
    }
}


void i2c_scan_nodes() {
  // scan for LCD
  Wire.beginTransmission (i2c_lcd);
  if (Wire.endTransmission() == 0) {
    lcd.setCursor(2, 0);
    lcd.print("LCD ready");
    delay(3000);
    lcd.clear();
  }
  Wire.endTransmission();

  // scan for I2C Slave
  Wire.beginTransmission (i2c_slave);
  if (Wire.endTransmission() == 0) {
    lcd.setCursor(1, 0);
    lcd.print("Slave detected");
    delay(3000);
    lcd.clear();
  }
  // if return = 2 : received NACK on transmit of address
  // if return = 4 : other error
  else if (Wire.endTransmission() ==2 || Wire.endTransmission() ==4) {
    lcd.setCursor(0, 0);
    lcd.print("Slave not found!");
    delay(3000);
    lcd.clear();
  }
  Wire.endTransmission();


  Wire.requestFrom(i2c_slave, 1);
  while(Wire.available())
  {
    c1 = Wire.read();        // receive a byte from Slave # 2
  }

  lcd.setCursor(2, 0);
  lcd.print("slave # ");
  lcd.setCursor(9, 0);
  lcd.print(i2c_slave);
  lcd.setCursor(2, 1);
  lcd.print("received : ");
  lcd.setCursor(13, 1);
  lcd.print(int(c1));
  if (c1 == i2c_slave) {
    delay(2000);              // to facilitate the recording of video, this can be deleted
    blink_led(3);             // blink led to indicate receive of acknowledgement (or data) from Slave
  }
  delay(2000);
  lcd.clear();
}


void sensor() {
  // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  digitalWrite(trigPin, LOW);
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(echoPin, INPUT);
  duration = pulseIn(echoPin, HIGH);

  // Convert the time into a distance
  cm = duration *0.034/2;
}


// Display ultrasonic sensor reading to LCD
void lcd_display(int cm)
{
  lcd.setCursor(4,1);
  lcd.print("    ");      // clear the previous reading

  lcd.setCursor(4,1);
  lcd.print(cm);
}


// send reading to Slave # 2
void send_reading(int msg)
{
  Wire.beginTransmission(i2c_slave);
  Wire.write(msg);
  Wire.endTransmission();
}



void setup() {
  delay(2000);   // to facilitate the recording of video, this can be deleted

  // join i2c bus as master, no address is require
  Wire.begin();

  // Initialize the LCD, must be called in order to initialize I2C communication
  // lcd.begin();           // LCD library for ATTiny45
  lcd.init();         // LCD library from Arduino

  // Turn on the backlight
  lcd.backlight();

  // blink LED (this must be setup calling i2c_scan_nodes)
  pinMode(ledPin, OUTPUT);

  // scan for presence of I2C LCD and Slave node
  i2c_scan_nodes();

  // display static text info for distance reading
  // these text doesn't change on LCD
  lcd.setCursor(2, 0);
  lcd.print("Distance is");
  lcd.setCursor(8,1);
  lcd.print("cm");

  // setup ultrasonic sensor pins
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}


void loop() {
  // activate sensor and receive reading data
  sensor();

  //send reading to SLAVE # 2
  send_reading(cm);

  lcd_display(cm);

  // slow down for easy reading
  delay(800);
}

My Code for the SLAVE connected to the PC/notebook:

/*
  T45_Slave_with_Serial_monitor.ino
  by Lucas Lim
  created on 10/5/2020

  Purpose: 1) To receive reading from MASTER, and
              display reading to serial monitor
              on user pc/notebook via FTDI


  The work is provided for academic purpose for Fab Academy 2020.
  Users accept all as is, no warranty is provided, no call/email
  from users will get a response. Users accept all liability.
*/

#include <SoftwareSerial.h>
#include <TinyWireS.h>

#define i2c_slave 2

int received = 0;           // receive cm reading
const int tx = 3;           // PB3 - Arduino pin no. 3
const int rx = 4;           // PB4 - Arduino pin no. 4
const int ledPin = 1;       // PB1 - Arduino pin no. 1

SoftwareSerial mySerial(rx, tx);



// Gets call when receive a request from MASTER and response with a message
void requestEvent() {
  TinyWireS.send(2);
}


void receiveEvent() {
  received = TinyWireS.receive();
}


void blink_led(int times)
{
    while(times--)
    {
        digitalWrite(ledPin, HIGH);
        delay(500);
        digitalWrite(ledPin, LOW);
        delay(500);
    }
}


// display reading to serial monitor
void display_reading()
{
    //if (TinyWireS.available()) {
      received = TinyWireS.receive();
      mySerial.println(received);
    //}
}




void setup() {
  TinyWireS.begin(i2c_slave);            // join i2c network as slave

  mySerial.begin(9600);                  // start serial for output

  TinyWireS.onRequest(requestEvent);     // listen to request from master node
  TinyWireS.onReceive(receiveEvent);     // function to be called when a slave device receives a transmission from a master

  pinMode(ledPin, OUTPUT);
}


void loop() {
  //display reading to serial monitor
  mySerial.println(received);

  // This needs to be here, Detects a stop sending command.
  TinyWireS_stop_check();
}


Video on demostration of commnuication between two projects

Duration : 41 secs
No Audio
Speed increased by 20%



Reflection :

Serial Commincation :

I learn that there are basically three type of communication protocols available UART, SPI, and I2C (or TWI in ATTiny44/45 chips). UART is the serial communication with the computer using Tx and Rx pins, and I2C using SDA and SCL pins. Each of these differ in their implementation, but they also can co-exist together.

Delay faced :
Previously I just mill one board at a time. But this time round, I am milling three boards at one go with the board designs arranged nicely next to each other... and didn't realise the tip of my v-bit was worn out half-way through the milling. During the etching, it is good that I periodically check on the traces to see if it look ok.


Programming : I2C :
I was using the original TinyWireS library, but having issue with RequestEvent() function working together with Serial Communication. Slave is not able to response to MASTER and no data was send. Moreover, the reading did not printed out on the screen. Later I changed the TinyWireS library to a modified version from "rambo" and it work fine.

Note on I2C Programming :
1. Try to set the same clock speed on all the I2C nodes, even thought the MASTER controls the clock. So I set the nodes to all use 8Mhz Internal Clock.
2. Don't put a Delay(); inside any I2C function or during I2C communication. It might have impact on the I2C communication process and 'jam' up the bus...
3. Try not to put a Seial.print() inside any I2C function or during I2C communication for the similiar reason above.
4. The memory space of ATTiny45 is quite limited. This restrict the number of variable as well as coding you can write.

Multiple projects :
Integrating multiple projects into the same I2C bus took me a lot of time, as I had to move to-and-fro to test and program the MASTER board and then the SLAVE board and back again...

Here I learned that you can have smaller board(s) to handle the sensor or simple serial communication or other simple role/function, while a board with more memory space can handle more complex operation, calculation, or tother heavy processing like using Array, etc. This is sort-of like a 'distributed' model where the workload can be distributed out.

I can use my ATMega328P board to do all these, but the wiring will be messy or untidy. I2C might also be seen a good way to do cable management.

Having the reading output to serial, I can link up with an GUI application like python.


Files :

Design :
- I2C Master Schemetic
- I2C Master Board
- I2C Slave Schemetic
- I2C Slave Board

Individual Assignment :
- attiny_i2c_master.ino
- attiny_i2c_slave1.ino
- attiny_i2c_slave2.ino

Multiple projects with ultrasonic sensor :
- M328P_Master_ultrasonic_and_i2c_scanner.ino
- T45_Slave_with_Serial_monitor.ino



References :

- Wikipedia : I²C
- Sparkfun : I²C
- Circuit Basic : I²C
- ATTiny I2C slave
- Simon Monk, 2019, Programming Arduino Next Steps : Going Futhur with sketches, Second Edition, McGraw-Hill Education, USA, pp 153 - 159 on I2C
- More info on the function of Wire.h on Arduino website
- More info on the function of TinyWireM.h on GitHub