Skip to content

Wired Communication

Introduction

There are quite a few wired protocols in which microcontrollers can talk to one another. Before digging into them, it is important to understand the differences between their characteristics.

Serial vs. Parallel

Parallel interfaces transfer multiple bits at the same time. They usually require buses of data - transmitting across eight, sixteen, or more wires. This means that waves of data can be sent, with high speeds, but with a lot of wires.

Image source: Sparkfun

Have you seen this?

Many printers use parallel communication before the advent of serial communication!

Serial interfaces stream their data with a reference signal, one single bit at a time. These interfaces can operate on as little as one wire, usually never more than four:

Image source: Sparkfun

Synchronous vs. Asynchronous

“Asynchronous” (not synchronous) means that data is transferred without support from an external clock signal. This transmission method is perfect for minimizing the required wires and I/O pins, but it does mean we need to put some extra effort into reliably transferring and receiving data.

Image source: Sparkfun

“Synchronous” data interface always pairs its data line(s) with a clock signal, so all devices on a synchronous bus share a common clock. This makes for a more straightforward, often faster transfer, but it also requires at least one extra wire between communicating devices.

Image source: Sparkfun

Serial communication

Learn more

In this section, we will focus on serial communication, making distinction between synchronous and asynchronous.

Asynchronous communication

We use the asynchronous communication when data can be sent without timing constraints as well as oftenly less speed, with the benefit of using one less wire, one less port on each side. The most well known asynchronous communication protocol is the RX/TX or simply Serial protocol (because it’s the most important).

RX/TX

By RX/TX we know the most common way of serial communication. It requires only two wires, appart from a common ground:

Data is sent asynchronously, so it means that both ends of the communication need to agree on some topics, being the speed the most important one (known as baud rate).

We also have to agree on how long the data strings are. Generally, they are grouped by bytes, with some extras, like parity bits or and synchronisation bits (start and stop). Usually, an asynchronous serial communication frame consists of a START bit (1 bit) followed by a data byte (8 bits) and then a STOP bit (1 bit), which forms a 10-bit frame as shown in the figure below. The frame can also consist of 2 STOP bits instead of a single bit, and there can also be a PARITY bit after the STOP bit.

More info

If you want to learn much more, you can visit this tutorial

UART

UART stands for Universal Asynchronous Receiver/Transmitter and is the piece of hardware in charge of managing the data. Microcontrollers might have one, many or none UARTs:

Sometimes it’s also present in a hybrid way, as UsART, which implements both, synchronous and asynchronous communication.

No UART? No worries

Chips without UART still can implementing by bit-banging (using pins to send the data as voltage levels very quickly). The SoftwareSerial is for example used in this case.

Libraries for Arduino

The most basic example of all time:

```

include

void setup() { Serial.begin(9600); // OR, in some boards like the arduino Zero: SerialUSB.begin(115200);

while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
}

}

void loop(){ Serial.println(“Hello Fabacademy”); } ```

Example using both, serial and software serial, when we only have one hardware serial comm:

```

include

SoftwareSerial BT1(10, 11); // RX | TX

void setup() {

Serial.begin(9600);

BT1.begin(38400); // No need for it to be at the same speed!

}

void loop() {

// Get something on BT1
if (BT1.available()){
    // Read it and send it to Serial
    Serial.write(BT1.read());
}

// Get something on Serial
if (Serial.available()) {
    // Read it and send it to BT1
    BT1.write(Serial.read());
}

} ```

Libraries for Python

``` import serial

PORT = ‘/dev/cu.usbmodem1421’ BAUDRATE = 115200

ser = serial.Serial(PORT, BAUDRATE)

print ser.readline().replace(“\r\n”, “”)

ser.write(‘Hello’) ```

V-USB

V-USB is a library that allows to add USB connectivity to ATtiny microcontrollers (it was previously named tinyUSB).

USB 1.1

V-USB will create an USB 1.1 compliant low-speed device.

USB is comprised of 4 lines: Vcc, GND, D+ and D-. It is a differential pair circuit in which, roughly, the difference between D+ and D- is the actual signal we want to transmit.

The data lines require 3.3V, and for that reason we need to limit the voltage coming from many USB ports (computers, chargers, etc). Some ways to do this:

  • Using an LDO in the Vcc
  • Using zener diodes in the D+, D-

Great tutorials available here

Find a quite nice tutorial for the hardware here And the actual implementation of V-USB here

V-USB is a library that allows to add USB connectivity to ATtiny microcontrollers (it was previously named tinyUSB).

USB 1.1

V-USB will create an USB 1.1 compliant low-speed device.

Digested USB specification

Find it here

Synchronous communication

When timing and speed are are important, and it’s worth having more wires, we will use synchronous communication. This means that we will have a clock line that stablishes the rate at which data is transferred. Most common inter-chip communication is implemented like this.

