//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//