Goal: Build a 3D scanner.

  • Explore Arduino, Stepper Motors, Sensors, Math, and CAD

  • How Should I read a report?

  • How should I implement math to tune my data

  • What should I do with a 3D scanner?

  • What limitations do I have?

Day 1: Analog distance sensing

What is Analog vs Digital?

Analog Sensing:

  • Produces continuous data that varies smoothly over (T)ime.
  • NO DATA DELAY
  • Directly reflects the data:
    • Output voltage/current changes proportionally with the measured quantity.

However, since for most digital systems, it can only understand 1 or 0, or True / False. It needs a convertor.

1
Requires analog-to-digital conversion for digital systems

Digital Sensing:

  • Produces discrete signals. BINARY
  • Output is quantized into steps
  • Yet, contain some latency.

How does Binary system work?

Normally, if we look at 1234.5 we can dissect it into:
1234.5 = 1000 + 200 + 30 + 4 + 0.5.

Now in BINARY each position represent a power of 2.

Position 2⁷ 2⁶ 2⁵ 2⁴ 2⁰
Value 128 64 32 16 8 4 2 1

Now Let try to Convert 1234 into Binary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1234:
1234 ÷ 2 = 617 remainder 0
617 ÷ 2 = 308 remainder 1
308 ÷ 2 = 154 remainder 0
154 ÷ 2 = 77 remainder 0
77 ÷ 2 = 38 remainder 1
38 ÷ 2 = 19 remainder 0
19 ÷ 2 = 9 remainder 1
9 ÷ 2 = 4 remainder 1
4 ÷ 2 = 2 remainder 0
2 ÷ 2 = 1 remainder 0
1 ÷ 2 = 0 remainder 1

Now read the remainders from bottom to top:

1234 in binary = 10011010010

1
2
3
Let Convert 11,111,111 into decimal:
= 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
= 255 (decimal)

Arduino Analog Set-Up:

I am referencing these websites:

Arduino Analog

Sharp Analog Sensor

SpeedyStepper

A4988_DMOS

CNC Shield

ATmega

Video Explaining Stepper Motor:

Arduino Docs

Based on Arduino Docs:

An Arduino UNO, for example, contains a multichannel, 10-bit analog to digital converter (ADC). This means that it will map input voltages between 0 and the operating voltage (+5 VDC) into integer values between 0 and 1023. This yields a resolution between readings of: 5 volts / 1024 units or 0.0049 volts (4.9 mV) per unit.

The voltage input range can be changed using analogReference(). The default analogRead() resolution on Arduino boards is set to 10 bits, for compatibility. You need to use analogReadResolution() to change it to a higher resolution.

Why does it matter?

What does “10-bit” mean?

  • 10-bit means you have 10 binary digits (bits)

  • The smallest value: 0000000000 = 0

  • The largest value: 1111111111 = 1023

  • Total possible values: 2^10 = 1024 values (0 through 1023)

ADC:

The Arduino can take an analog voltage and convert it to a digital number.

YET:
It uses 10BITS to convert this number, and it can only map “volatges between 0~5Volts”.

So:

  • If you have 2.5 V, you get 1000000000, or 512 in decimal.
  • Digital Value = (Input Voltage / 5V) × 1023

HOWEVER:
LIMITATIONS:

It cannot be as detailed as you want it to be.
In order to find the base unit, we do:

5V ÷ 1024 = 0.00488V ≈ 4.9mV

This means the Arduino cannot distinguish between 2.500V and 2.504V—they both read as the same digital value

Sample Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
int analogPin = A3; // potentiometer wiper (middle terminal) connected to analog pin 3
// outside leads to ground and VCC
int val = 0; // variable to store the value read

void setup() {
Serial.begin(9600); // setup serial
}

void loop() {
val = analogRead(analogPin); // read the input pin
Serial.println(val); // debug value
delay(200);
}

RAW ANALOG to VOLATGE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Constants
const float V_REF = 5.0; // Analog reference voltage (e.g., 5V or 3.3V)
const float R_BITS = 10.0; // ADC resolution (bits)
const float ADC_STEPS = (1 << int(R_BITS)) - 1; // Number of steps (2^R_BITS - 1)

const int potentiometerPin = A3; // Potentiometer wiper connected to analog pin A3

void setup() {
Serial.begin(9600); // Initialize serial communication
Serial.println(ADC_STEPS);
}

void loop() {
int rawValue = analogRead(potentiometerPin); // Read the analog input
float voltage = (rawValue / ADC_STEPS) * V_REF; // Convert to voltage

Serial.print("Voltage: ");
Serial.print(voltage, 3); // Print voltage with 3 decimal places
Serial.println(" V");

delay(200); // Small delay to avoid flooding the serial monitor
}