Very important

I2C and SPI seen here are seen at a descriptive level. When facing a new design, sensor usage, etc. try to find already existing libraries from people like Adafruit, Sparkfun, Arduino and others. Unless the set up is super simple, the implementation is not straight forward in many cases and it’s better to follow a more copy approach.

I2C

The Inter-integrated Circuit (I2C) Protocol is a protocol intended to allow multiple slave digital integrated circuits (chips) to communicate with one or more master chips. It is only meant for short distance communications within a single device. I2C is a convenient way of setting up communication because:

  • It allows for several masters
  • It allows for several slaves
  • The number of wires does not change

It allows for several masters to be in the same system and for only the masters to be able to drive the data line high. This means that no slave will be able to lock the line in case other slave or master is talking:

Image Source: NXP semiconductors

So, I2C bus consists of two signals (apart from VCC and GND): SCL and SDA. SCL is the clock signal, and SDA is the data signal. Each device is recognized by an unique address (whether it is a microcontroller, LCD driver, memory or keyboard interface) and can operate as either a transmitter or receiver, depending on the function of the device. In addition to transmitters and receivers, devices can also be considered as masters or slaves when performing data transfers. A master is the device which initiates a data transfer on the bus and generates the clock signals to permit that transfer. At that time, any device addressed is considered a slave.

More and more!

Check this AAN by NXP semiconductors to learn much more about I2C.

Remember the pull-ups!

The data lines need to be driven high when they are not used, and for that they need pull-up resistors. Values for the pull up resistors could be around 5kΩ.

The actual protocol works with messages broken up into two types of frame: an address frame, where the master indicates the slave to which the message is being sent, and one or more data frames, which are 8-bit data messages passed from master to slave or vice versa. Data is placed on the SDA line after SCL goes low, and is sampled after the SCL line goes high.

Image Source: Sparkfun

Grove connector from Seeed

Grove, by Seeed, uses this connector as a way to interface with several protocols. This is how it looks for the I2C one:

Libraries for Arduino

Examples explained

The following examples are copied from here and here.

Example 1 Master as receiver: requesting information

Receiver:

```

include

void setup() { Wire.begin(); // join i2c bus (address optional for master) Serial.begin(9600); // start serial for output }

void loop() { Wire.requestFrom(8, 6); // request 6 bytes from slave device #8

while (Wire.available()) { // slave may send less than requested
    char c = Wire.read();    // receive a byte as character
    Serial.print(c);         // print the character
}

delay(500);

}

```

Sender:

```

include

void setup() { Wire.begin(8); // join i2c bus with address #8 Wire.onRequest(requestEvent); // register event }

void loop() { delay(100); }

// function that executes whenever data is requested by master // this function is registered as an event, see setup() void requestEvent() { Wire.write(“hello “); // respond with message of 6 bytes // as expected by master } ```

Example 2 Master as sender: sending information

Sender:

```

include

void setup() { Wire.begin(); // join i2c bus (address optional for master) }

byte x = 0;

void loop() { Wire.beginTransmission(8); // transmit to device #8 Wire.write(“x is “); // sends five bytes Wire.write(x); // sends one byte Wire.endTransmission(); // stop transmitting

x++;
delay(500);

} ```

Receiver:

```

include

void setup() { Wire.begin(8); // join i2c bus with address #8 Wire.onReceive(receiveEvent); // register event Serial.begin(9600); // start serial for output }

void loop() { delay(100); }

// function that executes whenever data is received from master // this function is registered as an event, see setup() void receiveEvent(int howMany) { while (1 < Wire.available()) { // loop through all but the last char c = Wire.read(); // receive byte as a character Serial.print©; // print the character } int x = Wire.read(); // receive byte as an integer Serial.println(x); // print the integer } ```

Example (for an analog pressure sensor that sends data via I2C):

```

include

include

include

include

// Set I2C Slave address

define I2C_SLAVE_ADDRESS 0x13

ifndef TWI_RX_BUFFER_SIZE

define TWI_RX_BUFFER_SIZE ( 16 )

endif

// Sensor and Indicator Led Pins

define SENSOR 1

// Measurement

define MAX_TICK 50

unsigned int tick = 0;

//Smoothing Factor

define LPF_FACTOR 0.5

volatile byte reg_position = 0; const byte reg_size = sizeof(i2c_regs); unsigned long lastReadout = 0;

// I2C Stuff volatile uint8_t i2c_regs[] = { 0, //older 8 0 //younger 8 };

void requestEvent() { TinyWireS.send(i2c_regs[reg_position]);

reg_position++;
if (reg_position >= reg_size)
{
        reg_position = 0;
}

}

void setup() { analogReference(EXTERNAL);

// Setup I2C
TinyWireS.begin(I2C_SLAVE_ADDRESS);
TinyWireS.onRequest(requestEvent);

// set clock divider to /1
CLKPR = (1 << CLKPCE);
CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);

}

void loop() { unsigned long currentMillis = millis();

// On tick value 0, do measurements

if (abs(currentMillis - lastReadout) > MAX_TICK) {

    // Read the values
    int sensorReading = analogRead(SENSOR);

    // Treat them
    float Vs = 0;
    float pressure = 0;

    Vs = ((float) sensorReading  + 0.5 ) / 1024.0 * 5.0;
    pressure = (Vs*687.8/Va - 18.77); // in kPa

    i2c_regs[0] = pressure >> 8 & 0xFF;
    i2c_regs[1] = pressure & 0xFF;

    // Update the last readout
    lastReadout = currentMillis;
}

}

```

