littlewire 0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|