What is SPI? Serial Peripheral Interface – Part 1

Introduction

The Serial Peripheral Interface is a synchronous serial communication interface for short-distance communication, it is typically used in embedded systems. The interface was developed by Motorola in the mid 1980’s and has become a very popular standard.

It is used with many kinds of sensors, LCD’s and also SD-Cards. SPI operates in a Master-Slave model, with a possibility of multiple slave devices, each selected in turn by a SS (slave select) or CS (chip select) pin that is usually pulled low by the master.

Typical connection between two SPI devices

Typical configuration

SPI is a four-wire interface, with the different lines being
– MOSI [Master Out Slave In]
-MISO [Master In Slave Out]
-SCLK [Serial Clock OUT – generated by the master]
-SS/CS [Slave Select or Chip Select, sometimes also labelled CE – Chip Enable]

SPI is a FULL DUPLEX interface, where the master initiates the communication frames between the various slave devices. This is usually done by pulling the particular device’s SS/CS pin low. Data is then shifted simultaneously into and out of the devices by means of the MOSI and MISO lines on the bus. The frequency of the serially shifted data is controlled by the SCLK line. This clock signal is generated by the master device.

It is important to note that MOST of the slave devices have a tri-state (HIGH IMPEDANCE) mode on their MISO pins. This electrically disconnects the MISO pin from the bus when the device is not selected via the SS/CS pin.

You should also note the SPI slave devices that do not have a tri-state mode on their MISO pins, should not be used on the same bus as devices that have without using an external tri-state buffer circuit between the non-tristate device and the rest of the devices on the MISO bus.

Typical connection between an SPI Master and three Slave devices


It is possible to connect multiple SPI slave devices to on Master device if you remember that each slave device will need its own dedicated SS/CS pin on the master. This can however quickly use a lot of IO pins on a microcontroller, thus being one of the disadvantages of SPI versus I2C. SPI is however quite a bit faster than I2C.

Data Transmission

To begin communication, the bus master configures the clock, using a frequency supported by the slave device, typically up to a few MHz. The master then selects the slave device with a logic level 0 on the select line. If a waiting period is required, such as for an analog-to-digital conversion, the master must wait for at least that period of time before issuing clock cycles.

During each SPI clock cycle, full-duplex data transmission occurs. The master sends a bit on the MOSI line and the slave reads it, while the slave sends a bit on the MISO line and the master reads it. This sequence is maintained even when only one-directional data transfer is intended.

A typical hardware setup using two shift registers to form an inter-chip circular buffer

Transmissions normally involve two shift registers of some given word-size, such as eight bits, one in the master and one in the slave; they are connected in a virtual ring topology. Data is usually shifted out with the most significant bit first. On the clock edge, both master and slave shift out a bit and output it on the transmission line to the counterpart. On the next clock edge, at each receiver the bit is sampled from the transmission line and set as a new least-significant bit of the shift register. After the register bits have been shifted out and in, the master and slave have exchanged register values. If more data needs to be exchanged, the shift registers are reloaded and the process repeats. Transmission may continue for any number of clock cycles. When complete, the master stops toggling the clock signal, and typically deselects the slave.

Transmissions often consist of eight-bit words. However, other word-sizes are also common, for example, sixteen-bit words for touch-screen controllers or audio codecs, such as the TSC2101 by Texas Instruments, or twelve-bit words for many digital-to-analogue or analogue-to-digital converters.

Every slave on the bus that has not been activated using its chip select line must disregard the input clock and MOSI signals and should not drive MISO (I.E. must have a tri-state output) although some devices need external tri-state buffers to implement this.

Clock polarity and phasing

In addition to setting the clock frequency, the master must also configure the clock polarity and phase with respect to the data. Motorola SPI Block Guide names these two options as CPOL and CPHA (for clock polarity and phase) respectively, a convention most vendors have also adopted.

The timing diagram is shown below. The timing is further described below and applies to both the master and the slave device.

  • CPOL determines the polarity of the clock. The polarities can be converted with a simple inverter.
  • CPOL=0 is a clock which idles at 0, and each cycle consists of a pulse of 1. That is, the leading edge is a rising edge, and the trailing edge is a falling edge.
  • CPOL=1 is a clock which idles at 1, and each cycle consists of a pulse of 0. That is, the leading edge is a falling edge, and the trailing edge is a rising edge.
  • CPHA determines the timing (i.e. phase) of the data bits relative to the clock pulses. Conversion between these two forms is non-trivial.
  • For CPHA=0, the “out” side changes the data on the trailing edge of the preceding clock cycle, while the “in” side captures the data on (or shortly after) the leading edge of the clock cycle. The out-side holds the data valid until the trailing edge of the current clock cycle. For the first cycle, the first bit must be on the MOSI line before the leading clock edge.
  • An alternative way of considering it is to say that a CPHA=0 cycle consists of a half cycle with the clock idle, followed by a half cycle with the clock asserted.
  • For CPHA=1, the “out” side changes the data on the leading edge of the current clock cycle, while the “in” side captures the data on (or shortly after) the trailing edge of the clock cycle. The out-side holds the data valid until the leading edge of the following clock cycle. For the last cycle, the slave holds the MISO line valid until slave select is de-selected.
  • An alternative way of considering it is to say that a CPHA=1 cycle consists of a half cycle with the clock asserted, followed by a half cycle with the clock idle.
