From 4ab39b1d0cdcbac838d2d15acd532493a33a3764 Mon Sep 17 00:00:00 2001 From: Jenna Fox Date: Sun, 30 Sep 2012 14:22:04 +1000 Subject: cleaning up directory structures --- ruby/dump.rb | 3 + ruby/micronucleus.rb | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++ ruby/upload.rb | 33 ++++++++++ 3 files changed, 202 insertions(+) create mode 100644 ruby/dump.rb create mode 100644 ruby/micronucleus.rb create mode 100644 ruby/upload.rb (limited to 'ruby') diff --git a/ruby/dump.rb b/ruby/dump.rb new file mode 100644 index 0000000..471d6d4 --- /dev/null +++ b/ruby/dump.rb @@ -0,0 +1,3 @@ +`rm temp-dump.bin` +`avrdude -c usbtiny -p t85 -U flash:r:temp-dump.bin:r` +puts open('temp-dump.bin').read(30).bytes.to_a.map { |x| x.to_s(16) }.join(' ') diff --git a/ruby/micronucleus.rb b/ruby/micronucleus.rb new file mode 100644 index 0000000..3211d84 --- /dev/null +++ b/ruby/micronucleus.rb @@ -0,0 +1,166 @@ +require 'libusb' + +# Abstracts access to micronucleus avr tiny85 bootloader - can be used only to erase and upload bytes +class Micronucleus + Functions = [ + :get_info, + :write_page, + :erase_application, + :run_program + ] + + # return all micronucleus devices connected to computer + def self.all + usb = LIBUSB::Context.new + usb.devices.select { |device| + device.idVendor == 0x16d0 && device.idProduct == 0x0753 + }.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| + slice.push(0xFF) while slice.length < info[:page_size] # ensure every slice is one page_size long - pad out if needed + + puts "uploading @ #{address} of #{bytes.length}" + control_transfer(function: :write_page, wIndex: address, wValue: slice.length, dataOut: slice.pack('C*')) + 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 + "" + 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 + + # calculate usb request type + def usb_request_type opts #:nodoc: + value = LIBUSB::REQUEST_TYPE_VENDOR | LIBUSB::RECIPIENT_DEVICE + value |= LIBUSB::ENDPOINT_OUT if opts.has_key? :dataOut + value |= LIBUSB::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/ruby/upload.rb b/ruby/upload.rb new file mode 100644 index 0000000..32aefa9 --- /dev/null +++ b/ruby/upload.rb @@ -0,0 +1,33 @@ +require_relative './micronucleus' + +if ARGV[0] + if ARGV[0].end_with? '.hex' + puts "parsing input file as intel hex" + test_data = HexProgram.new(open ARGV[0]).binary + else + puts "parsing input file as raw binary" + test_data = open(ARGV[0]).read + end +else + raise "Pass intel hex or raw binary as argument to script" +end + +#test_data += ("\xFF" * 64) + +puts "Plug in programmable device now: (waiting)" +sleep 0.25 while Micronucleus.all.length == 0 + +nucleus = Micronucleus.all.first +puts "Attached to device: #{nucleus.inspect}" + +#puts "Attempting to write '#{test_data.inspect}' to first thinklet's program memory" +#puts "Bytes: #{test_data.bytes.to_a.inspect}" +sleep(0.25) # some time to think? +puts "Attempting to write supplied program in to device's memory" +nucleus.program = test_data + +puts "Great! Starting program..." + + +nucleus.finished # let thinklet know it can go do other things now if it likes +puts "All done!" -- cgit v1.2.3