Current section

Files

Jump to
exred_node_picar lib rear_wheels.ex
Raw

lib/rear_wheels.ex

defmodule Exred.Node.Picar.RearWheels do
require Logger
use GenServer
alias Exred.Node.Picar.PWM
alias ElixirALE.GPIO
@pwma 4 # pwm channel for motor a
@pwmb 5
@motor_a_dir_gpio 17 # gpio pin to control motor direction
@motor_b_dir_gpio 27
@forward 0
@backward 1
# API
#####################
def start_link(default) when is_list(default) do
GenServer.start_link(__MODULE__, default, name: __MODULE__)
end
def stop do
GenServer.call(__MODULE__, :stop)
end
def speed(speed) do
GenServer.call(__MODULE__, {:set_speed, speed})
end
# Callbacks
#####################
@impl true
def init(_args) do
Logger.debug "Starting..."
state = %{
gpio_pid_motor_a: None,
gpio_pid_motor_b: None,
freq: 60,
speed: 0,
target_speed: 0
}
PWM.prescale(state.freq)
PWM.set(@pwma, 0, 0)
PWM.set(@pwmb, 0, 0)
{:ok, gpio_pid_motor_a} = GPIO.start_link @motor_a_dir_gpio, :output
{:ok, gpio_pid_motor_b} = GPIO.start_link @motor_b_dir_gpio, :output
GPIO.write gpio_pid_motor_a, @forward
GPIO.write gpio_pid_motor_b, @forward
{:ok, %{state | gpio_pid_motor_a: gpio_pid_motor_a, gpio_pid_motor_b: gpio_pid_motor_b}, 200}
end
@impl true
def handle_info(:timeout, %{speed: speed, target_speed: speed} = state) do
{:noreply, state, 200}
end
def handle_info(:timeout, %{speed: speed, target_speed: target, gpio_pid_motor_a: gpio_a, gpio_pid_motor_b: gpio_b} = state) do
# calculate a possible new speed value
proposed = round(speed + (target-speed)/abs(target-speed) * 5)
# if proposed is close to the target then skip straight to the target
new_speed = if abs(target-proposed) < 5 do
target
else
proposed
end
# set new speed
PWM.set(@pwma, 0, pulse_width(new_speed))
PWM.set(@pwmb, 0, pulse_width(new_speed))
# change direction if speed value goes from positive to negative or vice versa
cond do
speed >= 0 and new_speed < 0 ->
GPIO.write gpio_a, @backward
GPIO.write gpio_b, @backward
speed <= 0 and new_speed > 0 ->
GPIO.write gpio_a, @forward
GPIO.write gpio_b, @forward
true ->
:pass
end
{:noreply, %{state | speed: new_speed}, 200}
end
@impl true
def handle_call(:stop, _from, state) do
PWM.set(@pwma, 0, 0)
PWM.set(@pwmb, 0, 0)
reply = {:ok, %{speed: 0}}
new_state = %{state | speed: 0, target_speed: 0}
{:reply, reply, new_state, 200}
end
# change the target speed
def handle_call({:set_speed, target_speed}, _from, state) do
Logger.debug "Speed target set to #{target_speed}"
{:reply, :ok, %{state | target_speed: target_speed}, 200}
end
# max pulse width on the PCA9685 PWM Driver is 4096
#
# absolute speed range is 0-100
# (this is just an arbitrary pick, speed range could be anything as long as
# we correctly map it to the 0-4096 pulse width range )
#
# we use the signum of the speed to indicate direction so to calculate
# pulse width we need the absolute of speed
defp pulse_width(speed) when speed>=-100 and speed<=100, do: abs(speed) * 40
end