Interactive Window Display

by Faransky in Craft > Art

1233 Views, 10 Favorites, 0 Comments

Interactive Window Display

P6221482.JPG
P6221339.JPG
P6221349.JPG
P6221340.JPG
P6221369.JPG
P6221390.JPG
P6221391.JPG
P6221378.JPG
P6221400.JPG
P6221403.JPG
P6221419.JPG
P6221476.JPG
P6221435.JPG
P6221469.JPG
P6221497.JPG

For a long time, I tried to develop technical gadgets whose purpose can be concluded in a single sentence: "It checks that thing. Huh-huh." I've came up with thought - I did not focus on decorative design at all. Hence, we've decided to collaborate with a pretty talented graphic designer and created an interactive showcase for a travelling agency (COVID-19 is still at large, right?) with a sense that may become true in a relatively short time.

As it can be seen in the illustrations, there are laser-cut 3D objects with embedded electronics in it. To make it much easier to debug and accelerate the engineering process - Arduino Nano was chosen to be the brain of the project.

Preparartion - Materials and Tools

20210528_172838.jpg
20210528_172834.jpg

As I've mentioned in the previous state, the decorative showcase consists of two main designs: Mechanical and electrical.

Electrical design is much easier to understand, since it has a single purpose - attach to the decorative model a portion of an interactive circuits with ability to operate without user interference.

Overall design implies the use of more advanced tools than Arduino and Soldering Iron, but it can be handled easily with the use of a pair of hands.

So, we've covered the basics and the introduction to the project, let's proceed to the required materials and tools

Tools

  • Soldering Iron
  • Cutter
  • Plier
  • Tweezers
  • Laser-cut engine (Optional)
  • Small-size scissors
  • Sharpening file
  • Hands
  • Soldering tin
  • Hot Glue Gun
  • 26AWG 2A wire (any wire can be used, since there is no harsh energy transfer between the parts)

Software

  • Arduino IDE
  • Adobe Photoshop
  • Adobe Illustrator
  • Image2LCD (In order to create 1-bit binary images)
  • KiCAD/OrCAD/PADS Logic/Altium Designer (Electronic Schematics - Optional)

Materials - Mechanical

  • 1 x A0 foldable 0.3mm cardboard
  • 6 x A2 foldable 0.3mm/0.4mm cardboard
  • 2 x unfoldable 40x10x2mm cardboard rectangle shapes
  • 2 x unfoldable 20x10x2mm cardboard rectangle shapes

Material - Electronic

  • 52 x White LED (Plane Runway & House lighting)
  • 8 x RGB LED (House lighting)
  • 1 x 0.91" OLED LCD (Advertising)
  • 2 x HC-SR04 Ultrasonic sensors (For interactive system)
  • 2 x 54HC595 Shift register
  • 1 x Arduino Pro-Mini/Nano (They have similar properties)
  • 8 x 2N2222A NPN Transistor
  • 5 x 2N1132A PNP Transistor
  • 15 x 1K TH Resistor
  • 15 x 100R TH Resistor

It looks like we are about to begin. So let's build it! :)

Schematics Design

6.JPG
7.JPG
8.JPG
1.JPG
2.JPG
3.JPG
4.JPG
5.JPG

Electronic schematics design provides a complete step-by-step guide for controlling a decorative lighting model with Arduino Pro Mini/Micro/Nano.

Let's split our single-page schematics into separate groups and explain them step by step:

Plane Runway

The easiest part of the circuit so far. As it can be seen from the schematics, there are a total of 8 parallel lines (directed in X-plane), whose purpose is to supply current to the adjacent LEDs that are located on the same parallel. Let's dive into calculations:

A NPN transistor when is driven open (Via B-E junction) - NPN is saturated and acts as a switch for our application. There are 4 LEDs connected in parallel, thus calculated current for every runway parallel is:

I(total)[mA] = (5 - 0.25 - 2) / 330 = 8.48

I(Single Led)[mA] = 8.48 / 4 = 2.12

This is a satisfying value for the amount of intensity we interested in.

Transient Lighting

In order to create a smooth-transitioning lighting at the buildings (Houses and agency buildings) there are several RGB LEDs that are tied to the Arduino PWM terminals, in order to observe transitions in colors. If we are relying on the physics of the human eye, we cannot observe very fast transitions of an observable object. Thus if we apply continuous rapid changes on a LED - we may observe only the mean intensity value. So, this is the main reason to implement relatively high-frequency PWM in this case.