A timing diagram showing clock polarity and phase. Red lines denote clock leading edges, and blue lines, trailing edges.

The MOSI and MISO signals are usually stable (at their reception points) for the half cycle until the next clock transition. SPI master and slave devices may well sample data at different points in that half cycle.

This adds more flexibility to the communication channel between the master and slave.

Mode numbers

The combinations of polarity and phases are often referred to as modes which are commonly numbered according to the following convention, with CPOL as the high order bit and CPHA as the low order bit:

For “Microchip PIC” / “ARM-based” microcontrollers (note that NCPHA is the inversion of CPHA):

SPI modeClock polarity
(CPOL/CKP)
Clock phase
(CPHA)
Clock edge
(CKE/NCPHA)
0001
1010
2101
3110
For PIC32MX: SPI mode configure CKP, CKE and SMP bits. Set SMP bit and CKP, CKE two bits configured as above table.
ModeCPOLCPHA
000
101
210
311
For other microcontrollers:

Another commonly used notation represents the mode as a (CPOL, CPHA) tuple; e.g., the value ‘(0, 1)’ would indicate CPOL=0 and CPHA=1.

Note that in Full Duplex operation, the Master device could transmit and receive with different modes. For instance, it could transmit in Mode 0 and be receiving in Mode 1 at the same time.

Independent Slave Configuration

In the independent slave configuration, there is an independent chip select line for each slave. This is the way SPI is normally used. The master asserts only one chip select at a time.

Pull-up resistors between the power source and chip select lines are recommended for systems where the master’s chip select pins may default to an undefined state. When separate software routines initialize each chip select and communicate with its slave, pull-up resistors prevent other uninitialized slaves from responding.

Since the MISO pins of the slaves are connected together, they are required to be tri-state pins (high, low or high-impedance), where the high-impedance output must be applied when the slave is not selected. Slave devices not supporting tri-state may be used in independent slave configuration by adding a tri-state buffer chip controlled by the chip select signal. (Since only a single signal line needs to be tri-stated per slave, one typical standard logic chip that contains four tristate buffers with independent gate inputs can be used to interface up to four slave devices to an SPI bus.)

Typical SPI configuration

Daisy chain configuration

Some products that implement SPI may be connected in a daisy chain configuration, the first slave output being connected to the second slave input, etc. The SPI port of each slave is designed to send out during the second group of clock pulses an exact copy of the data it received during the first group of clock pulses. The whole chain acts as a communication shift register; daisy chaining is often done with shift registers to provide a bank of inputs or outputs through SPI. Each slave copies input to output in the next clock cycle until the active low SS line goes high. Such a feature only requires a single SS line from the master, rather than a separate SS line for each slave.

Note that not all SPI devices support this. You should thus check your datasheet before using this configuration!

SPI Daisy Chain configuration

Valid Communications

Some slave devices are designed to ignore any SPI communications in which the number of clock pulses is greater than specified. Others do not care, ignoring extra inputs and continuing to shift the same output bit. It is common for different devices to use SPI communications with different lengths, as, for example, when SPI is used to access the scan chain of a digital IC by issuing a command word of one size (perhaps 32 bits) and then getting a response of a different size (perhaps 153 bits, one for each pin in that scan chain).

Interrupts

SPI devices sometimes use another signal line to send an interrupt signal to a host CPU. Examples include pen-down interrupts from touchscreen sensors, thermal limit alerts from temperature sensors, alarms issued by real-time clock chips, SDIO, and headset jack insertions from the sound codec in a cell phone. Interrupts are not covered by the SPI standard; their usage is neither forbidden nor specified by the standard. In other words, interrupts are outside the scope of the SPI standard and are optionally implemented independently from it.

Bit Banging a SPI Master – Example code

Below is an example of bit-banging the SPI protocol as an SPI master with CPOL=0, CPHA=0, and eight bits per transfer. The example is written in the C programming language. Because this is CPOL=0 the clock must be pulled low before the chip select is activated. The chip select line must be activated, which normally means being toggled low, for the peripheral before the start of the transfer, and then deactivated afterwards. Most peripherals allow or require several transfers while the select line is low; this routine might be called several times before deselecting the chip.

/*
 * Simultaneously transmit and receive a byte on the SPI.
 *
 * Polarity and phase are assumed to be both 0, i.e.:
 *   - input data is captured on rising edge of SCLK.
 *   - output data is propagated on falling edge of SCLK.
 *
 * Returns the received byte.
 */
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
    uint8_t byte_in = 0;
    uint8_t bit;

    for (bit = 0x80; bit; bit >>= 1) {
        /* Shift-out a bit to the MOSI line */
        write_MOSI((byte_out & bit) ? HIGH : LOW);

        /* Delay for at least the peer's setup time */
        delay(SPI_SCLK_LOW_TIME);

        /* Pull the clock line high */
        write_SCLK(HIGH);

        /* Shift-in a bit from the MISO line */
        if (read_MISO() == HIGH)
            byte_in |= bit;

        /* Delay for at least the peer's hold time */
        delay(SPI_SCLK_HIGH_TIME);

        /* Pull the clock line low */
        write_SCLK(LOW);
    }

    return byte_in;
}

