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.
@@ -0,0 +1,3 @@
1
+ module PiPiper
2
+ class PinError < StandardError; end
3
+ end
@@ -0,0 +1,10 @@
1
+ module PiPiper
2
+ module PinValues
3
+ GPIO_PUD_OFF = 0
4
+ GPIO_PUD_DOWN = 1
5
+ GPIO_PUD_UP = 2
6
+
7
+ GPIO_HIGH = 1
8
+ GPIO_LOW = 0
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ module PiPiper
2
+
3
+ #Hardware abstraction manager. Not intended for direct use.
4
+ class Platform
5
+ @@driver = nil
6
+
7
+ #gets the current platform driver. Defaults to BCM2835.
8
+ def self.driver
9
+ unless @@driver
10
+ require 'pi_piper/bcm2835'
11
+ PiPiper::Bcm2835.init
12
+ @@driver = PiPiper::Bcm2835
13
+ at_exit do
14
+ Bcm2835.release_pins
15
+ Bcm2835.close
16
+ end
17
+ end
18
+ @@driver
19
+ end
20
+
21
+ def self.driver=(instance)
22
+ @@driver = instance
23
+ end
24
+ end
25
+ end
@@ -28,39 +28,9 @@
28
28
  #++
29
29
 
30
30
 
31
-
32
-
33
-
34
31
  module PiPiper
35
32
  # class for SPI interfaces on the Raspberry Pi
36
33
  class Spi
37
- # 65536 = 256us = 4kHz
38
- CLOCK_DIVIDER_65536 = 0
39
- # 32768 = 126us = 8kHz
40
- CLOCK_DIVIDER_32768 = 32768
41
- # 16384 = 64us = 15.625kHz
42
- CLOCK_DIVIDER_16384 = 16384
43
- # 8192 = 32us = 31.25kHz
44
- CLOCK_DIVIDER_8192 = 8192
45
- # 4096 = 16us = 62.5kHz
46
- CLOCK_DIVIDER_4096 = 4096
47
- # 2048 = 8us = 125kHz
48
- CLOCK_DIVIDER_2048 = 2048
49
- # 1024 = 4us = 250kHz
50
- CLOCK_DIVIDER_1024 = 1024
51
- # 512 = 2us = 500kHz
52
- CLOCK_DIVIDER_512 = 512
53
- # 256 = 1us = 1MHz
54
- CLOCK_DIVIDER_256 = 256
55
- # 128 = 500ns = = 2MHz
56
- CLOCK_DIVIDER_128 = 128
57
- # 64 = 250ns = 4MHz
58
- CLOCK_DIVIDER_64 = 64
59
- # 32 = 125ns = 8MHz
60
- CLOCK_DIVIDER_32 = 32
61
- # 16 = 50ns = 20MHz
62
- CLOCK_DIVIDER_16 = 16
63
-
64
34
  # Least signifigant bit first, e.g. 4 = 0b001
65
35
  LSBFIRST = 0
66
36
  # Most signifigant bit first, e.g. 4 = 0b100
@@ -75,18 +45,24 @@ module PiPiper
75
45
  # No CS, control it yourself
76
46
  CHIP_SELECT_NONE = 3
77
47
 
48
+ # SPI Modes
49
+ SPI_MODE0 = 0
50
+ SPI_MODE1 = 1
51
+ SPI_MODE2 = 2
52
+ SPI_MODE3 = 3
53
+
78
54
  #Sets the SPI mode. Defaults to mode (0,0).
79
55
  def self.set_mode(cpol, cpha)
80
56
  mode = SPI_MODE0 #default
81
57
  mode = SPI_MODE1 if cpol == 0 and cpha == 1
82
58
  mode = SPI_MODE2 if cpol == 1 and cpha == 0
83
59
  mode = SPI_MODE3 if cpol == 1 and cpha == 1
84
- Bcma2835.spi_set_data_mode mode
60
+ Platform.driver.spi_set_data_mode mode
85
61
  end
86
62
 
87
63
  #Begin an SPI block. All SPI communications should be wrapped in a block.
88
64
  def self.begin(chip=nil, &block)
89
- Bcm2835.spi_begin
65
+ Platform.driver.spi_begin
90
66
  chip = CHIP_SELECT_0 if !chip && block_given?
91
67
  spi = new(chip)
92
68
 
@@ -99,11 +75,39 @@ module PiPiper
99
75
 
100
76
  # Not needed when #begin is called with a block
101
77
  def self.end
102
- Bcm2835.spi_end
78
+ Platform.driver.spi_end
103
79
  end
104
80
 
105
- def clock(divider)
106
- Bcm2835.spi_clock(divider)
81
+ # Uses /dev/spidev0.0 to write to the SPI
82
+ # NOTE: Requires that you have /dev/spidev0.0
83
+ # see: http://learn.adafruit.com/adafruit-raspberry-pi-educational-linux-distro/overview
84
+ # most likely requires `chmod 666 /dev/spidev0.0`
85
+ #
86
+ # @example Writing red, green, blue to a string of WS2801 pixels
87
+ # PiPiper::Spi.spidev_out([255,0,0,0,255,0,0,0,255])
88
+ #
89
+ def self.spidev_out(array)
90
+ Platform.driver.spidev_out(array)
91
+ end
92
+
93
+ # Sets the SPI clock frequency
94
+ def clock(frequency)
95
+ options = {4000 => 0, #4 kHz
96
+ 8000 => 32768, #8 kHz
97
+ 15625 => 16384, #15.625 kHz
98
+ 31250 => 8192, #31.25 kHz
99
+ 62500 => 4096, #62.5 kHz
100
+ 125000 => 2048, #125 kHz
101
+ 250000 => 1024, #250 kHz
102
+ 500000 => 512, #500 kHz
103
+ 1000000 => 256, #1 MHz
104
+ 2000000 => 128, #2 MHz
105
+ 4000000 => 64, #4 MHz
106
+ 8000000 => 32, #8 MHz
107
+ 20000000 => 16 #20 MHz
108
+ }
109
+ divider = options[frequency]
110
+ Platform.driver.spi_clock(divider)
107
111
  end
108
112
 
109
113
  def bit_order(order=MSBFIRST)
@@ -115,7 +119,7 @@ module PiPiper
115
119
  end
116
120
  end
117
121
 
118
- Bcm2835.spi_bit_order(order)
122
+ Platform.driver.spi_bit_order(order)
119
123
  end
120
124
 
121
125
  # Activate a specific chip so that communication can begin
@@ -137,13 +141,13 @@ module PiPiper
137
141
  # @yield
138
142
  # @param [optional, CHIP_SELECT_*] chip the chip select line options
139
143
  def chip_select(chip=CHIP_SELECT_0)
140
- chip = @chip if @chip
141
- Bcm2835.spi_chip_select(chip)
144
+ chip = @chip if @chip
145
+ Platform.driver.spi_chip_select(chip)
142
146
  if block_given?
143
147
  begin
144
148
  yield
145
149
  ensure
146
- Bcm2835.spi_chip_select(CHIP_SELECT_NONE)
150
+ Platform.driver.spi_chip_select(CHIP_SELECT_NONE)
147
151
  end
148
152
  end
149
153
  end
@@ -162,7 +166,7 @@ module PiPiper
162
166
  chip = @chip if @chip
163
167
  chip = CHIP_SELECT_0 unless chip
164
168
 
165
- Bcm2835.spi_chip_select_polarity(chip, active_low ? 0 : 1)
169
+ Platform.driver.spi_chip_select_polarity(chip, active_low ? 0 : 1)
166
170
  end
167
171
 
168
172
  # Read from the bus
@@ -182,7 +186,7 @@ module PiPiper
182
186
  if count
183
187
  write([0xFF] * count)
184
188
  else
185
- enable { Bcm2835.spi_transfer(0) }
189
+ enable { Platform.driver.spi_transfer(0) }
186
190
  end
187
191
  end
188
192
 
@@ -208,9 +212,9 @@ module PiPiper
208
212
  enable do
209
213
  case data
210
214
  when Numeric
211
- Bcm2835.spi_transfer(data)
215
+ Platform.driver.spi_transfer(data)
212
216
  when Enumerable
213
- Bcm2835.spi_transfer_bytes(data)
217
+ Platform.driver.spi_transfer_bytes(data)
214
218
  else
215
219
  raise ArgumentError.new("#{data.class} is not valid data. Use Numeric or an Enumerable of numbers")
216
220
  end
