diff options
| -rw-r--r-- | firmware/microboot.rb | 167 | ||||
| -rw-r--r-- | firmware/upload.rb | 169 | 
2 files changed, 169 insertions, 167 deletions
| diff --git a/firmware/microboot.rb b/firmware/microboot.rb new file mode 100644 index 0000000..0b3ec58 --- /dev/null +++ b/firmware/microboot.rb @@ -0,0 +1,167 @@ +require 'libusb' + +# Abstracts access to uBoot avr tiny85 bootloader - can be used only to upload bytes +class MicroBoot +  Functions = [ +    :get_info, +    :write_page, +    :erase_application, +    :run_program +  ] + +  # return all thinklets +  def self.all +    usb = LIBUSB::Context.new +    usb.devices.select { |device| +      device.idVendor == 0x16d0 && device.idProduct == 0x0753 #&& device.product == "\xB5B" +    }.map { |device| +      self.new(device) +    } +  end + +  def initialize devref +    @device = devref +  end + +  def info +    unless @info +      result = control_transfer(function: :get_info, dataIn: 4) +      flash_length, page_size, write_sleep = result.unpack('S>CC') + +      @info = { +        flash_length: flash_length, +        page_size: page_size, +        pages: (flash_length.to_f / page_size).ceil, +        write_sleep: write_sleep.to_f / 1000.0, +        version: "#{@device.bcdDevice >> 8}.#{@device.bcdDevice & 0xFF}", +        version_numeric: @device.bcdDevice +      } +    end + +    @info +  end + +  def erase! +    puts "erasing" +    info = self.info +    control_transfer(function: :erase_application) +    info[:pages].times do +      sleep(info[:write_sleep]) # sleep for as many pages as the chip has to erase +    end +    puts "erased chip" +  end + +  # upload a new program +  def program= bytestring +    info = self.info +    raise "Program too long!" if bytestring.bytesize > info[:flash_length] +    bytes = bytestring.bytes.to_a + +    erase! + +    address = 0 +    bytes.each_slice(info[:page_size]) do |slice| +      puts "uploading @ #{address} of #{bytes.length}" +      control_transfer(function: :write_page, wIndex: address, wValue: slice.length, dataOut: slice.pack('C*')) +      sleep(info[:write_sleep]) if address == 0 # additional sleep just for first page, as it includes an erase too +      sleep(info[:write_sleep]) +      address += slice.length +    end +  end + +  def finished +    info = self.info + +    puts "asking device to finish writing" +    control_transfer(function: :run_program) +    puts "waiting for device to finish" + +    # sleep for as many pages as the chip could potentially need to write - this could be smarter +    info[:pages].times do +      sleep(info[:write_sleep])  +    end + +    @io.close +    @io = nil +  end + +  def inspect +    "<MicroBoot #{info[:version]}: #{(info[:flash_length] / 1024.0).round(1)} kb programmable>" +  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 + +class HexProgram +  def initialize input +    @bytes = Hash.new(0xFF) +    input = input.read if input.is_a? IO +    parse input +  end + +  def binary +    highest_address = @bytes.keys.max + +    bytestring = Array.new(highest_address + 1) { |index| +      @bytes[index] +    }.pack('C*') +  end + +  protected + +  def parse input_text +    input_text.each_line do |line| +      next unless line.start_with? ':' +      line.chomp! +      length = line[1..2].to_i(16) # usually 16 or 32 +      address = line[3..6].to_i(16) # 16-bit start address +      record_type = line[7..8].to_i(16) +      data = line[9.. 9 + (length * 2)] +      checksum = line[9 + (length * 2).. 10 + (length * 2)].to_i(16) +      checksum_section = line[1...9 + (length * 2)] + +      checksum_calculated = checksum_section.chars.to_a.each_slice(2).map { |slice| +        slice.join('').to_i(16) +      }.reduce(0, &:+) + +      checksum_calculated = (((checksum_calculated % 256) ^ 0xFF) + 1) % 256 + +      raise "Hex file checksum mismatch @ #{line}" unless checksum == checksum_calculated + +      if record_type == 0 # data record +        data_bytes = data.chars.each_slice(2).map { |slice| slice.join('').to_i(16) } +        data_bytes.each_with_index do |byte, index| +          @bytes[address + index] = byte +        end +      end +    end +  end +end
\ No newline at end of file diff --git a/firmware/upload.rb b/firmware/upload.rb index abfd15e..705f7de 100644 --- a/firmware/upload.rb +++ b/firmware/upload.rb @@ -1,170 +1,4 @@ -require 'libusb' - -# Abstracts access to uBoot avr tiny85 bootloader - can be used only to upload bytes -class MicroBoot -  Functions = [ -    :get_info, -    :write_page, -    :erase_application, -    :run_program -  ] -   -  # return all thinklets -  def self.all -    usb = LIBUSB::Context.new -    usb.devices.select { |device| -      device.idVendor == 0x16d0 && device.idProduct == 0x0753 #&& device.product == "\xB5B" -    }.map { |device| -      self.new(device) -    } -  end - -  def initialize devref -    @device = devref -  end - -  def info -    unless @info -      result = control_transfer(function: :get_info, dataIn: 4) -      flash_length, page_size, write_sleep = result.unpack('S>CC') - -      @info = { -        flash_length: flash_length, -        page_size: page_size, -        pages: (flash_length.to_f / page_size).ceil, -        write_sleep: write_sleep.to_f / 1000.0, -        version: "#{@device.bcdDevice >> 8}.#{@device.bcdDevice & 0xFF}", -        version_numeric: @device.bcdDevice -      } -    end -     -    @info -  end -   -  def erase! -    puts "erasing" -    info = self.info -    control_transfer(function: :erase_application) -    info[:pages].times do -      sleep(info[:write_sleep]) # sleep for as many pages as the chip has to erase -    end -    puts "erased chip" -  end -   -  # upload a new program -  def program= bytestring -    info = self.info -    raise "Program too long!" if bytestring.bytesize > info[:flash_length] -    bytes = bytestring.bytes.to_a -     -    erase! -     -    address = 0 -    bytes.each_slice(info[:page_size]) do |slice| -      puts "uploading @ #{address} of #{bytes.length}" -      control_transfer(function: :write_page, wIndex: address, wValue: slice.length, dataOut: slice.pack('C*')) -      sleep(info[:write_sleep]) if address == 0 # additional sleep just for first page, as it includes an erase too -      sleep(info[:write_sleep]) -      address += slice.length -    end -  end -   -  def finished -    info = self.info -     -    puts "asking device to finish writing" -    control_transfer(function: :run_program) -    puts "waiting for device to finish" -     -    # sleep for as many pages as the chip could potentially need to write - this could be smarter -    info[:pages].times do -      sleep(info[:write_sleep])  -    end -     -    @io.close -    @io = nil -  end -   -  def inspect -    "<MicroBoot #{info[:version]}: #{(info[:flash_length] / 1024.0).round(1)} kb programmable>" -  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 - -class HexProgram -  def initialize input -    @bytes = Hash.new(0xFF) -    input = input.read if input.is_a? IO -    parse input -  end -   -  def binary -    highest_address = @bytes.keys.max -     -    bytestring = Array.new(highest_address + 1) { |index| -      @bytes[index] -    }.pack('C*') -  end -   -  protected -   -  def parse input_text -    input_text.each_line do |line| -      next unless line.start_with? ':' -      line.chomp! -      length = line[1..2].to_i(16) # usually 16 or 32 -      address = line[3..6].to_i(16) # 16-bit start address -      record_type = line[7..8].to_i(16) -      data = line[9.. 9 + (length * 2)] -      checksum = line[9 + (length * 2).. 10 + (length * 2)].to_i(16) -      checksum_section = line[1...9 + (length * 2)] -       -      checksum_calculated = checksum_section.chars.to_a.each_slice(2).map { |slice| -        slice.join('').to_i(16) -      }.reduce(0, &:+) -       -      checksum_calculated = (((checksum_calculated % 256) ^ 0xFF) + 1) % 256 -       -      raise "Hex file checksum mismatch @ #{line}" unless checksum == checksum_calculated -       -      if record_type == 0 # data record -        data_bytes = data.chars.each_slice(2).map { |slice| slice.join('').to_i(16) } -        data_bytes.each_with_index do |byte, index| -          @bytes[address + index] = byte -        end -      end -    end -  end -end +require_relative './microboot'  if ARGV[0]    if ARGV[0].end_with? '.hex' @@ -186,6 +20,7 @@ puts "Attached to device: #{thinklet.inspect}"  #puts "Attempting to write '#{test_data.inspect}' to first thinklet's program memory"  #puts "Bytes: #{test_data.bytes.to_a.inspect}" +sleep(0.1) # some time to think?  puts "Attempting to write supplied program in to device's memory"  thinklet.program = test_data | 