This concludes part 1 of my series on SPI. I hope you found it interesting and useful.

Multiple I2C Devices on the same Bus, I2C Part 3

Today I will continue my series on I2C by showing you how to use multiple devices on the I2C bus. This will be an extremely short post, as it builds on skills that we have already covered.

I will connect the following

1 x 16×2 I2C LCD Screen address 0x27
1x 128×32 I2C OLED Display address 0x3C
2x PCF8574 I2C Io Extenders address 0x20 and 0x21

All of these devices will be controlled from Arduino Uno, using the following libraries


LiquidCrystal_I2C.h to control the LCD screen,
Wire.h and PCF8574.h to control the I2C IO extenders and
Adafruit_GFX, Adafruit_SSD1306.h and SPI.h to control the SSD1306 128×32 OLED display.

With DuPont wires and breadboards being the reliable things they are, I decided that, after initial testing, I will not show you how to do button inputs on the PCF8574 at this stage. The amount of stray capacitance floating around on the breadboards, and small momentary push-button switches, made for a very impressive but unreliable mess of wires, with no real learning value to it 😉 Maybe some more on that later when I do a decent real-world example using these technologies 🙂

As the total distance between the devices is relatively short, it was not necessary to use pull-up resistors on the I2C bus in my setup. I suspect that that is due to the fact that they may already be included on some of my devices.

The circuit is quite straight forward.

  1. Connect all SDA pins on the I2C devices together serially, and connect that to the Arduino SDA pin ( That is usually A4)
  2. Connect all SCL pins on the I2C devices together serially, and connect that to the Arduino SCL pin ( That is usually A5)

    A note: On my Uno clone, there is an additional I2C breakout at the top of the device, near the USB adapter. I chose to use that as well as A4 and A5, as the bus hung itself up when connected to the breadboard. Your mileage may vary on this one 🙂
  3. Connect all 5v (Vcc) lines to 5v on the Arduino, and all Ground (GND) lines to GND on the Arduino.
  4. Now connect 4 LEDs, through a suitable resistor ( 640 ohms up to 1k ohm ) to pin P0 and P1 on both of the PCF8574 IO extenders. Also, connect the other leg of the LED to ground.
  5. I have powered my Uno from an external 5v power supply, as I did not want to pull too much current from the regulator on the actual Uno clone.

That should complete your hardware setup. Double check all your connections, and then load the i2c scanner sketch in the Arduino IDE, you may find it under the examples for the Wire.h library.

Power up the circuit, and upload the sketch to the Uno. Open the Serial Monitor.

You should see 4 I2C devices being detected. Note their addresses. If you dont see 4 devices, check your wiring and addresses. You may have a device with a conflicting address or a bad connection. If you used the breadboard to connect the bus, chances are very good that you will not see all the devices.

Good, if all of that is working, copy paste the following code into a new Arduino IDE window.
I will explain the code in the section below:

/*
  Multiple devices on the I2C bus
  Maker and Iot Ideas, MakerIoT2020
*/
// Include the libraries that we will need
#include <SPI.h> // needed for OLED display. 
#include <PCF8574.h> // PCF8574
#include <Wire.h> // Generic I2C library
#include <Adafruit_GFX.h> // for OLED display
#include <Adafruit_SSD1306.h> // for OLED display
#include <LiquidCrystal_I2C.h> // For I2C LCD display

// we need to define the size of the OLED screen

#define OLED_WIDTH 128
#define OLED_HEIGHT 32

// mine does not have an onboard reset pin. If yours do, specify the 
// pin that it is connected to on the Arduino here. To use the 
// Arduino reset pin, specify -1 as below

#define OLED_RESET -1

// Define the OLED display, width,hight protocol and reset pin
Adafruit_SSD1306 oled(OLED_WIDTH,OLED_HEIGHT, &Wire, OLED_RESET);

// Define the I2C LCD screen address and pin configuration
LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7,3,POSITIVE);

// Define the PCF8574 devices ( you can have up to 8 on a bus )
// but in this case, my LCD uses address 0x27, so I will have a 
// conflicting address if I were to use 8 of them together with the
// LCD

PCF8574 Remote_1(0x20); 
PCF8574 Remote_2(0x21);

// Note the I2C addresses. You can obtain them from the i2c_scanner

