littlewire 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/littlewire.rb +79 -0
- data/examples/i2c/microchip-eeprom.rb +33 -0
- data/examples/i2c/nunchuck.rb +80 -0
- data/examples/i2c/search.rb +5 -0
- data/examples/ws2811/all.rb +44 -0
- data/examples/ws2811/colors.rb +28 -0
- data/examples/ws2811/manual.rb +43 -0
- data/examples/ws2811/scan.rb +26 -0
- data/examples/ws2811/spin.rb +17 -0
- data/examples/ws2811/strobe.rb +93 -0
- data/firmware/1.0.hex +254 -0
- data/firmware/1.1.hex +356 -0
- data/firmware/1.2.hex +364 -0
- data/firmware/cdc232.hex +173 -0
- data/firmware/latest-beta.hex +377 -0
- data/firmware/latest.hex +364 -0
- data/lib/littlewire.rb +16 -14
- data/lib/{analog.rb → littlewire/analog.rb} +0 -0
- data/lib/{digital.rb → littlewire/digital.rb} +1 -1
- data/lib/littlewire/gadgets/micronucleus.rb +171 -0
- data/lib/littlewire/gadgets/nunchuck.rb +102 -0
- data/lib/{hardware-pwm.rb → littlewire/hardware-pwm.rb} +0 -0
- data/lib/littlewire/i2c.rb +92 -0
- data/lib/{one-wire.rb → littlewire/one-wire.rb} +0 -0
- data/lib/{servo.rb → littlewire/servo.rb} +0 -0
- data/lib/{software-pwm.rb → littlewire/software-pwm.rb} +0 -0
- data/lib/{spi.rb → littlewire/spi.rb} +0 -0
- data/lib/littlewire/version.rb +8 -0
- data/lib/{ws2811.rb → littlewire/ws2811.rb} +7 -5
- metadata +40 -16
- data/examples/led pixel.rb +0 -55
- data/lib/i2c.rb +0 -43
data/lib/littlewire.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
-
# A little library for a little wire, by Bluebie
|
2
|
-
# Provides an arduino or wiring style interface to the LittleWire device's IO features
|
3
|
-
# and provides a nicer invented ruby-style interface also.
|
4
1
|
require 'libusb'
|
5
2
|
class LittleWire; end
|
6
|
-
require_relative 'digital'
|
7
|
-
require_relative 'analog'
|
8
|
-
require_relative 'hardware-pwm'
|
9
|
-
require_relative 'software-pwm'
|
10
|
-
require_relative 'servo'
|
11
|
-
require_relative 'spi'
|
12
|
-
require_relative 'i2c'
|
13
|
-
require_relative 'one-wire'
|
14
|
-
require_relative 'ws2811'
|
3
|
+
require_relative 'littlewire/digital'
|
4
|
+
require_relative 'littlewire/analog'
|
5
|
+
require_relative 'littlewire/hardware-pwm'
|
6
|
+
require_relative 'littlewire/software-pwm'
|
7
|
+
require_relative 'littlewire/servo'
|
8
|
+
require_relative 'littlewire/spi'
|
9
|
+
require_relative 'littlewire/i2c'
|
10
|
+
require_relative 'littlewire/one-wire'
|
11
|
+
require_relative 'littlewire/ws2811'
|
12
|
+
require_relative 'littlewire/version'
|
15
13
|
|
14
|
+
# A little library for a little wire, by Bluebie
|
15
|
+
# Provides an arduino or wiring style interface to the LittleWire device's IO features
|
16
|
+
# and provides a nicer invented ruby-style interface also.
|
17
|
+
#
|
16
18
|
# LittleWire class represents LittleWire's connected to your computer via USB
|
17
19
|
#
|
18
20
|
# Most of the time you'll only have one LittleWire - in this case, use LittleWire.connect to get
|
@@ -66,7 +68,7 @@ class LittleWire
|
|
66
68
|
softpwm_c: [:software_pwm, :softpwm_c],
|
67
69
|
}
|
68
70
|
|
69
|
-
SupportedVersions = ['1.2', '1.1', '1.0'] # in order of newness.
|
71
|
+
SupportedVersions = ['1.2', '1.1', '1.0'] # in order of newness.
|
70
72
|
|
71
73
|
|
72
74
|
# An array of all unclaimed littlewires connected to computer via USB
|
@@ -95,7 +97,7 @@ class LittleWire
|
|
95
97
|
|
96
98
|
# shut everything down, trying to setup littlewire in consistent initial state in case previous programs
|
97
99
|
# messed with it's state
|
98
|
-
self.software_pwm_enabled = false
|
100
|
+
self.software_pwm_enabled = false if version_hex >= 0x11
|
99
101
|
self.hardware_pwm_enabled = false
|
100
102
|
self.pin_mode(pin1: :input, pin2: :input, pin3: :input, pin4: :input)
|
101
103
|
self.digital_write(pin1: :gnd, pin2: :gnd, pin3: :gnd, pin4: :gnd)
|
File without changes
|
@@ -81,7 +81,7 @@ module LittleWire::Digital
|
|
81
81
|
# Multiple pins can be set using `my_wire.pin_mode(:pin1 => :input, :pin2 => :output)`
|
82
82
|
#
|
83
83
|
# :input mode leaves the pin disconnected if `digital_write`n to false, or connected
|
84
|
-
# via a 20kohm resistor to 5 volts if `digital_write`
|
84
|
+
# via a 20kohm resistor to 5 volts if `digital_write` to true.
|
85
85
|
#
|
86
86
|
# :output mode connects the pin directly to 5 volts when `digital_write`n to true
|
87
87
|
# and connects it to ground when digitally written to false. Be careful not to create
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'libusb'
|
2
|
+
|
3
|
+
# Abstracts access to micronucleus avr tiny85 bootloader - can be used only to erase and upload bytes
|
4
|
+
class Micronucleus
|
5
|
+
Functions = [
|
6
|
+
:get_info,
|
7
|
+
:write_page,
|
8
|
+
:erase_application,
|
9
|
+
:run_program
|
10
|
+
]
|
11
|
+
|
12
|
+
# return all micronucleus devices connected to computer
|
13
|
+
def self.all
|
14
|
+
usb = LIBUSB::Context.new
|
15
|
+
usb.devices.select { |device|
|
16
|
+
device.idVendor == 0x16d0 && device.idProduct == 0x0753
|
17
|
+
}.map { |device|
|
18
|
+
self.new(device)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize devref
|
23
|
+
@device = devref
|
24
|
+
end
|
25
|
+
|
26
|
+
def info
|
27
|
+
unless @info
|
28
|
+
result = control_transfer(function: :get_info, dataIn: 4)
|
29
|
+
flash_length, page_size, write_sleep = result.unpack('S>CC')
|
30
|
+
|
31
|
+
@info = {
|
32
|
+
flash_length: flash_length,
|
33
|
+
page_size: page_size,
|
34
|
+
pages: (flash_length.to_f / page_size).ceil,
|
35
|
+
write_sleep: write_sleep.to_f / 1000.0,
|
36
|
+
version: "#{@device.bcdDevice >> 8}.#{(@device.bcdDevice & 0xFF).to_s.rjust(2, '0')}",
|
37
|
+
version_numeric: @device.bcdDevice
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
@info
|
42
|
+
end
|
43
|
+
|
44
|
+
def erase!
|
45
|
+
puts "erasing"
|
46
|
+
info = self.info
|
47
|
+
control_transfer(function: :erase_application)
|
48
|
+
info[:pages].times do
|
49
|
+
sleep(info[:write_sleep]) # sleep for as many pages as the chip has to erase
|
50
|
+
end
|
51
|
+
puts "erased chip"
|
52
|
+
end
|
53
|
+
|
54
|
+
# upload a new program
|
55
|
+
def program= bytestring
|
56
|
+
info = self.info
|
57
|
+
raise "Program too long!" if bytestring.bytesize > info[:flash_length]
|
58
|
+
bytes = bytestring.bytes.to_a
|
59
|
+
bytes.push(0xFF) while bytes.length < info[:flash_length]
|
60
|
+
|
61
|
+
erase!
|
62
|
+
|
63
|
+
address = 0
|
64
|
+
bytes.each_slice(info[:page_size]) do |slice|
|
65
|
+
slice.push(0xFF) while slice.length < info[:page_size] # ensure every slice is one page_size long - pad out if needed
|
66
|
+
|
67
|
+
puts "uploading @ #{address} of #{bytes.length}"
|
68
|
+
control_transfer(function: :write_page, wIndex: address, wValue: slice.length, dataOut: slice.pack('C*'))
|
69
|
+
sleep(info[:write_sleep])
|
70
|
+
address += slice.length
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def finished
|
75
|
+
info = self.info
|
76
|
+
|
77
|
+
puts "asking device to finish writing"
|
78
|
+
control_transfer(function: :run_program)
|
79
|
+
puts "waiting for device to finish"
|
80
|
+
|
81
|
+
# sleep for as many pages as the chip could potentially need to write - this could be smarter
|
82
|
+
info[:pages].times do
|
83
|
+
sleep(info[:write_sleep])
|
84
|
+
end
|
85
|
+
|
86
|
+
@io.close
|
87
|
+
@io = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def inspect
|
91
|
+
"<Micronucleus #{info[:version]}: #{(info[:flash_length] / 1024.0).round(1)} kb programmable>"
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
# raw opened device
|
96
|
+
def io
|
97
|
+
unless @io
|
98
|
+
@io = @device.open
|
99
|
+
end
|
100
|
+
|
101
|
+
@io
|
102
|
+
end
|
103
|
+
|
104
|
+
def control_transfer(opts = {})
|
105
|
+
opts[:bRequest] = Functions.index(opts.delete(:function)) if opts[:function]
|
106
|
+
io.control_transfer({
|
107
|
+
wIndex: 0,
|
108
|
+
wValue: 0,
|
109
|
+
bmRequestType: usb_request_type(opts),
|
110
|
+
timeout: 5000
|
111
|
+
}.merge opts)
|
112
|
+
end
|
113
|
+
|
114
|
+
# calculate usb request type
|
115
|
+
def usb_request_type opts #:nodoc:
|
116
|
+
value = LIBUSB::REQUEST_TYPE_VENDOR | LIBUSB::RECIPIENT_DEVICE
|
117
|
+
value |= LIBUSB::ENDPOINT_OUT if opts.has_key? :dataOut
|
118
|
+
value |= LIBUSB::ENDPOINT_IN if opts.has_key? :dataIn
|
119
|
+
return value
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class HexProgram
|
124
|
+
def initialize input
|
125
|
+
@bytes = Hash.new(0xFF)
|
126
|
+
input = input.read if input.is_a? IO
|
127
|
+
parse input
|
128
|
+
end
|
129
|
+
|
130
|
+
def binary
|
131
|
+
bytes.pack('C*')
|
132
|
+
end
|
133
|
+
|
134
|
+
def bytes
|
135
|
+
highest_address = @bytes.keys.max
|
136
|
+
|
137
|
+
bytes = Array.new(highest_address + 1) { |index|
|
138
|
+
@bytes[index]
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def parse input_text
|
145
|
+
input_text.each_line do |line|
|
146
|
+
next unless line.start_with? ':'
|
147
|
+
line.chomp!
|
148
|
+
length = line[1..2].to_i(16) # usually 16 or 32
|
149
|
+
address = line[3..6].to_i(16) # 16-bit start address
|
150
|
+
record_type = line[7..8].to_i(16)
|
151
|
+
data = line[9... 9 + (length * 2)]
|
152
|
+
checksum = line[9 + (length * 2).. 10 + (length * 2)].to_i(16)
|
153
|
+
checksum_section = line[1...9 + (length * 2)]
|
154
|
+
|
155
|
+
checksum_calculated = checksum_section.chars.to_a.each_slice(2).map { |slice|
|
156
|
+
slice.join('').to_i(16)
|
157
|
+
}.reduce(0, &:+)
|
158
|
+
|
159
|
+
checksum_calculated = (((checksum_calculated % 256) ^ 0xFF) + 1) % 256
|
160
|
+
|
161
|
+
raise "Hex file checksum mismatch @ #{line} should be #{checksum_calculated.to_s(16)}" unless checksum == checksum_calculated
|
162
|
+
|
163
|
+
if record_type == 0 # data record
|
164
|
+
data_bytes = data.chars.each_slice(2).map { |slice| slice.join('').to_i(16) }
|
165
|
+
data_bytes.each_with_index do |byte, index|
|
166
|
+
@bytes[address + index] = byte
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class LittleWire
|
2
|
+
# get Wii Nunchuck interface
|
3
|
+
def nunchuck
|
4
|
+
@nunchuck ||= LittleWire::Nunchuck.new self
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class LittleWire::Nunchuck
|
9
|
+
BusAddress = 82 # Address of Nunchuck on i2c bus
|
10
|
+
|
11
|
+
def initialize wire, interval = 0
|
12
|
+
@wire = wire
|
13
|
+
|
14
|
+
raise "Nunchuck requires LittleWire firmware 1.3 or newer" unless wire.version_hex >= 0x13
|
15
|
+
|
16
|
+
# config i2c to reliably talk with nunchuck devices
|
17
|
+
i2c.delay = interval # The fastest option of 0 seems to work. Neat!
|
18
|
+
|
19
|
+
# initialize the nunchuck
|
20
|
+
set_register 0xf0, 0x55
|
21
|
+
set_register 0xfb, 0x00
|
22
|
+
sample # do a sample, to get the ball rolling
|
23
|
+
end
|
24
|
+
|
25
|
+
def sample
|
26
|
+
# tell device to gather new sensor data
|
27
|
+
i2c.transmit BusAddress, [0]
|
28
|
+
# load sample in to PC
|
29
|
+
data = i2c.request(BusAddress, 6)
|
30
|
+
|
31
|
+
LittleWire::Nunchuck::NunchuckFrame.new(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def i2c
|
36
|
+
@i2c ||= @wire.i2c
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_register register, value
|
40
|
+
i2c.transmit BusAddress, [register, value]
|
41
|
+
sleep(0.01)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class LittleWire::Nunchuck::NunchuckFrame
|
46
|
+
def initialize data
|
47
|
+
@data = data
|
48
|
+
@joystick = Vector3D.new((data[0] / 127.5) - 1.0, (data[1] / 127.5) - 1.0)
|
49
|
+
@buttons = Buttons.new(((data[5] >> 1) & 1) == 0, (data[5] & 1) == 0)
|
50
|
+
|
51
|
+
#calculate accelerometer values
|
52
|
+
@accelerometer = Vector3D.new(data[2] << 2, data[3] << 2, data[4] << 2)
|
53
|
+
@accelerometer.x |= (data[5] >> 2) & 0b11
|
54
|
+
@accelerometer.y |= (data[5] >> 4) & 0b11
|
55
|
+
@accelerometer.z |= (data[5] >> 6) & 0b11
|
56
|
+
|
57
|
+
@accelerometer.x = (@accelerometer.x / 511.5) - 1.0
|
58
|
+
@accelerometer.y = (@accelerometer.y / 511.5) - 1.0
|
59
|
+
@accelerometer.z = (@accelerometer.z / 511.5) - 1.0
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :joystick, :accelerometer, :buttons
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"<Nunchuck:#{buttons.inspect}:#{accelerometer.inspect}:#{joystick.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
class Vector3D
|
69
|
+
def initialize x=nil, y=nil, z=nil
|
70
|
+
@x,@y,@z = x,y,z
|
71
|
+
end
|
72
|
+
|
73
|
+
attr_accessor :x,:y,:z
|
74
|
+
def to_a; [@x,@y,@z].compact; end
|
75
|
+
def to_h; {x: @x, y: @y, z: @z}; end
|
76
|
+
def to_hash; to_h; end
|
77
|
+
def inspect
|
78
|
+
precision = 2
|
79
|
+
pretty = lambda { |n| (n >= 0 ? '+' : '-') + n.abs.round(precision).to_s.ljust(precision + 2, '0') }
|
80
|
+
numbers = to_a.map { |n| pretty[n] }
|
81
|
+
"<Coords:" + numbers.join(',') + ">"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Buttons
|
86
|
+
def initialize c,z
|
87
|
+
@c,@z = c,z
|
88
|
+
end
|
89
|
+
attr_accessor :c, :z
|
90
|
+
|
91
|
+
# is button down?
|
92
|
+
def down? button
|
93
|
+
instance_variable_get("@#{button.to_s.downcase}")
|
94
|
+
end
|
95
|
+
|
96
|
+
def up? button
|
97
|
+
not down? button
|
98
|
+
end
|
99
|
+
|
100
|
+
def inspect; "<Buttons:#{'C' if @c}#{'Z' if @z}>"; end
|
101
|
+
end
|
102
|
+
end
|
File without changes
|
@@ -0,0 +1,92 @@
|
|
1
|
+
class LittleWire::I2C
|
2
|
+
def initialize wire
|
3
|
+
@wire = wire
|
4
|
+
raise "i2c support requires littlewire firmware 1.2 or newer. Please update to firmware #{LittleWire::SupportedVersions.first} with the `littlewire.rb install #{LittleWire::SupportedVersions.first}` command!" if wire.version_hex < 0x12
|
5
|
+
warn "i2c delay support is buggy in firmware 1.2. Please update firmware to at least 1.3" if wire.version_hex == 0x12
|
6
|
+
@wire.control_transfer(function: :i2c_init, dataIn:8)
|
7
|
+
end
|
8
|
+
|
9
|
+
# start an i2c message
|
10
|
+
#
|
11
|
+
# Arguments:
|
12
|
+
# address: (Integer) 7 bit numeric address
|
13
|
+
# direction: (Symbol) :in or :out
|
14
|
+
#
|
15
|
+
# Returns: true if device is active on i2c bus, false if it is unresponsive
|
16
|
+
def start address_7bit, direction
|
17
|
+
raise "Address is too high" if address_7bit > 127
|
18
|
+
raise "Address is too low" if address_7bit < 0
|
19
|
+
|
20
|
+
direction = :write if direction == :out || direction == :output || direction == :send
|
21
|
+
config = (address_7bit.to_i << 1) | ((direction == :write) ? 0 : 1)
|
22
|
+
|
23
|
+
@wire.control_transfer(function: :i2c_begin, wValue: config, dataIn: 8)
|
24
|
+
@wire.control_transfer(function: :read_buffer, dataIn: 8).bytes.first == 0
|
25
|
+
end
|
26
|
+
|
27
|
+
# read bytes from i2c device, optionally ending with a stop when finished
|
28
|
+
def read length, stop_at_end = true
|
29
|
+
@wire.control_transfer(function: :i2c_read, dataIn: 8,
|
30
|
+
wValue: ((length.to_i & 0xFF) << 8) + (stop_at_end ? 1 : 0),
|
31
|
+
wIndex: stop_at_end ? 1 : 0)
|
32
|
+
@wire.control_transfer(function: :read_buffer, dataIn: 8).bytes.first(length)
|
33
|
+
end
|
34
|
+
|
35
|
+
# write data to i2c device, optionally sending a stop when finished
|
36
|
+
def write send_buffer, stop_at_end = true
|
37
|
+
#send_buffer = send_buffer.pack('C*') if send_buffer.is_a? Array
|
38
|
+
send_buffer = send_buffer.bytes if send_buffer.is_a? String
|
39
|
+
|
40
|
+
#byte_sets = send_buffer.each_slice(4).to_a
|
41
|
+
#byte_sets.each_with_index.map do |slice, index|
|
42
|
+
# do_stop = stop_at_end && index >= byte_sets.length - 1
|
43
|
+
slice = send_buffer
|
44
|
+
do_stop = stop_at_end
|
45
|
+
@wire.control_transfer(
|
46
|
+
bRequest: 0xE0 + slice.length + ((do_stop ? 1 : 0) << 3),
|
47
|
+
wValue: ((slice[1] || 0) << 8) + (slice[0] || 0),
|
48
|
+
wIndex: ((slice[3] || 0) << 8) + (slice[2] || 0),
|
49
|
+
dataIn: 8
|
50
|
+
)
|
51
|
+
#end
|
52
|
+
end
|
53
|
+
|
54
|
+
# simplified syntax to send a message to an address
|
55
|
+
def transmit address, *args
|
56
|
+
raise "I2C Device #{address} Unresponsive" unless start(address, :write)
|
57
|
+
write(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# simplified syntax to read value of a register
|
61
|
+
def request address, *args
|
62
|
+
raise "I2C Device #{address} Unresponsive" unless start(address, :read)
|
63
|
+
read *args
|
64
|
+
end
|
65
|
+
|
66
|
+
# set the update delay of LittleWire's i2c module in microseconds
|
67
|
+
def delay= update_delay
|
68
|
+
@wire.control_transfer(function: :i2c_update_delay, wValue: update_delay.to_i)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Search all 128 possible i2c device addresses, starting a message to each and
|
72
|
+
# testing if devices respond at each location.
|
73
|
+
#
|
74
|
+
# Returns: Array of Integer addresses
|
75
|
+
def search
|
76
|
+
128.times.select do |address|
|
77
|
+
address_responds? address
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Test if an i2c address responds to requests - e.g. is it plugged in to the network?
|
82
|
+
#
|
83
|
+
# Arguments:
|
84
|
+
# - address - an integer between 0 and 127 inclusive
|
85
|
+
# Returns: true or false
|
86
|
+
def address_responds? address
|
87
|
+
raise "Address must be an Integer" unless address.is_a? Integer
|
88
|
+
raise "Address too high. Max is 127" if address > 127
|
89
|
+
raise "Address too low. Needs to be at least 0" if address < 0
|
90
|
+
start(address, :write)
|
91
|
+
end
|
92
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|