GeoffR
Published © CC BY-NC

eLazy Suzan

A Bluetooth enabled Lazy Susan with an Arduino controller to allow diners to control the rotation of the Lazy Susan to access food items.

AdvancedFull instructions providedOver 4 days31
eLazy Suzan

Things used in this project

Hardware components

Arduino Uno R4 WiFi
Arduino Uno R4 WiFi
×1
PCF8575 16 Ch I/O I2C extender
×3
L298N Motor Driver
×1
Tri-Color LED
×12
Push Button Switch Momentory
×12
DC-DC buck module (12V > 5V)
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

CNC Router

Story

Read more

Custom parts and enclosures

Button Holder

STL file for 3D printing

Motor Mount

STL File for Motor mount for 3D printing

Schematics

LSManCircuit

The circuit diagram for the eLazy Susan

eLazy Susan BT Client Program

This is a MIT AppInventor Block Diagram

Code

LSManServer

C/C++
This the the Arduino code for the UNO R4 WiFi
#include <ArduinoBLE.h>

#include <Wire.h>
#include <Adafruit_PCF8575.h>
#include <EEPROM.h>



#define WIRE Wire1

// Nordic UART Service UUIDs (commonly used with App Inventor BLE extensions)
#define UART_SERVICE_UUID "698bd1da-4efb-48b5-8a55-e6c40894fd18"
#define UART_RX_CHAR_UUID "f3ed0622-c79d-4aa7-975d-4ce2abdeec08"   //iPhone -> Aruino
#define UART_TX_CHAR_UUID "7aa97144-3ae7-4454-a5ee-e5330875ee94"   //Arduino -> iPhone

BLEService uartService(UART_SERVICE_UUID);

// RX: App  Arduino
BLEStringCharacteristic rxChar(
  UART_RX_CHAR_UUID,
  BLEWrite | BLEWriteWithoutResponse,
  64
);

// TX: Arduino  App
BLEStringCharacteristic txChar(
  UART_TX_CHAR_UUID,
  BLENotify,
  64
);
// basic operating modes
String Mode = "MS";
int MSloc = 0;
String Direction = "MD";
int MDloc = 1;
String Pattern = "MP";
int MPloc = 2;
String Speed = "RS";
int RSloc = 3;

//  mode params for EEPROM
byte selectMode = 1;    // 0 - false, 1 - true
byte autoMode = 1;
byte showPattern = 1;
byte rotationSpeed = 25;
int autoMove = 2; // minutes for auto move
int autoMoveSteps = 5;

bool buttonHold = false;  // button state
bool buttonHoldPrev = false;
bool motorRunning = false;
int currentButton = -1;
int prevButton = -1;


//  motor defs
const int motor1pin1 = 8;
const int motor1pin2 = 9;

const int posStep = 50;
const int speed = 80;  // for testing (0 - 100%);
const int ENA = 7;
const int ENB = 6;
const int ENC_A = 2;  // interrupts
const int ENC_B = 3;
volatile long encoderCount = 0;  // encoder variables
volatile int lastA = 0;
unsigned long lastRPMTime = 0; //  Timing for RPM
long lastCount = 0;
double rpm = 0.0;
const int pulseSteps = 3208;   // pulses per 1/12 step (rev)

// motor params
const int PPR = 11; // pulses fer shaft rev
const int GEAR_RATIO = 1400;
const int  PPR_OUTPUT = PPR * GEAR_RATIO *2;

//states
const int IDLE = 0;
const int SELECT = 1;
const int BTC = 2;

int state = IDLE;  // start up state
int buttonN = -1;  // the home button
int buttonS = -1;  // the select button
bool CWDir = true;  // direction of movement in manual mode

// fan port - leave for the moment
const int POW = 5;

unsigned long startTime;
unsigned long endTime;
unsigned long elapsedTime;
unsigned long count = 0;
unsigned long lastButtonPush;

//timeouts
int userActivity = 5;  //seconds
int BTtimeout    = 30; // secs


Adafruit_PCF8575 pcf[3];



//==========================  encoder interrupt
void encoderISR() 
{
  int A = digitalRead(ENC_A);
  int B = digitalRead(ENC_B);

  // Quadrature decoding
  if (A == lastA) return;
  lastA = A;

  if (A == B) encoderCount++;
  else encoderCount--;
}