void setup() {
  // serial debugging if needed
  Serial.begin(115200);
  // Start OLED Display Init

  if (!oled.begin(SSD1306_SWITCHCAPVCC,0x3C)) { // Init the OLED 
    Serial.println(F("OLED INIT FAILED"));
    for(;;); // Dont proceed ... loop forever
  }
  oled.display();
  delay(2000); // This delay is required to give display time to 
  // initialise properly
  oled.clearDisplay();
  oled.setTextSize(0);
  oled.setTextColor(SSD1306_WHITE);
  oled.setCursor(0,0);
  oled.println("TEST SCREEN");
  oled.display();
  delay(2000);
  oled.clearDisplay();
  oled.setCursor(1,0);
  oled.println("OLED SCREEN ON");
  oled.display();

  // Start the LCD

  lcd.begin(16,2);
  
  // Set the initial state of the pins on the PCF8574 devices
  // I found that the PCF8574 library sometimes does funny things
  // This is also an example of how to use native i2c to set the 
  // status of the pins
  
  Wire.begin();
  Wire.beginTransmission(0x20); // device 1
  Wire.write(0x00); // all ports off
  Wire.endTransmission();
  Wire.begin();
  Wire.beginTransmission(0x21); // device 2
  Wire.write(0x00); // all ports off
  Wire.endTransmission();
  // Set pinModes for PCF8574 devices
  // Note that there are two of them

  Remote_1.pinMode(P0,OUTPUT);
  Remote_1.pinMode(P1,OUTPUT);
  Remote_2.pinMode(P0,OUTPUT);
  Remote_2.pinMode(P1,OUTPUT);
  
  // Start both IO extenders

  Remote_1.begin();
  Remote_2.begin();

  // and set ports to low on both
  // you may find that if you ommit this step, they come up in an
  // unstable state.

  Remote_1.digitalWrite(P0,LOW);
  Remote_1.digitalWrite(P1,LOW);
  Remote_2.digitalWrite(P0,LOW);
  Remote_2.digitalWrite(P1,LOW);
  
}

void loop() {
  // Draw a character map on the OLED display.
  // This function is borrowed from the Adafruit library

  testdrawchar();

  // Write to the IO extenders

  Remote_1.digitalWrite(P0,HIGH);
  Remote_1.digitalWrite(P1,LOW);
  Remote_2.digitalWrite(P0,HIGH);
  Remote_2.digitalWrite(P1,LOW);
  
  // Display their status on the LCD
  lcd.setCursor(0,0);
  lcd.print(" R1 P0=1 P1=0");
  lcd.setCursor(0,1);
  lcd.print(" R2 P0=1 P1=0");
  delay(500);

  // Change status
  Remote_1.digitalWrite(P1,HIGH);
  Remote_1.digitalWrite(P0,LOW);
  Remote_2.digitalWrite(P1,HIGH);
  Remote_2.digitalWrite(P0,LOW);

  // Update LCD
  lcd.setCursor(0,0);
  lcd.print(" R1 P0=0 P1=1");
  lcd.setCursor(0,1);
  lcd.print(" R2 P0=0 P1=1");
  delay(500);
  // Do some graphics on the OLED display
  // Function borrowed from Adafruit
  testdrawrect();
  oled.clearDisplay();
  delay(500);
  // repeat indefinitely

}

void testdrawrect(void) {
  oled.clearDisplay();

  for(int16_t i=0; i<oled.height()/2; i+=2) {
    oled.drawRect(i, i, oled.width()-2*i, oled.height()-2*i, SSD1306_WHITE);
    oled.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(500);
}

void testdrawchar(void) {
  oled.clearDisplay();

  oled.setTextSize(1);      // Normal 1:1 pixel scale
  oled.setTextColor(SSD1306_WHITE); // Draw white text
  oled.setCursor(0, 0);     // Start at top-left corner
  oled.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') oled.write(' ');
    else          oled.write(i);
  }

  oled.display();
  delay(500);
}

This concludes a quick and dirty show and tell… I hope that it will stimulate questions and ideas for a lot of people.

Thank you

I2C between Maker Nano and Kid-Bright32 (Esp32)

In our last post, we started looking at the workings of the I2C protocol. In case you missed that, you can read about that here. Today, I will continue with I2C by showing you how to implement the protocol between an Maker Nano (a Arduino Nano Clone) and Kid-Bright32 (a ESP32 based development and Education device).

This project will eventually be turned into an IoT device, with Google Assistant voice control. The Kid-Bright have very limited IO pins, and the Maker Nano has no Network Connectivity unless we use an Ethernet Shield. Enough of that for now, let us start with today’s tutorial, we will get back to this project later…

The Code for the Master

/* I2C Master Code - Kid Bright 32 v 1.3
   Can be adapted to Arduino or NodeMCU or STM32
   As it uses no special libraries, only the standard Wire.h
   that is already included with the Arduino IDE
*/

#include <Wire.h>

#define button1 16 // Button 1
#define button2 14 // Button 2
#define led_blue 17 // Led 1
#define led_red 2 // Led 2
#define led_yellow 15 // Led 3


void setup() {
  Wire.begin(); // Start I2C, join bus as a Master
  pinMode(button1,INPUT_PULLUP); // Set as input
  pinMode(button2,INPUT_PULLUP); 
  pinMode(led_blue,OUTPUT); // Set as output
  pinMode(led_red,OUTPUT);
  pinMode(led_yellow,OUTPUT);
  digitalWrite(led_blue,HIGH); // LED is Active Low
  digitalWrite(led_red,HIGH); // LED is Active Low
  digitalWrite(led_yellow,HIGH); // LED is Active Low
  Serial.begin(115200); // Start Serial for debugging
}

byte Data = 0; // Variable for sending data to the slave
int SlaveData = 0; // Variable for receiving data from the slave

