Last active
March 30, 2021 15:06
-
-
Save kieraneglin/ff8707c5db1aeeb5d28049d6ed29d61b to your computer and use it in GitHub Desktop.
Elixir module for interfacing with DRV8825 via Nerves
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_My_ portion of this code is licensed under the very permissive DWYW License (https://github.com/thornjad/DWYW). | |
If you are unable to recognize DWYW as a valid license, _my_ portion of this code is then licensed under MIT. | |
TL;DR: I don't care what you do with this, just don't get mad at me if a stepper breaks itself/something else when using this module. Liability = no. When I say "my code", I mean everything that isn't Circuits.GPIO. | |
The Circuits.GPIO library is licenced as such: https://github.com/elixir-circuits/circuits_gpio/blob/main/LICENSE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Motor.StepperDriver do | |
@moduledoc """ | |
This module interfaces directly with the DRV8825, including things | |
like GPIO initialization, raw stepping, mode changes, etc. This module | |
does not concern real-world implementation details such as gear ratios | |
""" | |
alias __MODULE__ | |
alias Circuits.GPIO | |
@enforce_keys [:dir, :step, :enable, :mode] | |
defstruct [:dir, :step, :enable, :mode] | |
@type t() :: %StepperDriver{} | |
# Order is important since the 0 or 1 written relates to the label's index in this list | |
# eg: `:reverse` = index 1 = write 1 (high) to GPIO | |
@directions [:forward, :reverse] | |
# This is more for code clarity than anything - I don't expect to be swapping these any time soon | |
@high 1 | |
@low 0 | |
@doc """ | |
Creates a new `StepperDriver` struct containing the GPIO references of main motor functions | |
""" | |
@spec new(reference(), reference(), reference(), [reference()]) :: StepperDriver.t() | |
def new(dir_gpio, step_gpio, enable_gpio, mode_gpios) do | |
%StepperDriver{ | |
dir: dir_gpio, | |
step: step_gpio, | |
enable: enable_gpio, | |
mode: mode_gpios | |
} | |
end | |
@doc """ | |
Given a map of pins, opens and sets all specified pins to a default. | |
Returns a struct containing the GPIO references to each pin. | |
Expects the input to be in the form of: | |
%{ | |
dir: integer(), | |
step: integer(), | |
enable: integer(), | |
mode: [integer()] | |
} | |
Where each integer is the number of the GPIO pin. | |
Sets the `enable` pin high by default to turn stepper "off". | |
With my specific motor hat (Waveshare) this was inverted so low was "off". Something to be aware of. | |
""" | |
@spec initialize(map()) :: StepperDriver.t() | |
def initialize(pins) do | |
{:ok, dir_gpio} = GPIO.open(pins.dir, :output, initial_value: @low) | |
{:ok, step_gpio} = GPIO.open(pins.step, :output, initial_value: @low) | |
{:ok, enable_gpio} = GPIO.open(pins.enable, :output, initial_value: @high) | |
mode_gpios = | |
Enum.map(pins.mode, fn mode_pin -> | |
{:ok, mode_gpio} = GPIO.open(mode_pin, :output, initial_value: @low) | |
mode_gpio | |
end) | |
StepperDriver.new(dir_gpio, step_gpio, enable_gpio, mode_gpios) | |
end | |
@doc """ | |
Rotates a given driver (ie: stepper) a given direction and number of steps. | |
The `step_delay` option is a pause between writing the step pin high and low (measured in ms) and is basically | |
how fast the motor rotates. 5 is a good baseline, but even 1 appears to be reasonable in my testing. | |
If steps are < 1 (eg: accidental negative instead of direction change), the motor is disabled | |
for paranoia's sake but is otherwise effectively a noop. | |
""" | |
@spec step(StepperDriver.t(), :forward | :reverse, integer(), keyword()) :: StepperDriver.t() | |
def step(driver, _direction, steps, opts \\ []) | |
def step(driver, _direction, steps, _opts) when steps < 1 do | |
stop(driver) | |
end | |
def step(driver, direction, steps, opts) when direction in @directions do | |
# Cheeky way to set pin value from index - see definition of @directions above | |
dir_pin_value = Enum.find_index(@directions, &(&1 == direction)) | |
delay = Keyword.get(opts, :step_delay, 5) | |
:ok = GPIO.write(driver.enable, @low) | |
:ok = GPIO.write(driver.dir, dir_pin_value) | |
Enum.each(1..steps, fn _ -> do_step(driver.step, delay) end) | |
# Stepping complete, shut it down | |
stop(driver) | |
end | |
@doc """ | |
Sets the enable pin low for a given driver, stopping it | |
""" | |
@spec stop(StepperDriver.t()) :: StepperDriver.t() | |
def stop(driver = %StepperDriver{enable: enable}) do | |
:ok = GPIO.write(enable, @high) | |
driver | |
end | |
@doc """ | |
Sets mode pins to specified values. Doesn't directly handle specific modes, | |
but according to documentation the microstepping modes are as follows: | |
Full step: 0, 0, 0 | |
Half step: 1, 0, 0 | |
1/4 step: 0, 1, 0 | |
1/8 step: 1, 1, 0 | |
1/16 step: 0, 0, 1 | |
1/32 step: 1, 0, 1 OR 0, 1, 1 OR 1, 1, 1 | |
""" | |
@spec set_mode(StepperDriver.t(), [integer()]) :: StepperDriver.t() | |
def set_mode(driver = %StepperDriver{mode: mode_pins}, mode_values) | |
when length(mode_pins) == length(mode_values) do | |
mode_pins | |
|> Enum.zip(mode_values) | |
|> Enum.each(fn {mode_pin, mode_value} -> | |
GPIO.write(mode_pin, mode_value) | |
end) | |
driver | |
end | |
defp do_step(step_gpio, delay) do | |
# Notice that the process sleeps after the write to low. | |
# Not a big deal, just something to be aware of. | |
:ok = GPIO.write(step_gpio, @high) | |
Process.sleep(delay) | |
:ok = GPIO.write(step_gpio, @low) | |
Process.sleep(delay) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
alias Motor.StepperDriver | |
motor_pins = %{ | |
dir: 13, | |
step: 19, | |
enable: 12, | |
mode: [16, 17, 20] | |
} | |
motor_pins | |
|> StepperDriver.initialize() | |
|> StepperDriver.set_mode([1, 0, 0]) | |
|> StepperDriver.step(:forward, 200) | |
|> StepperDriver.step(:reverse, 400, step_delay: 3) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment