Skip to content

Week 11 Machine Week Students A

Adam, Griffin, Ryan, Jed, Merritt

Final Video

Final Slide

Planning/Sketching

After talking and discussing, our group eventually settled on a table cleaning machine. We first started off by doing a group sketch on the whiteboard, where we shared ideas and planning. In a general term, we first divided our group with software/electronics(Adam and Merritt) and Mechanical Design and Assembly(Jed, Ryan, and Griffin)

(Image of White Board)

There were many things to think about, many of which we didn’t even consider in the beginning. The main takeaways included The number of motors we need and power consumption Measurements were important in designing, for everything needed to fit in a desired way to work

Once we had the planning done we split up and started our work. The person who contributed a certain part is written next to the headers. This document shares the basic overall process of the design. To check out a deeper dive in individual processes and troubleshooting, navigate to our individual websites.

Crude CAD Model - Jed

Jed started the design off by creating a rough sketch of the portions, and something more physical than a white board to help invision what our machine would look like. I go into more depth on my site.

To start off this week I made a really rough and crude design, that does not use any correct dimensions, of what our machine would look like. The purpose of this sketch was just to get a feel for where everything would go and a feel for what it would look like. (File)

(Image)

The design included shafts to allow the squeegee to move up and down, there are shafts on both sides because we were going to have steppers in both shafts to move the squeegee up and down for redundancy and so that it moves evenly across the width of the table. The design also has 2 slots for a basic and early option of how to mount the sensors.

Programming, Electronics, Video Production – Adam

I found this video from this forum which I’ll use to talk to the VL53L1X PCB1540 sensor with MicroPython.

from enum import Enum

class RAG_POSITION(Enum):
    DOWN = 1
    UP = 2

class RAG_ROTATION(Enum):
    SPINNING = 3
    IDLE = 4

class DIRECTION(Enum):
    FORWARD = True
    BACK = False

CLEANING_DIRECTION = DIRECTION.FORWARD

def start_condition(): # -> bool
    """
    determines whether the on button/bluetooth start connection/etc has been pressed
    """
    pass

def wait_for_startup(): # -> void
    while True:
        if start_condition():
            main_flow()

def set_rag_position(rag_pos): # -> void
    """
    - moves rags to position RAG_POSITION.UP or RAG_POSITION.DOWN
    - doesn't end the function until movement complete
    """
    pass

def set_rag_rotation(rag_rot): # -> void
    """
    - moves rags to position RAG_ROTATION.SPINNING or RAG_ROTATION.IDLE
    - doesn't end the function until state achieved
    """
    pass

def hit_edge(sensor_dir): # -> bool
    """
    checks the the FORWARD or BACKWARD sensor (depending on `sensor_dir`) senses a table, and returns False if there's a table and True if not
    """
    pass

def move_until_edge(current_dir): # -> void
    """
    - begins wheel movement in `current_dir`
    - waits for sensors to indicate the end of a table
    - returns opposite direction
    """
    while not hit_edge(current_dir): # don't have to worry about the current edge since it's a different sensor
        delay(0.05)
    """
    - stops wheel movement
    """
    return not current_dir

def main_flow():
    current_direction = CLEANING_DIRECTION
    set_rag_position(RAG_POSITION.DOWN)
    set_rag_rotation(RAG_ROTATION.SPINNING)
    current_direction = move_until_edge(current_direction)
    set_rag_rotation(RAG_ROTATION.IDLE)
    set_rag_position(RAG_POSITION.UP)
    move_until_edge(current_direction)

if __name__ == "__main__":
    while True:
        wait_for_startup()

I also had an idea that an emergency stop button would probably be a good idea.

Step 1 - Networking With The Sensor

I soldered the sensor.

Then I asked ChatGPT 4.0 to point me to some resources.

Prompt

Point me to some resources to control a PCB1540 VL53L1X sensor with a raspberry pi pico w using micropython

Response

The VL53L1X is a Time-of-Flight (ToF) ranging sensor that uses STMicroelectronics' patented FlightSense technology. It can be used with a Raspberry Pi Pico to measure distance accurately. Here are some resources to help you get started with controlling a PCB1540 VL53L1X sensor using MicroPython on a Raspberry Pi Pico:

MicroPython for Raspberry Pi Pico:
To get started with MicroPython on your Raspberry Pi Pico, visit the official documentation:
https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf

VL53L1X Datasheet:
To understand the VL53L1X sensor's capabilities and specifications, refer to its datasheet:
https://www.st.com/resource/en/datasheet/vl53l1x.pdf

MicroPython VL53L1X library:
You'll need a MicroPython library for the VL53L1X sensor. Here is a library you can use:
https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/pimoroni_pico/vl53l1x.py

Download the vl53l1x.py file and copy it to your Raspberry Pi Pico's storage.

Wiring:
Connect the VL53L1X sensor to the Raspberry Pi Pico as follows:

VCC to 3.3V
GND to GND
SDA to GPIO0 (I2C0_SDA)

SCL to GPIO1 (I2C0_SCL)
Example code:
After connecting the sensor and copying the library, you can use the following example code to get started:
Copy code
import machine
import utime
from vl53l1x import VL53L1X

# Configure I2C
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(0), freq=400000)

# Initialize the VL53L1X sensor
sensor = VL53L1X(i2c)

# Set the distance mode (1=Short, 2=Medium, 3=Long)
sensor.set_distance_mode(2)

while True:
    # Get the distance measurement
    distance = sensor.get_distance()
    print("Distance:", distance, "mm")

    # Wait for a second before taking the next measurement
    utime.sleep(1)
This example initializes the I2C interface, sets up the VL53L1X sensor, and takes distance measurements every second, printing the results.

Remember to always consult the official documentation for the Raspberry Pi Pico and the VL53L1X sensor for the most up-to-date and accurate information. Good luck with your project!

I tried doing this, based off of page 4 of this datasheet and this Raspberry Pi Pico W pinout sheet, connecting 6 on the breakout board to 3V+, 5 to GND, 2 to SDA (GPIO 0), 4 to SCL (GPIO 1). It started smoking, and we realized after testing with the multimeter that all of these board had continuity between the supposed VCC and GND.

I also realized that, in the future, we’ll need to calibrate the sensor values for distances on and off the table.

I realized that maybe the weird symbols on the pinout sheet for the breakout board meant that 7 and 8 are actually power and ground, and these pins didn’t have continuity. I plugged it in (VBUS instead of 3V+) this time since Merritt Backerman taught me that is 5V+, as this board requires.

I installed MicroPython for the Pico W in Thonny on the Pico (holding the bootloader button as I plugged it in).

Installing MicroPython

Then I installed the vl53l1x library at Tools > Manage packages....

Installing VL

I got a warning that it doesn’t look like MicroPython/CircuitPython but installed it anyway since the page said Python.

I got this error.

install VL53L1X
Preparing working environment ...
Collecting pip==22.2.2
  Using cached pip-22.2.2-py3-none-any.whl (2.0 MB)
Collecting setuptools==65.4.1
  Downloading setuptools-65.4.1-py3-none-any.whl (1.2 MB)
     ---------------------------------------- 1.2/1.2 MB 4.1 MB/s eta 0:00:00
Collecting wheel==0.38.4
  Using cached wheel-0.38.4-py3-none-any.whl (36 kB)
Installing collected packages: wheel, setuptools, pip
  Attempting uninstall: setuptools
    Found existing installation: setuptools 65.5.0
    Uninstalling setuptools-65.5.0:
      Successfully uninstalled setuptools-65.5.0
  Attempting uninstall: pip
    Found existing installation: pip 22.3.1
    Uninstalling pip-22.3.1:
      Successfully uninstalled pip-22.3.1
Successfully installed pip-22.2.2 setuptools-65.4.1 wheel-0.38.4
Looking in indexes: http://127.0.0.1:36628
127.0.0.1 - - [29/Mar/2023 18:30:26] "GET /vl53l1x/ HTTP/1.1" 200 -
Collecting VL53L1X
127.0.0.1 - - [29/Mar/2023 18:30:26] "GET /vl53l1x/VL53L1X-0.0.5.tar.gz/ HTTP/1.1" 200 -
  Downloading http://127.0.0.1:36628/vl53l1x/VL53L1X-0.0.5.tar.gz/
     - 182.3 kB ? 0:00:00
  Installing build dependencies: started
127.0.0.1 - - [29/Mar/2023 18:30:27] "GET /setuptools/ HTTP/1.1" 200 -
127.0.0.1 - - [29/Mar/2023 18:30:28] "GET /setuptools/setuptools-67.6.1-py3-none-any.whl/ HTTP/1.1" 200 -
127.0.0.1 - - [29/Mar/2023 18:30:28] "GET /wheel/ HTTP/1.1" 200 -
127.0.0.1 - - [29/Mar/2023 18:30:28] "GET /wheel/wheel-0.40.0-py3-none-any.whl/ HTTP/1.1" 200 -
127.0.0.1 - - [29/Mar/2023 18:30:31] "GET /pip/ HTTP/1.1" 200 -
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
127.0.0.1 - - [29/Mar/2023 18:30:33] "GET /smbus2/ HTTP/1.1" 200 -
Collecting smbus2
127.0.0.1 - - [29/Mar/2023 18:30:33] "GET /smbus2/smbus2-0.4.2-py2.py3-none-any.whl/ HTTP/1.1" 200 -
  Downloading http://127.0.0.1:36628/smbus2/smbus2-0.4.2-py2.py3-none-any.whl/
     - 11.5 kB ? 0:00:00
Building wheels for collected packages: VL53L1X
  Building wheel for VL53L1X (pyproject.toml): started
  Building wheel for VL53L1X (pyproject.toml): finished with status 'error'
  error: subprocess-exited-with-error

  Building wheel for VL53L1X (pyproject.toml) did not run successfully.
  exit code: 1

  [9 lines of output]
  running bdist_wheel
  running build
  running build_py
  creating build
  creating build\lib.win-amd64-cpython-310
  copying python\VL53L1X.py -> build\lib.win-amd64-cpython-310
  running build_ext
  building 'vl53l1x_python' extension
  error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/
  [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for VL53L1X
Failed to build VL53L1X
ERROR: Could not build wheels for VL53L1X, which is required to install pyproject.toml-based projects
Error Command '['C:\\Users\\adams\\AppData\\Local\\pipkin\\cache\\workspaces\\62544052081391d01bd1e406413cd91d\\Scripts\\python.exe', '-I', '-m', 'pip', '--no-color', '--disable-pip-version-check', '--trusted-host', '127.0.0.1', 'install', '--no-compile', '--use-pep517', '--upgrade-strategy', 'only-if-needed', 'VL53L1X', '--index-url', 'http://127.0.0.1:36628']' returned non-zero exit status 1.

I deleted ChatGPT’s code.

import machine
import utime
from vl53l1x import VL53L1X

# Configure I2C
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(0), freq=400000)

# Initialize the VL53L1X sensor
sensor = VL53L1X(i2c)

# Set the distance mode (1=Short, 2=Medium, 3=Long)
sensor.set_distance_mode(2)

while True:
    # Get the distance measurement
    distance = sensor.get_distance()
    print("Distance:", distance, "mm")

    # Wait for a second before taking the next measurement
    utime.sleep(1)

And used this tutorial’s code based on their website but using the wiring I described. I got this error.

MPY: soft reboot
Traceback (most recent call last):
  File "main.py", line 4, in <module>
  File "PiicoDev_VL53L1X.py", line 111, in __init__
  File "PiicoDev_VL53L1X.py", line 135, in reset
  File "PiicoDev_VL53L1X.py", line 124, in writeReg
OSError: [Errno 5] EIO
MicroPython v1.19.1-994-ga4672149b on 2023-03-29; Raspberry Pi Pico W with RP2040
Type "help()" for more information.

Decided to take a break and try to do it with an Arduino Uno, since I learned last week that I need to get a basic tutorial to work.

Following this tutorial I downloaded this repo as a ZIP file and included as a ZIP library in the Arduino IDE. I then restarted the Arduino IDE and saw the example!

Example Library

I was just getting this output, though.

VL53L1X Qwiic Test
v�

Realized Baud Rate in the Serial Monitor wasn’t 115200 like the program said.

This pinout sheet should A5 and A4 weren’t the right pins, so I changed them. I also realized I had been connecting to the wrong end of the sensor board. Merritt Backerman sent me this library and I used the Continuous example, which worked finally (with the sensor that was smoking before)!!!

(sensor)

I also had a big learning that, oftentime finding code examples for MicroPython is difficult, and instead searching for CircuitPython can lead to better results.

Considering the amount of issues using the Pico, I decided to use the Arduino Uno.

I followed this tutorial for a wiring setup and learned my driver is called a ULN2003 driver board.

I used this code, which worked!

//Includes the Arduino Stepper Library
#include <Stepper.h>

// Defines the number of steps per rotation
const int stepsPerRevolution = 2038;

// Creates an instance of stepper class
// Pins entered in sequence IN1-IN3-IN2-IN4 for proper step sequence
Stepper myStepper = Stepper(stepsPerRevolution, 8, 10, 9, 11);

void setup() {
    // Nothing to do (Stepper Library sets pins as outputs)
}

void loop() {
    // Rotate CW slowly at 5 RPM
    myStepper.setSpeed(5);
    myStepper.step(stepsPerRevolution);
    delay(1000);

    // Rotate CCW quickly at 10 RPM
    myStepper.setSpeed(10);
    myStepper.step(-stepsPerRevolution);
    delay(1000);
}

I added the program together to the following which stops the motor when there is an object close to the sensor, whick works!

//Includes the Arduino Stepper Library
#include <Stepper.h>

// Defines the number of steps per rotation
const int stepsPerRevolution = 2038;

// Creates an instance of stepper class
// Pins entered in sequence IN1-IN3-IN2-IN4 for proper step sequence
Stepper myStepper = Stepper(stepsPerRevolution, 8, 10, 9, 11);

/*
This example shows how to take simple range measurements with the VL53L1X. The
range readings are in units of mm.
*/

#include <Wire.h>
#include <VL53L1X.h>

VL53L1X sensor;

