|  | @@ -28,7 +28,7 @@ CMD_MOTOR           = 5
 | 
	
		
			
				|  |  |  CMD_READ_FLUX       = 6
 | 
	
		
			
				|  |  |  CMD_WRITE_FLUX      = 7
 | 
	
		
			
				|  |  |  CMD_GET_FLUX_STATUS = 8
 | 
	
		
			
				|  |  | -CMD_GET_READ_INFO   = 9
 | 
	
		
			
				|  |  | +CMD_GET_INDEX_TIMES = 9
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  # Bootloader-specific:
 | 
	
		
			
				|  |  |  CMD_UPDATE          = 1
 | 
	
	
		
			
				|  | @@ -68,9 +68,9 @@ def get_fw_info():
 | 
	
		
			
				|  |  |    return x
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def print_fw_info(info):
 | 
	
		
			
				|  |  | -  (major, minor, max_revs, max_cmd, freq) = info
 | 
	
		
			
				|  |  | +  (major, minor, max_index, max_cmd, freq) = info
 | 
	
		
			
				|  |  |    print("Greaseweazle v%u.%u" % (major, minor))
 | 
	
		
			
				|  |  | -  print("Max revs %u" % (max_revs))
 | 
	
		
			
				|  |  | +  print("Max index timings %u" % (max_index))
 | 
	
		
			
				|  |  |    print("Max cmd %u" % (max_cmd))
 | 
	
		
			
				|  |  |    print("Sample frequency: %.2f MHz" % (freq / 1000000))
 | 
	
		
			
				|  |  |    
 | 
	
	
		
			
				|  | @@ -102,17 +102,20 @@ def set_delays(step_delay = None, seek_settle = None,
 | 
	
		
			
				|  |  |  def motor(state):
 | 
	
		
			
				|  |  |    send_cmd(struct.pack("3B", CMD_MOTOR, 3, int(state)))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def get_read_info():
 | 
	
		
			
				|  |  | -  send_cmd(struct.pack("2B", CMD_GET_READ_INFO, 2))
 | 
	
		
			
				|  |  | +def get_index_times():
 | 
	
		
			
				|  |  | +  send_cmd(struct.pack("2B", CMD_GET_INDEX_TIMES, 2))
 | 
	
		
			
				|  |  |    x = []
 | 
	
		
			
				|  |  | -  for i in range(7):
 | 
	
		
			
				|  |  | -    x.append(struct.unpack("<2I", ser.read(2*4)))
 | 
	
		
			
				|  |  | +  for i in range(15):
 | 
	
		
			
				|  |  | +    x.append(struct.unpack("<I", ser.read(4))[0])
 | 
	
		
			
				|  |  |    return x
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def print_read_info(info):
 | 
	
		
			
				|  |  | -  for (time, samples) in info:
 | 
	
		
			
				|  |  | -    print("%u ticks, %u samples" % (time, samples))
 | 
	
		
			
				|  |  | +def print_index_times(index_times):
 | 
	
		
			
				|  |  | +  for ticks in index_times:
 | 
	
		
			
				|  |  | +    print("%u ticks" % (ticks))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# write_flux:
 | 
	
		
			
				|  |  | +# Write the current track via Greaseweazle with the specified flux timings.
 | 
	
		
			
				|  |  |  def write_flux(flux):
 | 
	
		
			
				|  |  |    start = timer()
 | 
	
		
			
				|  |  |    x = bytearray()
 | 
	
	
		
			
				|  | @@ -152,13 +155,18 @@ def write_flux(flux):
 | 
	
		
			
				|  |  |      end = timer()
 | 
	
		
			
				|  |  |      #print("Track written in %f seconds" % (end-start))
 | 
	
		
			
				|  |  |      break
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | -def read_flux(nr_revs):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# read_flux:
 | 
	
		
			
				|  |  | +# Read flux timings from Greaseweazle for the current track.
 | 
	
		
			
				|  |  | +def read_flux(nr_idx):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  # Read the encoded flux stream, retrying if necessary.
 | 
	
		
			
				|  |  |    retry = 0
 | 
	
		
			
				|  |  |    while True:
 | 
	
		
			
				|  |  |      start = timer()
 | 
	
		
			
				|  |  |      x = collections.deque()
 | 
	
		
			
				|  |  | -    send_cmd(struct.pack("3B", CMD_READ_FLUX, 3, nr_revs))
 | 
	
		
			
				|  |  | +    send_cmd(struct.pack("3B", CMD_READ_FLUX, 3, nr_idx))
 | 
	
		
			
				|  |  |      nr = 0
 | 
	
		
			
				|  |  |      while True:
 | 
	
		
			
				|  |  |        x += ser.read(1)
 | 
	
	
		
			
				|  | @@ -180,6 +188,7 @@ def read_flux(nr_revs):
 | 
	
		
			
				|  |  |      
 | 
	
		
			
				|  |  |    #print("Read %u bytes in %u batches in %f seconds" % (len(x), nr, end-start))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  # Decode the flux stream into a list of flux samples.
 | 
	
		
			
				|  |  |    start = timer()
 | 
	
		
			
				|  |  |    y = []
 | 
	
		
			
				|  |  |    while x:
 | 
	
	
		
			
				|  | @@ -204,8 +213,78 @@ def read_flux(nr_revs):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    return y
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def read(args):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# flux_to_scp:
 | 
	
		
			
				|  |  | +# Converts Greaseweazle flux samples into a Supercard Pro Track.
 | 
	
		
			
				|  |  | +# Returns the Track Data Header (TDH) and the SCP "bitcell" array.
 | 
	
		
			
				|  |  | +def flux_to_scp(flux, track, nr_revs):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    factor = scp_freq / sample_freq
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  index_times = get_index_times()[:nr_revs+1]
 | 
	
		
			
				|  |  | +  tdh = struct.pack("<3sB", b"TRK", track)
 | 
	
		
			
				|  |  | +  dat = bytearray()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  len_at_index = rev = 0
 | 
	
		
			
				|  |  | +  to_index = index_times[0]
 | 
	
		
			
				|  |  | +  rem = 0.0
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  for x in flux:
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # Are we processing initial samples before the first revolution?
 | 
	
		
			
				|  |  | +    if rev == 0:
 | 
	
		
			
				|  |  | +      if to_index >= x:
 | 
	
		
			
				|  |  | +        # Discard initial samples
 | 
	
		
			
				|  |  | +        to_index -= x
 | 
	
		
			
				|  |  | +        continue
 | 
	
		
			
				|  |  | +      # Now starting the first full revolution
 | 
	
		
			
				|  |  | +      rev = 1
 | 
	
		
			
				|  |  | +      to_index += index_times[rev]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # Does the next flux interval cross the index mark?  
 | 
	
		
			
				|  |  | +    while to_index < x:
 | 
	
		
			
				|  |  | +      # Append to the Track Data Header for the previous full revolution
 | 
	
		
			
				|  |  | +      tdh += struct.pack("<III",
 | 
	
		
			
				|  |  | +                         int(round(index_times[rev]*factor)),
 | 
	
		
			
				|  |  | +                         (len(dat) - len_at_index) // 2,
 | 
	
		
			
				|  |  | +                         4 + nr_revs*12 + len_at_index)
 | 
	
		
			
				|  |  | +      # Set up for the next revolution
 | 
	
		
			
				|  |  | +      len_at_index = len(dat)
 | 
	
		
			
				|  |  | +      rev += 1
 | 
	
		
			
				|  |  | +      if rev > nr_revs:
 | 
	
		
			
				|  |  | +        # We're done: We simply discard any surplus flux samples
 | 
	
		
			
				|  |  | +        return (tdh, dat)
 | 
	
		
			
				|  |  | +      to_index += index_times[rev]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    # Process the current flux sample into SCP "bitcell" format
 | 
	
		
			
				|  |  | +    to_index -= x
 | 
	
		
			
				|  |  | +    y = x * factor + rem
 | 
	
		
			
				|  |  | +    val = int(round(y))
 | 
	
		
			
				|  |  | +    if (val & 65535) == 0:
 | 
	
		
			
				|  |  | +      val += 1
 | 
	
		
			
				|  |  | +    rem = y - val
 | 
	
		
			
				|  |  | +    while val >= 65536:
 | 
	
		
			
				|  |  | +      dat.append(0)
 | 
	
		
			
				|  |  | +      dat.append(0)
 | 
	
		
			
				|  |  | +      val -= 65536
 | 
	
		
			
				|  |  | +    dat.append(val>>8)
 | 
	
		
			
				|  |  | +    dat.append(val&255)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  # Header for last track(s) in case we ran out of flux timings.
 | 
	
		
			
				|  |  | +  while rev <= nr_revs:
 | 
	
		
			
				|  |  | +    tdh += struct.pack("<III",
 | 
	
		
			
				|  |  | +                       int(round(index_times[rev]*factor)),
 | 
	
		
			
				|  |  | +                       (len(dat) - len_at_index) // 2,
 | 
	
		
			
				|  |  | +                       4 + nr_revs*12 + len_at_index)
 | 
	
		
			
				|  |  | +    len_at_index = len(dat)
 | 
	
		
			
				|  |  | +    rev += 1
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +  return (tdh, dat)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# read_to_scp:
 | 
	
		
			
				|  |  | +# Reads a floppy disk and dumps it into a new Supercard Pro image file.
 | 
	
		
			
				|  |  | +def read_to_scp(args):
 | 
	
		
			
				|  |  |    trk_dat = bytearray()
 | 
	
		
			
				|  |  |    trk_offs = []
 | 
	
		
			
				|  |  |    if args.single_sided:
 | 
	
	
		
			
				|  | @@ -214,34 +293,16 @@ def read(args):
 | 
	
		
			
				|  |  |    else:
 | 
	
		
			
				|  |  |      track_range = range(args.scyl*2, (args.ecyl+1)*2)
 | 
	
		
			
				|  |  |      nr_sides = 2
 | 
	
		
			
				|  |  | -  for i in track_range:
 | 
	
		
			
				|  |  | -    cyl = i >> (nr_sides - 1)
 | 
	
		
			
				|  |  | -    side = i & (nr_sides - 1)
 | 
	
		
			
				|  |  | +  for track in track_range:
 | 
	
		
			
				|  |  | +    cyl = track >> (nr_sides - 1)
 | 
	
		
			
				|  |  | +    side = track & (nr_sides - 1)
 | 
	
		
			
				|  |  |      print("\rReading Track %u.%u..." % (cyl, side), end="")
 | 
	
		
			
				|  |  |      trk_offs.append(len(trk_dat))
 | 
	
		
			
				|  |  |      seek(cyl, side)
 | 
	
		
			
				|  |  | -    flux = read_flux(args.revs)
 | 
	
		
			
				|  |  | -    info = get_read_info()[:args.revs]
 | 
	
		
			
				|  |  | -    #print_read_info(info)
 | 
	
		
			
				|  |  | -    trk_dat += struct.pack("<3sB", b"TRK", i)
 | 
	
		
			
				|  |  | -    dat_off = 4 + args.revs*12
 | 
	
		
			
				|  |  | -    for (time, samples) in info:
 | 
	
		
			
				|  |  | -      time = int(round(time * factor))
 | 
	
		
			
				|  |  | -      trk_dat += struct.pack("<III", time, samples, dat_off)
 | 
	
		
			
				|  |  | -      dat_off += samples * 2
 | 
	
		
			
				|  |  | -    rem = 0.0
 | 
	
		
			
				|  |  | -    for x in flux:
 | 
	
		
			
				|  |  | -      y = x * factor + rem
 | 
	
		
			
				|  |  | -      val = int(round(y))
 | 
	
		
			
				|  |  | -      if (val & 65535) == 0:
 | 
	
		
			
				|  |  | -        val += 1
 | 
	
		
			
				|  |  | -      rem = y - val
 | 
	
		
			
				|  |  | -      while val >= 65536:
 | 
	
		
			
				|  |  | -        trk_dat.append(0)
 | 
	
		
			
				|  |  | -        trk_dat.append(0)
 | 
	
		
			
				|  |  | -        val -= 65536
 | 
	
		
			
				|  |  | -      trk_dat.append(val>>8)
 | 
	
		
			
				|  |  | -      trk_dat.append(val&255)
 | 
	
		
			
				|  |  | +    flux = read_flux(args.revs+1)
 | 
	
		
			
				|  |  | +    (tdh, dat) = flux_to_scp(flux, track, args.revs)
 | 
	
		
			
				|  |  | +    trk_dat += tdh
 | 
	
		
			
				|  |  | +    trk_dat += dat
 | 
	
		
			
				|  |  |    print()
 | 
	
		
			
				|  |  |    csum = 0
 | 
	
		
			
				|  |  |    for x in trk_dat:
 | 
	
	
		
			
				|  | @@ -272,7 +333,10 @@ def read(args):
 | 
	
		
			
				|  |  |      f.write(trk_offs_dat)
 | 
	
		
			
				|  |  |      f.write(trk_dat)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def write(args):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# write_from_scp:
 | 
	
		
			
				|  |  | +# Writes the specified Supercard Pro image file to floppy disk.
 | 
	
		
			
				|  |  | +def write_from_scp(args):
 | 
	
		
			
				|  |  |    factor = sample_freq / scp_freq
 | 
	
		
			
				|  |  |    with open(args.file, "rb") as f:
 | 
	
		
			
				|  |  |      dat = f.read()
 | 
	
	
		
			
				|  | @@ -310,7 +374,10 @@ def write(args):
 | 
	
		
			
				|  |  |      write_flux(flux)
 | 
	
		
			
				|  |  |    print()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def update(args):
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# update_firmware:
 | 
	
		
			
				|  |  | +# Updates the Greaseweazle firmware using the specified Update File.
 | 
	
		
			
				|  |  | +def update_firmware(args):
 | 
	
		
			
				|  |  |    with open(args.file, "rb") as f:
 | 
	
		
			
				|  |  |      dat = f.read()
 | 
	
		
			
				|  |  |    (sig, maj, min, pad1, pad2, crc) = struct.unpack(">2s4BH", dat[-8:])
 | 
	
	
		
			
				|  | @@ -331,12 +398,15 @@ def update(args):
 | 
	
		
			
				|  |  |    print("Done.")
 | 
	
		
			
				|  |  |    print("** Disconnect Greaseweazle and remove the Programming Jumper.")
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +# _main:
 | 
	
		
			
				|  |  | +# Argument processing and dispatch.
 | 
	
		
			
				|  |  |  def _main(argv):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    actions = {
 | 
	
		
			
				|  |  | -    "read" : read,
 | 
	
		
			
				|  |  | -    "write" : write,
 | 
	
		
			
				|  |  | -    "update" : update
 | 
	
		
			
				|  |  | +    "read" : read_to_scp,
 | 
	
		
			
				|  |  | +    "write" : write_from_scp,
 | 
	
		
			
				|  |  | +    "update" : update_firmware
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    
 | 
	
		
			
				|  |  |    parser = argparse.ArgumentParser(
 | 
	
	
		
			
				|  | @@ -349,8 +419,6 @@ def _main(argv):
 | 
	
		
			
				|  |  |    parser.add_argument("--ecyl", type=int, default=81,
 | 
	
		
			
				|  |  |                        help="last cylinder to read/write")
 | 
	
		
			
				|  |  |    parser.add_argument("--single-sided", action="store_true")
 | 
	
		
			
				|  |  | -#  parser.add_argument("--total", type=float, default=8.0,
 | 
	
		
			
				|  |  | -#                      help="total length, seconds")
 | 
	
		
			
				|  |  |    parser.add_argument("file", help="in/out filename")
 | 
	
		
			
				|  |  |    parser.add_argument("device", help="serial device")
 | 
	
		
			
				|  |  |    args = parser.parse_args(argv[1:])
 | 
	
	
		
			
				|  | @@ -406,11 +474,13 @@ def _main(argv):
 | 
	
		
			
				|  |  |    if not update_mode:
 | 
	
		
			
				|  |  |      motor(False)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  def main(argv):
 | 
	
		
			
				|  |  |    try:
 | 
	
		
			
				|  |  |      _main(argv)
 | 
	
		
			
				|  |  |    except CmdError as error:
 | 
	
		
			
				|  |  |      print("Command Failed: %s" % error)
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  if __name__ == "__main__":
 | 
	
		
			
				|  |  |    main(sys.argv)
 |