pi_piper 1.3.2 → 1.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require 'pi_piper'
2
+
3
+ puts "Press the switch to get started"
4
+
5
+ PiPiper.watch :pin => 17, :invert => true do |pin|
6
+ puts "Pin changed from #{pin.last_value} to #{pin.value}"
7
+ end
8
+
9
+ PiPiper.wait
10
+
@@ -1,8 +1,5 @@
1
- Dir[File.dirname(__FILE__) + '/pi_piper/*.rb'].each {|file| require file }
2
- at_exit do
3
- PiPiper::Bcm2835.close
4
- end
5
- PiPiper::Bcm2835.init
1
+ require 'eventmachine'
2
+ Dir[File.dirname(__FILE__) + '/pi_piper/*.rb'].each {|file| require file unless file.end_with?('bcm2835.rb') }
6
3
 
7
4
  module PiPiper
8
5
  extend self
@@ -38,8 +35,44 @@ module PiPiper
38
35
  options[:trigger] = options.delete(:goes) == :high ? :rising : :falling
39
36
  watch options, &block
40
37
  end
38
+
39
+ #Defines an event block to be called on a regular schedule. The block will be passed the slave output.
40
+ #
41
+ # @param [Hash] options A hash of options.
42
+ # @option options [Fixnum] :every A frequency of time (in seconds) to poll the SPI slave.
43
+ # @option options [Fixnum] :slave The slave number to poll.
44
+ # @option options [Number|Array] :write Data to poll the SPI slave with.
45
+ def poll_spi(options, &block)
46
+ EM::PeriodicTimer.new(options[:every]) do
47
+ Spi.begin options[:slave] do
48
+ output = write options[:write]
49
+ block.call output
50
+ end
51
+ end
52
+ end
53
+
54
+ #Defines an event block to be called when SPI slave output meets certain characteristics.
55
+ #The block will be passed the slave output.
56
+ #
57
+ # @param [Hash] options A hash of options.
58
+ # @option options [Fixnum] :every A frequency of time (in seconds) to poll the SPI slave.
59
+ # @option options [Fixnum] :slave The slave number to poll.
60
+ # @option options [Number|Array] :write Data to poll the SPI slave with.
61
+ # @option options [Fixnum] :eq Tests for SPI slave output equality.
62
+ # @option options [Fixnum] :lt Tests for SPI slave output less than supplied value.
63
+ # @option options [Fixnum] :gt Tests for SPI slave output greater than supplied value.
64
+ def when_spi(options, &block)
65
+ poll_spi options do |value|
66
+ if (options[:eq] && value == options[:eq]) ||
67
+ (options[:lt] && value < options[:lt]) ||
68
+ (options[:gt] && value > options[:gt])
69
+ block.call value
70
+ end
71
+ end
72
+ end
41
73
 
42
74
  #Prevents the main thread from exiting. Required when using PiPiper.watch
75
+ # @deprecated Please use EventMachine.run instead
43
76
  def wait
44
77
  loop do sleep 1 end
45
78
  end
@@ -2,19 +2,73 @@ require 'ffi'
2
2
 
3
3
  module PiPiper
4
4
  # The Bcm2835 module is not intended to be directly called.
5
- # It serves as an FFI library for PiPiper::SPI.
5
+ # It serves as an FFI library for PiPiper::SPI & PiPiper::I2C.
6
6
  module Bcm2835
7
7
  extend FFI::Library
8
- ffi_lib File.dirname(__FILE__) + '/libbcm2835.img'
8
+ ffi_lib File.dirname(__FILE__) + '/libbcm2835.so'
9
+ @pins = []
9
10
 
10
11
  SPI_MODE0 = 0
11
12
  SPI_MODE1 = 1
12
13
  SPI_MODE2 = 2
13
14
  SPI_MODE3 = 3
14
15
 
16
+ I2C_REASON_OK = 0 #Success
17
+ I2C_REASON_ERROR_NACK = 1 #Received a NACK
18
+ I2C_REASON_ERROR_CLKT = 2 #Received Clock Stretch Timeout
19
+ I2C_REASON_ERROR_DATA = 3 #Not all data is sent / received
20
+
15
21
  attach_function :init, :bcm2835_init, [], :uint8
16
22
  attach_function :close, :bcm2835_close, [], :uint8
17
23
 
