module Beaglebone::GPIO

GPIO

procedural methods for GPIO control

Summary

pin_mode is called to initialize a pin. Further basic functionality is available with digital_read and digital_write

Constants

EDGES

Edge trigger options

GPIOTEMPLATE

dts template for GPIO pin

MODES

GPIO modes

PULLMODES

pull modes

SLEWRATES

Slew rates

STATES

GPIO states

Public Class Methods

cleanup() click to toggle source

Resets all the GPIO pins that we have used and unexport them

# File lib/beaglebone/gpio.rb, line 277
def cleanup
  get_gpio_pins.each { |x| disable_gpio_pin(x) }
end
digital_read(pin) click to toggle source

Reads a pin’s input state and return that value

@param pin should be a symbol representing the header pin, i.e. :P9_11

@return [Symbol] :HIGH or :LOW

@example

GPIO.digital_read(:P9_11) => :HIGH
# File lib/beaglebone/gpio.rb, line 166
def digital_read(pin)
  check_gpio_enabled(pin)

  raise StandardError, "PIN not in GPIO IN mode: #{pin}" unless get_gpio_mode(pin) == :IN

  fd = get_value_fd(pin)
  fd.rewind
  value = fd.read.to_s.strip
  state = STATES.key(value.to_i)

  Beaglebone::set_pin_status(pin, :state, state)
end
digital_write(pin, state) click to toggle source

Sets a pin’s output state

@param pin should be a symbol representing the header pin @param state should be a symbol representin the state, :HIGH or :LOW

@example

GPIO.digital_write(:P9_12, :HIGH)
GPIO.digital_write(:P9_12, :LOW)
# File lib/beaglebone/gpio.rb, line 146
def digital_write(pin, state)
  check_valid_state(state)
  check_gpio_enabled(pin)

  raise StandardError, "PIN not in GPIO OUT mode: #{pin}" unless get_gpio_mode(pin) == :OUT

  fd = get_value_fd(pin)
  fd.write STATES[state.to_sym].to_s
  fd.flush
  Beaglebone::set_pin_status(pin, :state, state)
end
disable_gpio_pin(pin) click to toggle source

Disable a GPIO pin

@param pin should be a symbol representing the header pin

# File lib/beaglebone/gpio.rb, line 420
def disable_gpio_pin(pin)

  Beaglebone::check_valid_pin(pin, :gpio)

  pininfo = PINS[pin]

  close_value_fd(pin)

  #close any running threads
  stop_edge_wait(pin)

  #write to unexport to disable gpio
  begin
    File.open('/sys/class/gpio/unexport', 'w') { |f| f.write(pininfo[:gpio]) }
  rescue
    #
  end

  #unload device tree
  Beaglebone::device_tree_unload("#{TREES[:GPIO][:pin]}#{pin}_.*") unless pininfo[:led]

  #remove status from hash so following enabled? call checks actual system
  Beaglebone::delete_pin_status(pin)

  #check to see if pin is GPIO enabled in /sys/class/gpio/
  raise StandardError, "GPIO was unable to uninitalize pin: #{pin.to_s}" if enabled?(pin)

end
enabled?(pin) click to toggle source

Returns true if specified pin is enabled in GPIO mode, else false

# File lib/beaglebone/gpio.rb, line 282
def enabled?(pin)

  return true if Beaglebone::get_pin_status(pin, :type) == :gpio

  return false unless valid?(pin)
  if Dir.exists?(gpio_directory(pin))

    Beaglebone::set_pin_status(pin, :type, :gpio)
    return true
  end

  false
end
get_gpio_edge(pin) click to toggle source

Returns the GPIO edge trigger type on an initialized pin @return [Symbol] :NONE, :RISING, :FALLING, or :BOTH

# File lib/beaglebone/gpio.rb, line 398
def get_gpio_edge(pin)
  check_gpio_enabled(pin)

  edge = Beaglebone::get_pin_status(pin, :trigger)
  return edge if edge

  read_gpio_edge(pin)
end
get_gpio_mode(pin) click to toggle source

Returns mode from pin, reads mode if unknown @returns [Symbol] :IN or :OUT

# File lib/beaglebone/gpio.rb, line 339
def get_gpio_mode(pin)
  check_gpio_enabled(pin)

  mode = Beaglebone::get_pin_status(pin, :mode)
  return mode if mode

  read_gpio_direction(pin)
end
get_gpio_pins() click to toggle source

Return an array of GPIO pins in use