Advertising LCD

This part of the circuit is not mandatory, but it improves the overall project observance level. As it was mentioned in the 1-st step, the LCD is of OLED type, and is monochrome by definition. Communication protocol is 2-Wire interface (I2C in this case) and a library that was used - u8g2, the open-source monochrome LCD library.

Ultrasonic Sensors

In order to change showcase operation, ultrasonic sensors were implemented instead of push buttons. The main reason for this - if the person passes through, he is able to observe a transition in showcase lighting algorithm.

Shift Registers

There are a lot of outputs for the Arduino MCU, thus shift registers are the best solution for the issue. There are a total of two 8-bit shift registers, that are extremely easy to use, of type 54HC595.

Microcontroller Unit - Arduino

As it was mentioned previously, any Arduino-based MCU system can be used. I used an Arduino Pro-Micro for this project with a preprogrammed bootloader, but the use of other Arduino system can be done in the same way.

Software Design

Part One: Structure

The code is based on Arduino system and is arranged in a parallel-like style: All the system components are updated at each loop cycle, while the timings are controlled via count variables. In this type of the algorithm we have "a kind of" parallel operation for all of the parts and peripherals, that do not prevent the SW to delay or skip timings - All the parts are updated at the single loop cycle.

Part Two: Libraries and Resources

There are a total of three (3) main libraries that we are about to use. Since there is a variety of support for graphic LCD, ultrasonic sensors and shift registers, hence we don't need to build the system from scratch.

The list of libraries and their references that we're using:

  • u8g2 - Great Monochrome LCD library by olikraus.
  • HC-SR04 - Ultrasonic sensor.
  • 74HC595 - Shift register library by Simmso.

So let's proceed with the code.

Part Three: Understanding and flashing the code

The Arduino sketch begins with the library definitions: Everything we need to attach to the project.

#include <Arduino.h>
#include <U8g2lib.h>
#include <EEPROM.h>
#include <ShiftRegister74HC595.h>
#include <HCSR04.h><br>

In order to make the code more friendly to use, we will define a list of #defines, so we can call the constant values from inside of the functions.

#define SR_BLOCKS_DIN 2
#define SR_BLOCKS_LATCH 4
#define SR_BLOCKS_CLK A1

#define SR_STRIP_DIN A3
#define SR_STRIP_LATCH A2
#define SR_STRIP_CLK A1

#define TRG0 A0
#define TRG1 12
#define ECHO0 13
#define ECHO1 11

#define US_L false
#define US_R true

#define US_DISTANCE_THRESHOLD 5

#define SWIPE_LEFT 1
#define SWIPE_RIGHT 2
#define SWIPE_NONE 0

#define PWM_GROUP_A 0
#define PWM_GROUP_B 1

#define STREET0 false
#define STREET1 true

#define STREET_COUNTER_MAX 100
#define BLOCK_COUNTER_MAX 10
#define SWITCH_COLOR_MAX 100
#define LCD_COUNTER_MAX 20
#define STRIP_COUNTER_MAX 8

#define ORANGE 0xFFB739
#define YELLOW 0xFF8000
#define PURPLE 0xCC00CC
#define WHITE  0xFFFFFF
#define CYAN   0x00FFFF

#define NUMBER_OF_STATES 5
#define STRIP_ONLY 0
#define SWITCHING_COLORS 1
#define STRIP_AND_RUNNING_RGB 2
#define ALL_ON 3
#define ALL_OFF 4<br>

We can define the strings as well in #define fields. We have a plenty of memory left for our purposes (Even at the Arduino UNO), thus there is no need for stitching the texts (for LCD) inside the flash memory instead of RAM.

#define STRING_A "Quarantine is over! "
#define STRING_B "A lot of tickets    "
#define STRING_C "Greatest Offer Ever!"

In order to use the Arduino pins and texts according to the system state, we declare constant variables, whose purpose is to hold a constant values inside the RAM and provide relevant values when are referenced with a desired indices.

static const uint32_t COLORS_ARRAY[] = { ORANGE, YELLOW, PURPLE, WHITE, CYAN };