24
+ #pin support...
25
+ attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void
26
+
27
+ def self.pin_input(pin)
28
+ export(pin)
29
+ pin_direction(pin, 'in')
30
+ end
31
+
32
+ def self.pin_set(pin, value)
33
+ File.open("/sys/class/gpio/gpio#{pin}/value", 'w') {|f| f.write("#{value}") }
34
+ end
35
+
36
+ def self.pin_output(pin)
37
+ export(pin)
38
+ pin_direction(pin, 'out')
39
+ end
40
+
41
+ def self.pin_read(pin)
42
+ File.read("/sys/class/gpio/gpio#{pin}/value").to_i
43
+ end
44
+
45
+ def self.pin_direction(pin, direction)
46
+ File.open("/sys/class/gpio/gpio#{pin}/direction", 'w') do |f|
47
+ f.write(direction)
48
+ end
49
+ end
50
+
51
+ # Exports pin and subsequently locks it from outside access
52
+ def self.export(pin)
53
+ File.open('/sys/class/gpio/export', 'w') { |f| f.write("#{pin}") }
54
+ @pins << pin unless @pins.include?(pin)
55
+ end
56
+
57
+ def self.release_pin(pin)
58
+ File.open('/sys/class/gpio/unexport', 'w') { |f| f.write("#{pin}") }
59
+ @pins.delete(pin)
60
+ end
61
+
62
+ def self.release_pins
63
+ @pins.dup.each { |pin| release_pin(pin) }
64
+ end
65
+
66
+ #NOTE to use: chmod 666 /dev/spidev0.0
67
+ def self.spidev_out(array)
68
+ File.open('/dev/spidev0.0', 'wb'){|f| f.write(array.pack('C*')) }
69
+ end
70
+
71
+ #SPI support...
18
72
  attach_function :spi_begin, :bcm2835_spi_begin, [], :uint8
19
73
  attach_function :spi_end, :bcm2835_spi_end, [], :uint8
20
74
  attach_function :spi_transfer, :bcm2835_spi_transfer, [:uint8], :uint8
@@ -26,8 +80,23 @@ module PiPiper
26
80
  attach_function :spi_chip_select_polarity,
27
81
  :bcm2835_spi_setChipSelectPolarity, [:uint8, :uint8], :void
28
82
 
83
+ #I2C support...
84
+ attach_function :i2c_begin, :bcm2835_i2c_begin, [], :void
85
+ attach_function :i2c_end, :bcm2835_i2c_end, [], :void
86
+ attach_function :i2c_write, :bcm2835_i2c_write, [:pointer, :uint], :uint8
87
+ attach_function :i2c_set_address,:bcm2835_i2c_setSlaveAddress, [:uint8], :void
88
+ attach_function :i2c_set_clock_divider, :bcm2835_i2c_setClockDivider, [:uint16], :void
89
+ attach_function :i2c_read, :bcm2835_i2c_read, [:pointer, :uint], :uint8
90
+
91
+ def self.i2c_allowed_clocks
92
+ [100.kilohertz,
93
+ 399.3610.kilohertz,
94
+ 1.666.megahertz,
95
+ 1.689.megahertz]
96
+ end
97
+
29
98
  def self.spi_transfer_bytes(data)
30
- data_out = FFI::MemoryPointer.new(data.count)
99
+ data_out = FFI::MemoryPointer.new(data.count)
31
100
  data_in = FFI::MemoryPointer.new(data.count)
32
101
  (0..data.count-1).each { |i| data_out.put_uint8(i, data[i]) }
33
102
 
@@ -36,5 +105,18 @@ module PiPiper
36
105
  (0..data.count-1).map { |i| data_in.get_uint8(i) }
37
106
  end
38
107
 
108
+ def self.i2c_transfer_bytes(data)
109
+ data_out = FFI::MemoryPointer.new(data.count)
110
+ (0..data.count-1).each{ |i| data_out.put_uint8(i, data[i]) }
111
+
112
+ i2c_write data_out, data.count
113
+ end
114
+
115
+ def self.i2c_read_bytes(bytes)
116
+ data_in = FFI::MemoryPointer.new(bytes)
117
+ i2c_read(data_in, bytes) #TODO reason codes
118
+
119
+ (0..bytes-1).map { |i| data_in.get_uint8(i) }
120
+ end
39
121
  end
40
122
  end
@@ -0,0 +1,19 @@
1
+ module Frequency
2
+
3
+ def kilohertz
4
+ self * 1000
5
+ end
6
+
7
+ def megahertz
8
+ self * 1000000
9
+ end
10
+
11
+ end
12
+
13
+ class Float
14
+ include Frequency
15
+ end
16
+
17
+ class Integer
18
+ include Frequency
19
+ end
@@ -0,0 +1,51 @@
1
+ module PiPiper
2
+
3
+ #I2C support
4
+ class I2C
5
+
6
+ #http://www.airspayce.com/mikem/bcm2835/group__i2c.html
7
+
8
+ def initialize
9
+ Platform.driver.i2c_begin
10
+ end
11
+
12
+ def end
13
+ Platform.driver.i2c_end
14
+ end
15
+
16
+ def self.begin(&block)
17
+ instance = I2C.new
18
+ begin
19
+ if block.arity > 0
20
+ block.call instance
21
+ else
22
+ instance.instance_exec &block
23
+ end
24
+ ensure
25
+ instance.end
26
+ end
27
+ end
28
+
29
+ def self.clock=(clock)
30
+ valid_clocks = Platform.driver.i2c_allowed_clocks
31
+ raise "Invalid clock rate. Valid clocks are 100 kHz, 399.3610 kHz, 1.666 MHz and 1.689 MHz" unless valid_clocks.include? clock
32
+
33
+ Platform.driver.i2c_set_clock clock
34
+ end
35
+
36
+ def write(params)
37
+ data = params[:data]
38
+ address = params[:to]
39
+
40
+ raise ":data must be enumerable" unless data.class.include? Enumerable
41
+ Platform.driver.i2c_set_address address
42
+ Platform.driver.i2c_transfer_bytes data
43
+ end
44
+
45
+ def read(bytes)
46
+ Platform.driver.i2c_read_bytes(bytes)
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -1,70 +1,148 @@
1
+ require_relative 'pin_values'
2
+
1
3
  module PiPiper
