littlewire 0.9
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.
- data/examples/blinky.rb +16 -0
- data/examples/fadey.rb +22 -0
- data/lib/analog.rb +33 -0
- data/lib/digital.rb +110 -0
- data/lib/hardware-pwm.rb +53 -0
- data/lib/i2c.rb +43 -0
- data/lib/littlewire.rb +302 -0
- data/lib/one-wire.rb +6 -0
- data/lib/servo.rb +18 -0
- data/lib/software-pwm.rb +51 -0
- data/lib/spi.rb +28 -0
- data/license.txt +1 -0
- data/readme.md +68 -0
- metadata +79 -0
data/examples/blinky.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# A little blinky test to show how to make stuff happen with a littlewire in ruby!
|
2
|
+
#
|
3
|
+
# To get started, plug an LED in to the ISP cable between ground and Pin 4 (they're next to each other)
|
4
|
+
require '../lib/littlewire.rb'
|
5
|
+
|
6
|
+
wire = LittleWire.connect
|
7
|
+
|
8
|
+
# set pin4 to an input, so our LED doesn't burn out without a resistor
|
9
|
+
wire.pin_mode :pin4 => :input
|
10
|
+
|
11
|
+
loop do
|
12
|
+
wire.digital_write(:pin4, true) # set the LED on for half a second
|
13
|
+
sleep 0.5
|
14
|
+
wire.digital_write(:pin4, false) # set the LED off for half a second
|
15
|
+
sleep 0.5
|
16
|
+
end
|
data/examples/fadey.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# A little blinky test to show how to make stuff happen with a littlewire in ruby!
|
2
|
+
#
|
3
|
+
# To get started, plug an LED in to the ISP cable between ground and Pin 4 (they're next to each other) via a resistor (resistor not optional)
|
4
|
+
require '../lib/littlewire.rb'
|
5
|
+
|
6
|
+
wire = LittleWire.connect
|
7
|
+
|
8
|
+
FPS = 60 # update 60 times per second
|
9
|
+
fader = 0.0 # our current position
|
10
|
+
|
11
|
+
loop do
|
12
|
+
value = (Math.sin(fader) + 1.0) * 127.0 # calculate an ideal brightness
|
13
|
+
|
14
|
+
## Some equivilent ways of expressing this idea:
|
15
|
+
# wire.software_pwm_write(:softpwm_a, value)
|
16
|
+
# wire[:softpwm_a] = value
|
17
|
+
# wire[:softpwm_1] = value
|
18
|
+
wire.softpwm_a = value
|
19
|
+
|
20
|
+
fader += 0.025
|
21
|
+
sleep 1.0 / FPS
|
22
|
+
end
|
data/lib/analog.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module LittleWire::Analog
|
2
|
+
# Read the current value of an analog input
|
3
|
+
# Valid inputs are
|
4
|
+
AnalogReferences = [:vcc, :internal_reference_1_1, :internal_reference_2_56]
|
5
|
+
def analog_read input_name, voltage_reference = :vcc
|
6
|
+
voltage_reference = AnalogReferences.index(voltage_reference) if AnalogReferences.include? voltage_reference
|
7
|
+
scaling_setting = 0x07
|
8
|
+
|
9
|
+
if @analog_reference != voltage_reference
|
10
|
+
@analog_reference = voltage_reference
|
11
|
+
# This reset step is to work around a bug in firmware 1.1 - hopefully it can be disabled in future releases
|
12
|
+
control_transfer(function: :analog_init, wValue: scaling_setting) # reset analog reference setting
|
13
|
+
# set new reference voltage
|
14
|
+
control_transfer(function: :analog_init, wValue: scaling_setting | voltage_reference << 8)
|
15
|
+
end
|
16
|
+
|
17
|
+
control_transfer(function: :read_adc,
|
18
|
+
wValue: get_pin(AnalogPinMap, input_name),
|
19
|
+
dataIn: 2).unpack('S<').first / 1024.0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get the current temperature inside the LittleWire device's microcontroller. At a courseness of about 1.1°C increments
|
23
|
+
# and quite a lot of noise in the measurements, some averaging is required. Rather neatly, because of the noise in each
|
24
|
+
# sample, when averaged you get higher than 1.1°C resolution.
|
25
|
+
#
|
26
|
+
# This method returns a value around 280.0 - you'll need to calibrate it yourself by simple subtraction. You can calibrate
|
27
|
+
# it even more accurately by linearly mapping it to two known temperature points. Each LittleWire is a bit different so some
|
28
|
+
# calibration is necessary (unless you just want to take relative measurements).
|
29
|
+
def temperature
|
30
|
+
(analog_read(:temperature, :internal_reference_1_1) * 1024.0) / 1.12
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/digital.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module LittleWire::Digital
|
2
|
+
EnableBulkWrite = false # this thing seems to not work good - not ready for real world use
|
3
|
+
# these firmwares have a default state specified, enabling bulk writing
|
4
|
+
BulkWriteDefaultStates = { '1.1' => 0b00001000 } # D- usb bit is high, others are low
|
5
|
+
BulkWriteBitmask = { '1.1' => 0b00100111 }
|
6
|
+
|
7
|
+
# Write one or more digital values to device pins
|
8
|
+
#
|
9
|
+
# A simple invokation is `my_wire.digital_write(:pin1, true)` setting pin1 to a
|
10
|
+
# high logic level. :high and :low can be substituted for true and false, as can
|
11
|
+
# :on and :off, :vcc and :gnd or :ground
|
12
|
+
#
|
13
|
+
# `digital_write` can also be called with a hash: `my_wire.digital_write(:pin1 => true)`
|
14
|
+
# allowing multiple pins to be set at once. A system for updating all pins simultaniously is being
|
15
|
+
# worked on, but is not yet stable, so for now digital_write uses a request for each pin.
|
16
|
+
#
|
17
|
+
# Automatically disables PWM and Servo features if you try to write to a pin used by those modules
|
18
|
+
def digital_write *args
|
19
|
+
if args.length > 1
|
20
|
+
self.digital_write({args[0] => args[1]})
|
21
|
+
else
|
22
|
+
raise "Incorrect Arguments" unless args.first.respond_to? :to_hash
|
23
|
+
hash = args.first.to_hash
|
24
|
+
|
25
|
+
#self.hardware_pwm_enabled = false if hash.keys.any? { |pin| LittleWire::HardwarePWMPinMap.has_key? pin }
|
26
|
+
#self.software_pwm_enabled = false if hash.keys.any? { |pin| LittleWire::SoftwarePWMPinMap.has_key? pin }
|
27
|
+
|
28
|
+
if use_experimental_bulk_write?
|
29
|
+
# could cause problems - must do tests when deciding if to enable this or not
|
30
|
+
@bulk_write_bitmap ||= 0
|
31
|
+
|
32
|
+
hash.each do |pin, state|
|
33
|
+
if get_boolean(state)
|
34
|
+
@bulk_write_bitmap |= 1 << get_pin(LittleWire::DigitalPinMap, pin)
|
35
|
+
else
|
36
|
+
@bulk_write_bitmap &= ~(1 << get_pin(LittleWire::DigitalPinMap, pin))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
value = (@bulk_write_bitmap & BulkWriteBitmask[self.version]) | BulkWriteDefaultStates[self.version]
|
40
|
+
control_transfer(function: :write, wValue: value)
|
41
|
+
else
|
42
|
+
hash.each do |pin, state|
|
43
|
+
control_transfer(
|
44
|
+
function: get_boolean(state) ? :pin_set_high : :pin_set_low,
|
45
|
+
wValue: get_pin(LittleWire::DigitalPinMap, pin)
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Read one or more digital values from device pins
|
53
|
+
#
|
54
|
+
# The simplest invocation is `my_wire.digital_read(:pin1)`, returning a true or false
|
55
|
+
# where true is a high (positive) voltage and false is a lower (near ground) voltage
|
56
|
+
#
|
57
|
+
# A more advanced invokation is passing several pins as arguments or an array of pins
|
58
|
+
# `sensor_a, sensor_b = my_wire.digital_read(:pin1, :pin2)` which reads both pins at
|
59
|
+
# the exact same instant in just one USB request, returning an array of the results
|
60
|
+
#
|
61
|
+
# digital_read tends to work best when the pin in :input mode. See also #pin_mode
|
62
|
+
def digital_read *args
|
63
|
+
raise "Incorrect Arguments" if args.length < 1
|
64
|
+
pins = args.flatten
|
65
|
+
port = control_transfer(function: :read, dataIn: 1).unpack('c').first
|
66
|
+
mapped = pins.map do |pin|
|
67
|
+
pin = get_pin(LittleWire::DigitalPinMap, pin)
|
68
|
+
(port & pin) != 0 # discover if pin is high or low
|
69
|
+
end
|
70
|
+
|
71
|
+
if args.length == 1 and (args.first.is_a?(Symbol) or args.first.is_a?(Integer))
|
72
|
+
mapped.first
|
73
|
+
else
|
74
|
+
mapped
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set the mode of one or more device pins
|
79
|
+
#
|
80
|
+
# A simple form is `my_wire.pin_mode(:pin1, :input)` setting pin1 to input mode.
|
81
|
+
# Multiple pins can be set using `my_wire.pin_mode(:pin1 => :input, :pin2 => :output)`
|
82
|
+
#
|
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.
|
85
|
+
#
|
86
|
+
# :output mode connects the pin directly to 5 volts when `digital_write`n to true
|
87
|
+
# and connects it to ground when digitally written to false. Be careful not to create
|
88
|
+
# short circuits as these may damage your LittleWire.
|
89
|
+
def pin_mode *args
|
90
|
+
if args.length == 2
|
91
|
+
self.pin_mode( args[0] => args[1] )
|
92
|
+
else
|
93
|
+
raise if args.length != 1 or !args.first.is_a?(Hash)
|
94
|
+
|
95
|
+
modes = {
|
96
|
+
:input => :pin_set_input, :in => :pin_set_input,
|
97
|
+
:output => :pin_set_output, :out => :pin_set_output
|
98
|
+
}
|
99
|
+
args.first.each do |pin, mode|
|
100
|
+
raise "Unknown mode #{mode.inspect}" unless modes[mode.to_sym]
|
101
|
+
control_transfer(function: modes[mode.to_sym], wValue: get_pin(LittleWire::DigitalPinMap, pin))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def use_experimental_bulk_write?
|
108
|
+
EnableBulkWrite and BulkWriteDefaultStates.include?(self.version.to_s)
|
109
|
+
end
|
110
|
+
end
|
data/lib/hardware-pwm.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module LittleWire::HardwarePWM
|
2
|
+
attr_reader :hardware_pwm_enabled # initially :unknown, then a boolean
|
3
|
+
|
4
|
+
# Set hardware pwm as enabled or disabled - hardware pwm is automatically enabled when you start using it
|
5
|
+
# but must be disabled before using these pins again for other purposes
|
6
|
+
def hardware_pwm_enabled= value
|
7
|
+
return if @hardware_pwm_enabled == value
|
8
|
+
@hardware_pwm_enabled = !!value
|
9
|
+
control_transfer(function: value ? :start_pwm : :stop_pwm)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get an array of the current values in the Hardware PWM module
|
13
|
+
def hardware_pwm; @hardware_pwm.dup; end
|
14
|
+
|
15
|
+
# Set Hardware PWM to an array of new values - array must be two items long
|
16
|
+
def hardware_pwm= values
|
17
|
+
self.hardware_pwm_enabled = true
|
18
|
+
@hardware_pwm[0] = values[0].to_i % 256
|
19
|
+
@hardware_pwm[1] = values[1].to_i % 256
|
20
|
+
control_transfer(function: :update_pwm_compare, wValue: @hardware_pwm[0].to_i, wIndex: @hardware_pwm[1].to_i)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the current value of a Hardware PWM channel (stored in littlewire.rb library - not requested from device)
|
24
|
+
def hardware_pwm_read channel; hardware_pwm[get_pin(LittleWire::HardwarePWMPinMap, channel)]; end
|
25
|
+
|
26
|
+
# Set an individual Hardware PWM channel to a new value in the range of 0-255
|
27
|
+
def hardware_pwm_write channel, value
|
28
|
+
updated = self.hardware_pwm
|
29
|
+
updated[get_pin(LittleWire::HardwarePWMPinMap, channel)] = value
|
30
|
+
self.hardware_pwm = updated
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
PWMPrescaleSettings = [1, 8, 64, 256, 1024] # :nodoc:
|
35
|
+
# Set division of the Hardware PWM Prescaler - default 1024. This setting controls how quickly LittleWire's PWM channels oscillate
|
36
|
+
# between their 'high' and 'low' state. Lower prescaler values are often nicer for lighting, while higher values can be better for
|
37
|
+
# motor speed control and 1024 is required for servo position control
|
38
|
+
# 1024: roughly 63hz
|
39
|
+
# 256: roughly 252hz
|
40
|
+
# 64: roughly 1khz
|
41
|
+
# 8: roughly 8khz
|
42
|
+
# 1: roughly 64khz
|
43
|
+
#
|
44
|
+
# No other values are accepted
|
45
|
+
def hardware_pwm_prescale= division
|
46
|
+
raise "Unsupported Hardware PWM Prescale value, must be #{PWMPrescaleSettings.inspect}" unless PWMPrescaleSettings.include? division
|
47
|
+
if @hardware_pwm_prescale != division
|
48
|
+
@hardware_pwm_prescale = division
|
49
|
+
control_transfer(function: :change_pwm_prescale, wValue: PWMPrescaleSettings.index(division))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/i2c.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
class LittleWire::I2C
|
2
|
+
def initialize wire
|
3
|
+
@wire = wire
|
4
|
+
@wire.control_transfer(function: :i2c_init)
|
5
|
+
end
|
6
|
+
|
7
|
+
# start an i2c message
|
8
|
+
#
|
9
|
+
# Arguments:
|
10
|
+
# address: (Integer) 7 bit numeric address
|
11
|
+
# direction: (Symbol) :in or :out
|
12
|
+
def start address_7bit, direction
|
13
|
+
direction = 1 if direction == :out || direction == :output || direction == :write
|
14
|
+
direction = 0 if direction != 1
|
15
|
+
config = (address_7bit.to_i << 1) | direction
|
16
|
+
@wire.control_transfer(function: :i2c_begin, wValue: config)
|
17
|
+
@wire.control_transfer(function: :read_buffer, dataIn: 8).bytes.first != 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# read bytes from i2c device, optionally ending with a stop when finished
|
21
|
+
def read length, endWithStop = false
|
22
|
+
@wire.control_transfer(function: :i2c_read, wValue: (length.to_i & 0xFF) << 8 | (endWithStop ? 1 : 0))
|
23
|
+
@wire.control_transfer(function: :read_buffer, dataIn: 8)[0...length.to_i]
|
24
|
+
end
|
25
|
+
|
26
|
+
# write data to i2c device, optionally sending a stop when finished
|
27
|
+
def write send_buffer, end_with_stop = false
|
28
|
+
send_buffer = send_buffer.pack('c*') if send_buffer.is_a? Array
|
29
|
+
raise "Send buffer is too long" if send_buffer.length > 7
|
30
|
+
|
31
|
+
# TODO: Send multiple requests to handle send buffers longer than 7 bytes
|
32
|
+
@wire.control_transfer(
|
33
|
+
wRequest: 0xE0 | send_buffer.length | (end_with_stop << 3),
|
34
|
+
wValue: (send_buffer.bytes[1] << 8) + send_buffer.bytes[0],
|
35
|
+
wIndex: (send_buffer.bytes[3] << 8) + send_buffer.bytes[2]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# set the update delay of LittleWire's i2c module
|
40
|
+
def delay= update_delay
|
41
|
+
@wire.control_transfer(function: :i2c_update_delay, wValue: update_delay.to_i)
|
42
|
+
end
|
43
|
+
end
|
data/lib/littlewire.rb
ADDED
@@ -0,0 +1,302 @@
|
|
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
|
+
require 'libusb'
|
5
|
+
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
|
+
|
15
|
+
# LittleWire class represents LittleWire's connected to your computer via USB
|
16
|
+
#
|
17
|
+
# Most of the time you'll only have one LittleWire - in this case, use LittleWire.connect to get
|
18
|
+
# ahold of your wire. If you have more than one, you can use LittleWire.all to fetch an array of them
|
19
|
+
class LittleWire
|
20
|
+
include Digital
|
21
|
+
include Analog
|
22
|
+
include HardwarePWM
|
23
|
+
include SoftwarePWM
|
24
|
+
include Servo
|
25
|
+
|
26
|
+
# pin name to numeric internal code maps
|
27
|
+
DigitalPinMap = { # maps common names to bit positions in PORTB
|
28
|
+
pin1: 1, d1: 1, miso: 1, pwm_b: 1, pwm_2: 1,
|
29
|
+
pin2: 2, d2: 2, sck: 2,
|
30
|
+
pin3: 5, d3: 5, reset: 5,
|
31
|
+
pin4: 0, d4: 0, mosi: 0, pwm_a: 0, pwm_1: 0 }
|
32
|
+
AnalogPinMap = { # maps common names to switch index in littlewire firmware
|
33
|
+
a1: 0, adc_1: 0, reset: 0, pin3: 0, d3: 0,
|
34
|
+
a2: 1, adc_2: 1, sck: 1, pin2: 1, d2: 1,
|
35
|
+
temperature: 2, temp: 2 }
|
36
|
+
HardwarePWMPinMap = { # maps common pin names to @hardware_pwm array index
|
37
|
+
pwm_b: 1, pwm_1: 1, d1: 1, pin1: 1, miso: 1,
|
38
|
+
pwm_a: 0, pwm_2: 0, d4: 0, pin4: 0, mosi: 0 }
|
39
|
+
SoftwarePWMPinMap = { # TODO: figure out which pins these are
|
40
|
+
softpwm_1: 0, softpwm_a: 0,
|
41
|
+
softpwm_2: 1, softpwm_b: 1,
|
42
|
+
softpwm_3: 2, softpwm_c: 2 }
|
43
|
+
GenericPinMap = { # generic pinmap used by [] and []= methods to refer to anything
|
44
|
+
d1: [:digital, :pin1],
|
45
|
+
d2: [:digital, :pin2],
|
46
|
+
d3: [:digital, :pin3],
|
47
|
+
d4: [:digital, :pin4],
|
48
|
+
pin1: [:digital, :pin1],
|
49
|
+
pin2: [:digital, :pin2],
|
50
|
+
pin3: [:digital, :pin3],
|
51
|
+
pin4: [:digital, :pin4],
|
52
|
+
a1: [:analog, :a1],
|
53
|
+
a2: [:analog, :a2],
|
54
|
+
adc_1: [:analog, :adc_1],
|
55
|
+
adc_1: [:analog, :adc_2],
|
56
|
+
pwm_1: [:hardware_pwm, :pwm_1],
|
57
|
+
pwm_2: [:hardware_pwm, :pwm_2],
|
58
|
+
pwm_a: [:hardware_pwm, :pwm_a],
|
59
|
+
pwm_b: [:hardware_pwm, :pwm_b],
|
60
|
+
softpwm_1: [:software_pwm, :softpwm_1],
|
61
|
+
softpwm_2: [:software_pwm, :softpwm_2],
|
62
|
+
softpwm_3: [:software_pwm, :softpwm_3],
|
63
|
+
softpwm_a: [:software_pwm, :softpwm_a],
|
64
|
+
softpwm_b: [:software_pwm, :softpwm_b],
|
65
|
+
softpwm_c: [:software_pwm, :softpwm_c],
|
66
|
+
}
|
67
|
+
|
68
|
+
SupportedVersions = ['1.1', '1.0'] # in order of newness. # TODO: Add version 1.0?
|
69
|
+
|
70
|
+
|
71
|
+
# An array of all unclaimed littlewires connected to computer via USB
|
72
|
+
def self.all
|
73
|
+
usb = LIBUSB::Context.new
|
74
|
+
usb.devices.select { |device|
|
75
|
+
device.idProduct == 0x0c9f && device.idVendor == 0x1781 && device.product == 'USBtinySPI'
|
76
|
+
}.map { |device|
|
77
|
+
self.new(device)
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Frst littlewire connected to this computer via USB - good when you only have one
|
82
|
+
def self.connect; all.first; end
|
83
|
+
|
84
|
+
|
85
|
+
# initializes a LittleWire with a libusb device reference and some default values - does not talk to device
|
86
|
+
def initialize devref #:nodoc:
|
87
|
+
@device = devref
|
88
|
+
|
89
|
+
@hardware_pwm_enabled = :unknown
|
90
|
+
@hardware_pwm_prescale = :unknown
|
91
|
+
@hardware_pwm = [0, 0]
|
92
|
+
@software_pwm_enabled = :unknown
|
93
|
+
@software_pwm = [0, 0, 0]
|
94
|
+
|
95
|
+
# shut everything down, trying to setup littlewire in consistent initial state in case previous programs
|
96
|
+
# messed with it's state
|
97
|
+
self.software_pwm_enabled = false
|
98
|
+
self.hardware_pwm_enabled = false
|
99
|
+
self.pin_mode(pin1: :input, pin2: :input, pin3: :input, pin4: :input)
|
100
|
+
self.digital_write(pin1: :gnd, pin2: :gnd, pin3: :gnd, pin4: :gnd)
|
101
|
+
end
|
102
|
+
|
103
|
+
# creates a lambda to close usb device when LittleWire is deallocated, without LittleWire instance closured in to it recursively
|
104
|
+
def self.create_destructor io #:nodoc:
|
105
|
+
lambda do
|
106
|
+
io.close
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Call finished when you're done with the LittleWire to release it for other programs to use. You can always claim it again
|
111
|
+
# later by using any of the methods on this class which communicate over USB
|
112
|
+
def finished
|
113
|
+
if @io
|
114
|
+
ObjectSpace.undefine_finalizer(self) # remove usb closer finalizer
|
115
|
+
@io.close
|
116
|
+
@io = nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# implementations of littlewire functions
|
122
|
+
# - generic requests
|
123
|
+
def echo; control_transfer(function: :echo, dataIn: 8).unpack('S<*'); end # echo's usb request for testing
|
124
|
+
def read; control_transfer(function: :read, wIndex: 0, dataIn: 1); end
|
125
|
+
def write byte; control_transfer(function: :write, wIndex: 0, wValue: byte); end
|
126
|
+
def clear_bit bit; control_transfer(function: :clear_bit, wIndex: 0, wValue: bit); end
|
127
|
+
def set_bit bit; control_transfer(function: :set_bit, wIndex: 0, wValue: bit); end
|
128
|
+
# - programming requests
|
129
|
+
#def power_up sck_period, reset; control_transfer(function: :power_up, wIndex: sck_period, wValue: reset ? 1 : 0); end
|
130
|
+
#def power_down; control_transfer(function: :power_down); end
|
131
|
+
# TODO: maybe spi, poll_bytes, flash_read, flash_write, eprom_read, eeprom_write
|
132
|
+
|
133
|
+
|
134
|
+
# returns version code number (treat it as a hex number)
|
135
|
+
def version_hex
|
136
|
+
@version_hex ||= control_transfer(function: :version, dataIn: 1).unpack('c').first
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns version number of firmware on LittleWire hardware
|
140
|
+
def version
|
141
|
+
@version ||= version_hex.to_s(16).chars.entries.join('.')
|
142
|
+
end
|
143
|
+
|
144
|
+
# get the SPI interface
|
145
|
+
def spi
|
146
|
+
@spi ||= SPI.new(self)
|
147
|
+
end
|
148
|
+
|
149
|
+
# get the I2C interface
|
150
|
+
def i2c
|
151
|
+
@i2c ||= I2C.new(self)
|
152
|
+
end
|
153
|
+
|
154
|
+
# get the 1wire interface (requires firmware 1.1 or newer
|
155
|
+
def one_wire
|
156
|
+
raise "You need to update your LittleWire firmware to version 1.1 to use One Wire" unless version_hex >= 0x11
|
157
|
+
@one_wire ||= OneWire.new(self)
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
# translate calls with arduino-style lowerCamelCase method names in to ruby-style underscored_method_names
|
163
|
+
def method_missing name, *args, &proc
|
164
|
+
underscorized = name.to_s.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2}" }.downcase # make underscored equivilent
|
165
|
+
return send(underscorized, *args, &proc) if respond_to? underscorized # translate casing style if we can find an equivilent
|
166
|
+
|
167
|
+
read_only = name.to_s.gsub('=', '').to_sym
|
168
|
+
if GenericPinMap.has_key? read_only
|
169
|
+
if name.to_s.end_with? '='
|
170
|
+
return (self[read_only] = args.first)
|
171
|
+
else
|
172
|
+
return self[read_only]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
super # default behaviour
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# get the value of something
|
181
|
+
def [] name
|
182
|
+
pin = GenericPinMap[name.to_sym]
|
183
|
+
raise "Unknown Pin '#{name}'" unless pin
|
184
|
+
self.send "#{pin[0]}_read", pin[1]
|
185
|
+
end
|
186
|
+
|
187
|
+
# set the value of something
|
188
|
+
def []= name, value
|
189
|
+
pin = GenericPinMap[name.to_sym]
|
190
|
+
raise "Unknown Pin '#{name}'" unless pin
|
191
|
+
self.send "#{pin[0]}_write", pin[1], value
|
192
|
+
end
|
193
|
+
|
194
|
+
protected
|
195
|
+
# raw opened device
|
196
|
+
def io #:nodoc:
|
197
|
+
unless @io
|
198
|
+
@io = @device.open
|
199
|
+
ObjectSpace.define_finalizer(self, self.class.create_destructor(@io))
|
200
|
+
|
201
|
+
# check for compatible firmware on littlewire device and warn if is unknown
|
202
|
+
warn "Unknown littlewire.cc firmware version #{version} might cause problems" unless SupportedVersions.include? version
|
203
|
+
end
|
204
|
+
@io
|
205
|
+
end
|
206
|
+
|
207
|
+
# functions offered by the LittleWire
|
208
|
+
Functions = [
|
209
|
+
## Generic requests
|
210
|
+
:echo, # echo test 0
|
211
|
+
:read, # read byte (wIndex:address) 1
|
212
|
+
:write, # write byte (wIndex:address, wValue:value) 2
|
213
|
+
:clear_bit, # clear bit (wIndex:address, wValue:bitno) 3
|
214
|
+
:set_bit, # set bit (wIndex:address, wValue:bitno) 4
|
215
|
+
## Programming requests
|
216
|
+
:power_up, # apply power (wValue:SCK-period, wIndex:RESET) 5
|
217
|
+
:power_down, # remove power from chip 6
|
218
|
+
:spi, # issue SPI command (wValue:c1c0, wIndex:c3c2) 7
|
219
|
+
:poll_bytes, # set poll bytes for write (wValue:p1p2) 8
|
220
|
+
:flash_read, # read flash (wIndex:address) 9
|
221
|
+
:flash_write, # write flash (wIndex:address, wValue:timeout) 10
|
222
|
+
:eeprom_read, # read eeprom (wIndex:address) 11
|
223
|
+
:eeprom_write, # write eeprom (wIndex:address, wValue:timeout) 12
|
224
|
+
## Additional requests - ihsanKehribar
|
225
|
+
:pin_set_input, # 13
|
226
|
+
:pin_set_output, # 14
|
227
|
+
:read_adc, # 15
|
228
|
+
:start_pwm, # 16
|
229
|
+
:update_pwm_compare, # 17
|
230
|
+
:pin_set_high, # 18
|
231
|
+
:pin_set_low, # 19
|
232
|
+
:pin_read, # 20
|
233
|
+
:single_spi, # 21
|
234
|
+
:change_pwm_prescale, # 22
|
235
|
+
:setup_spi, # 23
|
236
|
+
:setup_i2c, # 24
|
237
|
+
:i2c_begin_tx, # 25
|
238
|
+
:i2c_add_buffer, # 26
|
239
|
+
:i2c_send_buffer, # 27
|
240
|
+
:spi_add_buffer, # 28
|
241
|
+
:spi_send_buffer, # 29
|
242
|
+
:i2c_request_from, # 30
|
243
|
+
:spi_update_delay, # 31
|
244
|
+
:stop_pwm, # 32
|
245
|
+
:debug_spi, # 33
|
246
|
+
:version, # 34
|
247
|
+
:analog_init, # 35
|
248
|
+
:reserved, :reserved, :reserved, :reserved,
|
249
|
+
:read_buffer, # 40
|
250
|
+
:onewire_reset_pulse, # 41
|
251
|
+
:onewire_send_byte, # 42
|
252
|
+
:onewire_read_byte, # 43
|
253
|
+
:i2c_init, # 44
|
254
|
+
:i2c_begin, # 45
|
255
|
+
:i2c_read, # 46
|
256
|
+
:init_softpwm, # 47
|
257
|
+
:update_softpwm, # 48
|
258
|
+
:i2c_update_delay, # 49
|
259
|
+
:onewire_read_bit, # 50
|
260
|
+
:onewire_write_bit, # 51
|
261
|
+
:pic_24f_programming, # 52 - experimental
|
262
|
+
:pic_24f_sendsix # 53 - experimental
|
263
|
+
# special cases
|
264
|
+
# pic 24f send bytes - request = 0xD*
|
265
|
+
# i2c send multiple messages - request = 0xE* ### experimental ###
|
266
|
+
# spi multiple message send - request = 0xF*
|
267
|
+
]
|
268
|
+
# transfer data between usb device and this program
|
269
|
+
def control_transfer(opts = {}) #:nodoc:
|
270
|
+
opts[:bRequest] = Functions.index(opts.delete(:function)) if opts[:function]
|
271
|
+
io.control_transfer({
|
272
|
+
wIndex: 0,
|
273
|
+
wValue: 0,
|
274
|
+
bmRequestType: usb_request_type(opts),
|
275
|
+
timeout: 5000
|
276
|
+
}.merge opts)
|
277
|
+
end
|
278
|
+
|
279
|
+
# calculate usb request type
|
280
|
+
def usb_request_type opts #:nodoc:
|
281
|
+
c = LIBUSB::Call
|
282
|
+
value = c::RequestTypes[:REQUEST_TYPE_VENDOR] | c::RequestRecipients[:RECIPIENT_DEVICE]
|
283
|
+
value |= c::EndpointDirections[:ENDPOINT_OUT] if opts.has_key? :dataOut
|
284
|
+
value |= c::EndpointDirections[:ENDPOINT_IN] if opts.has_key? :dataIn
|
285
|
+
return value
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
# lookup a pin name in a map and return it's raw identifier
|
290
|
+
def get_pin map, value #:nodoc:
|
291
|
+
value = value.to_sym if value.is_a? String
|
292
|
+
value = map[value] if map.has_key? value
|
293
|
+
value
|
294
|
+
end
|
295
|
+
|
296
|
+
# translate possible literal values in to a boolean true or false (meaning high or low)
|
297
|
+
def get_boolean value #:nodoc:
|
298
|
+
# some exceptions
|
299
|
+
value = false if value == :low or value == 0 or value == nil or value == :off or value == :ground or value == :gnd
|
300
|
+
!! value # double invert value in to boolean form
|
301
|
+
end
|
302
|
+
end
|
data/lib/one-wire.rb
ADDED
data/lib/servo.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module LittleWire::Servo
|
2
|
+
# Get the current andle of a servo connected to a hardware pwm channel as an angle between roughly -90° and +90°
|
3
|
+
def servo_read hardware_pwm_channel
|
4
|
+
value = hardware_pwm_read(hardware_pwm_channel)
|
5
|
+
90 - ((value - 13).to_f * (180.0 / 23.0))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Set a servo connected to a hardware pwm channel to an angle between -90° and +90° inclusive
|
9
|
+
# Note that setting a servo's position automatically enables Hardware PWM - disable hardware pwm when you're done
|
10
|
+
# if you want to use these pins for something else
|
11
|
+
def servo_write hardware_pwm_channel, angle
|
12
|
+
self.hardware_pwm_prescale = 1024 # make sure our PWM is running at the correct frequency
|
13
|
+
|
14
|
+
value = ((angle + 90.0) / (180.0 / 23.0)).round + 13
|
15
|
+
|
16
|
+
hardware_pwm_write(hardware_pwm_channel, value)
|
17
|
+
end
|
18
|
+
end
|
data/lib/software-pwm.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Interface to LittleWire 1.1's software pwm feature
|
2
|
+
module LittleWire::SoftwarePWM
|
3
|
+
# Has the software pwm module been enabled?
|
4
|
+
attr_reader :software_pwm_enabled
|
5
|
+
|
6
|
+
# Set if the software pwm module is enabled or inactive
|
7
|
+
def software_pwm_enabled= value
|
8
|
+
value = !! value # booleanify it
|
9
|
+
if @software_pwm_enabled != value
|
10
|
+
require_software_pwm_available
|
11
|
+
control_transfer(function: :init_softpwm, wValue: value ? 1 : 0)
|
12
|
+
@software_pwm_enabled = value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# An array of current software pwm values
|
17
|
+
def software_pwm; @software_pwm.dup; end
|
18
|
+
|
19
|
+
# Set software pwm to the values of an array - values must be a number between 0 and 255 inclusive
|
20
|
+
def software_pwm= values
|
21
|
+
require_software_pwm_available
|
22
|
+
self.software_pwm_enabled = true
|
23
|
+
|
24
|
+
3.times do |idx|
|
25
|
+
@software_pwm[idx] = values[idx].to_i % 256
|
26
|
+
end
|
27
|
+
|
28
|
+
control_transfer(function: :update_softpwm, wValue: @software_pwm[1] << 8 | @software_pwm[0], wIndex: @software_pwm[2])
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the value of a single software pwm channel
|
32
|
+
def software_pwm_read channel
|
33
|
+
@software_pwm[get_pin(LittleWire::SoftwarePWMPinMap, channel)]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set the value of a single software pwm channel - value must be a number between 0 and 255 inclusive
|
37
|
+
def software_pwm_write channel, value
|
38
|
+
state = self.software_pwm
|
39
|
+
state[get_pin(LittleWire::SoftwarePWMPinMap, channel)] = value
|
40
|
+
self.software_pwm = state
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_software_pwm_available?
|
44
|
+
version_hex >= 0x11
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def require_software_pwm_available
|
49
|
+
raise "Software PWM not available in version #{self.version} firmware" unless is_software_pwm_available?
|
50
|
+
end
|
51
|
+
end
|
data/lib/spi.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class LittleWire::SPI
|
2
|
+
def initialize wire
|
3
|
+
@wire = wire
|
4
|
+
@wire.control_transfer(function: :setup_spi)
|
5
|
+
raise "SPI requires LittleWire firmware 1.1. Yours = #{@wire.version}" unless @wire.version_hex >= 0.11
|
6
|
+
end
|
7
|
+
|
8
|
+
# send and receive a message of up to four bytes
|
9
|
+
def send send, receive, auto_chipselect = false
|
10
|
+
mode = auto_chipselect ? 1 : 0
|
11
|
+
@wire.control_transfer(
|
12
|
+
wRequest: 0xF0 + send.length + (mode << 3),
|
13
|
+
wValue: send.bytes[1] << 8 | send.bytes[0],
|
14
|
+
wIndex: send.bytes[3] << 8 | send.bytes[0]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
# change spi delay setting
|
19
|
+
def delay= number
|
20
|
+
@wire.control_transfer(function: :spi_update_delay, wValue: number)
|
21
|
+
end
|
22
|
+
|
23
|
+
# get debug status
|
24
|
+
def debug
|
25
|
+
@wire.control_transfer(function: :debug_spi, dataIn: 8)
|
26
|
+
@wire.control_transfer(function: :read_buffer, dataIn: 8).unpack('c').first
|
27
|
+
end
|
28
|
+
end
|
data/license.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
I give this code to you, dear reader. Do what you like with it. It's none of my concern! — Bluebie
|
data/readme.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
Little Wire is a little multiheaded animal who pokes in a USB port, and uploads
|
2
|
+
programs to AVR and PIC chips. But Little Wire does so much more - and that's
|
3
|
+
what this library is about. The [littlewire.cc](http://littlewire.cc/) gadget
|
4
|
+
exposes four digital wires and a five volt power supply. Those four wires can
|
5
|
+
each be individually controlled, with three capable of varying brightness of
|
6
|
+
lights, two capable of controlling motors and servos, two 10-bit analog inputs,
|
7
|
+
a temperature sensor, a Serial Peripheral Interface, an i2c interface, and a
|
8
|
+
1-wire interface.
|
9
|
+
|
10
|
+
Eventually littlewire.rb hopes to share fun, simple, principal of least surprise
|
11
|
+
ruby interfaces to all of these.
|
12
|
+
|
13
|
+
|
14
|
+
### a blinky ###
|
15
|
+
|
16
|
+
require 'littlewire'
|
17
|
+
|
18
|
+
wire = LittleWire.connect # connects to the first Little Wire on your computer
|
19
|
+
|
20
|
+
loop do
|
21
|
+
wire.digital_write :pin3, :vcc # connect pin3 to 5v
|
22
|
+
sleep 0.5
|
23
|
+
wire.digital_write :pin3, :gnd # connect pin3 to ground
|
24
|
+
sleep 0.5
|
25
|
+
end
|
26
|
+
|
27
|
+
And so it is that the ruby on the computer did remotely control the Little Wire's
|
28
|
+
digital port. Don't forget a resistor for that LED of yours! If you don't have a
|
29
|
+
resistor handy, add `wire.pin_mode :pin3 => :input` before `loop do` to use
|
30
|
+
Little Wire's internal 20kohm resistor and keep that light shining.
|
31
|
+
|
32
|
+
|
33
|
+
### a philosophy ###
|
34
|
+
|
35
|
+
Little Wire is such a small creature it's possible perhaps to implement every way
|
36
|
+
you might want to use it! Every method name and every symbol. I've tried to do this
|
37
|
+
a bit. My hope is that you'll play with littlewire in irb (or better yet
|
38
|
+
[pry](http://pryrepl.org)) and everything you try just works. To that end, littlewire
|
39
|
+
supports `ruby_style_methods` and `wiringStyleMethods`, and has methods like
|
40
|
+
`digitalWrite(:pin1, true)` - familliar to arduinoers but also syntaxes like
|
41
|
+
`mywire[:d1] = true`. Initialization sequences automatically run the moment you try
|
42
|
+
to use features, and they always try to make sure littlewire is correctly configured
|
43
|
+
for the task at hand, while not making any unnecessary requests. The only thing
|
44
|
+
littlewire.rb doesn't do (yet) is program other chips - use avrdude for that.
|
45
|
+
|
46
|
+
|
47
|
+
### what's it good for? ###
|
48
|
+
|
49
|
+
You could use the three pulse width modulated analog outputs to control an RGB light
|
50
|
+
adjusting the mood of your batcave at the click of a button. Hook it up to displays,
|
51
|
+
memory, sensors, iButtons, RFID readers, digital radios, motors, switches, fairy
|
52
|
+
lights... whatever floats your boat really. The possabilities are not especially
|
53
|
+
limited. Most projects you might use an Arduino for can be done with a Little Wire
|
54
|
+
if you don't mind leaving a computer turned on connected to it, and with the advent
|
55
|
+
of Raspberry Pi, that's not all that bad of an idea. I use my Little Wire to quickly
|
56
|
+
test ideas before changing them to C and uploading them to cheaper attiny chips (also
|
57
|
+
using the Little Wire to program them)
|
58
|
+
|
59
|
+
|
60
|
+
### a warning ###
|
61
|
+
|
62
|
+
littlewire.rb is experimental (till it is released as 1.0 via rubygems) and there's
|
63
|
+
a pretty good chance the API will change a bit until then. Not to worry - rubygems
|
64
|
+
have a mechanism for you to [require a specific version][1] of littlewire.rb,
|
65
|
+
ensuring your programs always work, so you can start building stuff on it today!
|
66
|
+
|
67
|
+
[1]: http://docs.rubygems.org/read/chapter/4#page71 "RubyGems Documentation"
|
68
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: littlewire
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.9'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bluebie
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: libusb
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.2.0
|
30
|
+
description: A little library for a little wire. Providing a pure ruby interface (via
|
31
|
+
the nonpure libusb gem) to littlewire.cc's wonderful gadget. littlewire.rb provides
|
32
|
+
general purpose digital IO, pulse width modulation analog outputs, analog inputs,
|
33
|
+
SPI, I2C, One Wire, and rough servo control via a friendly interface which responds
|
34
|
+
both to familliar Wiring/Arduino style methods and a more concise ruby alternative.
|
35
|
+
email: a@creativepony.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- lib/analog.rb
|
41
|
+
- lib/digital.rb
|
42
|
+
- lib/hardware-pwm.rb
|
43
|
+
- lib/i2c.rb
|
44
|
+
- lib/littlewire.rb
|
45
|
+
- lib/one-wire.rb
|
46
|
+
- lib/servo.rb
|
47
|
+
- lib/software-pwm.rb
|
48
|
+
- lib/spi.rb
|
49
|
+
- readme.md
|
50
|
+
- license.txt
|
51
|
+
- examples/blinky.rb
|
52
|
+
- examples/fadey.rb
|
53
|
+
homepage: http://creativepony.com/littlewire/
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --main
|
58
|
+
- lib/littlewire.rb
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.21
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A tiny library for littlewire.cc usb devices
|
79
|
+
test_files: []
|