gin66 / FastAccelStepper

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

Dir pin via shift register ? #132

Closed ahmadfarisfs closed 1 year ago

ahmadfarisfs commented 2 years ago

Hi, thx for the great library! My question is it safe to use multiplexed dir pin via shift register (in terms of timing and ISR servicing) ? I see that the timing critical part happen when dir pin need to be toggled by ISR (StepperISR_esp32_rmt.cpp:169 and StepperISR_esp32_mcpwm_pcnt.cpp:116). Is it safe to do SPI transaction (to use multiplexed direction pin) in those 2 sections ? I'm planning to do fork if you think that's safe to do. I don't see any external callback for the direction change (just like the enable pin did). I'm planning to use multiplexed direction pin to control 10 stepper using esp32.

Thank you

gin66 commented 2 years ago

For using SPI in interrupts in general: (https://github.com/espressif/esp-idf/issues/1428). Not sure, which multiplexing you have in mind….

The lib has an option to set a dir change delay: ˋ void setDirectionPin(uint8_t dirPin, bool dirHighCountsUp = true, uint16_t dir_change_delay_us = 0);ˋ. This means, a delay of up to 4ms to next step can be configured. So the timing seems to be relaxed, if it would not be managed in an ISR.

Possible route to go:

  1. Use a dir pin value >= 128 to flag an non gpio dir pin
  2. At every usage of dir pin (set direction pin or output value), check for MSB set. If set, do not execute the original action. Whenever direction pin value is changed, include your own handler.
  3. The kind of handler is up to you. Your handler must be very fast. You could try to trigger SPI transfer, signal a task or just set a variable, which another task polls e.g. once a ms.
  4. In your app configure appropriate dir delay, depending on your handler implementation.

So it can be done, but it is a bit complicated.

ahmadfarisfs commented 2 years ago

At first, i'm trying to use i/o multiplexer such as mcp23s17 or good old 74hc595 to multiplex the dir pin. Thanks for your insight, given the complexity, for now i'll just mux'd the enable pin instead.

gin66 commented 2 years ago

After some more thoughts, I think an esp32 only solution should be ok. The new idea is to introduce a new bit in the command queue entry called e.g. “wait_external_dir”. This should flag repetition of the current queue entry infinitely. In the regular steppertask it shall be checked, if the stepper has hit such a marked entry. If yes, then an external callback will be called (same as externalEnableCall). If the callback signals execution, then that bit will be cleared and the stepper will run it as a pause only and continue the movement.

Implementation detail: the mcpwm-driver performs a look ahead to next command (in order to prepare the pcnt-module for that next command’s step value). Consequently the addQueueEntry() has to add a pause following the change dir command. And in order to do that, before adding a direction change to the queue, it has to be ensured, that two commands can fit in the queue….

gin66 commented 2 years ago

The repo master branch contains an experimental solution for external direction pin. Not tested on hw. If you have time, you could give it a try.

ahmadfarisfs commented 2 years ago

Thanks for the changes! I will test it when I had the chance. And one more question: how do we set decelaration values different from accelereation ?

gin66 commented 2 years ago

Please feedback your results, when you have tested the new feature.

There is no support for different acceleration and deceleration values. The application can change acceleration values, while the stepper is running at any time.

gin66 commented 1 year ago

works?

astorun commented 1 year ago

Hi gin66,

Could you provide a simple example project under examples folder for how to use shift registers as pins to control multiple steppers with fastaccelstepper?

Do we need to use an extra library like https://github.com/Simsso/ShiftRegister74HC595 to define pins?

We are kinda lost and trying to figure out to solve the problem as simple as possible.

Our project requires to drive 3 tmc2209 drivers via single 74HC595 wired on esp32. All the ena/dir/step pins are wired to 74HC595, single ena pin is shared between all drivers.

Thanks.

gin66 commented 1 year ago

Hi,

as I do not have shift registers or similar, it is difficult to come up with an example, which works. If the shift register is best controlled with a library, then that’s a good way to go.

The FastAccelStepper.h contains an explanation, how to use this feature!

it:
  // ## External Pins
  //
  // If the direction/enable pins are e.g. connected via external HW (shift
  // registers), then an external callback function can be supplied. The
  // supplied value is either LOW or HIGH. The return value shall be the status
  // of the pin (either LOW or HIGH). If returned value and supplied value do
  // not match, the stepper does not continue, but calls this function again.
  //
  // This function is called from cyclic task/interrupt with 4ms rate, which
  // creates the commands to put into the command queue. Thus the supplied
  // function should take much less time than 4ms. Otherwise there is risk, that
  // other running steppers are running out of commands in the queue. If this
  // takes longer, then the function should be offloaded and return the new
  // status, after the pin change has been successfully completed.
  void setExternalCallForPin(bool (*func)(uint8_t pin, uint8_t value));

So a possible callback with your extra lib could be as simple as:

bool exampleCallback(uint8_t pin, uint8_t value) {
   sr.set(pin & ~PIN_EXTERNAL_FLAG, value);
   return value == 1;
}

with setup:

stepper->setEnablePin(0 | PIN_EXTERNAL_FLAG);
    stepper->setExternalCallForPin(exampleCallback);

I understand, that after return from sr.set, the pin has the chosen value, so just use value as current output is appropriate.

The PIN_EXTERNAL_FLAG is unfortunately not so well documented.

BTW: There is an asymmetry between value being uint8_t and the return value being bool. And in the lib both values are compared. Apparently the compiler is quite relaxed and it works only, if true equals HIGH , which should be the case.

Hope this helps.

astorun commented 1 year ago

Thanks a lot!

We are just confused on one step. How can we assign step(pulse) pin via shift register. Since in your description you mentioned direction/enable pins only if we are not mistaken?

astorun commented 1 year ago

Also we got an error when we use your code: stepper->setExternalCallForPin(exampleCallback);

Error: class "FastAccelStepper" has no member "setExternalCallForPin"C/C++(135)

gin66 commented 1 year ago

Step pin cannot be assigned to a shift register output.

For the compile error: I have made a mistake. The callback needs to be informed to the engine. So use instead engine->setExternalCallForPin(exampleCallback);

astorun commented 1 year ago

Oh then this library it is not useful to us since the CNC board that we are using has all the stepper driver control pins including step pin attached to a shift register. It is something that out of our control.

Is it possible that you may consider adding an additional option to control step pin via shift register as well? What we are looking for an easy way to control the drivers without writing or modifying a library since I am not an expert on programming.

gin66 commented 1 year ago

If the step pin is connected to a shift register, then this lib is not for you. Perhaps you could check for AccelStepper.