@@ -0,0 +1,107 @@
1
+ # @description driver that can be used either with tests or with rails or other frameworks for development
2
+ require_relative 'pin_values'
3
+
4
+ module PiPiper
5
+
6
+ class NullLogger
7
+ def debug(*) end
8
+ end
9
+
10
+ module StubDriver
11
+ include PiPiper::PinValues
12
+ extend self
13
+
14
+ def new(options = {})
15
+ opts = {
16
+ :logger => NullLogger.new
17
+ }.merge(options)
18
+
19
+ @logger = opts[:logger]
20
+
21
+ @pins = {}
22
+ @spi = {data:[], chip_select:0,}
23
+
24
+ self
25
+ end
26
+ alias_method :reset, :new
27
+
28
+ def pin_input(pin_number)
29
+ #@pins[pin_number] = { direction: :in }
30
+ pin(pin_number)[:direction] = :in
31
+ @logger.debug("Pin ##{pin_number} -> Input")
32
+ end
33
+
34
+ def pin_output(pin_number)
35
+ #@pins[pin_number] = { direction: :in }
36
+ pin(pin_number)[:direction] = :out
37
+ @logger.debug("Pin ##{pin_number} -> Output")
38
+ end
39
+
40
+ def pin_direction(pin_number)
41
+ pin(pin_number)[:direction] if @pins[pin_number]
42
+ end
43
+
44
+ def pin_set(pin_number, value)
45
+ pin(pin_number)[:value] = value
46
+ @logger.debug("Pin ##{pin_number} -> #{value}")
47
+ end
48
+
49
+ def pin_set_pud(pin_number, value)
50
+ pin(pin_number)[:pud] = value
51
+ @logger.debug("PinPUD ##{pin_number} -> #{value}")
52
+ end
53
+
54
+ def spidev_out(array)
55
+ @spi[:data] = array
56
+ @logger.debug("SPIDEV -> #{array.pack('C*')}")
57
+ end
58
+
59
+ def spi_begin
60
+ @logger.debug("SPI Begin")
61
+ @spi[:data] = []
62
+ end
63
+
64
+ def spi_transfer_bytes(data)
65
+ @logger.debug("SPI CS#{@spi[:chip_select]} <- #{data.to_s}")
66
+ @spi[:data] = Array(data)
67
+ end
68
+
69
+ def spi_chip_select(chip = nil)
70
+ chip = chip || @spi[:chip_select]
71
+ @logger.debug("SPI Chip Select = #{chip}")
72
+ @spi[:chip_select] = chip
73
+ end
74
+
75
+ def pin_read(pin_number)
76
+ val = pin(pin_number)[:value]
77
+ val ||= case pin(pin_number)[:pud]
78
+ when GPIO_PUD_UP then GPIO_HIGH
79
+ when GPIO_PUD_DOWN then GPIO_LOW
80
+ else nil
81
+ end
82
+ end
83
+
84
+ def release_pins
85
+ @pins.keys.each { |pin_number| release_pin(pin_number) }
86
+ end
87
+
88
+ def release_pin(pin_number)
89
+ @pins.delete(pin_number)
90
+ end
91
+
92
+ def method_missing(meth, *args, &block)
93
+ puts "Needs Implementation: StubDriver##{meth}"
94
+ end
95
+
96
+ private
97
+
98
+ def pin(pin_number)
99
+ @pins[pin_number] ||= {}
100
+ end
101
+
102
+ ## The following methods are only for testing and are not available on any platforms
103
+ def spi_data
104
+ @spi[:data]
105
+ end
106
+ end
107
+ end
@@ -1,16 +1,20 @@
1
- # -*- encoding: utf-8 -*-
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pi_piper/version'
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = "pi_piper"
5
- s.version = "1.3.2"
8
+ s.version = PiPiper::VERSION
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["Jason Whitehorn"]
9
- s.date = "2013-09-04"
12
+ s.date = "2013-09-14"
10
13
  s.description = "Event driven Raspberry Pi GPIO library"
11
14
  s.email = "jason.whitehorn@gmail.com"
12
- s.extra_rdoc_files = ["README.md", "lib/pi_piper.rb", "lib/pi_piper/bcm2835.rb", "lib/pi_piper/libbcm2835.img", "lib/pi_piper/pin.rb", "lib/pi_piper/spi.rb"]
13
- s.files = ["Manifest", "README.md", "Rakefile", "lib/pi_piper.rb", "lib/pi_piper/bcm2835.rb", "lib/pi_piper/libbcm2835.img", "lib/pi_piper/pin.rb", "lib/pi_piper/spi.rb", "pi_piper.gemspec"]
15
+ s.extra_rdoc_files = ["README.md", "lib/pi_piper.rb", "lib/pi_piper/bcm2835.rb", "lib/pi_piper/frequency.rb", "lib/pi_piper/i2c.rb", "lib/pi_piper/libbcm2835.so", "lib/pi_piper/pin.rb", "lib/pi_piper/platform.rb", "lib/pi_piper/spi.rb"]
16
+ s.files = `git ls-files -z`.split("\x0")
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
14
18
  s.homepage = "http://github.com/jwhitehorn/pi_piper"
15
19
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pi_piper", "--main", "README.md"]
16
20
  s.require_paths = ["lib"]
