littlewire 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|