# Rotary table/indexer controller.



## EricB (Nov 8, 2022)

Just wondering if anyone here has built a stand alone controller for a rotary table?

I just acquired a CNC table with no controller and I'm planning to roll my own.


----------



## Eddyde (Nov 8, 2022)

That sounds like an interesting project, can you please post some pictures of the table?


----------



## EricB (Nov 8, 2022)

Sure. It's a Sherline 8730. I picked it up from ebay for $270 from an estate seller. It's normally about $450 retail.

Here's the picture from the motor test. I used and arduino and driver boards I had laying about, and ran it off a 9 volt battery.


----------



## rabler (Nov 8, 2022)

Just started working on this:  https://www.hobby-machinist.com/threads/rotary-table-automation.102461/page-3#post-991727
In addition to basic rotary control I'd like to add an encoder  to track the x-axis leadscrew for spiral cutting, etc.


----------



## markba633csi (Nov 8, 2022)

I built an electronic dividing head using Arduino code online- Here's the link:
The code seems to be very solid
-Mark





						Arduino Rotary Table for Dummies
					

Ive had a few people tell me that they like the project (http://www.homemodelenginemachinist.com/showthread.php?t=25783) but dont feel comfortable with the electronics. Ive been playing around to simplify it down to where I feel anyone who wants to could comfortably build the controller. With...




					www.homemodelenginemachinist.com


----------



## EricB (Nov 11, 2022)

After some experimenting, it seems to me that the thing to do is leave the motor disabled until it's time for a  move. Then when a move is needed, pull the enable lines high, drive to the new position, then drop the enables low. 

The pro is the driver doesn't cook itself just holding the motor position. Mine got up to egg frying temp in just a few minutes.

The con is very little holding torque.

Thoughts?

Eric


----------



## WobblyHand (Nov 12, 2022)

EricB said:


> After some experimenting, it seems to me that the thing to do is leave the motor disabled until it's time for a  move. Then when a move is needed, pull the enable lines high, drive to the new position, then drop the enables low.
> 
> The pro is the driver doesn't cook itself just holding the motor position. Mine got up to egg frying temp in just a few minutes.
> 
> ...


In normal operation for the driver is the hold current the same as the stepping current?  Can your driver reduce it's hold current?  I have an ELS using a stepper, it doesn't get particularly hot.  Mine is 4 Nm, NEMA 24.  Of course my lathe is the heatsink.  I've left the motor on, but not stepping and it seems ok, it's not hot.  I'm using a closed loop stepper motor driver that has various current settings.  A CL57T, I think.

If you are using the stepper just to position and stop, then you could use the locks on the RT.  Can't do that if the RT is rotating!


----------



## rabler (Nov 12, 2022)

EricB said:


> After some experimenting, it seems to me that the thing to do is leave the motor disabled until it's time for a  move. Then when a move is needed, pull the enable lines high, drive to the new position, then drop the enables low.
> 
> The pro is the driver doesn't cook itself just holding the motor position. Mine got up to egg frying temp in just a few minutes.
> 
> ...


I'm planning on using the rotary table locks on mine if it needs to hold in place.  I'm using a clearpath servo, so the microcontroller can monitor torque (via an A/D converter) to warn if the table is locked when trying to turn. 

I believe simple stepper drivers are essential a current source, varying the voltage to get a steady current.  I wonder if you could hack the driver to adjust the current settings dynamically by controlling the DIP switches from your microcontroller, so you could drop to something like 75% current for holding.


----------



## EricB (Nov 12, 2022)

rabler said:


> I'm planning on using the rotary table locks on mine if it needs to hold in place.  I'm using a clearpath servo, so the microcontroller can monitor torque (via an A/D converter) to warn if the table is locked when trying to turn.
> 
> I believe simple stepper drivers are essential a current source, varying the voltage to get a steady current.  I wonder if you could hack the driver to adjust the current settings dynamically by controlling the DIP switches from your microcontroller, so you could drop to something like 75% current for holding.


The data sheets tell that current can be controlled buy selecting the proper "sense" resistor, but don't elaborate further. I guess you need an EE degree for that. Holding current as tested is 1.4 amps per coil. The driver is rated 3 amps max per channel. I guess it needs a bigger heat sink.


----------



## EricB (Nov 12, 2022)

WobblyHand said:


> In normal operation for the driver is the hold current the same as the stepping current?  Can your driver reduce it's hold current?  I have an ELS using a stepper, it doesn't get particularly hot.  Mine is 4 Nm, NEMA 24.  Of course my lathe is the heatsink.  I've left the motor on, but not stepping and it seems ok, it's not hot.  I'm using a closed loop stepper motor driver that has various current settings.  A CL57T, I think.
> 
> If you are using the stepper just to position and stop, then you could use the locks on the RT.  Can't do that if the RT is rotating!


I would need to check the current while stepping.

In it's present configuration I can't adjust output current. I just checked and the board has the current setting pins tied to ground, so it's set for max output current. Time to get the soldering iron out.


----------



## EricB (Nov 13, 2022)

Reverse engineering the Sherline controller:






Just trying to figure out what features I want.


----------



## markba633csi (Nov 13, 2022)

If your motor is getting too hot you should be able to set the holding current on your driver.
I'm using a DRV8825 driver chip which has a current set adjustment
You should be able to leave the stepper powered for hours without getting too hot








						Pololu - Stepper Motor Drivers
					

With features like adjustable current limiting and selectable microstep resolutions, these drivers make it easy to get a stepper motor running with simple step and direction control interfaces.




					www.pololu.com


----------



## EricB (Nov 13, 2022)

The motor is cool. The driver chip is cooking breakfast. It's curious because the Sherline driver boards have no heat sinks on the chips.



			https://www.sherline.com/wp-content/uploads/2018/10/87625-09.png
		


The driver I'm playing with now is an L298N "H" bridge, because that's what I have on hand. It is capable of driving a load up to 4 amps and I can adjust the current limiting. I just have to cut some traces and add resistors. What size resistors is arcane knowledge not included in the data sheets. I guess it's trail and error until I get what I want.

Some drivers can reduce motor current after a timeout period. I'm looking into how to do that.

I have several other drivers coming to experiment with later.

The fun part for me is the software. So many different ways to solve the problem!


----------



## EricB (Nov 13, 2022)

I made a video when I initially tested the motor. For power I used a 9 volt transistor battery, so not a lot of current. Looking back at it, the LEDs on the driver board dim when the motor is stopped. That tells me the holding current is much higher than the moving current. Makes sense since the coils are pulsed they're not drawing full current when the motor is moving.






Hmmmm...


----------



## markba633csi (Nov 13, 2022)

The 8825 chip is highly recommended, very efficient, runs cool, does microsteps- and it's dirt cheap on Aliexpress
I'm a total convert


----------



## EricB (Nov 14, 2022)

I'm starting to get an appreciation about why CNC controllers cost so much. That didn't take long.


----------



## EricB (Nov 17, 2022)

I've made some progress toward a functioning controller. So far I have two modes. The degree mode works good. 

The division mode gets the math wrong for counts that are not factors of the stepper/table combination, so it looses several degrees by the time you make a full circle. It always comes out right on the screen but the motor and the screen don't match. 

It does work and drives the table. I had it going for several hours today trying various methods, and check the accuracy. I was starting to make a video when I let the smoke out of my power supply.

Here's what I have so far. This is just the test setup. The parts will be changed for the final version.



Here is the video I was working on before the smoke.






Eric


----------



## RJSakowski (Nov 18, 2022)

RT's are unique in that they use a worm drive.  Worm gears cannot be back driven so holding current can be set at a minimum.  Additionally, the 90:1 gearing provides tremendous mechanical advantage so a high drive current isn't necessary. I have a Tormach 4th axis based on the Phase II 6' RT.  There is no rating plate on the motor but it is a Nema 23 frame and the drive current is set for a maximum of 2.6 amps.


----------



## EricB (Nov 18, 2022)

I've found in the spec sheets that some of the better driver chips reduce holding current automatically after a short timeout period. It's easy enough for me to just toggle the enable lines to the driver after the motor stops. I think that once I get a proper power supply I'll be fine. I was trying to pull almost 3 amps from a 1 amp wall transformer. It worked for a few hours.


----------



## markba633csi (Nov 18, 2022)

Try some other driver chips- you'd be surprised how little current you really need


----------



## EricB (Nov 18, 2022)

markba633csi said:


> Try some other driver chips- you'd be surprised how little current you really need


I received a TB6560 driver today, and I have some other choices coming soon. My new power supply is on order too.

The L298N driver I'm using can take the load. It's rated for up to 46 volts at 4 amps. I solved its temp problem with a cooling fan. The motor needs 24 volts and draws 1.4 amps per coil to hold, so that's 2.8 amps total. I can limit the current with some added resistors. Just ain't got there yet.

I looked over your code and the original version I started with was identical in some parts, so it must be in common use. I don't recall where I got my copy. 

The discussion on the other message board presents some of the same questions I've asked myself about accuracy of divisions. I didn't see any solutions and I'm still working it out. My interim solution for handling division counts that are not factors of the table ratio and motor step count is let the controller do the math and make the move, then disable the driver so I can manually correct the final position with the hand wheel. Still easier than figuring it all out for each division on a manual table.


----------



## markba633csi (Nov 19, 2022)

The code knows how to add or subtract steps so that odd numbers and primes come out correctly- That's the high-falutin' math portion that was
a bit beyond my skill level, so I borrowed the code from a better mathematician than me.
If the code initialization is set correctly for the number of steps/div and the gear ratio then no manual intervention should be required

"Sometimes a man's got to know his limitations" Clint Eastwood


----------



## EricB (Nov 19, 2022)

markba633csi said:


> The code knows how to add or subtract steps so that odd numbers and primes come out correctly- That's the high-falutin' math portion that was
> a bit beyond my skill level, so I borrowed the code from a better mathematician than me.
> If the code initialization is set correctly for the number of steps/div and the gear ratio then no manual intervention should be required
> 
> "Sometimes a man's got to know his limitations" Clint Eastwood



We must not be looking at the same code. The file I downloaded is called "Arduino_Rotary_Table_Control_2016". Is there another?

Eric


----------



## markba633csi (Nov 19, 2022)

Did you use the one from the "Rotary table for Dummies" article?
I can post the Arduino file here if you want to compare- mine says 2017 beta but I remember having to make a slight change regarding the
pinout of the keypad being reversed

Hmm, seems like I'm having trouble trying to post a .ino file- need to cogitate for a bit


----------



## WobblyHand (Nov 19, 2022)

markba633csi said:


> Did you use the one from the "Rotary table for Dummies" article?
> I can post the Arduino file here if you want to compare- mine says 2017 beta but I remember having to make a slight change regarding the
> pinout of the keypad being reversed
> 
> ...


Depending on length, you could cut and paste the ino code using code icon </>.  You could call it C++ and the formatting would come out ok.  Sort of like this:

```
#include <SPI.h>
#include <WiFi101.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display = Adafruit_SSD1306();

#if defined(ESP8266)
  #define BUTTON_A 0
  #define BUTTON_B 16
  #define BUTTON_C 2
  #define LED      0
#elif defined(ARDUINO_STM32F2_FEATHER)
  #define BUTTON_A PA15
  #define BUTTON_B PC7
  #define BUTTON_C PC5
  #define LED PB5
#elif defined(TEENSYDUINO)
  #define BUTTON_A 4
  #define BUTTON_B 3
  #define BUTTON_C 8
  #define LED 13
#else
  #define BUTTON_A 9
  #define BUTTON_B 6
  #define BUTTON_C 5
  #define LED      13
#endif

#if (SSD1306_LCDHEIGHT != 32)
 #error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

int pingPin = 13;
int inPin = 12;
long microseconds;
int16_t xx, yy;
void setup() {
  // put your setup code here, to run once:
  pinMode(pingPin, OUTPUT);
  pinMode(inPin, INPUT);
  //Configure pins for Adafruit ATWINC1500 Feather
  WiFi.setPins(8,7,4,2);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  Serial.begin(9600);
  display.display();
  delay(1000);
  // Clear the buffer.
  display.clearDisplay();
  display.display();
   // text display tests
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
 
  //display.println("Pinger:");
  //xx = display.getCursorX();  // returns with x value of cursor
  //yy = display.getCursorY();
  display.display();
}

void loop() {
  // put your main code here, to run repeatedly:
  long duration, inches, cm;
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  duration = pulseIn(inPin, HIGH);

  cm = usToCm(duration);
  Serial.print(cm);
  Serial.println(" cm");

  delay(500);              // ping once a second
  display.clearDisplay();   // clear display and rewrite
  display.display();
  display.setCursor(0,0);   // make sure the cursor points to 0,0, the top left of screen
 
  //display.println("Pinger:");
  //display.print("Rng: ");
  display.print(cm);
  display.println(" cm");
 
  display.display();
}

long usToCm(long microseconds){ return microseconds/58;
}
```


----------



## markba633csi (Nov 19, 2022)

It's the forum software that's the holdup- let me try renaming it as a .txt file- change the filename back to .ino when you get it


----------



## markba633csi (Nov 19, 2022)

I think that worked
I seem to remember that the final, truly full-featured code was a couple revisions in from the first one posted in the article.
When I verified that it was in fact ok, I made a few comments so I could recognize it easily.  MS is me, not the original author.
I found that the author suddenly swapped the keypad pinout so I swapped it back.  It took me some time to figure out the I2C address for the LCD
There is a tool "I2C scanner"  I believe in both the Arduino repository and in the article
-M


----------



## EricB (Nov 20, 2022)

Here is the relevant portion:
{
bob = bob + Degrees;

ToMove=(bob/360) * Multiplier + 0.5 - cumSteps;

cumSteps=cumSteps+ToMove;
}

I had to make a spreadsheet to prove it to myself but that's a simple and effective solution!




Thanks!


----------



## markba633csi (Nov 20, 2022)

It may look simple to you but I couldn't even conceive of the algorithm to do it let alone convert it into code to execute it- I'm glad it meets
your approval  I'm more of a hardware guy than a software guy
Hats off to the original author(s) I think it was a collaborative effort


----------



## EricB (Nov 20, 2022)

I was heading in that direction, adding a half step every so often (or extra degrees), but had not figured out the how many and when or how to keep track of them.

I'm using a library function to drive the motor so I'll probably have to change that. It takes a degree input rather than a step count, and I don't know if it performs any error correction. Assuming it doesn't, I can still use your algorithm by multiplying "ToMove" by the degrees/step of the motor (1.8) to get the input in degrees for my function.

It could work. Hmmm...

Eric


----------



## EricB (Nov 21, 2022)

WARNING! Programming Stuff Follows!

After some sleep loss I have some working code with error correction.


```
case '3':                             // Rotate CW
             newKey = false;                    // Clear key flag
             if(MotStat == true)                // Do this if motor is on
             {   
                current = current + Degrees;       // Add new degrees to current total
                if(current >= 360)
                  {
                     current = (current - 360);       // Keep it under 360
                     CSteps = 0; 
                  }
                if(current == 0)                  // Prevents "0/360" error below
                  {
                     ToMove = Degrees * (Multiplier/360); // Figure out how many steps to move
                     CSteps = 0;                  // Reset  cumulative step counter
                  }
                else
                  {
                     ToMove = (current/360) * Multiplier + 0.5 - CSteps; // This is where the magic happens!
                     CSteps = CSteps + ToMove;   // Add moved steps to step counter
                  }
               // More stuff follows to move table and update display...
             }
```

I had to do some extra stuff to move counter clockwise because if you step backward past zero degrees the original code failed.


```
case '1':                             // Rotate CCW
             newKey = false;                    // Clear key flag
             if(MotStat == true)               // Do this if motor is on
                {
                   current = current - Degrees;
                   if(current < 0)
                      {
                         current = (current + 360);      // Add new degrees to current total
                         CSteps = (Multiplier - CSteps); // Add table steps/rev to total steps
                      }
                   if(current == 0)                  // Prevents "0/360" error below
                      {
                         ToMove = Degrees * (Multiplier/360);
                         CSteps = 0;                
                      }
                   else
                      {
                          ToMove = CSteps + 0.5 - (current * Multiplier)/360; // More magic!
                          CSteps = CSteps - ToMove;
                      }
                   // More stuff follows to move table and update display...
                }
```

I'll be back to explain this after some chores.

Eric


----------



## WobblyHand (Nov 21, 2022)

EricB said:


> WARNING! Programming Stuff Follows!
> 
> After some sleep loss I have some working code with error correction.
> 
> ...


Why don't you enclose that in the </> code tag?  It makes it a lot easier to read.  Use C language, or C like.  It preserves the C formatting, unlike the ordinary forum SW.


----------



## EricB (Nov 21, 2022)

WobblyHand said:


> Why don't you enclose that in the </> code tag?  It makes it a lot easier to read.  Use C language, or C like.  It preserves the C formatting, unlike the ordinary forum SW.


OK, I found the button to do that, but I think you assume I formatted the code with indents and all in the first place. I fixed that.


----------



## WobblyHand (Nov 21, 2022)

EricB said:


> OK, I found the button to do that, but I think you assume I formatted the code with indents and all in the first place.


Well, as a person who does program in python and in C, formatting helps.  In python, the formatting can matter a lot.  

While not required for the C compiler, it sure makes it easier for other humans to read it.  And a lot easier for one to get help, if needed.  Generally it is a good thing to format your code neatly and consistently.  Just an observation, based on about five decades of coding.

Have to laugh whenever I see the word magic in code.   I have often written comments in my code, highlighting some "clever" section, describing how it works, and telling the _future me_ not to mess with it, else I will break everything!  Every time I have disregarded the _past me_, I have regretted it, because things did indeed break.


----------



## EricB (Nov 21, 2022)

I still fails sometimes if I step past zero.

It's been a while since my computer science classes so I'm out of the formatting habit for my own use. I'll have to work on that.

The magic is changing data types. "ToMove" is a long int while "current" and "Degrees" are float. When you put the float into the int it truncates the value and ToMove only gets full steps.


----------



## WobblyHand (Nov 21, 2022)

EricB said:


> I still fails sometimes if I step past zero.
> 
> It's been a while since my computer science classes so I'm out of the formatting habit for my own use. I'll have to work on that.
> 
> The magic is changing data types. "ToMove" is a long int while "current" and "Degrees" are float. When you put the float into the int it truncates the value and ToMove only gets full steps.


That kind of stuff worries me.  Compiler rules and behavior can change over time...  Not saying it is true for you, but I have experienced that.  Is it that the Arduino can't do the math reliably and convert to integer steps?  I guess that is what you are doing?

Failing steps past zero sounds like a unsigned integer problem.  All the integers need to be signed, not uint16_t, or uint32_t.  Need to be int16_t or int32_t.  I manually do the math for going negative to see if it is correct.  What you find may surprise you.  It happens to me, occasionally, which is why I check my math...


----------



## EricB (Nov 21, 2022)

Changing data types is not the way I would have done it from scratch. I would have expected a compiler error. I started with someone else's code so I can't answer for their choices. The code is really a mess right now and I'm beating it until it breaks. Once I have the features I want I'll start again.

I think the arduino can handle the math fine. Stepper drivers need to get whole number inputs. Dividing 360 degrees doesn't always give a whole number result. I recall there is a method in C++ to separate the whole number from the remainder (modulo?). The whole number portion could be multiplied by the number of steps/degree to move the table. The remainders could be accumulated to add an extra step when their value + .5 exceeds 1 to keep things close.

I've found passing zero using an arduino is a pain. My first impulse was just don't allow it, but I know there are times I would need to. I'll get there eventually.

I have some lines is places to output the variables to the serial port so I can see how they change. I just have not tried every combination of inputs yet. 

I need to dust off my books and refresh my memory.


----------



## WobblyHand (Nov 21, 2022)

EricB said:


> Changing data types is not the way I would have done it from scratch. I would have expected a compiler error. I started with someone else's code so I can't answer for their choices. The code is really a mess right now and I'm beating it until it breaks. Once I have the features I want I'll start again.
> 
> I think the arduino can handle the math fine. Stepper drivers need to get whole number inputs. Dividing 360 degrees doesn't always give a whole number result. I recall there is a method in C++ to separate the whole number from the remainder (modulo?). The whole number portion could be multiplied by the number of steps/degree to move the table. The remainders could be accumulated to add an extra step when their value + .5 exceeds 1 to keep things close.
> 
> ...


Modulo returns the remainder.  7 % 3 = 1.  8 % 3 = 2.  9 % 3 = 0  In C, % is only defined for integers.  If you want to do modulo with floats then the function is fmod.


----------



## EricB (Nov 21, 2022)

WobblyHand said:


> That kind of stuff worries me.  Compiler rules and behavior can change over time...  Not saying it is true for you, but I have experienced that.  Is it that the Arduino can't do the math reliably and convert to integer steps?  I guess that is what you are doing?
> 
> Failing steps past zero sounds like a unsigned integer problem.  All the integers need to be signed, not uint16_t, or uint32_t.  Need to be int16_t or int32_t.  I manually do the math for going negative to see if it is correct.  What you find may surprise you.  It happens to me, occasionally, which is why I check my math...



Yes, for some reason after passing zero the math procedure sends a negative step count the the motor procedure.

Still looking.


----------



## WobblyHand (Nov 21, 2022)

Naively, if there is a negative step count, shouldn't one take the absolute value of the counts and reverse the direction to the stepper?  I have not looked at the code.


----------



## EricB (Nov 21, 2022)

WobblyHand said:


> Naively, if there is a negative step count, shouldn't one take the absolute value of the counts and reverse the direction to the stepper?  I have not looked at the code.



Yes, that will keep the steps going in the correct direction, but it masks the problem that it should not go negative in the first place.


----------



## WobblyHand (Nov 21, 2022)

EricB said:


> Yes, that will keep the steps going in the correct direction, but it masks the problem that it should not go negative in the first place.


Why can't they go negative?  Again, perhaps a naive question.


----------



## EricB (Nov 21, 2022)

I found it! I had a condition while moving CCW that I wasn't testing for "current == 360". I was resetting the counters for everything but that. In operation my display should never show 360 degrees, only "0.00".

More testing needed but everything appears to work now.


----------



## WobblyHand (Nov 21, 2022)

Great!


----------



## EricB (Nov 21, 2022)

WobblyHand said:


> Why can't they go negative?  Again, perhaps a naive question.


Because I don't want them to. Simple as that. If all the computations are positive It's easier for me to model on a spreadsheet.


----------



## EricB (Nov 22, 2022)

Well I thought I had it fixed. I fails in different ways based on the number of divisions entered. Works perfect for some. For others it gets weird.
At 7 divisions the motor runs backward when you pass zero, at 13 the motor stops and skips that step. Maybe it's superstitious.

Hmmm...


----------



## EricB (Nov 22, 2022)

I have it sorted out now. The borrowed code I started with was using the same procedure to move the motor and update the display, making the motor dependent to the display. I got rid of that and now the motor procedure acts like it should for whatever the input is. The display updates are now dependent on the motor position and are calculated separately. When I started I had made a comment in the code that the main procedure had too much stuff going on. Little did I know.


----------



## EricB (Nov 24, 2022)

After a couple of weeks I have software I can use. It's accurate at least and, aside from the occasional scrambling of my special display characters, it hasn't failed in use.

It only has the two modes, Degree and Divisions. I can jog by entering a number of degrees and holding the movement key. For "continuous" I just set 1 division or 360+ degrees.

It's meant to do all the movement going clockwise in order to control backlash. It compensates for counter clockwise backlash by overshooting the reading and coming back to it from clockwise. My table has .2 degrees backlash, so the overshoot is .3 degrees. It works.

It will not make initial moves going counter clockwise. It will also not drive past the origin point going counter clockwise. I tried to figure out why I would want to index while counting backward and decided it was a bad idea.

It can toggle the motor on and off to make manual adjustments, and reminds the user of the motor status.

It can vary the stepping speed between moves, once the motor has stopped.

I think I've covered the situations where I plan to use it.

Eric


```
//Program for controlling a single stepper motor driving a rotary table.
//Uses I2C 4x4 matrix keypad for entry of degrees or number of divisions, and direction to move the table.
//Uses I2C 20 character by 4 line display.
//Uses bipolar stepper motor with TB6560 driver.



#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <I2CKeyPad.h>


//SETUP VARIABLES
#define dirPin 2
#define stepPin 3
#define enablePin 5

//CUSTOM CHARACTER BIT MAPS
//NOTE: These don't always display correctly without performing softwareReset()!
byte customChar0[8] = { //this is a CCW arrow symbol
  0b10110,   // * **
  0b11001,   // **  *
  0b11101,   // *** *
  0b00001,   //     *
  0b00001,   //     *
  0b10001,   // *   *
  0b01110,   //  ***
  0b00000
};

byte customChar1[8] = { //this is a CW arrow symbol
  0b01101,   //  ** *
  0b10011,   // *  **
  0b10111,   // * ***
  0b10000,   // *
  0b10000,   // *
  0b10001,   // *   *
  0b01110,   //  ***
  0b00000
};

const uint8_t KEYPAD_ADDRESS = 0x20; //keypad I2C address
const uint8_t LCD_ADDRESS = 0x3f;    //display I2C address
const int stepsPerRevolution = 400;  //motor steps per revolution
const int TableRatio = 72;           //ratio of rotary table shaft turns to 360 degrees
const long Multiplier = (stepsPerRevolution * TableRatio);
const int MaxSpeed = 500;            //minimum step delay for motor
const int MinSpeed = 1250;           //maximum step delay for motor

char keymap[19] = "147.2580369#ABCDNF"; //keypad map, N = NoKey, F = Fail, listed by columns

char key;                     //input from keypad
float Degrees = 0.;           //Degrees/move desired
long ToMove = 0;              //Steps to move
float Divisions;              //number of table postitions desired
float current = 0.;           //expected final table position after move in degrees
long cumSteps = 0;            //total steps sent to motor
int Mode = 0;                 //selected operating mode
int MotorSpeed = 500;         //initial motor step delay in micro seconds
bool newKey = false;          //was a key pressed status flag, set to true when a new key is read
bool MotStat = true;          //motor dirver enable status flag



//CREATE EXTERNAL DEVICES

I2CKeyPad keyPad(KEYPAD_ADDRESS); //create keyPad

LiquidCrystal_I2C lcd(LCD_ADDRESS,20,4);  // create 20x4 lcd



//START OF PROGRAM//

void setup()
{
  lcd.createChar(0, customChar0); //create CCW arrow character from bit map
  lcd.createChar(1, customChar1); //create CW arrow character from bit map
 
  pinMode(stepPin, OUTPUT);       //motor control pins as outputs
  pinMode(dirPin, OUTPUT);
  pinMode(enablePin, OUTPUT);

  digitalWrite(enablePin, LOW); //enable motors now or nothing works
 
  if (keyPad.begin() == false) //make sure keypad is working
    {
        lcd.setCursor(0,0);
        lcd.print("Keypad Fail"); //tell if it isn't
        delay(2500);
        lcd.clear();
      while (1);
    }

  keyPad.loadKeyMap(keymap); //create the keymap for the keypad
 
  lcd.init();                  //initialize the lcd
  lcd.backlight();             //turn on the backlight
  lcd.setCursor(0,0);
  lcd.print("       Eric's");  //display welcome message
  lcd.setCursor(0,1);
  lcd.print("   Rotary Indexer");
  lcd.setCursor(0,2);
  lcd.print("    Version 3.0");
  lcd.setCursor(7,3);
  lcd.write((byte)0);         //display custom characters
  lcd.print("   ");
  lcd.write((byte)1);
  delay(2500);
}



//This procedure restarts program from beginning but does not reset the peripherals and registers
//This uses an absolute call to the staring point in the processor memory
//Bang the Hardware!
void software_Reset()
{
  asm volatile ("  jmp 0"); //jump to start of processor memory
}



//This procedure pulses the step pin on the driver.
//Stopping the motor during this procedure will require a hard reset or panic switch!
//Inputs: movement amount in steps
//Uses: MotorSpeed
void rotation(long tm)
{
  for (int i = 0; i < tm; i++) {
    // These four lines result in 1 step:
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(MotorSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(MotorSpeed);
  }
}



//This procedure checks for a new keypress by polling the keypad and updates the value of key
//Modifies: char key, bool newKey
void GetKey()
{
  if (keyPad.isPressed())   //check if a key has been pressed
  {
    key = keyPad.getChar(); //this gets the mapped char of the last key pressed
    delay(250);             //allow time to release key
    newKey = true;          //tell rest of program "key" has new value
  }
}



//This procedure gets a floating point number with decimal from the keypad and returns it
//to the calling procedure.
//Uses: GetKey()
//Modifies: bool newKey
//Returns: float decnum
float GetNumber()
{
   float num = 0.00;
   float decimal = 0.00;
   float decnum = 0.00;
   int counter = 0;
   GetKey();

   lcd.setCursor(0,0);lcd.print("Enter degrees then");lcd.setCursor(0,1);lcd.print("    press [#].");
   lcd.setCursor(0,3);lcd.print("  Restart [D]");
   lcd.setCursor(8,2);
   bool decOffset = false;

   while(key != '#')
   {
    if (newKey == true)
    {
      switch (key)
      {
         case 'N':
            break;
        
         case '.':
           if(!decOffset)
           {
             decOffset = true;
           }
            lcd.print(key);
            newKey = false;
            break;
      
         case '0': case '1': case '2': case '3': case '4':
         case '5': case '6': case '7': case '8': case '9':
           if(!decOffset)
           {
            num = num * 10 + (key - '0');
            lcd.print(key);
           }
           else if((decOffset) && (counter <= 1))
           {
            num = num * 10 + (key - '0');
            lcd.print(key);
            counter++;
           }
           newKey = false;
           break;

         case 'D':
           software_Reset();
         break;
      }

      decnum = num / pow(10, counter);
 
   }
   newKey = false;
   GetKey();
   }
  return decnum;
}



//This procedure gets a floating point number with no decimal from the keypad and returns it
//to the calling procedure.
//Uses: GetKey()
//Modifies: bool newKey
//Returns: float num
float GetDivisions()
{
   float num = 0.00;
   GetKey();

   lcd.clear();
   lcd.setCursor(0,0);lcd.print("Enter Divisions then");lcd.setCursor(0,1);lcd.print("     press [#].");
   lcd.setCursor(0,30);lcd.print("  Restart [D]");
   lcd.setCursor(8,2);

   while(key != '#')
   {
     if(newKey == true)
     {
       switch (key)
       {
         case 'N':
            break;
        
         case '0': case '1': case '2': case '3': case '4':
         case '5': case '6': case '7': case '8': case '9':
            num = num * 10 + (key - '0');
            lcd.print(key);
            newKey = false;
            break;
    
        case 'D':
          software_Reset();
          break;
       }
     }
     newKey = false;
     GetKey();
   }
  return num;
}



//This procedure gets the selected mode from keypad and returns it tothe main loop.
//Allows user to reset program.
//Uses: GetKey(), software_Reset()
//Modifies: bool newkey
//Returns: int mode
int GetMode()
{
  int mode = 0;
  lcd.clear();
  lcd.setCursor(0,0);lcd.print("    Select Mode");
  lcd.setCursor(0,1);lcd.print("   [A] Divisions");
  lcd.setCursor(0,2);lcd.print("   [B] Degrees");
  while(mode == 0)
    {
     GetKey();
     if(key == 'A')
       {
         mode = 1;
       }
     if(key == 'B')
       {
         mode = 2;
       }
     if(key == 'D')
       {
         software_Reset(); //hidden reset in case **** don't work
       }
    }
  newKey = false;
  lcd.clear();
  return mode;
}



//There is too much stuff happening in the main loop!
//Uses: GetMode(), GetDivisions(), GetNumber(), rotation(), MotorOn(), MotorOff(), ChgSpeed()
//      updateDisplay(), GetKey()
//Modifies: Divisions, Degrees, current, cumSteps, newKey, ToMove, dirPin
void loop()
{
  Mode = GetMode();
  if(Mode == 1)
    {
      Divisions = GetDivisions(); //get desired divisions from keypad
      Degrees = (360/Divisions);  //convert to degrees
    }
  if(Mode == 2)
    {
      Degrees = GetNumber();      //get desired degrees from keypad
    }
    current = 0;   //reset current position with mode change
    cumSteps = 0;  //reset step counter with mode change
 
    lcd.clear();   //setup the display for table movements
    lcd.setCursor(0,0);lcd.print("Degrees/Move: ");lcd.print(Degrees);
    lcd.setCursor(0,1);lcd.print("POS: ");lcd.print(current);
    lcd.setCursor(1,3);lcd.write((byte)0);lcd.print(" [1] ");
    lcd.write((byte)1);lcd.print(" [3] Canx[C]");
 
    GetKey();      //get input from user

    while(key != 'C')              //mode not cancelled
      {
        if(newKey == true)         //a key has been pressed
          {
            switch(key)            //see if it's one we care about
              {
               case 'N':           //No key press, not sure if this case can happen
                 break;            //but if it does, this will save a few clock cycles
      
               case '3':                            // Rotate CW
                 newKey = false;                    // Clear key flag
                 if(MotStat == true)                // Do this if motor is on
                   {
                     current = current + Degrees;   // Add new degrees to current total
                     ToMove = (current/360) * Multiplier + 0.5 - cumSteps; // this eliminates partial steps
                     cumSteps = cumSteps + ToMove;     // Add move to step counter

                     lcd.setCursor(0,3);lcd.print("                    ");
                     lcd.setCursor(0,2);
                     lcd.print("     Moving ");
                     lcd.write((byte)1);             //display CW character
                     digitalWrite(dirPin, HIGH);
                     rotation(abs(ToMove));         //don't know if abs() is still needed
                     updateDisplay();
                   }
                 else                               //if motor is off
                   {
                     lcd.setCursor(0,2);
                     lcd.print("    Motor OFF");   //just a reminder
                     delay(1000);
                     lcd.setCursor(0,2);
                     lcd.print("                 ");
                   }
                 break;
      
               case '1':                           // Rotate CCW
                 newKey = false;                   // Clear key flag
                 if(MotStat == true)               // Do this if motor is on
                   {
                     current = current - Degrees;
                     if(cumSteps <= 0)             //prevents first move in reverse
                       {
                         current = 0;
                         break;
                       }
                     else
                       {
                         ToMove = cumSteps + 0.5 - (current * Multiplier)/360; // this eliminates partial steps
                         cumSteps = cumSteps - ToMove;
                       }
                  
                     lcd.setCursor(0,3);lcd.print("                    ");
                     lcd.setCursor(0,2);
                     lcd.print("     Moving ");
                     lcd.write((byte)0); //display CCW character
                     digitalWrite(dirPin, LOW);
                     rotation(abs(ToMove));        //don't know if abs() is still needed
                     rotation(24);                 //add .3 degrees overshoot for backlash
                     //delay(2000);                  //just for testing
                     digitalWrite(dirPin, HIGH);
                     rotation(24);                 //bring it back .3 degrees to target setting
                     updateDisplay();
                   }
                 else                              //if motor is off
                   {
                     lcd.setCursor(0,2);
                     lcd.print("    Motor OFF");   //just a reminder
                     delay(1000);
                     lcd.setCursor(0,2);
                     lcd.print("                 ");
                   }
                 break;

               case '4':
                 newKey = false;
                 MotorOff();
                 break;

               case '6':
                 newKey = false;
                 MotorOn();
                 break;

               case '9': case '7':
                 newKey = false;
                 ChgSpeed();
                 break;

             newKey = false;
            }
        }
          GetKey();
      }
      lcd.clear();
}


//This proceedure updates display after table moves to show current degrees always < 360.
//Uses: current
void updateDisplay()
{
  float dd;
  dd = current;
  while(dd >= 360){
    dd = dd - 360;
  }
  lcd.setCursor(0,2);lcd.print("                    ");
  lcd.setCursor(4,1);lcd.print("        ");
  lcd.setCursor(5,1);lcd.print(dd);
  lcd.setCursor(1,3);lcd.write((byte)0);lcd.print(" [1] ");
  lcd.write((byte)1);lcd.print(" [3] Canx[C]");
}



//This procedure changes the step delay to increase or decrease motor speed.
//Uses: key, MinSpeed, MaxSpeed
//Modifies: MotorSpeed
void ChgSpeed()
{
      if (key == '9')
        {
          if (MotorSpeed > MaxSpeed)         //has it reached maximum speed?
            {
              MotorSpeed = MotorSpeed - 250; //decrease step delay by 250 micro seconds
            }
        }
      if (key == '7')
        {
          if (MotorSpeed <= MinSpeed)        //has it reached minimum speed?
            {
              MotorSpeed = MotorSpeed + 250; //increase step delay by 250 micro seconds
            }
        }
        lcd.setCursor(0,2);
        lcd.print("  Motor Speed: ");
        lcd.print(MotorSpeed);
        delay(500);
        lcd.setCursor(0,2);
        lcd.print("                    ");
}


//This procedure disables the motor driver alowing motor shaft to be turned by hand
//Uses: MotStat
//Modifies: enablePin, MotStat
void MotorOff()
{
  if (MotStat == true)
    {
      digitalWrite(enablePin, HIGH);
      lcd.setCursor(0,2);
      lcd.print("    Motor OFF");
      delay(1000);
      lcd.setCursor(0,2);
      lcd.print("                 ");
      MotStat = false;
    }
}

//This procedure enables the motor driver and locks the motor shaft
//Uses: MotStat
//Modifies: enablePin, MotStat
void MotorOn()
{
  if (MotStat == false)
    {
      digitalWrite(enablePin, LOW);
      lcd.setCursor(0,2);
      lcd.print("    Motor ON");
      delay(1000);
      lcd.setCursor(0,2);
      lcd.print("                 ");
      MotStat = true;
    }
}


//END OF PROGRAM//
```


----------



## EricB (Nov 26, 2022)

WobblyHand said:


> That kind of stuff worries me.  Compiler rules and behavior can change over time...  Not saying it is true for you, but I have experienced that.  Is it that the Arduino can't do the math reliably and convert to integer steps?  I guess that is what you are doing?
> 
> Failing steps past zero sounds like a unsigned integer problem.  All the integers need to be signed, not uint16_t, or uint32_t.  Need to be int16_t or int32_t.  I manually do the math for going negative to see if it is correct.  What you find may surprise you.  It happens to me, occasionally, which is why I check my math...


I found the correct method to cast data types from floating point to integer is "y = int(x);" where y is the integer variable and x is the floating point variable. I guess the arduino compiler doesn't always need the explicit command.


----------



## EricB (Dec 3, 2022)

Here is a picture of the working controller with Version 4 software almost ready to go in the box. All the code I've published is now obsolete. I stripped out all the fancy stuff that never really worked, reworked some of the procedures, added a hardware interrupt for the keypad, and added a very nice (IMHO) Jog mode.

The jog mode works in two ways. 1) The table will turn while the CCW or CW (1 or 3) key is held and stop when the key is released, which is what I wanted. Or 2) the table run continuously if the 1 or 3 key is tapped and released quickly, and stop when the key or any other key in the same column is pressed. This is a quirk of the interrupt timing. I didn't expect it but I like the results.


----------



## EricB (Dec 8, 2022)

Here is a video of my Jog mode after a couple of mods:


----------



## EricB (Dec 26, 2022)

At long last it's in the box. I put an acrylic lens over the display to protect it. I still need to put a mask behind the lens to hide the cut out, and trim the screws for the display and key pad, but it works and does what I want it to do.

I had this odd idea that a black case would look good. Maybe not.


----------



## wrat (Dec 27, 2022)

EricB said:


> I had this odd idea that a black case would look good. Maybe not.


Certainly looks good ENOUGH, anyway.
Very nice.


----------



## greenail (Dec 30, 2022)

wrat said:


> Certainly looks good ENOUGH, anyway.
> Very nice.


here's one with a pneumatic lock that uses a stack of belleville washers, not sure how to do that in the vertical position but maybe worth playing with.


----------

