The OLED Display

Introduction

Adding a display to any project can instantly increase its visual appeal, as well as make the project easier to control. Displays available to Electronic enthusiasts mostly include some sort of LCD or even TFT display. LCD displays are usually bulky and very limited in their ability to display a lot of information, whereas TFT type displays are still a bit on the expensive side, and not very easy to interface with for the beginner.

Today, I would like to introduce a different type of display, which is available in an I2C as well as SPI version. These displays are very easily readable in almost any light conditions, lightweight, and most importantly, they are extremely cheap. I am talking about the OLED display of course… Many of us may already have one of them in our mobile phones, or even TV screen…

128×32 I2C OLED Display (40mmx10mm) [0.91″] Front view

Some Technical Data

An organic light-emitting diode (OLED or Organic LED), also known as an organic EL (organic electroluminescent) diode,[1][2] is a light-emitting diode (LED) in which the emissive electroluminescent layer is a film of organic compound that emits light in response to an electric current. This organic layer is situated between two electrodes; typically, at least one of these electrodes is transparent. OLEDs are used to create digital displays in devices such as television screens, computer monitors, portable systems such as smartphoneshandheld game consoles and PDAs. A major area of research is the development of white OLED devices for use in solid-state lighting applications.[3][4][5]

There are two main families of OLED: those based on small molecules and those employing polymers. Adding mobile ions to an OLED creates a light-emitting electrochemical cell (LEC) which has a slightly different mode of operation. An OLED display can be driven with a passive-matrix (PMOLED) or active-matrix (AMOLED) control scheme. In the PMOLED scheme, each row (and line) in the display is controlled sequentially, one by one,[6] whereas AMOLED control uses a thin-film transistor backplane to directly access and switch each individual pixel on or off, allowing for higher resolution and larger display sizes.

An OLED display works without a backlight because it emits visible light. Thus, it can display deep black levels and can be thinner and lighter than a liquid crystal display (LCD). In low ambient light conditions (such as a dark room), an OLED screen can achieve a higher contrast ratio than an LCD, regardless of whether the LCD uses cold cathode fluorescent lamps or an LED backlight. OLED displays are made in the same way as LCDs, but after TFT (for active matrix displays), addressable grid (for passive matrix displays) or ITO segment (for segment displays) formation, the display is coated with hole injection, transport and blocking layers, as well with electroluminescent material after the 2 first layers, after which ITO or metal may be applied again as a cathode and later the entire stack of materials is encapsulated. The TFT layer, addressable grid or ITO segments serve as or are connected to the anode, which may be made of ITO or metal.[7][8] OLEDs can be made flexible and transparent, with transparent displays being used in smartphones with optical fingerprint scanners and flexible displays being used in foldable smartphones.

The full article is available here if you are interested.

128×32 I2C OLED Display (40mmx10mm) [0.91″] Back view

Connecting the circuit

This display is once again extremely easy to connect, as it uses the very versatile I2C protocol. (An SPI version is also available).

Connecting 128×32 OLED display to an Arduino Uno Clone

Connect the following wires to the Arduino / ESP32
+5v (red) to the VCC pin on the display
Gnd to Gnd
SDA (A4 on Uno) to SDA, and SCL (A5 on Uno) to SCL

The Software Libraries

The 128×32 OLED display that we will be using today, is based on the SSD1306. We will thus be using a library suplied by Adafruit to interface with this chip. There are various other libraries available, but I have found the Adafruit library the most stable.

To load this, start by opening the Arduino IDE, and go to the Sketch->Include Library->Manage Libraries option on the menu

The Library Manager will now open

We need to install two (2) Libraries

– Adafruit GFX ( this is for graphics)
– Adafruit SSD1306 ( to control the actual display )

Click on “Close” after installation is completed.

Using the display

We will use one of the standard Adafruit examples to show you the capabilities of the tiny little screen. The example are so straight forward to use, that I find it unnecessary to say anything else about it 🙂