@return [Array<Symbol>]

@example

GPIO.get_gpio_pins => [:P9_12, :P9_13]
# File lib/beaglebone/gpio.rb, line 413
def get_gpio_pins
  Beaglebone.pinstatus.clone.select { |x,y| x if y[:type] == :gpio && !PINS[x][:led] }.keys
end
get_gpio_state(pin) click to toggle source

Returns last known state from pin, reads state if unknown @returns [Symbol] :HIGH or :LOW

# File lib/beaglebone/gpio.rb, line 328
def get_gpio_state(pin)
  check_gpio_enabled(pin)

  state = Beaglebone::get_pin_status(pin, :state)
  return state if state

  digital_read(pin)
end
pin_mode(pin, mode, pullmode = nil, slewrate = nil) click to toggle source

Initialize a GPIO pin

@param pin should be a symbol representing the header pin @param mode should specify the mode of the pin, either :IN or :OUT @param pullmode (optional) should specify the pull mode, :PULLUP, :PULLDOWN, or :NONE @param slewrate (optional) should specify the slew rate, :FAST or :SLOW @example

GPIO.pin_mode(:P9_12, :OUT)
GPIO.pin_mode(:P9_11, :IN, :PULLUP, :FAST)
# File lib/beaglebone/gpio.rb, line 70
def pin_mode(pin, mode, pullmode = nil, slewrate = nil)

  #make sure a valid mode was passed
  check_valid_mode(mode)

  #make sure a valid pin was passed and that it supports GPIO
  Beaglebone::check_valid_pin(pin, :gpio)

  #get info from PINS hash
  pininfo = PINS[pin]

  #if pin is enabled for something else, disable it
  if Beaglebone::get_pin_status(pin) && Beaglebone::get_pin_status(pin, :type) != :gpio
    Beaglebone::disable_pin(pin)
  end

  pullmode = pullmode || :PULLUP
  slewrate = slewrate || :FAST

  raise ArgumentError, "Invalid Slew Rate (#{slewrate}) specified on: #{pin}" unless SLEWRATES.include?(slewrate)
  raise ArgumentError, "Invalid Pull Mode (#{pullmode}) specified on: #{pin}" unless PULLMODES.include?(pullmode)

  if mode == :IN and pullmode != :PULLUP and ( pininfo[:mmc] or pin == :P9_15 )
    raise ArgumentError, "Invalid Pull mode specified for pin: #{pin} (#{pullmode})"
  end

  #export pin unless its an on board LED, if it isn't already exported
  if pininfo[:led]
    raise StandardError, "LEDs only support OUT mode: #{pin.to_s}" unless mode == :OUT
    File.open("#{gpio_directory(pin)}/trigger", 'w') { |f| f.write('gpio') }
  else
    # if pin is not an onboard LED

    # create device tree overlay for this pin, do not force rebuild
    pin_data = create_device_tree(pin, mode, pullmode, slewrate, false)

    # unload previous dtb if loaded
    begin
      Beaglebone::device_tree_unload("#{TREES[:GPIO][:pin]}#{pin}_.*")
    rescue
      #
    end

    # load device tree overlay
    Beaglebone::device_tree_load("#{TREES[:GPIO][:pin]}#{pin}_0x#{pin_data.to_s(16)}")

    # export gpio pin
    begin
      File.open('/sys/class/gpio/export', 'w') { |f| f.write pininfo[:gpio] }
    rescue
      #
    end

    #check to see if pin is GPIO enabled in /sys/class/gpio/
    raise StandardError, "GPIO was unable to initalize pin: #{pin.to_s}" unless enabled?(pin)

  end unless Beaglebone::get_pin_status(pin, :type) == :gpio

  #set pin mode
  unless pininfo[:led]
    set_gpio_mode(pin, mode)
    dir = read_gpio_direction(pin)
    raise StandardError, "GPIO was unable to set mode: #{pin.to_s} to #{mode.to_s} (#{dir})" if mode != dir
  end

  Beaglebone::set_pin_status(pin, :mode, mode)
end
run_on_edge(callback, pin, edge, timeout = nil, repeats=nil) click to toggle source

Runs a callback on an edge trigger event. This creates a new thread that runs in the background

@param callback A method to call when the edge trigger is detected. This method should take 3 arguments, the pin, the edge, and the counter @param pin should be a symbol representing the header pin, i.e. :P9_11 @param edge should be a symbol representing the trigger type, e.g. :RISING, :FALLING, :BOTH @param timeout is optional and specifies a time window to wait @param repeats is optional and specifies the number of times the callback will be run