void setup()
{
  while (!Serial) {}
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000); // use 400 kHz I2C

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1);
  }

  // Use long distance mode and allow up to 50000 us (50 ms) for a measurement.
  // You can change these settings to adjust the performance of the sensor, but
  // the minimum timing budget is 20 ms for short distance mode and 33 ms for
  // medium and long distance modes. See the VL53L1X datasheet for more
  // information on range and timing limits.
  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);

  // Start continuous readings at a rate of one measurement every 50 ms (the
  // inter-measurement period). This period should be at least as long as the
  // timing budget.
  sensor.startContinuous(50);
    // Nothing to do (Stepper Library sets pins as outputs)
}

void loop() {
  if (sensor.read() < 100) return;
  Serial.println(sensor.read());
    // Rotate CW slowly at 5 RPM
    myStepper.setSpeed(10);
    myStepper.step(100);
}

I also got DC motors to work with a driver. Here’s the final code.

!Wiring video

#include <SparkFun_TB6612.h>

// Pins for all inputs, keep in mind the PWM defines must be on PWM pins
// the default pins listed are the ones used on the Redbot (ROB-12097) with
// the exception of STBY which the Redbot controls with a physical switch
#define AIN1 2
#define BIN1 7
#define AIN2 4
#define BIN2 8
#define PWMA 5
#define PWMB 6
#define STBY 9

// these constants are used to allow you to make your motor configuration 
// line up with function names like forward.  Value can be 1 or -1
const int offsetA = 1;
const int offsetB = 1;

// Initializing motors.  The library will allow you to initialize as many
// motors as you have memory for.  If you are using functions like forward
// that take 2 motors as arguements you can either write new functions or
// call the function more than once.
Motor motor1 = Motor(AIN1, AIN2, PWMA, offsetA, STBY);
Motor motor2 = Motor(BIN1, BIN2, PWMB, offsetB, STBY);

//Includes the Arduino Stepper Library
#include <Stepper.h>

// Defines the number of steps per rotation
const int stepsPerRevolution = 2038;

// Creates an instance of stepper class
// Pins entered in sequence IN1-IN3-IN2-IN4 for proper step sequence
Stepper myStepper = Stepper(stepsPerRevolution, 8, 10, 9, 11);

/*
This example shows how to take simple range measurements with the VL53L1X. The
range readings are in units of mm.
*/

#include <Wire.h>
#include <VL53L1X.h>

VL53L1X sensor;

void setup()
{
  while (!Serial) {}
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000); // use 400 kHz I2C

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1);
  }

  // Use long distance mode and allow up to 50000 us (50 ms) for a measurement.
  // You can change these settings to adjust the performance of the sensor, but
  // the minimum timing budget is 20 ms for short distance mode and 33 ms for
  // medium and long distance modes. See the VL53L1X datasheet for more
  // information on range and timing limits.
  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);

  // Start continuous readings at a rate of one measurement every 50 ms (the
  // inter-measurement period). This period should be at least as long as the
  // timing budget.
  sensor.startContinuous(50);
    // Nothing to do (Stepper Library sets pins as outputs)
}

void loop() {
  forward(motor1, motor2, 150);
  while (sensor.read() < 100) {
    Serial.print("going "); Serial.println(sensor.read());
  }
  brake(motor1, motor2);
  while (sensor.read() > 100) {
    Serial.print("waiting "); Serial.println(sensor.read());
  }
  //Serial.println(sensor.read());
    // Rotate CW slowly at 5 RPM
    /*myStepper.setSpeed(10);
    myStepper.step(100);*/
}

I skimmed this and this as well as installed this Arduino library.

![Functioning Video]

SCL arduino - 2 sensor SDA arduino - 4 sensor 5V arduino - 5 sensor GND arduino - 6 sensor

GND - dc motors’ grounds, power supply ground BO1 - dc motor 1 power AO1 - dc motor 2 power MV - power supply voltage PWMA - 5 AI2 - 4 AI1 - 2 STBY - 9 BI1 - 7 BI2 - 8 PWMB - 6

STEPPER MOTOR

Following this tutorial I wired the L298n Driver and Nema-17 stepper as follows.

I knew that the two adjacent wires on the stepper were of the same phase because I stuck in LED between then and turned the motor, which caused the LED to turn on.

Arduino 1-4 - Driver int1-int4 Arduino gnd - power supply gnd - driver gnd (middle of three screw-in-pins) Driver two - two on each side of the four of the stepper motor 5V (right of three) driver - 5v arduino (if arduino not plugged in) 12V drivr (left of three) - 12v power supply arduino A0 - either GND or 3V or 5V arduino to change speed

// Include the Arduino Stepper Library
#include <Stepper.h>

// Define Constants

// Number of steps per output rotation
const int STEPS_PER_REV = 200;
const int SPEED_CONTROL = A0;

// The pins used are 1,2,3,4 
// Connected to L298N Motor Driver In1, In2, In3, In4 
// Pins entered in sequence 1-2-3-4 for proper step sequencing

Stepper stepper_NEMA17(STEPS_PER_REV, 1, 2, 3, 4);

void setup() {
  Serial.begin(9600);
  // nothing to do inside the void setup
}

void loop() {
  // read the sensor value:
  int sensorReading = analogRead(SPEED_CONTROL);
  // map it to a range from 0 to 100:
  int motorSpeed = map(sensorReading, 0, 1023, 0, 100);
  // set the motor speed:
  if (motorSpeed > 0) {
    Serial.println(motorSpeed);
    stepper_NEMA17.setSpeed(motorSpeed);
    // step 1/100 of a revolution:
    stepper_NEMA17.step(STEPS_PER_REV / 100);
  }
}

The motor would vibrate weirdly and not turn sometimes (when I was adjusting both grounds connecting into the screw-in-pin) and other times did nothing. I tried hooking it up directly to the Arduino with the Arduino Stepper example sketch, but it just vibrated when it was supposed to be turning (I confirmed the steps per revolution is 200 here).

I connected the motor pins to pins 8-11 on the Arduino.

/*
 Stepper Motor Control - one revolution

 This program drives a unipolar or bipolar stepper motor.
 The motor is attached to digital pins 8 - 11 of the Arduino.

 The motor should revolve one revolution in one direction, then
 one revolution in the other direction.


 Created 11 Mar. 2007
 Modified 30 Nov. 2009
 by Tom Igoe

 */

#include <Stepper.h>

const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution
// for your motor

// initialize the stepper library on pins 8 through 11:
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);

void setup() {
  // set the speed at 60 rpm:
  myStepper.setSpeed(60);
  // initialize the serial port:
  Serial.begin(9600);
}

void loop() {
  // step one revolution  in one direction:
  Serial.println("clockwise");
  //myStepper.step(stepsPerRevolution);
  delay(500);

  // step one revolution in the other direction:
  Serial.println("counterclockwise");
  //myStepper.step(-stepsPerRevolution);
  delay(500);
}

It just vibrated again.

I tried using the ULN2003 Stepper with this tutorial and it worked, so I believe that I simply have a bad stepper motor.

![video/img]

I tried using a smaller stepper motor, which worked, leading me to believe there is a problem with the stepper (later it turns out it kind of worked, so I’m not sure whether this was truly an issue).

4-STEPPER DRIVER

Mr. Dubick told us it would be a better idea to use a four-stepper driver arduino shield.

![img/vid]

Worked first try! 12V power supply, connect ENABLE to GND and connected the servo to one of the axis. Followed this tutorial.

Here’s the code.

// CNC Shield Stepper  Control Demo
// Superb Tech
// www.youtube.com/superbtech

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;


void setup() {
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);

}

void loop() {
 digitalWrite(DirX, HIGH); // set direction, HIGH for clockwise, LOW for anticlockwise
 digitalWrite(DirY, HIGH);
 digitalWrite(DirZ, HIGH);

 for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepX,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepX,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepY,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepY,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepZ,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepZ,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

}

I plugged it in to all the other motor drivers which worked, except for the red “A” axis driver.

When I ran the following code from this website, the driver for the A-axis got very hot and the motor stopped working for the other axes. I also had to install the AccelStepper library from the Arduino Library Manager.

#include <AccelStepper.h>

// Voor de Arduino Uno + CNC shield V3
#define MOTOR_X_ENABLE_PIN 8
#define MOTOR_X_STEP_PIN 2
#define MOTOR_X_DIR_PIN 5


#define MOTOR_Y_ENABLE_PIN 8
#define MOTOR_Y_STEP_PIN 3
#define MOTOR_Y_DIR_PIN 6


#define MOTOR_Z_ENABLE_PIN 8
#define MOTOR_Z_STEP_PIN 4
#define MOTOR_Z_DIR_PIN 7

#define MOTOR_A_ENABLE_PIN 8
#define MOTOR_A_STEP_PIN 12
#define MOTOR_A_DIR_PIN 13

AccelStepper motorX(1, MOTOR_X_STEP_PIN, MOTOR_X_DIR_PIN);
AccelStepper motorY(1, MOTOR_Y_STEP_PIN, MOTOR_Y_DIR_PIN);
AccelStepper motorZ(1, MOTOR_Z_STEP_PIN, MOTOR_Z_DIR_PIN);
AccelStepper motorA(1, MOTOR_A_STEP_PIN, MOTOR_A_DIR_PIN);

void setup()
{
   pinMode(MOTOR_X_ENABLE_PIN, OUTPUT);
   pinMode(MOTOR_Y_ENABLE_PIN, OUTPUT);
   pinMode(MOTOR_Z_ENABLE_PIN, OUTPUT);
   pinMode(MOTOR_A_ENABLE_PIN, OUTPUT);

   motorX.setEnablePin(MOTOR_X_ENABLE_PIN);
   motorX.setPinsInverted(false, false, true);
   motorX.setAcceleration(100);
   //motorX.setMaxSpeed(100);
   //motorX.setSpeed(100);
   motorX.enableOutputs();

   motorY.setEnablePin(MOTOR_Y_ENABLE_PIN);
   motorY.setPinsInverted(false, false, true);
   motorY.setAcceleration(100);
   //motorY.setMaxSpeed(100);
   //motorY.setSpeed(100);
   motorY.enableOutputs();

   motorZ.setEnablePin(MOTOR_Z_ENABLE_PIN);
   motorZ.setPinsInverted(false, false, true);
   motorZ.setAcceleration(100);
   //motorZ.setMaxSpeed(100);
   //motorZ.setSpeed(100);
   motorZ.enableOutputs();

   motorA.setEnablePin(MOTOR_Z_ENABLE_PIN);
   motorA.setPinsInverted(false, false, true);
   motorA.setAcceleration(100);
   //motorA.setMaxSpeed(100);
   //motorA.setSpeed(100);
   motorA.enableOutputs();
}

void loop()
{
   motorX.move(3000);
   motorX.run();
   motorY.move(3000);
   motorY.run();
   motorZ.move(3000);
   motorZ.run();
   motorA.move(3000);
   motorA.run();
}

I tried with a different motor from wires we took apart from an old 3d-printer, and it worked!

This video revealed it’s a clone axis, which it perfect for our application!

After connecting the two blue wires and the two yellow wires next to y-axis and plugging in the motor I thought burnt out and the working motor, it worked! The motion on the previously-really-hot motor was choppy but consistent, and the other motor vibrated at times when it wasn’t moving, but the movement was consistent. The motors and drivers also got fairly hot. I asked Mr. Nelson for advice.

google how to tune the current of a stepper motor look up this specific one and how much current it needs add heat sinks to drivers and have small potentiometers on the drivers that can change the current

![img drivers w heat sinks]

Next I tried plugging the Time-of-Flight sensor into the SDA, SCL, 5V, and GND pins on the shield, but it didn’t work. I tried taking off the shield and wiring it directly to the Arduino, which worked.

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;

#include <VL53L1X.h>

VL53L1X sensor;




void setup() {
  Serial.begin(115200);
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);

  Wire.begin();
  Wire.setClock(400000); // use 400 kHz I2C

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1);
  }

  // Use long distance mode and allow up to 50000 us (50 ms) for a measurement.
  // You can change these settings to adjust the performance of the sensor, but
  // the minimum timing budget is 20 ms for short distance mode and 33 ms for
  // medium and long distance modes. See the VL53L1X datasheet for more
  // information on range and timing limits.
  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);

  // Start continuous readings at a rate of one measurement every 50 ms (the
  // inter-measurement period). This period should be at least as long as the
  // timing budget.
  sensor.startContinuous(50);

}