//---------------------------------------- setup
void setup() 
{
  Serial.begin(115200);
  //while (!Serial);
  delay(2000);

  initPCF();

 

  pinMode(motor1pin1, OUTPUT);
  pinMode(motor1pin2, OUTPUT);

  pinMode(ENA, OUTPUT);
  
  
  pinMode(POW, OUTPUT);
  // Encoder pins
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);

  Serial.println("DC Motor + Encoder Ready");


  if (!BLE.begin()) 
  {
    Serial.println("BLE start failed");
    while (1);
  }

  BLE.setLocalName("LSman-L");
  BLE.setDeviceName("LSman-D");
  
  
  BLE.setAdvertisedService(uartService);

  uartService.addCharacteristic(rxChar);
  uartService.addCharacteristic(txChar);

  BLE.setAdvertisingInterval(160);

  BLE.addService(uartService);

 txChar.writeValue("READY");   // App can read this on connect
  //delay(500);
  BLE.advertise();
  Serial.println("BLE UART service advertising...");



  setPinModes();
  //testLED();

  setupEEPROM();

//  rest MCP
  //selectMode = 1;
  
  lastButtonPush = millis();
  //  show startup
  if(showPattern == 1) showStartup();
  Serial.println("Ready");
  
}

void initPCF()
{
  WIRE.begin();
  

  bool OK1 = pcf[0].begin(0x20,&Wire1);
  bool OK2 = pcf[1].begin(0x21,&Wire1);
  bool OK3 = pcf[2].begin(0x22,&Wire1);
  Serial.println("PCF   1:"+String(OK1)+"  2:"+String(OK2)+"  3:"+String(OK3));
  
  int n = scanI2C(true);  // checks status if I2C devices
}

