integrating chamnit's grbl-edge's math
authorArthur Wolf <wolf.arthur@gmail.com>
Sat, 1 Oct 2011 22:36:12 +0000 (00:36 +0200)
committerArthur Wolf <wolf.arthur@gmail.com>
Sat, 1 Oct 2011 22:36:12 +0000 (00:36 +0200)
src/libs/nuts_bolts.h
src/modules/robot/Block.cpp
src/modules/robot/Block.h
src/modules/robot/Planner.cpp
src/modules/robot/Planner.h
src/modules/robot/Robot.cpp
src/modules/robot/Robot.h
src/modules/robot/Stepper.cpp

index cb33cc3..7b1cac7 100644 (file)
@@ -33,9 +33,10 @@ using std::string;
 #define GAMMA_STEPPER 2
 
 #define clear_vector(a) memset(a, 0, sizeof(a))
+#define clear_vector_double(a) memset(a, 0.0, sizeof(a))
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 
-#define dd(...) LPC_GPIO2->FIOCLR = 0xffff; LPC_GPIO2->FIOSET = __VA_ARGS__ 
+#define dd(...) LPC_GPIO2->FIODIR = 0xffff; LPC_GPIO2->FIOCLR = 0xffff; LPC_GPIO2->FIOSET = __VA_ARGS__ 
 
 
 #endif
index 7eb50c6..298d9a8 100644 (file)
@@ -20,11 +20,11 @@ using std::string;
 
 Block::Block(){
     clear_vector(this->steps);
-    clear_vector(this->speeds);
+    //clear_vector(this->speeds);
 }
 
 void Block::debug(Kernel* kernel){
-    kernel->serial->printf(" steps:%4d|%4d|%4d(max:%4d) speeds:%8.2f|%8.2f|%8.2f nominal:r%10d/s%6.1f mm:%9.6f rdelta:%8d acc:%5d dec:%5d factor:%6.4f rates:%10d>%10d \r\n", this->steps[0], this->steps[1], this->steps[2], this->steps_event_count, this->speeds[0], this->speeds[1], this->speeds[2], this->nominal_rate, this->nominal_speed, this->millimeters, this->rate_delta, this->accelerate_until, this->decelerate_after, this->entry_factor, this->initial_rate, this->final_rate );
+    kernel->serial->printf(" steps:%4d|%4d|%4d(max:%4d) nominal:r%10d/s%6.1f mm:%9.6f rdelta:%8d acc:%5d dec:%5d rates:%10d>%10d \r\n", this->steps[0], this->steps[1], this->steps[2], this->steps_event_count, this->nominal_rate, this->nominal_speed, this->millimeters, this->rate_delta, this->accelerate_until, this->decelerate_after, this->initial_rate, this->final_rate );
 }
 
 
