gw.py 14 KB

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