//---------------------------------------------------- loop
void loop() 
{
  bool runnow = true;
  if(runnow)
  {
    //selectMode = 1;  // temp only
    count++;
    int nd = 0;
    
    if(count % 100 == 0)     // Check that I2C bus is still live with 3 PCF devices every 100 scans
    {
      nd = scanI2C(false);
      if(nd != 3) Serial.println("count: "+ String(count)+"  I2C: "+nd);
      
      
        //if(nd !=3 ) restartArduino();
      displayBLUE(0,true);       // flash LED 0 depending on mode
      delay(5);
      displayBLUE(0,false);
      if(selectMode == 0)
      {
        delay(100);
        displayBLUE(0,true);
        delay(5);
        displayBLUE(0,false);
      }
      
    }


    BLEDevice central = BLE.central();    
    
    startTime = millis();

    if (central) // check status of BLE and handle comms if connected
    {
      state = BTC;
      Serial.println("Connected");

      while (central.connected()) 
      {
        displayBLUE(0,true);

        // Check if App Inventor wrote something
        if (rxChar.written()) 
        {
          startTime = millis();
          String incoming = (rxChar.value()).c_str();
          Serial.print("App  Arduino: ");
          Serial.println(incoming);
          
          if(incoming.equals("UP") )  // update request
          {
              String message = "UP:"+ Mode + String(selectMode)+":"+ Direction+String(autoMode)+":" + 
                  Pattern+String(showPattern)+":"+Speed+String(rotationSpeed);
              Serial.println("Message: "+message);
              txChar.writeValue(message.c_str());
          }
          // Echo back to App
          else 
          {
            if(incoming.startsWith("MS"))
            {
                if(incoming.charAt(2) == '0') selectMode = 0;
                else selectMode = 1;
                EEPROM.write(MSloc,selectMode);
                
            }
            else if(incoming.startsWith("MD"))
            {
                if(incoming.charAt(2) == '0') autoMode = 0;
                else autoMode = 1;
                EEPROM.write(MDloc,autoMode);
                
            }
            else if(incoming.startsWith("MP"))
            {
                if(incoming.charAt(2) == '0') showPattern = 0;
                else showPattern = 1;
                EEPROM.write(MPloc,showPattern);
                
            }
            else if(incoming.startsWith("RS"))
            {
                rotationSpeed = (byte) (incoming.substring(2)).toInt();
                Serial.println(incoming + "  RS = "+ String(rotationSpeed));
                EEPROM.write(RSloc,rotationSpeed);
                
                
            }

            String reply = "ACK: " + incoming;
            txChar.writeValue(reply.c_str());
          }
        }

        delay(10);
        elapsedTime = millis() - startTime;
        if(elapsedTime > BTtimeout * 1000) 
        {
          Serial.println("No interaction...");
          BLE.disconnect();
          
        }
      }

      Serial.println("Disconnected");
      state = IDLE;
      displayBLUE(0,false);
    }


    //scanI2C(false);
    // Now handle buttons and moves
    
    if(state == IDLE)
    {
      int but = scanButtons();  //   Scan buttons -----------------
      if(but >= 0) Serial.println("**** but="+String(but));
      if(but >=0) 
      {
        lastButtonPush = millis();
        Serial.println("Button: "+String(but) +" Hold:"+String(buttonHold)+"  Prev:"+String(buttonHoldPrev));
      // hold = false;
        if(!buttonHold )     // button click
        {
          if(motorRunning) motorOFF();
          if(!buttonHoldPrev)
          {
            
            if(selectMode == 1)    // enter select mode, 
            {
                buttonN = but;
                buttonS = buttonN;
                displayRED(buttonN, true);
                state = SELECT;
                startTime = millis();
                bool wait = true;
                while(wait)  // secs to make selection
                {
                  if(millis() - startTime > userActivity*1000) 
                  {
                    wait = false;
                    Serial.println("Timeout...");
                  }
                  if(wait)
                  {
                    but = scanButtons();
                    
                    if(but >= 0)
                    {
                        startTime = millis();
                        if(!buttonHold)    // click
                        {
                          // if(but == buttonS)
                          // {
                          //    displayRED(buttonN, false);
                          // }
                          if (buttonS != buttonN) displayGREEN(buttonS, false);
                            buttonS = (buttonS + 1) % 12;
                            displayGREEN(buttonS, true);

                        }
                        else              // hold
                        {
                              
                            int rot = (buttonN - buttonS + 6) % 12;
                            if(rot < 0) rot = 12 + rot;
                            
                            int moveSteps = rot - 6;
                            Serial.println("Moving: N="+String(buttonN)+"  S="+String(buttonS) + " steps:"+String(moveSteps));
                            moveStep(moveSteps);
                            displayGREEN(buttonS, false);
                            state = IDLE;
                        }
                    }
                  }
                  delay(10);
                }
                for(int k=0; k < 12; k++)
                {
                  displayRED(k,false);
                  displayGREEN(k,false);
                  displayBLUE(k,false);
                }
                buttonHold = false;
                buttonHoldPrev = false;
                state = IDLE;
            }
            else   // must be manual mode, change direction
            {
                CWDir = !CWDir;
            }
          }
        }
        else if(selectMode == 0)
        {
          Serial.println("But: "+String(but)+"  Hold");
          displayRED(but,true);
          motorON();
        }
      }
      else    // button must have been released
      {
        buttonHold = false;
        //Serial.println("Prev But: "+String(prevButton));
          displayRED(prevButton,false);
        if(motorRunning) 
        {
          motorOFF();
          
        }
        if(buttonHoldPrev) 
        {
          CWDir = !CWDir;
          Serial.println("Direction change");
          buttonHoldPrev = false;
        }

        if(autoMode == 1)
        {
          if((millis() - lastButtonPush) > autoMove*60000)
          {
            moveStep(autoMoveSteps);
            lastButtonPush = millis();
            Serial.println("Auto move: "+String(autoMoveSteps));
          }
        }
      }
    }
  }
  delay(10);   // short delay to scan again
}




//-------------------------- restartArduino
void restartArduino()
{
  Serial.println("Restarting .......");
  NVIC_SystemReset();
}

// ------------------------------------ showStartup
void showStartup()
{
  
 
  Serial.println("Startup display");
     for(int i = 0; i < 5; i++)
     {
        //Serial.println("i="+String(i));
        for (int j=0; j <= 6; j++)
        {
          
          int jj = 12 - j;
          //Serial.println("j="+String(j)+"  jj="+String(jj));
          displayBLUE(j, true);
          if( jj > 6 && jj < 12) displayBLUE(jj, true);
          delay(300);
          displayBLUE(j, false);
          if( jj > 6 && jj < 12) displayBLUE(jj, false);
          
        }
        
     }
     
}