@@ -18,15 +22,22 @@ Gem::Specification.new do |s|
18
22
  s.rubygems_version = "2.0.0"
19
23
  s.summary = "Event driven Raspberry Pi GPIO library"
20
24
 
21
- if s.respond_to? :specification_version then
25
+ if s.respond_to? :specification_version
22
26
  s.specification_version = 4
23
27
 
24
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
28
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0')
25
29
  s.add_runtime_dependency(%q<ffi>, [">= 0"])
30
+ s.add_runtime_dependency(%q<eventmachine>, ["= 1.0.9"])
26
31
  else
27
32
  s.add_dependency(%q<ffi>, [">= 0"])
33
+ s.add_dependency(%q<eventmachine>, ["= 1.0.9"])
28
34
  end
29
35
  else
30
36
  s.add_dependency(%q<ffi>, [">= 0"])
37
+ s.add_dependency(%q<eventmachine>, ["= 1.0.9"])
31
38
  end
39
+
40
+ s.add_development_dependency 'rspec'
41
+ s.add_development_dependency 'mocha'
42
+ s.add_development_dependency 'simplecov'
32
43
  end
@@ -0,0 +1,83 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe 'I2C' do
4
+ describe 'clock setting' do
5
+ it 'should check driver settings' do
6
+ Platform.driver = StubDriver.new.tap do |d|
7
+ expect(d).to receive(:i2c_allowed_clocks).and_return([100.kilohertz])
8
+ end
9
+
10
+ I2C.clock = 100.kilohertz
11
+ end
12
+
13
+ it 'should accept 100 kHz' do
14
+ Platform.driver = StubDriver.new.tap do |d|
15
+ expect(d).to receive(:i2c_allowed_clocks).and_return([100.kilohertz])
16
+ expect(d).to receive(:i2c_set_clock).with(100.kilohertz)
17
+ end
18
+
19
+ I2C.clock = 100.kilohertz
20
+ end
21
+
22
+ it 'should not accept 200 kHz' do
23
+ Platform.driver = StubDriver.new.tap do |d|
24
+ expect(d).to receive(:i2c_allowed_clocks).and_return([100.kilohertz])
25
+ end
26
+
27
+ expect { I2C.clock = 200.kilohertz }.to raise_error(RuntimeError)
28
+ end
29
+ end
30
+
31
+ describe 'when in block' do
32
+ it 'should call i2c_begin' do
33
+ driver = StubDriver.new
34
+ expect(driver).to receive(:i2c_begin)
35
+
36
+ Platform.driver = driver
37
+ I2C.begin do
38
+ end
39
+ end
40
+
41
+ it 'should call i2c_end' do
42
+ driver = StubDriver.new
43
+ expect(driver).to receive(:i2c_end)
44
+
45
+ Platform.driver = driver
46
+ I2C.begin do
47
+ end
48
+ end
49
+
50
+ it 'should call i2c_end even after raise' do
51
+ driver = StubDriver.new
52
+ expect(driver).to receive(:i2c_end)
53
+
54
+ Platform.driver = driver
55
+ begin
56
+ I2C.begin { raise 'OMG' }
57
+ rescue
58
+ end
59
+ end
60
+
61
+ describe 'write operation' do
62
+ it 'should set address' do
63
+ Platform.driver = StubDriver.new.tap do |d|
64
+ expect(d).to receive(:i2c_set_address).with(4)
65
+ end
66
+
67
+ I2C.begin do
68
+ write to: 4, data: [1, 2, 3, 4]
69
+ end
70
+ end
71
+
72
+ it 'should pass data to driver' do
73
+ Platform.driver = StubDriver.new.tap do |d|
74
+ expect(d).to receive(:i2c_transfer_bytes).with([1, 2, 3, 4])
75
+ end
76
+
77
+ I2C.begin do
78
+ write to: 4, data: [1, 2, 3, 4]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+ include PiPiper
3
+
4
+ describe 'Pin' do
5
+ it 'should export pin for input' do
6
+ Platform.driver = StubDriver.new.tap do |d|
7
+ expect(d).to receive(:pin_input).with(4)
8
+ end
9
+ Pin.new pin: 4, direction: :in
10
+ end
11
+
12
+ it 'should export pin for output' do
13
+ Platform.driver = StubDriver.new.tap do |d|
14
+ expect(d).to receive(:pin_output).with(4)
15
+ end
16
+
17
+ Pin.new pin: 4, direction: :out
18
+ end
19
+
20
+ it 'should read start value on construction' do
21
+ Platform.driver = StubDriver.new.tap do |d|
22
+ expect(d).to receive(:pin_read).with(4).and_return(0)
23
+ end
24
+
25
+ Pin.new pin: 4, direction: :in
26
+ end
27
+
28
+ it 'should detect on?' do
29
+ Platform.driver = StubDriver.new.tap do |d|
30
+ expect(d).to receive(:pin_read).with(4).and_return(1)
31
+ end
32
+
33
+ pin = Pin.new pin: 4, direction: :in
34
+ expect(pin.on?).to be(true)
35
+ end
36
+
37
+ it 'should detect off?' do
38
+ Platform.driver = StubDriver.new.tap do |d|
39
+ expect(d).to receive(:pin_read).with(4).and_return(0)
40
+ end
41
+
42
+ pin = Pin.new pin: 4, direction: :in
43
+ expect(pin.off?).to be(true)
44
+ end
45
+
46
+ it 'should invert true' do
47
+ Platform.driver = StubDriver.new.tap do |d|
48
+ expect(d).to receive(:pin_read).with(4).and_return(1)
49
+ end
50
+
51
+ pin = Pin.new pin: 4, direction: :in, invert: true
52
+ expect(pin.on?).to be(false)
53
+ end
54
+
55
+ it 'should invert true' do
56
+ Platform.driver = StubDriver.new.tap do |d|
57
+ expect(d).to receive(:pin_read).with(4).and_return(0)
58
+ end
59
+
60
+ pin = Pin.new pin: 4, direction: :in, invert: true
61
+ expect(pin.off?).to be(false)
62
+ end
63
+
64
+ it 'should write high' do
65
+ Platform.driver = StubDriver.new.tap do |d|
66
+ expect(d).to receive(:pin_set).with(4, 1)
67
+ end
68
+
69
+ pin = Pin.new pin: 4, direction: :out
70
+ pin.on
71
+ end
72
+
73
+ it 'should write low' do
74
+ Platform.driver = StubDriver.new.tap do |d|
75
+ expect(d).to receive(:pin_set).with(4, 0)
76
+ end
77
+
78
+ pin = Pin.new pin: 4, direction: :out
79
+ pin.off
80
+ end
81
+
82
+ it 'should not write high on direction in' do
83
+ Platform.driver = StubDriver.new.tap do |d|
84
+ expect(d).not_to receive(:pin_set)
85
+ end
86
+
87
+ pin = Pin.new pin: 4, direction: :in
88
+ pin.on
89
+ end
90
+
91
+ it 'should not write low on direction in' do
92
+ Platform.driver = StubDriver.new.tap do |d|
93
+ expect(d).not_to receive(:pin_set)
94
+ end
95
+
96
+ pin = Pin.new pin: 4, direction: :in
97
+ pin.off
98
+ end
99
+
100
+ it 'should detect high to low change' do
101
+ Platform.driver = StubDriver.new.tap do |d|
102
+ value = 1
103
+ # begins low, then high, low, high, low...
104
+ allow(d).to receive(:pin_read) { value ^= 1 }
105
+ end
106
+
107
+ pin = Pin.new pin: 4, direction: :in
108
+ expect(pin.off?).to be(true)
109
+ pin.read
110
+ expect(pin.off?).to be(false)
111
+ expect(pin.changed?).to be(true)
112
+ end
113
+
114
+ xit 'should wait for change' do
115
+ pending
116
+ end
117
+
118
+ context 'given a pin is released' do
119
+ it 'should actually release it' do
120
+ Platform.driver = StubDriver.new.tap do |driver|
121
+ expect(driver).to receive(:release_pin).with(4)
122
+ end
123
+
124
+ pin = Pin.new(pin: 4, direction: :in)
125
+ pin.release
126
+ expect(pin.released?).to be(true)
127
+ end
128
+
129
+ it 'should not mark unreleased pins as released' do
130
+ pin = Pin.new(pin: 4, direction: :in)
131
+ expect(pin.released?).to be(false)
132
+ end
133
+
134
+ it 'should not continue to use the pin' do
135
+ Platform.driver = StubDriver.new.tap do |driver|
136
+ expect(driver).to receive(:release_pin).with(4)
137
+ end
138
+
139
+ pin = Pin.new(pin: 4, direction: :in)
140
+ pin.release
141
+
142
+ expect { pin.read }.to raise_error(PinError, 'Pin 4 already released')
143
+ expect { pin.on }.to raise_error(PinError, 'Pin 4 already released')
144
+ expect { pin.off }.to raise_error(PinError, 'Pin 4 already released')
145
+ expect { pin.pull!(:up) }.to(
146
+ raise_error(PinError, 'Pin 4 already released'))
147
+ end
148
+ end
149
+ end