@example

GPIO.run_on_edge(lambda { |pin,edge,count| puts "[#{count}] #{pin} -- #{edge}" }, :P9_11, :RISING)
# File lib/beaglebone/gpio.rb, line 190
def run_on_edge(callback, pin, edge, timeout = nil, repeats=nil)

  raise StandardError, "Already waiting for trigger on pin: #{pin}" if Beaglebone::get_pin_status(pin, :trigger)
  raise StandardError, "Already waiting for trigger on pin: #{pin}" if Beaglebone::get_pin_status(pin, :thread)

  thread = Thread.new(callback, pin, edge, timeout, repeats) do |c, p, e, t, r|
    begin
      count = 0
      loop do

        state = wait_for_edge(p, e, t, false)

        c.call(p, state, count) if c
        count += 1
        break if r && count >= r
      end
    rescue => ex
      puts ex
      puts ex.backtrace
    ensure
      cleanup_edge_trigger(p)
    end
  end

  Beaglebone::set_pin_status(pin, :thread, thread)
end
run_once_on_edge(callback, pin, edge, timeout = nil) click to toggle source

Runs a callback one time on an edge trigger event. This is a convenience method for run_on_edge @see run_on_edge

# File lib/beaglebone/gpio.rb, line 220
def run_once_on_edge(callback, pin, edge, timeout = nil)
  run_on_edge(callback, pin, edge, timeout, 1)
end
set_gpio_edge(pin, edge, force=nil) click to toggle source

Set GPIO edge trigger type on an initialized pin

@param pin should be a symbol representing the header pin @param edge should be a symbol representing the trigger type, e.g. :RISING, :FALLING, :BOTH @param force is optional, if set will set the mode even if already set

@example

GPIO.set_gpio_edge(:P9_11, :RISING)
# File lib/beaglebone/gpio.rb, line 373
def set_gpio_edge(pin, edge, force=nil)
  check_valid_edge(edge)
  Beaglebone::check_valid_pin(pin, :gpio)

  raise StandardError, "PIN not in GPIO IN mode: #{pin}" unless get_gpio_mode(pin) == :IN

  return if get_gpio_edge(pin) == edge && !force

  File.open("#{gpio_directory(pin)}/edge", 'w') { |f| f.write edge.to_s.downcase }
  testedge = read_gpio_edge(pin)
  if testedge != edge.to_s.downcase
    Beaglebone::delete_pin_status(pin, :trigger)
    raise StandardError, "GPIO was unable to set edge: #{pin.to_s} to #{edge.to_s}"
  end

  if edge.to_sym == :NONE
    Beaglebone::delete_pin_status(pin, :trigger)
  else
    Beaglebone::set_pin_status(pin, :trigger, edge.to_sym)
  end

end
set_gpio_mode(pin, mode) click to toggle source

Set GPIO mode on an initialized pin

@param pin should be a symbol representing the header pin @param mode should specify the mode of the pin, either :IN or :OUT

@example

GPIO.set_gpio_mode(:P9_12, :OUT)
GPIO.set_gpio_mode(:P9_11, :IN)
# File lib/beaglebone/gpio.rb, line 356
def set_gpio_mode(pin, mode)
  Beaglebone::check_valid_pin(pin, :gpio)
  check_valid_mode(mode)
  check_gpio_enabled(pin)

  File.open("#{gpio_directory(pin)}/direction", 'w') { |f| f.write mode.to_s.downcase }
  Beaglebone::set_pin_status(pin, :mode, mode)
end
shift_out(latch_pin, clock_pin, data_pin, data, lsb=nil) click to toggle source

Sends data to a shift register

@param latch_pin should be a symbol representing the header pin, i.e. :P9_12 @param clock_pin should be a symbol representing the header pin, i.e. :P9_13 @param data_pin should be a symbol representing the header pin, i.e. :P9_14 @param data Integer value to write to the shift register @param lsb optional, send least significant bit first if set

@example

GPIO.shift_out(:P9_11, :P9_12, :P9_13, 255)
# File lib/beaglebone/gpio.rb, line 306
def shift_out(latch_pin, clock_pin, data_pin, data, lsb=nil)
  raise ArgumentError, "data must be > 0 (#{data})" if data < 0
  digital_write(latch_pin, :LOW)

  binary = data.to_s(2)
  pad = 8 - ( binary.size % 8 )
  binary = ( '0' * pad ) + binary if pad.between?(1,7)

  binary.reverse! if lsb

  binary.each_char do |bit|
    digital_write(clock_pin, :LOW)
    digital_write(data_pin, bit == '0' ? :LOW : :HIGH)
    digital_write(clock_pin, :HIGH)
  end
  digital_write(latch_pin, :HIGH)

  data