void loop() {

  Serial.println(sensor.read());

 digitalWrite(DirX, HIGH); // set direction, HIGH for clockwise, LOW for anticlockwise
 digitalWrite(DirY, HIGH);
 digitalWrite(DirZ, HIGH);

 for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepX,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepX,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second
Serial.println("Turning Y");
for(int x = 0; x<1000; x++) { // loop for 200 steps
  digitalWrite(StepY,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepY,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepZ,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepZ,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

}

Next, I realized that the SDA and SCL pins for communication with the sensor weren’t working because they’re connected to A5 and Pin 1 on the Arduino, and there aren’t actually and pins from the shield that connect to the SDA and SCL pins of the Arduino!

So, I grabbed two WHAT THEY’RE CALLED wires that would fit under their shield, which worked extremely well!

![imgs]

I then created a new Arduino file and noticed that I could copy the INO file in the project folder and then could include that code using the #include<OTHER_FILE_NAME.cpp> command.

Basically I needed to change the structure to get that to work (see final working example). The sensor started not working, then I realized I switched to a different one but the header were upside down. Then it started working sometimes but something just getting stuck without writing anything on sensor.init(). I printed something before that and it only printed out half of that thing. I realized it could be that the serial monitor had something wrong so I tried hooking it up to a motor again, which required me to hold the ground to the ground on the middle of the Arduino so the shield could fit on. Now I got it to print out if after I compiled I unplugged then plugged into a different port, but this worked inconsistently. Finally I went back to a different ground and used the SparkFun_VL53L1X.h library instead because this document revealed that the other library might actually require two more pins to be corrected. Now it worked, and I integrated the example code from the library. (I also fed it 3.3v instead of 5v, but then later Merritt Backerman revealed to me that this is incorrect and it should be 5V).

Library example code:

/*
  Reading distance from the laser based VL53L1X
  By: Nathan Seidle
  SparkFun Electronics
  Date: April 4th, 2018
  License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

  SparkFun labored with love to create this code. Feel like supporting open source hardware?
  Buy a board from SparkFun! https://www.sparkfun.com/products/14667

  This example prints the distance to an object.

  Are you getting weird readings? Be sure the vacuum tape has been removed from the sensor.
*/

#include <Wire.h>
#include "SparkFun_VL53L1X.h" //Click here to get the library: http://librarymanager/All#SparkFun_VL53L1X

//Optional interrupt and shutdown pins.
#define SHUTDOWN_PIN 2
#define INTERRUPT_PIN 3

SFEVL53L1X distanceSensor;
//Uncomment the following line to use the optional shutdown and interrupt pins.
//SFEVL53L1X distanceSensor(Wire, SHUTDOWN_PIN, INTERRUPT_PIN);

void setup(void)
{
  Wire.begin();

  Serial.begin(115200);
  Serial.println("VL53L1X Qwiic Test");

  if (distanceSensor.begin() != 0) //Begin returns 0 on a good init
  {
    Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor online!");
}

void loop(void)
{
  distanceSensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!distanceSensor.checkForDataReady())
  {
    delay(1);
  }
  int distance = distanceSensor.getDistance(); //Get the result of the measurement from the sensor
  distanceSensor.clearInterrupt();
  distanceSensor.stopRanging();

  Serial.print("Distance(mm): ");
  Serial.print(distance);

  float distanceInches = distance * 0.0393701;
  float distanceFeet = distanceInches / 12.0;

  Serial.print("\tDistance(ft): ");
  Serial.print(distanceFeet, 2);

  Serial.println();
}

Here’s the final, integrated code for the sensor.

FINAL_PROGRAM.ino

#include "sensor.h"
#include "serial.h"
#include "stepper.h"

void setup() {
  InitializeSerial();
  InitializeSensorCommunicationI2C();
}

void loop() {
  float mm;
  float in;
  float ft;
  SensorVal(mm, in, ft);
  Serial.println(mm);
}

sensor.h

#include <Wire.h>
#include "SparkFun_VL53L1X.h"

void InitializeSensorCommunicationI2C();
void SensorVal(float&, float&, float&); // mm, in, ft

sensor.cpp

#include "sensor.h"

SFEVL53L1X sensor;

/*
  Reading distance from the laser based VL53L1X
  By: Nathan Seidle
  SparkFun Electronics
  Date: April 4th, 2018
  License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

  SparkFun labored with love to create this code. Feel like supporting open source hardware?
  Buy a board from SparkFun! https://www.sparkfun.com/products/14667

  This example prints the distance to an object.

  Are you getting weird readings? Be sure the vacuum tape has been removed from the sensor.
*/


//Uncomment the following line to use the optional shutdown and interrupt pins.
//SFEVL53L1X distanceSensor(Wire, SHUTDOWN_PIN, INTERRUPT_PIN);

void SensorVal(float &mm, float &in, float &ft) {
  sensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!sensor.checkForDataReady())
  {
    delay(1);
  }
  int distance = sensor.getDistance(); //Get the result of the measurement from the sensor
  sensor.clearInterrupt();
  sensor.stopRanging();

  float distanceMm = distance;

  float distanceInches = distance * 0.0393701;
  float distanceFeet = distanceInches / 12.0;

  mm = distanceMm;
  in = distanceInches;
  ft = distanceFeet;
}

void InitializeSensorCommunicationI2C() {
  Wire.begin();

  Serial.begin(115200);
  Serial.println("VL53L1X Qwiic Test");

  if (sensor.begin() != 0) //Begin returns 0 on a good init
  {
    Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor online!");
}

serial.h

void InitializeSerial();

serial.cpp

#include<Arduino.h>
#include "serial.h"

void InitializeSerial() {
  Serial.begin(115200);
}

stepper.h

void setupf();
void loopf();

stepper.cpp

#include "stepper.h"
#include "sensor.h"

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;



void setupf() {
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);



}

void loopf() {


 digitalWrite(DirX, HIGH); // set direction, HIGH for clockwise, LOW for anticlockwise
 digitalWrite(DirY, HIGH);
 digitalWrite(DirZ, HIGH);
 if (SensorVal() < 100) return;
 for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepX,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepX,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second
Serial.println("Turning Y");
for(int x = 0; x<1000; x++) { // loop for 200 steps
  digitalWrite(StepY,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepY,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepZ,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepZ,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

}

This source helped me when I realized using tuples in an Arduino environment will be hard.

FINAL_PROGRAM.ino

#include "sensor.h"
#include "serial.h"
#include "stepper.h"

void setup() {
  InitializeSerial();
  InitializeSensorCommunicationI2C();
}

void loop() {
  loopff();
}

sensor.h

#include <Wire.h>
#include "SparkFun_VL53L1X.h"

void InitializeSensorCommunicationI2C();
void loopff();

sensor.cpp

#include "sensor.h"

#define SHUTDOWN_PIN 2
#define INTERRUPT_PIN 3

SFEVL53L1X sensor;

void loopff() {
  sensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!sensor.checkForDataReady())
  {
    delay(1);
  }
  int distance = sensor.getDistance(); //Get the result of the measurement from the sensor
  sensor.clearInterrupt();
  sensor.stopRanging();

  Serial.print("Distance(mm): ");
  Serial.print(distance);

  float distanceInches = distance * 0.0393701;
  float distanceFeet = distanceInches / 12.0;

  Serial.print("\tDistance(ft): ");
  Serial.print(distanceFeet, 2);

  Serial.println();
}

void InitializeSensorCommunicationI2C() {
  Wire.begin();

  Serial.begin(115200);
  Serial.println("VL53L1X Qwiic Test");

  if (sensor.begin() != 0) //Begin returns 0 on a good init
  {
    Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor online!");
}

serial.h

void InitializeSerial();

serial.cpp

#include<Arduino.h>
#include "serial.h"

void InitializeSerial() {
  Serial.begin(115200);
}

stepper.h

void setupf();
void loopf();

stepper.cpp

#include "stepper.h"
#include "sensor.h"

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;



void setupf() {
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);



}

void loopf() {


 digitalWrite(DirX, HIGH); // set direction, HIGH for clockwise, LOW for anticlockwise
 digitalWrite(DirY, HIGH);
 digitalWrite(DirZ, HIGH);
 if (SensorVal() < 100) return;
 for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepX,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepX,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second
Serial.println("Turning Y");
for(int x = 0; x<1000; x++) { // loop for 200 steps
  digitalWrite(StepY,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepY,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

for(int x = 0; x<200; x++) { // loop for 200 steps
  digitalWrite(StepZ,HIGH);
  delayMicroseconds(500);
  digitalWrite(StepZ,LOW); 
  delayMicroseconds(500);
 }
delay(1000); // delay for 1 second

}

This source taught me that 200 steps is one revolution.

C:\Users\adams\AppData\Local\Temp\arduino-sketch-F69D2D2183674197C8C30C5B05CC1A93\sketch

img in snip

FINAL_PROGRAM.ino

#include "sensor.h"
#include "serial.h"
#include "stepper.h"
#include "logic_lib.h"

const int CALIBRATION_BUTTON_PIN = -1; // TO DO DO THIS //
const int START_BUTTON_PIN = -1; // TO DO DO THIS //
const bool SKIP_CALIBRATION = false; // TO DO DO THIS //
const bool DOWN_ROTATION_Z = true; // TO DO DO THIS //

int currentRotation;
int BOTTOM_ROTATION;
int TOP_ROTATION;

void setup() {
  InitializeSerial();
  InitializeSensorCommunicationI2C();
  InitializeSteppers();
  InitializeLogic(START_BUTTON_PIN);

  if (SKIP_CALIBRATION) return;

  Calibrate(CALIBRATION_BUTTON_PIN, DOWN_ROTATION_Z, BOTTOM_ROTATION, TOP_ROTATION, currentRotation);
}

void loop() {
  WaitForStartup(START_BUTTON_PIN, currentRotation, BOTTOM_ROTATION, TOP_ROTATION, DOWN_ROTATION_Z);
}



void Calibrate(int calibrationButtonPin, bool downRotation, int &_bottomRot, int &_topRot, int &_currentRot) {
  pinMode(calibrationButtonPin, INPUT); // CHECK AND SEE IF PULLUP OR PULLDOWN

  while (digitalRead(calibrationButtonPin) == LOW) { // assumes x and y are the two z motors and z is the wheel motor
    TurnXYOpposite(180, downRotation);
    delay(500);
  }

  int bottomRot = 0;
  int topRot;
  int currentRot = 0;

  while (digitalRead(calibrationButtonPin) == LOW) { // assumes x and y are the two z motors and z is the wheel motor
    TurnXYOpposite(180, !downRotation);
    currentRot += 180; // going up = positive angle
    delay(500);
  }

  topRot = currentRot;

  _bottomRot = bottomRot;
  _topRot = topRot;
  _currentRot = currentRot;
}

sensor.h

#include <Wire.h>
#include "SparkFun_VL53L1X.h"

void InitializeSensorCommunicationI2C();
void SensorVal(float&, float&, float&, bool); // mm, in, ft

sensor.cpp

#include "sensor.h"

SFEVL53L1X sensor;

/*
  Reading distance from the laser based VL53L1X
  By: Nathan Seidle
  SparkFun Electronics
  Date: April 4th, 2018
  License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

  SparkFun labored with love to create this code. Feel like supporting open source hardware?
  Buy a board from SparkFun! https://www.sparkfun.com/products/14667

  This example prints the distance to an object.

  Are you getting weird readings? Be sure the vacuum tape has been removed from the sensor.
*/


//Uncomment the following line to use the optional shutdown and interrupt pins.
//SFEVL53L1X distanceSensor(Wire, SHUTDOWN_PIN, INTERRUPT_PIN);

void SensorVal(float &mm, float &in, float &ft, bool forwardSensor) { // AS OF NOW FORWARD SENSOR DOESN'T DO ANYTHING
  sensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!sensor.checkForDataReady())
  {
    delay(1);
  }
  int distance = sensor.getDistance(); //Get the result of the measurement from the sensor
  sensor.clearInterrupt();
  sensor.stopRanging();

  float distanceMm = distance;

  float distanceInches = distance * 0.0393701;
  float distanceFeet = distanceInches / 12.0;

  mm = distanceMm;
  in = distanceInches;
  ft = distanceFeet;
}

void InitializeSensorCommunicationI2C() {
  Wire.begin();

  Serial.begin(115200);
  Serial.println("VL53L1X Qwiic Test");

  if (sensor.begin() != 0) //Begin returns 0 on a good init
  {
    Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor online!");
}

serial.h

void InitializeSerial();

serial.cpp

#include<Arduino.h>
#include "serial.h"

void InitializeSerial() {
  Serial.begin(115200);
}

stepper.h

void InitializeSteppers();
void TurnAxis(float, int, int, bool);
void TurnX(float, bool); // angle in degrees
void TurnY(float, bool); // angle in degrees
void TurnXYOpposite(float, bool);
void TurnZ(float, bool); // angle in degrees

stepper.cpp

#include "stepper.h"
#include "sensor.h"

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;



void InitializeSteppers() {
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);
}

void TurnAxis(float angle, int stepVar, int dirVar, bool clockwise) {
  auto s = clockwise ? HIGH : LOW;
  digitalWrite(dirVar, s);

  int steps = 200 * (angle / 360);

  for(int i = 0; i < steps; i++) { 
    digitalWrite(stepVar,HIGH);
    delayMicroseconds(500);
    digitalWrite(stepVar,LOW); 
    delayMicroseconds(500);
  }
}

void TurnX(float angle, bool clockwise) {TurnAxis(angle, StepX, DirX, clockwise);}

void TurnY(float angle, bool clockwise) {TurnAxis(angle, StepY, DirY, clockwise);}

void TurnXYOpposite(float angle, bool xClockwise) {
  digitalWrite(DirX, xClockwise);
  digitalWrite(DirY, !xClockwise);

  int steps = 200 * (angle / 360);

  for(int i = 0; i < steps; i++) { 
    digitalWrite(DirX,HIGH);
    digitalWrite(DirY,HIGH);
    delayMicroseconds(500);
    digitalWrite(DirX,LOW);
    digitalWrite(DirY,LOW); 
    delayMicroseconds(500);
  }
}

void TurnZ(float angle, bool clockwise) {TurnAxis(angle, StepZ, DirZ, clockwise);}

logic_lib.h

#include<Arduino.h>

enum RAG_POSITION {
    DOWN = 1,
    UP = 2
};

enum DIRECTION {
    FORWARD = true,
    BACK = false
};

void InitializeLogic(int);
void Calibrate();
void WaitForStartup(int, int&, int, int, bool);
bool StartCondition(int);
void SetRagPosition(int, int&, int, int, bool); // first is really RAG_POSITION but is an int
bool HitEdge(bool);
void MoveUntilEdge(bool);
void MainFlow(int&, int, int, bool);
void MoveToRot(int&, int, bool);

logic_lib.cpp

#include "logic_lib.h"
#include "stepper.h"
#include "sensor.h"

const DIRECTION CLEANING_DIRECTION = FORWARD;

void InitializeLogic(int startButtonPin) {
  pinMode(startButtonPin, INPUT); // TODO FIGURE THIS OUT INPUT TYPE FOR THE BUTTON
}

void WaitForStartup(int startButtonPin, int& currentRot, int bottomRot, int topRot, bool downRotation) {
  while (1) {
    if (StartCondition(startButtonPin)) {
      MainFlow(currentRot, bottomRot, topRot, downRotation);
    }
  }
}

bool StartCondition(int startButtonPin) {
  return (digitalRead(startButtonPin) == HIGH);
}

void SetRagPosition(RAG_POSITION ragPosition, int& currentRot, int bottomRot, int topRot, bool downRotation) {
  int target;
  if (ragPosition == DOWN) {
    target = bottomRot;
  }
  else {
    target = topRot;
  }

  MoveToRot(currentRot, target, downRotation);
}

void MoveToRot(int& currentRot, int targetRot, bool downRotation) {
  int difference = targetRot - currentRot; // positive difference = move down
  TurnXYOpposite(difference * (difference < 0 ? -1 : 1), (difference < 0) ? !downRotation : downRotation);
}

bool HitEdge(DIRECTION direction, int offTableThreshold) {
  int mm, _;
  SensorVal(mm, _, _, direction);
  return (mm >= offTableThreshold);
}

DIRECTION MoveUntilEdge(DIRECTION currentDir) {


    while (!HitEdge(currentDir)) {
      TurnZ(180, currentDir);
      delay(250);
    }

    return ((currentDir == FORWARD) ? BACK : FORWARD);
}

void MainFlow(int &currentRot, int bottomRot, int topRot, bool downRotation) {
  DIRECTION currentDirection = CLEANING_DIRECTION;
  SetRagPosition(DOWN, currentRot, bottomRot, topRot, downRotation);
  currentDirection = MoveUntilEdge(currentDirection);
  SetRagPosition(UP, currentRot, bottomRot, topRot, downRotation);
  MoveUntilEdge(currentDirection);
}

Next I started trying to use the Raspberry Pi Pico to control the second sensor and have it talk to the Uno. This image revealed taht the SpnEn pin on the CNC Shield is connected to pin D12 on the Arduino. Then I plugged in the Pico, opened the Blink program in the Arduino IDE (I decided I’ll use C++ for both microcontrollers because I was having trouble with the sensor and MicroPython earlier), changed the pin to 28, plugged an LED into GP28 (pin 34) and connected it through a resistor to ground. The upload failed, but I simply forgot to press the Bootloader button when plugging in my board. I then uploaded (I think I also originally had the wrong port since I had both the Uno and the Pico plugged into my computer), but it still didn’t work, as I realized the resistor was very high, so I plugged it directly into ground, which worked!

I first tried using this library, but this forum revealed that it won’t work because it relies on the Raw Pico SDK instead of Arduino.h.

Merritt also pointed out that we don’t need a constant line of communication between the Pico and the Uno - we can just send a stop signal when the sensor is near the edge.

I found this tutorial and decided that I’ll give MicroPython one more try, and if it doesn’t work, I’ll use C++ with CMake.

When I plugged the sensor into GPIO 17 and 16 and connected power and ground, I ran this code and it output 0x29!

import machine

# Create I2C object
i2c = machine.I2C(0, scl=machine.Pin(17), sda=machine.Pin(16))

# Print out any addresses found
devices = i2c.scan()

if devices:
    for d in devices:
        print(hex(d))
else:
    print("No devices found")

I give up. I can’t find the right registers to replace the registers in this example program for an accelerometer (I only knew to change ADXL343_ADDR from 0x53 to 0x29 because that’s what the i2c_scan.py program returned).

import machine
import utime
import ustruct
import sys

###############################################################################
# Constants

# I2C address
ADXL343_ADDR = 0x29

# Registers
REG_DEVID = 0x00
REG_POWER_CTL = 0x2D
REG_DATAX0 = 0x32

# Other constants
DEVID = 0xE5
SENSITIVITY_2G = 1.0 / 256  # (g/LSB)
EARTH_GRAVITY = 9.80665     # Earth's gravity in [m/s^2]

###############################################################################
# Settings

# Initialize I2C with pins
i2c = machine.I2C(0,
                  scl=machine.Pin(17),
                  sda=machine.Pin(16),
                  freq=400000)

devices = i2c.scan()

addrADDR = hex(devices[0])


###############################################################################
# Functions

def reg_write(i2c, addr, reg, data):
    """
    Write bytes to the specified register.
    """

    # Construct message
    msg = bytearray()
    msg.append(data)

    # Write out message to register
    i2c.writeto_mem(addr, reg, msg)

def reg_read(i2c, addr, reg, nbytes=1):
    """
    Read byte(s) from specified register. If nbytes > 1, read from consecutive
    registers.
    """

    # Check to make sure caller is asking for 1 or more bytes
    if nbytes < 1:
        return bytearray()
    print("starting to rest after writing")
    utime.sleep(1)
    print("done resting")
    # Request data from specified register(s) over I2C
    data = i2c.readfrom_mem(addr, reg, nbytes)

    return data

###############################################################################
# Main

# Read device ID to make sure that we can communicate with the ADXL343
data = reg_read(i2c, ADXL343_ADDR, REG_DEVID)
if (data != bytearray((DEVID,))):
    print("ERROR: Could not communicate with ADXL343")
    sys.exit()

# Read Power Control register
data = reg_read(i2c, ADXL343_ADDR, REG_POWER_CTL)
print(data)

# Tell ADXL343 to start taking measurements by setting Measure bit to high
data = int.from_bytes(data, "big") | (1 << 3)
reg_write(i2c, ADXL343_ADDR, REG_POWER_CTL, data)

# Test: read Power Control register back to make sure Measure bit was set
data = reg_read(i2c, ADXL343_ADDR, REG_POWER_CTL)
print(data)

# Wait before taking measurements
utime.sleep(2.0)

# Run forever
while True:

    # Read X, Y, and Z values from registers (16 bits each)
    data = reg_read(i2c, ADXL343_ADDR, REG_DATAX0, 6)

    # Convert 2 bytes (little-endian) into 16-bit integer (signed)
    acc_x = ustruct.unpack_from("<h", data, 0)[0]
    acc_y = ustruct.unpack_from("<h", data, 2)[0]
    acc_z = ustruct.unpack_from("<h", data, 4)[0]

    # Convert measurements to [m/s^2]
    acc_x = acc_x * SENSITIVITY_2G * EARTH_GRAVITY
    acc_y = acc_y * SENSITIVITY_2G * EARTH_GRAVITY
    acc_z = acc_z * SENSITIVITY_2G * EARTH_GRAVITY

    # Print results
    print("X:", "{:.2f}".format(acc_x), \
          "| Y:", "{:.2f}".format(acc_y), \
          "| Z:", "{:.2f}".format(acc_z))

    utime.sleep(0.1)

I went through this tutorial again, but I got an error.

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "PiicoDev_VL53L1X.py", line 111, in _init_
  File "PiicoDev_VL53L1X.py", line 135, in reset
  File "PiicoDev_VL53L1X.py", line 124, in writeReg
OSError: [Errno 5] EIO

Desperately, I looked to the comments where I found someone with the same error!

comments

But I couldn’t find they’re forum post. Instead, I went to the link and navigated to the forum where I searched for the error, and I found this discussion which revealed that THERE IS A PROBLEM WITH THE NEWEST VERSION OF MICROPYTHON AND I NEED TO INSTALL 1.18! When I did this in Thonny, the sensor worked perfectly!

Working Sensor

I tried replicating all of this on the Pico W (I had switched to the normal one), but noticed that in Thonny I can’t install any version below 1.19, so I’ll stick to the normal Pico for now.

Here’s PiicoDev_Unified.py

'''
PiicoDev.py: Unifies I2C drivers for different builds of MicroPython
Changelog:
    - 2022-10-13 P.Johnston Add helptext to run i2csetup script on Raspberry Pi 
    - 2022-10-14 M.Ruppe Explicitly set default I2C initialisation parameters for machine-class (Raspberry Pi Pico + W)
    - 2023-01-31 L.Howell Add minimal support for ESP32
'''
import os
_SYSNAME = os.uname().sysname
compat_ind = 1
i2c_err_str = 'PiicoDev could not communicate with module at address 0x{:02X}, check wiring'
setupi2c_str = ', run "sudo curl -L https://piico.dev/i2csetup | bash". Suppress this warning by setting suppress_warnings=True'

if _SYSNAME == 'microbit':
    from microbit import i2c
    from utime import sleep_ms

elif _SYSNAME == 'Linux':
    from smbus2 import SMBus, i2c_msg
    from time import sleep
    from math import ceil

    def sleep_ms(t):
        sleep(t/1000)

else:
    from machine import I2C, Pin
    from utime import sleep_ms

class I2CBase:
    def writeto_mem(self, addr, memaddr, buf, *, addrsize=8):
        raise NotImplementedError('writeto_mem')

    def readfrom_mem(self, addr, memaddr, nbytes, *, addrsize=8):
        raise NotImplementedError('readfrom_mem')

    def write8(self, addr, buf, stop=True):
        raise NotImplementedError('write')

    def read16(self, addr, nbytes, stop=True):
        raise NotImplementedError('read')

    def __init__(self, bus=None, freq=None, sda=None, scl=None):
        raise NotImplementedError('__init__')

class I2CUnifiedMachine(I2CBase):
    def __init__(self, bus=None, freq=None, sda=None, scl=None):
        if bus is not None and freq is not None and sda is not None and scl is not None:
            print('Using supplied freq, sda and scl to create machine I2C')
            self.i2c = I2C(bus, freq=freq, sda=sda, scl=scl)
        elif _SYSNAME == 'esp32' and (bus is None and freq is None and sda is None and scl is None):
            raise Exception('Please input bus, frequency, machine.pin SDA and SCL objects to use ESP32')
        else:
            self.i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=100000)

        self.writeto_mem = self.i2c.writeto_mem
        self.readfrom_mem = self.i2c.readfrom_mem

    def write8(self, addr, reg, data):
        if reg is None:
            self.i2c.writeto(addr, data)
        else:
            self.i2c.writeto(addr, reg + data)

    def read16(self, addr, reg):
        self.i2c.writeto(addr, reg, False)
        return self.i2c.readfrom(addr, 2)

class I2CUnifiedMicroBit(I2CBase):
    def __init__(self, freq=None):
        if freq is not None:
            print('Initialising I2C freq to {}'.format(freq))
            microbit.i2c.init(freq=freq)

    def writeto_mem(self, addr, memaddr, buf, *, addrsize=8):
        ad = memaddr.to_bytes(addrsize // 8, 'big')  # pad address for eg. 16 bit
        i2c.write(addr, ad + buf)

    def readfrom_mem(self, addr, memaddr, nbytes, *, addrsize=8):
        ad = memaddr.to_bytes(addrsize // 8, 'big')  # pad address for eg. 16 bit
        i2c.write(addr, ad, repeat=True)
        return i2c.read(addr, nbytes)    

    def write8(self, addr, reg, data):
        if reg is None:
            i2c.write(addr, data)
        else:
            i2c.write(addr, reg + data)

    def read16(self, addr, reg):
        i2c.write(addr, reg, repeat=True)
        return i2c.read(addr, 2)

class I2CUnifiedLinux(I2CBase):
    def __init__(self, bus=None, suppress_warnings=True):
        if suppress_warnings == False:
            with open('/boot/config.txt') as config_file:
                if 'dtparam=i2c_arm=on' in config_file.read():
                    pass
                else:
                    print('I2C is not enabled. To enable' + setupi2c_str)
                config_file.close()
            with open('/boot/config.txt') as config_file:
                if 'dtparam=i2c_arm_baudrate=400000' in config_file.read():
                    pass
                else:
                    print('Slow baudrate detected. If glitching occurs' + setupi2c_str)
                config_file.close()
        if bus is None:
            bus = 1
        self.i2c = SMBus(bus)

    def readfrom_mem(self, addr, memaddr, nbytes, *, addrsize=8):
        data = [None] * nbytes # initialise empty list
        self.smbus_i2c_read(addr, memaddr, data, nbytes, addrsize=addrsize)
        return data

    def writeto_mem(self, addr, memaddr, buf, *, addrsize=8):
        self.smbus_i2c_write(addr, memaddr, buf, len(buf), addrsize=addrsize)

    def smbus_i2c_write(self, address, reg, data_p, length, addrsize=8):
        ret_val = 0
        data = []
        for index in range(length):
            data.append(data_p[index])
        if addrsize == 8:
            msg_w = i2c_msg.write(address, [reg] + data)
        elif addrsize == 16:
            msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff] + data)
        else:
            raise Exception('address must be 8 or 16 bits long only')
        self.i2c.i2c_rdwr(msg_w)
        return ret_val

    def smbus_i2c_read(self, address, reg, data_p, length, addrsize=8):
        ret_val = 0
        if addrsize == 8:
            msg_w = i2c_msg.write(address, [reg]) # warning this is set up for 16-bit addresses
        elif addrsize == 16:
            msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff]) # warning this is set up for 16-bit addresses
        else:
            raise Exception('address must be 8 or 16 bits long only')
        msg_r = i2c_msg.read(address, length)
        self.i2c.i2c_rdwr(msg_w, msg_r)
        if ret_val == 0:
            for index in range(length):
                data_p[index] = ord(msg_r.buf[index])
        return ret_val

    def write8(self, addr, reg, data):
        if reg is None:
            d = int.from_bytes(data, 'big')
            self.i2c.write_byte(addr, d)
        else:
            r = int.from_bytes(reg, 'big')
            d = int.from_bytes(data, 'big')
            self.i2c.write_byte_data(addr, r, d)

    def read16(self, addr, reg):
        regInt = int.from_bytes(reg, 'big')
        return self.i2c.read_word_data(addr, regInt).to_bytes(2, byteorder='little', signed=False)

