Motion control project

Hi All,

Need a little guidance with a project (first real crack at this since turning on a LED a few times and getting a display to say "Hello World")

In short what I want to achieve is to build an electronic lead screw.

Hardware that I am using:

Mega 2560
TTL to RS485 board(MAX485 RS485 module)
Delta Dop B07s515 HMI
Hontko 1024PPR Push Pull Encoder on spindle
Delta B series Servo Drive and Motor (10000ppr)

Why am I using an arduino when everything else is industrial hardware?
In lock down for the the next couple of weeks and so unable to get the brand of control I am familiar with using (Delta SA2 plc with 100kHz output would have been used for the project)

What I want to be able to achieve with the ELS:

Threading in both metric and imperial standards
Setting a start/stop/return/repeat for the threading

What I need the Arduino to do.

Receive the encoder signal
Output the Servo Enable/Step/Direction signals
Comm with the screen so I can change the ratio between input pulse and output pulse.

What I am struggling with:

RS485 comms with HMI
A good way to deal with the step out put pulse.

RS485 comms
I am not sure whether I should set the Arduino as master or slave. I have looked at Libraries for RS485 Modbus RTU coms by Cadish12 (Modbus XT) and 4-20MA (Modbus Master). I can set the screen as either the Master or Slave, I want to use the best setting on the Arduino to keep the cycle time on the program low. Will also need to assign Hex addresses to the relevant registers and bits that will be needed.

Step output pulse
I am using the original lead screw till I get a ball screw installed. It is a six TPI which equates to 0.00021167...mm per step on the servo drive after 2:1 reduction between motor and screw. This means when threading at finest setting I plan on adding (0.2mm pitch) I need to pulse the servo driver one step for every 4.3349...pulses from the spindle. Any thoughts on how to tackle this? On short sections of threads the small error should not be an issue as it will be a micron or less. Secondary to this, will I need to set the signal inputs from the Encoder as interrupts?

I have considered sending the A and B pulse from the encoder straight to the Servo Drive and then using the Z Pulse (only pulsed once per rotation) as signal to the arduino which will then enable the servo at the same point in the spindle rotation for every pass. Count the number of Z pulses and then disable the servo again. How ever I will not the be able to put in a rapid return to start position.

These are my two major head scratchers for now. I would appreciate being pointed in the right direction of similar projects that have been shared or any other relevant reading.

I don't know enough about your stuff to help with your project but this strikes me as strange

I need to pulse the encoder once for every 4.3349...pulses from the spindle.

First I would expect an encoder to produce rather than receive pulses.

Second, I would expect that a digital system could only detect 4 or 5 pulses but nothing in between.

..R

PS ... if you really have very little Arduino experience this project will be very difficult - it could be complex even for an experienced person

How many microseconds between encoder pulses?

Arduino Uno can use the micros() function to time down to 4 usecs granular. Any closer you have to use AVR code (usually NOPs) or at least a well tested timing/sensing loop to get.

If you get a Teensy 4.0 (ARM chip @600Mhz) you can time even closer, but really what do you need?

You can poll at 20 micros average and still get things done using cooperative multitasking on an Uno. 20 micros is 320 cycles and AVR's are RISC chips, the compiler optimizes to use mostly 1 cycle instructions and the CPU has 32 GP registers to keep many instructions as reg-reg. With NO OS getting in the way, it's a surprise at first how fast 16MHz can be.

In 2 weeks.... how good are you at plain old C? Are you quick "getting" new concepts?

Robin2:
I don't know enough about your stuff to help with your project but this strikes me as strange
First I would expect an encoder to produce rather than receive pulses.

Apologies. I corrected original post. The Servo would need to be pulsed 1 step every 4.33.... encoder pulses from the spindle.

@goforsmoke

