3D Scanner Turntable for DAVID4
DAVID4 is a capable software that allows one to scan 3D objects using an illumination source and a camera. The illumination source can be a line laser or projector. Since the camera can only see part of the object to be scanned, multiple scans need to me made and fused together. To automate this, the object can be put on a motorized turntable.
Commercial tables for DAVID connect via some sort of custom USB connection and are quite pricey:
http://www.makershop3d.com/3d-scanners/526-turntable-david-tt1.html
But DAVID also allows control of a self-built turntable via a COM/serial port (which can also implemented via USB, of course but DAVID sees them in a different way). I have some small 400 step/rev stepper motors left over from the PNC CNC mill conversion.
So I will design the turntable around that. I also have a ChipKit Uno32 on hand. This can connect to the computer and appear as a serial port and arduino stepper motor control shields are compatible with it, cheap, and plentiful.
I chose an Adafruit motor shield v2.3:
Adafruit made a library available for Arduino to drive this board. And UECIDE makes it easy to import it:
I don't want to power the stepper motor separately. Powering it from the USB port is a bit tricky. The resistance of the wiring on the stepper motor reads about 5 Ohm. At a supply voltage of 5 Volt each winding could pull as much as 1 Ampere. I want to keep the maximum current drawn from the USB bus to approximately 0.5 Ampere. So I put a 10 Ohm resistor in series with each winding. Each winding will then draw no more than 1/3 Ampere. In full step mode only a single winding is on at a time. One problem is that on the 10 Ohm resistor there is a power dissipation of 0.9 Watt. My resistors are only rated at 0.25 Watt, and at any rate even on a 1W resistor the heat generated would be of concern. To get around this problem, I only drive the motor part of the time. If there is enough friction on the table, it won't move when the motor power is turned off. So even conservatively if it takes a second to rotate the platform to the next position and 10 seconds for DAVID to perform the scan, the power dissipated over the resistors would be less than 0.1W. And there are two of them. So this is a reasonable thing to try.
The turntable itself consists of two main mechanical parts, the base and the rotating platform.
I wanted a way to start/abort the scans right on the turntable, so I added a button for that too. Here is the 3D model as it looked in Fusion360:
If the rotating platform were simply placed on the stepper motor shaft, then it would be very easy for the scanned object to torque the platform and create a wobble. Thus, I chose to use a bearing to take up the load. I had a 2503 bearing left over from another project, so that is what I used. The bearing is a bit of a press fit on the top table, and I added a chamfer to stop the inner ring (marked red) to make sure the outer ring does not rub. The outer ring then transmits the load to the bottom (marked in yellow).
Then I printed the part with ZIRO carbon fiber filament on a 3D printer.
In DAVID4, the COM port access has to be enabled in the advanced settings menu:
Only then is the "Motor Scanner Setup" option visible which allows the selection of the COM port.
The advanced menu also gives a nice list of the commands that can pass between DAVID4 and the turntable:
With that in hand, we can start the design of the firmware of the turntable. First, lets get the motor spinning using the Adafruit library:
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// Connect a stepper motor with 400 steps per revolution (0.9 degree)
// to motor port #2 (M3 and M4)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(400, 2);
void setup() {
AFMS.begin(); // create with the default frequency 1.6KHz
myMotor->setSpeed(1); // 1 rpm
}
void loop() {
myMotor->step(1, FORWARD, SINGLE); // take a step
delay(1000); // wait a second
}
This will spin the motor forever, one step every second, one complete rotation every 400 steps. This works quite well, my "USB Power Monitor" measures less than 500 mA of current with this code.
The steps are a bit jerky, so instead of single stepping, I changed it to microstepping:
myMotor->step(1, FORWARD, MICROSTEP); // take a step
Also, the motor windings are always on, burning up more power than we want. After moving a step, we should wait a short amount of time to let the system mechanically settle and then turn off power to the windings.
delay(250); // settle for 250 ms
myMotor->release(); // turn off current
On the very first step, the motor could be anywhere in relation to the step angle, so it tends to make a sharp jerk. To avoid that, we make sure that happens on plugging in power.
myMotor->step(1, FORWARD, MICROSTEP); // take a step to initialize position
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current
Next, we need to set up the serial port to be able to receive messages from DAVID. First, we open the port using
Serial.begin(9600); // open serial port
and then we can use that facility to make sure that the motor works the way we want. So we will add a counter and increment it every step. Also every step we will print out the counter on the serial port. At a count of 400 we should have a full rotation. Lets call this part the 'test code'
static int counter = 0;
counter = counter + 1;
Serial.print("step ");Serial.println(counter);
Works as expected. When the serial port reported step 400 the table had rotated once.
We can now remove the 'test code' since everything works as expected.
At this point the source code looks like this:
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// Connect a stepper motor with 400 steps per revolution (0.9 degree)
// to motor port #2 (M3 and M4)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(400, 2);
void setup() {
Serial.begin(9600); // open serial port
AFMS.begin(); // create with the default frequency 1.6KHz
myMotor->setSpeed(1); // 1 rpm
myMotor->step(1, FORWARD, MICROSTEP); // take a step to initialize position
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current}
void loop() {
myMotor->step(1, FORWARD, MICROSTEP); // take a step
delay(250); // settle for 250 ms
myMotor->release(); // turn off current
delay(1000); // wait a second
}
400 steps per scan may be too much. So for now, we will increase the step size to 10, so that we 'only' have 40 scans for a full revolution.
void setup() {
Serial.begin(9600); // open serial port
AFMS.begin(); // create with the default frequency 1.6KHz
myMotor->setSpeed(1); // 1 rpm
myMotor->step(1, FORWARD, MICROSTEP); // take a step to initialize position
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current}
void loop() {
myMotor->step(10, FORWARD, MICROSTEP); // take a step
delay(250); // settle for 250 ms
myMotor->release(); // turn off current
delay(1000); // wait a second
}
When the 'start scan' button is pressed in DAVID, it performs a scan and some processing, and then sends a '2' character over the serial line when it returns to "Scanning" Tab. So at the simplest level, we can just look out for that character and turn the table to the next position.
void loop() {
if(Serial.available()>0) // is there any unread serial data?
{
char ch = Serial.read(); // read a character from the serial port
if (ch == '2') // is it the character for the number 2?
{
myMotor->step(10, FORWARD, MICROSTEP); // take a step
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current
}
} // end Serial.available
}
This requires us to press the start button manually every time in David, but the rotation is automated, at least. To further automate the process, we need to have a way to send DAVID a message to start the next scan. The character to accomplish that is 'S'. However, if we just send the letter S every time DAVID is done, we will be stuck in a loop forever:
DAVID says '2', we rotate and say 'S'.
DAVID says '2', we rotate and say 'S'.
DAVID says '2', we rotate and say 'S'.
DAVID says '2', we rotate and say 'S'.
.
.
.
So we need a way to stop. Since we take 10 microsteps between commands, 40 commands equal 1 rotation. The scan should end there. So we add a loop counter and don't send 'S' when we reach 40.
if (counter == 40) // we have performed a full rotation
{
counter = 0; // reset for next scan
}
else
{
Serial.println("S"); // Tell DAVID to do another Scan
}
This is now functioning as a fully automated turntable with DAVID4 with the following code:
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// Connect a stepper motor with 400 steps per revolution (0.9 degree)
// to motor port #2 (M3 and M4)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(400, 2);
void setup() {
Serial.begin(9600); // open serial port
AFMS.begin(); // create with the default frequency 1.6KHz
myMotor->setSpeed(1); // 1 rpm
myMotor->step(1, FORWARD, MICROSTEP); // take a step to initialize position
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current}
void loop() {
if(Serial.available()>0) // is there any unread serial data?
{
char ch = Serial.read(); // read a character from the serial port
if (ch == '2') // is it the character for the number 2?
{
myMotor->step(10, FORWARD, MICROSTEP); // take a step
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current
if (counter == 40) // we have performed a full rotation
{
counter = 0; // reset for next scan
}
else
{
Serial.println("S"); // Tell DAVID to do another Scan
}
}
} // end Serial.available
}
This is pretty nice already, but there are two missing features.
1. There is no way to start or cancel the scan on the Base itself. This is probably not a problem, as both can be done from within DAVID.
2. Every selection of the "Scanning" Tab causes a '2' to be sent and starts the scan loop on the table. We should address this.
3. Cancellation in the program does not reset the count in the turntable. We should address this too.
The command/message options window that DAVID gives is not very clear about when those messages are being sent. DAVID does have an option to show a debug console window, which is very convenient. However, it only displays what messages are being received, not the ones sent. To change that, lets echo back any command we receive in the turntable:
Serial.print("echo");Serial.println(ch); // echo back so that it is displayed in DAVID debug console
The original settings in DAVID assign the letter 'S' to several events: 'StartScanning', 'StartSLCalib' and 'StartSLScan'. We will change that so that we can keep these events apart.
Now, with those settings we can press some buttons and switch between tabs and the messages sent will be echoed back to us in the debug window:
It appears that both the background scan and the start buttons send a '7' now and changing to the scanning tab sends a '2'. So, if we want to do a background removal, the scan procedure is:
- In the "Setup" tab disable the turntable by selecting port 'none'
- Then in the "Scanning" tab, perform the background scan
- Place the object.
- Go back to the Setup tab, and enable the turntable by selecting its COM port.
- Wait a moment to give the turntable time to initialize
- Select the "Scanning" tab. The scanning sequence will commence immediately.
Not very complicated compared to the manual alternative....
Here is the final source code:
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// Connect a stepper motor with 400 steps per revolution (0.9 degree)
// to motor port #2 (M3 and M4)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(400, 2);
void setup() {
Serial.begin(9600); // open serial port
AFMS.begin(); // create with the default frequency 1.6KHz
myMotor->setSpeed(1); // 1 rpm
myMotor->step(1, FORWARD, MICROSTEP); // take a step to initialize position
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current
}
void loop() {
static int counter = 0;
if(Serial.available()>0) // is there any unread serial data?
{
char ch = Serial.read(); // read a character from the serial port
Serial.print("echo");Serial.println(ch); // echo back so that it is displayed in DAVID debug console
if (ch == '2') // is it the character for the number 2?
{
counter = counter + 1; // increment the scan count
myMotor->step(10, FORWARD, MICROSTEP); // take a step
delay(250); // settle mechanics for 250 ms
myMotor->release(); // turn off current
if (counter == 40) // we have performed a full rotation
{
counter = 0; // reset for next scan
}
else
{
Serial.println("S"); // Tell DAVID to do another Scan
}
}
} // end Serial.available
}
And this is how I hooked it up.