def create_unified_i2c(bus=None, freq=None, sda=None, scl=None, suppress_warnings=True):
    if _SYSNAME == 'microbit':
        i2c = I2CUnifiedMicroBit(freq=freq)
    elif _SYSNAME == 'Linux':
        i2c = I2CUnifiedLinux(bus=bus, suppress_warnings=suppress_warnings)
    else:
        i2c = I2CUnifiedMachine(bus=bus, freq=freq, sda=sda, scl=scl)
    return i2c

PiicoDev_VL53L1X.py

from PiicoDev_Unified import *

compat_str = '\nUnified PiicoDev library out of date.  Get the latest module: https://piico.dev/unified \n'

VL51L1X_DEFAULT_CONFIGURATION = bytes([
0x00, # 0x2d : set bit 2 and 5 to 1 for fast plus mode (1MHz I2C), else don't touch */
0x00, # 0x2e : bit 0 if I2C pulled up at 1.8V, else set bit 0 to 1 (pull up at AVDD) */
0x00, # 0x2f : bit 0 if GPIO pulled up at 1.8V, else set bit 0 to 1 (pull up at AVDD) */
0x01, # 0x30 : set bit 4 to 0 for active high interrupt and 1 for active low (bits 3:0 must be 0x1), use SetInterruptPolarity() */
0x02, # 0x31 : bit 1 = interrupt depending on the polarity, use CheckForDataReady() */
0x00, # 0x32 : not user-modifiable (NUM)*/
0x02, # 0x33 : NUM */
0x08, # 0x34 : NUM */
0x00, # 0x35 : NUM */
0x08, # 0x36 : NUM */
0x10, # 0x37 : NUM */
0x01, # 0x38 : NUM */
0x01, # 0x39 : NUM */
0x00, # 0x3a : NUM */
0x00, # 0x3b : NUM */
0x00, # 0x3c : NUM */
0x00, # 0x3d : NUM */
0xff, # 0x3e : NUM */
0x00, # 0x3f : NUM */
0x0F, # 0x40 : NUM */
0x00, # 0x41 : NUM */
0x00, # 0x42 : NUM */
0x00, # 0x43 : NUM */
0x00, # 0x44 : NUM */
0x00, # 0x45 : NUM */
0x20, # 0x46 : interrupt configuration 0->level low detection, 1-> level high, 2-> Out of window, 3->In window, 0x20-> New sample ready , TBC */
0x0b, # 0x47 : NUM */
0x00, # 0x48 : NUM */
0x00, # 0x49 : NUM */
0x02, # 0x4a : NUM */
0x0a, # 0x4b : NUM */
0x21, # 0x4c : NUM */
0x00, # 0x4d : NUM */
0x00, # 0x4e : NUM */
0x05, # 0x4f : NUM */
0x00, # 0x50 : NUM */
0x00, # 0x51 : NUM */
0x00, # 0x52 : NUM */
0x00, # 0x53 : NUM */
0xc8, # 0x54 : NUM */
0x00, # 0x55 : NUM */
0x00, # 0x56 : NUM */
0x38, # 0x57 : NUM */
0xff, # 0x58 : NUM */
0x01, # 0x59 : NUM */
0x00, # 0x5a : NUM */
0x08, # 0x5b : NUM */
0x00, # 0x5c : NUM */
0x00, # 0x5d : NUM */
0x01, # 0x5e : NUM */
0xdb, # 0x5f : NUM */
0x0f, # 0x60 : NUM */
0x01, # 0x61 : NUM */
0xf1, # 0x62 : NUM */
0x0d, # 0x63 : NUM */
0x01, # 0x64 : Sigma threshold MSB (mm in 14.2 format for MSB+LSB), use SetSigmaThreshold(), default value 90 mm  */
0x68, # 0x65 : Sigma threshold LSB */
0x00, # 0x66 : Min count Rate MSB (MCPS in 9.7 format for MSB+LSB), use SetSignalThreshold() */
0x80, # 0x67 : Min count Rate LSB */
0x08, # 0x68 : NUM */
0xb8, # 0x69 : NUM */
0x00, # 0x6a : NUM */
0x00, # 0x6b : NUM */
0x00, # 0x6c : Intermeasurement period MSB, 32 bits register, use SetIntermeasurementInMs() */
0x00, # 0x6d : Intermeasurement period */
0x0f, # 0x6e : Intermeasurement period */
0x89, # 0x6f : Intermeasurement period LSB */
0x00, # 0x70 : NUM */
0x00, # 0x71 : NUM */
0x00, # 0x72 : distance threshold high MSB (in mm, MSB+LSB), use SetD:tanceThreshold() */
0x00, # 0x73 : distance threshold high LSB */
0x00, # 0x74 : distance threshold low MSB ( in mm, MSB+LSB), use SetD:tanceThreshold() */
0x00, # 0x75 : distance threshold low LSB */
0x00, # 0x76 : NUM */
0x01, # 0x77 : NUM */
0x0f, # 0x78 : NUM */
0x0d, # 0x79 : NUM */
0x0e, # 0x7a : NUM */
0x0e, # 0x7b : NUM */
0x00, # 0x7c : NUM */
0x00, # 0x7d : NUM */
0x02, # 0x7e : NUM */
0xc7, # 0x7f : ROI center, use SetROI() */
0xff, # 0x80 : XY ROI (X=Width, Y=Height), use SetROI() */
0x9B, # 0x81 : NUM */
0x00, # 0x82 : NUM */
0x00, # 0x83 : NUM */
0x00, # 0x84 : NUM */
0x01, # 0x85 : NUM */
0x01, # 0x86 : clear interrupt, use ClearInterrupt() */
0x40  # 0x87 : start ranging, use StartRanging() or StopRanging(), If you want an automatic start after VL53L1X_init() call, put 0x40 in location 0x87 */
])


