Greetings Earthicans!

 

For this write up, I present my project for "Build Inside the Box." This contest was designed to give various makers a "random assortment of components and have them create a project with them. This is somewhat in the style of "Chopped" cooking show.

 

I was able to get as far as a solid mechanical assembly of components, but did run quite a bit short on final assembly time.

 

I had a  build log running which can be found here if anyone is interested.

 

What did I build?

I attempted to create a "ball on a beam" project which uses a PID loop to position a rolling ball on a horizontal beam. I found out that 'nothing is simple' and  spent a lot of time over the last 2+ weeks trouble shooting. I had issues with everything rom communication to the distance sensor to reading from the SD card. Regardless, I do have basic functionality on all necessary components.

 

(Post updated to include better link to the video)

How did I use the components?

Great question!

The official list can be found here.

MKR Zero

Main brains. Controls based on the code linked here.

 

Battery Bank

Powered the system. I was able to pull over 2 amps using a programmable load. Actual system uses around 1.4 amps nominal with microstepping the stepper motor. Using normal steps takes over 1.9 amps.

 

Wago Lever Nuts

I love these things! We actually use them at work all the time.  I only found out in the last week about the IPx8 version of the GelBox from Wago and am patiently waiting on samples.... I ended up using these for connecting my microphone input to the Op Amp. Hey Wago - howz about some samples of these???

 

Vishay Phototransistor

I used this for a homing sensor to let the controller know where the beam is located. I have a 'flag' hanging off the beam that goes down and blocks the beam of this sensor when in the 'low' position.

 

Microchip Op-Amp

This component ended up being very useful. I wasn't sure at first how to use it. I ended up using it to clear up a "clap" [audio] signal into an extremely simple digital input. I look at a microphone input in one stage, then pass into an R/C circuit for the second stage. At the third stage, I just clean up the signal into a digital which the microcontroller can easily read. So the microphone input uses 3/4 of the channels on this chip. The fourth channel was used by the photo interrupter to clean up the signal into digital.

 

The circuit is as shown here.

 

OLED Display

I used this to read out all the info from the system.

 

Temperature Sensor

This is read from an analog input. My original idea was to position the ball based on the current temperature. Time constraints got to me on this one, and I was only able to read the temperature and display it on the screen.

 

Distance Sensor

In a lot of ways, this is a main component of the system. The ball's position is read from the sensor and fed into a control loop which drives the motor.

 

SD Card

We were lovingly provided with a 32 gig SD card; just for fun. I chose to use this like I which we used some things at work - local settings. I created (BORROWED)  a script to read settings from an SD card to configure the Arduino. On boot, settings are pulled in and used. I ran into  a lot of stability issues with the SD. I was at least able to read and have values displayed on-screen.

 

 

The Code

Here is the code I used in the video .

/*
 * Notes:
 * 
 * MCP9701e Datasheet: https://www.sparkfun.com/datasheets/DevTools/LilyPad/MCP9700.pdf
 * Temperature conversion = 19.5 mV/°C and range from  0°C to +70°C 
 * 400mv = 0°C (~124 bit-level). 1024 bits (using default 10-bit ADC) gives 0.00322265625 V/level
 * Formala: ((analogRead(A4) * 0.00322265625)- 0.4) /0.0195
 * 
 * I2C Addresses:
 * 0x29 VL530x distance measurement
 * 0x60 Motor shield
 * 0x70 Motor shield
 * 
 * Stepper:
 * US-17HS4401S Usongshine
 * bipolar stepper
 * Black + Green 
 * Red + Blue
 * 1.8° (200 steps/rev)
 * 9-42V DC
 * 
 * Running Microstep
 * 
 * OLED
 * Connections:
 * OLED--> MKR
 * GND-GND
 * VIN open
 * VDD-3.3v (MKR 'VCC' pin)
 * CS - 2
 * Reset- Reset
 * DC-3
 * SCL-9 (SCK)
 * SDA - 8 (MOSI)
 * 
 */