static const uint8_t PWM_ARRAY[] = {2, 5, 6, 9, 10};
static const uint8_t STREET_ARRAY[] = {7, 8};

static const char STRING_ARRAY[3][20] = { STRING_A, STRING_B, STRING_C};<br>

Now we declare variables that may change their value and/or properties. Since Arduino IDE based on a C++/C system, we are able to use all the properties of OOP (Object Oriented Programming) and declare our hardware components as complete objects inside the code.

ShiftRegister74HC595<1> blocksSR(SR_BLOCKS_DIN, SR_BLOCKS_CLK, SR_BLOCKS_LATCH);
ShiftRegister74HC595<1> stripSR(SR_STRIP_DIN, SR_STRIP_CLK, SR_STRIP_LATCH);

UltraSonicDistanceSensor distanceSensorA(A0, 13);
UltraSonicDistanceSensor distanceSensorB(12, 11);

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0);<br>

The following code snippet is the classic use of C variables - We are about to use them inside the system.

uint8_t usSensors;
uint8_t currentDeviceMode;
uint8_t previousDeviceMode;

uint32_t stripCounter;
uint8_t stripIndex;

uint16_t blockCounter;
uint8_t blockValue;

uint16_t streetCounter;
bool street0Value;
bool street1Value;

uint16_t colorCounterA;
uint16_t colorCounterB;

uint32_t lcdCounter;
uint8_t lcdStringIndex;
bool lcdBlinkFlag;

uint16_t switchColorCounter;
uint8_t switchColorIndex;<br>

All the functions are basic Arduino-Like encapsulated algorithms and may be understood by merely looking at them.

void initIO() {
  pinMode(TRG0, OUTPUT);
  pinMode(TRG1, OUTPUT);
  pinMode(ECHO0, INPUT);
  pinMode(ECHO1, INPUT);
  for (uint8_t i = 0; i < 5; i++) pinMode(PWM_ARRAY[i], OUTPUT);
  for (uint8_t i = 0; i < 2; i++) pinMode(STREET_ARRAY[i], OUTPUT);
}

void setPwmValue(uint8_t number, uint8_t value) {
  analogWrite(PWM_ARRAY[number], value); 
}

uint8_t sampleUS() {
  if (distanceSensorA.measureDistanceCm() <= US_DISTANCE_THRESHOLD) {
    delay(1000);
    return SWIPE_RIGHT;
    }
  else if (distanceSensorB.measureDistanceCm() <= US_DISTANCE_THRESHOLD) {
    delay(1000);
   return SWIPE_LEFT;
  }
  return SWIPE_NONE;
}

void setStrip(uint8_t index) {
  if (index > 7) stripSR.setAllLow();
  else {
  for (uint8_t i = 0; i < 8; i++) {
    if (i == index) stripSR.set(i, HIGH); // set single pin HIGH
    else stripSR.set(i, LOW);
  }
  }
}

void setBlockLight(uint8_t block) {
  for (uint8_t i = 0; i < 8; i++) {
    if ((1 << i) & block) blocksSR.set(i, HIGH); // set single pin HIGH
    else blocksSR.set(i, LOW);
  }
}

void setStreet(bool street, bool val) {
  if (street == STREET0) digitalWrite(STREET_ARRAY[0], val);
  else digitalWrite(STREET_ARRAY[1], val);
}

void runningRGB(uint8_t group, uint16_t color) {
  uint8_t redIntensity;
  uint8_t greenIntensity;
  uint8_t blueIntensity;
        
  if (color <= 255) {
    redIntensity = 255 - color;    
    greenIntensity = color;       
    blueIntensity = 0;
  }
  else if (color <= 511) {
    redIntensity = 0;                     
    greenIntensity = 255 - (color - 256); 
    blueIntensity = (color - 256);
  }
  else {
    redIntensity = (color - 512);         
    greenIntensity = 0;                   
    blueIntensity = 255 - (color - 512);  
  }
  if (group == PWM_GROUP_A) {
    setPwmValue(0, 255 - greenIntensity);
    setPwmValue(1, 255 - blueIntensity);
  }
  else {
    setPwmValue(2, 255 - redIntensity);
    setPwmValue(3, 255 - greenIntensity);
    setPwmValue(4, 255 - blueIntensity);
  }
}