class PiicoDev_VL53L1X:
    def __init__(self, bus=None, freq=None, sda=None, scl=None, address=0x29):
        try:
            if compat_ind >= 1:
                pass
            else:
                print(compat_str)
        except:
            print(compat_str)
        self.i2c = create_unified_i2c(bus=bus, freq=freq, sda=sda, scl=scl)
        self.addr = address
        self.reset()
        sleep_ms(1)
        if self.read_model_id() != 0xEACC:
            raise RuntimeError('Failed to find expected ID register values. Check wiring!')
        # write default configuration
        self.i2c.writeto_mem(self.addr, 0x2D, VL51L1X_DEFAULT_CONFIGURATION, addrsize=16)
        sleep_ms(100)
        # the API triggers this change in VL53L1_init_and_start_range() once a
        # measurement is started; assumes MM1 and MM2 are disabled
        self.writeReg16Bit(0x001E, self.readReg16Bit(0x0022) * 4)
        sleep_ms(200)

    def writeReg(self, reg, value):
        return self.i2c.writeto_mem(self.addr, reg, bytes([value]), addrsize=16)
    def writeReg16Bit(self, reg, value):
        return self.i2c.writeto_mem(self.addr, reg, bytes([(value >> 8) & 0xFF, value & 0xFF]), addrsize=16)
    def readReg(self, reg):
        return self.i2c.readfrom_mem(self.addr, reg, 1, addrsize=16)[0]
    def readReg16Bit(self, reg):
        data = self.i2c.readfrom_mem(self.addr, reg, 2, addrsize=16)
        return (data[0]<<8) + data[1]
    def read_model_id(self):
        return self.readReg16Bit(0x010F) 
    def reset(self):
        self.writeReg(0x0000, 0x00)
        sleep_ms(100)
        self.writeReg(0x0000, 0x01)
    def read(self):
        try:
            data = self.i2c.readfrom_mem(self.addr, 0x0089, 17, addrsize=16) # RESULT__RANGE_STATUS
        except:
            print(i2c_err_str.format(self.addr))
            return float('NaN')
        range_status = data[0]
        # report_status = data[1]
        stream_count = data[2]
        dss_actual_effective_spads_sd0 = (data[3]<<8) + data[4]
        # peak_signal_count_rate_mcps_sd0 = (data[5]<<8) + data[6]
        ambient_count_rate_mcps_sd0 = (data[7]<<8) + data[8]
        # sigma_sd0 = (data[9]<<8) + data[10]
        # phase_sd0 = (data[11]<<8) + data[12]
        final_crosstalk_corrected_range_mm_sd0 = (data[13]<<8) + data[14]
        peak_signal_count_rate_crosstalk_corrected_mcps_sd0 = (data[15]<<8) + data[16]
        #status = None
        #if range_status in (17, 2, 1, 3):
            #status = "HardwareFail"
        #elif range_status == 13:
            #status = "MinRangeFail"
        #elif range_status == 18:
            #status = "SynchronizationInt"
        #elif range_status == 5:
            #status = "OutOfBoundsFail"
        #elif range_status == 4:
            #status = "SignalFail"
        #elif range_status == 6:
            #status = "SignalFail"
        #elif range_status == 7:
            #status = "WrapTargetFail"
        #elif range_status == 12:
            #status = "XtalkSignalFail"
        #elif range_status == 8:
            #status = "RangeValidMinRangeClipped"
        #elif range_status == 9:
            #if stream_count == 0:
                #status = "RangeValidNoWrapCheckFail"
            #else:
                #status = "OK"
        return final_crosstalk_corrected_range_mm_sd0

    def change_addr(self, new_addr):
        self.writeReg(0x0001, new_addr & 0x7F)
        sleep_ms(50)
        self.addr = new_addr

main.py

from PiicoDev_VL53L1X import PiicoDev_VL53L1X
from time import sleep

distSensor = PiicoDev_VL53L1X()

output_pin = machine.Pin(28, machine.Pin.OUT)

while True:
    dist = distSensor.read() # read the distance in millimetres
    print(str(dist) + " mm") # convert the number to a string and print
    sleep(0.1)

Next I changed main.py to the following.

from PiicoDev_VL53L1X import PiicoDev_VL53L1X
from time import sleep

distSensor = PiicoDev_VL53L1X()

output_pin = machine.Pin(28, machine.Pin.OUT)

while True:
    dist = distSensor.read() # read the distance in millimetres
    print(str(dist) + " mm") # convert the number to a string and print
    sleep(0.1)

    if (dist > 100):
        output_pin.value(1)
    else:
        output_pin.value(0)

I connected GPIO28 to SpnEn (D12) on the Uno and ran this program on the Uno.

void setup() {
  // put your setup code here, to run once:
pinMode(12, INPUT);
Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(digitalRead(12));
}

And it worked! Whenever I lift up the sensor the Arduino reads 1 and when it’s facing the table it reads 0.

![video]

Working Wiring:

SCL on sensor - 12 on pico (GPIO 9, I2C SCL) SDA on sensor - 11 on pico (GPIO 8, I2C SDA) Ground on sensor - Ground on Pico - Ground on Arduino VBUS on Pico - Power on sensor SpnEn (D12) on Arduino - 34 on pico (GPIO 28)

I realized there may be a problem if this pin is used when the stepper motors are running.

ALL FINAL WIRING

ALL FINAL CODE

FINAL_PROGRAM.ino (pin 7 - I was looking on the pinout sheet for extra I/O pins but I noticied when I used it I couldn’t control one of the axes’ rotation - I’ll mess with this in a bit)

#include "sensor.h"
#include "serial.h"
#include "stepper.h"
#include "logic_lib.h"

const int CALIBRATION_BUTTON_PIN = 7; // TO DO DO THIS //// FIX FIX FIX NOT 7 BUT WIRING WRONG
const int START_BUTTON_PIN = 13; // TO DO DO THIS //
const bool SKIP_CALIBRATION = false; // TO DO DO THIS //
const bool DOWN_ROTATION_Z = false; // TO DO DO THIS //

int currentRotation;
int BOTTOM_ROTATION;
int TOP_ROTATION;

void setup() {
  InitializeSerial();
  InitializeSensorCommunicationI2C();
  InitializeSteppers();
  InitializeLogic(START_BUTTON_PIN);

  TurnX(100000, true);

  if (SKIP_CALIBRATION) return;

  Serial.println("Press START to initialize calibration");
  while (!digitalRead(START_BUTTON_PIN));
  Serial.println("Calibrating -- press CALIBRATION when the Z-axis is in the right position");
  Calibrate(CALIBRATION_BUTTON_PIN, DOWN_ROTATION_Z, BOTTOM_ROTATION, TOP_ROTATION, currentRotation);
  Serial.print("Current rot is ");
  Serial.println(currentRotation);
  Serial.print("Bottom rot is ");
  Serial.println(BOTTOM_ROTATION);
  Serial.print("Top rot is ");
  Serial.println(TOP_ROTATION);
  Serial.println("Calibration complete -- press START to begin");
}

void loop() {
  WaitForStartup(START_BUTTON_PIN, currentRotation, BOTTOM_ROTATION, TOP_ROTATION, DOWN_ROTATION_Z);
}



void Calibrate(int calibrationButtonPin, bool downRotation, int &_bottomRot, int &_topRot, int &_currentRot) {
  pinMode(calibrationButtonPin, INPUT); // CHECK AND SEE IF PULLUP OR PULLDOWN

  int bottomRot = 0;
  int topRot;
  int currentRot = 0;

  while (digitalRead(calibrationButtonPin) == LOW) { // assumes x and y are the two z motors and z is the wheel motor
    TurnXYOpposite(180, !downRotation);
    //TurnY(180, !downRotation);
    currentRot += 180; // going up = positive angle
    Serial.println(currentRot);
    //delay(1/*500*/);
  }

  bottomRot = currentRot;

  _bottomRot = bottomRot;
  _topRot = topRot;
  _currentRot = currentRot;
}

sensor.h

#include <Wire.h>
#include "SparkFun_VL53L1X.h"

void InitializeSensorCommunicationI2C();
void SensorVal(float&, float&, float&, bool); // mm, in, ft
//void HitEdge(bool forward);

sensor.cpp

#include "sensor.h"

SFEVL53L1X sensor;

/*
  Reading distance from the laser based VL53L1X
  By: Nathan Seidle
  SparkFun Electronics
  Date: April 4th, 2018
  License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

  SparkFun labored with love to create this code. Feel like supporting open source hardware?
  Buy a board from SparkFun! https://www.sparkfun.com/products/14667

  This example prints the distance to an object.

  Are you getting weird readings? Be sure the vacuum tape has been removed from the sensor.
*/


//Uncomment the following line to use the optional shutdown and interrupt pins.
//SFEVL53L1X distanceSensor(Wire, SHUTDOWN_PIN, INTERRUPT_PIN);

void SensorVal(float &mm, float &in, float &ft, bool forwardSensor) { // AS OF NOW FORWARD SENSOR DOESN'T DO ANYTHING
  sensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!sensor.checkForDataReady())
  {
    delay(1);
  }
  int distance = sensor.getDistance(); //Get the result of the measurement from the sensor
  sensor.clearInterrupt();
  sensor.stopRanging();

  float distanceMm = distance;

  float distanceInches = distance * 0.0393701;
  float distanceFeet = distanceInches / 12.0;

  mm = distanceMm;
  in = distanceInches;
  ft = distanceFeet;
}

/*bool HitEdge(bool forward) {
  float mm, __;
  SensorVal(mm, __, __, forward);
  return mm > 100;
}*/

void InitializeSensorCommunicationI2C() {
  Wire.begin();

  Serial.begin(115200);
  Serial.println("VL53L1X Qwiic Test");

  if (sensor.begin() != 0) //Begin returns 0 on a good init
  {
    Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor online!");
}

serial.h

void InitializeSerial();

*serial.cpp

#include<Arduino.h>
#include "serial.h"

void InitializeSerial() {
  Serial.begin(115200);
}

stepper.h

void InitializeSteppers();
void TurnAxis(float, int, int, bool);
void TurnX(float, bool); // angle in degrees
void TurnY(float, bool); // angle in degrees
void TurnXYOpposite(float, bool);
void TurnZ(float, bool); // angle in degrees

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;

stepper.cpp

#include "stepper.h"
#include "sensor.h"



void InitializeSteppers() {
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);
}

