MarlinFirmware / Marlin

Marlin is an optimized firmware for RepRap 3D printers based on the Arduino platform. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.
https://marlinfw.org
GNU General Public License v3.0
16.33k stars 19.25k forks source link

Use Lidar to Trigger Endstop #3269

Closed chevellebro1 closed 8 years ago

chevellebro1 commented 8 years ago

I would like to use a Lidar sensor as an edge finder for a CNC mill. Is it possible to set the Lidar to trigger the endstop pin if the distance changes so that the machine ZERO is at the edge of the material?

Roxy-3D commented 8 years ago

You should be able to configure things so the Lidar Sensor triggers at the edge of the bed. You would want to configure Marlin for Min End stops and it should find the edge and call that 0.000 mm

chevellebro1 commented 8 years ago

The thing I am struggling with is there to configure this? I can't seem to find out how to trigger the endstop.

jbrazio commented 8 years ago

You will need something to interface between your LIDAR and Marlin, hardware wise I mean. Marlin only cares if the end switch is triggered or not, the"relay" device would need to check the distance threshold and trigger the pin.

chevellebro1 commented 8 years ago

Am I not able to manually change pinstate to act as if the endstop has been triggered? Right now I have the Lidar changing the pin associated with the endstop if the value is greater than the threshold. Do I need a relay in order to do this?

Roxy-3D commented 8 years ago

Electrically.... What does the Lidar put out? The easiest endstop interface to an AVR (and Marlin) is a switch that opens and closes. With the pull up resistors activated, you just need to connect this switch from a pin on the AVR and the other connection on the switch to electrical ground.

If your Lidar puts out a signal, you will need to turn that into something that Marlin thinks is a switch opening or closing.

chevellebro1 commented 8 years ago

The Lidar unit is attached: https://github.com/PulsedLight3D/LIDARLite_Basics/tree/master/Arduino/LIDARLite_PWM_GetDistance_ContinuousRead

The sensor counts how long the signal is HIGH and determines distance using that. I was hoping to use there distance reading to change the pin status and act as if the endstop was triggered

jbrazio commented 8 years ago

The example code uses pulseIn() which is a blocking function, for this to be usable with Marlin we had to use an interrupt trigger. The best solution would you to adapt the example code:

Roxy-3D commented 8 years ago

Oh! I get it! Your post is talking about using an AVR as the electronics to do the conversion. And then there would be a separate AVR that is running the Marlin firmware.

It looks like it puts out a Pulse Width Modulated signal. You can use an NE556 to watch for the width of the pulse and adjust it so it puts out a digital signal at the edge of the bed. If you did this, you would just connect this digital output to the pin you are trying to emulate a switch on.

chevellebro1 commented 8 years ago

I've tried adding the lidar function into the main loop but it slows down the process considerably. How can I have it gather more than one value without slowing down the machine?

jbrazio commented 8 years ago

Marlin's main loop ?

chevellebro1 commented 8 years ago

I created a lidar( ) function and put that into the void loop( ) but it seems to be slowing down the movements

jbrazio commented 8 years ago

Did you take time to read my comments ? I said pulseIn() is a blocking function.

chevellebro1 commented 8 years ago

Sorry I misunderstood. So what would I use in place of pulseIn()? I understand that I can trigger another pin to act as my HIGH but what would I replace the pulseIn with?

jbrazio commented 8 years ago

Before going crazy test if the solution will fit your needs, I'm thinking it may not have enough resolution for the application.

Just do a test mockup: Do you have a spare Arduino ? Connect the LIDAR to the Arduino and load up the example sketch you linked in.

Adjust the sketch so when the distance you want to trigger the endstop is reached, it does digitalWrite() to a pin, connect that pin to the Marlin endstop input.

This is the fastest way to test if it will work for you. Makes no sense at this stage to start messing with Marlin source code.

jbrazio commented 8 years ago

Arduino code to get you on the ballpark.