//Distance Measurement
#include "Adafruit_VL53L0X.h"
Adafruit_VL53L0X lox = Adafruit_VL53L0X();


//Stepper setup
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include <AccelStepper.h>
Adafruit_MotorShield AFMS = Adafruit_MotorShield(); 
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 2);




// OLED setup
#include <SPI.h>
// #include <Wire.h> (Wire library included above)
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


//SD Card
#include <SD.h>


File mySettings;
char homePin[1];
char micPin[1];
char temp;
char settingHomePin[8] = "HomePin";
char settingMicPin[7] = "MicPin";
char settingTemp[10];
char valueTemp[63];
int settingTempLastPos = 0;
int valueTempLastPos = 0;
int percentCount = 0;
int andCount = 0;
bool isSetting = true;
bool homePinValueFound = false;
bool micPinValueFound = false;




uint16_t motorPos;


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


// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI  8
#define OLED_CLK   9
#define OLED_DC    3
#define OLED_CS    2
#define OLED_RESET 13
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);






void forwardstep() {  
  myMotor->onestep(FORWARD, MICROSTEP);
}
void backwardstep() {  
  myMotor->onestep(BACKWARD,MICROSTEP);
}


AccelStepper AcStepMotor(forwardstep, backwardstep);




//PID tuning values (read from analog pins)
volatile uint16_t P_val;
volatile uint16_t I_val;
volatile uint16_t D_val;


void readSDConfig(){
  
  Serial.println("Initializing SD card...");
  display.setTextSize(1); 
  display.clearDisplay();
  display.setCursor(0,0);
  display.println("Reading SD...");
  display.display();


  if (!SD.begin(SDCARD_SS_PIN)) {
    Serial.println("Initialization failed!");
    display.println("Read from SD Failed.");
    display.display();
    delay(3000);
    return;
  }
  Serial.println("Initialization done.");
  mySettings = SD.open("BITB.TXT");
  if (mySettings) {
    Serial.println("BITB.TXT");  


    while (mySettings.available()) {
      temp = mySettings.read();
      if (temp == '%') {
        percentCount++;
        isSetting = true;
        if (percentCount == 2) {
          //Serial.println(settingTemp);
          if (strcmp(settingHomePin,settingTemp) == 0) {
            Serial.println("HomePin SETTING FOUND");
            //IF FOUND RAISE FLAG FOR ADDING VALUE TO SSID CHAR ARRAY
            homePinValueFound = true;
            }
          else if (strcmp(settingMicPin,settingTemp) == 0) {
            Serial.println("MicPin SETTING FOUND");


            //IF FOUND RAISE FLAG FOR ADDING VALUE TO SSID CHAR ARRAY
            micPinValueFound = true;
            }
          memset(&settingTemp[0], 0, sizeof(settingTemp));
          settingTempLastPos = 0;
          percentCount = 0;
        }
      }
      else if (temp == '&') {
        andCount++;
        isSetting = false;
        if (andCount == 2) {
          //Serial.println(valueTemp);
          if (homePinValueFound) {
            //Serial.println(valueTemp);
            memcpy(homePin,valueTemp,63);
            Serial.println(homePin);
            display.print("Home Pin is: ");
            display.println(homePin);
            display.display();
            homePinValueFound = false;
            }
          else if (micPinValueFound) {
            //Serial.println(valueTemp);
            memcpy(micPin,valueTemp,63);
            display.print("Mic Pin is: ");
            display.println(micPin);
            display.display();
            Serial.println(micPin);
            micPinValueFound = false;
            }
          memset(&valueTemp[0], 0, sizeof(valueTemp));
          valueTempLastPos = 0;
          andCount = 0;
        }
      }
      else {
        if (isSetting == true) { //setting
          settingTemp[settingTempLastPos] = temp;
          settingTempLastPos++;
        }
        else if (isSetting == false) { //value
          valueTemp[valueTempLastPos] = temp;
          valueTempLastPos++;
        }
      }
    }
    //Serial.println(settingTemp);
    mySettings.close();
    delay(3000);
  } else {
    Serial.println("Can't open BITB.TXT");
  }




}




void setup() {
  Serial.begin(115200);
  //pinMode(A4, INPUT);
  delay(100);




  
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  
  //enable display first
  Serial.println("Starting OLED display...");
  if(!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  display.clearDisplay();
  display.setTextSize(2);             
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Ball on"));
  display.println(F("   a Beam"));
  display.display();
  delay(2000);
    
  Serial.println("Starting VL53L0X device");
  if (!lox.begin()) {
      display.clearDisplay();
      display.setTextSize(1); 
      display.setCursor(0,16);    
      display.println(F("Dist. Sense failed to boot"));
      display.display();
    //Serial.println(F("Failed to boot VL53L0X"));
    delay(1000);
  }
  
  Serial.println("Attempting to read SD settings");
  readSDConfig();
  
  //stepper setup
  AFMS.begin(); 
  AcStepMotor.setMaxSpeed(3200.0); //was up to 1200
  AcStepMotor.setAcceleration(3000.0); //was up to 1000.0


  //clear display before starting
  display.clearDisplay();
  display.display();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
}




uint32_t lastTime = 0;
uint32_t waitTimer = 50;


int myDistance =0;
float myTemperature =0;


void loop() {
  readPID();
  
  if(millis() > lastTime + waitTimer){
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("P"); 
    display.print(P_val);
    display.setCursor(0, 9);
    display.print("I"); 
    display.print(I_val);    
    display.setCursor(0, 18);
    display.print("D"); 
    display.print(D_val);


    myTemperature = readTemperature();
    display.setCursor(50, 0);
    display.print(myTemperature);
    display.print("C");
  
    //Serial.print(" Distance: ");
    myDistance = readDistanceSensor();


    
    display.setTextSize(2);
    display.setCursor(40, 15);
    display.print("D:");
    display.print(myDistance);


    display.setTextSize(1);
    uint8_t homePresent = 1;
    if(digitalRead(6)) homePresent=0;
    display.setCursor(110, 24);
    display.print(homePresent);
        
    uint8_t MicActive = 0;
    if(digitalRead(5)) MicActive=1;
    display.setCursor(110, 14);
    display.print(MicActive);
    
    //AcStepMotor.moveTo(myDistance);
    
    display.display();
    lastTime = millis();
  }
  
    
  //stepper positioning
  AcStepMotor.moveTo(analogRead(A1));
  AcStepMotor.run();
}


float readTemperature(){
  float temp=round(((analogRead(A4) * 0.00322265625)- 0.4) /0.0195 * 10) / 10;
  return temp;
}


void readPID(){
  uint32_t tempVal=0;
  uint8_t readLoops=32;
  
  for(int i=0; i<readLoops; i++){
    tempVal += analogRead(A1);
  }
  P_val = tempVal/readLoops;
  tempVal = 0;
  
  for(int i=0; i<readLoops; i++){
    tempVal += analogRead(A2);
  }
  I_val = tempVal/readLoops;
  tempVal = 0;


  for(int i=0; i<readLoops; i++){
    tempVal += analogRead(A3);
  }  
  D_val = tempVal/readLoops;
  tempVal = 0;
}




int readDistanceSensor(){
  VL53L0X_RangingMeasurementData_t measure;
    
  //Serial.print("Reading a measurement... ");
  lox.rangingTest(&measure, false); // pass in 'true' to get debug data printout!


  if (measure.RangeStatus != 4) {  // phase failures have incorrect data
    //Serial.print("Distance (mm): "); Serial.println(measure.RangeMilliMeter);
    return(measure.RangeMilliMeter);
  } else {
    return(-1);
    //Serial.println(" out of range ");
  }  
}

 

The code for after reading settings from SD are taken from here - but I always got a lock-up after trying.

 

 

 

I could easily have used an extra week or three on this project, but was able to get this far given the time constraints. Thanks as always to the E14 team for making this all possible.

-James O'Gorman