void TurnAxis(float angle, int stepVar, int dirVar, bool clockwise) {
  auto s = clockwise ? HIGH : LOW;
  digitalWrite(dirVar, s);

  int steps = 200 * (angle / 360);

  for(int i = 0; i < steps; i++) { 
    digitalWrite(stepVar,HIGH);
    delayMicroseconds(500);
    digitalWrite(stepVar,LOW); 
    delayMicroseconds(500);
  }
}

void TurnX(float angle, bool clockwise) {TurnAxis(angle, StepX, DirX, clockwise);}

void TurnY(float angle, bool clockwise) {TurnAxis(angle, StepY, DirY, clockwise);}

void TurnXYOpposite(float angle, bool xClockwise) {
  digitalWrite(DirX, xClockwise);
  digitalWrite(DirY, !xClockwise);

  int steps = 200 * (angle / 360);

  for(int i = 0; i < steps; i++) { 
    digitalWrite(StepX,HIGH);
    digitalWrite(StepY,HIGH);
    delayMicroseconds(500);
    digitalWrite(StepX,LOW);
    digitalWrite(StepY,LOW); 
    delayMicroseconds(500);
  }
}

void TurnZ(float angle, bool clockwise) {TurnAxis(angle, StepZ, DirZ, clockwise);}

logic_lib.h

#include<Arduino.h>

const int offTableThreshold = 100;
const int OUTSIDE_SENSOR_INDICATOR_PIN = 12;

enum RAG_POSITION {
    DOWN = 1,
    UP = 2
};

enum DIRECTION {
    FORWARD = true,
    BACK = false
};

void InitializeLogic(int);
void Calibrate();
void WaitForStartup(int, int&, int, int, bool);
bool StartCondition(int);
void SetRagPosition(int, int&, int, int, bool); // first is really RAG_POSITION but is an int
bool HitEdge(bool);
void MoveUntilEdge(bool);
void MainFlow(int&, int, int, bool);
void MoveToRot(int&, int, bool);

logic_lib.cpp

#include "logic_lib.h"
#include "stepper.h"
#include "sensor.h"

const DIRECTION CLEANING_DIRECTION = FORWARD;

void InitializeLogic(int startButtonPin) {
  pinMode(startButtonPin, INPUT); // TODO FIGURE THIS OUT INPUT TYPE FOR THE BUTTON
  pinMode(OUTSIDE_SENSOR_INDICATOR_PIN, INPUT);
}

void WaitForStartup(int startButtonPin, int& currentRot, int bottomRot, int topRot, bool downRotation) {
  while (1) {
    if (StartCondition(startButtonPin)) {
      Serial.println("Starting");
      MainFlow(currentRot, bottomRot, topRot, downRotation);
    }
  }
}

bool StartCondition(int startButtonPin) {
  return (digitalRead(startButtonPin) == HIGH);
}

void SetRagPosition(RAG_POSITION ragPosition, int& currentRot, int bottomRot, int topRot, bool downRotation) {
  int target;
  if (ragPosition == DOWN) {
    target = bottomRot;
  }
  else {
    target = topRot;
  }

  MoveToRot(currentRot, target, downRotation);
}

void MoveToRot(int& currentRot, int targetRot, bool downRotation) {
  Serial.print("Moving ");
  int difference = targetRot - currentRot; // positive difference = move down
  Serial.println(difference);
  for (int i = 0; i < targetRot * (targetRot < 0 ? -1 : 1); i += 180) {
    TurnXYOpposite(180, (difference > 0) ? !downRotation : downRotation);
  }

}

bool HitEdge(DIRECTION direction) {
  if (direction == BACK) return (digitalRead(OUTSIDE_SENSOR_INDICATOR_PIN) == HIGH);

  float mm, _;

  SensorVal(mm, _, _, direction);
  return (mm >= offTableThreshold);
}

DIRECTION MoveUntilEdge(DIRECTION currentDir) {


    while (!HitEdge(currentDir)) {
      TurnZ(180, currentDir);
      delay(250);
    }

    return ((currentDir == FORWARD) ? BACK : FORWARD);
}

void MainFlow(int &currentRot, int bottomRot, int topRot, bool downRotation) {
  DIRECTION currentDirection = CLEANING_DIRECTION;
  SetRagPosition(DOWN, currentRot, bottomRot, topRot, downRotation);
  currentDirection = MoveUntilEdge(currentDirection);
  SetRagPosition(UP, currentRot, bottomRot, topRot, downRotation);
  MoveUntilEdge(currentDirection);
}

I also noticed sometimes I got the Erno/OS error when the I2C pins weren’t wired properly or the current wasn’t running to the sensor, or when there was an input to the VSYS while the program was being run via USBC.

Making electronics

I found this EagleCAD component, even though it’s not the same I have, I think it’ll work.

schematic

This source taught me I can type ripup into the command line and select All which will remove all routes.

layout

![imgs]

Testing Leadscrew

Running the motor with the leadscrew didn’t work at first, but it turns out it was simply a cable, and when I switched it out, it worked.

I also realized that I had to change the code to where instead of TurnXYOpposite(1890, true) where 1890 is just an example for the calibrated rotation to a loop of turning 180 degrees many times, or else the motor would turn less than it was supposed to. You can see these changes in the final version of the code.

I finalized the code, here it is:

FINAL_PROGRAM.ino

#include "sensor.h"
#include "serial.h"
#include "stepper.h"
#include "logic_lib.h"

const int CALIBRATION_BUTTON_PIN = 13;
const int START_BUTTON_PIN = 12;
const bool SKIP_CALIBRATION = false; 
const bool DOWN_ROTATION_Z = false; 

int currentRotation;
int BOTTOM_ROTATION;
int TOP_ROTATION;

void setup() {
  InitializeSerial();
  InitializeSensorCommunicationI2C();
  InitializeSteppers();
  InitializeLogic(START_BUTTON_PIN);

  if (SKIP_CALIBRATION) return;

  Serial.println("Press START to initialize calibration");
  while (!digitalRead(START_BUTTON_PIN));
  Serial.println("Calibrating -- press CALIBRATION when the Z-axis is in the right position");
  Calibrate(CALIBRATION_BUTTON_PIN, DOWN_ROTATION_Z, BOTTOM_ROTATION, TOP_ROTATION, currentRotation);
  Serial.print("Current rot is ");
  Serial.println(currentRotation);
  Serial.print("Bottom rot is ");
  Serial.println(BOTTOM_ROTATION);
  Serial.print("Top rot is ");
  Serial.println(TOP_ROTATION);
  Serial.println("Calibration complete -- press START to begin");
}

void loop() {
  WaitForStartup(START_BUTTON_PIN, currentRotation, BOTTOM_ROTATION, TOP_ROTATION, DOWN_ROTATION_Z);
}



void Calibrate(int calibrationButtonPin, bool downRotation, int &_bottomRot, int &_topRot, int &_currentRot) {
  pinMode(calibrationButtonPin, INPUT); // CHECK AND SEE IF PULLUP OR PULLDOWN

  int bottomRot = 0;
  int topRot;
  int currentRot = 0;

  while (digitalRead(calibrationButtonPin) == LOW) { // assumes x and y are the two z motors and z is the wheel motor
    TurnXYOpposite(180, !downRotation);
    //TurnY(180, !downRotation);
    currentRot += 180; // going up = positive angle
    Serial.println(currentRot);
    //delay(1/*500*/);
  }

  bottomRot = currentRot;

  _bottomRot = bottomRot;
  _topRot = topRot;
  _currentRot = currentRot;
}

sensor.h

#include <Wire.h>
#include "SparkFun_VL53L1X.h"

void InitializeSensorCommunicationI2C();
void SensorVal(float&, float&, float&, bool); // mm, in, ft
//void HitEdge(bool forward);

sensor.cpp

#include "sensor.h"

SFEVL53L1X sensor;

/*
  Reading distance from the laser based VL53L1X
  By: Nathan Seidle
  SparkFun Electronics
  Date: April 4th, 2018
  License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

  SparkFun labored with love to create this code. Feel like supporting open source hardware?
  Buy a board from SparkFun! https://www.sparkfun.com/products/14667

  This example prints the distance to an object.

  Are you getting weird readings? Be sure the vacuum tape has been removed from the sensor.
*/


//Uncomment the following line to use the optional shutdown and interrupt pins.
//SFEVL53L1X distanceSensor(Wire, SHUTDOWN_PIN, INTERRUPT_PIN);

void SensorVal(float &mm, float &in, float &ft, bool forwardSensor) { // AS OF NOW FORWARD SENSOR DOESN'T DO ANYTHING
  sensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!sensor.checkForDataReady())
  {
    delay(1);
  }
  int distance = sensor.getDistance(); //Get the result of the measurement from the sensor
  sensor.clearInterrupt();
  sensor.stopRanging();

  float distanceMm = distance;

  float distanceInches = distance * 0.0393701;
  float distanceFeet = distanceInches / 12.0;

  mm = distanceMm;
  in = distanceInches;
  ft = distanceFeet;
}

/*bool HitEdge(bool forward) {
  float mm, __;
  SensorVal(mm, __, __, forward);
  return mm > 100;
}*/

void InitializeSensorCommunicationI2C() {
  Wire.begin();

  Serial.begin(115200);
  Serial.println("VL53L1X Qwiic Test");

  if (sensor.begin() != 0) //Begin returns 0 on a good init
  {
    Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
    while (1)
      ;
  }
  Serial.println("Sensor online!");
}

serial.h

void InitializeSerial();

serial.cpp

#include<Arduino.h>
#include "serial.h"

void InitializeSerial() {
  Serial.begin(115200);
}

stepper.h

void InitializeSteppers();
void TurnAxis(float, int, int, bool);
void TurnX(float, bool); // angle in degrees
void TurnY(float, bool); // angle in degrees
void TurnXYOpposite(float, bool);
void TurnZ(float, bool); // angle in degrees

const int StepX = 2;
const int DirX = 5;
const int StepY = 3;
const int DirY = 6;
const int StepZ = 4;
const int DirZ = 7;

stepper.cpp

#include "stepper.h"
#include "sensor.h"



void InitializeSteppers() {
  pinMode(StepX,OUTPUT);
  pinMode(DirX,OUTPUT);
  pinMode(StepY,OUTPUT);
  pinMode(DirY,OUTPUT);
  pinMode(StepZ,OUTPUT);
  pinMode( DirZ,OUTPUT);
}

void TurnAxis(float angle, int stepVar, int dirVar, bool clockwise) {
  auto s = clockwise ? HIGH : LOW;
  digitalWrite(dirVar, s);

  int steps = 200 * (angle / 360);

  for(int i = 0; i < steps; i++) { 
    digitalWrite(stepVar,HIGH);
    delayMicroseconds(500);
    digitalWrite(stepVar,LOW); 
    delayMicroseconds(500);
  }
}

void TurnX(float angle, bool clockwise) {TurnAxis(angle, StepX, DirX, clockwise);}

void TurnY(float angle, bool clockwise) {TurnAxis(angle, StepY, DirY, clockwise);}

void TurnXYOpposite(float angle, bool xClockwise) {
  digitalWrite(DirX, xClockwise);
  digitalWrite(DirY, /*!*/xClockwise);

  int steps = 200 * (angle / 360);

  for(int i = 0; i < steps; i++) { 
    digitalWrite(StepX,HIGH);
    digitalWrite(StepY,HIGH);
    delayMicroseconds(500);
    digitalWrite(StepX,LOW);
    digitalWrite(StepY,LOW); 
    delayMicroseconds(500);
  }
}

void TurnZ(float angle, bool clockwise) {TurnAxis(angle, StepZ, DirZ, clockwise);}

logic_lib.h

#include<Arduino.h>

const int offTableThreshold = 100;
const int OUTSIDE_SENSOR_INDICATOR_PIN = A3;

enum RAG_POSITION {
    DOWN = 1,
    UP = 2
};

enum DIRECTION {
    FORWARD = true,
    BACK = false
};

void InitializeLogic(int);
void Calibrate();
void WaitForStartup(int, int&, int, int, bool);
bool StartCondition(int);
void SetRagPosition(int, int&, int, int, bool); // first is really RAG_POSITION but is an int
bool HitEdge(bool);
void MoveUntilEdge(bool);
void MainFlow(int&, int, int, bool);
void MoveToRot(int&, int, bool);

logic_lib.cpp

#include "logic_lib.h"
#include "stepper.h"
#include "sensor.h"

const DIRECTION CLEANING_DIRECTION = FORWARD;

void InitializeLogic(int startButtonPin) {
  pinMode(startButtonPin, INPUT); // TODO FIGURE THIS OUT INPUT TYPE FOR THE BUTTON
  pinMode(OUTSIDE_SENSOR_INDICATOR_PIN, INPUT);
}

void WaitForStartup(int startButtonPin, int& currentRot, int bottomRot, int topRot, bool downRotation) {
  while (1) {
    if (StartCondition(startButtonPin)) {
      Serial.println("Starting");
      MainFlow(currentRot, bottomRot, topRot, downRotation);
    }
  }
}

bool StartCondition(int startButtonPin) {
  return (digitalRead(startButtonPin) == HIGH);
}

void SetRagPosition(RAG_POSITION ragPosition, int& currentRot, int bottomRot, int topRot, bool downRotation) {
  int target;
  if (ragPosition == DOWN) {
    target = topRot; // check if flipped
  }
  else {
    target = bottomRot; // check if flipped
  }

  MoveToRot(currentRot, target, downRotation);
}

void MoveToRot(int& currentRot, int targetRot, bool downRotation) {
  Serial.print("Moving ");
  int difference = targetRot - currentRot; // positive difference = move down
  Serial.println(difference);
  for (int i = 0; i < targetRot * (targetRot < 0 ? -1 : 1); i += 180) {
    TurnXYOpposite(180, (difference > 0) ? !downRotation : downRotation);
  }

}