//--------------------------------------- setupEEPROM
void setupEEPROM()
{
  if(EEPROM.read(0) == 255)
  {
    Serial.println("EEPROM is empty");
    EEPROM.write(MSloc, selectMode);
     
    EEPROM.write(MDloc, autoMode);
     
    EEPROM.write(MPloc, showPattern);
     
    EEPROM.write(RSloc, rotationSpeed);
     
  }
  else
  {
    Serial.println("EEPROM is available");
    selectMode = EEPROM.read(MSloc);
    autoMode = EEPROM.read(MDloc);
    showPattern = EEPROM.read(MPloc);
    rotationSpeed = EEPROM.read(RSloc);
    Serial.println("MS:"+String(selectMode)+"  MD:"+String(autoMode)+"  MP:"+String(showPattern)+"  RS:"+String(rotationSpeed));

  }
}

// poweron - use for fan!
void powerOn(bool on)
{
  if(on) digitalWrite(POW,HIGH);
  else digitalWrite(POW,LOW);
  
}

//------------------------------------------motorON
void motorON()
{
  int speedValue = rotationSpeed *255 / 100;
  Serial.println("Motor ON");
  if(CWDir)
  {
    digitalWrite(motor1pin1, HIGH);
    digitalWrite(motor1pin2, LOW);
  }
  else
  {
     digitalWrite(motor1pin1, LOW);
     digitalWrite(motor1pin2, HIGH);
  }
  
  analogWrite(ENA,speedValue);  // start motor
  motorRunning = true;
}

//----------------------------------------- motorOFF
void motorOFF()
{
  int speedValue = 0;   
  analogWrite(ENA,speedValue);  // stop motor
  motorRunning = false;
  delay(1000);
}

//------------------------------------------ moveStep
void moveStep(int n)
{
  // set speed
  //powerOn(true);
  //delay(1000);
  int speedValue = rotationSpeed *255 / 100;
  int stepCount = abs(n)*pulseSteps;
  encoderCount = 0;

  Serial.println("Start motor - steps: "+String(n));

  analogWrite(ENA,speedValue);  // start motor
  if(n > 0)      // CW
  {
    digitalWrite(motor1pin1, HIGH);
    digitalWrite(motor1pin2, LOW);
  }
  else       //ACW
  {
    digitalWrite(motor1pin1, LOW);
    digitalWrite(motor1pin2, HIGH);
  }
  while(abs(encoderCount) < stepCount)
  {

  } 
  analogWrite(ENA,0);  // stop motor
  Serial.println("encoderCount: "+String(encoderCount));
  //powerOn(false);
}

//---------------------------------------- scanI2C
int scanI2C(bool detail)
{
  byte error, address;
  int nDevices = 0;

  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    WIRE.beginTransmission(address);
    error = WIRE.endTransmission();

    if (error == 0)
    {
      if(detail) 
      {
            Serial.print("I2C device found at address 0x");
          if (address<16)
            Serial.print("0");
          Serial.print(address,HEX);
          Serial.println("  !");
      }

      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }
  }
  if (nDevices == 0)
  {
    if(detail) Serial.println("****** No I2C devices found\n");
  }
  else
   if(detail)  Serial.println("done\n");
   return nDevices;
}

//----------------------------------- scanButtons
int scanButtons()
{
  int but = -1;
  int holdTime = 10;
  int numberSamples = 50;
  int j = 0;
  
  for(int i=0; i < 12; i++)
  {
    
    
      if(pcf[0].digitalRead(i) == LOW)
      {
         but = i;
         buttonHoldPrev = buttonHold;
         buttonHold = true;   // assume hold
         j=0;
         while(j < numberSamples) // wait for release
         {
           
            
            if(pcf[0].digitalRead(i) == HIGH)  // button released
            {
                buttonHold = false;
                Serial.println("Button  released: "+String(i)+"  j="+String(j)+" buttonHold:"+String(buttonHold));
                but = i;
                return but;
                
            }
            j++;
            delay(holdTime);
            //if(!buttonHold) break;
         }

         if(!buttonHold)
         {
            prevButton = i;
            currentButton = i;
            buttonHoldPrev = buttonHold;
              but = -1;
              return but;
         }
         else
         {
            prevButton = i;
            currentButton = i;
            buttonHoldPrev = buttonHold;
            Serial.println("Button down: "+String(i)+"  buttonHold:"+String(buttonHold));
            return i;
         }
         
      }
    
  }
  
  return but;
}