end
stop_edge_wait(pin) click to toggle source

Stops any threads waiting for data on specified pin

@param pin should be a symbol representing the header pin, i.e. :P9_11

# File lib/beaglebone/gpio.rb, line 227
def stop_edge_wait(pin)
  thread = Beaglebone::get_pin_status(pin, :thread)

  thread.exit if thread
  thread.join if thread
end
wait_for_edge(pin, edge, timeout = nil, disable=true) click to toggle source

Wait for an edge trigger. Returns the type that triggered the event, e.g. :RISING, :FALLING, :BOTH

@returns [Symbol] :RISING, :FALLING, or :BOTH

@param pin should be a symbol representing the header pin, i.e. :P9_11 @param edge should be a symbol representing the trigger type, e.g. :RISING, :FALLING, :BOTH @param timeout is optional and specifies a time window to wait @param disable is optional. If set, edge trigger detection is cleared on return

@example

wait_for_edge(:P9_11, :RISING, 30) => :RISING
# File lib/beaglebone/gpio.rb, line 246
def wait_for_edge(pin, edge, timeout = nil, disable=true)
  check_valid_edge(edge)
  raise ArgumentError, "Cannot wait for edge trigger NONE: #{pin}" if edge.to_sym == :NONE

  check_gpio_enabled(pin)
  raise StandardError, "PIN not in GPIO IN mode: #{pin}" unless get_gpio_mode(pin) == :IN

  #ensure we're the only ones waiting for this trigger
  if Beaglebone::get_pin_status(pin, :thread) && Beaglebone::get_pin_status(pin, :thread) != Thread.current
    raise StandardError, "Already waiting for trigger on pin: #{pin}"
  end

  if Beaglebone::get_pin_status(pin, :trigger) && Beaglebone::get_pin_status(pin, :thread) != Thread.current
    raise StandardError, "Already waiting for trigger on pin: #{pin}"
  end

  set_gpio_edge(pin, edge)

  fd = get_value_fd(pin)
  fd.read

  #select will return fd into the error set "es" if it recieves an interrupt
  _, _, es = IO.select(nil, nil, [fd], timeout)

  set_gpio_edge(pin, :NONE) if disable

  es ? digital_read(pin) : nil

end

Private Class Methods

check_gpio_enabled(pin) click to toggle source

ensure gpio pin is enabled

# File lib/beaglebone/gpio.rb, line 539
def check_gpio_enabled(pin)
  Beaglebone::check_valid_pin(pin, :gpio)
  raise StandardError, "PIN not GPIO enabled: #{pin}" unless enabled?(pin)
end
check_valid_edge(edge) click to toggle source

ensure edge type is valid

# File lib/beaglebone/gpio.rb, line 452
def check_valid_edge(edge)
  raise ArgumentError, "No such edge: #{edge.to_s}" unless EDGES.include?(edge)
end
check_valid_mode(mode) click to toggle source

ensure mode is valid

# File lib/beaglebone/gpio.rb, line 532
def check_valid_mode(mode)
  #check to see if mode is valid
  mode = mode.to_sym
  raise ArgumentError, "No such mode: #{mode.to_s}" unless MODES.include?(mode)
end
check_valid_state(state) click to toggle source

ensure state is valid

# File lib/beaglebone/gpio.rb, line 525
def check_valid_state(state)
  #check to see if mode is valid
  state = state.to_sym
  raise ArgumentError, "No such state: #{state.to_s}" unless STATES.include?(state)
end
cleanup_edge_trigger(pin) click to toggle source

set edge trigger to none

# File lib/beaglebone/gpio.rb, line 472
def cleanup_edge_trigger(pin)
  if Beaglebone::get_pin_status(pin, :thread) == Thread.current
    set_gpio_edge(pin, :NONE)
    Beaglebone::delete_pin_status(pin, :thread)
  end
end
close_value_fd(pin) click to toggle source

close value fd if open

# File lib/beaglebone/gpio.rb, line 518
def close_value_fd(pin)
  fd = Beaglebone::get_pin_status(pin, :fd_value)
  fd.close if fd
  Beaglebone::delete_pin_status(pin, :fd_value)