unsigned long pulse_width;

const unsigned int trigger = 100; // select here your trigger condition
const unsigned int endstop = 5; // Digital PIN connected to endstop

void setup()
{
  Serial.begin(9600); // Start serial communications
  pinMode(2, OUTPUT); // Set pin 2 as trigger pin
  pinMode(3, INPUT); // Set pin 3 as monitor pin
  digitalWrite(2, LOW); // Set trigger LOW for continuous read

  pinMode(endstop , OUTPUT); // Set endstop pin as output
  digitalWrite(endstop , LOW); // Set endstop LOW
}

void loop()
{
  pulse_width = pulseIn(3, HIGH); // Count how long the pulse is high in microseconds

  // Serial debug
  if(pulse_width != 0){ // If we get a reading that isn't zero, let's print it
    pulse_width = pulse_width/10; // 10usec = 1 cm of distance for LIDAR-Lite
    Serial.println(pulse_width); // Print the distance
  }

  // endstop trigger
  if (pulse_width <= trigger) digitalWrite(endstop, HIGH);
  else digitalWrite(endstop, LOW);

  delay(20); //Delay so we don't overload the serial port
}
chevellebro1 commented 8 years ago

Thank you, this works great! Now my next question is how to run this while the machine is moving? Since the pulseIn( ) is a block, how am I able to check without it stopping the machine?

jbrazio commented 8 years ago

As you're running this on a different hardware than Marlin is running on there are no issues with pulseIn() nor with delay().

chevellebro1 commented 8 years ago

I see what you're saying, running a separate board for this function. I am a bit limited on space for electronics, is there an alternative for pulseIn( )?

jbrazio commented 8 years ago

You have to use interrupts, raising edge store the time stamp, falling edge do the diff from now and the stored time stamp and you have your distance.

jbrazio commented 8 years ago

All of this was said in my second reply to your question.

chevellebro1 commented 8 years ago

I'm sorry, I'm fairly new at all this. I appreciate you taking time to explain it to me

jbrazio commented 8 years ago

It's not that I do not want to help you but the change you want in Marlin is not as trivial as it seems. You should validate first in a practical way if the hardware solution of the LIDAR works for your intended use. The fastest way is to use an additional Arduino as a relay. The data sheet for that sensor shows the operating range up to 40m, in a CNC application we speak about fractions of a mm (unit).

chevellebro1 commented 8 years ago

For what I am doing, I want the edge finder to sense changes in height which are most likely going to be well within the accuracy of the sensor. For instance I usually mill foam which is 1". I want to use the Lidar to sense the change in height of 1" and trigger the endstop to mark the edge

jbrazio commented 8 years ago

Yes it's "in range", but the sensor was designed to operate up to 40m, 1" is 2.5cm. The example code they give specifies 10us as 1cm.. 10us is not even detectable by millis() you have to use micros() and micros() is known for not be very accurate on AVR. And on top of this we do not know how wide the beam is..

chevellebro1 commented 8 years ago

Yes you are right, I will need to do more testing to see if it is plausible for what I want. I will get a second microprocessor to run the Lidar

Roxy-3D commented 8 years ago

Yes you are right, I will need to do more testing to see if it is plausible for what I want. I will get a second microprocessor to run the Lidar.

Just to prove the idea, a second AVR to run the Lidar is the right answer. But with a small NE556 chip (wired correctly) (and costing $.37) you can make the Lidar into a digital switch. If you are not manufacturing this, it doesn't matter. But if this is going to go a lot of places, you can dramatically cut costs if the idea proves out and works.

jbrazio commented 8 years ago

But with a small NE556 chip you can make the Lidar into a digital switch

This will be a couple of orders of magnitude easier do to than change Marlin to accept a LIDAR as a end-switch..

chevellebro1 commented 8 years ago