void setPwmColor(uint8_t group, uint32_t color) {
  uint8_t redColor = (color >> 16) & 0x0000FF;
  uint8_t greenColor = (color >> 8) & 0x0000FF;
  uint8_t blueColor = color & 0x0000FF;
  
  if (group == PWM_GROUP_A) {
    setPwmValue(0, 255 - greenColor);
    setPwmValue(1, 255 - blueColor);
  }
  else {
    setPwmValue(2, 255 - redColor);
    setPwmValue(3, 255 - greenColor);
    setPwmValue(4, 255 - blueColor);
  }
}

void u8g2_prepare(void) {
  u8g2.setFont(u8g2_font_logisoso22_tf);
  u8g2.setFontRefHeightExtendedText();
  u8g2.setDrawColor(1);
  u8g2.setFontPosTop();
  u8g2.setFontDirection(0);
}

void initVariables() {
  stripCounter = 0;
  stripIndex = 0x00;
  colorCounterA = 0;
  colorCounterB = 0;
  lcdCounter = 0;
  lcdStringIndex = 0;
  lcdBlinkFlag = 0;
  blockCounter = 0;
  blockValue = 0x00;
  streetCounter = 0;
  street0Value = false;
  street1Value = false;
  currentDeviceMode = STRIP_ONLY;
  previousDeviceMode = STRIP_ONLY;
  switchColorCounter = 0;
  switchColorIndex = 0;
}

void printLcd(uint8_t mode) {
  u8g2.setFont(u8g2_font_logisoso22_tf);
  u8g2.clearBuffer();

  switch(currentDeviceMode) {
    case STRIP_ONLY:
        u8g2.drawStr(0, 0, "Only Today!");
        break;

    case SWITCHING_COLORS:
        u8g2.drawStr(0, 0, "Grand Opening!");
        break;

    case STRIP_AND_RUNNING_RGB:
        u8g2.drawStr(0, 0, "Book Flight!");
        break;

    case ALL_ON:
        u8g2.drawStr(0, 0, "Cheap and Cozy!");
        break;      
         
    case ALL_OFF:
        u8g2.drawStr(0, 0, "No Limits!");
        break;      

      default: 
      break;
  }
    
  u8g2.sendBuffer();
}

Now, let's take a look at two main functions of an Arduino system. They are converging all the system SW together.

void setup() {
  initIO();
  u8g2.begin();
  delay(300);
  u8g2_prepare();
  delay(300);
  initVariables();
  Serial.begin(9600);
}

