Skip to Content

I2C Bus on the Arduino-based PLC for industrial automation

SDA, SCL, pull-ups and the Wire library on M-Duino and Ardbox
February 11, 2020 by
I2C Bus on the Arduino-based PLC for industrial automation
Boot & Work Corp. S.L, Support Team

Complete guide: Choosing an industrial controller

I2C is a two-wire synchronous bus: SDA carries the data and SCL the clock, and a master talks to slaves that each have a 7-bit address. On an Industrial Shields Arduino based PLC, you connect the devices to the SDA and SCL terminals of the PLC (Arduino pins 20 and 21 on the M-Duino 21+), add pull-up resistors, and use the Wire library: beginTransmission(), write() and endTransmission() to write, requestFrom() and read() to read. I2C is made for short distances, inside the cabinet.

All the Arduino based controllers of Industrial Shields, M-Duino and Ardbox, have an I2C port, and so do the ESP32 PLC and the Raspberry PLC. Many devices have an I2C connection: analog to digital converters, real-time clocks, EEPROM memories, displays, accelerometers and compasses. This guide uses an M-Duino 21+. The Wire code is the same on the other Arduino based models: only the SDA and SCL terminals change.

How does the I2C protocol work?

Philips created I2C (Inter-Integrated Circuit) in 1982 to connect chips inside its products. Today the specification is published by NXP (UM10204). The master generates the clock and starts every transfer. Slaves never start a transfer and do not talk to each other.

  1. Start. The master pulls SDA low while SCL is high.
  2. Address and direction. 7 address bits and 1 bit for read or write.
  3. ACK. The slave with that address pulls SDA low for one clock to say "I am here".
  4. Data. One or more bytes, each one followed by an ACK. When the master reads, it answers the last byte with NACK.
  5. Stop. The master releases SDA while SCL is high. With a repeated start instead of a stop, the master keeps the bus and starts a new transfer.
I2C timing diagram: SCL clock and SDA data with start, 7-bit address 0110001 and W bit, ACK, data byte 01111010, NACK and stop
One transfer: start, address 0110001 with the W bit, ACK, data byte 01111010, NACK and stop.
ModeSpeedWire.setClock() on the AVR
Standard mode100 kbit/s100000
Fast mode400 kbit/s400000
Fast mode plus1 Mbit/sNot on all processors
High speed mode3.4 Mbit/sNot on all processors

The 7-bit address gives 128 combinations. The specification reserves 0x00 to 0x07 and 0x78 to 0x7F, so devices use 0x08 to 0x77. Each device on the bus needs its own address. Many devices let you change the last bits with address pins.

Advantages and limits of I2C

Thumb up and thumb down icons for the advantages and limits of I2C
Advantages and limits of the I2C bus.
AdvantagesLimits
Two wires (plus ground) for many devicesShort distance: the bus capacitance limits the cable length
Each byte is acknowledged (ACK), so the master knows if a device answersNo CRC: the ACK says that a byte arrived, not that its content is right
Addressing on the bus, without one select line per deviceHalf duplex and medium speed
Supported by most microcontrollers and sensorsA device that holds SDA low can block the whole bus

Wiring and pull-up resistors

Connect SDA to SDA and SCL to SCL on every device, and connect the grounds. SDA and SCL are open-drain lines: the devices only pull them down, and a pull-up resistor to Vcc brings them up.

I2C bus: one master and several slaves share the SDA and SCL lines, with two pull-up resistors to Vcc
One master and several slaves on the same SDA and SCL lines, with the pull-up resistors to Vcc.
  • Pull-ups. Wire.begin() turns on the internal pull-ups of the microcontroller, but they are weak: the edges rise slowly, and the bus only works at low speed and with short cables. For a reliable bus, add external pull-ups of 1 kΩ to 4.7 kΩ. Industrial Shields has an I2C pull-up resistor module for DIN rail.
  • Logic level. The microcontrollers of the M-Duino and the Ardbox work at 5 V. A 3.3 V device needs a level shifter.
  • Distance. Keep the I2C wiring short and inside the cabinet. For longer distances use an I2C bus extender, or a field bus such as RS-485 with Modbus.
  • Addresses in use. On models with I/O expanders on the I2C bus, such as the Raspberry PLC, some addresses are already taken. The datasheet of the Raspberry PLC lists them.

Where are SDA and SCL on the PLC?

Each PLC has the SDA and SCL terminals on its communication connector. Check the user guide of your model. On the M-Duino 21+, SCL is Arduino pin 21 and SDA is Arduino pin 20. On the Ardbox HF+, SDA is pin 2 and SCL is pin 3.

Zone A pin table of the M-Duino 21+ with SCL on Arduino pin 21 and SDA on Arduino pin 20 marked
Zone A of the M-Duino 21+: SCL on Arduino pin 21 and SDA on pin 20.

The Wire library

The Arduino IDE includes the Wire library (Wire.h), which controls the I2C hardware of the microcontroller. With the Industrial Shields boards installed, you find its examples in File > Examples > Wire.

Arduino IDE menu File, Examples, Wire with the examples master_reader, master_writer, slave_receiver and slave_sender for the M-Duino family
The Wire examples in the Arduino IDE with the M-Duino family selected.
FunctionWhat it does
Wire.begin()Starts I2C as master. Wire.begin(address) starts it as a slave with that address
Wire.setClock(100000)Sets the clock: 100000 (standard) or 400000 (fast)
Wire.beginTransmission(address)Prepares a write to the slave with that 7-bit address
Wire.write(data)Queues one or more bytes. Returns the number of bytes queued
Wire.endTransmission(stop)Sends the queued bytes. With false it ends with a repeated start and keeps the bus. Returns 0 if it worked, 1 data too long, 2 NACK on the address, 3 NACK on the data, 4 other error, 5 timeout
Wire.requestFrom(address, quantity, stop)Reads a number of bytes from a slave. Returns the bytes received
Wire.available() and Wire.read()Number of bytes waiting, and the next byte
Wire.onReceive(handler) and Wire.onRequest(handler)In a slave: functions called when the master sends data or asks for data
Wire.setWireTimeout(us, reset)Time limit for a transfer, so that a blocked bus does not stop the program

