gin66 / FastAccelStepper

A high speed stepper library for Atmega 168/328p (nano), Atmega32u4, Atmega 2560, ESP32, ESP32S2, ESP32S3, ESP32C3 and Atmel SAM Due

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Concept of low level access

theelims opened this issue · comments

commented

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

commented

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

  • direction pin is set to the value for counting up (so HIGH or LOW depending on the dir pin setting)
  • one step is generated
  • exactly 1ms after the first step, the second step is issued
  • exactly 1ms after the second step, the third step is issued
  • wait 1ms
  • done with the command

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:

  • ticks=40000, steps = 1, count_up = 1 => first step
  • ticks=40000, steps = 0, count_up = 1
  • ticks=40000, steps = 0, count_up = 1
  • ticks=40000, steps = 0, count_up = 1
  • ticks=40000, steps = 1, count_up = 1 => second step
  • ticks=40000, steps = 0, count_up = 1
  • ticks=40000, steps = 0, count_up = 1
  • ticks=40000, steps = 0, count_up = 1
  • ticks=1000, steps = 1, count_up = 1 => third step, no need to add long pause

Or alternatively this would work, too:

  • ticks=50000, steps = 1, count_up = 1 => first step
  • ticks=30000, steps = 0, count_up = 1
  • ticks=30000, steps = 0, count_up = 1
  • ticks=50000, steps = 0, count_up = 1
  • ticks=20000, steps = 1, count_up = 1 => second step
  • ticks=40000, steps = 0, count_up = 1
  • ticks=50000, steps = 0, count_up = 1
  • ticks=50000, steps = 0, count_up = 1
  • ticks=10000, steps = 1, count_up = 1 => third step, we just add more pauses as an example
  • ticks=60000, steps = 0, count_up = 1
  • ticks=60000, steps = 0, count_up = 1
  • ticks=60000, steps = 0, count_up = 1
  • ticks=60000, steps = 0, count_up = 1
  • ticks=60000, steps = 0, count_up = 1
  • ticks=60000, steps = 0, count_up = 1

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.

commented

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.

commented

Several reasons for 16bit:

  • The registers of the mcpwm module from esp32 are all 16bit.
  • The code base between avr and esp32 have major portion of common code
  • At max after ~4ms the current command is finished and the queue length is only filled up for approx. 10ms in order to be able to react to position, speed, acceleration changes nearly instantly. Consequently more than 16bit would make the driver just slow. This is a lessons learned from past driver incarnations: there was kind of 24bit values in use by a trick, and the driver just felt slow

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…..