void loop() {
  Wire.beginTransmission(4); // Send data to Slave at address #4
  Wire.write(Data); // Transmit one byte ( 8 bits)
  Wire.endTransmission(); // End Transmission
  Wire.requestFrom(4,1); // Request one (1) byte of data from the      //slave at address$
  while (Wire.available()) { // If data received
    SlaveData = Wire.read(); // Save it into a variable
  }

// We will implement a simple latch in software, where a single 
// button latches or releases a bit with every press and release.  
// This code should ideally include debouncing as well. It was left
// out for clarity.

  if (digitalRead(button1) == LOW) { // If button 1 pressed
    if (bitRead(Data,0) == HIGH) { // test if bit 0 in variable is set
      bitClear(Data,0); // clear it if it is set
    } else {
      bitSet(Data,0) == HIGH; // set bit to high
    }
  } 
// Do the same for the second button

  if (digitalRead(button2) == LOW) {
    if (bitRead(Data,1) == HIGH) {
      bitClear(Data,1);
    } else {
      bitSet(Data,1) == HIGH;
    }
  } 
 
// We will test for a set bit in the transmitted byte, and invert it, 
// as the LED's are active LOW

  digitalWrite(led_blue,!bitRead(Data,0)); // Toggle Led 1
  digitalWrite(led_red,!bitRead(Data,1)); // Toggle Led 2

// Same with the data received from the slave

  digitalWrite(led_yellow,!bitRead(SlaveData,0)); // Toggle Led 3
  
// Print Debug info on serial port
  
  Serial.print("Send to Slave 0xb");
  Serial.println(Data,BIN);
  Serial.print("Received from Slave 0xb");
  Serial.println(SlaveData,BIN);
  
 // Small delay, should change to millis in production code

  delay(200);
       
}
The Master Device. Connections are +5v (red) SCL (brown) SDA (orange) Ground (blue)

Code for the Slave

/*
     I2C Slave
     Arduino Nano or Compatible, can be used with ESP32 or STM32
     as well as no special libraries used, only standard Wire.h
*/

#include <Wire.h>

#define button 2 // A user button
#define led1 3 // Led 1
#define led2 4 // Led 2

byte Data = 0; // Variable for sending data to the Master

void setup() 
{
  pinMode(button,INPUT_PULLUP); // Set as Input
  pinMode(led1,OUTPUT);
  pinMode(led2,OUTPUT);
  digitalWrite(led1,LOW); // Led is Active High, so switch it off
  digitalWrite(led2,LOW);
  Wire.begin(4); // Join I2C Bus as device #4
  Wire.onReceive(receiveEvent); // Register receive Event
  Wire.onRequest(requestEvent); // Register request event
  Serial.begin(115200); // start serial debugging
  
}

void loop() {
// implement a software bit latch, on bit 0 of the Data variable
// the latch is toggled by pressing and releasing the button
// should ideally be debounced as well

 if (digitalRead(button) == LOW) {
  if (bitRead(Data,0) == HIGH) {
    bitClear(Data,0);
  } else {
    bitSet(Data,0) == HIGH;
  }
 }
 delay(200); // small delay
}

// This event will be triggered when the master requests data

void requestEvent() 
{
  Wire.write(Data); // Send data to the master
  Serial.print("Sending to Master 0xb");
  Serial.println(Data,BIN);
}
// This event gets triggered when the master sends data

void receiveEvent(int Quantity)
{
  int x = Wire.read(); // read the data ( one byte in this case)
  digitalWrite(led1,bitRead(x,0)); // Toggle LED 1 on Bit 0 state
  digitalWrite(led2,bitRead(x,1)); // Toggle LED 2 on Bit 1 state
  Serial.print("Received from Master 0xb"); // Debugginh
  Serial.println(x,BIN);

}
Maker Nano on an IO Shield, Connections are +5v (red) SCL (brown) SDA (orange) Ground (blue)

How does this work.

After uploading the code to the two boards, and connecting the boards to the I2C bus, we may power everything up. Please note that the boards MUST have a common ground. I have also powered both from the same supply. also make sure the SDA goes to SDA, and SCL to SCL… On a short distance like this, pull-up resistors are not required ( your milage may vary )

When we first power it up, is will seem as if nothing happened, but if you press and release one of the switches, the LED’s will light up, and stay lit until you press the switch again.

What next ?

In further parts of this, we will expand on this device, turning it into an IoT device, by combining many different skills that I have presented in previous tutorials.

What exactly is I2C?

In this post, I will tell you all the basics of the I2C protocol. What it is, where it comes from and also how it is configured and setup. We will also look at how data is transferred and received

Table of contents
1. Introduction
2. The Features of I2C
3. The Hardware
3.1 The physical I2C Bus
3.2 The Master and Slave devices on the bus
4. The data transfer protocol
4.1 The Start Condition
4.2 The Address Block
4.3 The Read/Write Bit
4.4 The ACK/NACK Bit
4.5 The Data Block
4.6 The Stop Condition
5. How does I2C work in practice
5.1 Sending data to a Slave Device
5.2 Reading data from a Slave Device
5.3 The Clock stretching concept

Introduction

I2C communication is the short form name for inter-integrated circuit protocol. It is a communication protocol developed by Philips Semiconductors for the transfer of data between a central processor and multiple integrated circuits on the same circuit board by using just two common wires.

Due to its simplicity, it is widely adopted for communication between microcontrollers and sensor arrays, displays, IoT devices, EEPROMs etc.

This is a synchronous serial communication protocol. It means that data bits are transferred one by one at regular intervals of time set by a reference clock line.

The Features of I2C

The I2C protocol has the following important features

  • Only two common bus lines (wires) are required to control any device/IC on the I2C network.
  • There is no need for a prior agreement on data transfer rate like in UART communications. The data transfer speed can thus be adjusted whenever it is required.
  • It has a simple mechanism for validating the transferred data.
  • It uses a 7-bit addressing system to target a specific device/IC on the I2C bus.
  • I2C networks are extremely easy to scale. New devices can simply be connected to the two common I2C bus lines.

The Hardware

The physical I2C Bus

The I2C Bus (Interface wires) consists of just two wires and are named Serial Clock Line (SCL) and Serial Data Line (SDA). The data to be transferred is sent through the SDA wire and is synchronized with the clock signal from SCL. All the devices/ICs on the I2C network are connected to the same SCL and SDA lines as shown in the image below:

The physical I2C Bus. All devices are connected to the same 2 wired on the bus, namely SDA and SCL

Both the I2C bus lines (SDA, SCL) are operated as in open-drain driver mode. It means that any device/IC on the I2C network can drive(pull) SDA and SCL low, but they cannot drive them high. So, a pull-up resistor is used on each bus line, to keep them high (at positive voltage) by default.

This is to prevent the bus from shorting, which might happen when one device tries to pull the line high and some other device tries to pull the line low.

The Master and Slave Devices on the I2C Bus

The devices connected to the I2C bus are categorized as either masters or slaves. At any instant of time, only a single master stays active on the I2C bus. It controls the SCL clock line and decides what operation is to be done on the SDA data line.

All the devices that respond to instructions from this master device are slaves. For differentiating between multiple slave devices connected to the same I2C bus, each slave device is physically assigned a permanent 7-bit address.

When a master device wants to transfer data to or from a slave device, it specifies this particular slave device address on the SDA line and then proceeds with the transfer. So effectively communication takes place between the master device and a particular slave device.

All the other slave devices don’t respond unless their address is specified by the master device on the SDA line.

The Master and Slave Devices on the I2C Bus. Note that each Slave device has it’s own address.

The Data Transfer Protocol

The protocol (set of rules) that is followed by the master device and slave devices for the transfer of data between them works as follows:

Data is transferred between the master device and slave devices through the SDA data line, via patterned sequences of 0’s and 1’s (bits). Each sequence of 0’s and 1’s is called a transaction and each data transaction is structured as in the image below:

The structure of an I2C Data transaction

The Start Condition

Whenever a master device/IC decides to start a transaction, it switches the SDA line from a high level to a low level before the SCL line switches from high to low.

Once a start condition is sent by the master device, all the slave devices get active even if they were in sleep mode, and wait for the address bits to see which device should respond.

The I2C Start Condition. Note that SDA Switches LOW before SCL. All slave devices on the bus will now listen for an address bit to decide which device should respond.

The Address Block

The Address block is comprised of 7 bits and are filled with the address of slave device (in binary) to/from which the master device needs to send/receive data. All the slave devices on the I2C bus will compare these address bits with their own address.

The Read/Write Bit

This bit specifies the direction that the data must be transferred in. If the master device/IC needs to send data to a slave device, this bit is set to ‘0’. If the master device/IC needs to receive data from the slave device, it is set to ‘1’.

The ACK/NACK Bit

This is the Acknowledged/Not-Acknowledged bit. If the physical address of any slave device is the same as the address that was broadcasted by the master device, that slave device will set the value of this bit to ‘0’ . If there are no slave device(s) with the broadcasted address, this bit will remain at logic ‘1’ (default). This will tell the master that the data/command has been received and/or acknowledged by a slave device.

The Data Block

The data block is comprised of 8 bits and they are set by the transmitter,wheather this be the master or the slave, depending on wheather a read or a write operation was requested, with the data bits that needs to transfered to the receiver. This block is followed by an ACK/NACK bit that is set to ‘0’ by the receiver if it successfully receives data. Otherwise it stays at logic ‘1’.

This combination of data blocks followed by an ACK/NACK bit is repeated until all the data is completely transferred.

The Stop Condition

After all the required data blocks are transferred through the SDA line, the master device switches the SDA line from low to high before the SCL line switches back from high to low.

The I2C Stop condition. This signals the end of a transaction. Note SDA returns to High BEFORE the SCL line is pulled High.

How does I2C work in practice

When an I2C transaction is initiated by a master device either to send or receive data to/from a slave device, all of the processes mentioned above will happen at least one.
Let us look at a typical scenario for each of the different type of scenarios.

Sending Data to a Slave Device

The following sequence of operations will take place when a master device tries to send data to a particular slave device through I2C bus:

  • The master device sends a start condition
  • The master device sends the 7 address bits which correspond to the slave device to be targeted
  • The master device sets the Read/Write bit to ‘0’, which signifies a write
  • Now two scenarios are possible:
    • If no slave device matches with the address sent by the master device, the next ACK/NACK bit stays at ‘1’ (default). This signals the master device that the slave device identification is unsuccessful. The master clock will end the current transaction by sending a Stop condition or a new Start condition
    • If a slave device exists with the same address as the one specified by the master device, the slave device sets the ACK/NACK bit to ‘0’, which signals the master device that a slave device is successfully targeted
  • If a slave device is successfully targeted, the master device now sends 8 bits of data which is only considered and received by the targeted slave device. This data means nothing to the remaining slave devices
  • If the data is successfully received by the slave device, it sets the ACK/NACK bit to ‘0’, which signals the master device to continue
  • The previous two steps are repeated until all the data is transferred
  • After all the data is sent to the slave device, the master device sends the Stop condition which signals all the slave devices that the current transaction has ended

