i2c-devices 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/ChangeLog +4 -0
- data/README.md +130 -0
- data/Rakefile +22 -0
- data/i2c-devices.gemspec +22 -0
- data/lib/i2c.rb +28 -0
- data/lib/i2c/device/acm1602ni.rb +18 -0
- data/lib/i2c/device/adt7410.rb +146 -0
- data/lib/i2c/device/aqm0802.rb +58 -0
- data/lib/i2c/device/hd44780.rb +137 -0
- data/lib/i2c/device/mpl115a2.rb +39 -0
- data/lib/i2c/driver/gpio.rb +198 -0
- data/lib/i2c/driver/i2c-dev.rb +53 -0
- data/lib/i2c/mocki2cdevice.rb +67 -0
- data/spec/device/adt7410_spec.rb +87 -0
- data/spec/device/hd44780_spec.rb +155 -0
- data/spec/driver/gpio_spec.rb +514 -0
- data/spec/i2cdevice_spec.rb +89 -0
- data/xt/acm1602ni.rb +22 -0
- data/xt/driver-gpio.rb +34 -0
- data/xt/i2cdetect.rb +69 -0
- data/xt/mpl115a2.rb +12 -0
- metadata +97 -0
@@ -0,0 +1,87 @@
|
|
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 "i2c/mocki2cdevice"
|
11
|
+
|
12
|
+
describe ADT7410 do
|
13
|
+
before do
|
14
|
+
@mock = MockI2CDevice.new
|
15
|
+
File.stub(: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 = 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 = 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 = 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 = 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
|
+
|
@@ -0,0 +1,155 @@
|
|
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 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
|
+
File.stub(: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 = 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 = 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 = 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
|
@@ -0,0 +1,514 @@
|
|
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
|
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
|