The A pulse train would come in at 8.5kHz so 117uS between pulses. Using the rising and falling edge of both A and B pulses would give a signal at 34kHz so that would be around 30uS between pulses. (On mobile, can't seem to do multiple quotes)

Nycticoraci:
Apologies. I corrected original post. The Servo would need to be pulsed 1 step every 4.33.... encoder pulses from the spindle.

That does not deal with the question of how you propose to detect fractional pulses that don't exist.

If the time between encoder pulses is constant then you could cause a pulse to be produced a certain amount of time after the 4th pulse. But 4.3349 suggests the acceptable timing error is better than 1 part in 43,000. How do propose to achieve that? What is the likely time interval between encoder pulses?

...R

Nycticoraci:
Apologies. I corrected original post. The Servo would need to be pulsed 1 step every 4.33.... encoder pulses from the spindle.

@goforsmoke

The A pulse train would come in at 8.5kHz so 117uS between pulses. Using the rising and falling edge of both A and B pulses would give a signal at 34kHz so that would be around 30uS between pulses. (On mobile, can't seem to do multiple quotes)

Pin state change interrupts might serve you better, you'd only need to process half as many and -any IO pin- can be used for state change interrupt though they're not as right on the edge as edge-detect interrupts that you don't need. When either pin changes the other is already known even if direction changes.

You still need to keep loop() running faster than the interrupts arrive (57us) which is possible using non-blocking code. You can learn the do many things at once lesson pretty well in days if you're bored enough and don't procrastinate, do you want to dive in there since without it you won't be writing non-blocking code.

With poor code you need a much faster chip. It'd be like using a chain saw to point tent stakes.

@Robin2

30uS between encoder pulses at a spindle speed of 500 rpm. Maybe I will back track a bit. My understanding is this. One full rotation of the spindle can be divided into 4096 divisions.
One full rotation of the lead screw can be divided into 20000 divisions. If I need to move my tool 0.2mm per rotation of the spindle the basic calc is as follows.
0.2÷4096=0.0000488...mm per pulse from the spindle.
Lead screw is a 6 tpi
25.4÷6=4.233...mm per rotation of the lead screw.
Therefore 4.233÷20000=0.00021167mm tool move per pulse/step on the servo.

0.00021167÷0.0000488=4.33...

How ever I like the idea of establishing the time between pulses from the spindle. That can then be used to to work out the number steps to be sent to the servo based on a time interval rather than directly linked to incoming pulses from the spindle. I could then just keep referencing back the input pulses to check that the servo pulses are neither lagging nor leading.

@goforsmoke

I am following what you are saying. Why not go for edge detect interrupt? Would state change interrupts not arrive at roughly the same speed (30uS between interupts)? Would the code not then need to run sub 30uS?

Robin2:
That does not deal with the question of how you propose to detect fractional pulses that don't exist.

If the time between encoder pulses is constant then you could cause a pulse to be produced a certain amount of time after the 4th pulse. But 4.3349 suggests the acceptable timing error is better than 1 part in 43,000. How do propose to achieve that? What is the likely time interval between encoder pulses?

...R

It doesn't have to be anywhere near that 4 decimal places close.

I ran Bridgeport lathes in shop classes back in the 70's and my fingers were fast enough on the control levers and dials. I'm wondering how much of this is a hand controlled lathe possibly missing parts or if there's just a lead screw wanting to be a CNC lathe? With feed and speed mechs built in all I needed to do was catch thread cuts at the end, watch the dials and flip a lever. I could automate one of those with a C64.

Nycticoraci:
30uS between encoder pulses at a spindle speed of 500 rpm

Assuming that it would be sufficient to produce the servo pulse 30% of the time after the 4th encoder pulse then the servo pulse would need to happen 9 microsecs after the encoder pulse. Achieving that on a 16MHz Arduino may be difficult.

On the other hand if the servo pulse just needs to happen some time between the 4th and 5th encoder pulses things would be a lot simpler.

The devil is in the detail.

...R

@goforsmoke

Correct, lathe missing parts and it wants to be a cnc lathe. (Will use a proper motion controller for it through) and yes of course hands are fast enough to do the job. But your gear train is never going to desynch and the beauty of it is that you can even engage your half nut a fraction early and the carriage will simply pull itself into the threads. I just want to make this electronic gear chain as accurate as possible.

@Robin2

The problem is that this is only one example of more 45 different thread types I want to have preloaded. The HMI can do a lot of the calculations and I am pretty familiar with writing macros for it to do that. I am going to play around with different options and see what I can actually do with it with what I currently know and then I may be able to refine me questions. Main thing that is holding me up is that I am all thumbs when it comes to understanding programming in C. I am still looking for good sources of reading (got arduino for dummies so will sit down with that for a bit)

Nycticoraci:
@goforsmoke

I am following what you are saying. Why not go for edge detect interrupt? Would state change interrupts not arrive at roughly the same speed (30uS between interupts)? Would the code not then need to run sub 30uS?

4 edge detect interrupts to detect 2 channels of rising and falling edges vs 2 change interrupts saves over 100 cycles to get the same information.

You then have code that does very little 4 times per A channel 117us pulse. The interrupt should write 0 to 3 to a volatile byte variable that the sketch uses and the changes to 255 to signal data used, wait until it changes to use again.

My previous statement
"You still need to keep loop() running faster than the interrupts arrive (57us)" is wrong, it should be 27us which is still well within non-blocking code speed on an Uno.

I'd like to see what the interrupts do to loop() speed. Are you adverse to trying testing the interrupts doing a little data collection? Do you have any of thise wired up yet?

And what about all those other parts you listed?

I am still looking for good sources of reading (got arduino for dummies so will sit down with that for a bit)

The Arduino main site has a whole manual's worth of material. That top bar of your forum page should have links to all that and more including the language and libraries reference pages. The tutorial section also has the Foundations page with links for pages that explain hardware and software ABC's.

It's easier for me to read online docs as I can zoom those whereas with my reference books I need at least 3x readers.

Don't bother with C++ for what you want. You only need to learn basic C which is a thinner book.

All hardware is ready to go(not installed on machine). Wiring up is pretty straight forward. Only bit I still need to do my homework on is the wiring on the n-type mosfets as the servo drive needs the step/dir signal at 24v.

I have no problem with collecting data to see what will work best. I will post up my sketch once I have something that I think may be able to do the job and then it can be pulled apart from there.

I think I am going to first figure out the RS485 coms. As that is more of a head scratcher for me. Even though it should be more straugh forward.

Robin2:
On the other hand if the servo pulse just needs to happen some time between the 4th and 5th encoder pulses things would be a lot simpler.

You send a pulse to move the servo some very small distance against some resistance (shallow cut in metal), you know it will take time to do that right? I know you know.

So every 468 micros or so the servo gets a pulse and continues moving. Compare to PWM with 1ms or 2ms pulses running an electric train motor smoothly. The coil smooths it out and slows it down, it is an inductor and they do that.

I'm not sure if the servo pulse should happen every 4 pulses at 0.33 of a pulse into that 4th pulse -or- if the servo pulses are 4.33 apart... 4.33, 8.66, 13, 17.33... my guess would be the first.

Nycticoraci:
All hardware is ready to go(not installed on machine). Wiring up is pretty straight forward. Only bit I still need to do my homework on is the wiring on the n-type mosfets as the servo drive needs the step/dir signal at 24v.

I have no problem with collecting data to see what will work best. I will post up my sketch once I have something that I think may be able to do the job and then it can be pulled apart from there.

I think I am going to first figure out the RS485 coms. As that is more of a head scratcher for me. Even though it should be more straugh forward.

You can collect data without connecting the servo or RS485.

Will the RS485 be used as a bus or is it only the distance you want like a long range RS232? Because if the latter you might not have a bunch of protocol to dance around. RS485 modules I know of interface to the RX/TX pins and you use Serial to read and write.

If you have to have the PC USB connection and RS485 at the same time it may be the Uno killer. SoftwareSerial runs its own interrupts and tops out (for me) at 57600 baud. Robin2 can probably tell you more about the load that library adds.

1st link is a store and the 2nd is to their tutorial. The store is Terry King's (member here, an EE) in HK.

https://arduinoinfo.mywikis.net/wiki/RS485-Modules

For the data gathering I dont need the RS485. I can just do that with the PC usb con. But the RS485 is the one keeping me up at night right now. I want to make sure I can read and write with modbus RTU hex adresses. When the system is complete I will use the RS485 connection to send the information needed to the arduino. Basically the step multiplier (I am calling it that for now) and the number of steps to thread the right length. It will also give feed back on rpm and what ever else seems like good info to have.

GoForSmoke:
If you have to have the PC USB connection and RS485 at the same time it may be the Uno killer.

Just a side note. I am working with a Mega 2560

2560 has 4 UARTs but only 1 cpu. You don't want to do a lot of printing while cutting, Some yes, lots no.

Generally the best start is do only what is needed. Next step is to engineer needs away. :slight_smile:
After that see what you have and have room to add -one piece at a time-.
That way you have less debugging to do.

Nycticoraci, I'd save myself countless hours of screwing around with code by getting
something like 3-axis-encoder-conter-arduino-shield

TommySr:
Nycticoraci, I'd save myself countless hours of screwing around with code by getting
something like 3-axis-encoder-conter-arduino-shield

So in the end I did not save myself the countless hours of programming. Here is the program I have come up with. This is my second program since doing the tutorial "blink" program, so it may be very naive and juvenile in the way I have gone about things. Tried to comment it as far as possible.

I decided that I would only use High interrupts for encoder inputs. Output pulse to the Drive is calculated based on the time for the spindle to complete a single revolution based on the encoder divisions. (Time is effectively updated 1024 times for every revolution)

Here is the code that will run during a pass (It obviously wont compile as I have just pulled the sections out for the ease of reading)

void setup() {
  // put your setup code here, to run once:
 pinMode (StepPulse, OUTPUT);
  pinMode (DirPulse, OUTPUT);
  pinMode (SPEncoderA, INPUT);
  pinMode (SPEncoderB, INPUT);
  pinMode (SPEncoderZ, INPUT);
  pinMode (DREncoderA, INPUT);
  pinMode (DREncoderB, INPUT);
  pinMode (DriveLeft, INPUT);
  pinMode (DriveRight, INPUT);

  attachInterrupt(digitalPinToInterrupt(SPEncoderAstate), SPINDLE_TIME, HIGH);
  attachInterrupt(digitalPinToInterrupt(SPEncoderZstate), SPINDLE_ZERO, HIGH);
  attachInterrupt(digitalPinToInterrupt(DREncoderAstate), DRIVE_POS, HIGH);
}

void loop() {
SquareWaveHILO = SpindleMicros/sppsr;//total square wave time calculated
  SquareWave = SquareWaveHILO/2;//half of wave form time
//Backlash correction
  if(LSDIR==HIGH){
   LeadScrewCurrentPos = DriveCurrentPos + BackLash;  
  }else{LeadScrewCurrentPos = DriveCurrentPos - BackLash;}
    

   //RUN ServoLEFT
 if (RUN_SERVOL == HIGH){
  SERVOPULSEFWD();
 }
  //RUN ServoRIGHT
 if (RUN_SERVOR == HIGH){
  SERVOPULSERVS();
 }

 if(RUN_SERVOL == LOW && RUN_SERVOR == LOW){
  PROG_SEL();
 }

 if(PassComplete==HIGH){
  RESET();
 }
}
//Section to generate squre wave pulse to drive
void SERVOPULSEFWD(){
digitalWrite(DirPulse, HIGH);
 if(LeadScrewLeftMAX >= LeadScrewCurrentPos){
 if(PassComplete == LOW){
  if(micros() - PreviousMicros >= SquareWave) {
    PreviousMicros = micros();
    if (PulseState == LOW){
      PulseState = HIGH;
    } else{
      PulseState = LOW;
    }
  }
 }
}
digitalWrite(StepPulse, PulseState);
if(LeadScrewLeftMAX <= LeadScrewCurrentPos){
  PassComplete = HIGH;
 }
}

//Interupt Functions
//Calculate time between A phases PULSES and direction based on relation between A and B phase.
void SPINDLE_TIME(){ 
      SPETimeA = SPETimeB;
      SPETimeB = micros();
      SPEncoderAstate = digitalRead(SPEncoderA);
      SPEncoderBstate = digitalRead(SPEncoderB);
      if (SPEncoderAstate != SPEncoderBstate){
        SpindlePositionABS++;
      }else{
       SpindlePositionABS--;
      }
  SpindlePulseMicros = SPETimeB - SPETimeA;
  SpindleMicros = SpindlePulseMicros * 1024;
     }
// Zero the spindle (Z pulses HIGH once every revolution)
void SPINDLE_ZERO(){
 SpindlePositionABS = 0;
 if(READY_FOR_PASSR == HIGH){
  digitalWrite(RUN_SERVOR, HIGH);  
 }
 if(READY_FOR_PASSL == HIGH){
  digitalWrite(RUN_SERVOL, HIGH);  
 }
}
  
void DRIVE_POS(){
  DREncoderAstate = digitalRead(DREncoderA);
  DREncoderBstate = digitalRead(DREncoderB);
    if (DREncoderAstate != DREncoderBstate){
        DriveCurrentPos++;
        digitalWrite(LSDIR, LOW);//Lead Screw DIRection for backlash
      }else{
        DriveCurrentPos--;
        digitalWrite(LSDIR, HIGH);//Lead Screw DIRection for backlash
      }  
}

I will see if I can attach the full code in another post.

Link to full code on GitHub