gin66 / FastAccelStepper

A high speed stepper library for Atmega 168/328p (nano), Atmega32u4, Atmega 2560, ESP32, ESP32S2, ESP32S3, ESP32C3, ESP32C6 and Atmel SAM Due
MIT License
312 stars 71 forks source link

Concept of low level access #86

Open theelims opened 3 years ago

theelims commented 3 years ago

Hi Gin66,

first of all, thank you very much for the fantastic FastAccelStepper lib. And that you finally convinced me to use the ESP32 chip. I'm using your lib with great success with the high level interface.

However, for my next development phase I want to implement something like a G-Code interpreter. In essence I need to chain commands together without the stepper coming to a halt in between commands. However, it is very likely that some ramps are still needed to glue the segments together. My input segments will be something like distance & time, or distance & speed. I want it to be piece-wise linear. Meaning, that it accelerates / decelerates as quick as possible and then travels the remaining distance with a constant speed. If I understood correctly that is what the low level interface is there for. Or can this be achieved as well with the high level interface? Polling for isRunning() is probably not working for me.

I tried to wrap my head around the low level examples, but frankly speaking, I'm completely lost in-between ticks and steps. Could you please elaborate what the concept under the hood is? Especially, what the parameters of the struct stepper_command_s cmd = {.ticks = curr_ticks, .steps = steps, .count_up = direction}; exactly mean?

Thank you very much, elims

gin66 commented 3 years ago

The high level interface is for running a motor from position A to position B with a speed bound to parameters for maximum speed and acceleration. Additional feature is, that the position B, maximum speed and acceleration can be adjusted, while the motor is running towards B. The issue is, that position B will always be reached with speed ~0 and while the stepper is running, the exact relation of position, speed, acceleration is not known to the application. Consequently a g-code interpreter (and especially for more than one axes) with the high level interface will be very difficult. (https://github.com/gin66/FastAccelStepper#usage-for-multi-axis-applications)

The low level interface gives way more control and - as long as interrupts can be serviced in time - will be timer exact. Just that the ramp generation of FastAccelStepper will not be used at all.

For this struct stepper_command_s cmd = {.ticks = curr_ticks, .steps = steps, .count_up = direction};. The definition is in common.h:

//  ticks is multiplied by (1/TICKS_PER_S) in s
//  If steps is 0, then a pause is generated
struct stepper_command_s {
  uint16_t ticks;
  uint8_t steps;
  bool count_up;
};

For esp32 TICKS_PER_S = 16_000_000 aka 16MHz.

One command is either a command to issue a defined amount of steps, or a pause (steps = 0). For example: ticks=16000, steps = 3, count_up = true means

If a step is needed every 10ms, then ticks should be 160_000. But ticks is an uint16_t, that value cannot be represented. In order to generate 3 steps at 10ms, the following commands could be generated:

Or alternatively this would work, too:

Hope this helps to understand the principle.

 // stepper queue management (low level access)
  //
  // If the queue is already running, then the start parameter is obsolote.
  // But the queue may run out of commands while executing addQueueEntry,
  // so it is better to set start=true to automatically restart/continue
  // a running queue.
  //
  // If the queue is not running, then the start parameter defines starting it
  // or not. The latter case is of interest to first fill the queue and then
  // start it.
  //
  // The call addQueueEntry(NULL, true) just starts the queue. This is intended
  // to achieve a near synchronous start of several steppers. Consequently it
  // should be called with interrupts disabled and return very fast.
  // Actually this is necessary, too, in case the queue is full and not
  // started.
  int8_t addQueueEntry(const struct stepper_command_s* cmd, bool start = true);

The application should then repeatedly call addQueueEntry() until its return value is >0.

It's obvious, that this will be a lot of work. As pointed out in the Readme. Perhaps you can have a look at the marlin project.

theelims commented 3 years ago

Thank you for that explanation. It has become a lot clearer. So ticks represents the pause between pulses and steps how many pulses shall be generated with that particular spacing.

Is there a particular reason to limit the ticks to uint16 on an ESP32? For the AVR it makes sense for a 16bit timer.

You mentioned the limitation on serving interrupts. It's there something I should be careful off? I'm no expert in programming.

I'll do some tests later this evening. I'm quite sure that will pop up a lot more questions as I progress.

gin66 commented 3 years ago

Several reasons for 16bit:

As stated in the readme: do not block for long time the interrupts. And for esp32 in addition: do not write to the flash, while the stepper is running. For example, try to let the stepper run continuously and initiate an ota update. The system will work, but the stepper will run quite bumpy…..

gin66 commented 9 months ago

should add to documentation