//--------------------------------------- setPinModes
void setPinModes()
{
  int dev;
  int port;
  Serial.println("Setting pin modes");
  for(int i= 0; i<12; i++)    // button inputs
  {
     dev = devIndex(i);
     port = portIndex(i);
    // Serial.println("Setting Modes: Dev="+String(dev) +"  port="+String(port));
     pcf[dev].pinMode(port,INPUT_PULLUP);
  }
  for(int i= 12; i<48; i++)    // LED outputs
  {
     dev = devIndex(i);
     port = portIndex(i);
     pcf[dev].pinMode(port,OUTPUT);   // turn LEDs OFF
     pcf[dev].digitalWrite(port,HIGH);
  }
}

//------------------------------------- testLED
void testLED()
{
   for (int i=0; i < 1; i++)
   {
      displayRED(i,false);
      displayGREEN(i,false);
      displayBLUE(i,true);
   }
}

//--------------------------------------- displayRED
void displayRED(int N, bool ON)
{
  int NP = N + 12;
  int dev = devIndex(NP);
  int port = portIndex(NP);
  
  if(ON)
  {
    //Serial.println("ON: NP="+String(NP)+"  RED: Dev="+String(dev) +  "  Port="+String(port));
    //pcf[dev].pinMode(port,OUTPUT);
    pcf[dev].digitalWrite(port, LOW);
  }
  else
  {
    //Serial.println("OFF: NP="+String(NP)+"  RED: Dev="+String(dev) +  "  Port="+String(port));
    //pcf[dev].pinMode(port,INPUT);
    pcf[dev].digitalWrite(port, HIGH);
  }
    
}

//-------------------------------------- displayGREEN
void displayGREEN(int N, bool ON)
{
  int NP = N + 24;
   int dev = devIndex(NP);
   int port = portIndex(NP);
   //Serial.println("NP="+String(NP)+"  GREEN: Dev="+String(dev) +  "  Port="+String(port));
   if(ON)
  {
    //pcf[dev].pinMode(port,OUTPUT);
    pcf[dev].digitalWrite(port, LOW);
  }
  else
  {
    //pcf[dev].pinMode(port,INPUT);
    pcf[dev].digitalWrite(port, HIGH);

  }
}

//----------------------------------------- displayBLUE
void displayBLUE(int N , bool ON)
{
  int NP = N + 36;
  int dev = devIndex(NP);
  int port = portIndex(NP);
  //Serial.println("NP="+String(NP)+"  BLUE: Dev="+String(dev) +  "  Port="+String(port));
  if(ON)
  {
    //pcf[dev].pinMode(port,OUTPUT);
    pcf[dev].digitalWrite(port, LOW);
  }
  else
  {
    //pcf[dev].pinMode(port,INPUT);
    pcf[dev].digitalWrite(port, HIGH);

  }
}


//-------------------------------- devIndex
int devIndex(int N)
{
    int base = 16;
    int D = N / base;
    return D;
}


//---------------------------------- portIndex
int portIndex(int N)
{
    int base = 16;
    int D = N/base;
    int I = N - D*base;
    return I;
}

// ------------------------------------------------------------
// Calculate RPM from encoder counts
// ------------------------------------------------------------
void updateRPM() 
{
  unsigned long now = millis();
  if (now - lastRPMTime >= 500) 
  {  // update every 0.5 sec
    long countNow = encoderCount;
    double delta = (double) (countNow - lastCount);

    //rpm = (delta * 120.0) / PPR_OUTPUT;  // 120 = (60 sec/min) / 0.5 sec
    rpm = (delta * 60*1000/(double)(now-lastRPMTime)) / (double) PPR_OUTPUT;  // 120 = (60 sec/min) / 0.5 sec
    
    Serial.println("Last Time: " + String(lastRPMTime) + "  Now: " + String (now) + "  lastCount:" + String(lastCount)+
            "  countNow:" + String(countNow) + "  RPM:" + String(rpm) + " Factor:"+String(PPR_OUTPUT));
    lastCount = countNow;
    lastRPMTime = now;
  }
}

Credits

GeoffR
1 project • 0 followers

Comments