The image below represents the transaction with the data bits sent on the SDA line and the device that controls each of them:

I2C Master sending data to a slave device

Reading Data from a Slave Device

The sequence of operations remain the same as in previous scenario except for the following:

  • The master device sets the Read/Write bit to ‘1’ instead of ‘0’ which signals the targeted slave device that the master device is expecting data from it
  • The 8 bits corresponding to the data block are sent by the slave device and the ACK/NACK bit is set by the master device
  • Once the required data is received by the master device, it sends a NACK bit. Then the slave device stops sending data and releases the SDA line

If the master device to read data from specific internal location of a slave device, it first sends the location data to the slave device using the steps in previous scenario. It then starts the process of reading data with a repeated start condition.

The below figure represents the overall data bits sent on the SDA line and the device that controls each of them:

Reading data from a Slave device on the I2C bus

The Clock Stretching concept

Let say the master device started a transaction and sent address bits of a particular slave device followed by a Read bit of ‘1’. The specific slave device needs to send an ACK bit, immediately followed by data.

But if the slave device needs some time to fetch and send data to master device, during this gap, the master device will think that the slave device is sending some data.

To prevent this, the slave device holds the SCL clock line low until it is ready to transfer data bits. By doing this, the slave device signals the master device to wait for data bits until the clock line is released

Conclusion

This concludes this tutorial. In a future post, I will show you how to use I2C to transfer data between two micro-controllers.



A glance back in History – Manufacturing your own transistors – At home!

We are living in an industrial age, electronics components are easily available. They are dirt cheap. I would like to share a few pages from an old book that I have recently found on a book-archive site on the internet. It is freely available for download, so I thought I would add a bit of history and share it with my fellow electronics enthusiasts…

The Book is called “Practical Transistors and Transistor Circuits” by JS Kendal.
It was first published in August 1954…

This is chapter 1, which I find the most interesting, I sincerely hope you will find it interesting too.

I hope that you have enjoyed the journey back in time… Thank you

Extending the Inputs and Outputs of your Arduino/ESP32/STM32

There will come a time that you will run out of available input or output pins on your Arduino/ESP32 or STM32 device. Today, I will show you one way to work around this problem and how to add additional input and/or outputs to your device.

There are many ways to do this, the easiest of them being adding some sort of i2c
chip (the Waveshare PCF8574 IO Expansion Board is a good example).

This particular device can be cascaded to provide up to 64 IO ports on the i2c bus.

At Maker and IoT Ideas, we would however like to show you electronics that you can build yourself, or stuff that have not already been made up into some kind of commercial project.

So, keeping this DIY attitude going, I will teach you how to use a shift register today.

Before we do that, we have to look at what a shift register is, and how they work…

A shift register, at its basic level, is actually just a series of D Type Flip-Flops.

D Type Flip Flop

These flip flops are connected together to give this

Parallel in, Serial out Shift Register

There are three basic types of Shift Register
– Serial In, Serial Out (SISO)
– Serial In, Parallel Out (SIPO) and
– Parallel In, Serial Out (PISO)

We can see that they these type of shift registers are mainly used to convert between an serial and parallel data interface.

Serial In, Serial Out ( SISO)

This is the easiest configuration for a shift register. It is basically just a row of flip flops, with the output of the first, connected to the input of the next… and so on. This type of shift register is mainly used to introduce a delay into the data stream. This means that for a 8 bit SISO Shift register, the first data will only appear at the output after 8 clock cycles !

Serial In, Parallel Out (SIPO)

This kind of Shift Register has the same configuration as the SISO type, but it differs in that there is output after every flip-flop. That makes this type of shift register a good choice to use to extend the outputs on a microcontroller like Arduino or ESP32. The downside of using a shift register as a parallel output device is that the outputs will be slightly slower than if you used the native outputs on the microcontroller.

Parallel In, Serial Out (PISO)

A Parallel In, Serial out shift register can be used to read inputs from buttons or other digital devices. These inputs are then serially shifted into a serial pin on the microcontroller to be processed. It is also slightly slower than if you used the native inputs on the microcontroller.

Universal Shift register

You are also able to use a universal shift register. These chips can be used as both inputs and outputs. They are however only available in a 4 bit configuration.

The Chips (IC’s)

TYPELOGICCHIP NUMBERBITS
SISO / SIPOTTL74HC5958 bit
SIPOTTL74LS1648 bit
PISOTTL74HC1668 bit
UNIVERSALTTL74LS1944 bit
UNIVERSALTTL74LS1954 bit
UNIVERSALCMOSCD40354 bit
Some common Shift Register Chips

Example using Arduino with 74HC595n SIPO Shift Register

We will look at an example to connect the 74HC5959N to Arduino to drive eight (8) LEDs.
This example can be adapted to drive other loads as well by using a small BJT transistor and current limiting resistor instead of the LED

