gw.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. # gw.py
  2. #
  3. # Greaseweazle control script.
  4. #
  5. # Written & released by Keir Fraser <keir.xen@gmail.com>
  6. #
  7. # This is free and unencumbered software released into the public domain.
  8. # See the file COPYING for more details, or visit <http://unlicense.org>.
  9. import crcmod.predefined
  10. import sys, struct, argparse, serial, collections
  11. from timeit import default_timer as timer
  12. from greaseweazle import version
  13. # 40MHz
  14. scp_freq = 40000000
  15. BAUD_CLEAR_COMMS = 10000
  16. BAUD_NORMAL = 9600
  17. CMD_GET_INFO = 0
  18. CMD_SEEK = 1
  19. CMD_SIDE = 2
  20. CMD_SET_PARAMS = 3
  21. CMD_GET_PARAMS = 4
  22. CMD_MOTOR = 5
  23. CMD_READ_FLUX = 6
  24. CMD_WRITE_FLUX = 7
  25. CMD_GET_FLUX_STATUS = 8
  26. CMD_GET_INDEX_TIMES = 9
  27. CMD_SELECT = 10
  28. # Bootloader-specific:
  29. CMD_UPDATE = 1
  30. ACK_OKAY = 0
  31. ACK_BAD_COMMAND = 1
  32. ACK_NO_INDEX = 2
  33. ACK_NO_TRK0 = 3
  34. ACK_FLUX_OVERFLOW = 4
  35. ACK_FLUX_UNDERFLOW = 5
  36. ACK_WRPROT = 6
  37. ACK_MAX = 6
  38. # CMD_{GET,SET}_PARAMS:
  39. PARAMS_DELAYS = 0
  40. ack_str = [
  41. "Okay", "Bad Command", "No Index", "Track 0 not found",
  42. "Flux Overflow", "Flux Underflow", "Disk is Write Protected" ]
  43. class CmdError(Exception):
  44. def __init__(self, cmd, code):
  45. self.cmd = cmd
  46. self.code = code
  47. def __str__(self):
  48. if self.code <= ACK_MAX:
  49. return ack_str[self.code]
  50. return "Unknown Error (%u)" % self.code
  51. def send_cmd(cmd):
  52. ser.write(cmd)
  53. (c,r) = struct.unpack("2B", ser.read(2))
  54. assert c == cmd[0]
  55. if r != 0:
  56. raise CmdError(c,r)
  57. def get_fw_info():
  58. send_cmd(struct.pack("4B", CMD_GET_INFO, 4, 0, 8))
  59. return struct.unpack("<4BI", ser.read(8))
  60. def print_fw_info(info):
  61. (major, minor, max_index, max_cmd, freq) = info
  62. print("Greaseweazle v%u.%u" % (major, minor))
  63. print("Max index timings %u" % (max_index))
  64. print("Max cmd %u" % (max_cmd))
  65. print("Sample frequency: %.2f MHz" % (freq / 1000000))
  66. def seek(cyl, side):
  67. send_cmd(struct.pack("3B", CMD_SEEK, 3, cyl))
  68. send_cmd(struct.pack("3B", CMD_SIDE, 3, side))
  69. def get_delays():
  70. send_cmd(struct.pack("4B", CMD_GET_PARAMS, 4, PARAMS_DELAYS, 5*2))
  71. return struct.unpack("<5H", ser.read(5*2))
  72. def print_delays(x):
  73. (select_delay, step_delay, seek_settle, motor_delay, auto_off) = x
  74. print("Select Delay: %uus" % select_delay)
  75. print("Step Delay: %uus" % step_delay)
  76. print("Settle Time: %ums" % seek_settle)
  77. print("Motor Delay: %ums" % motor_delay)
  78. print("Auto Off: %ums" % auto_off)
  79. def set_delays(seek_delay = None, step_delay = None, seek_settle = None,
  80. motor_delay = None, auto_off = None):
  81. (_seek_delay, _step_delay, _seek_settle,
  82. _motor_delay, _auto_off) = get_delays()
  83. if not seek_delay: seek_delay = _seek_delay
  84. if not step_delay: step_delay = _step_delay
  85. if not seek_settle: seek_settle = _seek_settle
  86. if not motor_delay: motor_delay = _motor_delay
  87. if not auto_off: auto_off = _auto_off
  88. send_cmd(struct.pack("<3B5H", CMD_SET_PARAMS, 3+5*2, PARAMS_DELAYS,
  89. seek_delay, step_delay, seek_settle,
  90. motor_delay, auto_off))
  91. def drive_select(state):
  92. send_cmd(struct.pack("3B", CMD_SELECT, 3, int(state)))
  93. def drive_motor(state):
  94. send_cmd(struct.pack("3B", CMD_MOTOR, 3, int(state)))
  95. def get_index_times(nr):
  96. send_cmd(struct.pack("4B", CMD_GET_INDEX_TIMES, 4, 0, nr))
  97. x = struct.unpack("<%dI" % nr, ser.read(4*nr))
  98. return x
  99. def print_index_times(index_times):
  100. for ticks in index_times:
  101. print("%u ticks" % (ticks))
  102. # write_flux:
  103. # Write the current track via Greaseweazle with the specified flux timings.
  104. def write_flux(flux):
  105. start = timer()
  106. x = bytearray()
  107. for val in flux:
  108. if val == 0:
  109. pass
  110. elif val < 250:
  111. x.append(val)
  112. else:
  113. high = val // 250
  114. if high <= 5:
  115. x.append(249+high)
  116. x.append(1 + val%250)
  117. else:
  118. x.append(255)
  119. x.append(1 | (val<<1) & 255)
  120. x.append(1 | (val>>6) & 255)
  121. x.append(1 | (val>>13) & 255)
  122. x.append(1 | (val>>20) & 255)
  123. x.append(0) # End of Stream
  124. end = timer()
  125. #print("%u flux -> %u bytes in %f seconds" % (len(flux), len(x), end-start))
  126. retry = 0
  127. while True:
  128. start = timer()
  129. send_cmd(struct.pack("<2BIB", CMD_WRITE_FLUX, 7, 0, 1))
  130. ser.write(x)
  131. ser.read(1) # Sync with Greaseweazle
  132. try:
  133. send_cmd(struct.pack("2B", CMD_GET_FLUX_STATUS, 2))
  134. except CmdError as error:
  135. if error.code == ACK_FLUX_UNDERFLOW and retry < 5:
  136. retry += 1
  137. print("Retry #%u..." % retry)
  138. continue;
  139. raise
  140. end = timer()
  141. #print("Track written in %f seconds" % (end-start))
  142. break
  143. # read_flux:
  144. # Read flux timings from Greaseweazle for the current track.
  145. def read_flux(nr_idx):
  146. # Read the encoded flux stream, retrying if necessary.
  147. retry = 0
  148. while True:
  149. start = timer()
  150. x = collections.deque()
  151. send_cmd(struct.pack("3B", CMD_READ_FLUX, 3, nr_idx))
  152. nr = 0
  153. while True:
  154. x += ser.read(1)
  155. x += ser.read(ser.in_waiting)
  156. nr += 1;
  157. if x[-1] == 0:
  158. break
  159. try:
  160. send_cmd(struct.pack("2B", CMD_GET_FLUX_STATUS, 2))
  161. except CmdError as error:
  162. if error.code == ACK_FLUX_OVERFLOW and retry < 5:
  163. retry += 1
  164. print("Retry #%u..." % retry)
  165. del x
  166. continue;
  167. raise
  168. end = timer()
  169. break
  170. #print("Read %u bytes in %u batches in %f seconds" % (len(x), nr, end-start))
  171. # Decode the flux stream into a list of flux samples.
  172. start = timer()
  173. y = []
  174. while x:
  175. i = x.popleft()
  176. if i < 250:
  177. y.append(i)
  178. elif i == 255:
  179. val = (x.popleft() & 254) >> 1
  180. val += (x.popleft() & 254) << 6
  181. val += (x.popleft() & 254) << 13
  182. val += (x.popleft() & 254) << 20
  183. y.append(val)
  184. else:
  185. val = (i - 249) * 250
  186. val += x.popleft() - 1
  187. y.append(val)
  188. assert y[-1] == 0
  189. y = y[:-1]
  190. end = timer()
  191. #print("Processed %u flux values in %f seconds" % (len(y), end-start))
  192. return y
  193. # flux_to_scp:
  194. # Converts Greaseweazle flux samples into a Supercard Pro Track.
  195. # Returns the Track Data Header (TDH) and the SCP "bitcell" array.
  196. def flux_to_scp(flux, track, nr_revs):
  197. factor = scp_freq / sample_freq
  198. index_times = get_index_times(nr_revs+1)
  199. tdh = struct.pack("<3sB", b"TRK", track)
  200. dat = bytearray()
  201. len_at_index = rev = 0
  202. to_index = index_times[0]
  203. rem = 0.0
  204. for x in flux:
  205. # Are we processing initial samples before the first revolution?
  206. if rev == 0:
  207. if to_index >= x:
  208. # Discard initial samples
  209. to_index -= x
  210. continue
  211. # Now starting the first full revolution
  212. rev = 1
  213. to_index += index_times[rev]
  214. # Does the next flux interval cross the index mark?
  215. while to_index < x:
  216. # Append to the Track Data Header for the previous full revolution
  217. tdh += struct.pack("<III",
  218. int(round(index_times[rev]*factor)),
  219. (len(dat) - len_at_index) // 2,
  220. 4 + nr_revs*12 + len_at_index)
  221. # Set up for the next revolution
  222. len_at_index = len(dat)
  223. rev += 1
  224. if rev > nr_revs:
  225. # We're done: We simply discard any surplus flux samples
  226. return (tdh, dat)
  227. to_index += index_times[rev]
  228. # Process the current flux sample into SCP "bitcell" format
  229. to_index -= x
  230. y = x * factor + rem
  231. val = int(round(y))
  232. if (val & 65535) == 0:
  233. val += 1
  234. rem = y - val
  235. while val >= 65536:
  236. dat.append(0)
  237. dat.append(0)
  238. val -= 65536
  239. dat.append(val>>8)
  240. dat.append(val&255)
  241. # Header for last track(s) in case we ran out of flux timings.
  242. while rev <= nr_revs:
  243. tdh += struct.pack("<III",
  244. int(round(index_times[rev]*factor)),
  245. (len(dat) - len_at_index) // 2,
  246. 4 + nr_revs*12 + len_at_index)
  247. len_at_index = len(dat)
  248. rev += 1
  249. return (tdh, dat)
  250. # read_to_scp:
  251. # Reads a floppy disk and dumps it into a new Supercard Pro image file.
  252. def read_to_scp(args):
  253. trk_dat = bytearray()
  254. trk_offs = []
  255. if args.single_sided:
  256. track_range = range(args.scyl, args.ecyl+1)
  257. nr_sides = 1
  258. else:
  259. track_range = range(args.scyl*2, (args.ecyl+1)*2)
  260. nr_sides = 2
  261. for track in track_range:
  262. cyl = track >> (nr_sides - 1)
  263. side = track & (nr_sides - 1)
  264. print("\rReading Track %u.%u..." % (cyl, side), end="")
  265. trk_offs.append(len(trk_dat))
  266. seek(cyl, side)
  267. flux = read_flux(args.revs+1)
  268. (tdh, dat) = flux_to_scp(flux, track, args.revs)
  269. trk_dat += tdh
  270. trk_dat += dat
  271. print()
  272. csum = 0
  273. for x in trk_dat:
  274. csum += x
  275. trk_offs_dat = bytearray()
  276. for x in trk_offs:
  277. trk_offs_dat += struct.pack("<I", 0x2b0 + x)
  278. trk_offs_dat += bytes(0x2a0 - len(trk_offs_dat))
  279. for x in trk_offs_dat:
  280. csum += x
  281. ds_flag = 0
  282. if args.single_sided:
  283. ds_flag = 1
  284. header_dat = struct.pack("<3s9BI",
  285. b"SCP", # Signature
  286. 0, # Version
  287. 0x80, # DiskType = Other
  288. args.revs, # Nr Revolutions
  289. track_range.start, # Start track
  290. track_range.stop-1, # End track
  291. 0x01, # Flags = Index
  292. 0, # 16-bit cell width
  293. ds_flag, # Double Sided
  294. 0, # 25ns capture
  295. csum & 0xffffffff)
  296. with open(args.file, "wb") as f:
  297. f.write(header_dat)
  298. f.write(trk_offs_dat)
  299. f.write(trk_dat)
  300. # write_from_scp:
  301. # Writes the specified Supercard Pro image file to floppy disk.
  302. def write_from_scp(args):
  303. factor = sample_freq / scp_freq
  304. with open(args.file, "rb") as f:
  305. dat = f.read()
  306. header = struct.unpack("<3s9BI", dat[0:16])
  307. assert header[0] == b"SCP"
  308. trk_offs = struct.unpack("<168I", dat[16:0x2b0])
  309. if args.single_sided:
  310. track_range = range(args.scyl, args.ecyl+1)
  311. nr_sides = 1
  312. else:
  313. track_range = range(args.scyl*2, (args.ecyl+1)*2)
  314. nr_sides = 2
  315. for i in track_range:
  316. cyl = i >> (nr_sides - 1)
  317. side = i & (nr_sides - 1)
  318. print("\rWriting Track %u.%u..." % (cyl, side), end="")
  319. if trk_offs[i] == 0:
  320. continue
  321. seek(cyl, side)
  322. thdr = struct.unpack("<3sBIII", dat[trk_offs[i]:trk_offs[i]+16])
  323. (sig,_,_,samples,off) = thdr
  324. assert sig == b"TRK"
  325. tdat = dat[trk_offs[i]+off:trk_offs[i]+off+samples*2]
  326. flux = []
  327. rem = 0.0
  328. for i in range(0,len(tdat),2):
  329. x = tdat[i]*256 + tdat[i+1]
  330. if x == 0:
  331. rem += 65536.0
  332. continue
  333. y = x * factor + rem
  334. val = int(round(y))
  335. rem = y - val
  336. flux.append(val)
  337. write_flux(flux)
  338. print()
  339. # update_firmware:
  340. # Updates the Greaseweazle firmware using the specified Update File.
  341. def update_firmware(args):
  342. with open(args.file, "rb") as f:
  343. dat = f.read()
  344. (sig, maj, min, pad1, pad2, crc) = struct.unpack(">2s4BH", dat[-8:])
  345. if len(dat) & 3 != 0 or sig != b'GW' or pad1 != 0 or pad2 != 0:
  346. print("%s: Bad update file" % (args.file))
  347. return
  348. crc16 = crcmod.predefined.Crc('crc-ccitt-false')
  349. crc16.update(dat)
  350. if crc16.crcValue != 0:
  351. print("%s: Bad CRC" % (args.file))
  352. print("Updating to v%u.%u..." % (maj, min))
  353. send_cmd(struct.pack("<2BI", CMD_UPDATE, 6, len(dat)))
  354. ser.write(dat)
  355. (ack,) = struct.unpack("B", ser.read(1))
  356. if ack != 0:
  357. print("** UPDATE FAILED: Please retry!")
  358. return
  359. print("Done.")
  360. print("** Disconnect Greaseweazle and remove the Programming Jumper.")
  361. # _main:
  362. # Argument processing and dispatch.
  363. def _main(argv):
  364. actions = {
  365. "read" : read_to_scp,
  366. "write" : write_from_scp,
  367. "update" : update_firmware
  368. }
  369. parser = argparse.ArgumentParser(
  370. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  371. parser.add_argument("action")
  372. parser.add_argument("--revs", type=int, default=3,
  373. help="number of revolutions to read per track")
  374. parser.add_argument("--scyl", type=int, default=0,
  375. help="first cylinder to read/write")
  376. parser.add_argument("--ecyl", type=int, default=81,
  377. help="last cylinder to read/write")
  378. parser.add_argument("--single-sided", action="store_true")
  379. parser.add_argument("file", help="in/out filename")
  380. parser.add_argument("device", help="serial device")
  381. args = parser.parse_args(argv[1:])
  382. if not args.action in actions:
  383. print("** Action \"%s\" is not recognised" % args.action)
  384. print("Valid actions: ", end="")
  385. print(", ".join(str(key) for key in actions.keys()))
  386. return
  387. global ser
  388. ser = serial.Serial(args.device)
  389. ser.baudrate = BAUD_CLEAR_COMMS
  390. ser.baudrate = BAUD_NORMAL
  391. ser.reset_input_buffer()
  392. global sample_freq
  393. info = get_fw_info()
  394. sample_freq = info[4]
  395. update_mode = (info[2] == 0)
  396. print("** %s v%u.%u, Host Tools v%u.%u"
  397. % (("Greaseweazle","Bootloader")[update_mode], info[0], info[1],
  398. version.major, version.minor))
  399. if (not update_mode
  400. and (version.major > info[0]
  401. or (version.major == info[0] and version.minor > info[1]))):
  402. print("Firmware is out of date: Require >= v%u.%u"
  403. % (version.major, version.minor))
  404. print("Install the Update Jumper and \"update <update_file>\"")
  405. return
  406. if update_mode and args.action != "update":
  407. print("Greaseweazle is in Firmware Update Mode:")
  408. print(" The only available action is \"update <update_file>\"")
  409. if info[4] & 1:
  410. print(" Remove the Update Jumper for normal operation")
  411. else:
  412. print(" Main firmware is erased: You *must* perform an update!")
  413. return
  414. if not update_mode and args.action == "update":
  415. print("Greaseweazle is in Normal Mode:")
  416. print(" To \"update\" you must install the Update Jumper")
  417. return
  418. set_delays(step_delay=5000)
  419. print_delays(get_delays())
  420. if not update_mode:
  421. drive_select(True)
  422. drive_motor(True)
  423. try:
  424. actions[args.action](args)
  425. except:
  426. if not update_mode:
  427. ser.reset_output_buffer()
  428. ser.baudrate = BAUD_CLEAR_COMMS
  429. ser.baudrate = BAUD_NORMAL
  430. ser.reset_input_buffer()
  431. drive_motor(False)
  432. drive_select(False)
  433. raise
  434. else:
  435. if not update_mode:
  436. drive_motor(False)
  437. drive_select(False)
  438. def main(argv):
  439. try:
  440. _main(argv)
  441. except CmdError as error:
  442. print("Command Failed: %s" % error)
  443. if __name__ == "__main__":
  444. main(sys.argv)