Open the ssd1306_128x32_ic2 Example from the Examples menu in the Arduino IDE and upload it to your Arduino, making sure that you set the dimensions of your screen first (in my case 128×32 )

/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x32 pixel display using I2C to communicate
 3 pins are required to interface (two I2C and one reset).

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source
 hardware by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

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

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

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

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

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.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') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}

I hope that you find this useful and inspiring.
Thank you

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.



Arduino Web Server – Part 2

Introduction


In my previous post, I showed you how to use AJAX and JavaScript to make a very responsive Web server on Arduino. In Part 2, we will look at making some important modifications.

Arduino has limited storage space – Use an SD Card

As all of us already know, the Arduino, especially the Uno and Nano, has very limited storage space. If we want to create a truly useful IoT Web server device, we need to do something to increase the available storage space on our Arduino Device.

We can however not increase the program memory. What we can do is store our static HTML page, as well as any images and icons, on a SD-CARD. These come in many sizes,
but for our example, I will use a 2gb card, not that we will use all of it anyway.

The standard Arduino Ethernet Shield also comes standard with a SD-CARD reader slot already built in.

Arduino Ethernet Shield for Arduino Uno or Mega. Note That SD Card Slot is already built in

This makes our lives a lot easier. You can also buy a stand-alone SPI SD Card module for a few bucks online. This will be needed if you try to make this project using an Arduino Nano.

Preparing the Card for use

You should format your card using your computer, and a suitable adapter. The card should be formatted to the FAT filesystem. NTFS or other filesystems does unfortunately not work as far as I know.

SD Card IO – How to use the SD Card in Arduino

The Arduino IDE already includes an SD Card library. You can also download additional libraries from the internet that allows more specialized control and functionality. The standard library will however be sufficient for our needs.

It is also easy to test if your card is working or not. The code below is from the “CardInfo” example that ships with the Arduino IDE

/*
  SD card test

  This example shows how use the utility libraries on which the'
  SD library is based in order to get info about your SD card.
  Very useful for testing a card when you're not sure whether its working or not.

  The circuit:
    SD card attached to SPI bus as follows:
 ** MOSI - pin 11 on Arduino Uno/Duemilanove/Diecimila
 ** MISO - pin 12 on Arduino Uno/Duemilanove/Diecimila
 ** CLK - pin 13 on Arduino Uno/Duemilanove/Diecimila
 ** CS - depends on your SD card shield or module.
 		Pin 4 used here for consistency with other Arduino examples


  created  28 Mar 2011
  by Limor Fried
  modified 9 Apr 2012
  by Tom Igoe
*/
// include the SD library:
#include <SPI.h>
#include <SD.h>

// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;

// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
// MKRZero SD: SDCARD_SS_PIN
const int chipSelect = 4;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.print("\nInitializing SD card...");

  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  if (!card.init(SPI_HALF_SPEED, chipSelect)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card inserted?");
    Serial.println("* is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    while (1);
  } else {
    Serial.println("Wiring is correct and a card is present.");
  }

  // print the type of card
  Serial.println();
  Serial.print("Card type:         ");
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:
      Serial.println("SD1");
      break;
    case SD_CARD_TYPE_SD2:
      Serial.println("SD2");
      break;
    case SD_CARD_TYPE_SDHC:
      Serial.println("SDHC");
      break;
    default:
      Serial.println("Unknown");
  }

  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  if (!volume.init(card)) {
    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
    while (1);
  }

  Serial.print("Clusters:          ");
  Serial.println(volume.clusterCount());
  Serial.print("Blocks x Cluster:  ");
  Serial.println(volume.blocksPerCluster());

  Serial.print("Total Blocks:      ");
  Serial.println(volume.blocksPerCluster() * volume.clusterCount());
  Serial.println();

  // print the type and size of the first FAT-type volume
  uint32_t volumesize;
  Serial.print("Volume type is:    FAT");
  Serial.println(volume.fatType(), DEC);

  volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
  volumesize *= volume.clusterCount();       // we'll have a lot of clusters
  volumesize /= 2;                           // SD card blocks are always 512 bytes (2 blocks are 1KB)
  Serial.print("Volume size (Kb):  ");
  Serial.println(volumesize);
  Serial.print("Volume size (Mb):  ");
  volumesize /= 1024;
  Serial.println(volumesize);
  Serial.print("Volume size (Gb):  ");
  Serial.println((float)volumesize / 1024.0);

  Serial.println("\nFiles found on the card (name, date and size in bytes): ");
  root.openRoot(volume);

  // list all files in the card with date and size
  root.ls(LS_R | LS_DATE | LS_SIZE);
}

