Jump to content
 

luce001

Members
  • Posts

    1
  • Joined

  • Last visited

Posts posted by luce001

  1. Firstly I would like to thank Ray for his hard work in starting off this excellent post! also for Eric and friends for the various coding updates..  I have made a few changes to the code and managed to get a working turntable on my layout, including an OLED display to show the position and head/tail indication.  I only have 4 positions on my turntable and I didn't use the servo brake or manual pot but left these functions in.

     

    The display I used was a 1.3" I2C IIC Serial 128X64 OLED LCD LED Display Module Digital for Arduino W which can be obtained easily on eBay... the driver for this is U8glib.h https://code.google.com/p/u8glib/ and the other components as mentioned by others I ordered from Proto-pic.

     

    The revised coding is as follows, pictures to follow...

     

    Jonathan

    ////////////////////////////////////////////////////////////////////////////////
    //
    //   DCC Turntable Rev 17 9 Jan 2015
    //   based on code from rmweb.co.uk forum - edited by J.Luce
    //   Program functions:
    //     "Real World" interface
    //     2- N.O. Push buttons to enable move to "Head" or "Tail"
    //     1- 10k Continuous turn POT to select track.
    //     1- N.O. Reset button on fascia
    //     Use digital inputs on 6 & 7 to stepforward and backward and 
    //       read out in the serial monitor so that you can program easier.
    //     Release & Servo Brake function works with brake and release function.
    //     Route positioning not taking shortest route to next location- (i.e. Tk.1 to Tk. 10 would be through 0)
    //     Always approaches a track from the same direction to account for gear/mechanical lash/slop.
    //       if the table was going CW to pos. 1 it approaches value B and stops
    //       if the table was going CCW to Pos 1 it should go past B a (value B-X) and then go CW to B.
    //     Added a (commented out)set up tool in the "loop" section of the code to set up the POT on the fascia panel.
    //       this let's you align the POT to the markings on the panel.  Uncomment to use, then re-comment to hide.
    //     Set up tool in the "loop" section of the code to set up the Brake Servo using the POT on the fascia panel. 
    //       this let's you find the optimal Release and Brake position for the servo brake.  Uncomment to use, then re-comment to hide.
    //     Added u8g display to show track position and heading, initialise at startup
    //     Move to track 0 "Head" at startup
    ////////////////////////////////////////////////////////////////////////////////
    
    ////////////////////////////////////////////////////////////////////////////////
    //
    // DCC Turntable Control Routines
    
    #include <DCC_Decoder.h>
    #include <AccelStepper.h>
    #include <Wire.h>
    #include <Adafruit_MotorShield.h>
    #include "utility/Adafruit_PWMServoDriver.h"
    #include <Servo.h>            //servo library reference
    #include "U8glib.h"
    
    // setup u8g object, please remove comment from one of the following constructor calls
    // IMPORTANT NOTE: The following list is incomplete. The complete list of supported 
    // devices with all constructor calls is here: http://code.google.com/p/u8glib/wiki/device
    
    U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST);	// Dev 0, Fast I2C / TWI
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Defines and structures
    //
    #define kDCC_INTERRUPT            0
    
    typedef struct
    {
        int               address;                // Address to respond to
       
    } DCCAccessoryAddress;
    
    DCCAccessoryAddress gAddresses[5];          // Allows 4 dcc addresses: [XX] = number of addresses you need (including 0).
    
    const unsigned long releaseTimeout_ms = 2000;      //reduced to 2 seconds for now    
    const long MOTOR_STEP_COUNT = 200 * 16;	            //number of steps for a full rotation
           
    //new stuff
    boolean tableTargetHead = false;        //the new variable for Head/Tail Position either from DCC or Analog
    int tableTargetPosition = 0;            //the new variable for Table Postition either from DCC or Analog
    boolean newTargetLocation = false;
    boolean buttonPushedHead = false;
    boolean buttonPushedTail = false;
    boolean inMotionToNewTarget = false;
    boolean isReleased = false;		// isReleased tries to make sure the motor is not continuously released
    unsigned long stepperLastMoveTime = 0;
    const long MOTOR_OVERSHOOT = 10;        // the amount of overshoot/ lash correction when approaching from CCW
    int overshootDestination = -1;
    
    //Servo Stuff
    Servo brakeservo;  // create servo object to control a servo
    const int servoBrake = 9;  //value for brake position
    const int servoRelease = 2;  //value for release position
    
    //Programming button variables
    boolean programmingMode = false;
    boolean programmingModeMoveForward = true;	//If in programming mode, are we moving forward?  
    unsigned long programmingLastMoveMillis = 0;	//When was the last programming move done?
    const int programRotateDelay = 100;			//Delay between steps in ms while holding button
    
    //location variables
    const int A = 144;          //TT Track 0-  Head
    const int B = 328;          //TT Track 1-  Head
    const int C = 482;          //TT Track 2-  Head
    const int D = 632;          //TT Track 3-  Head
    const int N = 1745;         //TT Track 0-  Tail
    const int O = 1928;         //TT Track 1-  Tail
    const int P = 2080;         //TT Track 2-  Tail
    const int Q = 2230;         //TT Track 3-  Tail
    
    const int PositionTrackHead[] = { A, B, C, D };
    const int PositionTrackTail[] = { N, O, P, Q };
    
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    //
    // Adafruit Setup
    
    Adafruit_MotorShield AFMStop(0x60); // Default address, no jumpers
    
    // Connect stepper with 200 steps per revolution (1.8 degree)
    // to the M3, M4 terminals (blue,yellow,green,red)
    
    Adafruit_StepperMotor *myStepper2 = AFMStop.getStepper(200, 2);
    
    // you can change these to SINGLE, DOUBLE, INTERLEAVE or MICROSTEP!
    
    // wrapper for the motor! (3200 Microsteps/revolution)
    void forwardstep2() {  
      myStepper2->onestep(BACKWARD, MICROSTEP);
    }
    void backwardstep2() {  
      myStepper2->onestep(FORWARD, MICROSTEP);
    }
    void release2()   {
      myStepper2->release();
    }
    
    // Now we'll wrap the stepper in an AccelStepper object  
    
    AccelStepper stepper2 = AccelStepper(forwardstep2, backwardstep2);
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Decoder Initiation 
    //
    void ConfigureDecoder()
    {                                    //Put all the decoder #'s you need here.  Remember to change
                                         //DCCAccessoryAddress gAddresses[XX];(above) where XX = number of addresses you need. 
        gAddresses[0].address = 200;      
        gAddresses[1].address = 201;      
        gAddresses[2].address = 202;
        gAddresses[3].address = 203;
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Basic accessory packet handler 
    //
    
    void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data)
    {
            // Convert NMRA packet address format to human address
        address -= 1;
        address *= 4;
        address += 1;
        address += (data & 0x06) >> 1;
        
        boolean enable = (data & 0x01) ? 1 : 0;
        
        for(int i=0; i<(int)(sizeof(gAddresses)/sizeof(gAddresses[0])); i++)
        {
            if( address == gAddresses[i].address )
            { 
     
                Serial.println("");  
                Serial.print("DCC addr: ");
                Serial.print(address,DEC);
                Serial.print("   Head/Tail = (1/0) : ");
                Serial.println(enable,DEC);
                                      
    			//new stuff
                            tableTargetHead = enable;
                            tableTargetPosition = i;
    
                            //picture loop
                            u8g.firstPage();  
                            do {
                              draw(tableTargetPosition, tableTargetHead);
                            } while( u8g.nextPage() ); 
    
    			//New packet and we have a new target location, set the flag
    			newTargetLocation = true;
                            doStepperMove();   
    			
      			// old location for if/else statements
    		}
    	}
    }
    
    /////////////////////////////////////////////////////////////////////////////
    //
    //  draw ug8 display
    //  
    
    void draw(int address, boolean enable) {
      // graphic commands to redraw the complete screen should be placed here  
      u8g.setFont(u8g_font_unifont);
      u8g.setPrintPos(20, 25);
      u8g.print("Position ");
      u8g.print(address,DEC);
      u8g.setPrintPos(20, 45); 
      if (enable)
      { 
        u8g.print("Head");
      }
      else
      {
        u8g.print("Tail");
      }
      u8g.drawRFrame(0,0,128,64,8);
    }
          
    /////////////////////////////////////////////////////////////////////////////
    //
    //  readAnalogLocation() Reads the pot input on analog pin 1, displays some
    //  information out the serial, and sets the target position variable
    //
    
    int readAnalogLocation()
    {
    	int potDisp = analogRead(1);
    	int potOut = potDisp / 79; //1023 / 13
    
    	Serial.println("");
            Serial.print("Analog Location: ");
    	Serial.print("Track # ");
    	Serial.print(potOut, DEC);
    	Serial.print("   Head/Tail = (1/0) : ");
    	Serial.println(tableTargetHead, DEC);
    
    	//tableTargetPosition = potOut;
    	return potOut;
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Subroutine: doStepperMove()
    //	 Moves the stepper based on location inputs, calls up "SetStepperTargetLocation()"
    //
    ///////////////////////////////////////
    
    void doStepperMove()
    {
    
            // Run the Stepper Motor //
            stepper2.run();
    
    	boolean isInMotion = (abs(stepper2.distanceToGo()) > 0);
    	boolean newTargetSet = false;
    
    	// If there is a new target location, set the target
    	if (newTargetLocation)
    	{
    		Serial.println("Moving to New Target Location...");
    		SetStepperTargetLocation();
    		newTargetSet = true;
    	}
    
    	if (inMotionToNewTarget)
    	{
    		if ((!isInMotion) && (!newTargetSet))
    		{
    			Serial.print("Not Moving!  DtG: ");
    			Serial.print(stepper2.distanceToGo());
    			Serial.print(" TP: ");
    			Serial.print(stepper2.targetPosition());
    			Serial.print(" CP: ");
    			Serial.print(stepper2.currentPosition());
    			Serial.print(" S: ");
    			Serial.print(stepper2.speed());
    			Serial.println();
    		}
    				//release the brake
                                    brakeservo.write(servoRelease);
                                    delay (5);  
    		inMotionToNewTarget = isInMotion;
    	}
    	else
    	{
    		if (programmingMode)
    		{
    			//If we are programming, do that move
    				//release the brake
                                    brakeservo.write(servoRelease);
                                    delay (5);  
            
                            if ((millis() - programmingLastMoveMillis) >= programRotateDelay)
    			{
    				programmingLastMoveMillis = millis();
    				stepper2.move(programmingModeMoveForward ? 1 : -1);
                                    
                                    Serial.println("");
    				Serial.print("Programming mode, Current location: ");
    				Serial.println(stepper2.currentPosition());
    		                int potDisp = analogRead(1);
    		                int potOut = potDisp / 79; //1023 / 13
    		                Serial.print("Analog Location: ");
                                    Serial.print("Track # ");
    		                Serial.println(potOut, DEC);
    		                delay(45);
    			}
    		}
    		if ((stepper2.currentPosition() % MOTOR_STEP_COUNT) == 0)
    		{
        		        //setCurrentPosition seems to always reset the position to 0, ignoring the parameter
    			Serial.print("Current location: ");
    			Serial.print(stepper2.currentPosition());
    			Serial.println(" % STEPCOUNT.  Why here?");
    		}
    	}
                          //stepper timer subroutine came from here.
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Subroutine: SetStepperTargetLocation()
    //     Takes the global variables: tableTargetHeadOrTail, and tableTargetPosition, and sets the stepper2
    //   object moveTo() target position in steps-  inserts values back into "doStepperMove()"
    //
    ///////////////////////////////////////
    
    void SetStepperTargetLocation()
    {
        int newTargetLoc = -1;
        if (tableTargetHead)
        {    //use head location variable
            {
                newTargetLoc = PositionTrackHead[tableTargetPosition];
                inMotionToNewTarget = true;
            }
        }
        else
        {    //use tail location variable
            {
                newTargetLoc = PositionTrackTail[tableTargetPosition];
                inMotionToNewTarget = true;
            }
        }
    
        if (newTargetLoc > 0)
        {
            int currentLoc = stepper2.currentPosition();
            int mainDiff = newTargetLoc - currentLoc;
            if (mainDiff > (MOTOR_STEP_COUNT / 2))
            {
                mainDiff = mainDiff - MOTOR_STEP_COUNT;
            }
            else if (mainDiff < (-MOTOR_STEP_COUNT / 2))
            {
                mainDiff = mainDiff + MOTOR_STEP_COUNT;
            }
    
            if (mainDiff < 0)
            {
                mainDiff -= MOTOR_OVERSHOOT;
                overshootDestination = MOTOR_OVERSHOOT;
            }
            stepper2.move(mainDiff);
        }
        programmingMode = false;
        newTargetLocation = false;
      }
     
    //////////////////////////////////////////////////////////////////////////////////////////////////
    //
    //  Stepper Timer sub routine this runs from the main loop. It also supports the release function.
    //
    //////////////////////////////////////////////////////////////////////////////////////////////////
    
    void stepperTimer()
    {
        // Run the Stepper Motor //
        stepper2.run();
           
        boolean isInMotion = (abs(stepper2.distanceToGo()) > 0);
        //Check if we have any distance to move for release() timeout.  Can check the
        // buffered var isInMotion because we also check the other variables.
        if (isInMotion || programmingMode )
        {
            //We still have some distance to move, so reset the release timeout
            stepperLastMoveTime = millis();
            isReleased = false;
        }
        else
        {
            if (!isReleased)
            {
                if (overshootDestination > 0)
                {
                    stepper2.move(overshootDestination);
                    overshootDestination = -1;
                }
                if (((millis() - stepperLastMoveTime) >= releaseTimeout_ms))
                {
                    //If isReleased, don't release again.
                    isReleased = true;
                    Serial.print ("Relative Current Position: ");
                    Serial.print(stepper2.currentPosition());    //shows position the table thinks it is at (how it got here)
    
                    int currentLoc = stepper2.currentPosition();    // Resets the positon to the actual positive number it should be
                    currentLoc = currentLoc % MOTOR_STEP_COUNT;
                    if (currentLoc < 0)
                    {
                        currentLoc += MOTOR_STEP_COUNT;
                    }
                    stepper2.setCurrentPosition(currentLoc);
                    stepper2.moveTo(currentLoc);
                    
                    Serial.print ("    Actual Current Position: ");
                    Serial.println(stepper2.currentPosition());    // shows the position value corrected.
    
                    //Set the servo brake
                    brakeservo.write(servoBrake);
                    delay(750);
    
                    //release the motor
                    release2();
                    Serial.println("    Brake Set & Motor Released ");
                }
            }
        }
    }
    
    ///////////////////////////////////////////////////////
    //
    //  Check Programming Buttons-  look for either of the programming buttons being pushed
    //
    
    void checkProgrammingButtons()
    {
    	programmingMode = false;
    	bool buttonPushedProgUp = (digitalRead(6) == LOW);
    	bool buttonPushedProgDown = (digitalRead(7) == LOW);
    
    	//If one button is pushed, but not both
    	if ((buttonPushedProgDown || buttonPushedProgUp) && !(buttonPushedProgDown && buttonPushedProgUp))
    	{
    		programmingMode = true;
    		programmingModeMoveForward = buttonPushedProgUp;
    	    doStepperMove();
            }
    }
    
    ///////////////////////////////////////////////////////////////
    // Manual move to using the pushbuttons and POT
    //
    
    void checkManualButtons()
    {
    	//Read the Head button input
    	if (digitalRead(4) == LOW)
    	{
    		buttonPushedHead = true;
    	}
    	else if (buttonPushedHead)
    	{
    		//Button was pushed, but is not being pushed now
    		//Clear pushed variable
    		buttonPushedHead = false;
    		//Set the target head variable
    		tableTargetHead = true;
    		//Read the analog location
    		tableTargetPosition = readAnalogLocation();
    
    		//Then set the new target flag
    		newTargetLocation = true;
              doStepperMove();
    	}
    
    	//Read the Tail button input
    	if (digitalRead(5) == LOW)
    	{
    		buttonPushedTail = true;
    	}
    	else if (buttonPushedTail)
    	{
    		//Button was pushed, but is not being pushed now
    		//Clear pushed variable
    		buttonPushedTail = false;
    		//Set the target head variable
    		tableTargetHead = false;
    		//Read the analog location
    		tableTargetPosition = readAnalogLocation();
    
    		//Then set the new target flag
    		newTargetLocation = true;
              doStepperMove();  
    	}
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    //
    // Setup
    //
    
    void setup()
    {
    	Serial.begin(9600);
    
    	AFMStop.begin(); // Start the shield
    
            //servo brake release before stepp moves
            brakeservo.attach(10);  // attaches the servo on pin 10 to the servo object
            brakeservo.write(servoRelease);
            delay (30);
    
            //initial display
            tableTargetHead = true;
            tableTargetPosition = 0;
    
            u8g.firstPage();  
            do {
            draw(tableTargetPosition, tableTargetHead);
            } while( u8g.nextPage() ); 
    
    	//configure pin3 as an input and enable the internal pull-up resistor
    	pinMode(3, INPUT_PULLUP);		//Hall Effect sensor: to reset position on startup
    	//configure pin4 as an input and enable the internal pull-up resistor
    	pinMode(4, INPUT_PULLUP);		//Head button
    	//configure pin5 as an input and enable the internal pull-up resistor
    	pinMode(5, INPUT_PULLUP);		//Tail button
    	//configure pin6 as an input and enable the internal pull-up resistor
    	pinMode(6, INPUT_PULLUP);		//Programming: Move forward single step
    	//configure pin7 as an input and enable the internal pull-up resistor
    	pinMode(7, INPUT_PULLUP);		//Programming: Move reverse single step
    
    	//read the sensoron Dig I/O #3 (open collector type- Hall Effect sensor) value into a variable
    	int sensorVal = digitalRead(3);
    
    	//set stepper motor speed and acceleration 
    	stepper2.setMaxSpeed(80.0);
    	stepper2.setAcceleration(10.0);
    
    	// if near reference point move away
    	while (sensorVal == LOW) {
    		sensorVal = digitalRead(3);
    		forwardstep2();
    		delay(25);
    		Serial.println("Stepper Home");
    	}
    
    	// step backward to sensor index point
    	while (sensorVal == HIGH) {
    		sensorVal = digitalRead(3);
    		backwardstep2();
    		delay(50);
    	}
    
            //when home- sets brake
            delay (500);
            brakeservo.write(servoBrake);
    
           //initial track position 0 head
           newTargetLocation = true;
           doStepperMove();   
    
           
       DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
       ConfigureDecoder();
       DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Main loop
    //
    
    void loop()
    {
       static int addr = 0;
        
            ////////////////////////////////////////////////////////////////
            // Loop DCC library
    
        DCC.loop();
    
            ////////////////////////////////////////////////////////////////
            // Bump to next address to test
        if( ++addr >= (int)(sizeof(gAddresses)/sizeof(gAddresses[0])) )
        {
            addr = 0;
        }
    
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	//  To configure the POT (uncomment this text)
    	//configure alalog pin 1 as an input
    	
            //  int potDisp = analogRead(1);
    	// 
    	//  int potOut = potDisp / 79; //1023 / 13
    	//     delay(1000);  
    	//     Serial.print("Analog Location: ");
    	//     Serial.println(potOut);
    	
            // end of POT configuration- re-comment this section to hide during normal use
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	//  To configure the Servo Brake (uncomment this text) 
    	//configure alalog pin 1 as an input
    	
            //    int potDisp = analogRead(1);            // reads the value of the potentiometer (value between 0 and 1023)
            //    int potOut = map(potDisp, 0, 1023, 0, 50);     // scale it to use it with the servo (value between 0 and 180)
            //    brakeservo.write(potOut);                  // sets the servo position according to the scaled value
            //    delay(1000); 
    	//    Serial.print("Servo Position Value: ");
    	//    Serial.println(potOut);
            
    	// end of POT configuration- re-comment this section to hide during normal use
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    	//Tests to see if the manual move buttons are pressed, if so set the needed flags
              checkManualButtons();
    
    	//Checks the programming buttons, if depressed then set the right flags
              checkProgrammingButtons();
    
    	//StepperAccel.Run() in this function
              stepperTimer();
    
    }
    
    
×
×
  • Create New...