From 6fb566dcf554c4857ce5f70c3004d874538f34ce Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 20 Mar 2016 14:36:38 -0500 Subject: [PATCH 1/6] Move files to their appropriate locations. Still need to eventually extend the PiPiper::Driver class. --- .travis.yml | 1 + Gemfile.lock | 16 ++- lib/bcm2835/driver.rb | 135 ------------------------- lib/pi_piper/bcm2835.rb | 11 ++- lib/pi_piper/bcm2835/bcm2835.rb | 4 - lib/pi_piper/bcm2835/driver.rb | 137 ++++++++++++++++++++++++++ lib/pi_piper/bcm2835/version.rb | 4 +- spec/pi_piper/bcm2835/bcm2835_spec.rb | 13 +++ spec/pi_piper/bcm2835_spec.rb | 7 -- 9 files changed, 174 insertions(+), 154 deletions(-) delete mode 100644 lib/bcm2835/driver.rb delete mode 100644 lib/pi_piper/bcm2835/bcm2835.rb create mode 100644 lib/pi_piper/bcm2835/driver.rb create mode 100644 spec/pi_piper/bcm2835/bcm2835_spec.rb delete mode 100644 spec/pi_piper/bcm2835_spec.rb diff --git a/.travis.yml b/.travis.yml index 3dfc619..e968680 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: ruby +cache: bundler rvm: - 1.9.3 - 2.1.0 diff --git a/Gemfile.lock b/Gemfile.lock index 17aaa59..9fcf70e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,21 @@ PATH remote: . specs: - bcm2835 (0.1.0) + pi_piper-bcm2835 (0.1.0) ffi - pi_piper + pi_piper (>= 2.0.0) GEM remote: https://rubygems.org/ specs: coderay (1.1.0) diff-lcs (1.2.5) + docile (1.1.5) eventmachine (1.0.9) ffi (1.9.10) + json (1.8.3) method_source (0.8.2) - pi_piper (1.9.9) + pi_piper (2.0.0) eventmachine (= 1.0.9) ffi pry (0.10.3) @@ -34,17 +36,23 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.4.0) rspec-support (3.4.1) + simplecov (0.11.2) + docile (~> 1.1.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) slop (3.6.0) PLATFORMS ruby DEPENDENCIES - bcm2835! bundler (~> 1.11) + pi_piper-bcm2835! pry rake (~> 10.0) rspec (~> 3.0) + simplecov BUNDLED WITH 1.11.2 diff --git a/lib/bcm2835/driver.rb b/lib/bcm2835/driver.rb deleted file mode 100644 index 2208f07..0000000 --- a/lib/bcm2835/driver.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'ffi' -require 'pi_piper/frequency' - -module Bcm2835 - # The Bcm2835 module is not intended to be directly called. - # It serves as an FFI library for PiPiper::SPI and PiPiper::I2C - class Driver - extend FFI::Library - - ffi_lib File.dirname(__FILE__) + '/../../bin/libbcm2835.so' - @pins = [] - - SPI_MODE0 = 0 - SPI_MODE1 = 1 - SPI_MODE2 = 2 - SPI_MODE3 = 3 - - I2C_REASON_OK = 0 # Success - I2C_REASON_ERROR_NACK = 1 # Received a NACK - I2C_REASON_ERROR_CLKT = 2 # Received Clock Stretch Timeout - I2C_REASON_ERROR_DATA = 3 # Not all data is sent / received - - attach_function :init, :bcm2835_init, [], :uint8 - attach_function :close, :bcm2835_close, [], :uint8 - - # Sets the Function Select register for the given pin, which configures the - # pin as Input, Output or one of the 6 alternate functions. - attach_function :gpio_select_function, :bcm2835_gpio_fsel, [:uint8, :uint8], :void - # attach_function :gpio_set, :bcm2835_gpio_set, [:uint8], :void - # attach_function :gpio_clear, :bcm2835_gpio_clr, [:uint8], :void - # attach_function :gpio_level, :bcm2835_gpio_lev, [:uint8], :uint8 - - # pin support... - attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void - - def self.pin_input(pin) - export(pin) - pin_direction(pin, 'in') - end - - def self.pin_set(pin, value) - File.write("/sys/class/gpio/gpio#{pin}/value", value) - end - - def self.pin_output(pin) - export(pin) - pin_direction(pin, 'out') - end - - def self.pin_read(pin) - File.read("/sys/class/gpio/gpio#{pin}/value").to_i - end - - # PWM support... - attach_function :pwm_clock, :bcm2835_pwm_set_clock, [:uint32], :void - attach_function :pwm_mode, :bcm2835_pwm_set_mode, [:uint8, :uint8, :uint8], :void - attach_function :pwm_range, :bcm2835_pwm_set_range, [:uint8, :uint32], :void - attach_function :pwm_data, :bcm2835_pwm_set_data, [:uint8, :uint32], :void - - def self.pin_direction(pin, direction) - File.write("/sys/class/gpio/gpio#{pin}/direction", direction) - end - - # Exports pin and subsequently locks it from outside access - def self.export(pin) - File.write('/sys/class/gpio/export', pin) - @pins << pin unless @pins.include?(pin) - end - - def self.release_pin(pin) - File.write('/sys/class/gpio/unexport', pin) - @pins.delete(pin) - end - - def self.release_pins - @pins.dup.each { |pin| release_pin(pin) } - end - - # NOTE to use: chmod 666 /dev/spidev0.0 - def self.spidev_out(array) - File.open('/dev/spidev0.0', 'wb') { |f| f.write(array.pack('C*')) } - end - - # SPI support... - attach_function :spi_begin, :bcm2835_spi_begin, [], :uint8 - attach_function :spi_end, :bcm2835_spi_end, [], :uint8 - attach_function :spi_transfer, :bcm2835_spi_transfer, [:uint8], :uint8 - attach_function :spi_transfernb, :bcm2835_spi_transfernb, [:pointer, :pointer, :uint], :void - attach_function :spi_clock, :bcm2835_spi_setClockDivider, [:uint8], :void - attach_function :spi_bit_order, :bcm2835_spi_setBitOrder, [:uint8], :void - attach_function :spi_chip_select, :bcm2835_spi_chipSelect, [:uint8], :void - attach_function :spi_set_data_mode, :bcm2835_spi_setDataMode, [:uint8], :void - attach_function :spi_chip_select_polarity, - :bcm2835_spi_setChipSelectPolarity, [:uint8, :uint8], :void - - #I2C support... - attach_function :i2c_begin, :bcm2835_i2c_begin, [], :void - attach_function :i2c_end, :bcm2835_i2c_end, [], :void - attach_function :i2c_write, :bcm2835_i2c_write, [:pointer, :uint], :uint8 - attach_function :i2c_set_address,:bcm2835_i2c_setSlaveAddress, [:uint8], :void - attach_function :i2c_set_clock_divider, :bcm2835_i2c_setClockDivider, [:uint16], :void - attach_function :i2c_read, :bcm2835_i2c_read, [:pointer, :uint], :uint8 - - def self.i2c_allowed_clocks - [100.kilohertz, - 399.3610.kilohertz, - 1.666.megahertz, - 1.689.megahertz] - end - - def self.spi_transfer_bytes(data) - data_out = FFI::MemoryPointer.new(data.count) - data_in = FFI::MemoryPointer.new(data.count) - (0..data.count - 1).each { |i| data_out.put_uint8(i, data[i]) } - - spi_transfernb(data_out, data_in, data.count) - - (0..data.count - 1).map { |i| data_in.get_uint8(i) } - end - - def self.i2c_transfer_bytes(data) - data_out = FFI::MemoryPointer.new(data.count) - (0..data.count - 1).each{ |i| data_out.put_uint8(i, data[i]) } - - i2c_write data_out, data.count - end - - def self.i2c_read_bytes(bytes) - data_in = FFI::MemoryPointer.new(bytes) - i2c_read(data_in, bytes) #TODO reason codes - - (0..bytes - 1).map { |i| data_in.get_uint8(i) } - end - end -end diff --git a/lib/pi_piper/bcm2835.rb b/lib/pi_piper/bcm2835.rb index a3a1637..806f1c8 100644 --- a/lib/pi_piper/bcm2835.rb +++ b/lib/pi_piper/bcm2835.rb @@ -1,5 +1,12 @@ -require "pi_piper/bcm2835/version" +require 'pi_piper/bcm2835/version' +require 'pi_piper/bcm2835/driver' module PiPiper - autoload PiPiper::Bcm2835, "pi_piper/bcm2835/bcm2835" + module Bcm2835 + class << self + def driver + @driver ||= PiPiper::Bcm2835::Driver.new + end + end + end end diff --git a/lib/pi_piper/bcm2835/bcm2835.rb b/lib/pi_piper/bcm2835/bcm2835.rb deleted file mode 100644 index 37127f0..0000000 --- a/lib/pi_piper/bcm2835/bcm2835.rb +++ /dev/null @@ -1,4 +0,0 @@ -module PiPiper - class Bcm2835 < Driver - end -end diff --git a/lib/pi_piper/bcm2835/driver.rb b/lib/pi_piper/bcm2835/driver.rb new file mode 100644 index 0000000..208852c --- /dev/null +++ b/lib/pi_piper/bcm2835/driver.rb @@ -0,0 +1,137 @@ +require 'ffi' +require 'pi_piper/frequency' + +module PiPiper + module Bcm2835 + # The Bcm2835 module is not intended to be directly called. + # It serves as an FFI library for PiPiper::SPI and PiPiper::I2C + class Driver + extend FFI::Library + + ffi_lib File.expand_path('../../../bin/libbcm2835/so', __FILE__) + @pins = [] + + SPI_MODE0 = 0 + SPI_MODE1 = 1 + SPI_MODE2 = 2 + SPI_MODE3 = 3 + + I2C_REASON_OK = 0 # Success + I2C_REASON_ERROR_NACK = 1 # Received a NACK + I2C_REASON_ERROR_CLKT = 2 # Received Clock Stretch Timeout + I2C_REASON_ERROR_DATA = 3 # Not all data is sent / received + + attach_function :init, :bcm2835_init, [], :uint8 + attach_function :close, :bcm2835_close, [], :uint8 + + # Sets the Function Select register for the given pin, which configures the + # pin as Input, Output or one of the 6 alternate functions. + attach_function :gpio_select_function, :bcm2835_gpio_fsel, [:uint8, :uint8], :void + # attach_function :gpio_set, :bcm2835_gpio_set, [:uint8], :void + # attach_function :gpio_clear, :bcm2835_gpio_clr, [:uint8], :void + # attach_function :gpio_level, :bcm2835_gpio_lev, [:uint8], :uint8 + + # pin support... + attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void + + def self.pin_input(pin) + export(pin) + pin_direction(pin, 'in') + end + + def self.pin_set(pin, value) + File.write("/sys/class/gpio/gpio#{pin}/value", value) + end + + def self.pin_output(pin) + export(pin) + pin_direction(pin, 'out') + end + + def self.pin_read(pin) + File.read("/sys/class/gpio/gpio#{pin}/value").to_i + end + + # PWM support... + attach_function :pwm_clock, :bcm2835_pwm_set_clock, [:uint32], :void + attach_function :pwm_mode, :bcm2835_pwm_set_mode, [:uint8, :uint8, :uint8], :void + attach_function :pwm_range, :bcm2835_pwm_set_range, [:uint8, :uint32], :void + attach_function :pwm_data, :bcm2835_pwm_set_data, [:uint8, :uint32], :void + + def self.pin_direction(pin, direction) + File.write("/sys/class/gpio/gpio#{pin}/direction", direction) + end + + # Exports pin and subsequently locks it from outside access + def self.export(pin) + File.write('/sys/class/gpio/export', pin) + @pins << pin unless @pins.include?(pin) + end + + def self.release_pin(pin) + File.write('/sys/class/gpio/unexport', pin) + @pins.delete(pin) + end + + def self.release_pins + @pins.dup.each { |pin| release_pin(pin) } + end + + # NOTE to use: chmod 666 /dev/spidev0.0 + def self.spidev_out(array) + File.open('/dev/spidev0.0', 'wb') { |f| f.write(array.pack('C*')) } + end + + # SPI support... + attach_function :spi_begin, :bcm2835_spi_begin, [], :uint8 + attach_function :spi_end, :bcm2835_spi_end, [], :uint8 + attach_function :spi_transfer, :bcm2835_spi_transfer, [:uint8], :uint8 + attach_function :spi_transfernb, :bcm2835_spi_transfernb, [:pointer, :pointer, :uint], :void + attach_function :spi_clock, :bcm2835_spi_setClockDivider, [:uint8], :void + attach_function :spi_bit_order, :bcm2835_spi_setBitOrder, [:uint8], :void + attach_function :spi_chip_select, :bcm2835_spi_chipSelect, [:uint8], :void + attach_function :spi_set_data_mode, :bcm2835_spi_setDataMode, [:uint8], :void + attach_function :spi_chip_select_polarity, + :bcm2835_spi_setChipSelectPolarity, [:uint8, :uint8], :void + + #I2C support... + attach_function :i2c_begin, :bcm2835_i2c_begin, [], :void + attach_function :i2c_end, :bcm2835_i2c_end, [], :void + attach_function :i2c_write, :bcm2835_i2c_write, [:pointer, :uint], :uint8 + attach_function :i2c_set_address,:bcm2835_i2c_setSlaveAddress, [:uint8], :void + attach_function :i2c_set_clock_divider, :bcm2835_i2c_setClockDivider, [:uint16], :void + attach_function :i2c_read, :bcm2835_i2c_read, [:pointer, :uint], :uint8 + + def self.i2c_allowed_clocks + [100.kilohertz, + 399.3610.kilohertz, + 1.666.megahertz, + 1.689.megahertz] + end + + def self.spi_transfer_bytes(data) + data_out = FFI::MemoryPointer.new(data.count) + data_in = FFI::MemoryPointer.new(data.count) + (0..data.count - 1).each { |i| data_out.put_uint8(i, data[i]) } + + spi_transfernb(data_out, data_in, data.count) + + (0..data.count - 1).map { |i| data_in.get_uint8(i) } + end + + def self.i2c_transfer_bytes(data) + data_out = FFI::MemoryPointer.new(data.count) + (0..data.count - 1).each{ |i| data_out.put_uint8(i, data[i]) } + + i2c_write data_out, data.count + end + + def self.i2c_read_bytes(bytes) + data_in = FFI::MemoryPointer.new(bytes) + i2c_read(data_in, bytes) #TODO reason codes + + (0..bytes - 1).map { |i| data_in.get_uint8(i) } + end + end + end +end diff --git a/lib/pi_piper/bcm2835/version.rb b/lib/pi_piper/bcm2835/version.rb index bcc346f..afddd14 100644 --- a/lib/pi_piper/bcm2835/version.rb +++ b/lib/pi_piper/bcm2835/version.rb @@ -1,5 +1,5 @@ module PiPiper - class Bcm2835 - VERSION = "0.1.0" + module Bcm2835 + VERSION = '0.1.0' end end diff --git a/spec/pi_piper/bcm2835/bcm2835_spec.rb b/spec/pi_piper/bcm2835/bcm2835_spec.rb new file mode 100644 index 0000000..b480aea --- /dev/null +++ b/spec/pi_piper/bcm2835/bcm2835_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe PiPiper::Bcm2835 do + it 'has a version number' do + expect(PiPiper::Bcm2835::VERSION).not_to be nil + end + + describe '#driver' do + it 'should return the Bcm2835 driver' do + expect(PiPiper::Bcm2835.driver).to be_a(PiPiper::Bcm2835::Driver) + end + end +end diff --git a/spec/pi_piper/bcm2835_spec.rb b/spec/pi_piper/bcm2835_spec.rb deleted file mode 100644 index ad68f86..0000000 --- a/spec/pi_piper/bcm2835_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'spec_helper' - -describe PiPiper::Bcm2835 do - it 'has a version number' do - expect(PiPiper::Bcm2835::VERSION).not_to be nil - end -end From 1cce4f8f279b61954fe2ba89e97aafefe5e0c694 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 20 Mar 2016 19:37:11 -0500 Subject: [PATCH 2/6] Break out pin command logic into module. Add tests for this module --- lib/pi_piper/bcm2835.rb | 1 + lib/pi_piper/bcm2835/driver.rb | 38 +-------- lib/pi_piper/bcm2835/pin.rb | 61 ++++++++++++++ spec/pi_piper/bcm2835/pin_spec.rb | 133 ++++++++++++++++++++++++++++++ spec/spec_helper.rb | 4 +- 5 files changed, 199 insertions(+), 38 deletions(-) create mode 100644 lib/pi_piper/bcm2835/pin.rb create mode 100644 spec/pi_piper/bcm2835/pin_spec.rb diff --git a/lib/pi_piper/bcm2835.rb b/lib/pi_piper/bcm2835.rb index 806f1c8..28e8b97 100644 --- a/lib/pi_piper/bcm2835.rb +++ b/lib/pi_piper/bcm2835.rb @@ -1,4 +1,5 @@ require 'pi_piper/bcm2835/version' +require 'pi_piper/bcm2835/pin' require 'pi_piper/bcm2835/driver' module PiPiper diff --git a/lib/pi_piper/bcm2835/driver.rb b/lib/pi_piper/bcm2835/driver.rb index 208852c..c848b5c 100644 --- a/lib/pi_piper/bcm2835/driver.rb +++ b/lib/pi_piper/bcm2835/driver.rb @@ -6,6 +6,7 @@ module Bcm2835 # The Bcm2835 module is not intended to be directly called. # It serves as an FFI library for PiPiper::SPI and PiPiper::I2C class Driver + extend PiPiper::Bcm2835::Pin extend FFI::Library ffi_lib File.expand_path('../../../bin/libbcm2835/so', __FILE__) @@ -33,24 +34,6 @@ class Driver # pin support... attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void - - def self.pin_input(pin) - export(pin) - pin_direction(pin, 'in') - end - - def self.pin_set(pin, value) - File.write("/sys/class/gpio/gpio#{pin}/value", value) - end - - def self.pin_output(pin) - export(pin) - pin_direction(pin, 'out') - end - - def self.pin_read(pin) - File.read("/sys/class/gpio/gpio#{pin}/value").to_i - end # PWM support... attach_function :pwm_clock, :bcm2835_pwm_set_clock, [:uint32], :void @@ -58,25 +41,6 @@ def self.pin_read(pin) attach_function :pwm_range, :bcm2835_pwm_set_range, [:uint8, :uint32], :void attach_function :pwm_data, :bcm2835_pwm_set_data, [:uint8, :uint32], :void - def self.pin_direction(pin, direction) - File.write("/sys/class/gpio/gpio#{pin}/direction", direction) - end - - # Exports pin and subsequently locks it from outside access - def self.export(pin) - File.write('/sys/class/gpio/export', pin) - @pins << pin unless @pins.include?(pin) - end - - def self.release_pin(pin) - File.write('/sys/class/gpio/unexport', pin) - @pins.delete(pin) - end - - def self.release_pins - @pins.dup.each { |pin| release_pin(pin) } - end - # NOTE to use: chmod 666 /dev/spidev0.0 def self.spidev_out(array) File.open('/dev/spidev0.0', 'wb') { |f| f.write(array.pack('C*')) } diff --git a/lib/pi_piper/bcm2835/pin.rb b/lib/pi_piper/bcm2835/pin.rb new file mode 100644 index 0000000..3fc7157 --- /dev/null +++ b/lib/pi_piper/bcm2835/pin.rb @@ -0,0 +1,61 @@ +require 'set' + +module PiPiper + module Bcm2835 + module Pin + def pins + @pins ||= Set.new + end + + def pin_input(pin) + export(pin) + pin_direction(pin, 'in') + end + + def pin_set(pin, value) + File.write("/sys/class/gpio/gpio#{pin}/value", value) + end + + def pin_output(pin) + export(pin) + pin_direction(pin, 'out') + end + + def pin_read(pin) + raise ArgumentError, "Pin #{pin} is not exported" if unexported?(pin) + File.read("/sys/class/gpio/gpio#{pin}/value").to_i + end + + def unexport(pin) + raise ArgumentError, "Pin #{pin} not exported" if unexported?(pin) + File.write('/sys/class/gpio/unexport', pin) + pins.delete(pin) + end + + def unexport_all + pins.dup.each { |pin| unexport(pin) } + end + + def exported?(pin) + pins.include?(pin) + end + + def unexported?(pin) + !exported?(pin) + end + + private + + def export(pin) + raise ArgumentError, "Pin #{pin} already exported" if exported?(pin) + File.write('/sys/class/gpio/export', pin) + pins << pin + end + + def pin_direction(pin, direction) + File.write("/sys/class/gpio/gpio#{pin}/direction", direction) + end + + end + end +end diff --git a/spec/pi_piper/bcm2835/pin_spec.rb b/spec/pi_piper/bcm2835/pin_spec.rb new file mode 100644 index 0000000..e745c97 --- /dev/null +++ b/spec/pi_piper/bcm2835/pin_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe PiPiper::Bcm2835::Pin do + let(:pin) { Class.new { extend PiPiper::Bcm2835::Pin } } + + before(:each) do + allow(File).to receive(:write).with('/sys/class/gpio/export', 4) + allow(File).to receive(:write).with('/sys/class/gpio/gpio4/direction', 'in') + end + + describe '.pins' do + it 'should instantiate an empty set' do + expect(pin.pins).to be_a(Set) + expect(pin.pins).to be_empty + end + + it 'should return the exported pins' do + pin.pin_input(4) + expect(pin.pins).to contain_exactly(4) + end + end + + describe '.pin_input' do + it 'should export the pin in the `in` direction' do + expect(pin).to receive(:export).with(4) + pin.pin_input(4) + end + + it 'should keep state of exported pins' do + pin.pin_input(4) + expect(pin.pins).to contain_exactly(4) + end + + it 'should raise an error if pin is exported already' do + pin.pin_input(4) + expect { pin.pin_input(4) }.to raise_error(ArgumentError) + end + end + + describe '.pin_set' do + it 'should set the value of the pin' do + expect(File).to receive(:write).with('/sys/class/gpio/gpio4/value', '1') + pin.pin_set(4, '1') + end + end + + describe '.pin_output' do + before(:each) do + allow(File).to( + receive(:write).with('/sys/class/gpio/gpio4/direction', 'out')) + end + + it 'should export the pin in the `in` direction' do + expect(pin).to receive(:export).with(4) + pin.pin_output(4) + end + + it 'should keep state of exported pins' do + pin.pin_output(4) + expect(pin.pins).to contain_exactly(4) + end + + it 'should raise an error if pin is exported already' do + pin.pin_output(4) + expect { pin.pin_output(4) }.to raise_error(ArgumentError) + end + end + + describe '.pin_read' do + it 'should return the value of the pin read' do + pin.pin_input(4) + expect(File).to( + receive(:read).with('/sys/class/gpio/gpio4/value').and_return(1)) + expect(pin.pin_read(4)).to eq(1) + end + + it 'should raise an error if pin is not exported' do + expect { pin.pin_read(4) }.to raise_error(ArgumentError) + end + end + + describe '.unexport' do + it 'should unexport the pin' do + expect(File).to receive(:write).with('/sys/class/gpio/unexport', 4) + pin.pin_input(4) + pin.unexport(4) + end + + it 'should raise an error if pin is not exported' do + expect { pin.unexport(4) }.to raise_error(ArgumentError) + end + end + + describe '.unexport_all' do + it 'should unexport all the export pins' do + allow(File).to receive(:write).with('/sys/class/gpio/export', 4) + allow(File).to receive(:write).with('/sys/class/gpio/gpio4/direction', 'in') + allow(File).to receive(:write).with('/sys/class/gpio/export', 5) + allow(File).to receive(:write).with('/sys/class/gpio/gpio5/direction', 'in') + + expect(File).to receive(:write).with('/sys/class/gpio/unexport', 4) + expect(File).to receive(:write).with('/sys/class/gpio/unexport', 5) + + pin.pin_input(4) + pin.pin_input(5) + + pin.unexport_all + expect(pin.pins).to be_empty + end + end + + describe '.exported?' do + it 'should return true if the pin is exported' do + pin.pin_input(4) + expect(pin.exported?(4)).to be(true) + end + + it 'should return false if the pin is not exported' do + expect(pin.exported?(4)).to be(false) + end + end + + describe '.unexported?' do + it 'should return true if the pin is not exported' do + expect(pin.unexported?(4)).to be(true) + end + + it 'should return false if the pin is exported' do + pin.pin_input(4) + expect(pin.unexported?(4)).to be(false) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 761371d..fc49a07 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,4 +2,6 @@ SimpleCov.start $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -require 'pi_piper/bcm2835' +#require 'pi_piper/bcm2835' +require 'pi_piper/bcm2835/version' +require 'pi_piper/bcm2835/pin' From a7523696b548cfb0baaff402c380fb2bf2ecdcf1 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 20 Mar 2016 21:17:38 -0500 Subject: [PATCH 3/6] temporarily remove core bcm2835 that imports driver class. Run tests for pin --- lib/pi_piper/bcm2835/driver.rb | 3 +-- spec/pi_piper/bcm2835/bcm2835_spec.rb | 13 ------------- spec/spec_helper.rb | 2 -- 3 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 spec/pi_piper/bcm2835/bcm2835_spec.rb diff --git a/lib/pi_piper/bcm2835/driver.rb b/lib/pi_piper/bcm2835/driver.rb index c848b5c..f3174d3 100644 --- a/lib/pi_piper/bcm2835/driver.rb +++ b/lib/pi_piper/bcm2835/driver.rb @@ -9,8 +9,7 @@ class Driver extend PiPiper::Bcm2835::Pin extend FFI::Library - ffi_lib File.expand_path('../../../bin/libbcm2835/so', __FILE__) - @pins = [] + ffi_lib File.expand_path('../../../../bin/libbcm2835.so', __FILE__) SPI_MODE0 = 0 SPI_MODE1 = 1 diff --git a/spec/pi_piper/bcm2835/bcm2835_spec.rb b/spec/pi_piper/bcm2835/bcm2835_spec.rb deleted file mode 100644 index b480aea..0000000 --- a/spec/pi_piper/bcm2835/bcm2835_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe PiPiper::Bcm2835 do - it 'has a version number' do - expect(PiPiper::Bcm2835::VERSION).not_to be nil - end - - describe '#driver' do - it 'should return the Bcm2835 driver' do - expect(PiPiper::Bcm2835.driver).to be_a(PiPiper::Bcm2835::Driver) - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fc49a07..d82176a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,4 @@ SimpleCov.start $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -#require 'pi_piper/bcm2835' -require 'pi_piper/bcm2835/version' require 'pi_piper/bcm2835/pin' From f87b2935c8ff9da7cc34dd63bc729ea4b42e07d6 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 20 Mar 2016 22:22:02 -0500 Subject: [PATCH 4/6] break out spi and i2c into separate modules and add tests for both --- lib/pi_piper/bcm2835.rb | 2 + lib/pi_piper/bcm2835/driver.rb | 107 ++++++++---------------------- lib/pi_piper/bcm2835/i2c.rb | 42 ++++++++++++ lib/pi_piper/bcm2835/spi.rb | 40 +++++++++++ spec/pi_piper/bcm2835/i2c_spec.rb | 33 +++++++++ spec/pi_piper/bcm2835/spi_spec.rb | 30 +++++++++ spec/spec_helper.rb | 2 +- 7 files changed, 175 insertions(+), 81 deletions(-) create mode 100644 lib/pi_piper/bcm2835/i2c.rb create mode 100644 lib/pi_piper/bcm2835/spi.rb create mode 100644 spec/pi_piper/bcm2835/i2c_spec.rb create mode 100644 spec/pi_piper/bcm2835/spi_spec.rb diff --git a/lib/pi_piper/bcm2835.rb b/lib/pi_piper/bcm2835.rb index 28e8b97..c033238 100644 --- a/lib/pi_piper/bcm2835.rb +++ b/lib/pi_piper/bcm2835.rb @@ -1,5 +1,7 @@ require 'pi_piper/bcm2835/version' require 'pi_piper/bcm2835/pin' +require 'pi_piper/bcm2835/spi' +require 'pi_piper/bcm2835/i2c' require 'pi_piper/bcm2835/driver' module PiPiper diff --git a/lib/pi_piper/bcm2835/driver.rb b/lib/pi_piper/bcm2835/driver.rb index f3174d3..18f5ae6 100644 --- a/lib/pi_piper/bcm2835/driver.rb +++ b/lib/pi_piper/bcm2835/driver.rb @@ -1,5 +1,4 @@ require 'ffi' -require 'pi_piper/frequency' module PiPiper module Bcm2835 @@ -7,94 +6,42 @@ module Bcm2835 # It serves as an FFI library for PiPiper::SPI and PiPiper::I2C class Driver extend PiPiper::Bcm2835::Pin + extend PiPiper::Bcm2835::SPI + extend PiPiper::Bcm2835::I2C extend FFI::Library - ffi_lib File.expand_path('../../../../bin/libbcm2835.so', __FILE__) - - SPI_MODE0 = 0 - SPI_MODE1 = 1 - SPI_MODE2 = 2 - SPI_MODE3 = 3 - - I2C_REASON_OK = 0 # Success - I2C_REASON_ERROR_NACK = 1 # Received a NACK - I2C_REASON_ERROR_CLKT = 2 # Received Clock Stretch Timeout - I2C_REASON_ERROR_DATA = 3 # Not all data is sent / received - - attach_function :init, :bcm2835_init, [], :uint8 - attach_function :close, :bcm2835_close, [], :uint8 - - # Sets the Function Select register for the given pin, which configures the - # pin as Input, Output or one of the 6 alternate functions. - attach_function :gpio_select_function, :bcm2835_gpio_fsel, [:uint8, :uint8], :void - # attach_function :gpio_set, :bcm2835_gpio_set, [:uint8], :void - # attach_function :gpio_clear, :bcm2835_gpio_clr, [:uint8], :void - # attach_function :gpio_level, :bcm2835_gpio_lev, [:uint8], :uint8 - - # pin support... - attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void - - # PWM support... - attach_function :pwm_clock, :bcm2835_pwm_set_clock, [:uint32], :void - attach_function :pwm_mode, :bcm2835_pwm_set_mode, [:uint8, :uint8, :uint8], :void - attach_function :pwm_range, :bcm2835_pwm_set_range, [:uint8, :uint32], :void - attach_function :pwm_data, :bcm2835_pwm_set_data, [:uint8, :uint32], :void - - # NOTE to use: chmod 666 /dev/spidev0.0 - def self.spidev_out(array) - File.open('/dev/spidev0.0', 'wb') { |f| f.write(array.pack('C*')) } - end - - # SPI support... - attach_function :spi_begin, :bcm2835_spi_begin, [], :uint8 - attach_function :spi_end, :bcm2835_spi_end, [], :uint8 - attach_function :spi_transfer, :bcm2835_spi_transfer, [:uint8], :uint8 - attach_function :spi_transfernb, :bcm2835_spi_transfernb, [:pointer, :pointer, :uint], :void - attach_function :spi_clock, :bcm2835_spi_setClockDivider, [:uint8], :void - attach_function :spi_bit_order, :bcm2835_spi_setBitOrder, [:uint8], :void - attach_function :spi_chip_select, :bcm2835_spi_chipSelect, [:uint8], :void - attach_function :spi_set_data_mode, :bcm2835_spi_setDataMode, [:uint8], :void - attach_function :spi_chip_select_polarity, - :bcm2835_spi_setChipSelectPolarity, [:uint8, :uint8], :void - - #I2C support... - attach_function :i2c_begin, :bcm2835_i2c_begin, [], :void - attach_function :i2c_end, :bcm2835_i2c_end, [], :void - attach_function :i2c_write, :bcm2835_i2c_write, [:pointer, :uint], :uint8 - attach_function :i2c_set_address,:bcm2835_i2c_setSlaveAddress, [:uint8], :void - attach_function :i2c_set_clock_divider, :bcm2835_i2c_setClockDivider, [:uint16], :void - attach_function :i2c_read, :bcm2835_i2c_read, [:pointer, :uint], :uint8 - - def self.i2c_allowed_clocks - [100.kilohertz, - 399.3610.kilohertz, - 1.666.megahertz, - 1.689.megahertz] + def instantiate + ffi_lib File.expand_path('../../../../bin/libbcm2835.so', __FILE__) + setup_gpio + setup_pwm + setup_spi + setup_i2c end - def self.spi_transfer_bytes(data) - data_out = FFI::MemoryPointer.new(data.count) - data_in = FFI::MemoryPointer.new(data.count) - (0..data.count - 1).each { |i| data_out.put_uint8(i, data[i]) } - - spi_transfernb(data_out, data_in, data.count) - - (0..data.count - 1).map { |i| data_in.get_uint8(i) } + def setup_gpio + attach_function :init, :bcm2835_init, [], :uint8 + attach_function :close, :bcm2835_close, [], :uint8 + + # Sets the Function Select register for the given pin, which configures the + # pin as Input, Output or one of the 6 alternate functions. + attach_function :gpio_select_function, :bcm2835_gpio_fsel, [:uint8, :uint8], :void + # attach_function :gpio_set, :bcm2835_gpio_set, [:uint8], :void + # attach_function :gpio_clear, :bcm2835_gpio_clr, [:uint8], :void + # attach_function :gpio_level, :bcm2835_gpio_lev, [:uint8], :uint8 + + # pin support... + attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void end - def self.i2c_transfer_bytes(data) - data_out = FFI::MemoryPointer.new(data.count) - (0..data.count - 1).each{ |i| data_out.put_uint8(i, data[i]) } - - i2c_write data_out, data.count + def setup_pwm + # PWM support... + attach_function :pwm_clock, :bcm2835_pwm_set_clock, [:uint32], :void + attach_function :pwm_mode, :bcm2835_pwm_set_mode, [:uint8, :uint8, :uint8], :void + attach_function :pwm_range, :bcm2835_pwm_set_range, [:uint8, :uint32], :void + attach_function :pwm_data, :bcm2835_pwm_set_data, [:uint8, :uint32], :void end - def self.i2c_read_bytes(bytes) - data_in = FFI::MemoryPointer.new(bytes) - i2c_read(data_in, bytes) #TODO reason codes - (0..bytes - 1).map { |i| data_in.get_uint8(i) } - end end end end diff --git a/lib/pi_piper/bcm2835/i2c.rb b/lib/pi_piper/bcm2835/i2c.rb new file mode 100644 index 0000000..b388dd1 --- /dev/null +++ b/lib/pi_piper/bcm2835/i2c.rb @@ -0,0 +1,42 @@ +require 'ffi' +require 'pi_piper/frequency' + +module PiPiper + module Bcm2835 + module I2C + I2C_REASON_OK = 0 # Success + I2C_REASON_ERROR_NACK = 1 # Received a NACK + I2C_REASON_ERROR_CLKT = 2 # Received Clock Stretch Timeout + I2C_REASON_ERROR_DATA = 3 # Not all data is sent / received + + def setup_i2c + attach_function :i2c_begin, :bcm2835_i2c_begin, [], :void + attach_function :i2c_end, :bcm2835_i2c_end, [], :void + attach_function :i2c_write, :bcm2835_i2c_write, [:pointer, :uint], :uint8 + attach_function :i2c_set_address,:bcm2835_i2c_setSlaveAddress, [:uint8], :void + attach_function :i2c_set_clock_divider, :bcm2835_i2c_setClockDivider, [:uint16], :void + attach_function :i2c_read, :bcm2835_i2c_read, [:pointer, :uint], :uint8 + end + + def i2c_allowed_clocks + [100.kilohertz, + 399.3610.kilohertz, + 1.666.megahertz, + 1.689.megahertz] + end + + def i2c_transfer_bytes(data) + data_out = FFI::MemoryPointer.new(data.count) + (0..data.count - 1).each{ |i| data_out.put_uint8(i, data[i]) } + i2c_write(data_out, data.count) + end + + def i2c_read_bytes(bytes) + data_in = FFI::MemoryPointer.new(bytes) + i2c_read(data_in, bytes) #TODO reason codes + + (0..bytes - 1).map { |i| data_in.get_uint8(i) } + end + end + end +end diff --git a/lib/pi_piper/bcm2835/spi.rb b/lib/pi_piper/bcm2835/spi.rb new file mode 100644 index 0000000..05851db --- /dev/null +++ b/lib/pi_piper/bcm2835/spi.rb @@ -0,0 +1,40 @@ +require 'ffi' + +module PiPiper + module Bcm2835 + module SPI + SPI_MODE0 = 0 + SPI_MODE1 = 1 + SPI_MODE2 = 2 + SPI_MODE3 = 3 + + def setup_spi + attach_function :spi_begin, :bcm2835_spi_begin, [], :uint8 + attach_function :spi_end, :bcm2835_spi_end, [], :uint8 + attach_function :spi_transfer, :bcm2835_spi_transfer, [:uint8], :uint8 + attach_function :spi_transfernb, :bcm2835_spi_transfernb, [:pointer, :pointer, :uint], :void + attach_function :spi_clock, :bcm2835_spi_setClockDivider, [:uint8], :void + attach_function :spi_bit_order, :bcm2835_spi_setBitOrder, [:uint8], :void + attach_function :spi_chip_select, :bcm2835_spi_chipSelect, [:uint8], :void + attach_function :spi_set_data_mode, :bcm2835_spi_setDataMode, [:uint8], :void + attach_function :spi_chip_select_polarity, + :bcm2835_spi_setChipSelectPolarity, [:uint8, :uint8], :void + end + + # NOTE to use: chmod 666 /dev/spidev0.0 + def spidev_out(array) + File.open('/dev/spidev0.0', 'wb') { |f| f.write(array.pack('C*')) } + end + + def spi_transfer_bytes(data) + data_out = FFI::MemoryPointer.new(data.count) + data_in = FFI::MemoryPointer.new(data.count) + (0..data.count - 1).each { |i| data_out.put_uint8(i, data[i]) } + + spi_transfernb(data_out, data_in, data.count) + + (0..data.count - 1).map { |i| data_in.get_uint8(i) } + end + end + end +end diff --git a/spec/pi_piper/bcm2835/i2c_spec.rb b/spec/pi_piper/bcm2835/i2c_spec.rb new file mode 100644 index 0000000..d69188b --- /dev/null +++ b/spec/pi_piper/bcm2835/i2c_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe PiPiper::Bcm2835::I2C do + let(:i2c) { Class.new { extend PiPiper::Bcm2835::I2C } } + + describe '.setup_i2c' do + it 'should run the :attach_function through ffi' do + expect(i2c).to receive(:attach_function).exactly(6).times + i2c.setup_i2c + end + end + + describe '.i2c_allowed_clocks' do + it 'should return the allowed clock rates' do + expect(i2c.i2c_allowed_clocks).to be_a(Array) + end + end + + describe '.i2c_transfer_bytes' do + it 'should write bytes via i2c' do + allow(i2c).to receive(:i2c_write) + i2c.i2c_transfer_bytes([1, 2, 3, 4]) + end + end + + describe '.i2c_read_bytes' do + it 'should read the bytes through i2c' do + allow(i2c).to receive(:i2c_read) + # read 4 bytes + expect(i2c.i2c_read_bytes(0x4)).to eq([0, 0, 0, 0]) + end + end +end diff --git a/spec/pi_piper/bcm2835/spi_spec.rb b/spec/pi_piper/bcm2835/spi_spec.rb new file mode 100644 index 0000000..91840d4 --- /dev/null +++ b/spec/pi_piper/bcm2835/spi_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe PiPiper::Bcm2835::SPI do + let(:spi) { Class.new { extend PiPiper::Bcm2835::SPI } } + + describe '.setup_spi' do + it 'should run the :attach_function through ffi' do + expect(spi).to receive(:attach_function).exactly(9).times + spi.setup_spi + end + end + + describe '.spidev_out' do + it 'should write to spidev' do + expect(File).to receive(:open).with('/dev/spidev0.0', 'wb') + spi.spidev_out([1, 2, 3]) + end + end + + describe '.spi_transfer_bytes' do + it 'should transfer bytes through spi interface' do + expect(spi).to( + receive(:spi_transfernb).with(instance_of(FFI::MemoryPointer), + instance_of(FFI::MemoryPointer), + instance_of(Fixnum))) + spi.spi_transfer_bytes([0x0, 0x1, 0x2]) + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d82176a..761371d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,4 +2,4 @@ SimpleCov.start $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -require 'pi_piper/bcm2835/pin' +require 'pi_piper/bcm2835' From 79a768d068a1db2cf6848b52debc0f9ed027722b Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 20 Mar 2016 22:24:56 -0500 Subject: [PATCH 5/6] add back the core spec file --- spec/pi_piper/bcm2835/bcm2835_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 spec/pi_piper/bcm2835/bcm2835_spec.rb diff --git a/spec/pi_piper/bcm2835/bcm2835_spec.rb b/spec/pi_piper/bcm2835/bcm2835_spec.rb new file mode 100644 index 0000000..b480aea --- /dev/null +++ b/spec/pi_piper/bcm2835/bcm2835_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe PiPiper::Bcm2835 do + it 'has a version number' do + expect(PiPiper::Bcm2835::VERSION).not_to be nil + end + + describe '#driver' do + it 'should return the Bcm2835 driver' do + expect(PiPiper::Bcm2835.driver).to be_a(PiPiper::Bcm2835::Driver) + end + end +end From 9f510dc6bb8d5a6ad935efff0c5d3d66b378d834 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 20 Mar 2016 23:55:50 -0500 Subject: [PATCH 6/6] stub out the ffi_lib and attach_function methods to get code coverage to 100% --- lib/pi_piper/bcm2835/driver.rb | 11 ++++++----- spec/pi_piper/bcm2835/bcm2835_spec.rb | 4 ++++ spec/pi_piper/bcm2835/driver_spec.rb | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 spec/pi_piper/bcm2835/driver_spec.rb diff --git a/lib/pi_piper/bcm2835/driver.rb b/lib/pi_piper/bcm2835/driver.rb index 18f5ae6..9a708ec 100644 --- a/lib/pi_piper/bcm2835/driver.rb +++ b/lib/pi_piper/bcm2835/driver.rb @@ -1,16 +1,17 @@ require 'ffi' +require 'pry' module PiPiper module Bcm2835 # The Bcm2835 module is not intended to be directly called. # It serves as an FFI library for PiPiper::SPI and PiPiper::I2C class Driver - extend PiPiper::Bcm2835::Pin - extend PiPiper::Bcm2835::SPI - extend PiPiper::Bcm2835::I2C - extend FFI::Library + include PiPiper::Bcm2835::Pin + include PiPiper::Bcm2835::SPI + include PiPiper::Bcm2835::I2C + include FFI::Library - def instantiate + def initialize ffi_lib File.expand_path('../../../../bin/libbcm2835.so', __FILE__) setup_gpio setup_pwm diff --git a/spec/pi_piper/bcm2835/bcm2835_spec.rb b/spec/pi_piper/bcm2835/bcm2835_spec.rb index b480aea..a50c56c 100644 --- a/spec/pi_piper/bcm2835/bcm2835_spec.rb +++ b/spec/pi_piper/bcm2835/bcm2835_spec.rb @@ -6,7 +6,11 @@ end describe '#driver' do + let(:driver) { PiPiper::Bcm2835::Driver } it 'should return the Bcm2835 driver' do + allow_any_instance_of(driver).to receive(:ffi_lib) + allow_any_instance_of(driver).to receive(:attach_function) + expect(PiPiper::Bcm2835.driver).to be_a(PiPiper::Bcm2835::Driver) end end diff --git a/spec/pi_piper/bcm2835/driver_spec.rb b/spec/pi_piper/bcm2835/driver_spec.rb new file mode 100644 index 0000000..86d4925 --- /dev/null +++ b/spec/pi_piper/bcm2835/driver_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe PiPiper::Bcm2835::Driver do + let(:driver) { PiPiper::Bcm2835::Driver } + describe '#initialize' do + it 'should run the setup attach_functions' do + allow_any_instance_of(driver).to receive(:ffi_lib) + allow_any_instance_of(driver).to receive(:attach_function) + expect_any_instance_of(driver).to receive(:attach_function) + + driver.new + end + end +end