I almost have the sensor working now with the external board. I am having on(e) problem though with the homing code. I want the sensor to move and sense the edge of the material but instead of setting it to zero, I want it to store the value. I want the zero position to remain where it hits the endstops and this is a reference to those. I hope that makes sense. I've attached my code

static void lidar_homeaxis(int axis) {
#define HOMEAXIS_DO_LIDAR(LETTER) \
  ((LETTER##_MIN_PIN > -1 && LETTER##_HOME_DIR==-1) || (LETTER##_MAX_PIN > -1 && LETTER##_HOME_DIR==1))

  if (axis==X_AXIS ? HOMEAXIS_DO_LIDAR(X) :
      axis==Y_AXIS ? HOMEAXIS_DO_LIDAR(Y) :
      axis==Z_AXIS ? HOMEAXIS_DO_LIDAR(Z) :
      0) {

    int axis_home_dir = home_dir(axis) * -1;

    current_position[axis] = 0;
    plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);

    destination[axis] = 1.5 * max_length(axis) * axis_home_dir;
    feedrate = 500;

    plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder);
    st_synchronize();

    current_position[axis] = 0;
    plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
    destination[axis] = -home_retract_mm(axis) * axis_home_dir;
    plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder);
    st_synchronize();

    destination[axis] = 2*home_retract_mm(X_AXIS) * axis_home_dir;
    feedrate = 500;

    plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder);
    st_synchronize();

    axis_is_at_home(axis);
    destination[axis] = current_position[axis];
    feedrate = 0.0;
    endstops_hit_on_purpose();
    axis_known_position[axis] = true;

    }
}
Roxy-3D commented 8 years ago

I want the sensor to move and sense the edge of the material but instead of setting it to zero, I want it to store the value. I want the zero position to remain where it hits the endstops and this is a reference to those. I hope that makes sense. I've attached my code

No, that doesn't make sense yet. Besides re-explaining what you want, can you answer "Why are you doing this?" That might give us a better idea what is needed.

chevellebro1 commented 8 years ago

For my cnc mill, I want a quick way to find the edge of a piece of material. Conventionally you would use an edge finder or a probe. I am hoping to replace this with the lidar sensor. I have had pretty good success so far. In the video attached, the machine zeros itself with the endstops before moving to a predefined position to start looking for the edge of the material. Once it finds the edge in the X direction, it then moves to the Y axis and also finds the edge there. This works great.

PROBLEM: Each times it finds the edge of the material, the machine position resets to 0,0,0. So when I find the edge of the material in the X axis, the machine zeroes itself and then moves to the Y axis where it repeats this and zeros on that edge. My issue is that because it is zeroing in both places, I am loosing my position for the edge of material in the X axis because it is reset when the Y finds the edge.

GOAL: I would like the machine to find the edge in the X axis and without resetting the machine zero, move to the Y axis and find the edge also without resetting zero. At this point I will have the edge position in X and Y. Then from here I want the machine to move to the corner of the two edges measured and then set machine zero.

Hopefully that helps explain more and the video will help.

https://youtu.be/zlWfJOwkzDA

jbrazio commented 8 years ago

Have you had a look at relative positioning ? You want to find you machine 0,0 and then the work piece 0,0 which will be relative to your machine 0,0 and should not replace it.

On a real CNC that's G54, on Marlin have a look at G92.

Please keep in your mind that the GCode used by a 3D printer may not be 100% valid for a CNC.

o_O

chevellebro1 commented 8 years ago

I'll see if I can adapt the G92 code for what I am doing. That's a good point, I don't want to move the home position, just reference it

Roxy-3D commented 8 years ago

I think relative positioning has a lot of merit here. But I've never used it so I'm kind of thinking about your problem from a different perspective. Isn't it appropriate for the CNC machine to position the corner of the material at (0,0) ? And you would just make sure your GCode has the generated part at the origin also?