bool HitEdge(DIRECTION direction) {
  if (direction == BACK) return (digitalRead(OUTSIDE_SENSOR_INDICATOR_PIN) == HIGH);

  float mm, _;

  SensorVal(mm, _, _, direction);
  return (mm >= offTableThreshold);
}

DIRECTION MoveUntilEdge(DIRECTION currentDir) {

    while (!HitEdge(currentDir)) {
      TurnZ(180, currentDir);
      delay(0.5);
    }

    return ((currentDir == FORWARD) ? BACK : FORWARD);
}

void MainFlow(int &currentRot, int bottomRot, int topRot, bool downRotation) {
  DIRECTION currentDirection = CLEANING_DIRECTION;
  SetRagPosition(DOWN, currentRot, bottomRot, topRot, downRotation);
  Serial.println("moving until edge...");
  currentDirection = MoveUntilEdge(currentDirection);
  SetRagPosition(UP, currentRot, bottomRot, topRot, downRotation);
  MoveUntilEdge(currentDirection);
}

ON PICO:

main.py

from machine import Pin
from PiicoDev_VL53L1X import PiicoDev_VL53L1X
from time import sleep
from utime import sleep

led = Pin(25, Pin.OUT)

for i in range(6):
    led.toggle()
    sleep(1)

for i in range(6):
    led.toggle()
    sleep(0.5)





distSensor = PiicoDev_VL53L1X()

output_pin = machine.Pin(28, machine.Pin.OUT)

b = False

while True:
    dist = distSensor.read() # read the distance in millimetres
    print(str(dist) + " mm") # convert the number to a string and print
    sleep(0.1)



    if (dist > 100):
        if not (b == True):
            led.toggle()
            b = not b
        output_pin.value(1)
    else:
        if b:
            led.toggle()
            b = not b
        output_pin.value(0)

PiicoDev_VL53L1X.py

from PiicoDev_Unified import *

compat_str = '\nUnified PiicoDev library out of date.  Get the latest module: https://piico.dev/unified \n'

VL51L1X_DEFAULT_CONFIGURATION = bytes([
0x00, # 0x2d : set bit 2 and 5 to 1 for fast plus mode (1MHz I2C), else don't touch */
0x00, # 0x2e : bit 0 if I2C pulled up at 1.8V, else set bit 0 to 1 (pull up at AVDD) */
0x00, # 0x2f : bit 0 if GPIO pulled up at 1.8V, else set bit 0 to 1 (pull up at AVDD) */
0x01, # 0x30 : set bit 4 to 0 for active high interrupt and 1 for active low (bits 3:0 must be 0x1), use SetInterruptPolarity() */
0x02, # 0x31 : bit 1 = interrupt depending on the polarity, use CheckForDataReady() */
0x00, # 0x32 : not user-modifiable (NUM)*/
0x02, # 0x33 : NUM */
0x08, # 0x34 : NUM */
0x00, # 0x35 : NUM */
0x08, # 0x36 : NUM */
0x10, # 0x37 : NUM */
0x01, # 0x38 : NUM */
0x01, # 0x39 : NUM */
0x00, # 0x3a : NUM */
0x00, # 0x3b : NUM */
0x00, # 0x3c : NUM */
0x00, # 0x3d : NUM */
0xff, # 0x3e : NUM */
0x00, # 0x3f : NUM */
0x0F, # 0x40 : NUM */
0x00, # 0x41 : NUM */
0x00, # 0x42 : NUM */
0x00, # 0x43 : NUM */
0x00, # 0x44 : NUM */
0x00, # 0x45 : NUM */
0x20, # 0x46 : interrupt configuration 0->level low detection, 1-> level high, 2-> Out of window, 3->In window, 0x20-> New sample ready , TBC */
0x0b, # 0x47 : NUM */
0x00, # 0x48 : NUM */
0x00, # 0x49 : NUM */
0x02, # 0x4a : NUM */
0x0a, # 0x4b : NUM */
0x21, # 0x4c : NUM */
0x00, # 0x4d : NUM */
0x00, # 0x4e : NUM */
0x05, # 0x4f : NUM */
0x00, # 0x50 : NUM */
0x00, # 0x51 : NUM */
0x00, # 0x52 : NUM */
0x00, # 0x53 : NUM */
0xc8, # 0x54 : NUM */
0x00, # 0x55 : NUM */
0x00, # 0x56 : NUM */
0x38, # 0x57 : NUM */
0xff, # 0x58 : NUM */
0x01, # 0x59 : NUM */
0x00, # 0x5a : NUM */
0x08, # 0x5b : NUM */
0x00, # 0x5c : NUM */
0x00, # 0x5d : NUM */
0x01, # 0x5e : NUM */
0xdb, # 0x5f : NUM */
0x0f, # 0x60 : NUM */
0x01, # 0x61 : NUM */
0xf1, # 0x62 : NUM */
0x0d, # 0x63 : NUM */
0x01, # 0x64 : Sigma threshold MSB (mm in 14.2 format for MSB+LSB), use SetSigmaThreshold(), default value 90 mm  */
0x68, # 0x65 : Sigma threshold LSB */
0x00, # 0x66 : Min count Rate MSB (MCPS in 9.7 format for MSB+LSB), use SetSignalThreshold() */
0x80, # 0x67 : Min count Rate LSB */
0x08, # 0x68 : NUM */
0xb8, # 0x69 : NUM */
0x00, # 0x6a : NUM */
0x00, # 0x6b : NUM */
0x00, # 0x6c : Intermeasurement period MSB, 32 bits register, use SetIntermeasurementInMs() */
0x00, # 0x6d : Intermeasurement period */
0x0f, # 0x6e : Intermeasurement period */
0x89, # 0x6f : Intermeasurement period LSB */
0x00, # 0x70 : NUM */
0x00, # 0x71 : NUM */
0x00, # 0x72 : distance threshold high MSB (in mm, MSB+LSB), use SetD:tanceThreshold() */
0x00, # 0x73 : distance threshold high LSB */
0x00, # 0x74 : distance threshold low MSB ( in mm, MSB+LSB), use SetD:tanceThreshold() */
0x00, # 0x75 : distance threshold low LSB */
0x00, # 0x76 : NUM */
0x01, # 0x77 : NUM */
0x0f, # 0x78 : NUM */
0x0d, # 0x79 : NUM */
0x0e, # 0x7a : NUM */
0x0e, # 0x7b : NUM */
0x00, # 0x7c : NUM */
0x00, # 0x7d : NUM */
0x02, # 0x7e : NUM */
0xc7, # 0x7f : ROI center, use SetROI() */
0xff, # 0x80 : XY ROI (X=Width, Y=Height), use SetROI() */
0x9B, # 0x81 : NUM */
0x00, # 0x82 : NUM */
0x00, # 0x83 : NUM */
0x00, # 0x84 : NUM */
0x01, # 0x85 : NUM */
0x01, # 0x86 : clear interrupt, use ClearInterrupt() */
0x40  # 0x87 : start ranging, use StartRanging() or StopRanging(), If you want an automatic start after VL53L1X_init() call, put 0x40 in location 0x87 */
])


class PiicoDev_VL53L1X:
    def __init__(self, bus=None, freq=None, sda=None, scl=None, address=0x29):
        try:
            if compat_ind >= 1:
                pass
            else:
                print(compat_str)
        except:
            print(compat_str)
        self.i2c = create_unified_i2c(bus=bus, freq=freq, sda=sda, scl=scl)
        self.addr = address
        self.reset()
        sleep_ms(1000)
        if self.read_model_id() != 0xEACC:
            raise RuntimeError('Failed to find expected ID register values. Check wiring!')
        # write default configuration
        self.i2c.writeto_mem(self.addr, 0x2D, VL51L1X_DEFAULT_CONFIGURATION, addrsize=16)
        sleep_ms(100)
        # the API triggers this change in VL53L1_init_and_start_range() once a
        # measurement is started; assumes MM1 and MM2 are disabled
        self.writeReg16Bit(0x001E, self.readReg16Bit(0x0022) * 4)
        sleep_ms(200)

    def writeReg(self, reg, value):
        return self.i2c.writeto_mem(self.addr, reg, bytes([value]), addrsize=16)
    def writeReg16Bit(self, reg, value):
        return self.i2c.writeto_mem(self.addr, reg, bytes([(value >> 8) & 0xFF, value & 0xFF]), addrsize=16)
    def readReg(self, reg):
        return self.i2c.readfrom_mem(self.addr, reg, 1, addrsize=16)[0]
    def readReg16Bit(self, reg):
        data = self.i2c.readfrom_mem(self.addr, reg, 2, addrsize=16)
        return (data[0]<<8) + data[1]
    def read_model_id(self):
        return self.readReg16Bit(0x010F) 
    def reset(self):
        self.writeReg(0x0000, 0x00)
        sleep_ms(100)
        self.writeReg(0x0000, 0x01)
    def read(self):
        try:
            data = self.i2c.readfrom_mem(self.addr, 0x0089, 17, addrsize=16) # RESULT__RANGE_STATUS
        except:
            print(i2c_err_str.format(self.addr))
            return float('NaN')
        range_status = data[0]
        # report_status = data[1]
        stream_count = data[2]
        dss_actual_effective_spads_sd0 = (data[3]<<8) + data[4]
        # peak_signal_count_rate_mcps_sd0 = (data[5]<<8) + data[6]
        ambient_count_rate_mcps_sd0 = (data[7]<<8) + data[8]
        # sigma_sd0 = (data[9]<<8) + data[10]
        # phase_sd0 = (data[11]<<8) + data[12]
        final_crosstalk_corrected_range_mm_sd0 = (data[13]<<8) + data[14]
        peak_signal_count_rate_crosstalk_corrected_mcps_sd0 = (data[15]<<8) + data[16]
        #status = None
        #if range_status in (17, 2, 1, 3):
            #status = "HardwareFail"
        #elif range_status == 13:
            #status = "MinRangeFail"
        #elif range_status == 18:
            #status = "SynchronizationInt"
        #elif range_status == 5:
            #status = "OutOfBoundsFail"
        #elif range_status == 4:
            #status = "SignalFail"
        #elif range_status == 6:
            #status = "SignalFail"
        #elif range_status == 7:
            #status = "WrapTargetFail"
        #elif range_status == 12:
            #status = "XtalkSignalFail"
        #elif range_status == 8:
            #status = "RangeValidMinRangeClipped"
        #elif range_status == 9:
            #if stream_count == 0:
                #status = "RangeValidNoWrapCheckFail"
            #else:
                #status = "OK"
        return final_crosstalk_corrected_range_mm_sd0

    def change_addr(self, new_addr):
        self.writeReg(0x0001, new_addr & 0x7F)
        sleep_ms(50)
        self.addr = new_addr

PiicoDev_Unified.py

'''
PiicoDev.py: Unifies I2C drivers for different builds of MicroPython
Changelog:
    - 2022-10-13 P.Johnston Add helptext to run i2csetup script on Raspberry Pi 
    - 2022-10-14 M.Ruppe Explicitly set default I2C initialisation parameters for machine-class (Raspberry Pi Pico + W)
    - 2023-01-31 L.Howell Add minimal support for ESP32
'''
import os
_SYSNAME = os.uname().sysname
compat_ind = 1
i2c_err_str = 'PiicoDev could not communicate with module at address 0x{:02X}, check wiring'
setupi2c_str = ', run "sudo curl -L https://piico.dev/i2csetup | bash". Suppress this warning by setting suppress_warnings=True'

if _SYSNAME == 'microbit':
    from microbit import i2c
    from utime import sleep_ms

elif _SYSNAME == 'Linux':
    from smbus2 import SMBus, i2c_msg
    from time import sleep
    from math import ceil

    def sleep_ms(t):
        sleep(t/1000)

else:
    from machine import I2C, Pin
    from utime import sleep_ms

class I2CBase:
    def writeto_mem(self, addr, memaddr, buf, *, addrsize=8):
        raise NotImplementedError('writeto_mem')

    def readfrom_mem(self, addr, memaddr, nbytes, *, addrsize=8):
        raise NotImplementedError('readfrom_mem')

    def write8(self, addr, buf, stop=True):
        raise NotImplementedError('write')

    def read16(self, addr, nbytes, stop=True):
        raise NotImplementedError('read')

    def __init__(self, bus=None, freq=None, sda=None, scl=None):
        raise NotImplementedError('__init__')

class I2CUnifiedMachine(I2CBase):
    def __init__(self, bus=None, freq=None, sda=None, scl=None):
        if bus is not None and freq is not None and sda is not None and scl is not None:
            print('Using supplied freq, sda and scl to create machine I2C')
            self.i2c = I2C(bus, freq=freq, sda=sda, scl=scl)
        elif _SYSNAME == 'esp32' and (bus is None and freq is None and sda is None and scl is None):
            raise Exception('Please input bus, frequency, machine.pin SDA and SCL objects to use ESP32')
        else:
            self.i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=100000)

        self.writeto_mem = self.i2c.writeto_mem
        self.readfrom_mem = self.i2c.readfrom_mem

    def write8(self, addr, reg, data):
        if reg is None:
            self.i2c.writeto(addr, data)
        else:
            self.i2c.writeto(addr, reg + data)

    def read16(self, addr, reg):
        self.i2c.writeto(addr, reg, False)
        return self.i2c.readfrom(addr, 2)

