Robot,StepperMotor: per-actuator speed limits
[clinton/Smoothieware.git] / src / modules / robot / Robot.cpp
index ef83a01..e5f82ae 100644 (file)
@@ -25,25 +25,60 @@ using std::string;
 #include "arm_solutions/JohannKosselSolution.h"
 #include "arm_solutions/HBotSolution.h"
 
-#define default_seek_rate_checksum             CHECKSUM("default_seek_rate")
-#define default_feed_rate_checksum             CHECKSUM("default_feed_rate")
-#define mm_per_line_segment_checksum           CHECKSUM("mm_per_line_segment")
-#define delta_segments_per_second_checksum     CHECKSUM("delta_segments_per_second")
-#define mm_per_arc_segment_checksum            CHECKSUM("mm_per_arc_segment")
-#define arc_correction_checksum                CHECKSUM("arc_correction")
-#define x_axis_max_speed_checksum              CHECKSUM("x_axis_max_speed")
-#define y_axis_max_speed_checksum              CHECKSUM("y_axis_max_speed")
-#define z_axis_max_speed_checksum              CHECKSUM("z_axis_max_speed")
+#define  default_seek_rate_checksum          CHECKSUM("default_seek_rate")
+#define  default_feed_rate_checksum          CHECKSUM("default_feed_rate")
+#define  mm_per_line_segment_checksum        CHECKSUM("mm_per_line_segment")
+#define  delta_segments_per_second_checksum  CHECKSUM("delta_segments_per_second")
+#define  mm_per_arc_segment_checksum         CHECKSUM("mm_per_arc_segment")
+#define  arc_correction_checksum             CHECKSUM("arc_correction")
+#define  x_axis_max_speed_checksum           CHECKSUM("x_axis_max_speed")
+#define  y_axis_max_speed_checksum           CHECKSUM("y_axis_max_speed")
+#define  z_axis_max_speed_checksum           CHECKSUM("z_axis_max_speed")
 
 // arm solutions
-#define arm_solution_checksum                  CHECKSUM("arm_solution")
-#define cartesian_checksum                     CHECKSUM("cartesian")
-#define rotatable_cartesian_checksum           CHECKSUM("rotatable_cartesian")
-#define rostock_checksum                       CHECKSUM("rostock")
-#define delta_checksum                         CHECKSUM("delta")
-#define hbot_checksum                          CHECKSUM("hbot")
-#define corexy_checksum                        CHECKSUM("corexy")
-#define kossel_checksum                        CHECKSUM("kossel")
+#define  arm_solution_checksum               CHECKSUM("arm_solution")
+#define  cartesian_checksum                  CHECKSUM("cartesian")
+#define  rotatable_cartesian_checksum        CHECKSUM("rotatable_cartesian")
+#define  rostock_checksum                    CHECKSUM("rostock")
+#define  delta_checksum                      CHECKSUM("delta")
+#define  hbot_checksum                       CHECKSUM("hbot")
+#define  corexy_checksum                     CHECKSUM("corexy")
+#define  kossel_checksum                     CHECKSUM("kossel")
+
+// stepper motor stuff
+#define  alpha_step_pin_checksum             CHECKSUM("alpha_step_pin")
+#define  beta_step_pin_checksum              CHECKSUM("beta_step_pin")
+#define  gamma_step_pin_checksum             CHECKSUM("gamma_step_pin")
+#define  alpha_dir_pin_checksum              CHECKSUM("alpha_dir_pin")
+#define  beta_dir_pin_checksum               CHECKSUM("beta_dir_pin")
+#define  gamma_dir_pin_checksum              CHECKSUM("gamma_dir_pin")
+#define  alpha_en_pin_checksum               CHECKSUM("alpha_en_pin")
+#define  beta_en_pin_checksum                CHECKSUM("beta_en_pin")
+#define  gamma_en_pin_checksum               CHECKSUM("gamma_en_pin")
+
+#define  alpha_steps_per_mm_checksum         CHECKSUM("alpha_steps_per_mm")
+#define  beta_steps_per_mm_checksum          CHECKSUM("beta_steps_per_mm")
+#define  gamma_steps_per_mm_checksum         CHECKSUM("gamma_steps_per_mm")
+
+#define  alpha_max_rate_checksum             CHECKSUM("alpha_max_rate")
+#define  beta_max_rate_checksum              CHECKSUM("beta_max_rate")
+#define  gamma_max_rate_checksum             CHECKSUM("gamma_max_rate")
+
+
+// new-style actuator stuff
+#define  actuator_checksum                   CHEKCSUM("actuator")
+
+#define  step_pin_checksum                   CHECKSUM("step_pin")
+#define  dir_pin_checksum                    CHEKCSUM("dir_pin")
+#define  en_pin_checksum                     CHECKSUM("en_pin")
+
+#define  steps_per_mm_checksum               CHECKSUM("steps_per_mm")
+#define  max_rate_checksum                   CHECKSUM("max_rate")
+
+#define  alpha_checksum                      CHECKSUM("alpha")
+#define  beta_checksum                       CHECKSUM("beta")
+#define  gamma_checksum                      CHECKSUM("gamma")
+
 
 // The Robot converts GCodes into actual movements, and then adds them to the Planner, which passes them to the Conveyor so they can be added to the queue
 // It takes care of cutting arcs into segments, same thing for line that are too long
