[How-To] Motorised rotab with CNC pretensions

tonyfoale

Registered
Registered
Joined
Apr 25, 2024
Messages
53
I am a newbie here, only my second post. I hope that this is the best subforum for this. If not maybe an admin can move it. My previous post showed a motorcycle clutch that I made. One of the tools that I used for that was a rotab that I had motorised. There were two aspects to this, firstly some simple turning to make a solid connection between the motor and rotab. Secondly a power supply and control box to enable semi automatic control. I also had in mind that it should be done so that I could quickly switch between the semi-automated but manually demanded control function and CNC driven as a fourth axis.

There are three manually demanded functions that I built into the software, viz;
1. Move to a set angle.
2. Divide a circle into a specified number of equally spaced angles and index around on the press of a button.
3. Continuous jogging whilst holding a button.

The direction and speed of movement are under operator control.

The heart of the control system is an Arduino Nano which accepts instructions from buttons, a keypad and potentiometer on the front panel. The Nano outputs step and direction signals which are fed into an off the shelf stepper driver which in turn drives a Nema24 4Nm closed loop stepper motor.

To change over to working as a CNC 4th axis all that is necessary is to change the input to the driver over to the output of the CNC controller.

Commanded resolution is 0.001 degree. Due to friction it would be unreasonable to expect individual movements that fine but a fine resolution helps with smoothness.

Rather than relying on the worm drive to hold the table against machining forces I have fitted an external drum brake. There is always some backlash in a worm drive and the brake renders that unimportant. The brake is powered by a spring which is released by a pneumatic cylinder when the table needs to move. I include a photo showing the holding torque of the brake being tested.
The brake system is great when the rotab is laying flat but is not so good when upright in the mill, it creates clearance problems. That is the only issued that has shown up in use.

RoTab008.jpgRoTab011.jpgRoTab013.jpgRoTab014.jpgRoTab020.jpgRoTab023.jpgRotab026.jpgRotab034.jpgRoTab046.pngRotab044.jpgRoTab045.jpgAl_clutch032.jpg
 
Last edited:
DavidR8
Wow, that's some clever work.
I see that you are a staff member. I accidentally put my post in Fabrickator's area. I think that it should be one folder level up. Can you fix that?
 
DavidR8
I see that you are a staff member. I accidentally put my post in Fabrickator's area. I think that it should be one folder level up. Can you fix that?
Done
 
It's very nice to have you join the forum. I have read some of your writings on motorcycle suspension design.
For those here not familiar with Tony, here's a little info about him:


Ted
 
Whoa!
I had no idea this was THAT Tony!
 
Nicely executed- did you write the Arduino code from scratch?
Rot tables usually have an inconvenient ratio like 1:72
 
Nicely executed- did you write the Arduino code from scratch?
Rot tables usually have an inconvenient ratio like 1:72