But all that aside... Your code up above does not ask the Lidar sensor where it is. This code sets the current position (for the axis being homed) to 0.0 and goes towards the endstop until it bumps into the switch.

    current_position[axis] = 0;
    plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
    destination[axis] = -home_retract_mm(axis) * axis_home_dir;
    plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder);
    st_synchronize();

Then it retracts a little bit... And it should bump at a slower speed, but you have the 1st and 2nd bump happening at the same speed (which doesn't seem right).

    destination[axis] = 2*home_retract_mm(X_AXIS) * axis_home_dir;
    feedrate = 500;
    plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder);
    st_synchronize();

And then this next code just sets the position based on the current location of the bump. But you never reference the Lidar sensor. (Unless you buried that way down into the stepper code that watches for endstops being triggered.)

    axis_is_at_home(axis);
    destination[axis] = current_position[axis];
    feedrate = 0.0;
    endstops_hit_on_purpose();
    axis_known_position[axis] = true;
chevellebro1 commented 8 years ago

You are correct for the 0,0 position for a CNC machine. In the code software, you define the corner in which the material is homed and use that as the 0,0. The zero for the machine is constantly changing based on the program. The homing endstops are just used to reference and not as a zero as it is with 3D printing.

I should have included this additional code:

case 20: // G20 Lidar Edge Finder

    saved_feedrate = feedrate;
    saved_feedmultiply = feedmultiply;
    feedmultiply = 100;
    previous_millis_cmd = millis();

    // First Move X and Y to Home Position

    home_all_axis = !((code_seen(axis_codes[X_AXIS])) || (code_seen(axis_codes[Y_AXIS])) || (code_seen(axis_codes[Z_AXIS])));

    if((home_all_axis) || (code_seen(axis_codes[X_AXIS])))
    {
      HOMEAXIS(X);
    }

    if((home_all_axis) || (code_seen(axis_codes[Y_AXIS]))) {
      HOMEAXIS(Y);
    }

    if(code_seen(axis_codes[X_AXIS]))
    {
      if(code_value_long() != 0) {
     current_position[X_AXIS]=code_value()+add_homing[X_AXIS];
      }
    }

    if(code_seen(axis_codes[Y_AXIS])) {
      if(code_value_long() != 0) {
       #ifdef SCARA
     current_position[Y_AXIS]=code_value();
  #else
     current_position[Y_AXIS]=code_value()+add_homing[Y_AXIS];
  #endif
      }
    }

    plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);

    //Enable Lidar Endstop and Zero to Edge of Material

          destination_lidar[X_AXIS] = 60; //Position to Start Lidar Edgefinder
          destination_lidar[Y_AXIS] = 20;
          feedrate_lidar = 2000;

          plan_buffer_line(destination_lidar[X_AXIS], destination_lidar[Y_AXIS], destination_lidar[Z_AXIS], destination_lidar[E_AXIS], feedrate_lidar/60, active_extruder);
          st_synchronize();

          enable_endstops(true);
          feedrate = 0.0;
          for(int8_t i=0; i < NUM_AXIS; i++) {
            destination[i] = current_position[i];
          }

          home_all_axis = !((code_seen(axis_codes[X_AXIS])) || (code_seen(axis_codes[Y_AXIS])) || (code_seen(axis_codes[Z_AXIS])));

          if((home_all_axis) || (code_seen(axis_codes[X_AXIS])))
          {
            digitalWrite(LIDAR_RESET, HIGH); //Trigger external AVR board to reset saved distance
            delay(1000);
            digitalWrite(LIDAR_RESET, LOW);

            digitalWrite(LIDAR_TRIGGER, HIGH); //Trigger external AVR to enable endstop
            delay(500);
            LIDAR_HOMEAXIS(X);
            digitalWrite(LIDAR_TRIGGER, LOW);
          }
          delay(1000);

          destination_lidar[X_AXIS] = 80;
          destination_lidar[Y_AXIS] = 245;
          feedrate_lidar = 2000;

          plan_buffer_line(destination_lidar[X_AXIS], destination_lidar[Y_AXIS], destination_lidar[Z_AXIS], destination_lidar[E_AXIS], feedrate_lidar/60, active_extruder);
          st_synchronize();

          if((home_all_axis) || (code_seen(axis_codes[Y_AXIS]))) {
            digitalWrite(LIDAR_RESET, HIGH);
            delay(1000);
            digitalWrite(LIDAR_RESET, LOW);

            digitalWrite(LIDAR_TRIGGER, HIGH);
            delay(500);
            LIDAR_HOMEAXIS(Y);
            digitalWrite(LIDAR_TRIGGER, LOW);
          }

          delay(1000);