@@ -69,12 +104,6 @@ void Robot::on_module_loaded() {
 
     // Configuration
     this->on_config_reload(this);
-
-    // Make our 3 StepperMotors
-    this->alpha_stepper_motor  = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(&alpha_step_pin,&alpha_dir_pin,&alpha_en_pin) );
-    this->beta_stepper_motor   = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(&beta_step_pin, &beta_dir_pin, &beta_en_pin ) );
-    this->gamma_stepper_motor  = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(&gamma_step_pin,&gamma_dir_pin,&gamma_en_pin) );
-
 }
 
 void Robot::on_config_reload(void* argument){
@@ -116,19 +145,55 @@ void Robot::on_config_reload(void* argument){
     this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f   )->as_number();
     this->mm_per_arc_segment  = THEKERNEL->config->value(mm_per_arc_segment_checksum  )->by_default(0.5f   )->as_number();
     this->arc_correction      = THEKERNEL->config->value(arc_correction_checksum      )->by_default(5      )->as_number();
+
     this->max_speeds[X_AXIS]  = THEKERNEL->config->value(x_axis_max_speed_checksum    )->by_default(60000  )->as_number();
     this->max_speeds[Y_AXIS]  = THEKERNEL->config->value(y_axis_max_speed_checksum    )->by_default(60000  )->as_number();
     this->max_speeds[Z_AXIS]  = THEKERNEL->config->value(z_axis_max_speed_checksum    )->by_default(300    )->as_number();
-    this->alpha_step_pin.from_string( THEKERNEL->config->value(alpha_step_pin_checksum )->by_default("2.0"  )->as_string())->as_output();
-    this->alpha_dir_pin.from_string(  THEKERNEL->config->value(alpha_dir_pin_checksum  )->by_default("0.5"  )->as_string())->as_output();
-    this->alpha_en_pin.from_string(   THEKERNEL->config->value(alpha_en_pin_checksum   )->by_default("0.4"  )->as_string())->as_output();
-    this->beta_step_pin.from_string(  THEKERNEL->config->value(beta_step_pin_checksum  )->by_default("2.1"  )->as_string())->as_output();
-    this->gamma_step_pin.from_string( THEKERNEL->config->value(gamma_step_pin_checksum )->by_default("2.2"  )->as_string())->as_output();
-    this->gamma_dir_pin.from_string(  THEKERNEL->config->value(gamma_dir_pin_checksum  )->by_default("0.20" )->as_string())->as_output();
-    this->gamma_en_pin.from_string(   THEKERNEL->config->value(gamma_en_pin_checksum   )->by_default("0.19" )->as_string())->as_output();
-    this->beta_dir_pin.from_string(   THEKERNEL->config->value(beta_dir_pin_checksum   )->by_default("0.11" )->as_string())->as_output();
-    this->beta_en_pin.from_string(    THEKERNEL->config->value(beta_en_pin_checksum    )->by_default("0.10" )->as_string())->as_output();
 
+    Pin alpha_step_pin;
+    Pin alpha_dir_pin;
+    Pin alpha_en_pin;
+    Pin beta_step_pin;
+    Pin beta_dir_pin;
+    Pin beta_en_pin;
+    Pin gamma_step_pin;
+    Pin gamma_dir_pin;
+    Pin gamma_en_pin;
+
+    alpha_step_pin.from_string( THEKERNEL->config->value(alpha_step_pin_checksum )->by_default("2.0"  )->as_string())->as_output();
+    alpha_dir_pin.from_string(  THEKERNEL->config->value(alpha_dir_pin_checksum  )->by_default("0.5"  )->as_string())->as_output();
+    alpha_en_pin.from_string(   THEKERNEL->config->value(alpha_en_pin_checksum   )->by_default("0.4"  )->as_string())->as_output();
+    beta_step_pin.from_string(  THEKERNEL->config->value(beta_step_pin_checksum  )->by_default("2.1"  )->as_string())->as_output();
+    beta_dir_pin.from_string(   THEKERNEL->config->value(beta_dir_pin_checksum   )->by_default("0.11" )->as_string())->as_output();
+    beta_en_pin.from_string(    THEKERNEL->config->value(beta_en_pin_checksum    )->by_default("0.10" )->as_string())->as_output();
+    gamma_step_pin.from_string( THEKERNEL->config->value(gamma_step_pin_checksum )->by_default("2.2"  )->as_string())->as_output();
+    gamma_dir_pin.from_string(  THEKERNEL->config->value(gamma_dir_pin_checksum  )->by_default("0.20" )->as_string())->as_output();
+    gamma_en_pin.from_string(   THEKERNEL->config->value(gamma_en_pin_checksum   )->by_default("0.19" )->as_string())->as_output();
+
+    float steps_per_mm[3] = {
+        THEKERNEL->config->value(alpha_steps_per_mm_checksum)->by_default(  80.0F)->as_number(),
+        THEKERNEL->config->value(beta_steps_per_mm_checksum )->by_default(  80.0F)->as_number(),
+        THEKERNEL->config->value(gamma_steps_per_mm_checksum)->by_default(2560.0F)->as_number(),
+    };
+
+    // TODO: delete or detect old steppermotors
+    // Make our 3 StepperMotors
+    this->alpha_stepper_motor  = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(alpha_step_pin, alpha_dir_pin, alpha_en_pin) );
+    this->beta_stepper_motor   = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(beta_step_pin,  beta_dir_pin,  beta_en_pin ) );
+    this->gamma_stepper_motor  = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(gamma_step_pin, gamma_dir_pin, gamma_en_pin) );
+
+    alpha_stepper_motor->change_steps_per_mm(steps_per_mm[0]);
+    beta_stepper_motor->change_steps_per_mm(steps_per_mm[1]);
+    gamma_stepper_motor->change_steps_per_mm(steps_per_mm[2]);
+
+    alpha_stepper_motor->max_rate = THEKERNEL->config->value(alpha_max_rate_checksum)->by_default(30000.0F)->as_number() / 60.0F;
+    beta_stepper_motor->max_rate  = THEKERNEL->config->value(beta_max_rate_checksum )->by_default(30000.0F)->as_number() / 60.0F;
+    gamma_stepper_motor->max_rate = THEKERNEL->config->value(gamma_max_rate_checksum)->by_default(30000.0F)->as_number() / 60.0F;
+
+    actuators.clear();
+    actuators.push_back(alpha_stepper_motor);
+    actuators.push_back(beta_stepper_motor);
+    actuators.push_back(gamma_stepper_motor);
 }
 
 void Robot::on_get_public_data(void* argument){
@@ -202,28 +267,31 @@ void Robot::on_gcode_received(void * argument){
                     }
                 }
                 memcpy(this->current_position, this->last_milestone, sizeof(float)*3); // current_position[] = last_milestone[];
-                this->arm_solution->millimeters_to_steps(this->current_position, THEKERNEL->planner->position);
+
+                // TODO: handle any number of actuators
+                float actuator_pos[3];
+                arm_solution->cartesian_to_actuator(current_position, actuator_pos);
+
+                for (int i = 0; i < 3; i++)
+                    actuators[i]->change_last_milestone(actuator_pos[i]);
+
                 gcode->mark_as_taken();
-                return; // TODO: Wait until queue empty
+                return;
            }
        }
    }else if( gcode->has_m){
-        float steps[3];
         switch( gcode->m ){
             case 92: // M92 - set steps per mm
-                this->arm_solution->get_steps_per_millimeter(steps);
                 if (gcode->has_letter('X'))
-                    steps[0] = this->to_millimeters(gcode->get_value('X'));
+                    actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
                 if (gcode->has_letter('Y'))
-                    steps[1] = this->to_millimeters(gcode->get_value('Y'));
+                    actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
                 if (gcode->has_letter('Z'))
-                    steps[2] = this->to_millimeters(gcode->get_value('Z'));
+                    actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
                 if (gcode->has_letter('F'))
                     seconds_per_minute = gcode->get_value('F');
-                this->arm_solution->set_steps_per_millimeter(steps);
-                // update current position in steps
-                this->arm_solution->millimeters_to_steps(this->current_position, THEKERNEL->planner->position);
-                gcode->stream->printf("X:%g Y:%g Z:%g F:%g ", steps[0], steps[1], steps[2], seconds_per_minute);
+
+                gcode->stream->printf("X:%g Y:%g Z:%g F:%g ", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm, seconds_per_minute);
                 gcode->add_nl = true;
                 gcode->mark_as_taken();
                 return;
@@ -287,8 +355,7 @@ void Robot::on_gcode_received(void * argument){
 
             case 500: // M500 saves some volatile settings to config override file
             case 503: // M503 just prints the settings
-                this->arm_solution->get_steps_per_millimeter(steps);
-                gcode->stream->printf(";Steps per unit:\nM92 X%1.5f Y%1.5f Z%1.5f\n", steps[0], steps[1], steps[2]);
+                gcode->stream->printf(";Steps per unit:\nM92 X%1.5f Y%1.5f Z%1.5f\n", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
                 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f\n", THEKERNEL->planner->acceleration/3600);
                 gcode->stream->printf(";X- Junction Deviation, S - Minimum Planner speed:\nM205 X%1.5f S%1.5f\n", THEKERNEL->planner->junction_deviation, THEKERNEL->planner->minimum_planner_speed);
                 gcode->mark_as_taken();
@@ -320,19 +387,27 @@ void Robot::on_gcode_received(void * argument){
 
    //Get parameters
     float target[3], offset[3];
-    clear_vector(target); clear_vector(offset);
+    clear_vector(offset);
 
     memcpy(target, this->current_position, sizeof(target));    //default to last target
 
-    for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter));                                                    } }
-    for(char letter = 'X'; letter <= 'Z'; letter++){ if( gcode->has_letter(letter) ){ target[letter-'X'] = this->to_millimeters(gcode->get_value(letter)) + ( this->absolute_mode ? 0 : target[letter-'X']);  } }
+    for(char letter = 'I'; letter <= 'K'; letter++){
+        if( gcode->has_letter(letter) ){
+            offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter));
+        }
+    }
+    for(char letter = 'X'; letter <= 'Z'; letter++){
+        if( gcode->has_letter(letter) ){
+            target[letter-'X'] = this->to_millimeters(gcode->get_value(letter)) + ( this->absolute_mode ? 0 : target[letter-'X']);
+        }
+    }
 
     if( gcode->has_letter('F') )
     {
         if( this->motion_mode == MOTION_MODE_SEEK )
-            this->seek_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
+            this->seek_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0F;
         else
-            this->feed_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
+            this->feed_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0F;
     }
 
     //Perform any physical actions
