Current section
Files
Jump to
Current section
Files
lib/pwm.ex
defmodule Exred.Node.Picar.PWM do
@moduledoc """
functions to interact with a pwm card over I2C
"""
require Logger
use Bitwise
use GenServer
alias ElixirALE.I2C
@mode1 0x00 # bit 4 is SLEEP 000X0000
@mode2 0x01
@prescale 0xFE
@all_on_l 0xFA
@all_on_h 0xFB
@all_off_l 0xFC
@all_off_h 0xFD
@led0_on_l 0x06
@led0_on_h 0x07
@led0_off_l 0x08
@led0_off_h 0x09
@allcall 0x01
@outdrv 0x04
@swrst 0x06
# These seem to be some of the 16 channels on the PCA9685
# I think the diagrams in the tutorial show how the PCA9685 is connected to the TB6612's on the board
# https://learn.adafruit.com/adafruit-dc-and-stepper-motor-hat-for-raspberry-pi?view=all
# @pwma 8
# @ain2 9
# @ain1 10
# @pwmb 13
# @bin2 12
# @bin1 11
# API
# #####################
def start_link([]) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def set(channel, on, off) do
GenServer.call(__MODULE__, {:set, channel, on, off})
end
def set_all(on, off) do
GenServer.call(__MODULE__, {:set_all, on, off})
end
def reset do
GenServer.call(__MODULE__, :reset)
end
# Callbacks
# ####################
@impl True
def init([]) do
Logger.debug "Initializing..."
set_all_pwm(0,0)
I2C.write(:i2c, <<@mode2, @outdrv>>) # external driver, see docs
I2C.write(:i2c, <<@mode1, @allcall>>) # program all PCA9685's at once
:timer.sleep 5
{:ok, %{}}
end
@impl True
def handle_call({:set, channel, on, off}, _from, state) do
res = set_pwm(channel, on, off)
{:reply, res, state}
end
def handle_call({:set_all, on, off}, _from, state) do
res = set_all_pwm(on, off)
{:reply, res, state}
end
def handle_call(:reset, _from, state) do
swrst()
{:stop, :pwm_reset, :pwm_reset_requested, state}
end
# Implementation
# ####################
def prescale(freq) do
Logger.debug "Setting prescale to #{freq} Hz"
# pg 14 and solve for prescale or example on pg 25
prescaleval = trunc(Float.round( 25000000.0 / 4096.0 / freq ) - 1 )
Logger.debug "prescale value is #{prescaleval}"
oldmode = I2C.write_read(:i2c, <<@mode1>>, 1)
:timer.sleep 5
I2C.write(:i2c, <<@mode1, 0x11>>) # set bit 4 (sleep) to allow setting prescale
I2C.write(:i2c, <<@prescale, prescaleval>> )
I2C.write(:i2c, <<@mode1, 0x01>> ) #un-set sleep bit
:timer.sleep 5 # pg 14 it takes 500 us for the oscillator to be ready
I2C.write(:i2c, <<@mode1>> <> oldmode ) # put back old mode
end
# The registers for each of the 16 channels are sequential
# so the address can be calculated as an offset from the first one
def set_pwm(channel, on, off) do
I2C.write(:i2c, <<@led0_on_l+4*channel, on &&& 0xFF>>)
I2C.write(:i2c, <<@led0_on_h+4*channel, on >>> 8>>)
I2C.write(:i2c, <<@led0_off_l+4*channel, off &&& 0xFF>>)
I2C.write(:i2c, <<@led0_off_h+4*channel, off >>> 8>>)
end
# The PCA9685 has special registers for setting ALL channels
# (or 1/3 of them) to the same value.
def set_all_pwm(on, off) do
I2C.write(:i2c, <<@all_on_l, on &&& 0xFF>>)
I2C.write(:i2c, <<@all_on_h, on >>> 8>>)
I2C.write(:i2c, <<@all_off_l, off &&& 0xFF>>)
I2C.write(:i2c, <<@all_off_h, off >>> 8>>)
end
# PCA9685 Software Reset (Section 7.6 on pg 28)
def swrst do
Logger.debug "PCA9685 Software Reset..."
{:ok,pid}=I2C.start_link("i2c-1", 0x00)
:timer.sleep 10
I2C.write(pid,<<@swrst>>)
end
end