void loop(void) {
}

Open this example in your Arduino IDE, and then make sure that the CS pin is set to the correct pin for your Ethernet Shield ( Pin 10 is for Ethernet, Pin 4 is usually for the SD Card).

Both of these devices will be connected to the SPI bus on your Arduino, and the CS pin will determine which device is active, by being pulled LOW.

Insert your formatted card into the slot, power on the Arduino, and upload the sketch to the Arduino. Open the Serial monitor. If all goes well, you should see information about your SD Card ( Size, sectors etc being displayed ). If your card was already formatted to the FAT file system and contained other files, the names of these files will also be displayed.

Create your Web Page

Power down the Arduino, and remove the SD Card. Put it into the relevant adapter and connect it to your computer.

Now, open a plain text editor, notepad on windows, or any other specialized html editor, as long as you feel comfortable with it, and create a simple html file. Feel free to use my example below, and modify it to your liking

<!DOCTYPE html>
<html>
    <head>
        <title>Arduino SD Card Web Page EXAMPLE - Maker and IOT Ideas</title>
    </head>
    <body>
        <h1>Welcome to your Arduino Based Web Server</h1>
        <p>This page is stored on the SD Card connected to your Arduino.</p>
        <p>Please do not remove the card while the Arduino is connected to a power source</p>

    </body>
</html>

Save this file as index.htm, and remove the card from your computer, making sure that you properly stop it as per the standard procedures for your operating system.

Put it back into the slot on the Arduino Ethernet Shield, open the serial monitor, and apply power to your Arduino. make sure that you see the file, index.htm listed in the output.

Coding your Webserver

Our next step will be to write the code to create our Arduino Web Server. This code will be similar to the code in part 1 of this series, but I recommend that you start fresh, open a new sketch, and copy-paste my code into the IDE. you can always modify it later to suit your needs…

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

// MAC address from Ethernet shield sticker under board
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 100, 32); // IP address, may need to change depending on network
EthernetServer server(80);  // create a server at port 80

File webFile;

void setup()
{
    
    
    // initialize SD card
    Serial.println("Initializing SD card...");
    if (!SD.begin(4)) {
        Serial.println("ERROR - SD card initialization failed!");
        return;    // init failed
    }
    Serial.println("SD card initialized. [OK]");
    // check for index.htm file
    if (!SD.exists("index.htm")) {
        Serial.println("ERROR - Can't find index.htm file!");
        return;  // can't find index file
    }
    Serial.println("index.htm file found - Starting Webserver");

    Ethernet.begin(mac, ip);  // initialize Ethernet device
    server.begin();           // start to listen for clients
    Serial.begin(9600);       // for debugging
}

void loop()
{
    EthernetClient client = server.available();  // try to get client

    if (client) {  // got client?
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {   // client data available to read
                char c = client.read(); // read 1 byte (character) from client
                // last line of client request is blank and ends with \n
                // respond to client only after last line received
                if (c == '\n' && currentLineIsBlank) {
                    // send a standard http response header
                    client.println("HTTP/1.1 200 OK");
                    client.println("Content-Type: text/html");
                    client.println("Connection: close");
                    client.println();
                    // send web page
                    webFile = SD.open("index.htm");        // open web page file
                    if (webFile) {
                        while(webFile.available()) {
                            client.write(webFile.read()); // send web page to client
                        }
                        webFile.close();
                    }
                    break;
                }
                // every line of text received from the client ends with \r\n
                if (c == '\n') {
                    // last character on line of received text
                    // starting new line with next character read
                    currentLineIsBlank = true;
                } 
                else if (c != '\r') {
                    // a text character was received from client
                    currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);      // give the web browser time to receive the data
        client.stop(); // close the connection
    } // end if (client)
}

Upload the sketch to your Arduino and navigate to the IP Address of the server using your browser. You should see the page displayed as you coded it.

What to do from here

You can now modify your page to include links and even images and CSS styling. You should however remember that the Arduino also does not have a lot of RAM memory.
You should thus not add extremely large images or pages. Those will take a long time to display, or may even time-out and not display at all

In the next part of this series, I will show you how to add links, images and CSS to make your page look a bit more visually appealing. We will also integrate the AJAX and JavaScript
functionality from the previous part of the series, to allow our server to interact with the inputs and outputs on the Arduino.

Arduino Web Server using AJAX – Part 1

There are many ways to use Arduino to create your own IoT device. One of the easiest is to configure your Arduino as a Web Server. This way, you can connect to it from any browser capable device on your home network. It is also quite a bit safer to do it this way, as you don’t have to expose your device to the internet, reducing the security implications of many of the other methods available.

It does, however, have the disadvantage of not being able to connect to your device from outside your home network. ( In a later part of this series, I will show you how to do this relatively safely, but take note that you still won’t have SSH encryption to the device, that is a huge security risk in today’s online world.

What use will this kind of IoT device have?

You can use an Arduino based web server to monitor various devices in your home, as well as control them. Many of us have old Android tablets and other devices lying around, that may be to old to run the newest Android Operating System. Such a Tablet can however be mounted to a wall, to provide a permanent display and control device. You will only be limited by your imagination, as well as your skill with interfacing your devices with electronics and Arduino.

A few examples of this can be
– controlling lights
– controlling a fan
– measuring temperature and light levels using various sensors, and performing actions based on those values

But Arduino Web Servers are slow

The normal Arduino web servers that we have all seen in various projects on the internet are indeed slow and cumbersome. This is because they usually have to refresh and reload the entire page to display every single update of a switch or output. We can however take advantage of technology used on computer web servers, as well as the browser of the end user.

Ajax and Javascript

What is AJAX?

AJAX stands for Asynchronous JavaScript and XML.

AJAX is basically the use of JavaScript functions for getting information from the webserver (Your Arduino). This means that data on a web page can be updated without fetching the whole page each time.

This means that only the relevant part of the web page will be updated, either automatically, or when the end user performs an action, like click on a button, or when an input on the actual Arduino changes state.

What is JavaScript?

JavaScript is a client-side scripting language. This means that the code will run on the web browser. Meaning on the end-user computer or mobile device.

The JavaScript is included in the HTML page that will be served by the Arduino Webserver. When you load the web page hosted by the Arduino, the page and the JavaScript is loaded to your browser. Your browser then runs the JavaScript code (provided that you have not disabled JavaScript in your browser).

What will we need to do this?

You will need the following hardware and software to do this project

– Arduino UNO or Compatible
– Arduino Ethernet Shield with NO SD-Card Inserted
– Breadboard
– 1K resistor
– LED
– 10k OR 22k Resistor
– Hookup Wire (5 pieces)
– Push Button

The Circuit

Wire the following circuit on your breadboard

Circuit Diagram for the Arduino AJAX Web Server – Part 1

Connect your Ethernet Shield to the Arduino Uno.
Connect the Resistors, LED and push-button Switch as shown.
Connect +5v from Arduino to Red Line, Gnd From Arduino to Blue line

Connect Orange Wire from Arduino Pin 2 to a hole BETWEEN the 10k resistor and the switch ( See Diagram).
Connect Green Wire from Arduino Pin 3 to a hole above the 1k resistor ( See Diagram)

The Code

Copy the following code into your Arduino IDE, Or download the file below

#include <SPI.h>
#include <Ethernet.h>
// MAC address from Ethernet shield sticker under board
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 100, 32); // IP address, may need to change depending on network
EthernetServer server(80); // create a server at port 80
String HTTP_req; // stores the HTTP request

void setup()
{
Ethernet.begin(mac, ip); // initialize Ethernet device
server.begin(); // start to listen for clients
Serial.begin(9600); // for diagnostics
pinMode(2, INPUT_PULLUP); // switch is attached to Arduino pin 2
pinMode(3, OUTPUT);
digitalWrite(3,LOW);
}

void loop()
{
EthernetClient client = server.available(); // try to get client
if (client) { // got client? 
boolean currentLineIsBlank = true; 
while (client.connected()) 
{ if (client.available()) 
{ // client data available to read char c = client.read(); // read 1 byte (character) from client HTTP_req += c; // save the HTTP request 1 char at a time 
// last line of client request is blank and ends with \n 
// respond to client only after last line received 
if (c == '\n' && currentLineIsBlank) { // send a standard http response header client.println("HTTP/1.1 200 OK"); 
client.println("Content-Type: text/html"); 
client.println("Connection: keep-alive"); 
client.println(); // AJAX request for switch state 
if (HTTP_req.indexOf("ajax_switch") > -1) 
{ 
// read switch state and send appropriate paragraph text 
GetSwitchState(client); 
} else 
{ 
// HTTP request for web page // send web page - contains JavaScript with AJAX calls 
client.println("<!DOCTYPE html>"); 
client.println("<html>"); 
client.println("<head>"); 
client.println("<title>Arduino Web Page with AJAX - Maker and IoT Ideas</title>"); 
client.println("<script>"); 
client.println("function GetSwitchState() {"); 
client.println("nocache = \"&nocache=\"\ + Math.random() * 1000000;"); 
client.println("var request = new XMLHttpRequest();"); 
client.println("request.onreadystatechange = function() {"); 
client.println("if (this.readyState == 4) {"); 
client.println("if (this.status == 200) {"); 
client.println("if (this.responseText != null) {"); client.println("document.getElementById(\"switch_txt\")\.innerHTML =this.responseText;");

client.println("}}}}");
client.println("request.open(\"GET\", \"ajax_switch\" + nocache, true);");
client.println("request.send(null);");
client.println("setTimeout('GetSwitchState()', 1000);");
client.println("}");
client.println("");
client.println("");
client.println(""); 
client.println("Arduino Web Server with AJAX"); 
client.println("Switch Status on D2"); 
client.println( "Switch state: Not requested…"); 
client.println("");
client.println("");
}
// display received HTTP request on serial port
Serial.print(HTTP_req);
HTTP_req = ""; // finished with request, empty string
break;
}
// every line of text received from the client ends with \r\n
if (c == '\n') {
// last character on line of received text
// starting new line with next character read
currentLineIsBlank = true;
}
else if (c != '\r') {
// a text character was received from client
currentLineIsBlank = false;
}
} // end if (client.available())
} // end while (client.connected())
delay(1); // give the web browser time to receive the data
client.stop(); // close the connection
} // end if (client)
}

// send the state of the switch to the web browser
void GetSwitchState(EthernetClient cl)
{
if (digitalRead(2)) {
cl.println("Switch at D2 is: OFF, LED at D3 is OFF");
digitalWrite(3,LOW);
}
else {
cl.println("Switch at D2 is: ON, LED at D2 is ON");
digitalWrite(3,HIGH);
}
}

Upload the code to your Arduino

Testing the results

Open a web browser and go to the IP Address of the server ( the one you set in your code).
If you did everything correctly, you should see a screen similar to this.

The Ajax Web Server shows the button and LED is OFF

Now press the button

The web page should immediately update and tell you that the button is On, and the LED is ON


Ajax Web Server showing Status of Button and LED as ON

The Generated HTML will look like this

<!DOCTYPE html>

<html>

<head>

<title>Arduino Web Page with AJAX - Maker and IoT Ideas</title>

<script>

function GetSwitchState() {

nocache = "&nocache=" + Math.random() * 1000000;

var request = new XMLHttpRequest();

request.onreadystatechange = function() {

if (this.readyState == 4) {

if (this.status == 200) {

if (this.responseText != null) {

document.getElementById("switch_txt").innerHTML = this.responseText;

}}}}

request.open("GET", "ajax_switch" + nocache, true);

request.send(null);

setTimeout('GetSwitchState()', 1000);

}

</script>

</head>

<body onload="GetSwitchState()">

<h1>Arduino Web Server with AJAX</h1>

<h3>Switch Status on D2</h3>

<p id="switch_txt">Switch state: Not requested...</p>

</body>

</html>

Images of the Working Hardware

Please note that I did not use a push button switch in my example. I have just used a piece of hookup wire to connect the pulled-up pin to ground, as it is easier to photograph that way, without my finger being in the way on a button. 🙂

Conclusion

This concludes part 1 of this series. This example can very easily be extended to be more useful, as well as be modified to work on other platforms, like ESP32.
In further parts, I will show you how to extend this very simple server into becoming something much more useful. Please visit again to see the rest of this series.

Extending Arduino/Esp32/STM32 GPIO Pins – PART 2

In the first part of this series, I showed you how to extend the available output pins on your microprocessor by using a SIPO (Serial In, Parallel Out) Shift Register. These work great to extend your outputs, but they do tend to involve a bit of extra work and organisation in your code. They are also a bit slower than the normal GPIO pins, because data has to be serially shifted into them, and then latched out onto the parallel port.

I have also mentioned that there are I2C devices available that can make this much easier… In today’s article, I will show you how to use one of these I2C devices, the PCF8574.

These little modules have some quite impressive features, for one, allowing you to cascade up to 8 of them together, giving you a quite impressive 64 GPIO ports ! I am also happy to tell you, that if you can find the PCF8574A variant, as well, you can increase the total amount of ports to 128! ( If you chain 8x PCF8574 as well as 8x PCF8574A together) This is possible because the I2C addresses of the two series of chips are different. Thus allowing us to add a total of 16 of them to the I2C bus.

It must however be said that you should calculate your bus resistance very carefully if you plan on doing that. For most of us, I do not believe we will need that much GPIO on a single microprocessor!

Enough introduction, let us start by looking closely at the chip, as well as the modules that you can purchase for around 1 USD each…

A word of caution, there is also another version of these available, which is specifically designed to be used with LCD screens. You should thus be careful when you buy a premade module, that you choose the io-extender version, and not the LCD controller version.

PCF8574 I2C IO Extender module – Front View
PCF8574 I2C IO Extender – Back View

As we can see, the GPIO ports are clearly labeled, from P0 to P7, with the INT (Interrupt Pin) on the very right.

As I have said before, you can cascade up to 8 of these onto the I2C bus. This is done by setting the I2C address of the module. This is done by setting the jumpers as seen in the picture below.

Address Jumpers on the PCF8574 I2C IO Extender Module

The Address can be set by using the following table to lookup the address and set the jumpers accordingly.

A2A1A0I2C Device Address
0000x20
0010x21
0100x22
0110x23
1000x24
1010x25
1100x26
1110x27
Available I2C Addresses for PCF8574 selected by setting the jumpers

Connecting the device is very easy. You only have to supply 5v and Ground, as well as connect it to the SCL and SDA Pins on your microprocessor. For Arduino Uno / Nano that is A4 (SDA) and A5 (SCL)

As far as the coding is concerned, you have two options. You can either use the built-in Wire library, or you can download a special library. Both works equally well, but I do believe that the built-in Wire library might be a little bit faster.

Another point to make is that there are a lot of “fake” modules on the market these days. These modules work, but some of them have extremely weak current sourcing abilities. ( I recently bought a pair online, and they are unable to properly light an LED even without a current limiting resistor. I fixed that issue by driving the LED through a small BJT transistor, like the 2n2222a.

You should also take note that the ports will start up in a weak HIGH state when the module is powered up. This should be taken into consideration when designing your circuit to drive external devices through the outputs. In other words, you should take precautions to prevent the devices from switching on before the microprocessor takes control of the module.

Let us start to look at the coding that you will need to do to use this device.
I will start with the built-in wire Library that is included with the Arduino IDE.

#include <Wire.h> // Wire.h provide access to I2C functions

void setup()
{
Wire.begin(); // Start I2C
}

void loop()
{

Wire.beginTransmission(0x20); // Our device is on Address 0x20
Wire.write(0x0F); // This is equal to 0b00001111, meaning it will switch ports P0 to P3 High
Wire.endTransmission();
delay(1000);
Wire.beginTransmission(0x20);
Wire.write(0xF0); // This is equal to 0b11110000, meaning it will switch ports P4 to P7 High
Wire.endTransmission();
delay(1000);
}

The code above will alternate between switching 4 ports high and low every one second.
You can observe this by connecting 8 LEDs through 330ohm resistors to ports P0 through P7



Reading the status of a port (meaning that you configured it as an input) can be done using the following code

#include<Wire.h>

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  Wire.beginTransmission(0x20);
  Wire.write(0x00);  //LED1 is OFF
  Wire.endTransmission();
}

void loop()
{
  Wire.requestFrom(0x20, 4); // Read the state of P4
  byte x = Wire.read();
  if (bitRead(x, 4) == LOW)
  {
    Wire.beginTransmission(0x20);
    Wire.write(0x01);  //LED1 is ON
    Wire.endTransmission();
  }
  else
  {
    Wire.beginTransmission(0x20);
    Wire.write(0x00);  //LED1 is OFF
    Wire.endTransmission();
  }
  delay(1000);
}

This code assumes that you have connected a LED throught a resistor to P0, and that you have connected a pullup resistor of 10k to P4, with a pushbutton to GROUND.

The LED should switch on when you press the switch, and go off again once you release it.

If you want to use the special library, you can download it below:

Install this into your Arduino Libraries, and use the following code:

include “Arduino.h”

include “PCF8574.h

// Set i2c address
PCF8574 pcf8574(0x20);

void setup()
{
Serial.begin(115200);

// Set pinMode to OUTPUT

pcf8574.pinMode(P0, OUTPUT);

pcf8574.begin();

}

void loop()
{
pcf8574.digitalWrite(P0, HIGH);
delay(1000);
pcf8574.digitalWrite(P0, LOW);
delay(1000);
}

Reading the status of an Input can be done like this:

include “Arduino.h”

include “PCF8574.h”

// Set i2c address
PCF8574 pcf8574(0x20);

void setup()
{
Serial.begin(115200);

pcf8574.pinMode(P0, OUTPUT);
pcf8574.pinMode(P1, INPUT);
pcf8574.begin();

}

void loop()
{
uint8_t val = pcf8574.digitalRead(P1);
if (val==HIGH) Serial.println(“KEY PRESSED”);
delay(50);

}

There are also excellent examples included with the library. These include using the interrupt pin.

I hope that this will be useful to somebody.

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.

Using MQTT with IoT Devices – A Short Summary

You are excited, you have just finished your new IoT device, and have many excellent ideas on how you will use it remotely, on your smartphone, or even from your computer at the office. You now start thinking about how you will send and receive data to this new device of yours…

There are many ways that you can do this, but today, I would like to suggest a very easy and lightweight data transfer protocol. It is called MQTT. Some of you may already be using it, or you may at least have heard about it. Read on if you would like to know more about what it is, as well as how it works.

The History of MQTT

MQTT was designed by Andy Stanford-Clark (IBM) and Arlen Nipper (Cirrus Link, then Eurotech) in 1999. It was first used to monitor an oil pipeline running through the desert. The goal was to design and implement a protocol that is bandwidth-efficient, lightweight and uses very little battery power, because the devices were connected via satellite link which, at that time, was extremely expensive.

In 2013, IBM submitted the MQTT v3.1 protocol to the OASIS specification body with a charter that ensured that only minor changes to the specification could be accepted.MQTT-SN is a variation of the main protocol aimed at embedded devices on non-TCP/IP networks, such as Zigbee.

Historically, the “MQ” in “MQTT” came from the IBM MQ (then ‘MQSeries’) MQ product line. The protocol, however, provides publish-and-subscribe messaging (no queues, in spite of the name) and was specifically designed for resource-constrained devices and low bandwidth, high latency networks. This makes it an excellent candidate for data transmission on IoT and other low resource devices.

The Protocol Architecture (How does it work)

MQTT uses a client-server architecture, where the server is called the broker, and the client, called a client. The broker typically functions like a post office, in the sense that it doesn’t use the client’s address, but rather the subject of the topic that a client is subscribed to, to determine which client should receive a certain message.

This enables many clients to subscribe to the same subject, with each receiving the same message. Clients can also publish a topic, thus transmitting or sending a message to other clients. This concept make bidirectional communication possible between clients, and it also ensures that it is extremely easy to use.

What do theses topics look like?

An MQTT Topic is a text string, delimited by a /
for example, let us say you have a device in your kitchen, that monitors the temperature, and controls the lights and microwave oven.

This device may use the following topics to publish (send data) or subscribe to (receive data)

@msg/myhouse/kitchen/temp
@msg/myhouse/kitchen/microwave
@msg/myhouse/kitchen/lights
@msg/myhouse/livingroom/lights
@msg/myhouse/bedroom/lights

As you can see, we can easily group topics by their location. It would thus be very easy for a smart home controller, like OpenHab or similar to get status or set a particular state in a certain room or area.

This can be done by using wildcard operators, of which there are two, the + and #

If we want to subscribe to all the lights in the house, we can subscribe to the following topic:

@msg/myhouse/+/lights

This will give us the data from the following topics:
@msg/myhouse/kitchen/lights
@msg/myhouse/livingroom/lights
@msg/myhouse/bedroom/lights

You can also subscribe to a multilevel wildcard topic, for example:

@msg/myhouse/kitchen/#

This will subscribe you to all the topics related to the kitchen.

How do I get access to MQTT

There are quite a few online MQTT brokers available that allow you to apply for a free account. These services have many limits on the amounts of messages you may send or receive, but generally, they are quite useful.

Adafruit.io should be well known. It is stable, and easy to use. They do however have a few limitations on how many devices and messages you can add to the service.

NetPie.io is a fairly unknown provider outside of Thailand. They limit you to 3 Projects, with 10 devices per project, but their messaging limits are extremely liberal and permissive. This service will also always be free (or at least that is according to NetPie themselves). On the negative side, all the configuration is available in English, but all the documentation is in Thai. You can however use Google Translate to translate it into a language that you can understand.

If you are not comfortable with running your data on an online broker, you can also download and install one of the many MQTT brokers that are available for installation on Linux and MS-Windows. This will, however, mean that you should also have permanent Internet access, as well as a public IP address, to enable your devices to connect to your broker.

Microprocessor and other Device support

MQTT Libraries are available for use on Arduino, STM32, ESP32 as well as Raspberry PI and Python. You can also get clients and brokers for the Linux and MS-Windows platforms.

Smartphone Access

Various applications exist on the google playstore, as well as the Apple App Store, that allow you to connect to an MQTT broker, subscribe to topics, and also publish topics. Many of these are free to use, and also provide you with a nice user interface.

You can also write your own smartphone app, if you are skilled enough, to do exactly what you want it do do.

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