Jump to content
 

Estreetcar

Members
  • Posts

    31
  • Joined

  • Last visited

Recent Profile Visitors

149 profile views

Estreetcar's Achievements

9

Reputation

  1. Simon- I haven't used the OLED display described in luce001's sketch, but I think there are some good examples on the arduino forums. What display are you using? I would search for info on the specific display and relate it back into the code replacing what luce001 did with the new code. Sorry to not be a better help. I used a continuous turn 10k pot and panel with a printed label (see photo) for my main interface. I also have a JMRI virtual panel and allow for DCC inputs. I tend to use the analog knob and buttons most as it is just easier. The more I automate, the more I like having the simple interface options when it is just me. -Eric
  2. Simon- Take a look at my earlier posts or on page 7- luce001 posted some nice modifications to my code for a 4 track table with release. He also added an OLED LED display, but you could just comment out that code if you don't want it. It is a rather nice solution. Haven't read this thread in a while. fun to see what people have done with it. I am really happy with mine. Made two tables to date, looking to make two more drives for the local club. I will post my updated code using some simpler drive modules once I build rev 2. Best of luck. Talk soon -Eric
  3. Simon- Take a look at previous pages on this thread. To rebuild on what you had it would be helpful to know what you did. Did you use a hall effect sensor? Did you follow the original posts for inputs? Also same question for driver. Did you use the Adafruit driver or did you use something else? Let me know if you aren't able to find what you need. I think you can find everything just searching this thread from the beginning. -Eric
  4. Note that the servo needs a separate power supply as a stalled or loaded servo will overload the ada-fruit board and reset the unit. (I found this out the hard way). Glad you like the servo brake. It worked really well.
  5. CFJ- I think that you are missing out on some of the features of the code and that may be part of your problem. I was using the micro-step function before I added the gearbox. Even with code 70 rail I can see a 2 step interleave, and without the gearbox using microstep each step was noticable when trying to program tracks. The code worked beautifuly. Run the test program that was offered back on page 3 I think where it just spins 180 deg. I used tape and a fine line. After an hour of back and forth there was no drift. At that point I went nuts on my own code, but the base code is very good. I find that the release function is really neat, but not necessary. The motor even after being energized for 4+ hours is hot, but not approaching its insulation class. It would be neat if the realease function could be used. I have thought about putting in a route code that would return the table to home, back off about a 1/2" and then release, so that when you were ready to use the turntable again you only needed to reset the unit, and the distance wouldn't be that far, so you wouldn't have to wait a while. I may do that before the upcoming show this summer where the table will be used for 3 days all day. Just food for thought. I call up more than just "step". There are commands in the library that allow different math operations to occur. that may be where your error is introduced. I use the following in my code (from the original code on this forum): stepper2.moveTo(VALUE); moves the step number (Value) 0 to 3200 for microstep. stepper2.move(VALUE); the stepper moves the value input: + or -. for the total number of steps in (VALUE) stepper2.distanceToGo(); returns the data from the library code (helpful for troubleshooting in serial monitor) stepper2.targetPosition(); returns the target position from the library stepper2.currentPosition(); returns what the library thinks the current position is from the library stepper2.setCurrentPosition(VALUE); This sets the position value to the integer you put in the (). Useful to re-zero after going around zero- other wise it "unwinds" to get back to the other number. stepper2.speed(); returns the speed value from the library. Speed may also be your problem, you may be trying to rotate too fast for the motor you choose with the current you have available. I think it is really just that you are calling "step" instead of "move to" or "move" try my code and see if it works for you. It is in this post further back. Hope that helps. -Eric
  6. CFJ- I have not had any problems with my AdaFruit V2 code. Check out the topic on DCC Controlled Peco Turntable. http://www.rmweb.co.uk/community/index.php?/topic/78578-dcc-controlled-peco-turntable-project/ The change I made to my table was to put in a 50:1 gearbox to get more torque, but the resolution is great. I have run the table for hours with multiple operators and the resolution stays 1:1. Check out my code. I did switch from microstep to interleave as I didn't need that many steps. The acceleration and speed controls are great, the torque is fine and I am very happy with the result. Perhaps your code is calling the target position more than once or you are calling "go to" rather than "target". -Eric
  7. All- As hinted in the last post, I have made some significant changes to the code I am running. I changed the stepper from direct drive to a gearbox drive (50:1) eliminated the brake, and did some other changes. I am really happy with the results. I wrote some JMRI code to just keep calling up DCC accessory codes (not in order) with about 2 min in between each request. Then at the end of the day, I stopped the code and called up each track to see is there was any noticeable drift over time. I did notice a little, but nothing significant. That was about 120 moves over 4 hours and in the end it still lined up correctly. What drew me to the gearbox, was that after about 10 moves with the direct drive and a heavy locomotive, the previous drive had a noticeable drift (or loss of resolution- ie- skipped steps). I really liked the "microstepping" but the torque I have now at "Interleave" stepping is significant. The motor also runs significantly cooler. I want to still try and overdrive the i2c clock, but I can't get that to work. That would allow me to go back to microstepping, but at higher speeds. So- Below is my code. Have fun. //////////////////////////////////////////////////////////////////////////////// // // February 1st 2015- Cleaned up code- ready for distribution again. // November 22nd 2014- working on the i2c bus speed problem.- same file name // October 28th 2014- change to 50:1 gear box. change to Interleave for motor resolution adjust software. // //////////////////////////////////////////////////////////////////////////////// // // On30_Module_DCC Turntable Rev_22e February_1_2015 // based on code from rmweb.co.uk forum // edited by E.Sitiko with programming help from D.Whetten // Program functions desired are: // 14 road turntable (works) // DCC addresses 200 to 214 (works!) // clean up code to make changes to Position easy (works) // "Real World" interface // 2- N.O. Push buttons to enable move to "Head" or "Tail" (works) // 1- 10k Continuous turn POT to select track. (works) // 1- N.O. Reset button on fascia (works) // Use digital inputs on 6 & 7 to stepforward and backward and // read out in the serial monitor so that you can program easier. (works) // "Release" function- to release power to the motor when idle. (works!!) // Route positioning not taking shortest route to next location- (i.e. Tk.1 to Tk. 10 would be through 0) (works) // Always approaches a track from the same direction to account for gear/mechanical lash/slop. (works) // 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. (works) // // (FUTURE) DCC Address 200 or Analog 0 = continuous slow rotation, based on "Head" or "Tail" (does not work now) // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // DCC Turntable Control Routines #include <DCC_Decoder.h> #include <AccelStepper.h> #include <Wire.h> #include <Adafruit_MotorShield.h> #include "utility/Adafruit_PWMServoDriver.h" ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Defines and structures // #define kDCC_INTERRUPT 0 typedef struct { int address; // Address to respond to } DCCAccessoryAddress; DCCAccessoryAddress gAddresses[15]; // Allows 15 dcc addresses: [XX] = number of addresses you need (including 0). const unsigned long releaseTimeout_ms = 900000; //timer set to 15 min for now - this is the release timer const unsigned long Count_Reset_Timeout_ms = 500; //timer for reseting position following a move set to 1/2 sec for now const long MOTOR_STEP_COUNT = 20000; //number of steps (200 * 4(Interleave)) * 50 for a full turntable rotation 20,000 //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 boolean isStopped = false; // isStopped is the boolean to enable the count reset timer to function. unsigned long stepperLastMoveTime = 0; const long MOTOR_OVERSHOOT = 50; // the amount of overshoot/ lash correction when approaching from CCW (best guess 50- 1/8 revolution of the worm gear) int overshootDestination = -1; //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 = 50; //Delay between steps in ms while holding button //Do display rotation const int displayRotateDelay = 5; //This is the minimum delay in ms between steps of the stepper motor boolean displayRotating = false; //Is the "Display Rotate" function enabled? boolean displayRotatingCW = true; //In Display Rotate mode, are we rotating CW or CCW? unsigned long displayRotatingLastMoveMillis = 0; //location variables const int A = 150; //TT Track 0- Head const int B = 736; //TT Track 1- Head const int C = 1589; //TT Track 2- Head const int D = 3045; //TT Track 3- Head const int E = 3997; //TT Track 4- Head const int F = 4704; //TT Track 5- Head const int G = 5644; //TT Track 6- Head const int H = 12548; //TT Track 7- Head const int I = 13400; //TT Track 8- Head const int J = 14150; //TT Track 9- Head const int K = 16090; //TT Track 10- Head const int L = 16801; //TT Track 11- Head const int M = 17513; //TT Track 12- Head const int N = 18600; //TT Track 13- Head const int O = 19530; //TT Track 14- Head const int P = 10281; //TT Track 0- Tail const int Q = 10740; //TT Track 1- Tail const int R = 11586; //TT Track 2- Tail const int S = 13042; //TT Track 3- Tail const int T = 13984; //TT Track 4- Tail const int U = 14681; //TT Track 5- Tail const int V = 15624; //TT Track 6- Tail const int W = 2549; //TT Track 7- Tail const int X = 3401; //TT Track 8- Tail const int Y = 4160; //TT Track 9- Tail const int Z = 6097; //TT Track 10- Tail const int AA = 6809; //TT Track 11- Tail const int AB = 7531; //TT Track 12- Tail const int AC = 8605; //TT Track 13- Tail const int AD = 9546; //TT Track 14- Tail const int PositionTrackHead[] = { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O }; const int PositionTrackTail[] = { P, Q, R, S, T, U, V, W, X, Y, Z, AA, AB, AC, AD }; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // 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! (400 Interleave steps/revolution * 50:1 gear box) void forwardstep2() { myStepper2->onestep(FORWARD, INTERLEAVE); //changed direction due to motor rewire } void backwardstep2() { myStepper2->onestep(BACKWARD, INTERLEAVE); //changed direction due to motor rewire } 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; gAddresses[4].address = 204; gAddresses[5].address = 205; gAddresses[6].address = 206; gAddresses[7].address = 207; gAddresses[8].address = 208; gAddresses[9].address = 209; gAddresses[10].address = 210; gAddresses[11].address = 211; gAddresses[12].address = 212; gAddresses[13].address = 213; gAddresses[14].address = 214; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // 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; //New packet and we have a new target location, set the flag newTargetLocation = true; doStepperMove(); // old location for if/else statements } } } ///////////////////////////////////////////////////////////////////////////// // // 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 / 73; //1023 / 14 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(); } inMotionToNewTarget = isInMotion; } else { if (programmingMode) { //If we are programming, do that move if ((millis() - programmingLastMoveMillis) >= programRotateDelay) { programmingLastMoveMillis = millis(); stepper2.move(programmingModeMoveForward ? 5 : -5); //Tried to change this to something like 5 steps- for programming Serial.println(""); Serial.print("Programming mode, Current location: "); Serial.println(stepper2.currentPosition()); int potDisp = analogRead(1); int potOut = potDisp / 73; //1023 / 14 Serial.print("Analog Location: "); Serial.print("Track # "); Serial.println(potOut, DEC); delay(50); //sets delay between steps when using programming buttons } } //Only do display rotate if we aren't doing the programming move else if (displayRotating) { //Are we in "DisplayRotate" mode? if so, do that.. if ((millis() - displayRotatingLastMoveMillis) >= displayRotateDelay) { displayRotatingLastMoveMillis = millis(); stepper2.move(displayRotatingCW ? 1 : -1); Serial.print("Display rotate mode: "); Serial.println(stepper2.currentPosition()); } } 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 if (tableTargetPosition == 0) { //Special case: Display Rotate displayRotating = true; displayRotatingCW = true; Serial.println("Entering display rotate mode, CW."); } else { displayRotating = false; newTargetLoc = PositionTrackHead[tableTargetPosition]; inMotionToNewTarget = true; } } else { //use tail location variable if (tableTargetPosition == 0) { //Special case: Display Rotate displayRotating = true; displayRotatingCW = false; Serial.println("Entering display rotate mode, CCW."); } else { displayRotating = false; newTargetLoc = PositionTrackTail[tableTargetPosition]; inMotionToNewTarget = true; } } if (newTargetLoc > 0) { long currentLoc = stepper2.currentPosition(); long 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) //changed this to less than or equal to, to wake up the motor if you recall the track it is sitting on. { mainDiff -= MOTOR_OVERSHOOT; overshootDestination = MOTOR_OVERSHOOT; } stepper2.move(mainDiff); delay (30); } 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 || displayRotating) // || newTargetSet) { //We still have some distance to move, so reset the release timeout stepperLastMoveTime = millis(); isReleased = false; isStopped = false; } else { if (!isStopped) { if (overshootDestination > 0) { stepper2.move(overshootDestination); overshootDestination = -1; } if (((millis() - stepperLastMoveTime) >= Count_Reset_Timeout_ms)) { isStopped = true; // isReleased = true; Serial.print ("Relative Current Position: "); Serial.print(stepper2.currentPosition()); //shows position the table thinks it is at (how it got here) long 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. } } if (!isReleased) { if (((millis() - stepperLastMoveTime) >= releaseTimeout_ms)) { //If isReleased, don't release again. isReleased = true; //release the motor release2(); Serial.println(" 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 //tried different values- liked the lack of noise with 4000- but not with interleave- no noticable change //TWBR = ((F_CPU /400000L) - 16) / 2; // TWBR = ((F_CPU /400000L) - 16) / 2; // Change the i2c clock to 400KHz per chat group- didn't work still 75rpm. //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(500); //values between 20 and 50 (experiment- 800 rpm= about 80 in real life with stepper in single, // or double, about 40rpm with Interleave, about 5rpm with microstep) stepper2.setAcceleration(25); //values between 5 and 20. (accel experiment- 25) // if near reference point move away while (sensorVal == LOW) { sensorVal = digitalRead(3); forwardstep2(); // Rewired motor- changed direction delay(5); //value is 5 Serial.println("Stepper Home"); } // step backward to sensor index point while (sensorVal == HIGH) { sensorVal = digitalRead(3); backwardstep2(); // Rewired motor- changed direction delay(50); //value is 50 } Serial.println(""); Serial.println("United Railways- East Yard Turntable: Ready for Use"); Serial.println(""); 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 / 73; //1023 / 14 // delay(1000); // Serial.print("Analog Location: "); // 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(); } On30_Module_DCC_Turntable_Rev_22e.ino
  8. Jonathan- Great stuff! Glad to see that some use is becoming of the code. I have been tweaking the code as well. I made some changes this past weekend that I really like. I will post a video soon of the unit in operation. -Eric
  9. All- That took a lot longer than I thought to get to this point. Amazing how life and work always get in the way of hobbies. My first post was on March 19th and for only 7 months of very off and on work on this I think I have made it quite far. I still want to tweak the code a little, but I have something that is very workable. I am building a second version for the HO scale train club that I belong to. I will update those pictures and code as I progress. Below is the success I has last night- It works very well! Able to smoothly turn a large On30 locomotive (about 8 pounds) and line up perfectly each time. I was able to attach the 50:1 gear box that I made out of plywood using a friends laser cutter. The gears came from Diamond Scale(I just called them directly). The gearbox has adjustments for gear lash and to gear mesh to really get a smooth running machine. I am very happy with the results. I have attached the final version of the CAD files as well as a PDF of them. TT- Attachment- actual size- final 9-3-14.dwg TT- Attachment- actual size 9-3-14.pdf I will take some video of the table once I put it back in the layout (it is designed to be semi-removable). Any questions, just ask. -Eric Portland, OR. USA
  10. All- This summer has taken me away from my modeling interests. Now that the busy summer season is over, I plan on getting back into my hobbies for winter. I did make some major changes to the turntable drive this summer. I was unhappy with the performance of the drive when turning large heavy locomotives (+8 pounds) using the direct drive. I looked at either putting in a larger stepper motor or adding in a gear reduction. I decided on the gear reduction. I revised the drive to include a 50:1 gear reduction. I remade the laser cut drive box and should get everything re-assembled within a few weeks. I also added two more tracks to the turntable, so now it is a 14 road turntable. The software has evolved a little to account for the larger (50x more) steps per revolutions. See attached for the new gear box design and a photo of the new gears. I hope to post soon with a revision to the code and pictures of the progress. When finished this should be suitable for O-scale turntables up to 32.5" diameter (O-scale 130ft). TT- Attachment- actual size 9-3-14.pdf -Eric
  11. All- I see that one person has downloaded the program. Has anyone else tried the code to see if I missed anything? The glue is drying on the motor mount. I should be able to try it over the weekend. As a side project I am working on a Raspberry-Pi running JMRI headless to act as the web-server for my layout. It will have the Panel from the post above auto loaded. I will be able to use my tablet or my phone (running Engine Driver and Wi-Throttle (respectively). It works great running off my laptop. I just wanted a more hands off option running in the background. I wonder if anyone else has had success with that? Thanks. -Eric
  12. All- I worked with my friend this weekend on the code and now I have something to really post. I would be comfortable calling this the "first released" version. The only thing left to work out is the "display mode" constant slow rotation, but that isn't needed for the functionality, so its DONE! I hope that someone else can try the code with their stepper set-up and let me know of any other problems. I should have this mounted up and begin some live testing this week. I will take some video to post as well. Thanks for all the help and suggestions along the way. I have amended the post #141 to include the newest software and code. Enjoy, -Eric
  13. All- To continue this post I think I have de-bugged the code enough to share it with everyone. There are still some rough patches, but I have most of the important stuff working. I need to re-cut one part with the Laser this weekend, and then I should be ready to assemble permanently. The Important things are working: Analog, DCC, Programming assistant and Release/Servo Brake. Still need to get working are: approaching the track from the same way each time and having the unit take the shortest route to the track instead of all the way around. If anyone knows how to do either, please help. Here is the code: (note it looks a lot different than my last posting- as that was Rev. 3. You should be able to follow. Questions- just ask. See post #99 for my break-out boards) **EDITS on 5-4-14 Revised Code** //////////////////////////////////////////////////////////////////////////////// // // May 4 2014 - Completed Majority of Open Items- First Release Version !!! // //////////////////////////////////////////////////////////////////////////////// // // DCC Turntable Rev_15e May_4_2014 // based on code from rmweb.co.uk forum // edited by E.Sitiko with programming help from D.Whetten // This is a work in progress, I need some help with programming // Program functions desired are: // 12 road turntable (works) // DCC addresses 200 to 212 (works!) // clean up code to make changes to Position easy (works) // "Real World" interface // 2- N.O. Push buttons to enable move to "Head" or "Tail" (works) // 1- 10k Continuous turn POT to select track. (works) // 1- N.O. Reset button on fascia (works) // Use digital inputs on 6 & 7 to stepforward and backward and // read out in the serial monitor so that you can program easier. (works) // "Release & Servo Brake" function works with brake and release function. (works!!) // Route positioning not taking shortest route to next location- (i.e. Tk.1 to Tk. 10 would be through 0) (works) // Always approaches a track from the same direction to account for gear/mechanical lash/slop. (works) // 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. (works) // Added a (commented out)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. (works) // // // (FUTURE) Address 200 (or Analog 0)= continuous slow rotation, based on "Head" or "Tail" (does not work now) // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // 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 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Defines and structures // #define kDCC_INTERRUPT 0 typedef struct { int address; // Address to respond to } DCCAccessoryAddress; DCCAccessoryAddress gAddresses[13]; // Allows 13 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 //Do display rotation const int displayRotateDelay = 5; //This is the minimum delay in ms between steps of the stepper motor boolean displayRotating = false; //Is the "Display Rotate" function enabled? boolean displayRotatingCW = true; //In Display Rotate mode, are we rotating CW or CCW? unsigned long displayRotatingLastMoveMillis = 0; //location variables const int A = 150; //TT Track 0- Head const int B = 45; //TT Track 1- Head const int C = 205; //TT Track 2- Head const int D = 490; //TT Track 3- Head const int E = 588; //TT Track 4- Head const int F = 765; //TT Track 5- Head const int G = 1939; //TT Track 6- Head const int H = 2072; //TT Track 7- Head const int I = 2205; //TT Track 8- Head const int J= 2499; //TT Track 9- Head const int K = 2623; //TT Track 10- Head const int L = 2739; //TT Track 11- Head const int M = 2872; //TT Track 12- Head const int N = 1750; //TT Track 0- Tail const int O = 1645; //TT Track 1- Tail const int P = 1805; //TT Track 2- Tail const int Q = 2090; //TT Track 3- Tail const int R = 2188; //TT Track 4- Tail const int S = 2365; //TT Track 5- Tail const int T = 339; //TT Track 6- Tail const int U = 472; //TT Track 7- Tail const int V = 605; //TT Track 8- Tail const int W = 899; //TT Track 9- Tail const int X = 1023; //TT Track 10- Tail const int Y = 1139; //TT Track 11- Tail const int Z = 1272; //TT Track 12- Tail const int PositionTrackHead[] = { A, B, C, D, E, F, G, H, I, J, K, L, M }; const int PositionTrackTail[] = { N, O, P, Q, R, S, T, U, V, W, X, Y, Z }; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // 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; gAddresses[4].address = 204; gAddresses[5].address = 205; gAddresses[6].address = 206; gAddresses[7].address = 207; gAddresses[8].address = 208; gAddresses[9].address = 209; gAddresses[10].address = 210; gAddresses[11].address = 211; gAddresses[12].address = 212; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // 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; //New packet and we have a new target location, set the flag newTargetLocation = true; doStepperMove(); // old location for if/else statements } } } ///////////////////////////////////////////////////////////////////////////// // // 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); } } //Only do display rotate if we aren't doing the programming move else if (displayRotating) { //Are we in "DisplayRotate" mode? if so, do that.. //release the brake brakeservo.write(servoRelease); delay (5); if ((millis() - displayRotatingLastMoveMillis) >= displayRotateDelay) { displayRotatingLastMoveMillis = millis(); stepper2.move(displayRotatingCW ? 1 : -1); Serial.print("Display rotate mode: "); Serial.println(stepper2.currentPosition()); } } 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 if (tableTargetPosition == 0) { //Special case: Display Rotate displayRotating = true; displayRotatingCW = true; Serial.println("Entering display rotate mode, CW."); } else { displayRotating = false; newTargetLoc = PositionTrackHead[tableTargetPosition]; inMotionToNewTarget = true; } } else { //use tail location variable if (tableTargetPosition == 0) { //Special case: Display Rotate displayRotating = true; displayRotatingCW = false; Serial.println("Entering display rotate mode, CCW."); } else { displayRotating = false; 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 || displayRotating) // || newTargetSet) { //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); //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); 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(); } And the File: **EDITED 5-4-14 REVISED FILE** DCC_Turntable_Rev_15e.ino I hope to have rev 14 out soon with the changes. Thank for the support, this has been a fun project. -Eric
  14. All- I had a very productive weekend, but a busy week at work so far. Below are pictures of the progress of the stepper motor mount I laser cut at my friends house. The unit went together right out of the laser (as should be expected) and I need to glue it up and get everything finalized. The small size is due to it needing to fit under a module and not protrude too far to make transport easier. It is very scale-able. I am building a second unit for the club layout (permanent install) and that will be a much more open design, and not so compact. I did not need a second bearing to keep the turntable shaft running true for the module, as my old drive system included that. The one for the club will have two shaft bearings and use that electric brake I picked up instead of the servo. I am quite curious to see the servo brake work in real life. Hope that was a little inspiring.... I can share the CAD files with anyone that wants, or just post them here. The PDF of the drive unit as built is attached below. Stepper Motor Turntable Mount- EMS 4-26-14.pdf Thanks -Eric
  15. Kev- The brake is only there so that I can release the power to the stepper and have confidence that the table will still be in the same position when I move it again. The stepper does not move at all when released. If there is any mechanical binding in the turntable, it might move, but mine does not. I just want to ensure that if a locomotive drives onto the table or someone bumps the table it does not move. As the only reference for positioning is at start-up, any slight unintended movement would cause error until the table was reset. I hope that helps.I should have the mount built this weekend. I will post some pictures and video to explain the set-up. -Eric
×
×
  • Create New...