2
4
  # Represents a GPIO pin on the Raspberry Pi
3
5
  class Pin
4
- attr_reader :pin, :last_value, :value, :direction, :invert
5
-
6
+ include PiPiper::PinValues
7
+
8
+ attr_reader :pin, :last_value, :direction, :invert
9
+
6
10
  #Initializes a new GPIO pin.
7
11
  #
8
12
  # @param [Hash] options A hash of options
9
13
  # @option options [Fixnum] :pin The pin number to initialize. Required.
10
- # @option options [Symbol] :direction The direction of communication, either :in or :out. Defaults to :in.
11
- # @option options [Boolean] :invert Indicates if the value read from the physical pin should be inverted. Defaults to false.
12
- # @option options [Symbol] :trigger Indicates when the wait_for_change method will detect a change, either :rising, :falling, or :both edge triggers. Defaults to :both.
14
+ #
15
+ # @option options [Symbol] :direction The direction of communication,
16
+ # either :in or :out. Defaults to :in.
17
+ #
18
+ # @option options [Boolean] :invert Indicates if the value read from the
19
+ # physical pin should be inverted. Defaults to false.
20
+ #
21
+ # @option options [Symbol] :trigger Indicates when the wait_for_change
22
+ # method will detect a change, either :rising, :falling, or :both edge
23
+ # triggers. Defaults to :both.
24
+ #
25
+ # @option options [Symbol] :pull Indicates if and how pull mode must be
26
+ # set when pin direction is set to :in. Either :up, :down or :offing.
27
+ # Defaults to :off.
28
+ #
13
29
  def initialize(options)
14
- options = {:direction => :in, :invert => false, :trigger => :both}.merge options
15
- @pin = options[:pin]
30
+ options = { :direction => :in,
31
+ :invert => false,
32
+ :trigger => :both,
33
+ :pull => :off}.merge(options)
34
+
35
+ @pin = options[:pin]
16
36
  @direction = options[:direction]
17
- @invert = options[:invert]
18
- @trigger = options[:trigger]
19
-
20
- raise "Invalid direction. Options are :in or :out" unless [:in, :out].include? @direction
21
- raise "Invalid trigger. Options are :rising, :falling, or :both" unless [:rising, :falling, :both].include? @trigger
22
-
23
- File.open("/sys/class/gpio/export", "w") { |f| f.write("#{@pin}") }
24
- File.open(direction_file, "w") { |f| f.write(@direction == :out ? "out" : "in") }
25
-
26
- read
27
- end
28
-
29
- # If the pin has been initialized for output this method will set the logic level high.
37
+ @invert = options[:invert]
38
+ @trigger = options[:trigger]
39
+ @pull = options[:pull]
40
+ @released = false
41
+
42
+ raise "Invalid pull mode. Options are :up, :down or :float (default)" unless
43
+ [:up, :down, :float, :off].include? @pull
44
+ raise "Unable to use pull-ups : pin direction must be ':in' for this" if
45
+ @direction != :in and [:up, :down].include?(@pull)
46
+ raise "Invalid direction. Options are :in or :out" unless
47
+ [:in, :out].include? @direction
48
+ raise "Invalid trigger. Options are :rising, :falling, or :both" unless
49
+ [:rising, :falling, :both].include? @trigger
50
+
51
+ if @direction == :out
52
+ Platform.driver.pin_output(@pin)
53
+ else
54
+ Platform.driver.pin_input(@pin)
55
+ end
56
+ pull!(@pull)
57
+
58
+ read
59
+ end
60
+
61
+ # If the pin has been initialized for output this method will set the
62
+ # logic level high.
30
63
  def on
31
- File.open(value_file, 'w') {|f| f.write("1") } if direction == :out
64
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
65
+ Platform.driver.pin_set(pin, GPIO_HIGH) if direction == :out
32
66
  end