74hc595n Pin NumberDescription and Connection
Q0…Q7 (15,1,2,3,4,5,6,7)Parallel outputs of the shift register to write up to 8 signals with only 3 pins on your Arduino or ESP32 microcontroller
GND (8)Connect to the ground on the microcontroller
VCC (16)Connect to 3.3V or 5V on the microcontroller
DS (14)Serial data input has to be connected to the microcontroller (in this example D4)
OE (13)Output enable input do we not need and connected to ground
STCP (Latch) (12)Storage register clock input has to be connected to the microcontroller (in this example D5)
SHCP (11)Shift register clock input has to be connected to the microcontroller (in this example D6)
MR (10)Master reset connected to VCC because is active with LOW and we do not want to reset the register
Q7S (9)Serial data output not needed and therefore not connected
Connections for 74HC595n to Arduino

The operation of the 74hc595n SIPO Shift Register can easily be explained in three steps:

  1. The latch (STCP pin) is pulled LOW because the data is only stored in the register on a LOW to HIGH transition of this pin.
  2. Data is shifted out to the register with the data pin (DS) and with a LOW to HIGH transition of the clock signal (SHCP).
  3. The latch (STCP pin) is pulled HIGH to save the data in the register.

Make the following connections to your Arduino and Breadboard ( we will use Arduino Nano today, but you can use the same pins and code for Arduino Uno )

Breadboard Layout for 74HC595 Shift Register as Output Extender with Arduino Nano
Schematic Diagram for using 74HC595 Shift Register as Output Extender with Arduino Nano

Double-check all your connections, and then you can start the coding. The code will be much easier than you may think, as there is already a builtin function in the Arduino C language that we will use to shift the data out to the register.

// Define the Control Pins for the register
int latch-Pin = 5;
int clock-Pin = 7;
int data-Pin = 6;

void setup()
{
pinMode(latch-Pin, OUTPUT);
pinMode(data-Pin, OUTPUT);
pinMode(clock-Pin, OUTPUT);

Serial.begin(9600);
}

void loop()
{
byte leds = 0;
updateShiftRegister();
delay(1000);
for (int i = 0; i < 8; i++)
{
bitSet(leds, i);
updateShiftRegister();
for (int i = 7; i >= 0; i–)
{
bool b = bitRead(leds, i);
Serial.print(b);
}
delay(1000);
Serial.println(” “);
}
}

void updateShiftRegister()
{
digitalWrite(latch-Pin, LOW);
shiftOut(data-Pin, clock-Pin, LSBFIRST, less);
digitalWrite(latch-Pin, HIGH);
}

In this example, we output a HIGH to each led in turn, shifting from 0 to 7.

I hope that this will make sense to everybody, and be useful.
Questions and comments are welcome. Next time, we will look as using a PISO Shift register to extend the inputs on you Arduino/ESP32/STM32

Thank you.

Useful Circuits using NAND Gates – Logic Gates Part 2

In part one of this series, I showed you how to construct the basic logic gates using transistors and a few resistors. Today I will expand on that by showing you two very simple, but useful circuits, constructed with NAND gates, as well as a handful of other components.

The first circuit will be a Capacitive On/OFF Switch, Based on the CD4011 CMOS Quad NAND Gate. I draw the circuit to function on 5v, but you can also change the relay and use it with a supply voltage of up to 12v DC

Let use look at the circuit

Capacitive Switch using CD4011 CMOS Quad Nand IC

As we can see here, the two NAND gates are configured as a LATCH or R/S Flip Flop. Touching the “ON” touch plate causes a change in the input logic, making the latch change state and switching ON the output. Touching the “OFF” touch plate, resets the latch, switching the output OFF.

You can also send the input directly to a microcontroller like Arduino. In that case you would take the output at pin 1 through a resistor to the input of the microcontroller.

The circuit can also work in reverse logic, as pin 4 will be the complementary state of pin 1, thus off becomes 1 and on becomes 0

Our second circuit for today is a PWM motor controller, made using 4 NAND gates, a few resistors, capacitors, diodes, a mosfet and a variable resistor.

PWM Motor Controller using NAND Gates

In this circuit, Nand Gate U7.1 generates the PWM frequency, as well as changes the on-off period of the PWM signal via R37, as the user turns the pot, the charge-discharge time of C1 is changed, thus altering the duty cycle of the signal.

This is thus an effective, low component way to do PWM motor control without a microcontroller.

Next week, I will introduce another two usefull NAND Gate based circuits for you to try out.

Previous Posts

A short list of previous posts available on the old website


Arduino and STM32 “Blue Pill”

Arduino with LCD

Arduino with HC-SR04 Ultrasonic Sensor

Arduino nRFL01+ Rf Module Remote IO Extender

Arduino SPI data to STM32

How to program the STM32 “Blue Pill” with Arduino IDE

How to use the native USB port to upload code to the STM32 “Blue Pill”

Electronics tutorials and circuit diagrams

What is an H-Bridge Motor Controller

An H-Bridge Motor Controller Code Example for Arduino

PCB Files for building your own H-Bridge controller – FREE DOWNLOAD

Logic Building Blocks – The Logic Gates