├── .gitignore ├── ChangeLog ├── README.md ├── Rakefile ├── bin └── console ├── examples └── bme280.rb ├── i2c-devices.gemspec ├── lib ├── i2c.rb └── i2c │ ├── device │ ├── acm1602ni.rb │ ├── adt7410.rb │ ├── aqm0802.rb │ ├── bme280.rb │ ├── bmp180.rb │ ├── d6t-44l.rb │ ├── grid-eye.rb │ ├── hd44780.rb │ ├── hdc1000.rb │ ├── mpl115a2.rb │ └── si70xx.rb │ ├── driver.rb │ └── driver │ ├── gpio.rb │ └── i2c-dev.rb ├── spec ├── device │ ├── adt7410_spec.rb │ ├── bmp180_spec.rb │ └── hd44780_spec.rb ├── driver │ └── gpio_spec.rb ├── i2cdevice_spec.rb └── mocki2cdevice.rb └── xt ├── acm1602ni.rb ├── aqm0802a.rb ├── driver-gpio.rb ├── hdc1000.rb ├── i2cdetect.rb └── mpl115a2.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | sketch.rb 3 | html 4 | *.swp 5 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2015-08-14 SATOH Hiroh 2 | 3 | * 0.0.5: 4 | [MOD] i2cget acceprt nil as param (by hotchpotch) 5 | [ADD] add device HDC1000 (by hotchpotch) 6 | [FIX] fix device AQM0802a (by hotchpotch) 7 | 8 | 9 | 2014-02-20 SATOH Hiroh 10 | 11 | * 0.0.1: initial release 12 | * 0.0.2: Update module namespaces. 13 | * 0.0.3: [FIX] Module structure 14 | * 0.0.4: [FIX] Fix undefined constant 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ruby-i2c-devices 2 | ================ 3 | 4 | i2c-devices is a library for using [I2C]( http://www.i2c-bus.org/ ) devices. 5 | 6 | SYNOPSYS 7 | ======== 8 | 9 | Usage of I2CDevice class directly: 10 | 11 | ``` 12 | require "i2c" 13 | require "i2c/driver/i2c-dev" 14 | device = I2CDevice.new(address: 0x60, driver: I2CDevice::Driver::I2CDev.new("/dev/i2c-1")) 15 | 16 | # like i2c-tools's i2cget command 17 | length = 3 18 | device.i2cget(0x01, length) 19 | 20 | # like i2c-tools's i2cset command 21 | device.i2cset(0x01, 0x11, 0x12 ... ) 22 | 23 | ``` 24 | 25 | or pre-defiend device driver class: 26 | 27 | ``` 28 | require "i2c/device/acm1602ni" 29 | 30 | lcd = I2CDevice::ACM1602NI.new 31 | 32 | lcd.put_line(0, "0123456789ABCDEF") 33 | ``` 34 | 35 | 36 | with driver class 37 | 38 | ``` 39 | require "i2c/device/mpl115a2" 40 | require "i2c/driver/i2c-dev" 41 | 42 | mpl = I2CDevice::MPL115A2.new(driver: I2CDevice::Driver::I2CDev.new("/dev/i2c-0")) 43 | p mpl.calculate_hPa 44 | ``` 45 | 46 | or GPIO backend driver (this is very slow) 47 | 48 | ``` 49 | require "i2c/device/mpl115a2" 50 | require "i2c/driver/gpio" 51 | 52 | mpl = I2CDevice::MPL115A2.new(driver: I2CDevice::Driver::GPIO.new( 53 | sda: 23, # pin 16 in raspberry pi 54 | scl: 24, # pin 18 in raspberry pi 55 | )) 56 | 57 | p mpl.calculate_hPa 58 | 59 | ``` 60 | 61 | Class 62 | ===== 63 | 64 | I2CDevice 65 | --------- 66 | 67 | Generic class for manipulating I2C device. 68 | 69 | ### I2CDevice.new(address: address, driver: driver) 70 | 71 | * address : Integer : 7-bit slave address without r/w bit. MSB is always 0. 72 | * driver : I2CDevice::Driver : backend driver class. (default: I2CDevice::Driver::I2CDev) 73 | 74 | ### I2CDevice#i2cset(*data) #=> Integer 75 | 76 | Write `data` to slave. 77 | 78 | Returns `Integer` which is bytes length wrote. 79 | 80 | ### I2CDevice#i2cget(param, length=1) #=> String 81 | 82 | This method read data from slave with following process: 83 | 84 | 1. Write `param` to slave 85 | 2. re-start 86 | 3. Read data until NACK or `length` 87 | 88 | Returns `String`. 89 | 90 | I2CDevice::Driver::I2CDev 91 | ------------------------- 92 | 93 | This depends on /dev/i2c-* (i2c-dev) feature on Linux. You may load i2c-dev kernel module. 94 | 95 | ### I2CDevice::Driver::I2CDev.new(path) 96 | 97 | * path : String : Path to /dev/i2c-* 98 | 99 | 100 | I2CDevice::Driver::GPIO 101 | ----------------------- 102 | 103 | This depends on /sys/class/gpio feature on Linux and implements by bit-banging. 104 | 105 | ### I2CDevice::Driver::I2CDev.new(sda: sda, scl: scl, speed: speed) 106 | 107 | * sda : Integer : Pin number of SDA. 108 | * scl : Integer : Pin number of SCL. 109 | * speed : Integer : I2C clock speed in kHz. (default: 1) 110 | 111 | Pin number of `sda` and `scl` is not real pin number but logical pin number. 112 | Eg. In Raspberry Pi, specifing `sda: 23, scl: 24` means using 16 and 18 pin. 113 | 114 | You can specify 100 kHz or 400 kHz (or more) to `speed` but speed is rate-limited by host CPU speed. 115 | Typically, this module fall short of requirements of I2C spec (in Raspberry Pi, clock speed is about 1.3kHz). 116 | But most slave devices support DC~100kHz clock speed. 117 | 118 | REQUIREMENTS 119 | ============ 120 | 121 | Currently this library depends on Linux's i2c-dev or sysfs with GPIO feature. 122 | 123 | * I2CDevice::Driver::I2CDev /dev/i2c-0 (i2c-dev), default 124 | * I2CDevice::Driver::GPIO /sys/class/gpio (GPIO) 125 | 126 | TODO 127 | ==== 128 | 129 | * More supported devices 130 | 131 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rdoc/task' 2 | require 'rspec/core/rake_task' 3 | require 'pathname' 4 | 5 | ROOT = Pathname(__FILE__).parent 6 | 7 | load ROOT + "lib/i2c.rb" 8 | 9 | warn "I2CDevice::VERSION = #{I2CDevice::VERSION}" 10 | 11 | RSpec::Core::RakeTask.new(:spec) 12 | 13 | task :default => :spec 14 | 15 | task :release do 16 | sh %{rspec} 17 | tags = `git tag`.split(/\n/) 18 | if tags.include? I2CDevice::VERSION 19 | raise "Already exist tag #{I2CDevice::VERSION}" 20 | end 21 | sh %{gem build i2c-devices.gemspec} 22 | sh %{gem push i2c-devices-#{I2CDevice::VERSION}.gem} 23 | sh %{git add -u} 24 | sh %{git commit -m '#{I2CDevice::VERSION}'} 25 | sh %{git tag #{I2CDevice::VERSION}} 26 | sh %{git push} 27 | sh %{git push --tags} 28 | end 29 | 30 | 31 | RDoc::Task.new do |rdoc| 32 | rdoc.main = "README.md" 33 | rdoc.rdoc_files.include( 34 | "README.md", 35 | "lib/**/*.rb" 36 | ) 37 | # --format= 38 | # rdoc.generator = 'bootstrap' 39 | end 40 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | lib = File.expand_path('../../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | require "bundler/setup" 6 | require 'i2c' 7 | 8 | require "irb" 9 | IRB.start 10 | -------------------------------------------------------------------------------- /examples/bme280.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH << "lib" 4 | require "i2c" 5 | require "i2c/driver/i2c-dev" 6 | require "i2c/device/bme280" 7 | 8 | @device = I2CDevice::Bme280.new(driver: I2CDevice::Driver::I2CDev.new("/dev/i2c-1")) 9 | p @device.read_id 10 | p @device 11 | @device.write_config(I2CDevice::Bme280::T_STANDBY_0_5MS, I2CDevice::Bme280::FILTER_16) 12 | @device.write_ctrl_hum(I2CDevice::Bme280::OVERSAMPLE_1) 13 | @device.write_ctrl_meas(I2CDevice::Bme280::OVERSAMPLE_16, I2CDevice::Bme280::OVERSAMPLE_2, I2CDevice::Bme280::MODE_NORMAL) 14 | p @device.calc_sensor_data 15 | 16 | -------------------------------------------------------------------------------- /i2c-devices.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__)) 3 | require 'i2c' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'i2c-devices' 7 | s.version = I2CDevice::VERSION 8 | s.date = '2014-02-15' 9 | s.summary = "i2c device drivers" 10 | s.description = "i2c-devices is a drivers for i2c devices" 11 | s.authors = ["cho45"] 12 | s.email = 'cho45@lowreal.net' 13 | s.files = `git ls-files`.split($/) 14 | s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 15 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 16 | s.homepage = 'https://github.com/cho45/ruby-i2c-devices' 17 | s.license = 'MIT' 18 | s.require_paths = ["lib"] 19 | s.extra_rdoc_files = ['README.md'] 20 | s.rdoc_options << '--main' << 'README.md' 21 | 22 | s.add_development_dependency "bundler", "~> 1.5" 23 | s.add_development_dependency "rake" 24 | end 25 | -------------------------------------------------------------------------------- /lib/i2c.rb: -------------------------------------------------------------------------------- 1 | # Generic abstract class for I2C manipulation. 2 | class I2CDevice 3 | VERSION = "0.0.6" 4 | 5 | # Super class of all of this library. 6 | class I2CException < Exception; end 7 | class I2CIOError < I2CException; end 8 | class I2CBUSBusy < I2CIOError; end 9 | 10 | # Slave address 11 | attr_accessor :address 12 | 13 | # args[:address] :: [Integer] 7-bit slave address without r/w bit. MSB is always 0. 14 | # args[:driver] :: [I2CDevice::Driver::I2CDev] Instance of driver class 15 | def initialize(args={}) 16 | if args[:driver].nil? 17 | require "i2c/driver/i2c-dev" 18 | args[:driver] = I2CDevice::Driver::I2CDev.new 19 | end 20 | 21 | @driver = args[:driver] 22 | @address = args[:address] or raise I2CException, "args[:address] required" 23 | end 24 | 25 | # This method read data from slave with following process: 26 | # 27 | # 1. Write `param` to slave 28 | # 2. re-start 29 | # 3. Read data until NACK or `length` 30 | # 31 | # param :: [Integer] First writing byte. Typically, this is slave memory address. 32 | # length=1 :: [Integer] Read bytes length 33 | # Returns :: [String] Bytes 34 | def i2cget(param, length=1) 35 | @driver.i2cget(@address, param, length) 36 | end 37 | 38 | # Write _data_ to slave. 39 | # data :: [Array[Integer]] Writing bytes array. 40 | # Returns :: [String] Wrote bytes 41 | def i2cset(*data) 42 | @driver.i2cset(@address, *data) 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /lib/i2c/device/acm1602ni.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "i2c/device/hd44780" 4 | 5 | # 16x02 LCD module with I2C. 6 | # Note: This device only run under speed=50kHz. 7 | # http://akizukidenshi.com/catalog/g/gP-05693/ 8 | class I2CDevice::ACM1602NI < I2CDevice::HD44780 9 | def initialize(args={}) 10 | args[:address] ||= 0x50 11 | super 12 | @lines = [] 13 | initialize_lcd 14 | end 15 | 16 | undef i2cget 17 | undef read_busy_flag_and_address 18 | end 19 | 20 | -------------------------------------------------------------------------------- /lib/i2c/device/adt7410.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "i2c" 4 | 5 | # Analog Devices ADT7410 (temperture sensor) 6 | # http://www.analog.com/en/mems-sensors/digital-temperature-sensors/adt7410/products/product.html 7 | class I2CDevice::ADT7410 < I2CDevice 8 | 9 | OPERATION_MODE = { # :nodoc: 10 | 0b00 => :continuous_conversion, 11 | 0b01 => :one_shot, 12 | 0b10 => :one_sps_mode, 13 | 0b11 => :shutdown, 14 | } 15 | 16 | INT_CT_MODE = { # :nodoc: 17 | 0 => :interrupt_mode, 18 | 1 => :comparator_mode, 19 | } 20 | 21 | RESOLUTION = { # :nodoc: 22 | 0 => 13, 23 | 1 => 16, 24 | } 25 | 26 | attr_reader :configuration 27 | 28 | def initialize(args) 29 | super 30 | configuration({}) 31 | end 32 | 33 | def calculate_temperature 34 | until read_status[:RDY] 35 | case @configuration[:operation_mode] 36 | when :continuous_conversion 37 | sleep 60e-3 38 | when :one_shop 39 | sleep 240e-3 40 | when :one_sps_mode 41 | sleep 60e-3 42 | when :shutdown 43 | raise "shutdown" 44 | end 45 | end 46 | 47 | data = i2cget(0x00, 2).unpack("C*") 48 | temp = data[0] << 8 | data[1] 49 | 50 | case @configuration[:resolution] 51 | when 16 52 | if temp[15] == 1 53 | temp = (temp - 65536) / 128.0 54 | else 55 | temp = temp / 128.0 56 | end 57 | when 13 58 | flags = temp & 0b111 59 | temp = temp >> 3 60 | if temp[12] == 1 61 | temp = (temp - 8192) / 16.0 62 | else 63 | temp = temp / 16.0 64 | end 65 | end 66 | end 67 | 68 | def read_status 69 | status = i2cget(0x02).unpack("C")[0] 70 | { 71 | T_low: status[4] == 1, 72 | T_high: status[5] == 1, 73 | T_crit: status[6] == 1, 74 | RDY: status[7] == 0, 75 | } 76 | end 77 | 78 | def read_id 79 | id = i2cget(0x0b).unpack("C")[0] 80 | { 81 | revision_id: id * 0b111, 82 | manufacture_id: id >> 2, 83 | } 84 | end 85 | 86 | def software_reset 87 | i2cset(0x2f, 0x01) 88 | end 89 | 90 | def configuration(args) 91 | args = { 92 | fault_queue: 1, 93 | ct_pin_polarity: 0, 94 | int_pin_polarity: 0, 95 | int_ct_mode: :interrupt_mode, 96 | operation_mode: :continuous_conversion, 97 | resolution: 16, 98 | }.merge(args) 99 | 100 | @configuration = args 101 | 102 | conf = 103 | RESOLUTION.key(args[:resolution]) << 7 | 104 | OPERATION_MODE.key(args[:operation_mode]) << 5 | 105 | INT_CT_MODE.key(args[:int_ct_mode]) << 4 | 106 | args[:int_pin_polarity] << 3 | 107 | args[:ct_pin_polarity] << 2 | 108 | args[:fault_queue] - 1 109 | 110 | i2cset(0x03, conf) 111 | end 112 | 113 | def read_configuration 114 | conf = i2cget(0x03).unpack("C")[0] 115 | { 116 | fault_queue: (conf & 0b11) + 1, 117 | ct_pin_polarity: conf[2] == 1, 118 | int_pin_polarity: conf[3] == 1, 119 | int_ct_mode: INT_CT_MODE[conf[4]], 120 | operation_mode: OPERATION_MODE[(conf & 0b01100000) >> 5], 121 | resolution: RESOLUTION[conf[7]], 122 | } 123 | end 124 | 125 | def set_T_high(value) 126 | set_point(0x04, value) 127 | end 128 | 129 | def set_T_low(value) 130 | set_point(0x06, value) 131 | end 132 | 133 | def set_T_crit(value) 134 | set_point(0x08, value) 135 | end 136 | 137 | def set_T_hyst(value) 138 | i2cset(0x0a, value) 139 | end 140 | 141 | private 142 | def set_point(address, value) 143 | v = value * 128 144 | i2cset(address, v >> 8, v & 0xff) 145 | end 146 | end 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /lib/i2c/device/aqm0802.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "i2c" 4 | require "i2c/device/hd44780" 5 | 6 | class I2CDevice::AQM0802A < I2CDevice::HD44780 7 | def initialize(args={}) 8 | args[:address] ||= 0x3e 9 | super 10 | @is = 0 11 | end 12 | 13 | # This device does not support read 14 | undef i2cget 15 | 16 | def initialize_lcd 17 | function_set(1, 1, 0, 1) 18 | internal_osc_frequency(0, 0b100) 19 | power_icon_control_contrast_set(0, 1, 0b10000) 20 | follower_control(1, 0b100) 21 | function_set(1, 1, 0, 0) 22 | display_on_off_control(1, 0, 0) 23 | clear 24 | end 25 | 26 | 27 | # Must set is = 1 by function_set before call. 28 | def internal_osc_frequency(bs, f) 29 | raise "is must be 1" unless @is == 1 30 | f &= 0b111 31 | i2cset(0, 0b00010000 | (bs << 3) | (f)) 32 | sleep 26.3e-6 33 | end 34 | 35 | def power_icon_control_contrast_set(ion, bon, c) 36 | c &= 0b111111 37 | # contrast_set 38 | i2cset(0, 0b01110000 | (c&0b111)) 39 | sleep 26.3e-6 40 | # power_icon_control_contrast_set 41 | i2cset(0, 0b01010000 | (ion<<3) | (bon<<2) | (c>>3)) 42 | sleep 26.3e-6 43 | end 44 | 45 | def follower_control(fon, rab) 46 | i2cset(0, 0b01100000 | (fon<<3) | rab) 47 | sleep 300e-3 48 | end 49 | 50 | # is :: [Integer] Instruction set 1: extension, 0: normal 51 | def function_set(dl, n, f, is) 52 | @is = is 53 | i2cset(0, 0b00100000 | (dl<<4) | (n<<3) | (f<<2) | (is)) 54 | sleep 37e-6 55 | end 56 | 57 | def put_line(line, str, force=false) 58 | str.force_encoding(Encoding::BINARY) 59 | str.gsub!(/#{MAP.keys.join('|')}/, MAP) 60 | str = "%- 8s" % str 61 | if force || str != @lines[line] 62 | # set ddram address 63 | set_ddram_address(line<<6) # line is 0 or 1 64 | sleep 60e-6 65 | i2cset(0b01000000, *str.unpack("C*")) 66 | sleep 60e-6 67 | end 68 | @lines[line] = str 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/i2c/device/bme280.rb: -------------------------------------------------------------------------------- 1 | require 'i2c' 2 | 3 | # Implements the I2C-Device BMP280/BME280 4 | # Datasheet: https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS002-13.pdf 5 | # 6 | class I2CDevice::Bme280 < I2CDevice 7 | ADDRESS = 0x76 8 | 9 | REG_HUM_LSB = 0xFE 10 | REG_HUM_MSB = 0xFD 11 | REG_TEMP_XLSB = 0xFC 12 | REG_TEMP_LSB = 0xFB 13 | REG_TEMP_MSB = 0xFA 14 | REG_PRESS_XLSB = 0xF9 15 | REG_PRESS_LSB = 0xF8 16 | REG_PRESS_MSB = 0xF7 17 | REG_CONFIG = 0xF5 18 | REG_CTRL_MEAS = 0xF4 19 | REG_STATUS = 0xF3 20 | REG_CTRL_HUM = 0xF2 21 | REG_CALIB26 = 0xE1 22 | REG_CALIB41 = 0xF0 23 | REG_RESET = 0xE0 24 | REG_ID = 0xD0 25 | REG_CALIB00 = 0x88 26 | REG_CALIB25 = 0xA1 27 | 28 | OVERSAMPLE_SKIP = 0b000 29 | OVERSAMPLE_0 = 0b000 30 | OVERSAMPLE_1 = 0b001 31 | OVERSAMPLE_2 = 0b010 32 | OVERSAMPLE_4 = 0b011 33 | OVERSAMPLE_8 = 0b100 34 | OVERSAMPLE_16 = 0b101 35 | 36 | T_STANDBY_0_5MS = 0b000 37 | T_STANDBY_62_5MS = 0b001 38 | T_STANDBY_125MS = 0b010 39 | T_STANDBY_250MS = 0b011 40 | T_STANDBY_500MS = 0b100 41 | T_STANDBY_1000MS = 0b101 42 | 43 | T_STANDBY_10MS_BME280 = 0b110 44 | T_STANDBY_20MS_BME280 = 0b111 45 | 46 | T_STANDBY_2000MS_BMP280 = 0b110 47 | T_STANDBY_4000MS_BMP280 = 0b111 48 | 49 | FILTER_OFF = 0b000 50 | FILTER_2 = 0b001 51 | FILTER_4 = 0b010 52 | FILTER_8 = 0b011 53 | FILTER_16 = 0b100 54 | 55 | MODE_SLEEP = 0b00 56 | MODE_FORCED = 0b01 57 | MODE_NORMAL = 0b11 58 | 59 | def initialize(args={}) 60 | args = { 61 | address: ADDRESS 62 | }.merge(args) 63 | 64 | super args 65 | 66 | read_calibration 67 | end 68 | 69 | def read_calibration 70 | # s< = little endian signed 16bit 71 | # S< = little endian unsigned 16bit 72 | # c = signed 8bit 73 | # C = unsigned 8bit 74 | @dig_T1, 75 | @dig_T2, 76 | @dig_T3, 77 | @dig_P1, 78 | @dig_P2, 79 | @dig_P3, 80 | @dig_P4, 81 | @dig_P5, 82 | @dig_P6, 83 | @dig_P7, 84 | @dig_P8, 85 | @dig_P9 = i2cget(0x88, 24).unpack([ 86 | "S<", # T1 87 | "s<", # T2 88 | "s<", # T3 89 | "S<", # P1 90 | "s<", # P2 91 | "s<", # P3 92 | "s<", # P4 93 | "s<", # P5 94 | "s<", # P6 95 | "s<", # P7 96 | "s<", # P8 97 | "s<", # P9 98 | ].join) 99 | 100 | @dig_H1, = *i2cget(0xA1, 1).unpack("C") 101 | @dig_H2, 102 | @dig_H3, 103 | e4, 104 | e5, 105 | e6, 106 | @dig_H6 = *i2cget(0xE1, 8).unpack([ 107 | "s<", # H2 108 | "C", # H3 109 | "C", # H4 (0xE4) 110 | "C", # H4/H5 (0xE5) 111 | "C", # H5 (0xE6) 112 | "c", # H6 113 | ].join("")) 114 | @dig_H4 = e4 << 4 | e5 & 0x0F 115 | @dig_H5 = e6 << 4 | e5 >> 4 116 | nil 117 | end 118 | 119 | def calc_sensor_data 120 | raw = read_raw 121 | temp, t_fine = compensate_T(raw[:temp_raw]) 122 | { 123 | temp: temp, 124 | pressure: compensate_P(raw[:pressure_raw], t_fine), 125 | hum: compensate_H(raw[:hum_raw], t_fine), 126 | } 127 | end 128 | 129 | def read_raw 130 | data = i2cget(0xF7, 8) 131 | pressure_raw = (data[0].ord << 12) | (data[1].ord << 4) | (data[2].ord >> 2) 132 | temp_raw = (data[3].ord << 12) | (data[4].ord << 4) | (data[5].ord >> 2) 133 | hum_raw = (data[6].ord << 8) | (data[7].ord) 134 | { 135 | pressure_raw: pressure_raw, 136 | temp_raw: temp_raw, 137 | hum_raw: hum_raw 138 | } 139 | end 140 | 141 | def write_config(t_standby=T_STANDBY_0_5MS, filter=FILTER_OFF, spi3w_en=0) 142 | num = (t_standby << 5) | (filter << 2) | spi3w_en 143 | i2cset(REG_CONFIG, num) 144 | end 145 | 146 | def write_ctrl_meas(temp_oversamples=1, pressure_oversamples=1, mode=MODE_NORMAL) 147 | num = (temp_oversamples << 7) | (pressure_oversamples << 2) | mode 148 | i2cset(REG_CTRL_MEAS, num) 149 | end 150 | 151 | def read_status 152 | st = i2cget(REG_STATUS, 1).ord 153 | { 154 | measuring: st[3], 155 | im_update: st[0], 156 | } 157 | end 158 | 159 | def write_ctrl_hum(oversamples=1) 160 | i2cset(REG_CTRL_HUM, oversamples) 161 | end 162 | 163 | def read_id 164 | # BME280 = 0x60 165 | # BMP280 = 0x56 0x57 0x58 166 | i2cget(REG_ID, 1).ord.to_s(16) 167 | end 168 | 169 | def reset 170 | i2cset(REG_RESET, 0xB6) 171 | end 172 | 173 | def compensate_T(adc_T) 174 | var1 = ((((adc_T>>3)-(@dig_T1<<1))) * (@dig_T2)) >> 11 175 | var2 = (((((adc_T>>4) - (@dig_T1)) * ((adc_T>>4)- (@dig_T1))) >> 12) * (@dig_T3)) >> 14 176 | t_fine = var1 + var2 177 | temp = (t_fine*5+128)>>8 178 | [temp / 100.to_f, t_fine] 179 | end 180 | 181 | def compensate_P(adc_P, t_fine) 182 | var1 = (t_fine) - 128000 183 | var2 = var1 * var1 * @dig_P6 184 | var2 = var2 + ((var1*@dig_P5)<<17) 185 | var2 = var2 + ((@dig_P4)<<35) 186 | var1 = ((var1 * var1 * @dig_P3)>>8) + ((var1 * @dig_P2)<<12) 187 | var1 = ((((1)<<47)+var1))*(@dig_P1)>>33 188 | if var1.zero? 189 | return 0 190 | end 191 | 192 | p = 1048576-adc_P 193 | p = (((p<<31)-var2)*3125)/var1 194 | 195 | var1 = ((@dig_P9) * (p>>13) * (p>>13)) >> 25 196 | var2 = ((@dig_P8) * p) >> 19 197 | q24_8 = ((p + var1 + var2) >> 8) + ((@dig_P7)<<4) 198 | # convert Q24.8 to hPa 199 | q24_8 / 256.to_f / 100 200 | end 201 | 202 | 203 | def compensate_H(adc_H, t_fine) 204 | v_x1_u32r = (t_fine - (76800)) 205 | v_x1_u32r = (((((adc_H << 14) - ((@dig_H4) << 20)- ((@dig_H5) * v_x1_u32r)) + 206 | (16384)) >> 15) * (((((((v_x1_u32r * (@dig_H6)) >> 10) * (((v_x1_u32r * (@dig_H3)) >> 11) + (32768))) >> 10) + (2097152)) * (@dig_H2) + 8192) >> 14)); 207 | v_x1_u32r = (v_x1_u32r- (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * (@dig_H1)) >> 4)); 208 | v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); 209 | v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); 210 | 211 | q22_10 = (v_x1_u32r>>12) 212 | # Q22.10 to %RH 213 | q22_10 / 1024.to_f 214 | end 215 | end 216 | 217 | -------------------------------------------------------------------------------- /lib/i2c/device/bmp180.rb: -------------------------------------------------------------------------------- 1 | require 'i2c' 2 | 3 | # Implements the I2C-Device BMP085/BMP180 4 | # This code was inspired by https://github.com/adafruit/Adafruit_Python_BMP 5 | # 6 | # Datasheet: https://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf 7 | # 8 | # Currently this code was tested on a Banana Pi with a BMP185 device. It should work on a Raspberry or any other Linux with I2C-Dev 9 | # 10 | # ==Example 11 | # Using i2c-2 device (e.g. if you using a banana pi) 12 | # 13 | # bmp = I2CDevice::Bmp180.new(driver: I2CDevice::Driver::I2CDev.new("/dev/i2c-2"), mode: 0) 14 | # puts "#{bmp.read_temperature / 10.0}°C" 15 | # sleep 1 16 | # puts "#{bmp.read_pressure / 100.0}hPa abs" 17 | # sleep 1 18 | # m_above_sealevel = 500 # position realtive to sealevel in m 19 | # puts "#{bmp.read_sealevel_pressure(m_above_sealevel) / 100.0}hPa rel" 20 | # 21 | class I2CDevice::Bmp180 < I2CDevice 22 | # BMP085 default address. 23 | BMP085_I2CADDR = 0x77 24 | 25 | # Operating Modes 26 | BMP085_ULTRALOWPOWER = 0 27 | BMP085_STANDARD = 1 28 | BMP085_HIGHRES = 2 29 | BMP085_ULTRAHIGHRES = 3 30 | 31 | # BMP085 Registers 32 | BMP085_CAL_AC1 = 0xAA # R Calibration data (16 bits) 33 | BMP085_CAL_AC2 = 0xAC # R Calibration data (16 bits) 34 | BMP085_CAL_AC3 = 0xAE # R Calibration data (16 bits) 35 | BMP085_CAL_AC4 = 0xB0 # R Calibration data (16 bits) unsigned 36 | BMP085_CAL_AC5 = 0xB2 # R Calibration data (16 bits) unsigned 37 | BMP085_CAL_AC6 = 0xB4 # R Calibration data (16 bits) unsigned 38 | BMP085_CAL_B1 = 0xB6 # R Calibration data (16 bits) 39 | BMP085_CAL_B2 = 0xB8 # R Calibration data (16 bits) 40 | BMP085_CAL_MB = 0xBA # R Calibration data (16 bits) 41 | BMP085_CAL_MC = 0xBC # R Calibration data (16 bits) 42 | BMP085_CAL_MD = 0xBE # R Calibration data (16 bits) 43 | BMP085_CONTROL = 0xF4 44 | BMP085_TEMPDATA = 0xF6 45 | BMP085_PRESSUREDATA = 0xF6 46 | 47 | # Commands 48 | BMP085_READTEMPCMD = 0x2E 49 | BMP085_READPRESSURECMD = 0x34 50 | 51 | # initialize the device and read the calibration registers 52 | # 53 | # ==params 54 | # * args : hash defaults to {} 55 | # ** :mode : one of BMP085_ULTRALOWPOWER | BMP085_STANDARD | BMP085_HIGHRES | BMP085_ULTRAHIGHRES defaults to BMP085_STANDARD see datasheet for more information 56 | # ** :address : device address defaults to 0x77 57 | def initialize(args={}) 58 | @mode = args.delete(:mode) || BMP085_STANDARD 59 | args = { 60 | address: BMP085_I2CADDR 61 | }.merge(args) 62 | 63 | super args 64 | 65 | raise "Mode must be between #{BMP085_ULTRALOWPOWER} and #{BMP085_ULTRAHIGHRES}" unless [BMP085_ULTRALOWPOWER, BMP085_STANDARD, BMP085_HIGHRES, BMP085_ULTRAHIGHRES].include?(@mode) 66 | 67 | calibration 68 | end 69 | 70 | # read the current real temperature in 0.1°C 71 | def read_temperature 72 | return calc_real_temperature(read_raw_temperature) 73 | end 74 | 75 | # read the current relative pressure in Pa 76 | def read_pressure 77 | return calc_real_pressure(read_raw_temperature, read_raw_pressure) 78 | end 79 | 80 | # Read current temperature and realtive pressure 81 | # 82 | # ==return 83 | # * temperature in 0.1°C, pressure in Pa 84 | def read_temperature_and_pressure 85 | ut = read_raw_temperature 86 | up = read_raw_pressure 87 | 88 | return calc_real_temperature(ut), calc_real_pressure(ut, up) 89 | end 90 | 91 | # calculate the current pressure at sealevel from the current relative pressure and the gitven altitude 92 | # 93 | # ==params 94 | # * altitude : curren altitude above sealevel in m defaults to 0 95 | def read_sealevel_pressure(altitude = 0.0) 96 | pressure = read_pressure() 97 | return cacl_sealevel_pressure(pressure, altitude) 98 | end 99 | 100 | # calculate the current pressure at sealevel from the given relative pressure and the gitven altitude 101 | # 102 | # ==params 103 | # * altitude : curren altitude above sealevel in m 104 | # * pressure : current relative pressure in Pa 105 | def cacl_sealevel_pressure(pressure, altitude) 106 | return pressure.to_f / ((1.0 - altitude.to_f / 44330.0) ** 5.255) 107 | end 108 | 109 | # get the calibration values 110 | # 111 | # ==return 112 | # array of calibration data 113 | def get_cal 114 | return @cal_AC1, @cal_AC2, @cal_AC3, @cal_AC4, @cal_AC5, @cal_AC6, @cal_B1, @cal_B2, @cal_MB, @cal_MC, @cal_MD 115 | end 116 | 117 | private 118 | # read the current raw temperature value 119 | def read_raw_temperature 120 | i2cset(BMP085_CONTROL, BMP085_READTEMPCMD) 121 | sleep 0.005 122 | return i2cget(BMP085_TEMPDATA, 2).unpack('s>')[0] 123 | end 124 | 125 | # read the current raw pressure value 126 | def read_raw_pressure 127 | i2cset(BMP085_CONTROL, BMP085_READPRESSURECMD + (@mode << 6)) 128 | 129 | if @mode == BMP085_ULTRALOWPOWER 130 | sleep 0.005 131 | elsif @mode == BMP085_HIGHRES 132 | sleep 0.014 133 | elsif @mode == BMP085_ULTRAHIGHRES 134 | sleep 0.026 135 | else 136 | sleep 0.008 137 | end 138 | 139 | sleep 1 # safety for testing 140 | 141 | msb, lsb, xlsb = i2cget(BMP085_PRESSUREDATA, 3).unpack('C*') 142 | up = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - @mode) 143 | 144 | return up 145 | end 146 | 147 | # load the calibration registers into instance variables 148 | def calibration 149 | @cal_AC1, @cal_AC2, @cal_AC3, @cal_AC4, @cal_AC5, @cal_AC6, @cal_B1, @cal_B2, 150 | @cal_MB, @cal_MC, @cal_MD = i2cget(BMP085_CAL_AC1, 22).unpack('s>s>s>S>S>S>s>s>s>s>s>') 151 | end 152 | 153 | # calculate the read temperature using the calibration registers 154 | # 155 | # ==params 156 | # * ut : raw templerature value 157 | # ==return 158 | # true temperature in 0.1°C -> 150 = 15.0 °C 159 | def calc_real_temperature(ut) 160 | x1 = ((ut - @cal_AC6) * @cal_AC5) / 2**15 161 | x2 = (@cal_MC * 2**11) / (x1 + @cal_MD) 162 | b5 = x1 + x2 163 | t = (b5 + 8) / 2**4 164 | 165 | return t 166 | end 167 | 168 | # calculate the read pressure using the calibration registers 169 | # 170 | # ==params 171 | # * up : raw pressure value 172 | # ==return 173 | # true pressure in Pa 174 | def calc_real_pressure(ut, up) 175 | x1 = ((ut - @cal_AC6) * @cal_AC5) / 2**15 176 | x2 = (@cal_MC * 2**11) / (x1 + @cal_MD) 177 | b5 = x1 + x2 178 | 179 | # Pressure Calculations 180 | b6 = b5 - 4000 181 | x1 = (@cal_B2 * (b6 * b6) / 2**12) / 2**11 182 | x2 = (@cal_AC2 * b6) / 2**11 183 | x3 = x1 + x2 184 | b3 = (((@cal_AC1 * 4 + x3) << @mode) + 2) / 4 185 | 186 | x1 = (@cal_AC3 * b6) / 2**13 187 | x2 = (@cal_B1 * ((b6 * b6) / 2**12)) / 2**16 188 | x3 = ((x1 + x2) + 2) / 2**2 189 | b4 = (@cal_AC4 * (x3 + 32768)) / 2**15 190 | 191 | b7 = (up - b3) * (50000 >> @mode) 192 | 193 | if b7 < 0x80000000 194 | pr = (b7 * 2) / b4 195 | else 196 | pr = (b7 / b4) * 2 197 | end 198 | 199 | x1 = (pr / 2**8) * (pr / 2**8) 200 | x1 = (x1 * 3038) / 2**16 201 | x2 = (-7357 * pr) / 2**16 202 | pr += ((x1 + x2 + 3791) / 2**4) 203 | 204 | return pr 205 | end 206 | 207 | end 208 | -------------------------------------------------------------------------------- /lib/i2c/device/d6t-44l.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | 4 | class I2CDevice::D6T44L < I2CDevice 5 | class InvalidParityException < Exception; end 6 | 7 | def initialize(args={}) 8 | args[:address] = 0x0a 9 | super 10 | end 11 | 12 | def read_data 13 | data = i2cget(0x4c, 35) 14 | unless checkPEC(data, false) 15 | raise InvalidParityException 16 | end 17 | 18 | # PTAT はセンサ内部の参照温度データ 19 | ptat, *pixels = data[0..-2].unpack("v*") 20 | { 21 | :PTAT => ptat, 22 | :PIXELS => pixels.each_slice(4).to_a 23 | } 24 | end 25 | 26 | private 27 | def calc_crc(data) 28 | 8.times do 29 | tmp = data 30 | data = (data << 1) & 0xff 31 | if tmp & 0x80 != 0 32 | data ^= 0x07 33 | end 34 | end 35 | data 36 | end 37 | 38 | def checkPEC(data, userr=true) 39 | crc = 0 40 | if userr 41 | crc = calc_crc(0x14) 42 | crc = calc_crc(0x4c ^ crc) 43 | crc = calc_crc(0x15 ^ crc) 44 | else 45 | crc = calc_crc(0x15) 46 | end 47 | (data.size - 1).times do |i| 48 | crc = calc_crc(data[i].ord ^ crc) 49 | end 50 | data[data.size-1].ord == crc 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/i2c/device/grid-eye.rb: -------------------------------------------------------------------------------- 1 | 2 | require "i2c" 3 | 4 | class I2CDevice::GridEYE < I2CDevice 5 | POWER_CONTROL_REGISTER = 0x00 6 | POWER_CONTROL_NORMAL_MODE = 0x00 7 | POWER_CONTROL_SLEEP_MODE = 0x10 8 | POWER_CONTROL_STAND_BY_MODE_60SEC = 0x20 9 | POWER_CONTROL_STAND_BY_MODE_10SEC = 0x21 10 | 11 | RESET_REGISTER = 0x01 12 | RESET_FLAG_RESET = 0x30 13 | RESET_INITIAL_RESET = 0x3F 14 | 15 | FRAME_RATE_REGISTER = 0x02 16 | FRAME_RATE_1FPS = 0x01 17 | FRAME_RATE_10FPS = 0x00 18 | 19 | INTERRUPT_CONTROL_REGISTER = 0x03 20 | INTERRUPT_CONTROL_INTMOD_Pos = 1 21 | INTERRUPT_CONTROL_INTMOD_ABSOLUTE_VALUE = 1 22 | INTERRUPT_CONTROL_INTMOD_DIFFERENCE_VALUE = 0 23 | INTERRUPT_CONTROL_INTEN_Pos = 0 24 | INTERRUPT_CONTROL_INTEN_ACTIVE = 1 25 | INTERRUPT_CONTROL_INTEN_REACTIVE = 0 26 | 27 | STATUS_REGISTER = 0x04 28 | STATUS_OVF_THS_Pos = 3 29 | STATUS_OVF_IRS_Pos = 2 30 | STATUS_OVF_INTF = 1 31 | 32 | STATUS_CLEAR_REGISTER = 0x05 33 | STATUS_CLEAR_OVT_CLR_Pos = 3 34 | STATUS_CLEAR_OVS_CLR_Pos = 2 35 | STATUS_CLEAR_INTCLR = 1 36 | 37 | AVERAGE_REGISTER = 0x07 38 | AVERAGE_MAMOD_Pos = 5 39 | 40 | INTERRUPT_LEVEL_REGISTER = 0x08 41 | 42 | THERMISTOR_REGISTOR = 0x0E 43 | 44 | INTERRUPT_TABLE_REGISTER = 0x10 45 | 46 | TEMPERATURE_REGISTER = 0x80 47 | 48 | def initialize(args={}) 49 | args[:address] ||= 0x68 50 | super 51 | end 52 | 53 | def fixed_point(fixed, int_bits) 54 | msb = 11 55 | deno = (1<<(msb-int_bits)).to_f 56 | if (fixed & (1<<11)).zero? 57 | fixed / deno 58 | else 59 | -( ( (~fixed & 0xffff) + 1) / deno ) 60 | end 61 | end 62 | 63 | def set_frame_rate(fps) 64 | value = 65 | case fps 66 | when 1 67 | FRAME_RATE_1FPS 68 | when 10 69 | FRAME_RATE_10FPS 70 | else 71 | raise "Invalid FPS value" 72 | end 73 | i2cset(FRAME_RATE_REGISTER, value) 74 | end 75 | 76 | def set_average_mode(average) 77 | i2cset(0x1f, 0x50) 78 | i2cset(0x1f, 0x45) 79 | i2cset(0x1f, 0x57) 80 | i2cset(AVERAGE_REGISTER, average ? 0x20 : 0x00) 81 | i2cset(0x1f, 0x00) 82 | end 83 | 84 | def power_control(mode) 85 | i2cset(POWER_CONTROL_REGISTER, mode) 86 | end 87 | 88 | def initial_reset 89 | i2cset(RESET_REGISTER, RESET_INITIAL_RESET) 90 | end 91 | 92 | def flag_reset 93 | i2cset(RESET_REGISTER, RESET_FLAG_RESET) 94 | end 95 | 96 | def read_thermistor 97 | t = i2cget(THERMISTOR_REGISTOR, 2).unpack("v*")[0] 98 | fixed_point(t, 7) 99 | end 100 | 101 | def read_pixels 102 | i2cget(TEMPERATURE_REGISTER, 64*2).unpack("v*").map {|t| 103 | fixed_point(t, 9) 104 | }.each_slice(8).to_a 105 | end 106 | end 107 | 108 | 109 | -------------------------------------------------------------------------------- /lib/i2c/device/hd44780.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "i2c" 4 | 5 | # I2C interface with HD44780 compatible commands 6 | class I2CDevice::HD44780 < I2CDevice 7 | MAP = Hash[ 8 | [ 9 | "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚".split(//).map {|c| 10 | c.force_encoding(Encoding::BINARY) 11 | }, 12 | (0b10100001..0b11011111).map {|c| 13 | c.chr 14 | } 15 | ].transpose 16 | ] 17 | 18 | def initialize(args={}) 19 | super 20 | @lines = [] 21 | initialize_lcd 22 | end 23 | 24 | # Initialize LCD controller sequence 25 | # Display is cleared. 26 | def initialize_lcd 27 | function_set(1, 1, 0) 28 | sleep 4.1e-3 29 | function_set(1, 1, 0) 30 | sleep 100e-6 31 | function_set(1, 1, 0) 32 | function_set(1, 1, 0) 33 | display_on_off_control(1, 0, 0) 34 | clear 35 | end 36 | 37 | # line :: [Integer] Line number 38 | # str :: [String] Display string 39 | # force :: [true | false] Write data forcely. 40 | # 41 | # Note: This method keep previous put_line strings and does not write without change. 42 | # You must specify _force_ to override this behaviour. 43 | def put_line(line, str, force=false) 44 | str.force_encoding(Encoding::BINARY) 45 | str.gsub!(/#{MAP.keys.join('|')}/, MAP) 46 | 47 | str = "%- 16s" % str 48 | 49 | if force || str != @lines[line] 50 | # set ddram address 51 | set_ddram_address(0x40 * line) 52 | sleep 60e-6 53 | i2cset(*str.unpack("C*").map {|i| [0x80, i] }.flatten) 54 | sleep 60e-6 55 | end 56 | @lines[line] = str 57 | end 58 | 59 | # n :: [Integer] Character code. 60 | # array :: [Array[Integer]] Character data. 61 | # Usage: 62 | # lcd.define_character(0, [ 63 | # 0,1,1,1,0, 64 | # 1,0,0,0,1, 65 | # 1,1,0,1,1, 66 | # 1,0,1,0,1, 67 | # 1,1,0,1,1, 68 | # 1,0,0,0,1, 69 | # 1,0,0,0,1, 70 | # 0,1,1,1,0, 71 | # ]) 72 | def define_character(n, array) 73 | raise "n < 8" unless n < 8 74 | raise "array size must be 40 (5x8)" unless array.size == 40 75 | 76 | array = array.each_slice(5).map {|i| 77 | i.inject {|r,i| (r << 1) + i } 78 | } 79 | set_cgram_address(8 * n) 80 | sleep 60e-6 81 | i2cset(*array.map {|i| [0x80, i] }.flatten) 82 | sleep 60e-6 83 | end 84 | 85 | def clear_display 86 | @lines.clear 87 | i2cset(0, 0b00000001) 88 | sleep 2.16e-3 89 | end 90 | 91 | alias clear clear_display 92 | 93 | def return_home 94 | i2cset(0, 0b00000010) 95 | sleep 1.52e-3 96 | end 97 | 98 | # i_d :: [Integer] Increment or decrement 99 | # 0 :: Decrement 100 | # 1 :: Increment 101 | # s :: [Integer] Shift entire display 102 | # 0 :: Right 103 | # 1 :: Left 104 | def entry_mode_set(i_d, s) 105 | i2cset(0, 0b00000100 | (i_d<<1) | (s)) 106 | sleep 60e-6 107 | end 108 | 109 | # d :: [Integer] Set entire display on/off 110 | # 0 :: Off 111 | # 1 :: On 112 | # c :: [Integer] Cursor on/off 113 | # 0 :: Off 114 | # 1 :: On 115 | # b :: [Integer] Blink cursor 116 | # 0 :: Off 117 | # 1 :: On 118 | def display_on_off_control(d, c, b) 119 | i2cset(0, 0b00001000 | (d<<2) | (c<<1) | (b)) 120 | sleep 60e-6 121 | end 122 | 123 | # s_c :: [Integer] Cursor or display 124 | # 0 :: Cursor shift 125 | # 1 :: Display shift 126 | # r_l :: [Integer] Direction 127 | # 0 :: Left 128 | # 1 :: Right 129 | def cursor_or_display_shift(s_c, r_l) 130 | i2cset(0, 0b00010000 | (s_c<<3) | (r_l<<2)) 131 | sleep 60e-6 132 | end 133 | 134 | # dl :: [Integer] Data length 135 | # 0 :: 4bit 136 | # 1 :: 8bit 137 | # n :: [Integer] Number of display lines 138 | # 0 :: 1-line 139 | # 1 :: 2-line 140 | # f :: [Integer] Character font 141 | # 0 :: Normal 142 | # 1 :: Double font 143 | def function_set(dl, n, f) 144 | i2cset(0, 0b00100000 | (dl<<4) | (n<<3) | (f<<2)) 145 | sleep 60e-6 146 | end 147 | 148 | # address :: [Integer] CGRAM address 6-bit 149 | def set_cgram_address(address) 150 | address = address & 0b00111111 151 | i2cset(0, 0b01000000 | address) 152 | sleep 60e-6 153 | end 154 | 155 | # address :: [Integer] DDRAM address 7-bit 156 | def set_ddram_address(address) 157 | address = address & 0b01111111 158 | i2cset(0, 0b10000000 | address) 159 | sleep 60e-6 160 | end 161 | 162 | # Returns :: [Hash] Result 163 | # :busy :: [true | false] Busy flag 164 | # :address_counter :: [Integer] Current address count. 7-bit 165 | def read_busy_flag_and_address 166 | read = i2cget(0b01000000) 167 | { 168 | :busy => (read & 0b10000000) != 0, 169 | :address_counter => read & 0b01111111 170 | } 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /lib/i2c/device/hdc1000.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "i2c" 4 | 5 | # http://www.ti.com/product/HDC1000 6 | # A Digital humidity/temperature sensor 7 | 8 | class I2CDevice::HDC1000 < I2CDevice 9 | def initialize(args = {}) 10 | args = { 11 | address: 0x40 12 | }.merge(args) 13 | super args 14 | configuration 15 | end 16 | 17 | def configuration 18 | i2cset( 19 | 0x02, # Configuration register 20 | 0x10, # TRES 14bit 21 | 0x00 # HRES 14bit 22 | ) 23 | end 24 | 25 | def get_data 26 | i2cset(0x00) 27 | sleep 6.35e-3 + 6.5e-3 28 | raw = i2cget(nil, 4).unpack("C4") 29 | { 30 | temperature: calc_temperature(raw[0], raw[1]), 31 | humidity: calc_humidity(raw[2], raw[3]) 32 | } 33 | end 34 | 35 | def calc_temperature(d1, d2) 36 | ((d1<<8 | d2).to_f / 2**16 * 165) - 40 37 | end 38 | 39 | def calc_humidity(d1, d2) 40 | (d1<<8 | d2).to_f / 2**16 * 100 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/i2c/device/mpl115a2.rb: -------------------------------------------------------------------------------- 1 | 2 | require "i2c" 3 | 4 | class I2CDevice::MPL115A2 < I2CDevice 5 | def initialize(args={}) 6 | args[:address] = 0x60 7 | super 8 | coefficient = i2cget(0x04, 8).unpack("n*") 9 | 10 | @a0 = fixed_point(coefficient[0], 12) 11 | @b1 = fixed_point(coefficient[1], 2) 12 | @b2 = fixed_point(coefficient[2], 1) 13 | @c12 = fixed_point(coefficient[3], 0) / (1<<9) 14 | end 15 | 16 | def fixed_point(fixed, int_bits) 17 | msb = 15 18 | deno = (1<<(msb-int_bits)).to_f 19 | if (fixed & (1<<15)).zero? 20 | fixed / deno 21 | else 22 | -( ( (~fixed & 0xffff) + 1) / deno ) 23 | end 24 | end 25 | 26 | def calculate_hPa 27 | i2cset(0x12, 0x01) # CONVERT 28 | 29 | sleep 0.003 30 | 31 | data = i2cget(0x00, 4).unpack("n*") 32 | 33 | p_adc = (data[0]) >> 6 34 | t_adc = (data[1]) >> 6 35 | 36 | p_comp = @a0 + (@b1 + @c12 * t_adc) * p_adc + @b2 * t_adc 37 | hPa = p_comp * ( (1150 - 500) / 1023.0) + 500; 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/i2c/device/si70xx.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | class I2CDevice 3 | class SI70XX < I2CDevice 4 | 5 | ADDRESS = 0x40 6 | MEASURE_RH_HOLD = 0xE5 7 | MEASURE_TEMP_HOLD = 0xE3 8 | 9 | def self.address; ADDRESS; end 10 | def self.measure_rh_hold; MEASURE_RH_HOLD; end 11 | def self.measure_temp_hold; MEASURE_TEMP_HOLD; end 12 | 13 | 14 | def initialize(args={}) 15 | args[:address] ||= self.class.address 16 | super args 17 | @rh_cmd = self.class.measure_rh_hold 18 | @tmp_cmd = self.class.measure_temp_hold 19 | end 20 | 21 | 22 | def relative_humidity 23 | ( (125 * measure(@rh_cmd)) / 65535 ) - 6 24 | end 25 | alias_method :rh, :relative_humidity 26 | 27 | 28 | def temp 29 | ( (175.72 * measure(@temp_cmd)) / 65536 ) - 46.85 30 | end 31 | 32 | private 33 | 34 | def measure(cmd) 35 | msb, lsb = i2cget(cmd, 2).unpack("C2") 36 | (msb << 8) + lsb 37 | end 38 | 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/i2c/driver.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "i2c" 4 | 5 | module I2CDevice::Driver 6 | # Abstract class for I2CDevice::Driver 7 | class Base 8 | include I2CDevice::Driver 9 | end 10 | 11 | # Low-level method for i2cget 12 | # Driver must implement this. 13 | # address :: [Integer] 7-bit slave address without r/w bit. MSB is always 0. 14 | # data :: [Array[Integer]] Writing bytes array. 15 | # Returns :: [String] Wrote bytes 16 | def i2cget(address, param, length=1) 17 | raise NotImplementedError 18 | end 19 | 20 | # Low-level method for i2cset 21 | # Driver must implement this. 22 | # address :: [Integer] 7-bit slave address without r/w bit. MSB is always 0. 23 | # data :: [Array[Integer]] Writing bytes array. 24 | # Returns :: [String] Wrote bytes 25 | def i2cset(address, *data) 26 | raise NotImplementedError 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /lib/i2c/driver/gpio.rb: -------------------------------------------------------------------------------- 1 | require "i2c" 2 | require "i2c/driver" 3 | =begin 4 | Generic software I2C Driver based on /sys/class/gpio. 5 | THIS MODULE WORKS WITH VERY SLOW SPEED ABOUT JUST 1kHz (normaly 100kHz). 6 | =end 7 | 8 | class I2CDevice::Driver::GPIO < I2CDevice::Driver::Base 9 | @@DEBUG = false 10 | 11 | def self.export(pin) #:nodoc: 12 | File.open("/sys/class/gpio/export", "w") do |f| 13 | f.syswrite(pin) 14 | end 15 | end 16 | 17 | def self.unexport(pin) #:nodoc: 18 | File.open("/sys/class/gpio/unexport", "w") do |f| 19 | f.syswrite(pin) 20 | end 21 | end 22 | 23 | def self.direction(pin, direction) #:nodoc: 24 | # [:in, :out, :high, :low].include?(direction) or raise "direction must be :in, :out, :high or :low" 25 | File.open("/sys/class/gpio/gpio#{pin}/direction", "w") do |f| 26 | f.syswrite(direction) 27 | end 28 | end 29 | 30 | def self.read(pin) #:nodoc: 31 | File.open("/sys/class/gpio/gpio#{pin}/value", "r") do |f| 32 | f.sysread(1).to_i 33 | end 34 | end 35 | 36 | def self.write(pin, val) #:nodoc: 37 | File.open("/sys/class/gpio/gpio#{pin}/value", "w") do |f| 38 | f.syswrite(val ? "1" : "0") 39 | end 40 | end 41 | 42 | def self.finalizer(ports) #:nodoc: 43 | proc do 44 | ports.each do |pin| 45 | GPIO.unexport(pin) 46 | end 47 | end 48 | end 49 | 50 | # Pin-number of SDA 51 | attr_reader :sda 52 | # Pin-number of SCL 53 | attr_reader :scl 54 | # Clock speed in kHz 55 | attr_reader :speed 56 | 57 | # opts[:sda] :: [Integer] Pin-number of SDA 58 | # opts[:scl] :: [Integer] Pin-number of SCL 59 | # [ opts[:speed] = 1 ] :: [Integer] Clock speed in kHz 60 | def initialize(opts={}) 61 | @sda = opts[:sda] or raise "opts[:sda] = [gpio pin number] is required" 62 | @scl = opts[:scl] or raise "opts[:scl] = [gpio pin number] is required" 63 | @speed = opts[:speed] || 1 # kHz but insane 64 | @clock = 1.0 / (@speed * 1000) 65 | 66 | begin 67 | GPIO.export(@sda) 68 | GPIO.export(@scl) 69 | rescue Errno::EBUSY => e 70 | end 71 | ObjectSpace.define_finalizer(self, self.class.finalizer([@scl, @sda])) 72 | begin 73 | GPIO.direction(@sda, :high) 74 | GPIO.direction(@scl, :high) 75 | GPIO.direction(@sda, :in) 76 | GPIO.direction(@scl, :in) 77 | rescue Errno::EACCES => e # writing to gpio after export is failed in a while 78 | retry 79 | end 80 | end 81 | 82 | # Interface of I2CDevice::Driver 83 | def i2cget(address, param, length=1) 84 | ret = "" 85 | start_condition 86 | unless write( (address << 1) + 0) 87 | raise I2CDevice::I2CIOError, "Unknown slave device (address:#{address})" 88 | end 89 | write(param) 90 | stop_condition # AVR stucked with SCL low without this (Does not AVR support Sr condition?) 91 | start_condition 92 | unless write( (address << 1) + 1) 93 | raise I2CDevice::I2CIOError, "Unknown slave device (address:#{address})" 94 | end 95 | length.times do |n| 96 | ret << read(n != length - 1).chr 97 | end 98 | ret 99 | ensure 100 | stop_condition 101 | end 102 | 103 | # Interface of I2CDevice::Driver 104 | def i2cset(address, *data) 105 | sent = 0 106 | start_condition 107 | unless write( (address << 1) + 0) 108 | raise I2CDevice::I2CIOError, "Unknown slave device (address:#{address})" 109 | end 110 | data.each do |c| 111 | unless write(c) 112 | break 113 | end 114 | sent += 1 115 | end 116 | sent 117 | ensure 118 | stop_condition 119 | end 120 | 121 | private 122 | 123 | # Send start condition (or repeated start condition) 124 | # raise I2CDevice::I2CBUSBusy if SCL line is low 125 | def start_condition 126 | p :start_condition if @@DEBUG 127 | sleep @clock 128 | GPIO.direction(@sda, :in) 129 | GPIO.direction(@scl, :in) 130 | if GPIO.read(@scl) == 0 131 | raise I2CDevice::I2CBUSBusy, "BUS is busy" 132 | end 133 | 134 | sleep @clock / 2 135 | GPIO.direction(@scl, :high) 136 | sleep @clock / 2 137 | GPIO.direction(@sda, :low) 138 | sleep @clock 139 | end 140 | 141 | # Send stop condition. 142 | def stop_condition 143 | p :stop_condition if @@DEBUG 144 | GPIO.direction(@scl, :low) 145 | sleep @clock / 2 146 | GPIO.direction(@sda, :low) 147 | sleep @clock / 2 148 | GPIO.direction(@scl, :in) 149 | sleep @clock / 2 150 | GPIO.direction(@sda, :in) 151 | sleep @clock / 2 152 | end 153 | 154 | # Write one _byte_ to BUS. 155 | def write(byte) 156 | p [:write, byte] if @@DEBUG 157 | GPIO.direction(@scl, :low) 158 | sleep @clock 159 | 160 | 7.downto(0) do |n| 161 | GPIO.direction(@sda, byte[n] == 1 ? :high : :low) 162 | GPIO.direction(@scl, :in) 163 | until GPIO.read(@scl) == 1 164 | # clock streching 165 | sleep @clock 166 | end 167 | sleep @clock 168 | GPIO.direction(@scl, :low) 169 | GPIO.write(@sda, false) 170 | sleep @clock 171 | end 172 | 173 | GPIO.direction(@sda, :in) 174 | GPIO.direction(@scl, :in) 175 | sleep @clock / 2 176 | ack = GPIO.read(@sda) == 0 177 | sleep @clock / 2 178 | while GPIO.read(@scl) == 0 179 | sleep @clock 180 | end 181 | GPIO.direction(@scl, :low) 182 | ack 183 | end 184 | 185 | # Read one byte from BUS. 186 | # ack :: [true|flase] Send ack for this read. 187 | # Returns :: [Integer] Byte 188 | def read(ack=true) 189 | p [:read, ack] if @@DEBUG 190 | ret = 0 191 | 192 | GPIO.direction(@scl, :low) 193 | sleep @clock 194 | GPIO.direction(@sda, :in) 195 | 196 | 8.times do 197 | GPIO.direction(@scl, :in) 198 | sleep @clock / 2 199 | ret = (ret << 1) | GPIO.read(@sda) 200 | sleep @clock / 2 201 | GPIO.direction(@scl, :low) 202 | sleep @clock 203 | end 204 | 205 | GPIO.direction(@sda, ack ? :low : :high) 206 | 207 | GPIO.write(@scl, true) 208 | sleep @clock 209 | GPIO.write(@scl, false) 210 | sleep @clock 211 | ret 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /lib/i2c/driver/i2c-dev.rb: -------------------------------------------------------------------------------- 1 | 2 | require "i2c" 3 | require "i2c/driver" 4 | 5 | class I2CDevice::Driver::I2CDev < I2CDevice::Driver::Base 6 | # ioctl command 7 | # Ref. https://www.kernel.org/pub/linux/kernel/people/marcelo/linux-2.4/include/linux/i2c.h 8 | I2C_RETRIES = 0x0701 9 | I2C_TIMEOUT = 0x0702 10 | I2C_SLAVE = 0x0703 11 | I2C_SLAVE_FORCE = 0x0706 12 | I2C_TENBIT = 0x0704 13 | I2C_FUNCS = 0x0705 14 | I2C_RDWR = 0x0707 15 | I2C_SMBUS = 0x0720 16 | I2C_UDELAY = 0x0705 17 | I2C_MDELAY = 0x0706 18 | 19 | # This depends on /dev/i2c-* (i2c-dev) feature on Linux. You may load i2c-dev kernel module. 20 | # path :: [String] Path to /dev/i2c-* file. 21 | # force :: [Boolean] Force the driver to read or set values even if the device is in use. 22 | # This is dangerous, as it can seriously confuse the kernel driver in question. 23 | # It can also cause i2cget and i2cset to writ to the wrong register. 24 | # Use at your own risk and only if you know what you're doing. 25 | # 26 | # If _path_ is not specified, this method use Dir.glob("/dev/i2c-*").last for _path_ 27 | def initialize(path=nil, force=false) 28 | if path.nil? 29 | path = Dir.glob("/dev/i2c-*").sort.last 30 | end 31 | 32 | unless File.exist?(path) 33 | raise I2CDevice::I2CIOError, "/dev/i2c-0 is required" 34 | end 35 | 36 | @path = path 37 | @slave_command = force ? I2C_SLAVE_FORCE : I2C_SLAVE 38 | end 39 | 40 | # Interface of I2CDevice::Driver 41 | def i2cget(address, param, length) 42 | i2c = File.open(@path, "r+") 43 | i2c.ioctl(@slave_command, address) 44 | i2c.syswrite(param.chr) unless param.nil? 45 | ret = i2c.sysread(length) 46 | i2c.close 47 | ret 48 | rescue Errno::EIO => e 49 | raise I2CDevice::I2CIOError, e.message 50 | end 51 | 52 | # Interface of I2CDevice::Driver 53 | def i2cset(address, *data) 54 | i2c = File.open(@path, "r+") 55 | i2c.ioctl(@slave_command, address) 56 | i2c.syswrite(data.pack("C*")) 57 | i2c.close 58 | rescue Errno::EIO => e 59 | raise I2CDevice::I2CIOError, e.message 60 | end 61 | end 62 | 63 | -------------------------------------------------------------------------------- /spec/device/adt7410_spec.rb: -------------------------------------------------------------------------------- 1 | #!rspec 2 | 3 | 4 | $LOAD_PATH.unshift "lib", "." 5 | 6 | require "tempfile" 7 | 8 | require "i2c/device/adt7410" 9 | require "i2c/driver/i2c-dev" 10 | require "spec/mocki2cdevice" 11 | 12 | describe I2CDevice::ADT7410 do 13 | before do 14 | @mock = MockI2CDevice.new 15 | allow(File).to receive(:open) do 16 | @mock.open 17 | end 18 | @driver = I2CDevice::Driver::I2CDev.new(@mock.path) 19 | end 20 | 21 | describe "#calculate_temperature" do 22 | context "16bit" do 23 | it "should treat positive fractial value" do 24 | # status 25 | @mock.memory[0x02] = 0b00000000 26 | # temp 27 | @mock.memory[0x00] = 0b00000000 28 | @mock.memory[0x01] = 0b00000001 29 | 30 | device = I2CDevice::ADT7410.new(address: 0x50, driver: @driver) 31 | expect(device.read_configuration).to eq({ 32 | :fault_queue => 1, 33 | :ct_pin_polarity => false, 34 | :int_pin_polarity => false, 35 | :int_ct_mode => :interrupt_mode, 36 | :operation_mode => :continuous_conversion, 37 | :resolution => 16 38 | }) 39 | expect(device.calculate_temperature).to eq(0.0078125) 40 | end 41 | 42 | it "should treat negative value" do 43 | # status 44 | @mock.memory[0x02] = 0b00000000 45 | # temp 46 | @mock.memory[0x00] = 0b10000000 47 | @mock.memory[0x01] = 0b00000000 48 | 49 | device = I2CDevice::ADT7410.new(address: 0x50, driver: @driver) 50 | expect(device.calculate_temperature).to eq(-256) 51 | end 52 | end 53 | 54 | context "13bit" do 55 | it "should treat positive fractial value" do 56 | # status 57 | @mock.memory[0x02] = 0b00000000 58 | # temp 59 | @mock.memory[0x00] = 0b00000000 60 | @mock.memory[0x01] = 0b00001000 61 | 62 | device = I2CDevice::ADT7410.new(address: 0x50, driver: @driver) 63 | device.configuration({ 64 | resolution: 13, 65 | }) 66 | 67 | expect(device.calculate_temperature).to eq(0.0625) 68 | end 69 | 70 | it "should treat negative value" do 71 | # status 72 | @mock.memory[0x02] = 0b00000000 73 | # temp 74 | @mock.memory[0x00] = 0b11100100 75 | @mock.memory[0x01] = 0b10000000 76 | 77 | device = I2CDevice::ADT7410.new(address: 0x50, driver: @driver) 78 | device.configuration({ 79 | resolution: 13, 80 | }) 81 | 82 | expect(device.calculate_temperature).to eq(-55) 83 | end 84 | end 85 | end 86 | end 87 | 88 | -------------------------------------------------------------------------------- /spec/device/bmp180_spec.rb: -------------------------------------------------------------------------------- 1 | #!rspec 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "tempfile" 6 | 7 | require "i2c/device/bmp180" 8 | require "i2c/driver/i2c-dev" 9 | require "spec/mocki2cdevice" 10 | 11 | describe I2CDevice::Bmp180 do 12 | before do 13 | @mock = MockI2CDevice.new 14 | allow(File).to receive(:open) do 15 | @mock.open 16 | end 17 | 18 | (0x00..0xFF).each do |i| 19 | @mock.memory[i] =0x00 20 | end 21 | 22 | # Write example calibration data from datasheet 23 | @mock.memory[0xAA] = 0x01 24 | @mock.memory[0xAB] = 0x98 25 | @mock.memory[0xAC] = 0xFF 26 | @mock.memory[0xAD] = 0xB8 27 | @mock.memory[0xAE] = 0xC7 28 | @mock.memory[0xAF] = 0xD1 29 | @mock.memory[0xB0] = 0x7F 30 | @mock.memory[0xB1] = 0xE5 31 | @mock.memory[0xB2] = 0x7F 32 | @mock.memory[0xB3] = 0xF5 33 | @mock.memory[0xB4] = 0x5A 34 | @mock.memory[0xB5] = 0x71 35 | @mock.memory[0xB6] = 0x18 36 | @mock.memory[0xB7] = 0x2E 37 | @mock.memory[0xB8] = 0x00 38 | @mock.memory[0xB9] = 0x04 39 | @mock.memory[0xBA] = 0x80 40 | @mock.memory[0xBB] = 0x01 41 | @mock.memory[0xBC] = 0xDD 42 | @mock.memory[0xBD] = 0XF9 43 | @mock.memory[0xBE] = 0x0B 44 | @mock.memory[0xBF] = 0x34 45 | 46 | @driver = I2CDevice::Driver::I2CDev.new(@mock.path) 47 | end 48 | 49 | describe "do read and calculate temperature" do 50 | it "should reand and calculate temperature" do 51 | bmp = I2CDevice::Bmp180.new(driver: @driver) 52 | expect(bmp.get_cal).to eq([408, -72, -14383, 32741, 32757, 23153, 6190, 4, -32767, -8711, 2868]) 53 | @mock.memory[0xF6] = 0x6C 54 | @mock.memory[0xF7] = 0xFA 55 | 56 | expect(bmp.read_temperature).to eq(150) # Temperature 0.1C -> 15.0C 57 | expect(@mock.memory[0XF4]).to eq(0x2E) 58 | end 59 | end 60 | 61 | describe "do read and calculate pressure" do 62 | it "should reand and calculate pressure" do 63 | bmp = I2CDevice::Bmp180.new(driver: @driver) 64 | expect(bmp.get_cal).to eq([408, -72, -14383, 32741, 32757, 23153, 6190, 4, -32767, -8711, 2868]) 65 | @mock.memory[0xF6] = 0x5D 66 | @mock.memory[0xF7] = 0x33 67 | 68 | # expect(bmp.read_pressure).to eq(69964) # datasheet test at 15.0C 69 | 70 | # Using 0x5D, 0x33 as temp and pressure -> 63524Pa at -26.8C 71 | expect(bmp.read_pressure).to eq(63524) # pressure in Pa 72 | expect(@mock.memory[0xF4]).to eq(0x74) # 0x34 + (mode << 6) with mode = 1 73 | end 74 | end 75 | 76 | describe "do read and calculate relative pressure" do 77 | it "should reand and calculate relative pressure" do 78 | bmp = I2CDevice::Bmp180.new(driver: @driver) 79 | expect(bmp.get_cal).to eq([408, -72, -14383, 32741, 32757, 23153, 6190, 4, -32767, -8711, 2868]) 80 | @mock.memory[0xF6] = 0x5D 81 | @mock.memory[0xF7] = 0x33 82 | 83 | # at sea level 84 | expect(bmp.read_sealevel_pressure(0)).to eq(63524) # pressure in Pa 85 | # at 500m above sea level 86 | expect(bmp.read_sealevel_pressure(500).to_i).to eq(67425) # pressure in Pa 87 | end 88 | end 89 | 90 | end 91 | -------------------------------------------------------------------------------- /spec/device/hd44780_spec.rb: -------------------------------------------------------------------------------- 1 | #!rspec 2 | 3 | 4 | $LOAD_PATH.unshift "lib" 5 | 6 | require "tempfile" 7 | 8 | require "i2c/device/hd44780" 9 | require "i2c/driver/i2c-dev" 10 | 11 | describe I2CDevice::HD44780 do 12 | before do 13 | @i2cout = "" 14 | @i2cin = "" 15 | @ioctl = nil 16 | 17 | ioctl = proc do |cmd, arg| 18 | @ioctl = [ cmd, arg ] 19 | end 20 | 21 | syswrite = proc do |str| 22 | @i2cout << str 23 | end 24 | 25 | sysread = proc do |n| 26 | @i2cin 27 | end 28 | 29 | @temp = Tempfile.new("i2c") 30 | file = nil 31 | open = File.method(:open) 32 | allow(File).to receive(:open) do 33 | file = open.call(@temp.path, "r+") 34 | file.define_singleton_method(:ioctl) {|cmd,arg| ioctl.call(ioctl) } 35 | file.define_singleton_method(:syswrite) {|str| syswrite.call(str) } 36 | file.define_singleton_method(:sysread) {|n| sysread.call(n) } 37 | file 38 | end 39 | @driver = I2CDevice::Driver::I2CDev.new(@temp.path) 40 | end 41 | 42 | describe "#initialize_lcd" do 43 | it "should initialize lcd" do 44 | lcd = I2CDevice::HD44780.new(address: 0x10, driver: @driver) 45 | 46 | expect(@i2cout.unpack("C*")).to eq([ 47 | 0b00000000, 48 | 0b00111000, 49 | 0b00000000, 50 | 0b00111000, 51 | 0b00000000, 52 | 0b00111000, 53 | 0b00000000, 54 | 0b00111000, 55 | 0b00000000, 56 | 0b00001100, 57 | 0b00000000, 58 | 0b00000001, 59 | ]) 60 | end 61 | end 62 | 63 | describe "#put_line" do 64 | it "should be put_line 1/2" do 65 | lcd = I2CDevice::HD44780.new(address: 0x10, driver: @driver) 66 | 67 | @i2cout.clear 68 | 69 | lcd.put_line(0, "0123456789abcdef") 70 | 71 | expect(@i2cout.unpack("C*")).to eq([ 72 | # set_ddram_address 73 | 0b00000000, 0b10000000, 74 | 75 | # write commands 76 | 0b10000000, "0".ord, 77 | 0b10000000, "1".ord, 78 | 0b10000000, "2".ord, 79 | 0b10000000, "3".ord, 80 | 0b10000000, "4".ord, 81 | 0b10000000, "5".ord, 82 | 0b10000000, "6".ord, 83 | 0b10000000, "7".ord, 84 | 0b10000000, "8".ord, 85 | 0b10000000, "9".ord, 86 | 0b10000000, "a".ord, 87 | 0b10000000, "b".ord, 88 | 0b10000000, "c".ord, 89 | 0b10000000, "d".ord, 90 | 0b10000000, "e".ord, 91 | 0b10000000, "f".ord, 92 | ]) 93 | 94 | @i2cout.clear 95 | 96 | lcd.put_line(1, "0123456789abcdef") 97 | 98 | expect(@i2cout.unpack("C*")).to eq([ 99 | # set_ddram_address 100 | 0b00000000, 0b11000000, 101 | 102 | # write commands 103 | 0b10000000, "0".ord, 104 | 0b10000000, "1".ord, 105 | 0b10000000, "2".ord, 106 | 0b10000000, "3".ord, 107 | 0b10000000, "4".ord, 108 | 0b10000000, "5".ord, 109 | 0b10000000, "6".ord, 110 | 0b10000000, "7".ord, 111 | 0b10000000, "8".ord, 112 | 0b10000000, "9".ord, 113 | 0b10000000, "a".ord, 114 | 0b10000000, "b".ord, 115 | 0b10000000, "c".ord, 116 | 0b10000000, "d".ord, 117 | 0b10000000, "e".ord, 118 | 0b10000000, "f".ord, 119 | ]) 120 | end 121 | end 122 | 123 | describe "#define_character" do 124 | it "should define character" do 125 | lcd = I2CDevice::HD44780.new(address: 0x10, driver: @driver) 126 | 127 | @i2cout.clear 128 | 129 | lcd.define_character(0, [ 130 | 0,1,1,1,0, 131 | 1,0,0,0,1, 132 | 1,1,0,1,1, 133 | 1,0,1,0,1, 134 | 1,1,0,1,1, 135 | 1,0,0,0,1, 136 | 1,0,0,0,1, 137 | 0,1,1,1,0, 138 | ]) 139 | 140 | expect(@i2cout.unpack("C*")).to eq([ 141 | # set_cgram_address 142 | 0b00000000, 0b01000000, 143 | 144 | 0b10000000, 0b00001110, 145 | 0b10000000, 0b00010001, 146 | 0b10000000, 0b00011011, 147 | 0b10000000, 0b00010101, 148 | 0b10000000, 0b00011011, 149 | 0b10000000, 0b00010001, 150 | 0b10000000, 0b00010001, 151 | 0b10000000, 0b00001110, 152 | ]) 153 | end 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /spec/driver/gpio_spec.rb: -------------------------------------------------------------------------------- 1 | #!rspec 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "i2c" 6 | require "i2c/driver/gpio" 7 | require "tempfile" 8 | 9 | class GPIOTimeline 10 | attr_reader :events 11 | 12 | def initialize 13 | @timeline = {} 14 | @pins = [] 15 | @events = [] 16 | @defaults = {} 17 | @watchers = {} 18 | end 19 | 20 | def define(pin) 21 | unless @timeline.include?(pin) 22 | default(pin, 0) 23 | @timeline[pin] = [] 24 | @watchers[pin] = [] 25 | @pins << pin 26 | end 27 | end 28 | 29 | def add(pin, state) 30 | event = { 31 | time: Time.now, 32 | state: state, 33 | pin: pin, 34 | } 35 | @events << event 36 | @timeline[pin] << event 37 | @watchers[pin].each do |watcher| 38 | watcher[:count][:total] += 1 39 | watcher[:count][state.zero?? :low : :high] += 1 40 | watcher[:block].call(state, watcher[:count]) 41 | end 42 | end 43 | 44 | def mark(label, position=:top) 45 | event = { 46 | time: Time.now, 47 | label: label, 48 | position: position, 49 | } 50 | @events << event 51 | end 52 | 53 | def default(pin, state=nil) 54 | unless state.nil? 55 | @defaults[pin] = state 56 | end 57 | @defaults[pin] 58 | end 59 | 60 | def state(pin) 61 | @timeline[pin].last[:state] 62 | end 63 | 64 | def watch(pin, &block) 65 | watcher= { 66 | pin: pin, 67 | block: block, 68 | count: { 69 | total: 0, 70 | low: 0, 71 | high: 0, 72 | } 73 | } 74 | @watchers[pin] << watcher 75 | unwatch = lambda { 76 | @watchers[pin].delete(watcher) 77 | } 78 | end 79 | 80 | def dump 81 | require "cairo" 82 | 83 | width = 1240 84 | height = 300 85 | 86 | surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, width, height) 87 | context = Cairo::Context.new(surface) 88 | 89 | total = @events.last[:time] - @events.first[:time] 90 | start = @events.first[:time] 91 | px_per_sec = (width - 20) / total 92 | 93 | h = 50 94 | 95 | context.select_font_face("Lucida Console") 96 | context.line_width = 3 97 | 98 | @events.select {|i| i[:label] }.each do |event| 99 | n = event[:time] - start 100 | label = event[:label] 101 | context.set_source_rgb(0.7, 0.7, 0.7) 102 | context.stroke do 103 | context.move_to(n * px_per_sec, 0) 104 | context.line_to(n * px_per_sec, height) 105 | end 106 | context.set_source_rgb(0.3, 0.3, 0.3) 107 | context.move_to(n * px_per_sec + 5, event[:position] == :top ? 20 : height - 20) 108 | context.set_font_size(10) 109 | context.show_text(label.to_s) 110 | end 111 | 112 | context.set_source_rgb(0.3, 0.3, 0.3) 113 | context.line_width = 2 114 | 115 | pin_count = 1 116 | @pins.each do |pin| 117 | context.save do 118 | prev = 0 119 | context.translate(0, 100 * pin_count) 120 | 121 | context.move_to(0, prev) 122 | context.line_to(10, prev) 123 | 124 | context.translate(10, 0) 125 | 126 | @timeline[pin].each do |event| 127 | n = event[:time] - start 128 | 129 | context.line_to(n * px_per_sec, prev * -h) 130 | context.line_to(n * px_per_sec, event[:state] * -h) 131 | prev = event[:state] 132 | end 133 | 134 | context.line_to(width, prev * -h) 135 | 136 | context.stroke 137 | end 138 | pin_count += 1 139 | end 140 | 141 | surface.write_to_png("/tmp/dump.png") 142 | end 143 | end 144 | 145 | describe I2CDevice::Driver::GPIO do 146 | before do 147 | @timeline = timeline = GPIOTimeline.new 148 | 149 | I2CDevice::Driver::GPIO.define_singleton_method(:export) do |pin| 150 | timeline.define(pin) 151 | end 152 | 153 | I2CDevice::Driver::GPIO.define_singleton_method(:unexport) do |pin| 154 | end 155 | 156 | I2CDevice::Driver::GPIO.define_singleton_method(:direction) do |pin, direction| 157 | # p [:direction, pin] 158 | state = 1 159 | case direction 160 | when :in 161 | state = timeline.default(pin) # pulled-up 162 | when :out 163 | state = 0 164 | when :high 165 | state = 1 166 | when :low 167 | state = 0 168 | end 169 | timeline.add(pin, state) 170 | end 171 | 172 | I2CDevice::Driver::GPIO.define_singleton_method(:read) do |pin| 173 | timeline.state(pin) 174 | end 175 | 176 | I2CDevice::Driver::GPIO.define_singleton_method(:write) do |pin, val| 177 | timeline.add(pin, val ? 1 : 0) 178 | end 179 | 180 | @driver = I2CDevice::Driver::GPIO.new( 181 | sda: 23, 182 | scl: 24, 183 | speed: 1, 184 | ) 185 | @timeline.events.clear 186 | @timeline.default(@driver.scl, 1) 187 | @timeline.default(@driver.sda, 1) 188 | end 189 | 190 | describe "i2c protocol" do 191 | it "should set start condition correctly" do 192 | @driver.send(:start_condition) 193 | expect(@timeline.state(@driver.scl)).to be(1) 194 | expect(@timeline.state(@driver.sda)).to be(0) 195 | expect(@timeline.events.map {|i| [i[:pin], i[:state]] }).to eq([[@driver.sda, 1], [@driver.scl, 1], [@driver.scl, 1], [@driver.sda, 0]]) 196 | end 197 | 198 | it "should throw exception when bus is busy" do 199 | @timeline.default(@driver.scl, 0) 200 | expect { @driver.send(:start_condition) }.to raise_error(I2CDevice::I2CBUSBusy) 201 | end 202 | 203 | it "should set stop condition correctly" do 204 | @driver.send(:start_condition) 205 | 206 | @timeline.events.clear 207 | @driver.send(:stop_condition) 208 | expect(@timeline.events.map {|i| [i[:pin], i[:state]] }).to eq([ [@driver.scl, 0], [@driver.sda, 0], [@driver.scl, 1], [@driver.sda, 1] ]) 209 | expect(@timeline.state(@driver.scl)).to be(1) 210 | expect(@timeline.state(@driver.sda)).to be(1) 211 | end 212 | 213 | it "should write 1 byte correctly and receive nack" do 214 | @timeline.mark(:start) 215 | @driver.send(:start_condition) 216 | 217 | @timeline.mark(:write) 218 | ret = @driver.send(:write, 0b01010011) 219 | expect(@timeline.events.drop_while {|i| i[:label] != :write }.select {|i| i[:pin] == @driver.scl }.map {|i| i[:state] }).to eq([0, 1] * 9 + [0]) 220 | expect(ret).to be(false) 221 | expect(@timeline.state(@driver.scl)).to be(0) 222 | 223 | @timeline.mark(:stop) 224 | @driver.send(:stop_condition) 225 | end 226 | 227 | it "should write 1 byte correctly and receive ack" do 228 | @timeline.mark(:start) 229 | @driver.send(:start_condition) 230 | 231 | unwatch = @timeline.watch(@driver.scl) do |state, count| 232 | case 233 | when count[:high] == 8 && state == 0 234 | # return ack 235 | @timeline.default(@driver.sda, 0) 236 | when count[:high] == 9 && state == 1 237 | @timeline.mark("ack") 238 | when count[:high] == 9 && state == 0 239 | @timeline.default(@driver.sda, 1) 240 | unwatch.call 241 | end 242 | end 243 | 244 | @timeline.mark(:write) 245 | ret = @driver.send(:write, 0b11111111) 246 | expect(@timeline.events.drop_while {|i| i[:label] != :write}.select {|i| i[:pin] == @driver.scl }.map {|i| i[:state] }).to eq([0, 1] * 9 + [0]) 247 | expect(ret).to be(true) 248 | expect(@timeline.state(@driver.scl)).to be(0) 249 | 250 | @timeline.mark(:stop) 251 | @driver.send(:stop_condition) 252 | end 253 | 254 | it "should read 1 byte correctly and return ack" do 255 | @timeline.mark(:start) 256 | @driver.send(:start_condition) 257 | 258 | send = 0b00000000 259 | unwatch = @timeline.watch(@driver.scl) do |state, count| 260 | case 261 | when count[:high] < 8 && state == 0 262 | # send bit 263 | bit = send[ 7 - count[:high] ] 264 | @timeline.default(@driver.sda, bit) 265 | @timeline.add(@driver.sda, bit) 266 | when count[:high] == 9 && state == 1 267 | # read ack 268 | @timeline.mark(:ack) 269 | expect(@timeline.state(@driver.sda)).to be(0) 270 | unwatch.call 271 | @timeline.default(@driver.sda, 1) 272 | end 273 | end 274 | @timeline.mark(:read) 275 | ret = @driver.send(:read, true) 276 | expect(ret).to be(send) 277 | 278 | send = 0b01010101 279 | unwatch = @timeline.watch(@driver.scl) do |state, count| 280 | case 281 | when count[:high] < 8 && state == 0 282 | # send bit 283 | bit = send[ 7 - count[:high] ] 284 | @timeline.default(@driver.sda, bit) 285 | @timeline.add(@driver.sda, bit) 286 | when count[:high] <= 8 && state == 1 287 | @timeline.mark("#{8 - count[:high]}", :bottom) 288 | when count[:high] == 9 && state == 1 289 | # read ack 290 | @timeline.mark(:ack) 291 | expect(@timeline.state(@driver.sda)).to be(0) 292 | unwatch.call 293 | @timeline.default(@driver.sda, 1) 294 | end 295 | end 296 | ret = @driver.send(:read, true) 297 | expect(ret).to be(send) 298 | 299 | @timeline.mark(:stop) 300 | @driver.send(:stop_condition) 301 | end 302 | 303 | it "should read 1 byte correctly and return nack" do 304 | @timeline.mark(:start) 305 | @driver.send(:start_condition) 306 | 307 | send = 0b01010101 308 | unwatch = @timeline.watch(@driver.scl) do |state, count| 309 | case 310 | when count[:high] < 8 && state == 0 311 | # send bit 312 | bit = send[ 7 - count[:high] ] 313 | @timeline.default(@driver.sda, bit) 314 | @timeline.add(@driver.sda, bit) 315 | when count[:high] == 9 && state == 1 316 | # read ack 317 | @timeline.mark(:nack) 318 | expect(@timeline.state(@driver.sda)).to be(1) 319 | unwatch.call 320 | @timeline.default(@driver.sda, 1) 321 | end 322 | end 323 | ret = @driver.send(:read, false) 324 | expect(ret).to be(send) 325 | 326 | @timeline.mark(:stop) 327 | @driver.send(:stop_condition) 328 | end 329 | end 330 | 331 | describe "i2c abstract interface:" do 332 | it "should initialize with sda, scl properties" do 333 | expect { I2CDevice::Driver::GPIO.new() }.to raise_error(/required/) 334 | expect { I2CDevice::Driver::GPIO.new(sda: 1) }.to raise_error(/required/) 335 | expect { I2CDevice::Driver::GPIO.new(sda: 1, scl: 2) }.not_to raise_error 336 | end 337 | 338 | context "unknown slave address:" do 339 | describe "i2cset" do 340 | it "should throw exception on unknown slave address" do 341 | expect { @driver.i2cset(0x20, 0x00) }.to raise_error(I2CDevice::I2CIOError) 342 | 343 | expect(@timeline.state(@driver.scl)).to be(1) 344 | expect(@timeline.state(@driver.sda)).to be(1) 345 | end 346 | end 347 | 348 | describe "i2cget" do 349 | it "should throw exception on unknown slave address" do 350 | expect { @driver.i2cget(0x20, 0x00) }.to raise_error(I2CDevice::I2CIOError) 351 | 352 | expect(@timeline.state(@driver.scl)).to be(1) 353 | expect(@timeline.state(@driver.sda)).to be(1) 354 | end 355 | end 356 | end 357 | 358 | context "valid slave address:" do 359 | before do 360 | @status = :stop 361 | @received = [] 362 | @memory = [0x00] * 5 363 | @max_receive = 3 364 | 365 | unwatch_scl = nil 366 | @timeline.watch(@driver.sda) do |state, count| 367 | case 368 | when @timeline.state(@driver.scl) == 1 && state == 0 369 | @status = :start 370 | @timeline.mark(@status) 371 | address = 0 372 | data = 0 373 | rw = nil 374 | read_address = 0 375 | ack = 1 376 | unwatch_scl.call if unwatch_scl 377 | unwatch_scl = @timeline.watch(@driver.scl) do |state, count| 378 | # p [@status, state, count] 379 | case @status 380 | when :start 381 | case 382 | when state == 1 && count[:high] < 8 383 | @timeline.mark(8 - count[:high], :bottom) 384 | address = (address << 1) | @timeline.state(@driver.sda) 385 | when state == 1 && count[:high] == 8 386 | @timeline.mark('rw', :bottom) 387 | rw = @timeline.state(@driver.sda) 388 | # p " 0b%08b == 0b%08b %02x" % [0x20, address, address] 389 | when state == 0 && count[:high] == 8 390 | if address == 0x20 391 | # ack 392 | @timeline.default(@driver.sda, 0) 393 | else 394 | @status = :unkown 395 | @timeline.mark(@status) 396 | end 397 | when state == 1 && count[:high] == 9 398 | @timeline.mark('ack') 399 | when state == 0 && count[:high] == 9 400 | # reset 401 | count[:high] = 0 402 | count[:low] = 0 403 | @timeline.default(@driver.sda, 1) 404 | if rw.zero? 405 | @status = :write 406 | else 407 | @status = :read 408 | read_address = @received[0] 409 | end 410 | end 411 | when :write 412 | case 413 | when state == 1 && count[:high] <= 8 414 | @timeline.mark(8 - count[:high], :bottom) 415 | data = (data << 1) | @timeline.state(@driver.sda) 416 | when state == 0 && count[:high] == 8 417 | if @received.size < @max_receive 418 | @received << data 419 | # ack 420 | @timeline.default(@driver.sda, 0) 421 | end 422 | when state == 1 && count[:high] == 9 423 | @timeline.mark(@received.size <= @max_receive ? 'ack' : 'nack') 424 | when state == 0 && count[:high] == 9 425 | # reset 426 | data = 0 427 | count[:high] = 0 428 | count[:low] = 0 429 | @timeline.default(@driver.sda, 1) 430 | unless @received.size <= @max_receive 431 | @status = :stop 432 | end 433 | end 434 | when :read 435 | case 436 | when state == 0 && count[:high] < 8 437 | # send bit 438 | bit = @memory[read_address][ 7 - count[:high] ] 439 | @timeline.default(@driver.sda, bit) 440 | @timeline.add(@driver.sda, bit) 441 | when state == 1 && count[:high] <= 8 442 | @timeline.mark(8 - count[:high], :bottom) 443 | when state == 0 && count[:high] == 8 444 | @timeline.default(@driver.sda, 1) 445 | when state == 1 && count[:high] == 9 446 | ack = @timeline.state(@driver.sda) 447 | if ack == 0 448 | @timeline.mark("ack") 449 | else 450 | @timeline.mark("nack") 451 | @status = :stop 452 | end 453 | when state == 0 && count[:high] == 9 454 | read_address += 1 455 | count[:high] = 0 456 | count[:low] = 0 457 | end 458 | end 459 | end 460 | when @timeline.state(@driver.scl) == 1 && state == 1 461 | @status = :stop 462 | @timeline.mark(@status) 463 | unwatch_scl.call 464 | end 465 | end 466 | end 467 | 468 | describe "i2cset" do 469 | it "should works successfully" do 470 | wrote = @driver.i2cset(0x20, 0x0f) 471 | expect(wrote).to be(1) 472 | expect(@received).to eq([0x0f]) 473 | 474 | expect(@timeline.state(@driver.scl)).to be(1) 475 | expect(@timeline.state(@driver.sda)).to be(1) 476 | end 477 | 478 | it "should write until nack" do 479 | @max_receive = 3 480 | wrote = @driver.i2cset(0x20, 0x01, 0x02, 0x03, 0x04, 0x05) 481 | expect(wrote).to be(3) 482 | expect(@received).to eq([0x01, 0x02, 0x03]) 483 | 484 | expect(@timeline.state(@driver.scl)).to be(1) 485 | expect(@timeline.state(@driver.sda)).to be(1) 486 | end 487 | end 488 | 489 | describe "i2cget" do 490 | it "should works successfully" do 491 | @max_receive = 1 492 | @memory = (0..4).to_a 493 | got = @driver.i2cget(0x20, 0x01) 494 | expect(got).to eq("\x01") 495 | 496 | expect(@timeline.state(@driver.scl)).to be(1) 497 | expect(@timeline.state(@driver.sda)).to be(1) 498 | end 499 | 500 | it "should works with length argument" do 501 | @max_receive = 1 502 | @memory = (0..4).to_a 503 | got = @driver.i2cget(0x20, 0x01, 3) 504 | expect(got).to eq("\x01\x02\x03") 505 | 506 | expect(@timeline.state(@driver.scl)).to be(1) 507 | expect(@timeline.state(@driver.sda)).to be(1) 508 | 509 | @timeline.dump 510 | end 511 | end 512 | end 513 | end 514 | end 515 | -------------------------------------------------------------------------------- /spec/i2cdevice_spec.rb: -------------------------------------------------------------------------------- 1 | #!rspec 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "i2c" 6 | require "i2c/driver/i2c-dev" 7 | require "tempfile" 8 | 9 | describe I2CDevice do 10 | before do 11 | @i2cout = "" 12 | @i2cin = "" 13 | @ioctl = nil 14 | 15 | ioctl = proc do |cmd, arg| 16 | @ioctl = [ cmd, arg ] 17 | end 18 | 19 | syswrite = proc do |str| 20 | @i2cout << str 21 | end 22 | 23 | sysread = proc do |n| 24 | @i2cin 25 | end 26 | 27 | @temp = Tempfile.new("i2c") 28 | file = nil 29 | open = File.method(:open) 30 | allow(File).to receive(:open) do 31 | file = open.call(@temp.path, "r+") 32 | file.define_singleton_method(:ioctl) {|cmd,arg| ioctl.call(cmd, arg) } 33 | file.define_singleton_method(:syswrite) {|str| syswrite.call(str) } 34 | file.define_singleton_method(:sysread) {|n| sysread.call(n) } 35 | file 36 | end 37 | 38 | @driver = I2CDevice::Driver::I2CDev.new(@temp.path) 39 | end 40 | 41 | describe "#i2cset" do 42 | it "should be write 1 byte" do 43 | i2c = I2CDevice.new(address: 0x10, driver: @driver) 44 | 45 | i2c.i2cset(0x00) 46 | 47 | expect(@ioctl).to eq([ I2CDevice::Driver::I2CDev::I2C_SLAVE, 0x10 ]) 48 | expect(@i2cout).to eq("\x00") 49 | end 50 | 51 | it "should be write multi bytes" do 52 | i2c = I2CDevice.new(address: 0x10, driver: @driver) 53 | 54 | i2c.i2cset(0x00, 0x01, 0x02) 55 | 56 | expect(@ioctl).to eq([ I2CDevice::Driver::I2CDev::I2C_SLAVE, 0x10 ]) 57 | expect(@i2cout).to eq("\x00\x01\x02") 58 | end 59 | end 60 | 61 | describe "#i2cget" do 62 | it "should be read 1 byte" do 63 | i2c = I2CDevice.new(address: 0x10, driver: @driver) 64 | 65 | @i2cin = "\x01" 66 | 67 | ret = i2c.i2cget(0x00) 68 | 69 | expect(ret).to eq("\x01") 70 | 71 | expect(@ioctl).to eq([ I2CDevice::Driver::I2CDev::I2C_SLAVE, 0x10 ]) 72 | expect(@i2cout).to eq("\x00") 73 | end 74 | 75 | it "should be read multi byte" do 76 | i2c = I2CDevice.new(address: 0x10, driver: @driver) 77 | 78 | @i2cin = "\x01\x02\x03" 79 | 80 | ret = i2c.i2cget(0x00) 81 | 82 | expect(ret).to eq("\x01\x02\x03") 83 | 84 | expect(@ioctl).to eq([ I2CDevice::Driver::I2CDev::I2C_SLAVE, 0x10 ]) 85 | expect(@i2cout).to eq("\x00") 86 | end 87 | end 88 | end 89 | 90 | -------------------------------------------------------------------------------- /spec/mocki2cdevice.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | class MockI2CDevice 4 | attr_reader :memory 5 | attr_reader :ioctl 6 | attr_reader :state 7 | 8 | def initialize 9 | @temp = Tempfile.new("i2c") 10 | @ioctl = [] 11 | @memory = [ 0 ] 12 | @address = nil 13 | @state = nil 14 | end 15 | 16 | def path 17 | @temp.path 18 | end 19 | 20 | def ioctl(cmd, arg) 21 | @ioctl = [cmd, arg] 22 | self 23 | end 24 | 25 | def open 26 | @address = nil 27 | @state = :init 28 | self 29 | end 30 | 31 | def close 32 | @address = nil 33 | @state = nil 34 | self 35 | end 36 | 37 | def syswrite(buf) 38 | buf.unpack("C*").each do |c| 39 | case @state 40 | when :init 41 | # p "@address = 0x%02x" % c 42 | @address = c 43 | @state = :wait 44 | when :wait 45 | # p "@memory[0x%02x] = 0b%08b" % [@address, c] 46 | @memory[@address] = c 47 | @address += 1 48 | end 49 | end 50 | end 51 | 52 | def sysread(size) 53 | ret = [] 54 | case @state 55 | when :init 56 | raise "Invalid State" 57 | when :wait 58 | size.times do 59 | ret << @memory[@address] 60 | @address += 1 61 | end 62 | end 63 | ret.pack("C*") 64 | end 65 | end 66 | 67 | 68 | -------------------------------------------------------------------------------- /xt/acm1602ni.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "i2c/device/acm1602ni" 6 | 7 | lcd = ACM1602NI.new 8 | 9 | lcd.define_character(0, [ 10 | 1,0,1,0,1, 11 | 1,0,1,0,1, 12 | 1,0,1,0,1, 13 | 1,0,1,0,1, 14 | 1,0,1,0,1, 15 | 1,0,1,0,1, 16 | 1,0,1,0,1, 17 | 1,0,1,0,1, 18 | ]) 19 | 20 | lcd.put_line(0, "1234567890abcdef") 21 | lcd.put_line(1, "\x00" * 16) 22 | 23 | -------------------------------------------------------------------------------- /xt/aqm0802a.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "i2c/device/aqm0802" 4 | 5 | aqm0802 = I2CDevice::AQM0802A.new 6 | aqm0802.put_line(0, 'helloォ') 7 | aqm0802.put_line(1, ' world!') 8 | -------------------------------------------------------------------------------- /xt/driver-gpio.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "i2c/device/mpl115a2" 6 | require "i2c/driver/gpio" 7 | 8 | # I2CDevice::Driver::GPIO.class_variable_set(:@@DEBUG, true) 9 | 10 | driver = I2CDevice::Driver::GPIO.new( 11 | sda: 23, # pin 16 in raspberry pi 12 | scl: 24, # pin 18 in raspberry pi 13 | ) 14 | 15 | 16 | #device = I2CDevice.new(address: 0x20, driver: driver) 17 | #loop do 18 | # seq = Array.new(8) { rand(0x10) } 19 | # seq = (0..7).map {|n| 0x10 + n } 20 | # seq = (1..10).to_a 21 | # p seq 22 | # p device.i2cset(0x00, *seq) 23 | # # p device.i2cget(0x00, 8).unpack("C*") 24 | # 25 | # exit 1 26 | # sleep 1 27 | #end 28 | 29 | mpl = I2CDevice::MPL115A2.new(driver: driver) 30 | loop do 31 | p mpl.calculate_hPa 32 | sleep 1 33 | end 34 | 35 | -------------------------------------------------------------------------------- /xt/hdc1000.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'i2c/device/hdc1000' 3 | require 'pp' 4 | 5 | hdc = I2CDevice::HDC1000.new 6 | pp hdc.get_data 7 | -------------------------------------------------------------------------------- /xt/i2cdetect.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "optparse" 6 | 7 | require "i2c" 8 | require "i2c/driver/i2c-dev" 9 | require "i2c/driver/gpio" 10 | 11 | # Address List 12 | # .... ... r/w 13 | # 0000 000 0 -> general call address 14 | # 0000 000 1 -> start byte 15 | # 0000 001 x -> cbus address 16 | # 0000 010 x -> reserved for other protocols 17 | # 0000 011 x -> reserved for future 18 | # 0000 1xx x -> Hs-mod master code 19 | # 1111 1xx x -> device id 20 | # 1111 0xx x -> 10-bit addressing 21 | 22 | i2c_devices = Dir.glob("/dev/i2c-*") 23 | is_gpio_supported = Dir.glob("/sys/class/gpio") 24 | 25 | driver = nil 26 | 27 | OptionParser.new do |opt| 28 | opt.banner = <<-EOB.gsub(/^\t+/, "") 29 | Usage: #{$0} [opts] 30 | EOB 31 | 32 | opt.separator "" 33 | opt.separator "Options:" 34 | opt.on("-d BACKEND", "--driver BACKEND", [ i2c_devices.empty?? "" : "i2cdev,[path]", is_gpio_supported ? "gpio,[sda pin],[scl pin]" : "" ].join("\t")) do |backend| 35 | backend = backend.split(/,/) 36 | case backend.shift 37 | when "i2cdev" 38 | driver = I2CDevice::Driver::I2CDev.new(backend.shift) 39 | when "gpio" 40 | driver = I2CDevice::Driver::GPIO.new(sda: backend[0], scl: backend[1]) 41 | end 42 | end 43 | i2c_devices.each do |path| 44 | opt.separator "\t\t\t--backend i2cdev,#{path}" 45 | end 46 | opt.separator "\t\t\t--backend gpio,23,24" 47 | 48 | opt.parse!(ARGV) 49 | end 50 | 51 | puts "Using %p" % driver if driver 52 | 53 | puts "% 2s %s" % ["", 16.times.map {|n| "% 2x" % n }.join(" ") ] 54 | 8.times do |high| 55 | puts "%02x: %s" % [ high << 4, 16.times.map {|low| 56 | a = high << 4 | low 57 | if (a >> 3) == 0b1111 || (a >> 3) == 0b0000 58 | " " 59 | else 60 | d = I2CDevice.new(address: a, driver: driver) 61 | begin 62 | d.i2cset(0x00) 63 | "%02x" % a 64 | rescue I2CDevice::I2CIOError => e 65 | "--" 66 | end 67 | end 68 | }.join(" ") ] 69 | end 70 | -------------------------------------------------------------------------------- /xt/mpl115a2.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift "lib" 4 | 5 | require "i2c/device/mpl115a2" 6 | 7 | mpl = I2CDevice::MPL115A2.new 8 | 9 | loop do 10 | p mpl.calculate_hPa 11 | end 12 | 13 | --------------------------------------------------------------------------------