class I2CUnifiedMicroBit(I2CBase):
    def __init__(self, freq=None):
        if freq is not None:
            print('Initialising I2C freq to {}'.format(freq))
            microbit.i2c.init(freq=freq)

    def writeto_mem(self, addr, memaddr, buf, *, addrsize=8):
        ad = memaddr.to_bytes(addrsize // 8, 'big')  # pad address for eg. 16 bit
        i2c.write(addr, ad + buf)

    def readfrom_mem(self, addr, memaddr, nbytes, *, addrsize=8):
        ad = memaddr.to_bytes(addrsize // 8, 'big')  # pad address for eg. 16 bit
        i2c.write(addr, ad, repeat=True)
        return i2c.read(addr, nbytes)    

    def write8(self, addr, reg, data):
        if reg is None:
            i2c.write(addr, data)
        else:
            i2c.write(addr, reg + data)

    def read16(self, addr, reg):
        i2c.write(addr, reg, repeat=True)
        return i2c.read(addr, 2)

class I2CUnifiedLinux(I2CBase):
    def __init__(self, bus=None, suppress_warnings=True):
        if suppress_warnings == False:
            with open('/boot/config.txt') as config_file:
                if 'dtparam=i2c_arm=on' in config_file.read():
                    pass
                else:
                    print('I2C is not enabled. To enable' + setupi2c_str)
                config_file.close()
            with open('/boot/config.txt') as config_file:
                if 'dtparam=i2c_arm_baudrate=400000' in config_file.read():
                    pass
                else:
                    print('Slow baudrate detected. If glitching occurs' + setupi2c_str)
                config_file.close()
        if bus is None:
            bus = 1
        self.i2c = SMBus(bus)

    def readfrom_mem(self, addr, memaddr, nbytes, *, addrsize=8):
        data = [None] * nbytes # initialise empty list
        self.smbus_i2c_read(addr, memaddr, data, nbytes, addrsize=addrsize)
        return data

    def writeto_mem(self, addr, memaddr, buf, *, addrsize=8):
        self.smbus_i2c_write(addr, memaddr, buf, len(buf), addrsize=addrsize)

    def smbus_i2c_write(self, address, reg, data_p, length, addrsize=8):
        ret_val = 0
        data = []
        for index in range(length):
            data.append(data_p[index])
        if addrsize == 8:
            msg_w = i2c_msg.write(address, [reg] + data)
        elif addrsize == 16:
            msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff] + data)
        else:
            raise Exception('address must be 8 or 16 bits long only')
        self.i2c.i2c_rdwr(msg_w)
        return ret_val

    def smbus_i2c_read(self, address, reg, data_p, length, addrsize=8):
        ret_val = 0
        if addrsize == 8:
            msg_w = i2c_msg.write(address, [reg]) # warning this is set up for 16-bit addresses
        elif addrsize == 16:
            msg_w = i2c_msg.write(address, [reg >> 8, reg & 0xff]) # warning this is set up for 16-bit addresses
        else:
            raise Exception('address must be 8 or 16 bits long only')
        msg_r = i2c_msg.read(address, length)
        self.i2c.i2c_rdwr(msg_w, msg_r)
        if ret_val == 0:
            for index in range(length):
                data_p[index] = ord(msg_r.buf[index])
        return ret_val

    def write8(self, addr, reg, data):
        if reg is None:
            d = int.from_bytes(data, 'big')
            self.i2c.write_byte(addr, d)
        else:
            r = int.from_bytes(reg, 'big')
            d = int.from_bytes(data, 'big')
            self.i2c.write_byte_data(addr, r, d)

    def read16(self, addr, reg):
        regInt = int.from_bytes(reg, 'big')
        return self.i2c.read_word_data(addr, regInt).to_bytes(2, byteorder='little', signed=False)

def create_unified_i2c(bus=None, freq=None, sda=None, scl=None, suppress_warnings=True):
    if _SYSNAME == 'microbit':
        i2c = I2CUnifiedMicroBit(freq=freq)
    elif _SYSNAME == 'Linux':
        i2c = I2CUnifiedLinux(bus=bus, suppress_warnings=suppress_warnings)
    else:
        i2c = I2CUnifiedMachine(bus=bus, freq=freq, sda=sda, scl=scl)
    return i2c

Then I had a lot of trouble with the OSError shown before when trying to get the milled board with a pico to work. I spent a long time resoldering, desoldering, bug fixing, until we eventually decided to use a breadboard for the moment. We got everything working on the breadboard with a 9V battery plugged into the Arduino (which this source confirmed is optimal), and I tried using a 15.3VDC transformer. When I was screwing this in to the CNC Shield, I saw a spark and immediately sparked. I realized I didn’t unplug the transformer and was very scared that I had fried the electronics. I went back to the old power supply, and it didn’t work! Oh gosh! I used a different arduino and chord and it would get hot still, so I unplugged everything, got another Arduino, and just plugged in the SDA and SCL pins with one sensor, and I kept getting the OSError and couldn’t get it to connect! I realized that I simply wasn’t running power to the sensor, and when I fixed this, it worked! I hooked everything back up again and it all worked! (I eventually decided to use D12, D13, and A3 for the start button pin, calibration button pin, and communication from the Raspberry Pi Pico, respectively). We also decided to use the power supply with long jumper wires that we stripped and soldered to male connectors.

Also sometimes I got the previous error where it wouldn’t write success or failure in the serial monitor and it wold just write that it failed when I unplugged it. This turned out to be an error with the wiring.

Making the Video

Merritt made the video in Adobe Premiere Pro and didn’t run into any issues.

Designing the squeegee sled - Griffin

Our group’s initial project Idea was to create a table cleaning robot. I initially theorized that we should use a robot that had a rag on a servo that would move up and down the table to clean it. This Idea was quickly shut down, as it was more complicated than it needed to be. The next Idea that was suggested was to have a spinning rag on a dowel to move. Before we tested it all we drew a bunch of designs for how everyhting would work machine. There would be a rack and pinion to drive the roller up and down, and wheels would be on the side of the table to move the machine back and forth. Eventually after designing and brainstorming, we decided to not use the rack and pinion for Z movement, but rather 2 lead screws on each side of the mechanism. We measured the width of the cafeteria tables at our school(the tables we wanted to clean), and took that into consideration for our design. I was in charge of creating the sweeping mechanism, so I used the gear tool to create a gear on the end of the dowel, also making everything a 1:2 gear ratio, in order to give the motors more torque. The motor we planned on using was a DC motor, but then later, Mr. Dubick told us we should’ve tested everything before designing. When we tested it, the motor didn’t have enough torque, and it still probably would’nt have worked with a 1:2 ratio. So we then decided to go down a different avenue for what we needed. Our group decided upon using a squeegee in the back to just pull off the food. I designed a new design now, completely redoing everthing previously done. For this next design I found two squeegees we would use for the project that would encompass the whole table. I designed everything around that. This next design was much more simple than the previous itteration.

Assembly Process

All that needed to have was the mounting brackets for the squeegee poles, as well as two pieces on the sides that would be used to move the contraption up and down. My plan was to cnc the board, and print the pieces, heat sink them with screws, and then screw them into the cnc’d board. I designed the mounting pieces with reguards to head sinking, so the screws would be easy to put in. I had to wait until we recieved our lead screw stepper motors to begin the design on the contraption to move the machine up and down. I took inspiration from our regular sized prusa printers, and used a contraption similar to that when designing them. The first two prints I did didn’t work, because the caliper I was using was not precise enough for me to get a good measurement. I found a better caliper and got better measurements slightly offset by 1/100th of an inch for clearance. I also designed the holes for the board, which Jed and I laser cut and glued sheets together. The holes overlapped so I used a drill to drill out the holes and they fit with the design. Putting everything together was very frustrating as we had an issue with the guide rails. The guide rails couldn’t be cut, because they were hardened steel, and we didn’t have anything else to use so we had to adjust the rest of our setup to compensate. Drilling through the T-bar was extremely difficult and we had many issues with it for whatever reason. Eventually we got it cut and moved to assembly. Putting everything together was a relatively simple task, but everything needed to harmonize well in order for the project to work. We got everything together and it worked (after a few modifications)!

Griffin’s Site includes more photos

Framing/Movement System - Ryan

Ryan worked on the physical part that would be mounted onto the table.

Wheels

In a group discussion with the mechanical making people, we decided to use a specific type of small wheel that we normally use for legos, for the rubber on that would be the best of use in case of something that rolls across a table. Also, this way, I could specify the distance between the wheels rather than using the three wheels normally used for carriages. I measured the diameter of the inside of the wheels, and made an inside part for the wheel that could be connected to the stepper motor. I printed one piece out, and to my surprise, it fit super fit and nicely. I stored this file so I could print one more later, since we had two wheels connected to the motors on top of the table.

Alt text

As for the wheels on the side and bottom, I had to figure out a way to make them roll but also not make the wheels come off. Since the rubber part of the wheels are flexible, I figured if I made a part where there were two squares on each side big enough to block the middle part that would go inside the rubber part of the wheels, but also make the squares small enough so I could assemble the rubber part of the wheel on it, then I could make it roll without falling out. I designed a prototype of this, estimating a good clearance between the rolling part and the rod that was connected to the squares.

Alt text Alt text I printed both, and both turned out well. I moved onto making an actual casing for the final product.

Laser Cut Wood PIllars

As I mentioned, I created separate components for everything, but I built off the components from other components. In this case, since the pillars housing the z-lead-screw of the stepper motor would have to fit tightly into the wheel case, I decided to create it on the edges of the wheel case, just like it should be in the final product.

Alt text

For the pillars on each side housing the z-axis controlling motors, we decided to laser cut a box to be housed with the stepper motor in the 3D design mentioned previously. I created tabs and used careful measurements and parameters to get an accurately measured design. I figured it would be best to screw these on along with the stepper motor box along with the wheels, so that it was stronger and didn’t fall off.

Jed first laser cutted this as a test on cardboard, which fit. We then tested it on wood, which also fit.

Alt text

Because I later figured out that the measurements of the height must be changed, I had to go back later on to change the size and fit.

Prototyping and Gluing- Jed

More images and files on Jed’s.

After helping with the CAD I worked with Ryan and Griffin on creating laser cutting files. I laser cut them and glued them together to help prototype as well as eventually creating the final cut for our machine. The way Ryan made the CAD files, everything is separate bodies so we were able to move each sketch of the shaft onto a new file and plane and we exported the file as a pdf to open in corel draw. After I opened it in Corel Draw I realized I needed to set the cut hairline.

I started with a single cardboard shaft to make sure the sizing works with the steppers in Ryan’s holder that he 3D printed. To put the cardboard together I hot glued it together so I could quickly give it to Ryan.

Once that worked Ryan gave me a pdf that removed unessesary parts of the carboard cut, this time I cut with 1/8th inch plywood, and since it wasn’t for the final version and to save time I taped the wood cut together. Once we knew that worked I cut the file again two more times and this time I used wood glue to hold it all together.

Connecting Joints

To connect the pillars and the horizontal extrusion which would house the electronics, wires, and others, I made a connecting piece to snugly fit the pillars, and an extrusion at the top where I could fit the horizontal beam. I used an extrusion file that Mr. Budzichowski provided me. On the extrusion going across the table, I planned on having a couple of extra wheels for stability(at the same level as the squeegee board dragging behind) and maybe a place that can hold a spray bottle. One issue was the more weight I put on the top, the more power it would take for the stepper drivers to turn the wheels, which could be a problem. Therefore, I figured I would need more support, such as the extra wheels that touch the table and prove stability in the middle, taking pressure off of the sides.

Alt text

Redesign Issues

Throughout the two weeks, I had lots of run-ins to aspects that messed up the measurements. For example, my first print of the motor casings was too big for the table. I shrunk that down to fit tightly on the table.

Another major issue was that the motor holding the wheel did not have a case, therefore it couldn’t rest on the table.

I was frustrated, and tried to think of another way without reprinting the huge print, since that would put a whole lot of filament to waste. I went back into my design and tried to think of any other way, and I came up with the idea of extending the part that went over the stepper motor controlling the Z-axis. If I extended it over, and extruded it down a little, then it could hold the stepper motor. Also, to make it even more secure, I decided to leave holes for screws. After taking the measurements into consideration using a caliper, I extended the part to hold both stepper motors.

Alt text

Before the change, there wasn’t enough room for the guide rails

Alt text

Another issue was the height of the pillars. The Nema Motor with the lead screws turned out to be taller than I thought. Our order for the Nema Stepper motors with the lead screws came in pretty late. When they finally did come in, I came to the frustrating understanding that the lead screw went over the laser cut. I didn’t plan on redesigning the laser part, for all I needed to do was create an extrusion in the bottom of the connecting joint that would be used to join the laser cut part and the aluminum extrusion going across. This actually ended up coming out as a good thing for me, for it allowed me to realize a couple of other flaws with my design that I could fix. First, the guide rails were thick and heavy, and therefore just holding it at the bottom could cause some problems. Therefore, along with the extrusion for the lead screw, I also created an extrusion at the top where I could tightly fit the guide rail, so the guide rails would be more secure on both sides.

Fusion 360 Frame Design

Assembly Of All Parts - Jed, Griffin, Ryan

Side Part/Motors/Wheel Assembly

With the laser cut pillars Ryan had designed and Jed had cut/glued, Ryan was able to assemble it inside the casing with the cables, along with the second motor turning the wheels with the new casing that would hold it in place with screws.

Alt text

Ryan had designed the part with the screws to hold the motors on both sides, so he got 10mm m3 nuts and screwed the part on. There was also an area that required hot glue due to a slight instability. With Adam, Ryan tested the motor spinning, and simulated the weight of everything else with his hand, in which the stepper motor turned the wheel and moved.

Alt text Alt text Alt text

The troubles of the assemnby are briefly mentioned prior, but again, we had trouble with the guide rails. Unable to cut them, we had to drill a hole for them to go through the top. We used the drill press with a big bit, which lucky for us was almost perfectly the size we needed it to be.

Alt text Alt text

Final

Afterwords, the assmbly team had to work with the electronics team. All of the CAD stuff had been produced, but it still needed to work. It would be a pain to unscrew and remove the stepper motors, so we put them in their correct spots, making sure the cables could come out, and put the remaining pieces together. First, the two edges were completed and screwed. We then connected the top part with screws to the aluminum extrusion, which was strong enough to support from both sides. We then realized we had to put on the z-axis movement system, so we removed the top and stuck the wood going across that would be holding the squeegee. This part had to be careful due to possibility of the lead screws messing up the z-axis movement. Once the hex nut, lead screw, and guide rails were in their space, we put the top on again, screwing the aluminum extrusion back on, holding everything together.

We rolled it onto the table, resting it on the supports, put the squeegee on, and the electronics team pressed the respective buttons. First, the z-axis moved down and stopped when the squeegee touched. Then, the moment of truth. We pressed the second button…

AND IT MOVED!

We put some legos in the way the way to see if the squeegee dragging along could pick them up, and it did. It was officially a mountable machine that moved across a table, cleaning up necessary crumbs. (Legos in our case) :)

Alt text


Last update: May 8, 2023