And this is the code being run on the external AVR that controls the lidar sensor

unsigned long pulse_width;

int trigger_pin = 2;
int monitor_pin = 3;
int reset_pin = 4;
int endstop = 10; // Digital PIN connected to endstop
int lidar_pin = 5;

unsigned int time = 0;
unsigned int lidar = 0;
unsigned int bed_height = 0;
unsigned int reset = 0;
unsigned int trigger = 0; // select here your trigger condition

void setup()
{
  Serial.begin(9600); // Start serial communications
  pinMode(trigger_pin, OUTPUT); // Set pin 2 as trigger pin
  pinMode(monitor_pin, INPUT); // Set pin 3 as monitor pin
  pinMode(reset_pin, INPUT_PULLUP); //Input from main board
  pinMode(lidar_pin, INPUT_PULLUP); //
  digitalWrite(trigger_pin, LOW); // Set trigger LOW for continuous read

  pinMode(endstop , OUTPUT); // Set endstop pin as output
  digitalWrite(endstop , LOW); // Set endstop LOW
}

void loop()
{
  lidar = digitalRead(lidar_pin);
  reset = digitalRead(reset_pin);

  pulse_width = pulseIn(monitor_pin, HIGH); // Count how long the pulse is high in microseconds

  // Serial debug
  if(pulse_width != 0){ // If we get a reading that isn't zero, let's print it
    pulse_width = pulse_width/10; // 10usec = 1 cm of distance for LIDAR-Lit
    Serial.print("DISTANCE ");
    Serial.println(pulse_width); // Print the distance
  }

  //Reset Bed Position
  if (reset == 1) {
    bed_height = pulse_width;
    trigger = bed_height - 5;
    Serial.println("RESET");
  }
  else {
  }

  // endstop trigger
  if (lidar == 1) {
    if (pulse_width <= trigger) {
      digitalWrite(endstop, HIGH);
      Serial.println("TRIGGERED!!");
    }
    else {
      digitalWrite(endstop, LOW);
    }
  }
  else {
    digitalWrite(endstop, LOW);
  }

  //Serial Debugger

    Serial.print("BED HEIGHT ");
    Serial.println(bed_height);
    Serial.print("TRIGGER ");
    Serial.println(trigger);
    Serial.print("STAGE");
    Serial.println(lidar);
    Serial.print("RESET");
    Serial.println(reset);
    Serial.println();

  delay(0); //Delay so we don't overload the serial port
}

I stil want the machine 0,0 to be set at the corner. I just want to delay the setting zero until after both the X and Y edges have been found and the machine is then moved to the corner. Then set machine 0,0 on the corner of the material.

jbrazio commented 8 years ago

Relative positioning is specially important on a CNC because you'll never want to cut your table, so after homing XYZ and zeroing your workpiece XYZ then you can only go as deep as the workpiece Z height and never into the table. CNC uses negative Z as we're removing material from the workpiece.

jbrazio commented 8 years ago

Thank you for your interest making Marlin better and reporting this issue but this topic has been open for a long period of time without any further development. Marlin has been under heavy development for the past couple of months and moving to it's last mile to finish the RC cycle and release Marlin v1.1.0. We suggest you to try out the latest RCBugfix branch and reopening this issue if required.

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.