summaryrefslogtreecommitdiffstats
path: root/firmware/upload.rb
blob: c68b02cad47f6a347574b3651fface33ccf2f0e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
require 'libusb'

# Abstracts access to uBoot avr tiny85 bootloader - can be used only to upload bytes
class MicroBoot
  Functions = [
    :get_info,
    :write_page,
    :run_program
  ]
  
  # return all thinklets
  def self.all
    usb = LIBUSB::Context.new
    usb.devices.select { |device|
      device.product == 'uBoot'
    }.map { |device|
      self.new(device)
    }
  end

  def initialize devref
    @device = devref
  end

  def info
    unless defined? @info
      result = control_transfer(function: :get_info, dataIn: 5)
      version, flash_length, page_size, write_sleep = result.unpack('CS>CC')

      @info = {
        flash_length: flash_length,
        page_size: page_size,
        version: version,
        write_sleep: write_sleep.to_f / 1000.0
      }
    end
    @info
  end
  
  # upload a new program
  def program= bytestring
    raise "Program too long!" if bytestring.bytesize > info[:flash_length]
    bytes = bytestring.bytes.to_a
    
    address = 0
    bytes.each_slice(info[:page_size]) do |bytes|
      control_transfer(function: :write_page, wIndex: address, wValue: bytes.length, dataOut: bytes.pack('C*'))
      sleep(info[:write_sleep])
      address += bytes.length
    end
  end
  
  def finished
    control_transfer(function: :run_program)
    sleep(info[:write_sleep]) # not sure if this is worth having? It's okay if USB fails now...
    
    @io.close
    @io = nil
  end

  protected
  # raw opened device
  def io
    unless @io
      @io = @device.open
    end

    @io
  end
  
  def control_transfer(opts = {})
    opts[:bRequest] = Functions.index(opts.delete(:function)) if opts[:function]
    io.control_transfer({
      wIndex: 0,
      wValue: 0,
      bmRequestType: usb_request_type(opts),
      timeout: 5000
    }.merge opts)
  end



  def usb_request_type opts
    c = LIBUSB::Call
    value = c::RequestTypes[:REQUEST_TYPE_VENDOR] | c::RequestRecipients[:RECIPIENT_DEVICE]
    value |= c::EndpointDirections[:ENDPOINT_OUT] if opts.has_key? :dataOut
    value |= c::EndpointDirections[:ENDPOINT_IN] if opts.has_key? :dataIn
    return value
  end
end

puts "Finding devices"
thinklets = MicroBoot.all
puts "Found #{thinklets.length} thinklet"
exit unless thinklets.length > 0

thinklet = thinklets.first

puts "First thinklet: #{thinklet.info.inspect}"

test_data = ("---- Hello World! ----" * 1).encode("BINARY")
puts "Attempting to write '#{test_data}' to first thinklet's program memory"
thinklet.program = test_data

puts "That seems to have gone well! Telling thinklet to run program..."

sleep(0.5)

thinklet.finished # let thinklet know it can go do other things now if it likes
puts "All done!"