pi_piper 1.3.2 → 1.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +65 -0
- data/Manifest +5 -0
- data/README.md +54 -8
- data/Rakefile +24 -14
- data/examples/2_bit_counter/2_bit_counter.rb +22 -0
- data/examples/7-segment/7-segment.rb +37 -0
- data/examples/dsl_switch/dsl_switch.rb +15 -0
- data/examples/mcp3008/circuit.png +0 -0
- data/examples/mcp3008/mcp3008.rb +55 -0
- data/examples/mcp3008_spi/mcp3008_spi.rb +24 -0
- data/examples/morse_code/circuit.png +0 -0
- data/examples/morse_code/morse_code.rb +43 -0
- data/examples/simple_switch/circuit.png +0 -0
- data/examples/simple_switch/simple_switch.rb +10 -0
- data/lib/pi_piper.rb +38 -5
- data/lib/pi_piper/bcm2835.rb +85 -3
- data/lib/pi_piper/frequency.rb +19 -0
- data/lib/pi_piper/i2c.rb +51 -0
- data/lib/pi_piper/libbcm2835.so +0 -0
- data/lib/pi_piper/pin.rb +129 -38
- data/lib/pi_piper/pin_error.rb +3 -0
- data/lib/pi_piper/pin_values.rb +10 -0
- data/lib/pi_piper/platform.rb +25 -0
- data/lib/pi_piper/spi.rb +47 -43
- data/lib/pi_piper/stub_driver.rb +107 -0
- data/pi_piper.gemspec +18 -7
- data/spec/i2c_spec.rb +83 -0
- data/spec/pin_spec.rb +149 -0
- data/spec/pull_mode_spec.rb +70 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/spi_spec.rb +44 -0
- data/spec/stub_driver_spec.rb +140 -0
- metadata +107 -14
- data/lib/pi_piper/libbcm2835.img +0 -0
Binary file
|
data/lib/pi_piper.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
|
2
|
-
|
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
|
data/lib/pi_piper/bcm2835.rb
CHANGED
@@ -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.
|
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
|
data/lib/pi_piper/i2c.rb
ADDED
@@ -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
|
Binary file
|
data/lib/pi_piper/pin.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
11
|
-
# @option options [
|
12
|
-
#
|
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,
|
15
|
-
|
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
|
18
|
-
@trigger
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
72
|
+
|
73
|
+
# If the pin has been initialized for output this method will set
|
74
|
+
# the logic level low.
|
40
75
|
def off
|
41
|
-
|
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 ==
|
82
|
+
value == GPIO_LOW
|
47
83
|
end
|
48
84
|
|
49
|
-
|
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 ==
|
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
|
-
#
|
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
|
77
|
-
#
|
78
|
-
|
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 =
|
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
|