Complete example: scan the bus and read a register

This sketch makes the PLC the I2C master. At start it scans the bus and prints the address of each device that answers. Then, every second, it reads two bytes from a register of one device: it writes the register number, keeps the bus with a repeated start and reads the answer. Change DEVICE_ADDRESS, FIRST_REGISTER and BYTES_TO_READ with the data of your device.

// I2C master on an Industrial Shields Arduino based PLC (M-Duino, Ardbox)
// 1. Scans the bus and prints the address of every device that answers.
// 2. Reads N bytes from a register of one device (write pointer + repeated start).
#include <Wire.h>

const uint8_t DEVICE_ADDRESS = 0x48;  // 7-bit address of your device (see its datasheet)
const uint8_t FIRST_REGISTER = 0x00;  // register to read
const uint8_t BYTES_TO_READ  = 2;

void scanBus() {
  uint8_t found = 0;
  for (uint8_t address = 0x08; address <= 0x77; address++) {  // 0x00-0x07 and 0x78-0x7F are reserved
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {  // 0 = the device answered with ACK
      Serial.print("Device found at 0x");
      Serial.println(address, HEX);
      found++;
    }
  }
  if (found == 0) Serial.println("No I2C device found: check SDA, SCL, GND and pull-ups");
}

bool readRegisters(uint8_t address, uint8_t reg, uint8_t *buffer, uint8_t length) {
  Wire.beginTransmission(address);
  Wire.write(reg);                           // register pointer
  if (Wire.endTransmission(false) != 0) {    // false = repeated start, keep the bus
    return false;                            // NACK, bus error or timeout
  }
  if (Wire.requestFrom(address, length) != length) {
    return false;
  }
  for (uint8_t i = 0; i < length; i++) {
    buffer[i] = Wire.read();
  }
  return true;
}

void setup() {
  Serial.begin(115200);
  Wire.begin();                    // this PLC is the master
  Wire.setClock(100000);           // standard mode, 100 kHz
  Wire.setWireTimeout(25000, true); // 25 ms: do not hang if a device blocks the bus
  scanBus();
}

void loop() {
  uint8_t data[BYTES_TO_READ];
  if (readRegisters(DEVICE_ADDRESS, FIRST_REGISTER, data, BYTES_TO_READ)) {
    uint16_t value = ((uint16_t)data[0] << 8) | data[1];  // most devices send the high byte first
    Serial.print("Register value: ");
    Serial.println(value);
  } else {
    Serial.println("Read failed");
    if (Wire.getWireTimeoutFlag()) {
      Wire.clearWireTimeoutFlag();
      Serial.println("I2C timeout: the bus was reset");
    }
  }
  delay(1000);
}
  • The scan uses an empty write: endTransmission() returns 0 when a device acknowledges its address.
  • endTransmission(false) leaves the bus with a repeated start, as most register based devices expect between the write of the register and the read.
  • setWireTimeout(25000, true) stops a transfer after 25 ms and resets the I2C hardware, so a blocked device does not freeze the PLC program.
  • Most devices send the high byte first. Check the byte order and the meaning of the register in the datasheet of your device.

What we tested. The sketch compiles without errors for the M-Duino 21+ with the Industrial Shields AVR boards (9,478 bytes of program memory, 3.6 %). We did not run it with an I2C device for this update.

Common I2C problems

What you seeUsual causeWhat to check
The scan finds no deviceWiring, power or pull-upsSDA and SCL not swapped, common ground, device powered, external pull-ups, logic level
endTransmission() returns 2No device answers at that addressThe address in the datasheet is 7-bit. Some datasheets give it already shifted with the R/W bit (8-bit)
Readings fail from time to timeLong cable, weak pull-ups or electrical noiseShorter wiring, stronger pull-ups, 100 kHz instead of 400 kHz
The program stopsA device holds the busWire.setWireTimeout() and the power of the device
Two devices with the same addressSame model on the same busThe address pins of the device, or an I2C multiplexer

An I2C device in a PLC project. The PLC Solution Library has a complete example that reads an ADS1015 analog to digital converter over I2C, with differential inputs and automatic gain.

See the ADS1015 exampleChoose an industrial controller

FAQ about I2C on an Arduino based PLC

How many devices can I connect to the I2C bus?

Up to 112 addresses with 7-bit addressing (0x08 to 0x77). In practice the limit is the capacitance of the bus: many devices and long wires slow down the edges.

How long can the I2C cable be?

I2C is designed for connections on a board or inside a cabinet. The length depends on the speed, the pull-ups and the cable capacitance. For connections between cabinets, use a bus extender or a field bus such as RS-485.

Can the PLC work as an I2C slave?

Yes. Start it with Wire.begin(address) and register Wire.onReceive() and Wire.onRequest().

What is the difference between I2C and SPI?

I2C uses two shared lines and addresses. SPI uses four lines, one chip select per device, and is faster and full duplex. Both are for short distances.

Which pull-up value should I use?

4.7 kΩ is a common value at 100 kHz. Lower values, down to about 1 kΩ, give faster edges for longer wiring or 400 kHz, as long as every device can sink the current.

​Search in our Blog

I2C Bus on the Arduino-based PLC for industrial automation
Boot & Work Corp. S.L, Support Team February 11, 2020
Share this post

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.

PLC Comparison