@@ -44,8 +44,8 @@ double Block::compute_factor_for_safe_speed(){
 //                              +-------------+
 //                                  time -->
 void Block::calculate_trapezoid( double entryfactor, double exitfactor ){
-    this->initial_rate = ceil(this->nominal_rate * entryfactor); 
-    this->final_rate   = ceil(this->nominal_rate * exitfactor);
+    this->initial_rate = ceil(this->nominal_rate * entryfactor);   // (step/min) 
+    this->final_rate   = ceil(this->nominal_rate * exitfactor);    // (step/min)
     double acceleration_per_minute = this->rate_delta * this->planner->kernel->stepper->acceleration_ticks_per_second * 60.0; 
     int accelerate_steps = ceil( this->estimate_acceleration_distance( this->initial_rate, this->nominal_rate, acceleration_per_minute ) );
     int decelerate_steps = ceil( this->estimate_acceleration_distance( this->nominal_rate, this->final_rate,  -acceleration_per_minute ) );
@@ -58,6 +58,8 @@ void Block::calculate_trapezoid( double entryfactor, double exitfactor ){
    // in order to reach the final_rate exactly at the end of this block.
    if (plateau_steps < 0) {
        accelerate_steps = ceil(this->intersection_distance(this->initial_rate, this->final_rate, acceleration_per_minute, this->steps_event_count));
+       accelerate_steps = max( accelerate_steps, 0 ); // Check limits due to numerical round-off
+       accelerate_steps = min( accelerate_steps, int(this->steps_event_count) );
        plateau_steps = 0;
    }
    
@@ -90,6 +92,7 @@ double Block::intersection_distance(double initialrate, double finalrate, double
    return((2*acceleration*distance-initialrate*initialrate+finalrate*finalrate)/(4*acceleration));
 }
 
+/*
 // "Junction jerk" in this context is the immediate change in speed at the junction of two blocks.
 // This method will calculate the junction jerk as the euclidean distance between the nominal
 // velocities of the respective blocks.
@@ -101,7 +104,7 @@ inline double junction_jerk(Block* before, Block* after) {
                 pow(before->speeds[Z_AXIS]-after->speeds[Z_AXIS], 2))
           );
 }
-
+*/
 
 // Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
 // acceleration within the allotted distance.
@@ -115,49 +118,49 @@ inline double max_allowable_speed(double acceleration, double target_velocity, d
 // Called by Planner::recalculate() when scanning the plan from last to first entry.
 void Block::reverse_pass(Block* next, Block* previous){
 
-    double entryfactor = 1.0;
-    double exitfactor;
-    if (next) { exitfactor = next->entry_factor; } else { exitfactor = this->compute_factor_for_safe_speed(); }
-
-    // Calculate the entry_factor for the this block.
-    if (previous) {
-        // Reduce speed so that junction_jerk is within the maximum allowed
-        double jerk = junction_jerk(previous, this);
-        if (jerk > this->planner->max_jerk ) {    //TODO: Get from config settings.max_jerk
-            entryfactor = (this->planner->max_jerk/jerk);
-        }
-        // If the required deceleration across the block is too rapid, reduce the entry_factor accordingly.
-        if (entryfactor > exitfactor) {
-            double max_entry_speed = max_allowable_speed(-this->planner->acceleration,this->nominal_speed*exitfactor, this->millimeters); 
-            double max_entry_factor = max_entry_speed/this->nominal_speed;
-            if (max_entry_factor < entryfactor) {
-                entryfactor = max_entry_factor;
+    if (next) {
+        // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising.
+        // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and
+        // check for maximum allowable speed reductions to ensure maximum possible planned speed.
+        if (this->entry_speed != this->max_entry_speed) {
+
+            // If nominal length true, max junction speed is guaranteed to be reached. Only compute
+            // for max allowable speed if block is decelerating and nominal length is false.
+            if ((!this->nominal_length_flag) && (this->max_entry_speed > next->entry_speed)) {
+                this->entry_speed = min( this->max_entry_speed, max_allowable_speed(-this->planner->acceleration,next->entry_speed,this->millimeters));
+            } else {
+                this->entry_speed = this->max_entry_speed;
             }
-        }
-    } else {
-        entryfactor = this->compute_factor_for_safe_speed();
-    }
-    this->entry_factor = entryfactor;
-}
+            this->recalculate_flag = true;
 
+        }
+    } // Skip last block. Already initialized and set for recalculation.
 
+}
 
 
 // Called by Planner::recalculate() when scanning the plan from first to last entry.
 void Block::forward_pass(Block* previous, Block* next){
-  if(previous) {
-      // If the previous block is an acceleration block, but it is not long enough to
-      // complete the full speed change within the block, we need to adjust out entry
-      // speed accordingly. Remember this->entry_factor equals the exit factor of
-      // the previous block.
-      if(previous->entry_factor < this->entry_factor) {
-          double max_entry_speed = max_allowable_speed(-this->planner->acceleration, this->nominal_speed*previous->entry_factor, previous->millimeters); 
-          double max_entry_factor = max_entry_speed/this->nominal_speed;
-          if (max_entry_factor < this->entry_factor) {
-              this->entry_factor = max_entry_factor;
+
+    if(!previous) { return; } // Begin planning after buffer_tail
+
+    // If the previous block is an acceleration block, but it is not long enough to complete the
+    // full speed change within the block, we need to adjust the entry speed accordingly. Entry
+    // speeds have already been reset, maximized, and reverse planned by reverse planner.
+    // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck.
+    if (!previous->nominal_length_flag) {
+        if (previous->entry_speed < this->entry_speed) {
+          double entry_speed = min( this->entry_speed,
+            max_allowable_speed(-this->planner->acceleration,previous->entry_speed,previous->millimeters) );
+
+          // Check for junction speed change
+          if (this->entry_speed != entry_speed) {
+            this->entry_speed = entry_speed;
+            this->recalculate_flag = true;
           }
-      }
-  }
+        }
+    }
+      
 }
 
 
index fd61e91..140e7d7 100644 (file)
@@ -16,6 +16,9 @@ using namespace std;
 #include "Planner.h"
 class Planner;
 
+
+double max_allowable_speed( double acceleration, double target_velocity, double distance);
+
 class Block {
     public:
         Block();
@@ -32,12 +35,12 @@ class Block {
         vector<std::string> commands;
 
         unsigned short steps[3];           // Number of steps for each axis for this block
-        float          speeds[3];          // Speeds for each axis, used in the planning process
+        //float          speeds[3];          // Speeds for each axis, used in the planning process
         unsigned short steps_event_count;  // Steps for the longest axis
         unsigned int   nominal_rate;       // Nominal rate in steps per minute
         float          nominal_speed;      // Nominal speed in mm per minute
         float          millimeters;        // Distance for this move
-        float          entry_factor;       // Starting speed for the block
+        double entry_speed;
         unsigned int rate_delta;           // Nomber of steps to add to the speed for each acceleration tick
         unsigned int   initial_rate;       // Initial speed in steps per minute
         unsigned int   final_rate;         // Final speed in steps per minute
@@ -45,6 +48,11 @@ class Block {
         unsigned short decelerate_after;   // Start decelerating after this number of steps
         unsigned int   direction_bits;     // Direction for each axis in bit form, relative to the direction port's mask
 
+
+        uint8_t recalculate_flag; // Planner flag to recalculate trapezoids on entry junction
+        uint8_t nominal_length_flag; // Planner flag for nominal speed always reached
+
+        double max_entry_speed;
         Planner* planner;
 };
 
index c7d5b02..2d3867d 100644 (file)
@@ -17,7 +17,9 @@ using namespace std;
 #include "Planner.h"
 
 Planner::Planner(){
-    clear_vector(this->position); 
+    clear_vector(this->position);
+    clear_vector_double(this->previous_unit_vec);
+    this->previous_nominal_speed = 0.0;
     this->has_deleted_block = false;
 }
 
@@ -32,62 +34,119 @@ void Planner::on_config_reload(void* argument){
 
 
 // Append a block to the queue, compute it's speed factors
-void Planner::append_block( int target[], double feed_rate, double distance, double speeds[] ){
-    
+void Planner::append_block( int target[], double feed_rate, double distance, double deltas[] ){
+   
     // TODO : Check if this is necessary
-    this->computing = true;
     
     // Do not append block with no movement
     if( target[ALPHA_STEPPER] == this->position[ALPHA_STEPPER] && target[BETA_STEPPER] == this->position[BETA_STEPPER] && target[GAMMA_STEPPER] == this->position[GAMMA_STEPPER] ){ this->computing = false; return; }
-    
-    // Stall here if the queue is full
+   
+    // Stall here if the queue is ful
     while( this->queue.size() >= this->queue.capacity() ){ wait_us(100); }     
     
     // Add/get a new block from the queue
     this->queue.push_back(Block());
     Block* block = this->queue.get_ref( this->queue.size()-1 );
     block->planner = this;
+
+    this->computing = true;
+
+    // Direction bits
+    block->direction_bits = 0; 
+    char direction_bits[3] = {this->kernel->stepper->alpha_dir_pin, this->kernel->stepper->beta_dir_pin, this->kernel->stepper->gamma_dir_pin}; 
+    for( int stepper=ALPHA_STEPPER; stepper<=GAMMA_STEPPER; stepper++){ 
+        if( target[stepper] < position[stepper] ){ block->direction_bits |= (1<<direction_bits[stepper]); } 
+    }
     
     // Number of steps for each stepper
     for( int stepper=ALPHA_STEPPER; stepper<=GAMMA_STEPPER; stepper++){ block->steps[stepper] = labs(target[stepper] - this->position[stepper]); } 
     
     // Max number of steps, for all axes
     block->steps_event_count = max( block->steps[ALPHA_STEPPER], max( block->steps[BETA_STEPPER], block->steps[GAMMA_STEPPER] ) );
+    if( block->steps_event_count == 0 ){ this->computing = false; return; }
 
-    // Speeds for each axis, needed for junction_jerk
-    for( int stepper=ALPHA_STEPPER; stepper<=GAMMA_STEPPER; stepper++){ block->speeds[stepper] = speeds[stepper]; } 
-
-    // Set rates and speeds
-    double microseconds = ( distance / feed_rate ) * 1000000;
-    double multiplier   = 60*1000000/microseconds;
-    block->nominal_rate = block->steps_event_count*multiplier;
-    block->nominal_speed = distance*multiplier;
     block->millimeters = distance;
-    block->entry_factor = 0;
-    
+    double inverse_millimeters = 1.0/distance;
+
+    // Calculate speed in mm/minute for each axis. No divide by zero due to previous checks.
+    // NOTE: Minimum stepper speed is limited by MINIMUM_STEPS_PER_MINUTE in stepper.c
+    double inverse_minute = feed_rate * inverse_millimeters;
+    block->nominal_speed = block->millimeters * inverse_minute;           // (mm/min) Always > 0
+    block->nominal_rate = ceil(block->steps_event_count * inverse_minute); // (step/min) Always > 0
+
     // Compute the acceleration rate for the trapezoid generator. Depending on the slope of the line
     // average travel per step event changes. For a line along one axis the travel per step event
     // is equal to the travel/step in the particular axis. For a 45 degree line the steppers of both
     // axes might step for every step event. Travel per step event is then sqrt(travel_x^2+travel_y^2).
     // To generate trapezoids with contant acceleration between blocks the rate_delta must be computed
     // specifically for each line to compensate for this phenomenon:
-    double travel_per_step = (double) ((double) block->millimeters / (double) block->steps_event_count);
-    block->rate_delta = ceil(( (this->acceleration*60) / this->kernel->stepper->acceleration_ticks_per_second ) / travel_per_step);
+    // Convert universal acceleration for direction-dependent stepper rate change parameter
+    block->rate_delta = ceil( block->steps_event_count*inverse_millimeters * this->acceleration*60.0 / this->kernel->stepper->acceleration_ticks_per_second ); // (step/min/acceleration_tick)
 
-    // Comupte a preliminary conservative acceleration trapezoid
-    double safe_speed_factor = block->compute_factor_for_safe_speed();
-    block->calculate_trapezoid( safe_speed_factor, safe_speed_factor );
-    
-    // Direction bits
-    block->direction_bits = 0; 
-    char direction_bits[3] = {this->kernel->stepper->alpha_dir_pin, this->kernel->stepper->beta_dir_pin, this->kernel->stepper->gamma_dir_pin}; 
-    for( int stepper=ALPHA_STEPPER; stepper<=GAMMA_STEPPER; stepper++){ 
-        if( target[stepper] < position[stepper] ){ block->direction_bits |= (1<<direction_bits[stepper]); } 
+
+    // Compute path unit vector
+    double unit_vec[3];
+    unit_vec[X_AXIS] = deltas[X_AXIS]*inverse_millimeters;
+    unit_vec[Y_AXIS] = deltas[Y_AXIS]*inverse_millimeters;
+    unit_vec[Z_AXIS] = deltas[Z_AXIS]*inverse_millimeters;
+  
+    // Compute maximum allowable entry speed at junction by centripetal acceleration approximation.
+    // Let a circle be tangent to both previous and current path line segments, where the junction
+    // deviation is defined as the distance from the junction to the closest edge of the circle,
+    // colinear with the circle center. The circular segment joining the two paths represents the
+    // path of centripetal acceleration. Solve for max velocity based on max acceleration about the
+    // radius of the circle, defined indirectly by junction deviation. This may be also viewed as
+    // path width or max_jerk in the previous grbl version. This approach does not actually deviate
+    // from path, but used as a robust way to compute cornering speeds, as it takes into account the
+    // nonlinearities of both the junction angle and junction velocity.
+    double vmax_junction = MINIMUM_PLANNER_SPEED; // Set default max junction speed
+
+    if (this->queue.size() > 1 && (this->previous_nominal_speed > 0.0)) {
+      // Compute cosine of angle between previous and current path. (prev_unit_vec is negative)
+      // NOTE: Max junction velocity is computed without sin() or acos() by trig half angle identity.
+      double cos_theta = - this->previous_unit_vec[X_AXIS] * unit_vec[X_AXIS]
+                         - this->previous_unit_vec[Y_AXIS] * unit_vec[Y_AXIS]
+                         - this->previous_unit_vec[Z_AXIS] * unit_vec[Z_AXIS] ;
+                           
+      // Skip and use default max junction speed for 0 degree acute junction.
+      if (cos_theta < 0.95) {
+        vmax_junction = min(this->previous_nominal_speed,block->nominal_speed);
+        // Skip and avoid divide by zero for straight junctions at 180 degrees. Limit to min() of nominal speeds.
+        if (cos_theta > -0.95) {
+          // Compute maximum junction velocity based on maximum acceleration and junction deviation
+          double sin_theta_d2 = sqrt(0.5*(1.0-cos_theta)); // Trig half angle identity. Always positive.
+          vmax_junction = min(vmax_junction,
+            sqrt(this->acceleration*60*60 * 0.05 * sin_theta_d2/(1.0-sin_theta_d2)) ); // TODO: Get from config
+        }
+      }
     }
-    
+    block->max_entry_speed = vmax_junction;
+   
+    // Initialize block entry speed. Compute based on deceleration to user-defined MINIMUM_PLANNER_SPEED.
+    double v_allowable = this->max_allowable_speed(-this->acceleration,0.0,block->millimeters); //TODO: Get from config
+    block->entry_speed = min(vmax_junction, v_allowable);
+
+    // Initialize planner efficiency flags
+    // Set flag if block will always reach maximum junction speed regardless of entry/exit speeds.
+    // If a block can de/ac-celerate from nominal speed to zero within the length of the block, then
+    // the current block and next block junction speeds are guaranteed to always be at their maximum
+    // junction speeds in deceleration and acceleration, respectively. This is due to how the current
+    // block nominal speed limits both the current and next maximum junction speeds. Hence, in both
+    // the reverse and forward planners, the corresponding block junction speed will always be at the
+    // the maximum junction speed and may always be ignored for any speed reduction checks.
+    if (block->nominal_speed <= v_allowable) { block->nominal_length_flag = true; }
+    else { block->nominal_length_flag = false; }
+    block->recalculate_flag = true; // Always calculate trapezoid for new block
+
+    // Update previous path unit_vector and nominal speed
+    memcpy(this->previous_unit_vec, unit_vec, sizeof(unit_vec)); // previous_unit_vec[] = unit_vec[]
+    this->previous_nominal_speed = block->nominal_speed;
+
     memcpy(this->position, target, sizeof(int)*3);
     this->recalculate();
     this->computing = false;
+
     this->kernel->call_event(ON_STEPPER_WAKE_UP, this);
 }
 
@@ -129,8 +188,7 @@ void Planner::recalculate() {
 // implements the reverse pass.
 void Planner::reverse_pass(){
      // For each block
-    for( int index = this->queue.size()-1; index >= 0; index-- ){
-        //this->queue.get_ref(index)->reverse_pass((index==this->queue.size()-1?NULL:this->queue.get_ref(index+1)), (index==0?NULL:this->queue.get_ref(index-1))); 
+    for( int index = this->queue.size()-1; index > 0; index-- ){  // Skip buffer tail/first block to prevent over-writing the initial entry speed.
         this->queue.get_ref(index)->reverse_pass((index==this->queue.size()-1?NULL:this->queue.get_ref(index+1)), (index==0? (this->has_deleted_block?&(this->last_deleted_block):NULL) :this->queue.get_ref(index-1))); 
     }
 }
@@ -138,24 +196,32 @@ void Planner::reverse_pass(){
 // Planner::recalculate() needs to go over the current plan twice. Once in reverse and once forward. This
 // implements the forward pass.
 void Planner::forward_pass() {
-     // For each block
+    // For each block
     for( int index = 0; index <= this->queue.size()-1; index++ ){
         this->queue.get_ref(index)->forward_pass((index==0?NULL:this->queue.get_ref(index-1)),(index==this->queue.size()-1?NULL:this->queue.get_ref(index+1))); 
     }
 }
 
-// Recalculates the trapezoid speed profiles for all blocks in the plan according to the
-// entry_factor for each junction. Must be called by Planner::recalculate() after
-// updating the blocks.
+// Recalculates the trapezoid speed profiles for flagged blocks in the plan according to the
+// entry_speed for each junction and the entry_speed of the next junction. Must be called by
+// planner_recalculate() after updating the blocks. Any recalulate flagged junction will
+// compute the two adjacent trapezoids to the junction, since the junction speed corresponds
+// to exit speed and entry speed of one another.
 void Planner::recalculate_trapezoids() {
     // For each block
-    for( int index = 0; index <= this->queue.size()-1; index++ ){
+    for( int index = 0; index <= this->queue.size()-1; index++ ){ // We skip the first one because we need a previous
 
         if( this->queue.size()-1 == index ){ //last block
-           this->queue.get_ref(index)->calculate_trapezoid( this->queue.get_ref(index)->entry_factor, this->queue.get_ref(index)->compute_factor_for_safe_speed() ); 
+            Block* last = this->queue.get_ref(index);
+            last->calculate_trapezoid( last->entry_speed / last->nominal_speed, MINIMUM_PLANNER_SPEED / last->nominal_speed ); 
         }else{
-           this->queue.get_ref(index)->calculate_trapezoid( this->queue.get_ref(index)->entry_factor, this->queue.get_ref(index+1)->entry_factor ); 
-        } 
+            Block* current = this->queue.get_ref(index);
+            Block* next =    this->queue.get_ref(index+1);
+            if( current->recalculate_flag || next->recalculate_flag ){
+                current->calculate_trapezoid( current->entry_speed/current->nominal_speed, next->entry_speed/current->nominal_speed );
+                current->recalculate_flag = false; // Reset current only to ensure next trapezoid is computed
+            } 
+        }
     }
 }
 
@@ -181,3 +247,15 @@ void Planner::dump_queue(){
        this->queue.get_ref(index)->debug(this->kernel); 
     }
 }
+
+
+
+// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
+// acceleration within the allotted distance.
+double Planner::max_allowable_speed(double acceleration, double target_velocity, double distance) {
+  return(
+    sqrt(target_velocity*target_velocity-2L*acceleration*60*60*distance)  //Was acceleration*60*60*distance, in case this breaks, but here we prefer to use seconds instead of minutes
+  );
+}
+
+
index d280564..7b2ef4f 100644 (file)
 #define acceleration_checksum 25326  
 #define max_jerk_checksum     61012 
 
+// TODO: Get from config
+#define MINIMUM_PLANNER_SPEED 0.0
 using namespace std;
 
+
+
+
+
 class Planner : public Module {
     public:
         Planner();
-        void append_block( int target[], double feed_rate, double distance, double speeds[] );
+        void append_block( int target[], double feed_rate, double distance, double deltas[] );
+        double max_allowable_speed( double acceleration, double target_velocity, double distance);
         void attach_gcode_to_queue(Gcode* gcode);
         void recalculate();
         void reverse_pass();
@@ -35,10 +42,12 @@ class Planner : public Module {
         void on_config_reload(void* argument);
 
         int position[3];              // Current position, in steps
-        RingBuffer<Block,128> queue;  // Queue of Blocks
+        double previous_unit_vec[3];
+        RingBuffer<Block,64> queue;  // Queue of Blocks
         bool computing;               // Whether or not we are currently computing the queue, TODO: Checks if this is necessary
         Block last_deleted_block;     // Item -1 in the queue, TODO: Grbl does not need this, but Smoothie won't work without it, we are probably doing something wrong
         bool has_deleted_block;       // Flag for above value
+        float previous_nominal_speed;
 
         double acceleration;          // Setting
         double max_jerk;              // Setting
index c3249fc..1f38c97 100644 (file)
@@ -1,5 +1,5 @@
 /*  
-      This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
+      This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl)
       Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
       Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
       You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. 
@@ -108,14 +108,14 @@ void Robot::append_milestone( double target[], double rate ){
     
     this->arm_solution->millimeters_to_steps( target, steps );
 
-    double millimeters_of_travel = sqrt( pow( target[X_AXIS]-this->last_milestone[X_AXIS], 2 ) +  pow( target[Y_AXIS]-this->last_milestone[Y_AXIS], 2 ) +  pow( target[Z_AXIS]-this->last_milestone[Z_AXIS], 2 ) );      
+    double deltas[3];
+    for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
+
+    double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) +  pow( deltas[Y_AXIS], 2 ) +  pow( deltas[Z_AXIS], 2 ) );      
     if( millimeters_of_travel < 0.001 ){ return; } 
     double duration = millimeters_of_travel / rate;
 
-    double speeds[3];
-    for(int axis=X_AXIS;axis<=Z_AXIS;axis++){speeds[axis]=(target[axis]-this->last_milestone[axis])/duration;}
-
-    this->kernel->planner->append_block( steps, rate, millimeters_of_travel, speeds ); 
+    this->kernel->planner->append_block( steps, rate*60, millimeters_of_travel, deltas ); 
 
     memcpy(this->last_milestone, target, sizeof(double)*3); // this->position[] = target[]; 
 
@@ -142,9 +142,99 @@ void Robot::append_line(double target[], double rate ){
     this->append_milestone(target, rate);
 }
 
-void Robot::append_arc(double theta_start, double angular_travel, double radius, double depth, double rate){
 
-    // The arc is approximated by generating a huge number of tiny, linear segments. The length of each 
+void Robot::append_arc( double target[], double offset[], double radius, bool is_clockwise ){
+
+    double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0];
+    double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1];
+    double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2];
+    double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
+    double r_axis1 = -offset[this->plane_axis_1];
+    double rt_axis0 = target[this->plane_axis_0] - center_axis0;
+    double rt_axis1 = target[this->plane_axis_1] - center_axis1;
+
+    // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+    double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
+    if (angular_travel < 0) { angular_travel += 2*M_PI; }
+    if (is_clockwise) { angular_travel -= 2*M_PI; }
+
+    double millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel));
+    if (millimeters_of_travel == 0.0) { return; }
+    uint16_t segments = floor(millimeters_of_travel/this->mm_per_arc_segment);
+
+    double theta_per_segment = angular_travel/segments;
+    double linear_per_segment = linear_travel/segments;
+
+    /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
+    and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
+    r_T = [cos(phi) -sin(phi);
+    sin(phi) cos(phi] * r ;
+    For arc generation, the center of the circle is the axis of rotation and the radius vector is
+    defined from the circle center to the initial position. Each line segment is formed by successive
+    vector rotations. This requires only two cos() and sin() computations to form the rotation
+    matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
+    all double numbers are single precision on the Arduino. (True double precision will not have
+    round off issues for CNC applications.) Single precision error can accumulate to be greater than
+    tool precision in some cases. Therefore, arc path correction is implemented.
+
+    Small angle approximation may be used to reduce computation overhead further. This approximation
+    holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
+    theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
+    to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
+    numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
+    issue for CNC machines with the single precision Arduino calculations.
+    This approximation also allows mc_arc to immediately insert a line segment into the planner
+    without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
+    a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
+    This is important when there are successive arc motions.
+    */
+    // Vector rotation matrix values
+    double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
+    double sin_T = theta_per_segment;
+
+    double arc_target[3];
+    double sin_Ti;
+    double cos_Ti;
+    double r_axisi;
+    uint16_t i;
+    int8_t count = 0;
+
+    // Initialize the linear axis
+    arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2];
+
+    for (i = 1; i<segments; i++) { // Increment (segments-1)
+
+        if (count < 25 ) { // TODO: Get from config
+          // Apply vector rotation matrix
+          r_axisi = r_axis0*sin_T + r_axis1*cos_T;
+          r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
+          r_axis1 = r_axisi;
+          count++;
+        } else {
+          // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
+          // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
+          cos_Ti = cos(i*theta_per_segment);
+          sin_Ti = sin(i*theta_per_segment);
+          r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
+          r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
+          count = 0;
+        }
+
+        // Update arc_target location
+        arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
+        arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
+        arc_target[this->plane_axis_2] += linear_per_segment;
+        this->append_milestone(arc_target, this->feed_rate);
+
+    }
+    // Ensure last segment arrives at target location.
+    this->append_milestone(target, this->feed_rate);
+}
+
+/*
+void robot::append_arc_old(double theta_start, double angular_travel, double radius, double depth, double rate){
+
+    // the arc is approximated by generating a huge number of tiny, linear segments. the length of each 
     // segment is configured in settings.mm_per_arc_segment.  
     double millimeters_of_travel = hypot(angular_travel*radius, labs(depth));
     if (millimeters_of_travel == 0.0) { return; }
@@ -170,10 +260,25 @@ void Robot::append_arc(double theta_start, double angular_travel, double radius,
     }
 
 }
-
+*/
 
 void Robot::compute_arc(double offset[], double target[]){
-    /*
+
+    // Find the radius
+    double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]);
+
+    // Set clockwise/counter-clockwise sign for mc_arc computations
+    bool is_clockwise = false;
+    if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; } 
+
+    // Append arc
+    this->append_arc( target, offset,  radius, is_clockwise );
+
+}
+
+
+/*
+void Robot::compute_arc_old(double offset[], double target[]){
      This segment sets up an clockwise or counterclockwise arc from the current position to the target position around 
      the center designated by the offset vector. All theta-values measured in radians of deviance from the positive 
      y-axis. 
@@ -186,7 +291,6 @@ void Robot::compute_arc(double offset[], double target[]){
                   *   /                                                     
                     C   <- theta_start (e.g. -145 degrees: theta_start == -PI*(3/4))
 
-    */
         
     // calculate the theta (angle) of the current point
     double theta_start = theta(-offset[this->plane_axis_0], -offset[this->plane_axis_1]);
@@ -208,7 +312,7 @@ void Robot::compute_arc(double offset[], double target[]){
     // Finish off with a line to make sure we arrive exactly where we think we are
     this->append_milestone( target, this->feed_rate );
 }
-
+*/
 
 // Convert from inches to millimeters ( our internal storage unit ) if needed
 inline double Robot::to_millimeters( double value ){
index c43d83a..4fbbe1d 100644 (file)
@@ -54,7 +54,10 @@ class Robot : public Module {
         void execute_gcode(Gcode* gcode);
         void append_milestone( double target[], double feed_rate);
         void append_line( double target[], double feed_rate);
-        void append_arc(double theta_start, double angular_travel, double radius, double depth, double rate);
+        //void append_arc(double theta_start, double angular_travel, double radius, double depth, double rate);
+        void append_arc( double target[], double offset[], double radius, bool is_clockwise );
+
+
         void compute_arc(double offset[], double target[]);
         double to_millimeters(double value);
         double theta(double x, double y);
index 5444839..af21518 100644 (file)
@@ -19,6 +19,7 @@ Stepper* stepper;
 
 Stepper::Stepper(){
     this->current_block = NULL;
+    this->step_events_completed = 0; 
     this->divider = 0;
 }
 
@@ -70,10 +71,12 @@ void Stepper::on_config_reload(void* argument){
 extern "C" void TIMER0_IRQHandler (void){
     if((LPC_TIM0->IR >> 1) & 1){
         LPC_TIM0->IR |= 1 << 1;
+        dd(1);
         stepper->step_gpio_port->FIOCLR = stepper->step_mask; 
     }
     if((LPC_TIM0->IR >> 0) & 1){
         LPC_TIM0->IR |= 1 << 0;
+        dd(2);
         stepper->main_interrupt();
     }
 }
@@ -102,6 +105,7 @@ void Stepper::main_interrupt(){
             for( int stpr=ALPHA_STEPPER; stpr<=GAMMA_STEPPER; stpr++){ this->counters[stpr] = 0; this->stepped[stpr] = 0; } 
             this->step_events_completed = 0; 
             this->kernel->call_event(ON_BLOCK_BEGIN, this->current_block);
+            //this->kernel->planner->dump_queue();
         }else{
             // Go to sleep
             LPC_TIM0->MR0 = 10000;  
@@ -109,8 +113,7 @@ void Stepper::main_interrupt(){
             LPC_TIM0->TCR = 0;
         }
     }
-   
-   
+    
     if( this->current_block != NULL ){
         // Set bits for direction and steps 
         this->out_bits = this->current_block->direction_bits;
@@ -138,6 +141,7 @@ void Stepper::main_interrupt(){
     }else{
         this->out_bits = 0;
     }
+
 }
 
 void Stepper::update_offsets(){
@@ -151,17 +155,18 @@ void Stepper::update_offsets(){
 // interrupt. It can be assumed that the trapezoid-generator-parameters and the
 // current_block stays untouched by outside handlers for the duration of this function call.
 void Stepper::trapezoid_generator_tick() {
+    //if( this->trapezoid_generator_busy || this->kernel->planner->computing ){ return; }
+    //this->trapezoid_generator_busy = true;
 
-    if( this->trapezoid_generator_busy || this->kernel->planner->computing ){ return; }
-    this->trapezoid_generator_busy = true;
     if(this->current_block) {
-      if (this->step_events_completed < this->current_block->accelerate_until) {
+      //this->kernel->serial->printf("#%d %u %u\r\n", this->step_events_completed, this->current_block->accelerate_until, this->current_block->accelerate_until<<16); 
+      if(this->step_events_completed < this->current_block->accelerate_until<<16) {
           this->trapezoid_adjusted_rate += this->current_block->rate_delta;
           if (this->trapezoid_adjusted_rate > this->current_block->nominal_rate ) {
               this->trapezoid_adjusted_rate = this->current_block->nominal_rate;
           }
           this->set_step_events_per_minute(this->trapezoid_adjusted_rate);
-      } else if (this->step_events_completed > this->current_block->decelerate_after) {
+      } else if (this->step_events_completed > this->current_block->decelerate_after<<16) {
           // NOTE: We will only reduce speed if the result will be > 0. This catches small
           // rounding errors that might leave steps hanging after the last trapezoid tick.
           if (this->trapezoid_adjusted_rate > this->current_block->rate_delta) {
@@ -179,8 +184,8 @@ void Stepper::trapezoid_generator_tick() {
           }
       }
   }
-  this->trapezoid_generator_busy = false;
-  
+  //this->trapezoid_generator_busy = false;
 }
 
 
@@ -194,8 +199,8 @@ void Stepper::trapezoid_generator_reset(){
 }
 
 void Stepper::set_step_events_per_minute( double steps_per_minute ){
-    if( ! this->current_block || this->kernel->planner->computing ){ return; }
-   
+    //if( ! this->current_block || this->kernel->planner->computing ){ return; }
+
     // We do not step slower than this 
     if( steps_per_minute < this->minimum_steps_per_minute ){ steps_per_minute = this->minimum_steps_per_minute; } //TODO: Get from config
 
@@ -210,6 +215,8 @@ void Stepper::set_step_events_per_minute( double steps_per_minute ){
     while(1<<i+1 < speed_factor){ i++; } // Probably not optimal ... 
     this->divider = i; 
 
+    //this->kernel->serial->printf(">> %d %f %f\r\n", this->divider, speed_factor, steps_per_minute);
+
     // Set the Timer interval 
     LPC_TIM0->MR0 = floor( ( SystemCoreClock/4 ) / ( (steps_per_minute/60L) * (1<<this->divider) ) );