void loop() {
  // Sample US Sensors
  usSensors = sampleUS();
  if(usSensors == SWIPE_RIGHT) {
    Serial.println("Swipe Right!");
    if (currentDeviceMode < NUMBER_OF_STATES) currentDeviceMode += 1;
    else currentDeviceMode = STRIP_ONLY;
    printLcd(currentDeviceMode);
  }
  if(usSensors == SWIPE_LEFT) {
    Serial.println("Swipe Left!");
    if (currentDeviceMode > 0) currentDeviceMode -= 1;
    else currentDeviceMode = ALL_OFF;
    printLcd(currentDeviceMode);
  }

  switch(currentDeviceMode) {
    case STRIP_ONLY:
        // Strip //
        setStrip(stripIndex);
        // PWM //
        setPwmColor(PWM_GROUP_A, 0);
        setPwmColor(PWM_GROUP_B, 0);
        // Street //
        setStreet(STREET0, LOW);
        setStreet(STREET1,LOW);
        // Block //
        setBlockLight(0x00);
        break;

    case SWITCHING_COLORS:
        // Strip //
        setStrip(stripIndex);
        // PWM //
        setPwmColor(PWM_GROUP_A, COLORS_ARRAY[switchColorIndex]);
        setPwmColor(PWM_GROUP_B, COLORS_ARRAY[4 - switchColorIndex]);
        // Street //
        setStreet(STREET0, LOW);
        setStreet(STREET1,LOW);
        // Block //
        setBlockLight(0x00);
        break;

    case STRIP_AND_RUNNING_RGB:
        // Strip //
        setStrip(stripIndex);
        // PWM //
        runningRGB(PWM_GROUP_A, colorCounterA);
        runningRGB(PWM_GROUP_B, colorCounterB);
        // Street //
        setStreet(STREET0, street0Value);
        setStreet(STREET1,street1Value);
        // Block //
        setBlockLight(0x00);
        break;

    case ALL_ON:
        // Strip //
        setStrip(stripIndex);
        // PWM //
        runningRGB(PWM_GROUP_A, colorCounterA);
        runningRGB(PWM_GROUP_B, colorCounterB);
        // Street //
        setStreet(STREET0, street0Value);
        setStreet(STREET1,street1Value);
        // Block //
        setBlockLight(blockValue);
        // LCD //
        //if (lcdBlinkFlag) printAdvertising(lcdStringIndex);
        //else u8g2.clearBuffer();
        break;      
         
    case ALL_OFF:
        // Strip //
        setStrip(8);
        // PWM //
        setPwmColor(PWM_GROUP_A, 0);
        setPwmColor(PWM_GROUP_B, 0);
        // Street //
        setStreet(STREET0, LOW);
        setStreet(STREET1,LOW);
        // Block //
        setBlockLight(0x00);
        // LCD //
        //u8g2.clearBuffer();
        break;      

      default: break;
  }

  /* Strip Counter */
  if (stripCounter >= STRIP_COUNTER_MAX) {
    stripCounter = 0;
    if (stripIndex < 8) stripIndex++;
    else stripIndex = 0;
  }
  else stripCounter++;

  /* Lcd Counter */
  if (lcdCounter < LCD_COUNTER_MAX) {
    lcdCounter++;
    lcdBlinkFlag = true;
  }
  else if (lcdCounter < LCD_COUNTER_MAX * 2) {
    lcdCounter++;
    lcdBlinkFlag = false;
  }
  else {
    if (lcdStringIndex < 3) lcdStringIndex++;
    else lcdStringIndex = 0;
    lcdCounter = 0;
  }
  
  /* PWM Counters */
  if (colorCounterA >= 767) colorCounterA = 0;
  else colorCounterA++;
  
  if (colorCounterB >= 767) colorCounterB = 0;
  else colorCounterB++;

  /* Switch Color Counters */
  if (switchColorCounter < SWITCH_COLOR_MAX) switchColorCounter++;
  else {
    switchColorCounter = 0;
    if (switchColorIndex < 4) switchColorIndex++;
    else switchColorIndex = 0;
  }

  /* Block Counter */
  if (blockCounter < BLOCK_COUNTER_MAX) {
    blockCounter++;
  }
  else {
    blockCounter = 0;
    if (blockValue < 0xFF) blockValue += 0x01;
    else blockValue = 0;
  }

  /* Street Counter */
  
  if (streetCounter < STREET_COUNTER_MAX) {
    streetCounter++;
    street0Value = LOW;
    street1Value = HIGH;
  }
  else if (streetCounter < STREET_COUNTER_MAX * 2) {
    streetCounter++;
    street0Value = HIGH;
    street1Value = LOW;
  }
  else if (streetCounter < STREET_COUNTER_MAX * 3) {
    streetCounter++;
    street0Value = HIGH;
    street1Value = HIGH;
  }
  else streetCounter = 0;
  previousDeviceMode = currentDeviceMode;
}<br>

Downloads

Laser-cut, 3D Printing and Composition

P6221351.JPG
P6221336.JPG
P6221341.JPG
P6221347.JPG
P6221384.JPG
P6221374.JPG
P6221382.JPG
P6221395.JPG
P6221409.JPG
P6221405.JPG
P6221396.JPG
P6221397.JPG
P6221441.JPG
P6221448.JPG
P6221449.JPG

Buildings and decorations

All the buildings and their compositions were designed in the Adobe Illustrator with a capacitive touch drawing pad. All the parts were defined as a binary pictures: TRUE areas describe where there is no need to cut the area. FALSE areas describe where there is need to create apertures and to perform a cut. Basically, all the buildings and decorations are single bit (1-bit) Bit Map images (They are even stored in .bmp extension).

Road Lamps

The idea that came up in the mind - to find an available STL model and to insert inside of it an yellow LED - recreate street view from the other perspective.

All the electrical objects were wired with 26AWG WireWrap type (Should be soldered and not tied). All the objects were glued with hot-glue gun - simple and fast way to attach the objects.