This is my .ino
Setup:
Using Arduino IDE, compile and upload this program onto a Teknic Clearcore.
***Make sure to have the Teknic libraries and board installed through their respective managers.
Physical requirements:
Clearcore Controller
Clearpath Motor
Limit switches mounted on clamp/bandsaw
4D Systems Display
Appropriate power and data cables
Respective power supplies (24VDC for Clearcore, 75VDC for Clearpath)
***Before attempting to run this program, make sure the motor has been tuned to the load it will move
***This requires a programming cable and the ClearPath MSP software
Connect motor to port labelled "M0" on the Clearcore
4D systems display to COM1 via cat5 or better ethernet cable.
Homing probe to A-9
Bandsaw limit switch to DI-6
*/
#include "Blade_Saw.h"
#include "Servo_Motor.h"
#include "HomeSensor.h"
#include <genieArduinoDEV.h>
#include <ClearCore.h>
//
// User defined parameters. Defaults shown in comment
float OffsetMM = 116.02; //Current offset is 116.11 MM, measured from back of clamp to blade when homed.
Genie genie;
//----------------------------------------------------------------------------------------
// Define the ClearCore COM port connected to the HMI
// This must be done using both the Arduino wrapper "Serialx" and ClearCore library "ConnectorCOMx"
// because both interfaces are used below
#define SerialPort Serial1 // ClearCore UART Port, connected to 4D Display COM1
#define CcSerialPort ConnectorCOM1 // ClearCore UART Port, connected to 4D Display COM1
//----------------------------------------------------------------------------------------
// ClearCore Baud Rate, for 4D Display
#define baudRate 9600
int i, k;
char text[100];
int loops = 0; // Used for the Form0 animation sequence
int CurrentForm = -1; // The current Form/Page we are on right now
int PreviousForm = -1; // The Form/Page we came from
int LEDDigitToEdit = -1; // The LED Digit index which will be edited
int DigitsToEdit = -1; // The number of digits in the LED Digit being edited
int MotorProgInputRes = 6400; // Motor Programmed Input Resolution (Change this if you change the motor value in MSP)
int SecondsInMinute = 60; // Seconds in a minute
//Stored Variables and defaults
int MoveDist = 0;
int MoveDistLast = 0;
bool fault = false;
int NextForm = 0;
int CutPosition = 0;
int PositionTarget = 0;
int UserDist = 0;
float UnitMM = 1256.7739391179;//steps per mm
float UnitIN = 31921.6922438822;
float UnitFactor = UnitMM; //Default: Millimeters to steps
float LengthMin = OffsetMM*UnitMM; //Offset to account for clamp depth and distance from blade
float LengthMax = 550000; //Length in steps from blade
bool UserUnits = true;
String Units = "Millimeters"; //Default Units
float UnitMin = LengthMin/UnitMM; //Minimum length in millimeters, updated later
float UnitMax = LengthMax/UnitMM; //Maximum length in millimeters, updated later
//Genie Form Item indeces - These correspond to internal labels for the genie objects
int DistGenieNum = 0;
int StartProcessGenieNum = 0;
int faultLEDGenieNum = 0;
int ClrfaultGenieNum = 1;
int DistEditGenieNum = 2;
int UnitSwitchNum0 = 0;
int UnitSwitchNum1 = 1;
int StopMotionGenieNum = 3;
int GoMainGenieNum = 4;
int ClampConfirmGenieNum = 5;
int FinishedCuttingGenieNum = 6;
int CutSameGenieNum = 7;
int NewCutGenieNum = 8;
int EditInputDigitNum = 1;
int BackGenieNum = 9;
int KeypadGenieNum = 0;
char keyvalue[10]; // Array to hold keyboard character values
int counter = 0; // Keyboard number of characters
int temp, sumTemp; // Keyboard Temp values
void setup() {
InitMotorParams();
InitHoming();
// Sets up serial communication and waits up to 5 seconds for a port to open.
// Serial communication is not required for this example to run.
Serial.begin(baudRate);
// Open the HMI serial port and wait until communication has been established
ConnectorCOM1.RtsMode(SerialBase::LINE_OFF);
SerialPort.ttl(true); // Set the Clearcore UART to be TTL
SerialPort.begin(baudRate); // Set up Serial1 using the Arduino Serial Class @ 9600 Baud
Serial.println("Comms Open");
while (!SerialPort) {
continue;
}
delay(3000); // Delay to allow Terminal to wake up to capture first debug messages
while (!genie.Begin(SerialPort));
if (genie.IsOnline()) // When the display has responded above, do the following once its online
{
genie.AttachEventHandler(myGenieEventHandler); // Attach the user function Event Handler for processing events
Serial.println("Genie attached");
}
resetMotor();
genie.SetForm(0); // Change to Form 0
CurrentForm = 0;
genie.WriteContrast(15); // Max Brightness (0-15 range)
}
void loop() {
static unsigned long waitPeriod = millis();
//Need to keep monitoring both home sensor and blade states
detectMotorStates(CutPosition);
detectBladeState();
genie.DoEvents(); // This calls the library each loop to process the queued responses from the display
// waitPeriod later is set to millis()+50, running this code every 50ms
if (millis() >= waitPeriod)
{
CurrentForm = genie.GetForm(); // Check what form the display is currently on
switch (CurrentForm)
{
/************************************* FORM 0 *********************************************/
case 0: // If the current Form is 0 - Splash Screen
// Keeping the splash screen open for 5 seconds
delay(1000);
genie.SetForm(1); // Change to main screen
break;
/************************************* FORM actions *********************************************/
case 1: //main screen
if (UserDist != MoveDistLast)
{
genie.WriteObject(GENIE_OBJ_LED_DIGITS, DistGenieNum, UserDist); // Update Move Distance
MoveDistLast = UserDist;
}
break;
case 2: //Motor In Motion Screen
detectMotorStates(PositionTarget);
if(MotorLocationState == MOTOR_IN_CUT_POSITION)
{
if(MotorRunState == MOTOR_STOPPED)
{
delay(500);
genie.SetForm(NextForm); //Switch to whatever NextForm is
}
}
break;
case 3: //Bolt Clamp Confirmation screen
break;
case 4://Start Cut Screen
break;
case 5: //Cut Finished Screen
break;
case 6: //Edit Length Screen
break;
}
// If a new fault is detected, turn on the fault LED
if (motor.StatusReg().bit.AlertsPresent && !fault)
{
fault = true;
Serial.println(" status: 'In Alert'");
genie.WriteObject(GENIE_OBJ_USER_LED, 0, 1);//Set user led 1, to value 1(On)
}
// If the fault has sucessfully been cleared, turn off the fault LED
else if (!motor.StatusReg().bit.AlertsPresent && fault)
{
fault = false;
genie.WriteObject(GENIE_OBJ_USER_LED, 1, 0);
}
waitPeriod = millis() + 50; // rerun this code in another 50ms time.
}
}
void myGenieEventHandler(void)
{
genieFrame Event;
genie.DequeueEvent(&Event); // Remove the next queued event from the buffer, and process it below
//If the cmd received is from a Reported Event (Events triggered from the Events tab of Workshop4 objects)
if (Event.reportObject.cmd == GENIE_REPORT_EVENT)
{
if (Event.reportObject.object == GENIE_OBJ_4DBUTTON) // If the Reported Message was from a 4DButton
{
/***************************** Form 4D Buttons **************************/
//None used
}
if (Event.reportObject.object == GENIE_OBJ_ISWITCH) // If the Reported Message was from a ISwitch
{
/***************************** Form Switch **************************/
//Check for Switch Change
if (Event.reportObject.index == UnitSwitchNum0) //the index of the Main Screen UnitSelect Switch
{
UserUnits = genie.GetEventData(&Event); //Read the value of the UnitSelect switch
// write object
genie.WriteObject(GENIE_OBJ_ISWITCH, UnitSwitchNum1, UserUnits);
//ON is Inches, Off is millimeters
Serial.print("Units changed. ");
if(UserUnits)
{
UnitFactor = UnitIN;//Inches to steps
Units = "Inches";
UnitMin = LengthMin/UnitFactor;//steps to inches
UnitMax = LengthMax/UnitFactor;//steps to inches
}else
{
UnitFactor = UnitMM;//Millimeters to steps
Units = "Millimeters";
UnitMin = LengthMin/UnitFactor;//steps to mm
UnitMax = LengthMax/UnitFactor;//steps to mm
}
Serial.print(Units);
}else if (Event.reportObject.index == UnitSwitchNum1) //the index of the Edit Screen UnitSelect Switch
{
UserUnits = genie.GetEventData(&Event); //Read the value of the UnitSelect switch
// write object
genie.WriteObject(GENIE_OBJ_ISWITCH, UnitSwitchNum0, UserUnits);
//ON is Inches, Off is millimeters
Serial.print("Units changed. ");
if(UserUnits)
{
UnitFactor = UnitIN;//Inches to steps
Units = "Inches";
UnitMin = LengthMin/UnitFactor;//steps to inches
UnitMax = LengthMax/UnitFactor;//steps to inches
}else
{
UnitFactor = UnitMM;//Millimeters to steps
Units = "Millimeters";
UnitMin = LengthMin/UnitFactor;//steps to mm
UnitMax = LengthMax/UnitFactor;//steps to mm
}
Serial.print(Units);
}
}
if (Event.reportObject.object == GENIE_OBJ_WINBUTTON) // If the Reported Message was from a WinButton
{
/*
//check for back button press
if (Event.reportObject.index == BackGenieNum) // If Winbutton3 (Index = 3) - 0 Information Back
{
genie.SetForm(3); // Change to Main Screen
}
*/
/***************************** Main Screen Winbuttons **************************/
//Check for clear fault press
if (Event.reportObject.index == ClrfaultGenieNum) //8 is the index of the clear fault button
{
Serial.println(" Clearing fault if present");
if (motor.StatusReg().bit.AlertsPresent) // If the ClearLink has an Alert present
{
if (motor.StatusReg().bit.MotorInFault) // Check if there also is a motor shutdown
{
InitMotorParams();
motor.EnableRequest(false);
delay(10);
motor.EnableRequest(true); // Cycle the enable to clear the motor fault
}
motor.ClearAlerts(); // Clear the Alert
}
}
//check for edit press
else if (Event.reportObject.index == DistEditGenieNum) // If Winbutton6 (Index = 6) - 0 Move Distance Edit
{ Serial.println("Edit pressed");
if (MotorRunState == MOTOR_STOPPED)
{
PreviousForm = 1; // Always return to the main screen
LEDDigitToEdit = DistGenieNum; // The LED Digit which will take this edited value
DigitsToEdit = 5; // The number of Digits (4 or 5)
genie.WriteObject(GENIE_OBJ_LED_DIGITS, EditInputDigitNum, 0); // Clear any previous data from the Edit Parameter screen //FIXME
genie.SetForm(6); // Change to Form 6 - Edit Parameter
Serial.println("Edit passed");
}//else alert user something here
}
//Check for Start Process press
else if (Event.reportObject.index == StartProcessGenieNum)
{
if (!motor.StatusReg().bit.AlertsPresent && (MotorRunState == MOTOR_STOPPED) && (BladeState == BLADE_DOWN))
{
NextForm = 3; //Go to Clamp Confirmation after MotorMotion screen
PositionTarget = LoadPosition;
genie.SetForm(2);
delay(1500);//Let user see the screen
UserSeeksHome();
Serial.print("Broke home loop, States: (Running,Location)");
Serial.print(MotorRunState);
Serial.println(MotorLocationState);
delay(100);
MoveAbsolutePosition((int)LoadPosition); //Move to Loading position
Serial.println("Start passed");
// Serial.println(NextForm);
}
}
/***************************** Motor In Motion Screen Winbutton **************************/
if (Event.reportObject.index == StopMotionGenieNum) // If the stop button is pressed
{
motor.MoveStopAbrupt(); //Immediately stop the motor
genie.SetForm(1); //return to main screen
}
/***************************** Clamp Confirmation Screen Winbuttons **************************/
if (Event.reportObject.index == GoMainGenieNum) // If 'Go Back' is pressed
{
if (MotorRunState == MOTOR_STOPPED)
{
genie.SetForm(1); //Return to main screen
}
}
if (Event.reportObject.index == ClampConfirmGenieNum) // If Proceed is pressed
{
if (MotorRunState == MOTOR_STOPPED)
{
if (BladeState == BLADE_UP)
{
NextForm = 4; //go to Begin cutting screen after MotorMotion Screen
CutPosition = abs(UserDist/100*UnitFactor-LengthMin);
PositionTarget = CutPosition;
genie.SetForm(2); //Motor in Motion Screen
delay(1000);
Serial.println(CutPosition);
Serial.println(UserDist);
Serial.println(UnitFactor);
MoveAbsolutePosition((int)CutPosition);
/*
Needs to be scaled from user input (Inches/millimeters) to steps
CutPosition = UserDist*UnitFactor-LengthMin
CutPosition - Value in steps of distance for bolt cutting (Int)
UserDist - Input from user, could be Inches/Millimeters (Int)
UnitFactor - Conversion from Inches/Millimeters to steps, dependent on UserUnit Value (Int)
UserUnit - Set by user on Main Screen, sets the value of UnitFactor (Bool)
LengthMin - Smallest Bolt that can be cut because of clamp depth and its distance to the blade (Int steps, predetermined)
*/
}
}
}
/***************************** Begin Cutting Screen Winbutton **************************/
if (Event.reportObject.index == FinishedCuttingGenieNum) // If Finished cut is pressed
{
if (MotorRunState == MOTOR_STOPPED)
{
if (BladeState == BLADE_DOWN)
{
genie.SetForm(5);//Go to cut finished screen
}
}
}
/***************************** User Finished Screen Winbutton **************************/
if (Event.reportObject.index == CutSameGenieNum) // If Cut same size is pressed
{
if (MotorRunState == MOTOR_STOPPED)
{
genie.SetForm(3); //Go back to Clamp Confirmation
}
}
if (Event.reportObject.index == NewCutGenieNum) // If Cut same size is pressed
{
if (MotorRunState == MOTOR_STOPPED)
{
genie.SetForm(1); //Go back to main screen
}
}
/***************************** Keypad Screen Winbuttons **************************/
if (Event.reportObject.index == BackGenieNum) // If Cancel is pressed
{
if (MotorRunState == MOTOR_STOPPED)
{
//Clear any partially entered values from Keyboard, ready for next time
for (int f = 0; f < 5; f++)
{
keyvalue[f] = 0;
}
counter = 0;
genie.WriteObject(GENIE_OBJ_LED_DIGITS, EditInputDigitNum, 0); // Undo changes visible on keypad
genie.SetForm(PreviousForm); // Change to Previous Form
}
}
}
/***************************** Form 5 Keyboard **************************/
if (Event.reportObject.object == GENIE_OBJ_KEYBOARD)
{
if (Event.reportObject.index == 0) // If keyboard0
{
temp = genie.GetEventData(&Event); // Store the value of the key pressed
if (temp >= 48 && temp <= 57 && counter < DigitsToEdit) // Convert value of key into Decimal, 48 ASCII = 0, 57 ASCII = 9, DigitsToEdit is 4 or 5 digits
{
keyvalue[counter] = temp; // Append the decimal value of the key pressed, into an character array
sumTemp = atoi(keyvalue); // Convert the array into a number
if (DigitsToEdit == 5) // If we are dealing with a parameter which takes a 5 digit number
{
Serial.println(sumTemp);
if (sumTemp > 65535) // If the number is > 16 bits (the max a Genie LED Digit can be sent)
{
sumTemp = 65535; // Limits the max value that can be typed in on a 5 digit parameter, to be 65535 (16 bit number)
}
}
genie.WriteObject(GENIE_OBJ_LED_DIGITS, EditInputDigitNum, sumTemp); // Prints to LED Digit 18 on Form 5 (max the LED digits can take is 65535)
counter = counter + 1; // Increment array to next position ready for next key press
}
else if (temp == 8) // Check if 'Backspace' Key
{
if (counter > 0)
{
counter--; // Decrement the counter to the previous key
keyvalue[counter] = 0; // Overwrite the position in the array with 0 / null
genie.WriteObject(GENIE_OBJ_LED_DIGITS, EditInputDigitNum, atoi(keyvalue)); // Prints the current array value (as an integer) to LED Digit 18 on Form 5
}
}
else if (temp == 13) // Check if 'Enter' Key
{
if(sumTemp > UnitMax*100) // If entered value is above maximum length, default to maximum length
{
sumTemp = UnitMax*100;
Serial.println(sumTemp);
Serial.println(UnitMax);
}
if(sumTemp < UnitMin*100) // If entered value is below minimum length, default to minimum length
{
sumTemp = UnitMin*100;
Serial.println(sumTemp);
Serial.println(UnitMin);
}
int newValue = sumTemp;
//Serial.println(newValue); // for debug
//Clear values ready for next time
sumTemp = 0;
for (int f = 0; f < 5; f++)
{
keyvalue[f] = 0;
}
counter = 0;
UserDist = newValue;
genie.SetForm(PreviousForm); // Return to the Form which triggered the Keyboard
}
}
}
}
}
Blade_Saw.h
/*
* Assumes normally-closed blade state switch
*/
#include "ClearCore.h"
#define BLADE_UP 7
#define BLADE_DOWN 8
#define Blade_State_Pin DI6 //Connect Saw limit switch to DI6
int BladeState;
void setBladeState(int state) {
BladeState = state;
}
int getBladeState() {
return BladeState;
}
//Put the bladesaw's state based on switch on and off on ClearCore
//SET BLADE_UP or BLADE_DOWN
void detectBladeState() {
// Read the state of the limit switch connected to digital I/O 16
int switchState = digitalRead(Blade_State_Pin);
// If the switch is not triggered, set BladeState to BLADE_UP
if (switchState == LOW) {
setBladeState(BLADE_UP);
}
// If the switch is triggered, set BladeState to BLADE_DOWN
else {
setBladeState(BLADE_DOWN);
}
}
servo_motor.h
/*
* This is the header file for the servo controls
* In short, this allows for motor setup and use based on user interactions
* Motor speed can be defined by changing DesiredRPM. This only affects movement towards cutting position.
* Homing has its own speed. Recommended maximum: 500 RPM
* WARNING: Do not exceed the maximum as high speeds are not tested for accuracy or stability
* Recommended value: 350 RPM or less
*/
#include "ClearCore.h"
#define MOTOR_IS_MOVING 3
#define MOTOR_STOPPED 4
#define MOTOR_IN_CUT_POSITION 5
#define MOTOR_NOT_IN_CUT_POSITION 6
// Specifies which connector the motor is on
//Make sure blue cable from motor connects to the M0 connector on the Clear Core
#define motor ConnectorM0
int MotorRunState;
int MotorLocationState;
int LoadPosition = 250000; //Arbitrary position away from blade, about 200 mm
int DesiredRPM = 350;
const uint8_t motorChannel = 0;
const uint8_t encoderChannel = 0;
void InitMotorParams() {
// Sets the input clocking rate. This normal rate is ideal for ClearPath
// step and direction applications.
MotorMgr.MotorInputClocking(MotorManager::CLOCK_RATE_NORMAL);
// Sets all motor connectors into step and direction mode.
MotorMgr.MotorModeSet(MotorManager::MOTOR_ALL, Connector::CPM_MODE_STEP_AND_DIR);
// Set the motor's HLFB mode to bipolar PWM
motor.HlfbMode(MotorDriver::HLFB_MODE_HAS_BIPOLAR_PWM);
// Set the HFLB carrier frequency to 482 Hz
motor.HlfbCarrier(MotorDriver::HLFB_CARRIER_482_HZ);
// Sets the maximum velocity for each move
motor.VelMax(DesiredRPM*60*6400);//Conversion to Revolutions per second
// Set the maximum acceleration for each move
motor.AccelMax(80000);
}
//resetMotor cycles power on the motor. If this succesfully clears the fault state, the fault LED will go out
void resetMotor() {
motor.EnableRequest(false);
delay(10);
motor.EnableRequest(true);
Serial.println("Motor Reset");
delay(10);
}
void setMotorStates(int runState, int locationState) {
MotorRunState = runState;
MotorLocationState = locationState;
}
int getMotorRunState() {
return MotorRunState;
}
int getMotorLocationState() {
return MotorLocationState;
}
void detectMotorStates(int CutPosition)
{
if(motor.PositionRefCommanded() == CutPosition) //Set state based on motor's reported position
{
MotorLocationState = MOTOR_IN_CUT_POSITION;
}else
{
MotorLocationState = MOTOR_NOT_IN_CUT_POSITION;
}
if(motor.StatusReg().bit.StepsActive) //Set state based on motor's activity
{
MotorRunState = MOTOR_IS_MOVING;
}else
{
MotorRunState = MOTOR_STOPPED;
}
}
/*------------------------------------------------------------------------------
* MoveAbsolutePosition
*
* Command step pulses to move the motor's current position to the absolute
* position specified by "position"
* Prints the move status to the USB serial port
* Returns when HLFB asserts (indicating the motor has reached the commanded
* position)
*
* Parameters:
* int position - The absolute position, in step pulses, to move to
*
* Returns: True/False depending on whether the move was successfully triggered.
*/
bool MoveAbsolutePosition(int position) {
// Check if an alert is currently preventing motion
if (motor.StatusReg().bit.AlertsPresent) {
Serial.println("Motor status: 'In Alert'. Move Canceled.");
return false;
}
Serial.print("Moving to absolute position: ");
Serial.println(position);
// Command the move of absolute distance
motor.Move(position, MotorDriver::MOVE_TARGET_ABSOLUTE);
return true;
}
This is everything, I apologize for being a noob. This project was dumped on me and I'm playing catch up lol