@@ -350,7 +425,7 @@ void Robot::on_gcode_received(void * argument){
     // As far as the parser is concerned, the position is now == target. In reality the
     // motion control system might still be processing the action and the real tool position
     // in any intermediate location.
-    memcpy(this->current_position, target, sizeof(float)*3); // this->position[] = target[];
+    memcpy(this->current_position, target, sizeof(this->current_position)); // this->position[] = target[];
 
 }
 
@@ -366,38 +441,58 @@ void Robot::distance_in_gcode_is_known(Gcode* gcode){
 // Reset the position for all axes ( used in homing and G92 stuff )
 void Robot::reset_axis_position(float position, int axis) {
     this->last_milestone[axis] = this->current_position[axis] = position;
-    this->arm_solution->millimeters_to_steps(this->current_position, THEKERNEL->planner->position);
+    actuators[axis]->change_last_milestone(position);
 }
 
 
 // Convert target from millimeters to steps, and append this to the planner
-void Robot::append_milestone( float target[], float rate ){
-    int steps[3]; //Holds the result of the conversion
-
-    // We use an arm solution object so exotic arm solutions can be used and neatly abstracted
-    this->arm_solution->millimeters_to_steps( target, steps );
-
+void Robot::append_milestone( float target[], float rate )
+{
     float deltas[3];
-    for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
+    float unit_vec[3];
+    float actuator_pos[3];
+    float millimeters_of_travel;
+
+    // find distance moved by each axis
+    for (int axis = X_AXIS; axis <= Z_AXIS; axis++)
+        deltas[axis] = target[axis] - last_milestone[axis];
 
     // Compute how long this move moves, so we can attach it to the block for later use
-    float millimeters_of_travel = sqrtf( pow( deltas[X_AXIS], 2 ) +  pow( deltas[Y_AXIS], 2 ) +  pow( deltas[Z_AXIS], 2 ) );
-
-    // Do not move faster than the configured limits
-    for(int axis=X_AXIS;axis<=Z_AXIS;axis++){
-        if( this->max_speeds[axis] > 0 ){
-            float axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * seconds_per_minute;
-            if( axis_speed > this->max_speeds[axis] ){
-                rate = rate * ( this->max_speeds[axis] / axis_speed );
-            }
+    millimeters_of_travel = sqrtf( pow( deltas[X_AXIS], 2 ) +  pow( deltas[Y_AXIS], 2 ) +  pow( deltas[Z_AXIS], 2 ) );
+
+    // find distance unit vector
+    for (int i = 0; i < 3; i++)
+        unit_vec[i] = deltas[i] / millimeters_of_travel;
+
+    // Do not move faster than the configured cartesian limits
+    for (int axis = X_AXIS; axis <= Z_AXIS; axis++)
+    {
+        if ( max_speeds[axis] > 0 )
+        {
+            float axis_speed = fabs(unit_vec[axis] * rate) * seconds_per_minute;
+
+            if (axis_speed > max_speeds[axis])
+                rate = rate * ( max_speeds[axis] / axis_speed );
         }
     }
 
+    // find actuator position given cartesian position
+    arm_solution->cartesian_to_actuator( target, actuator_pos );
+
+    // check per-actuator speed limits
+    for (int actuator = 0; actuator <= 2; actuator++)
+    {
+        float actuator_rate  = fabs(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * rate / millimeters_of_travel;
+
+        if (actuator_rate > actuators[actuator]->max_rate)
+            rate *= (actuators[actuator]->max_rate / actuator_rate);
+    }
+
     // Append the block to the planner
-    THEKERNEL->planner->append_block( steps, rate * seconds_per_minute, millimeters_of_travel, deltas );
+    THEKERNEL->planner->append_block( actuator_pos, rate * seconds_per_minute, millimeters_of_travel, unit_vec );
 
     // Update the last_milestone to the current target for the next time we use last_milestone
-    memcpy(this->last_milestone, target, sizeof(float)*3); // this->last_milestone[] = target[];
+    memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->last_milestone[] = target[];
 
 }
 
@@ -408,9 +503,7 @@ void Robot::append_line(Gcode* gcode, float target[], float rate ){
     gcode->millimeters_of_travel = sqrtf( pow( target[X_AXIS]-this->current_position[X_AXIS], 2 ) +  pow( target[Y_AXIS]-this->current_position[Y_AXIS], 2 ) +  pow( target[Z_AXIS]-this->current_position[Z_AXIS], 2 ) );
 
     // We ignore non-moves ( for example, extruder moves are not XYZ moves )
-    if( gcode->millimeters_of_travel < 0.0001 ){
-        // an extruder only move means we stopped so we need to tell planner that previous speed and unitvector are zero
-        clear_vector_float(THEKERNEL->planner->previous_unit_vec);
+    if( gcode->millimeters_of_travel < 0.0001F ){
         return;
     }
 
@@ -422,7 +515,7 @@ void Robot::append_line(Gcode* gcode, float target[], float rate ){
     // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second The latter is more efficient and avoids splitting fast long lines into very small segments, like initial z move to 0, it is what Johanns Marlin delta port does
     uint16_t segments;
 
-    if(this->delta_segments_per_second > 1.0) {
+    if(this->delta_segments_per_second > 1.0F) {
         // enabled if set to something > 1, it is set to 0.0 by default
         // segment based on current speed and requested segments per second
         // the faster the travel speed the fewer segments needed
@@ -432,7 +525,7 @@ void Robot::append_line(Gcode* gcode, float target[], float rate ){
         // TODO if we are only moving in Z on a delta we don't really need to segment at all
 
     }else{
-        if(this->mm_per_line_segment == 0.0){
+        if(this->mm_per_line_segment == 0.0F){
             segments= 1; // don't split it up
         }else{
             segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
@@ -442,7 +535,7 @@ void Robot::append_line(Gcode* gcode, float target[], float rate ){
     // A vector to keep track of the endpoint of each segment
     float temp_target[3];
     //Initialize axes
-    memcpy( temp_target, this->current_position, sizeof(float)*3); // temp_target[] = this->current_position[];
+    memcpy( temp_target, this->current_position, sizeof(temp_target)); // temp_target[] = this->current_position[];
 
     //For each segment
     for( int i=0; i<segments-1; i++ ){
@@ -480,7 +573,7 @@ void Robot::append_arc(Gcode* gcode, float target[], float offset[], float radiu
     gcode->millimeters_of_travel = hypotf(angular_travel*radius, fabs(linear_travel));
 
     // We don't care about non-XYZ moves ( for example the extruder produces some of those )
-    if( gcode->millimeters_of_travel < 0.0001 ){ return; }
+    if( gcode->millimeters_of_travel < 0.0001F ){ return; }
 
     // Mark the gcode as having a known distance
     this->distance_in_gcode_is_known( gcode );
@@ -515,7 +608,7 @@ void Robot::append_arc(Gcode* gcode, float target[], float offset[], float radiu
     This is important when there are successive arc motions.
     */
     // Vector rotation matrix values
-    float cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
+    float cos_T = 1-0.5F*theta_per_segment*theta_per_segment; // Small angle approximation
     float sin_T = theta_per_segment;
 
     float arc_target[3];