Yes, I wrote the code. Mine has a 1:90 ratio. Which is convenient because with microstepping set to 4000 p/r I need 4000 x 90 = 360000 steps/table revolution. 1:72 is OK if your stepper driver can give 5000 p/r. So each stepper step commands 0.0001 deg. I made a couple of short videos of it in action but being a new boy in school I have to make 3 posts without links.
Here is the code FWIW.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
long counter, angle, pot;
long Count = 0, oldCount = 0 ;
char myStr[8], pressedKey;
boolean numPadOn = true;
int mode = 4, stepsMoved = 0, numSteps = 0;
char myKeys[4][4] = {
{ '1','2','3','A' },
{ '4','5','6','B' },
{ '7','8','9','C' },
{ '*','0','#','D' }
};
byte rowPins[4] = {9, 10, 11, 12}; //connect to the column pinouts of the keypad
byte colPins[4] = {5, 6, 7, 8}; //connect to the row pinouts of the keypad
Keypad kPad = Keypad(makeKeymap(myKeys), rowPins, colPins, 4, 4);
LiquidCrystal_I2C lcd(0x27,20,4); // Create LCD and set address to 0x27 and a 20x4 display.
void setup() {
kPad.setDebounceTime(100);
kPad.setHoldTime(500);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0,0); lcd.print("Mode : Idle ");
lcd.setCursor(0,1); lcd.print(" ");
lcd.setCursor(0,2); lcd.print("Position : 0.000 ");
lcd.setCursor(0,3); lcd.print("Status : Idle ");
pinMode(14,INPUT); // pin 14 is the same as A0 use for speed pot input
pinMode(2, INPUT_PULLUP); // Direction switch
pinMode(3, INPUT_PULLUP); // Zero
pinMode(4, INPUT_PULLUP); // GO
pinMode(A1, OUTPUT); // Motor pulse
pinMode(A2, OUTPUT); // Motor dir
pinMode(13, OUTPUT); // Brake
digitalWrite(13, LOW); // Brake on
//Serial.begin(115200, SERIAL_8N1); // For initial testing only
//delay(1000);
}
void loop() {
if (!digitalRead(4)) {
if (mode == 0) jog();
else if ((mode == 1) || (mode == 2)) goToAngle();
else {
mode = 4; // mode 4 a signal for the idle mode in getData()
getData();
}
}
if (!digitalRead(3)) {
Count = 0;
lcd.setCursor(0,2); lcd.print("Position : 0.000 ");
}
pressedKey = kPad.getKey();
if (pressedKey == '*'){
mode = 4;
getData();
}
}
void getData(){ // Reads, writes and counts the input data
char numBuf[8];
int i = 0;
/* Key mapping
A > Jog continuous
B > divide by N
C > Go to angle
D > Enter data and signal ready
* > Only used in loop() to enter getData() in mode 4.
# > Clear
*/

startAgain:;
if (mode == 0) {
lcd.setCursor(0,0); lcd.print("Mode : Jogging ");
lcd.setCursor(0,1); lcd.print(" ");
lcd.setCursor(0,3); lcd.print("Status : Ready ");
}
else if (mode == 1) {
lcd.setCursor(0,0); lcd.print("Mode : Divide by N ");
lcd.setCursor(0,1); lcd.print("N divisions : ");
lcd.setCursor(0,3); lcd.print("Status : Needs input");
}
else if (mode == 2) {
lcd.setCursor(0,0); lcd.print("Mode : Go to angle ");
lcd.setCursor(0,1); lcd.print("Angle : ");
lcd.setCursor(0,3); lcd.print("Status : Needs input");
}
else if (mode == 4) {
lcd.setCursor(0,0); lcd.print("Mode : Idle ");
lcd.setCursor(0,1); lcd.print(" ");
lcd.setCursor(0,3); lcd.print("Status : Needs mode ");
}
for (int j=0; j<7; j++) numBuf[j] = ' '; // Clear buffer
numBuf[7] = '\0'; // End character
i = 0; counter = 0;
if (mode == 2) {lcd.setCursor(9,1); lcd.print(" 0.000");}
else if (mode == 1) {lcd.setCursor(15,1); lcd.print(" 0");}
while (true){
pressedKey = kPad.getKey();
if (pressedKey){
if (i > 6) {
angle = 0;
lcd.setCursor(0,1); lcd.print("Too many digits ");
delay(3000); // Time to read message
goto startAgain;
}
if (pressedKey == '#') { // # is Clear key
angle = 0;
goto startAgain;
}
else if (pressedKey == 'D') {
angle = counter; // Used in mode 2 only
oldCount = Count; // This is to give a fixed starting point for mode 1 only
goto thatsIt;
}
else if (pressedKey == 'A') {
mode = 0;
lcd.setCursor(0,0); lcd.print("Mode : Jogging ");
lcd.setCursor(0,1); lcd.print(" ");
lcd.setCursor(0,3); lcd.print("Status : Ready ");
goto thatsIt;
}
else if (pressedKey == 'B') {
mode = 1;
lcd.setCursor(0,0); lcd.print("Mode : Divide by N ");
lcd.setCursor(0,1); lcd.print("N divisions : ");
lcd.setCursor(0,3); lcd.print("Status : Needs input");
goto enterNumbers;
}
else if (pressedKey == 'C') {
mode = 2;
lcd.setCursor(0,0); lcd.print("Mode : Go to angle ");
lcd.setCursor(0,1); lcd.print("Angle : ");
lcd.setCursor(0,3); lcd.print("Status : Needs input");
goto enterNumbers;
}
else numBuf = pressedKey;

counter = strtol(&numBuf[0], NULL, 10);
if (mode == 2) {
dtostrf(float(counter)/1000,7,3,myStr);
lcd.setCursor(0,1); lcd.print("Angle : ");
lcd.setCursor(9,1); lcd.print(myStr);
}
else if (mode == 1) {
lcd.setCursor(0,1); lcd.print("N divisions : ");
lcd.setCursor(15,1); lcd.print(counter);
}
i++;
}
enterNumbers:;
}
thatsIt:;

lcd.setCursor(0,3); lcd.print("Status : Ready ");
if (mode == 2) {
dtostrf(float(angle)/1000,7,3,myStr);
lcd.setCursor(0,1); lcd.print("Angle : ");
lcd.setCursor(9,1); lcd.print(myStr);
}
else if (mode == 1) {
numSteps = angle;
lcd.setCursor(0,1); lcd.print("N divisions : ");
lcd.setCursor(15,1); lcd.print(angle);
stepsMoved = 0;
}
}
void jog(){
int dir ;

digitalWrite(13, HIGH); //Brake off
delay(500); // Give time for brake to release
digitalWrite(A2, digitalRead(2)); //set direction.
delayMicroseconds(4); // Direction must be set at least 2 uSecs before step
dir = (2*digitalRead(A2) -1); // "(2*digitalRead(A2) -1)" determines sign of each step.
lcd.setCursor(0,2); lcd.print("Position : *.* ");
while (!digitalRead(4)) { // GO button pressed
pot = analogRead(A0); // analogRead() is slow so it only called once per degree
pot = round((0.04*pot*pot*0.0049*0.0049 - 0.26*pot*0.0049 + 0.6)*pot*0.2); // equation to even out pot vs pot position %
if (pot > 60) pot = 600; // If the pot is turned right down then a very slow speed is activated
for (long i=0; i <250;++i){ // Moves in 0.25 deg steps.
digitalWrite(A1, HIGH);
delayMicroseconds(2);
digitalWrite(A1, LOW);
Count = Count + dir;
delayMicroseconds(pot+1); //Delay goes from 1 to 1024
}
}
dtostrf(float(Count)/1000,7,3,myStr);
lcd.setCursor(0,2); lcd.print("Position : ");
lcd.setCursor(11,2); lcd.print(myStr);
digitalWrite(13, LOW); //Brake on
// lcd.setCursor(0,3); lcd.print(" ");
// lcd.setCursor(0,3); lcd.print(pot);
}
void goToAngle(){
int dir ;

digitalWrite(13, HIGH); //Brake off
delay(500); // Give time for brake to release
digitalWrite(A2, digitalRead(2)); //set direction.
delayMicroseconds(4); // Direction must be set at least 2 uSecs before step
dir = (2*digitalRead(A2) -1); // "(2*digitalRead(A2) -1)" determines sign of each step.

if (mode == 1) {
stepsMoved = stepsMoved + 1;
angle = oldCount + dir * round((stepsMoved * 360000.0)/numSteps);
}
pot = analogRead(A0); //A0 = 14
pot = round((0.066*pot*pot*0.0049*0.0049 - 0.43*pot*0.0049 + 1)*pot); // equation to even out pot vs pot position %
lcd.setCursor(0,2); lcd.print("Position : *.* ");

while (dir*Count < dir*angle) {
digitalWrite(A1, HIGH);
delayMicroseconds(2);
digitalWrite(A1, LOW);
Count = Count + dir;

delayMicroseconds(1+pot); // Pot goes from 0 to 1023
}
dtostrf(float(Count)/1000,7,3,myStr);
lcd.setCursor(0,2); lcd.print("Position : ");
lcd.setCursor(11,2); lcd.print(myStr);
if (mode == 2) {
lcd.setCursor(0,3); lcd.print("Status : Needs data ");
mode = 4;
}
digitalWrite(13, LOW); //Brake on
}
 
Nicely done!
And welcome aboard!!
 
Back
Top