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..c033238 100644 --- a/lib/pi_piper/bcm2835.rb +++ b/lib/pi_piper/bcm2835.rb @@ -1,5 +1,15 @@ -require "pi_piper/bcm2835/version" +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 - 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..9a708ec --- /dev/null +++ b/lib/pi_piper/bcm2835/driver.rb @@ -0,0 +1,48 @@ +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 + include PiPiper::Bcm2835::Pin + include PiPiper::Bcm2835::SPI + include PiPiper::Bcm2835::I2C + include FFI::Library + + def initialize + ffi_lib File.expand_path('../../../../bin/libbcm2835.so', __FILE__) + setup_gpio + setup_pwm + setup_spi + setup_i2c + end + + 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 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 + + + 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/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/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/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..a50c56c --- /dev/null +++ b/spec/pi_piper/bcm2835/bcm2835_spec.rb @@ -0,0 +1,17 @@ +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 + 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 +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 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/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/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/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