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.
- 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
|