i2c 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/i2c.rb +14 -0
- data/lib/i2c/backends/i2c-dev.rb +71 -0
- data/lib/i2c/drivers/mcp17026.rb +124 -0
- data/lib/i2c/i2c.rb +35 -0
- data/rules/88-i2c.rules +1 -0
- data/test/mcp17026_spec.rb +90 -0
- metadata +50 -0
data/lib/i2c.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# I2C gem setup.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2012 Christoph Anderegg <christoph@christoph-anderegg.ch>
|
5
|
+
# This file may be distributed under the terms of the GNU General Public
|
6
|
+
# License Version 2.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'i2c/i2c.rb'
|
10
|
+
require 'i2c/backends/i2c-dev.rb'
|
11
|
+
require 'i2c/drivers/mcp17026.rb'
|
12
|
+
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# I2C - Linux i2c-dev backend.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2012 Christoph Anderegg <christoph@christoph-anderegg.ch>
|
5
|
+
# Copyright (c) 2008 Jonas Bähr, jonas.baehr@fs.ei.tum.de
|
6
|
+
# This file may be distributed under the terms of the GNU General Public
|
7
|
+
# License Version 2.
|
8
|
+
#
|
9
|
+
module I2C
|
10
|
+
class Dev
|
11
|
+
# see i2c-dev.h
|
12
|
+
I2C_SLAVE = 0x0703
|
13
|
+
|
14
|
+
def self.create(device_path)
|
15
|
+
raise Errno::ENOENT, "Device #{device_path} not found." unless File.exists?(device_path)
|
16
|
+
@instances ||= Hash.new
|
17
|
+
@instances[device_path] = Dev.new(device_path) unless @instances.has_key?(device_path)
|
18
|
+
@instances[device_path]
|
19
|
+
end
|
20
|
+
|
21
|
+
# sends every param, begining with +params[0]+
|
22
|
+
# If the current param is a Fixnum, it is treated as one byte.
|
23
|
+
# If the param is a String, this string will be send byte by byte.
|
24
|
+
# You can use Array#pack to create a string from an array
|
25
|
+
# For Fixnum there is a convinient function to_short which transforms
|
26
|
+
# the number to a string this way: 12345.to_short == [12345].pack("s")
|
27
|
+
def write(address, *params)
|
28
|
+
data = String.new
|
29
|
+
data.force_encoding("US-ASCII")
|
30
|
+
params.each do |value|
|
31
|
+
data << value
|
32
|
+
end
|
33
|
+
@device.ioctl(I2C_SLAVE, address)
|
34
|
+
@device.syswrite(data)
|
35
|
+
end
|
36
|
+
|
37
|
+
# this sends *params as the write function and then tries to read
|
38
|
+
# +size+ bytes. The result is a String which can be treated with
|
39
|
+
# String#unpack afterwards
|
40
|
+
def read(address, size, *params)
|
41
|
+
ret = ""
|
42
|
+
write(address, *params)
|
43
|
+
ret = @device.sysread(size)
|
44
|
+
return ret
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def initialize(device_path)
|
49
|
+
@device = File.new(device_path, 'r+')
|
50
|
+
# change the sys* functions of the file object to meet our requirements
|
51
|
+
class << @device
|
52
|
+
alias :syswrite_orig :syswrite
|
53
|
+
def syswrite(var)
|
54
|
+
begin
|
55
|
+
syswrite_orig var
|
56
|
+
rescue Errno::EREMOTEIO
|
57
|
+
raise AckError, "No acknowledge received"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
alias :sysread_orig :sysread
|
61
|
+
def sysread(var)
|
62
|
+
begin
|
63
|
+
sysread_orig var
|
64
|
+
rescue Errno::EREMOTEIO
|
65
|
+
raise AckError, "No acknowledge received"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end # class
|
69
|
+
end # initialize
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# I2C IO-Expander driver
|
3
|
+
# for the MCP23017 16-bit IO-Expander.
|
4
|
+
#
|
5
|
+
# The interface is compatible to the interface
|
6
|
+
# of the WiringPi gem. PWM is not supported though.
|
7
|
+
#
|
8
|
+
# Copyright (c) 2012 Christoph Anderegg <christoph@christoph-anderegg.ch>
|
9
|
+
# This file may be distributed under the terms of the GNU General Public
|
10
|
+
# License Version 2.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'i2c/i2c.rb'
|
14
|
+
|
15
|
+
# Constants for mode()
|
16
|
+
INPUT = 1
|
17
|
+
OUTPUT = 0
|
18
|
+
|
19
|
+
# Constants for write()
|
20
|
+
HIGH = 1
|
21
|
+
LOW = 0
|
22
|
+
|
23
|
+
module I2C
|
24
|
+
module Drivers
|
25
|
+
class MCP17026
|
26
|
+
# Registers
|
27
|
+
IODIRA = 0x00
|
28
|
+
IODIRB = 0x01
|
29
|
+
GPIOA = 0x12
|
30
|
+
GPIOB = 0x13
|
31
|
+
|
32
|
+
# Creates an instance representing exactly one
|
33
|
+
# MCP17026 on one I2C-bus.
|
34
|
+
#
|
35
|
+
# device: I2C-device file (usually /dev/i2c-0).
|
36
|
+
# Or an intantiated io class that supports
|
37
|
+
# the necessary operations (#read, #write
|
38
|
+
# and #ioctl).
|
39
|
+
# address: Device address on the bus.
|
40
|
+
def initialize(device, address)
|
41
|
+
if device.kind_of?(String)
|
42
|
+
@device = ::I2C.create(device)
|
43
|
+
else
|
44
|
+
[ :read, :write ].each do |m|
|
45
|
+
raise IncompatibleDeviceException,
|
46
|
+
"Missing #{m} method in device object." unless device.respond_to?(m)
|
47
|
+
end
|
48
|
+
@device = device
|
49
|
+
end
|
50
|
+
@address = address
|
51
|
+
|
52
|
+
@dir_a = 0xFF # Direction is input initially
|
53
|
+
@dir_b = 0xFF # Direction is input initially
|
54
|
+
@device.write(@address, IODIRA, @dir_a, @dir_b)
|
55
|
+
|
56
|
+
@data_a = 0xFF # Initial data
|
57
|
+
@data_b = 0xFF # Initial data
|
58
|
+
@data_a, @data_b = @device.read(@address, 2, GPIOA).unpack("CC")
|
59
|
+
end
|
60
|
+
|
61
|
+
def mode?(pin)
|
62
|
+
@dir_a, @dir_b = @device.read(@address, 2, IODIRA).unpack("CC")
|
63
|
+
dir = @dir_a
|
64
|
+
if 8 <= pin
|
65
|
+
dir = @dir_b
|
66
|
+
pin -= 8
|
67
|
+
end
|
68
|
+
return (dir >> pin) & 0x01
|
69
|
+
end
|
70
|
+
|
71
|
+
def mode(pin, pin_mode)
|
72
|
+
raise ArgumentError, "Pin not 0-15" unless (0..16).include?(pin)
|
73
|
+
raise ArgumentError, 'invalid value' unless [0,1].include?(pin_mode)
|
74
|
+
if 8 <= pin
|
75
|
+
@dir_b = set_bit_value(@dir_b, (pin-8), pin_mode)
|
76
|
+
else
|
77
|
+
@dir_a = set_bit_value(@dir_a, pin, pin_mode)
|
78
|
+
end
|
79
|
+
@device.write(@address, IODIRA, @dir_a, @dir_b)
|
80
|
+
end
|
81
|
+
|
82
|
+
def []=(pin, value)
|
83
|
+
raise ArgumentError, "Pin not 0-15" unless (0..15).include?(pin)
|
84
|
+
raise ArgumentError, 'invalid value' unless [0,1].include?(value)
|
85
|
+
if 8 <= pin
|
86
|
+
@data_b = set_bit_value(@data_b, (pin-8), value)
|
87
|
+
else
|
88
|
+
@data_a = set_bit_value(@data_a, pin, value)
|
89
|
+
end
|
90
|
+
@device.write(@address, GPIOA, @data_a, @data_b)
|
91
|
+
end
|
92
|
+
alias :write :[]=
|
93
|
+
|
94
|
+
def [](pin)
|
95
|
+
raise ArgumentError, "Pin not 0-15." unless (0..15).include?(pin)
|
96
|
+
@data_a, @data_b = @device.read(@address, 2, GPIOA).unpack("CC")
|
97
|
+
data = @data_a
|
98
|
+
if 8 <= pin
|
99
|
+
data = @data_b;
|
100
|
+
pin -= 8
|
101
|
+
end
|
102
|
+
return (data >> pin) & 0x01
|
103
|
+
end
|
104
|
+
alias :read :[]
|
105
|
+
|
106
|
+
private
|
107
|
+
def set_bit_value(byte, bit, value)
|
108
|
+
mask = 0x00
|
109
|
+
mask = (0x01 << bit)
|
110
|
+
case value
|
111
|
+
when 0
|
112
|
+
byte = (byte & ((~mask) & 0xFF)) & 0xFF
|
113
|
+
when 1
|
114
|
+
byte = (byte | mask) & 0xFF
|
115
|
+
else
|
116
|
+
raise ArgumentError, "Bit not 0-7."
|
117
|
+
end
|
118
|
+
# puts "Byte: (0x#{"%X" % byte}) 0b#{"%B" % byte}; " +
|
119
|
+
# "Mask: 0b#{"%B" % mask}; Bit: #{bit}; Value: #{value}"
|
120
|
+
byte
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/i2c/i2c.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Entry point to the I2C library.
|
3
|
+
#
|
4
|
+
# Essentially this requires the correct backend
|
5
|
+
# driver, right now this means the linux i2c-dev driver.
|
6
|
+
# This could be extended to do some system specific
|
7
|
+
#
|
8
|
+
# Copyright (c) 2012 Christoph Anderegg <christoph@christoph-anderegg.ch>
|
9
|
+
# Copyright (c) 2008 Jonas Bähr, jonas.baehr@fs.ei.tum.de
|
10
|
+
#
|
11
|
+
# This file may be distributed under the terms of the GNU General Public
|
12
|
+
# License Version 2.
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'i2c/backends/i2c-dev.rb'
|
16
|
+
|
17
|
+
module I2C
|
18
|
+
# some common error classes
|
19
|
+
class AckError < StandardError; end
|
20
|
+
|
21
|
+
# Returns an instance of the current backend
|
22
|
+
# driver.
|
23
|
+
#
|
24
|
+
# Is there a system agnostic way to do this?
|
25
|
+
#
|
26
|
+
# +bus_descriptor+ describes the bus to use. This is
|
27
|
+
# of course system specific. For the
|
28
|
+
# Linux i2c-dev driver this is the
|
29
|
+
# device file (e.g. /dev/i2c-0").
|
30
|
+
def self.create(bus_descriptor)
|
31
|
+
I2C::Dev.create(bus_descriptor)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
data/rules/88-i2c.rules
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
KERNEL=="i2c-[0-9]", GROUP="i2c", MODE="0660"
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'i2c'
|
2
|
+
#require 'mock/mock_i2c_io.rb'
|
3
|
+
|
4
|
+
class MockI2CIO
|
5
|
+
|
6
|
+
attr_reader :registers
|
7
|
+
attr_reader :last_address
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@registers = Hash.new
|
11
|
+
# Initialize according to data sheet
|
12
|
+
(0x00..0x01).each do |reg|
|
13
|
+
@registers[reg] = 0xFF
|
14
|
+
end
|
15
|
+
(0x02..0x15).each do |reg|
|
16
|
+
@registers[reg] = 0x00
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(address, *params)
|
21
|
+
@last_address = address
|
22
|
+
if params.count >= 1
|
23
|
+
reg_addr = params.shift
|
24
|
+
index = 0
|
25
|
+
params.each do |p|
|
26
|
+
@registers[reg_addr+index] = p
|
27
|
+
index += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def read(address, size, *params)
|
33
|
+
@last_address = address
|
34
|
+
answer = String.new
|
35
|
+
answer.force_encoding("US-ASCII")
|
36
|
+
if (size > 0) && (params.count >= 1)
|
37
|
+
reg_addr = params.shift
|
38
|
+
(0...size).each do |index|
|
39
|
+
answer << (@registers[reg_addr+index] & 0xFF)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
answer
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe I2C::Drivers::MCP17026, "#mode?" do
|
47
|
+
it "initially returns 1 for all pin modes" do
|
48
|
+
io = MockI2CIO.new
|
49
|
+
mcp17026 = I2C::Drivers::MCP17026.new(io, 0x20)
|
50
|
+
(0..15).each do |pin|
|
51
|
+
mcp17026.mode?(pin).should eq(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe I2C::Drivers::MCP17026, "#mode?" do
|
57
|
+
it "returns what has been set through #mode" do
|
58
|
+
io = MockI2CIO.new
|
59
|
+
mcp17026 = I2C::Drivers::MCP17026.new(io, 0x20)
|
60
|
+
(0..500).each do |pin|
|
61
|
+
pin = rand(16)
|
62
|
+
mode = rand(2)
|
63
|
+
mcp17026.mode(pin, mode)
|
64
|
+
mcp17026.mode?(pin).should eq(mode)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe I2C::Drivers::MCP17026, "#[]" do
|
70
|
+
it "initially returns 0 for all I/O pins" do
|
71
|
+
io = MockI2CIO.new
|
72
|
+
mcp17026 = I2C::Drivers::MCP17026.new(io, 0x20)
|
73
|
+
(0..15).each do |pin|
|
74
|
+
mcp17026[pin].should eq(0)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe I2C::Drivers::MCP17026, "#[]" do
|
80
|
+
it "returns what has been set through #[]=" do
|
81
|
+
io = MockI2CIO.new
|
82
|
+
mcp17026 = I2C::Drivers::MCP17026.new(io, 0x20)
|
83
|
+
(0..500).each do |pin|
|
84
|
+
pin = rand(16)
|
85
|
+
value = rand(2)
|
86
|
+
mcp17026[pin] = value
|
87
|
+
mcp17026[pin].should eq(value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: i2c
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christoph Anderegg
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-03 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Interface to Linux I2C (a.k.a. TWI) implementations.
|
15
|
+
email: christoph@christoph-anderegg.ch
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/i2c.rb
|
21
|
+
- lib/i2c/i2c.rb
|
22
|
+
- lib/i2c/backends/i2c-dev.rb
|
23
|
+
- lib/i2c/drivers/mcp17026.rb
|
24
|
+
- test//mcp17026_spec.rb
|
25
|
+
- rules/88-i2c.rules
|
26
|
+
homepage: https://github.com/andec/i2c
|
27
|
+
licenses: []
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.24
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: I2C access library.
|
50
|
+
test_files: []
|