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