Libraries for Python

Example 1, master as receiver

``` import smbus import time

bus = smbus.SMBus(1) # Indicates /dev/i2c-1 DEVICE_ADDRESS = 0x13 packet_size = 4

def ReadSensor(_address):

i = 0
_value = 0

while (i < packet_size):

    _measure = bus.read_i2c_block_data(_address, 0, 1)

    _value |= _measure[0] << (8*(packet_size-(1+i)))
    i+=1

return _value

while True: result = ReadSensor(DEVICE_ADDRESS) print result time.sleep(1) ```

Example 2, master as sender

``` import smbus

bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)

DEVICE_ADDRESS = 0x15 #7 bit address (will be left shifted to add the read write bit) DEVICE_REG_MODE1 = 0x00 DEVICE_REG_LEDOUT0 = 0x1d

Write a single register

bus.write_byte_data(DEVICE_ADDRESS, DEVICE_REG_MODE1, 0x80)

Write an array of registers

ledout_values = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff] bus.write_i2c_block_data(DEVICE_ADDRESS, DEVICE_REG_LEDOUT0, ledout_values) ```

WiringPi

If you are using a Raspberry Pi, you can use WiringPi (C++), instead of sm.BUS (Python).

More references

sm.BUS in python is not greatly documented with examples, but here you can find some reference

SPI

Serial Peripheral Interface (SPI) is a synchronous serial protocol, used for short-distance communication, primarily in embedded systems.

Image Credit: SparkFun

It uses a master-slave architecture with a single master. The master device originates the frame for reading and writing. Multiple slave-devices are supported through selection with individual slave select (SS) lines.

All these pins are explained in the SPI tutorial above. To summarize:

  • In SPI, only one side generates the clock signal (usually called CLK or SCK for S**erial **C**loc**K). The side that generates the clock is called the “master”, and the other side is called the “slave”. There is always only one master (which is almost always your microcontroller), but there can be multiple slaves.

  • When data is sent from the master to a slave, it’s sent on a data line called MOSI, for “Master Out / Slave In”. If the slave needs to send a response back to the master, the master will continue to generate a prearranged number of clock cycles, and the slave will put the data onto a third data line called MISO, for “Master In / Slave Out”.

  • To select the Slave, we use the SS line, by dropping it, telling the particular slave to go active

Image Credit: SparkFun

We can summarize all this in the following image, but note we will be using a different Serial Communication on the PC side:

Image Credit: ATMEL

SPI is used by many programmers to flash the microcontroller’s code onto the flash memory, mainly because it’s fast, really fast. In many cases is the only protocol available, and in some other cases it’s much faster than I2C.

Read the datasheet

For example, for the ATtiny, the Section 19.5 explains all the necessary instructions for the Serial Programming. Below, the needed connections are summarized:

Symbol Pins I/O Description
MOSI PA6 I Serial Data In
MISO PA5 O Serial Data Out
SCK PA4 I Serial Clock

A master can communicate with multiple slaves (however only one at a time). It does this by asserting SS for one slave and de-asserting it for all the others. The slave which has SS asserted (usually this means LOW) configures its MISO pin as an output so that slave, and that slave alone, can respond to the master. The other slaves ignore any incoming clock pulses if SS is not asserted. Thus you need one additional signal for each slave.

More info

  • Check the Serial Peripheral Interface - SPI, to understand why it’s important to use Synchronous Serial communication.
  • Another great page by gammon. Probably the best of the best that you’ll ever see

Libraries for arduino

Example, arduino as master, sending:

```

include

void setup (void) { digitalWrite(SS, HIGH); // ensure SS stays high SPI.begin (); } // end of setup

void loop (void) { byte c;

// enable Slave Select
digitalWrite(SS, LOW);    // SS is pin 10

// send test string
for (const char * p = "Fab" ; c = *p; p++)
    SPI.transfer (c);

// disable Slave Select
digitalWrite(SS, HIGH);

delay (100);
} // end of loop

```

Looking to implement SPI?

Check the first response on this thread