littlewire 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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. # TODO: Add version 1.0?
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`n to true.
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
@@ -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
@@ -0,0 +1,8 @@
1
+ class LittleWire
2
+ Version = '0.9.8'
3
+
4
+ # correct interface, to enable dynamic stuff later if need be:
5
+ def self.version
6
+ LittleWire::Version
7
+ end
8
+ end