Day 2 SHARP Sensors:

So Before You connect the Sensors/ANYTHING Directly:
Remember:

Servo Wire Common Color Connects To
Signal Yellow / White / Orange Arduino digital pin
VCC / +5V Red Arduino 5V pin (or external 5V supply)
GND Brown / Black Arduino GND

Sharp Sensor

Scanner Data

Observe these two Photos:

GP2Y0A21YK0F is an infrared (IR) distance sensor that:

  • Measures distances from 10 to 80 cm
  • Outputs an analog voltage (not digital)
  • Uses IR triangulation

What is IR Triangulation?

Well, if you don’t know it, you 're in big trouble, that’s why our data never worked.
According to Claude:

1
2
3
4
5
6
7
8
9
An IR LED emits a beam of infrared light.

That light reflects off a target (wall, object, hand, etc.).

The reflected light hits a position-sensitive detector (PSD) or a linear sensor chip.

The angle of reflection changes with distance.

The sensor converts that angle into a distance reading.
1
Emitter → Object → Detector

So, This is what we came up in class:

Set Up

And Here are some interpretation of the Graph:

Graph

And here is my code Derived from the data.
Notice how the data starts at 10cm and ends at 80cm.

Before I show you the code:
I need to explain what is INTERPOLATION:

Interpolation: is estimating unknown values between known data points by assuming a straight line between them.

The math is here:

1
distance = d1 + (voltage - v1) * (d2 - d1) / (v2 - v1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Calibration version - measure at known distances

const int sensorPin = A0;

// Define your calibration points (measure these with a ruler!)
// Add as many points as you want for better accuracy
struct CalibrationPoint {
float voltage;
float distance_mm;
};

// Example calibration data - REPLACE WITH YOUR MEASUREMENTS
CalibrationPoint calPoints[] = {
{2.8, 50}, // At 50mm, you measured 2.8V
{2.0, 100}, // At 100mm, you measured 2.0V
{1.5, 150}, // At 150mm, you measured 1.5V
{1.0, 250}, // At 250mm, you measured 1.0V
{0.75, 400}, // At 400mm, you measured 0.7V
{0.5, 600}, // At 600mm, you measured 0.5V
{0.4, 800}
};

void setup() {
Serial.begin(9600);
pinMode(sensorPin, INPUT);
}

void loop() {
int analogValue = analogRead(sensorPin);
float voltage = analogValue * (5.0 / 1023.0);

// Method 1: Power function
float distance_power = voltageToDistancePower(voltage);

// Method 2: Linear interpolation (more accurate if calibrated)
float distance_interp = voltageToDistanceInterpolation(voltage);

Serial.print("V: ");
Serial.print(voltage, 2);
Serial.print(" | Power method: ");
Serial.print(distance_power, 1);
Serial.print(" mm | Interpolation: ");
Serial.print(distance_interp, 1);
Serial.println(" mm");

delay(500);
}

// Method 1: Power function
float voltageToDistancePower(float voltage) {
if (voltage < 0.39 || voltage > 3.2) return -1;

float distance_mm = 27000.0 / pow(voltage + 0.3, 1.2);

if (distance_mm < 10) distance_mm = 10;
if (distance_mm > 800) distance_mm = 800;

return distance_mm;
}

// Method 2: Linear interpolation between calibration points
float voltageToDistanceInterpolation(float voltage) {
int numPoints = sizeof(calPoints) / sizeof(CalibrationPoint);

// Check if out of range
if (voltage > calPoints[0].voltage) {
return calPoints[0].distance_mm; // Too close
}
if (voltage < calPoints[numPoints-1].voltage) {
return calPoints[numPoints-1].distance_mm; // Too far
}

// Find the two points to interpolate between
for (int i = 0; i < numPoints - 1; i++) {
if (voltage <= calPoints[i].voltage && voltage >= calPoints[i+1].voltage) {
// Linear interpolation
float v1 = calPoints[i].voltage;
float v2 = calPoints[i+1].voltage;
float d1 = calPoints[i].distance_mm;
float d2 = calPoints[i+1].distance_mm;

float distance = d1 + (voltage - v1) * (d2 - d1) / (v2 - v1);
return distance;
}
}

return -1; // Should never reach here
}

And we based our measured data of the given sample graph.
And Here is a comparison of the real data:

Compare

I mean, in order to improve this next line,
We should first test the basic data and find an exponential regression by plotting it on Desmos to obtain a well-Tuned data.

Also Here are some advice to avoid errors:

Advice