end
create_device_tree(pin, mode, pullmode, slewrate, force = false) click to toggle source
# File lib/beaglebone/gpio.rb, line 544
def create_device_tree(pin, mode, pullmode, slewrate, force = false)
  #get info from PINS hash
  pininfo = PINS[pin]

  #generate data value for dts
  #Bit 15 PIN USAGE is an indicator and should be a 1 if the pin is used or 0 if it is unused.
  # Bits 14-7 RESERVED is not to be used and left as 0.
  # Bit 6 SLEW CONTROL 0=Fast 1=Slow
  # Bit 5 RX Enabled 0=Disabled 1=Enabled
  # Bit 4 PU/PD 0=Pulldown 1=Pullup.
  # Bit 3 PULLUP/DN 0=Pullup/pulldown enabled 1= Pullup/pulldown disabled
  # Bit 2-0 MUX MODE SELECT Mode 0-7. (refer to TRM)
  pin_data = 0
  pin_data |= 0b1000000 if slewrate == :SLOW
  pin_data |= 0b100000 if mode == :IN
  case pullmode
    when :PULLUP
      pin_data |= 0b10000
    when :NONE
      pin_data |= 0b1000
    else
      # default is pulldown enabled
  end
  #set mux mode, 7 is gpio
  pin_data |= 7

  dts = GPIOTEMPLATE.clone

  dts.gsub!('!PIN_KEY!', pin.to_s)
  dts.gsub!('!PIN_DOT_KEY!', pin.to_s.gsub('_', '.'))
  dts.gsub!('!PIN_FUNCTION!', pininfo[:gpiofunc])
  dts.gsub!('!PIN_OFFSET!', pininfo[:muxoffset])
  dts.gsub!('!DATA!', "0x#{pin_data.to_s(16)}")

  filename = "/lib/firmware/#{TREES[:GPIO][:pin]}#{pin}_0x#{pin_data.to_s(16)}-00A0"
  dts_fn = "#{filename}.dts"
  dtb_fn = "#{filename}.dtbo"

  # if we've already built this file, we don't need to do it again
  return pin_data if File.exists?(dtb_fn) && !force

  dts_file = File.open(dts_fn, 'w')
  dts_file.write(dts)
  dts_file.close

  system("dtc -O dtb -o #{dtb_fn} -b 0 -@ #{dts_fn}")

  pin_data
end
get_value_fd(pin) click to toggle source

return the open value fd, or open if needed

# File lib/beaglebone/gpio.rb, line 499
def get_value_fd(pin)
  check_gpio_enabled(pin)

  fd = Beaglebone::get_pin_status(pin, :fd_value)
  return fd if fd

  pininfo = PINS[pin]

  #leds aren't normal gpio pins, we can toggle them on and off however.
  if pininfo[:led]
    fd = File.open("#{gpio_directory(pin)}/brightness", 'w+')
  else
    fd = File.open("#{gpio_directory(pin)}/value", 'w+')
  end

  Beaglebone::set_pin_status(pin, :fd_value, fd)
end
gpio_directory(pin) click to toggle source

convenience method for getting gpio dir in /sys

# File lib/beaglebone/gpio.rb, line 480
def gpio_directory(pin)
  raise StandardError, 'Invalid Pin' unless valid?(pin)
  #led's are in a special place
  if PINS[pin][:led]
    "/sys/class/leds/beaglebone:green:#{pin.to_s.downcase}"
  else
    #normal gpio pins
    "/sys/class/gpio/gpio#{PINS[pin][:gpio]}"
  end
end
read_gpio_direction(pin) click to toggle source

read gpio direction file

# File lib/beaglebone/gpio.rb, line 492
def read_gpio_direction(pin)
  check_gpio_enabled(pin)

  Beaglebone::set_pin_status(pin, :mode, File.open("#{gpio_directory(pin)}/direction", 'r').read.to_s.strip.to_sym.upcase)
end
read_gpio_edge(pin) click to toggle source

read gpio edge file

# File lib/beaglebone/gpio.rb, line 457
def read_gpio_edge(pin)
  check_gpio_enabled(pin)
  File.open("#{gpio_directory(pin)}/edge", 'r').read.to_s.strip
end
valid?(pin) click to toggle source

check if pin is valid to use as gpio pin

# File lib/beaglebone/gpio.rb, line 463
def valid?(pin)
  #check to see if pin exists
  return false unless PINS[pin]
  return false unless PINS[pin][:gpio]

  true
end