33
-
67
+
34
68
  # Tests if the logic level is high.
35
69
  def on?
36
70
  not off?
37
71
  end
38
-
39
- # If the pin has been initialized for output this method will set the logic level low.
72
+
73
+ # If the pin has been initialized for output this method will set
74
+ # the logic level low.
40
75
  def off
41
- File.open(value_file, 'w') {|f| f.write("0") } if direction == :out
76
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
77
+ Platform.driver.pin_set(pin, GPIO_LOW) if direction == :out
42
78
  end
43
-
79
+
44
80
  # Tests if the logic level is low.
45
81
  def off?
46
- value == 0
82
+ value == GPIO_LOW
47
83
  end
48
84
 
49
- # If the pin has been initialized for output this method will either raise or lower the logic level depending on `new_value`.
85
+ def value
86
+ @value ||= read
87
+ end
88
+
89
+ # If the pin has been initialized for output this method will either raise
90
+ # or lower the logic level depending on `new_value`.
50
91
  # @param [Object] new_value If false or 0 the pin will be set to off, otherwise on.
51
92
  def update_value(new_value)
52
- !new_value || new_value == 0 ? off : on
93
+ !new_value || new_value == GPIO_LOW ? off : on
94
+ end
95
+ alias_method :value=, :update_value
96
+
97
+ # When the pin has been initialized in input mode, internal resistors can
98
+ # be pulled up or down (respectively with :up and :down).
99
+ #
100
+ # Pulling an input pin will prevent noise from triggering it when the input
101
+ # is floating.
102
+ #
103
+ # For instance when nothing is plugged in, pulling the pin-up will make
104
+ # subsequent value readings to return 'on' (or high, or 1...).
105
+ # @param [Symbol] state Indicates if and how pull mode must be set when
106
+ # pin direction is set to :in. Either :up, :down or :offing. Defaults to :off.
107
+ def pull!(state)
108
+ return nil if @direction != :in
109
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
110
+ @pull = case state
111
+ when :up then GPIO_PUD_UP
112
+ when :down then GPIO_PUD_DOWN
113
+ # :float and :off are just aliases
114
+ when :float, :off then GPIO_PUD_OFF
115
+ else nil
116
+ end
117
+
118
+ Platform.driver.pin_set_pud(@pin, @pull) if @pull
119
+ @pull
120
+ end
121
+
122
+ # If the pin direction is input, it will return the current state of
123
+ # pull-up/pull-down resistor, either :up, :down or :off.
124
+ def pull?
125
+ case @pull
126
+ when GPIO_PUD_UP then :up
127
+ when GPIO_PUD_DOWN then :down
128
+ else :off
129
+ end
53
130
  end
54
-
131
+
55
132
  # Tests if the logic level has changed since the pin was last read.
56
133
  def changed?
57
134
  last_value != value
58
135
  end
59
136
 
60
- # blocks until a logic level change occurs. The initializer option `:trigger` modifies what edge this method will release on.
137
+ # Blocks until a logic level change occurs. The initializer option
138
+ # `:trigger` modifies what edge this method will release on.
61
139
  def wait_for_change
62
140
  fd = File.open(value_file, "r")
63
141
  File.open(edge_file, "w") { |f| f.write("both") }
64
142
  loop do
65
143
  fd.read
66
144
  IO.select(nil, nil, [fd], nil)
67
- read
145
+ read
68
146
  if changed?
69
147
  next if @trigger == :rising and value == 0
70
148
  next if @trigger == :falling and value == 1
@@ -73,15 +151,29 @@ module PiPiper
73
151
  end
74
152
  end
75
153
 
76
- # Reads the current value from the pin. Without calling this method first, `value`, `last_value` and `changed?` will not be updated.
77
- # In short, you must call this method if you are curious about the current state of the pin.
78
- def read
154
+ # Reads the current value from the pin. Without calling this method
155
+ # first, `value`, `last_value` and `changed?` will not be updated.
156
+ #
157
+ # In short, you must call this method if you are curious about the
158
+ # current state of the pin.
159
+ def read
160
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
79
161
  @last_value = @value
80
- val = File.read(value_file).to_i
162
+ val = Platform.driver.pin_read(@pin)
81
163
  @value = invert ? (val ^ 1) : val
82
164
  end
83
-
165
+
166
+ def release
167
+ Platform.driver.release_pin(@pin)
168
+ @released = true
169
+ end
170
+
171
+ def released?
172
+ @released == true
173
+ end
174
+
84
175
  private
176
+
85
177
  def value_file
86
178
  "/sys/class/gpio/gpio#{pin}/value"
87
179
  end
@@ -93,6 +185,5 @@ module PiPiper
93
185
  def direction_file
94
186
  "/sys/class/gpio/gpio#{pin}/direction"
95
187
  end
96
-
97
188
  end
98
189
  end