emulator.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. from threading import Thread, Event
  2. from os import path
  3. import _msp430emu
  4. import wx
  5. source_dir = path.dirname(path.realpath(__file__))
  6. class Emulator:
  7. EVENT_CONSOLE = 0
  8. EVENT_SERIAL = 1
  9. EVENT_GPIO = 2
  10. P1_0_ON_PACKET = 0x00
  11. P1_0_OFF_PACKET = 0x01
  12. P1_1_ON_PACKET = 0x02
  13. P1_1_OFF_PACKET = 0x03
  14. P1_2_ON_PACKET = 0x04
  15. P1_2_OFF_PACKET = 0x05
  16. P1_3_ON_PACKET = 0x06
  17. P1_3_OFF_PACKET = 0x07
  18. P1_4_ON_PACKET = 0x08
  19. P1_4_OFF_PACKET = 0x09
  20. P1_5_ON_PACKET = 0x0A
  21. P1_5_OFF_PACKET = 0x0B
  22. P1_6_ON_PACKET = 0x0C
  23. P1_6_OFF_PACKET = 0x0D
  24. P1_7_ON_PACKET = 0x0E
  25. P1_7_OFF_PACKET = 0x0F
  26. def __init__(self, load=None, callback=None):
  27. # self.process = Popen([path.join(emu_dir, 'MSP430'), str(ws_port)], stdout=PIPE, stderr=PIPE)
  28. # self.ws_port = ws_port
  29. # self._start_ws()
  30. self.load = load
  31. self.started = False
  32. self.callback = callback
  33. _msp430emu.on_serial(self._on_serial)
  34. _msp430emu.on_console(self._on_console)
  35. # _msp430emu.on_control(self._on_control)
  36. if self.load is not None:
  37. self.process = Thread(target=self._start_emu, daemon=False)
  38. self.process.start()
  39. def _on_serial(self, s):
  40. self._cb(self.EVENT_SERIAL, s)
  41. def _on_console(self, s):
  42. self._cb(self.EVENT_CONSOLE, s)
  43. def _on_control(self, opcode, data):
  44. if opcode <= 0x0F:
  45. self._cb(self.EVENT_GPIO, opcode)
  46. def send_command(self, cmd):
  47. if self.started:
  48. _msp430emu.cmd(cmd)
  49. def get_port1_regs(self):
  50. if self.started:
  51. return _msp430emu.get_regs(0x05)
  52. def reset(self):
  53. if self.started:
  54. _msp430emu.reset()
  55. def _start_emu(self):
  56. print("starting emulator...")
  57. self.started = True
  58. _msp430emu.init(self.load)
  59. print("stopping emulator...")
  60. # def _start_ws(self):
  61. # self.ws = websocket.WebSocketApp(
  62. # f"ws://127.0.0.1:{self.ws_port}",
  63. # subprotocols={"emu-protocol"},
  64. # on_open=self._ws_open,
  65. # on_data=self._ws_msg,
  66. # on_error=self._ws_err,
  67. # on_close=self._ws_close
  68. # )
  69. # Thread(target=self.ws.run_forever).start()
  70. #
  71. def load_file(self, fname):
  72. print("loading " + fname)
  73. self.load = fname
  74. self.process = Thread(target=self._start_emu, daemon=False)
  75. self.process.start()
  76. # with open(self.load, 'rb') as f:
  77. # self.firmware = fname
  78. # fdata = f.read()
  79. # name = path.basename(fname)
  80. # payload = b'\x00' # opcode
  81. # payload += len(fdata).to_bytes(2, byteorder='big')
  82. # payload += len(name).to_bytes(2, byteorder='big')
  83. # payload += name.encode() + fdata
  84. # self.ws.send(payload, websocket.ABNF.OPCODE_BINARY)
  85. def _cb(self, ev, data):
  86. if callable(self.callback):
  87. self.callback(ev, data)
  88. # def _ws_open(self):
  89. # self.started = True
  90. # if self.load is not None:
  91. # self.load_file(self.load)
  92. #
  93. # def _ws_msg(self, data, frame, x):
  94. # opcode = data[0]
  95. # if opcode == 0:
  96. # if len(data) == 2 and data[1] <= 15:
  97. # self._cb(self.EVENT_GPIO, data[1])
  98. #
  99. # elif opcode == 1:
  100. # message = data[1:-1].decode()
  101. # self._cb(self.EVENT_CONSOLE, message)
  102. # # print(message, end=None)
  103. # return
  104. # elif opcode == 2:
  105. # message = data[1:-1].decode()
  106. # self._cb(self.EVENT_SERIAL, message)
  107. # return
  108. # else:
  109. # pass
  110. # def _ws_err(self, err):
  111. # if not self.started:
  112. # self.start_errors += 1
  113. # if self.start_errors < 5:
  114. # print(f"Failed to connect to emulator backend attempt {self.start_errors}")
  115. # sleep(1)
  116. # self._start_ws()
  117. # raise ConnectionError("Failed to connect to emulation backend after 5 tries")
  118. # raise err
  119. #
  120. # def _ws_close(self):
  121. # if not self.started:
  122. # return
  123. # pass
  124. def __del__(self):
  125. self.close()
  126. def emulation_pause(self):
  127. if self.started:
  128. _msp430emu.pause()
  129. # if self.load is not None:
  130. # self.ws.send(b'\x02', websocket.ABNF.OPCODE_BINARY)
  131. def emulation_start(self):
  132. if self.started:
  133. _msp430emu.play()
  134. # if self.load is not None:
  135. # self.ws.send(b'\x01', websocket.ABNF.OPCODE_BINARY)
  136. def close(self):
  137. if self.started:
  138. try:
  139. _msp430emu.pause()
  140. _msp430emu.stop()
  141. except SystemError:
  142. print("Failed gradually stop emulator")
  143. self.process.join(2)
  144. class EmulatorWindow(wx.Frame):
  145. def __init__(self, parent, title, load=None):
  146. wx.Frame.__init__(self, parent, title=title)
  147. self.control = wx.TextCtrl(self, size=wx.Size(400, 450),
  148. style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP)
  149. self.control.Hide()
  150. self.serial = wx.TextCtrl(self, size=wx.Size(400, 450), style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP)
  151. self.serial_input = wx.TextCtrl(self)
  152. self.statusBar = self.CreateStatusBar() # A Statusbar in the bottom of the window
  153. file_menu = wx.Menu()
  154. menuFile = file_menu.Append(wx.ID_OPEN, "&Firmware", " Open firmware")
  155. self.Bind(wx.EVT_MENU, self.OnOpen, menuFile)
  156. menuReset = file_menu.Append(wx.ID_CLOSE_ALL, "&Reset", " Reset Emulator")
  157. self.Bind(wx.EVT_MENU, self.RestartEmulator, menuReset)
  158. menuExit = file_menu.Append(wx.ID_EXIT, "E&xit", " Terminate the program")
  159. self.Bind(wx.EVT_MENU, self.OnClose, menuExit)
  160. self.Bind(wx.EVT_CLOSE, self.OnExit)
  161. view_menu = wx.Menu()
  162. self.view_console = view_menu.AppendCheckItem(101, "View Console", "Show/Hide Emulator debug console")
  163. self.view_registers = view_menu.AppendCheckItem(102, "View Registers", "Show/Hide Emulator registers table")
  164. self.Bind(wx.EVT_MENU, self.ToggleConsole, self.view_console)
  165. self.Bind(wx.EVT_MENU, self.ToggleRegisters, self.view_registers)
  166. self.registers = None
  167. menuBar = wx.MenuBar()
  168. menuBar.Append(file_menu, "&File")
  169. menuBar.Append(view_menu, "&View")
  170. self.SetMenuBar(menuBar)
  171. self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
  172. self.btn_start_emu = wx.Button(self, -1, "Start")
  173. self.Bind(wx.EVT_BUTTON, self.OnStart, self.btn_start_emu)
  174. self.btn_stop_emu = wx.Button(self, -1, "Pause")
  175. self.Bind(wx.EVT_BUTTON, self.OnPause, self.btn_stop_emu)
  176. self.btn_key = wx.Button(self, -1, "Press Key")
  177. self.btn_key.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
  178. self.btn_key.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)
  179. self.btn_rst = wx.Button(self, -1, "Reset")
  180. self.Bind(wx.EVT_BUTTON, self.OnKeyReset, self.btn_rst)
  181. self.sizer2.Add(self.btn_key, 1, wx.EXPAND)
  182. self.sizer2.Add(self.btn_start_emu, 1, wx.EXPAND)
  183. self.sizer2.Add(self.btn_stop_emu, 1, wx.EXPAND)
  184. self.sizer2.Add(self.btn_rst, 1, wx.EXPAND)
  185. self.sizer = wx.BoxSizer(wx.VERTICAL)
  186. self.sizer3 = wx.BoxSizer(wx.HORIZONTAL)
  187. self.btn_start_emu = wx.Button(self, -1, "Send")
  188. self.Bind(wx.EVT_BUTTON, self.SendSerial, self.btn_start_emu)
  189. self.sizer3.Add(self.serial_input, 1)
  190. self.sizer3.Add(self.btn_start_emu, 0)
  191. self.sizer0 = wx.BoxSizer(wx.VERTICAL)
  192. self.sizer0.Add(self.serial, 1, wx.EXPAND)
  193. self.sizer0.Add(self.sizer3, 0, wx.EXPAND)
  194. panel = wx.Panel(self, size=wx.Size(275, 375))
  195. # img =
  196. # wx.StaticBitmap(panel, -1, img, (0, 0), (img.GetWidth(), img.GetHeight()))
  197. self.diagram = DrawRect(panel, -1, size=wx.Size(275, 375))
  198. #
  199. # dc = wx.WindowDC(panel)
  200. # dc.SetPen(wx.WHITE_PEN)
  201. # dc.SetBrush(wx.WHITE_BRUSH)
  202. # dc.DrawRectangle(50, 50, 500, 500)
  203. # self.sizer_key_rst = wx.BoxSizer(wx.HORIZONTAL)
  204. # btn_key = wx.Button(self, -1, "Press Key")
  205. # self.Bind(wx.EVT_BUTTON, self.OnKeyPress, btn_key)
  206. # btn_rst = wx.Button(self, -1, "Reset")
  207. # self.Bind(wx.EVT_BUTTON, self.OnKeyReset, btn_rst)
  208. # self.sizer_key_rst.Add(btn_key, 1, wx.EXPAND)
  209. # self.sizer_key_rst.Add(btn_rst, 1, wx.EXPAND)
  210. self.sizer_diagram = wx.BoxSizer(wx.VERTICAL)
  211. self.sizer_diagram.Add(panel, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)
  212. #
  213. # self.sizer_left = wx.BoxSizer(wx.VERTICAL)
  214. # self.sizer_left.Add(self.sizer_diagram, 1, wx.EXPAND)
  215. # self.sizer_left.Add(self.sizer_key_rst, 0, wx.ALIGN_BOTTOM)
  216. self.sizer1 = wx.BoxSizer(wx.HORIZONTAL)
  217. self.sizer1.Add(self.sizer_diagram, 0, wx.EXPAND)
  218. self.sizer1.Add(self.control, 1, wx.EXPAND)
  219. self.sizer1.Add(self.sizer0, 1, wx.EXPAND)
  220. self.sizer.Add(self.sizer1, 1, wx.EXPAND)
  221. self.sizer.Add(self.sizer2, 0, wx.EXPAND)
  222. self.SetSizer(self.sizer)
  223. self.SetAutoLayout(1)
  224. self.sizer.Fit(self)
  225. self.Show()
  226. self.timer_locked = True
  227. self.timer_running = Event()
  228. self.timer = Thread(target=self.OnTimer)
  229. self.timer.start()
  230. self.control.WriteText("Initialising Emulator..\n")
  231. self.load = load
  232. self.emu = Emulator(load=self.load, callback=self.callback)
  233. if self.load is None:
  234. self.serial_input.Disable()
  235. self.serial.Disable()
  236. self.btn_rst.Disable()
  237. self.btn_key.Disable()
  238. self.btn_start_emu.Disable()
  239. self.btn_stop_emu.Disable()
  240. else:
  241. self.statusBar.SetStatusText("Press start to run emulation")
  242. def callback(self, event, data):
  243. if event == Emulator.EVENT_CONSOLE:
  244. wx.CallAfter(self.control.AppendText, data)
  245. elif event == Emulator.EVENT_SERIAL:
  246. wx.CallAfter(self.serial.AppendText, data)
  247. # elif event == Emulator.EVENT_GPIO:
  248. # self.diagram.port1[data // 2] = data % 2 == 0
  249. # wx.CallAfter(self.diagram.Refresh)
  250. def RestartEmulator(self, e):
  251. self.emu.close()
  252. self.control.Clear()
  253. self.serial.Clear()
  254. self.emu = Emulator(load=self.load, callback=self.callback)
  255. def ToggleConsole(self, e):
  256. if e.Int == 0:
  257. self.control.Hide()
  258. self.Layout()
  259. else:
  260. self.control.Show()
  261. self.Layout()
  262. def OnOpen(self, e):
  263. with wx.FileDialog(self, "Open Firmware File", wildcard="BIN files (*.bin)|*.bin",
  264. style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
  265. if fileDialog.ShowModal() == wx.ID_CANCEL:
  266. return
  267. self.load = fileDialog.GetPath()
  268. self.emu.load_file(self.load)
  269. self.diagram.power = False
  270. self.serial_input.Enable()
  271. self.serial.Enable()
  272. self.btn_rst.Enable()
  273. self.btn_key.Enable()
  274. self.btn_start_emu.Enable()
  275. self.btn_stop_emu.Enable()
  276. self.statusBar.SetStatusText("Press start to run emulation")
  277. def OnTimer(self):
  278. while 1:
  279. try:
  280. if self.timer_running.wait(0.04):
  281. break
  282. except TimeoutError:
  283. pass
  284. if self.timer_locked:
  285. continue
  286. ports = self.emu.get_port1_regs()
  287. if ports is not None:
  288. for i in range(8):
  289. self.diagram.port1[i] = (ports[0] >> i) & 1 == 1
  290. wx.CallAfter(self.diagram.Refresh)
  291. if self.view_registers.IsChecked():
  292. wx.CallAfter(self.registers.set_values, self.emu)
  293. def OnPause(self, e):
  294. self.emu.emulation_pause()
  295. self.diagram.power = False
  296. self.diagram.Refresh()
  297. self.emu.get_port1_regs()
  298. self.timer_locked = True
  299. def OnStart(self, e):
  300. if self.load is None:
  301. self.OnOpen(e)
  302. else:
  303. self.statusBar.SetStatusText("")
  304. self.emu.emulation_start()
  305. self.diagram.power = True
  306. self.diagram.Refresh()
  307. self.timer_locked = False
  308. def OnClose(self, e):
  309. self.Close(True)
  310. def OnExit(self, e):
  311. self.emu.close()
  312. self.timer_running.set()
  313. e.Skip()
  314. def OnMouseDown(self, e):
  315. print("down")
  316. e.Skip()
  317. def OnMouseUp(self, e):
  318. print("up")
  319. e.Skip()
  320. def OnKeyReset(self, e):
  321. self.diagram.port1 = [False, False, False, False, False, False, False, False]
  322. self.emu.reset()
  323. def SendSerial(self, e):
  324. text = self.serial_input.GetValue()
  325. print(text)
  326. self.serial_input.Clear()
  327. def ToggleRegisters(self, e):
  328. if e.Int == 0:
  329. self.registers.Close()
  330. else:
  331. self.registers = RegisterPanel(self)
  332. self.registers.Bind(wx.EVT_CLOSE, self.OnRegistersClose)
  333. self.registers.set_values(self.emu)
  334. def OnRegistersClose(self, e):
  335. self.view_registers.Check(False)
  336. e.Skip()
  337. class RegisterPanel(wx.Frame):
  338. def __init__(self, parent):
  339. wx.Frame.__init__(self, parent, title="Emulator Registers")
  340. self.parent = parent
  341. self.grid = wx.FlexGridSizer(9, 2, 0, 10)
  342. self.p1_registers = {
  343. 'P1OUT': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  344. 'P1DIR': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  345. 'P1IFG': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  346. 'P1IES': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  347. 'P1IE': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  348. 'P1SEL': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  349. 'P1SEL2': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  350. 'P1REN': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  351. 'P1IN': wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_NO_VSCROLL),
  352. }
  353. gridvals = []
  354. for name, text in self.p1_registers.items():
  355. text.SetMinSize((80, 15))
  356. text.SetValue("00000000")
  357. gridvals.append((wx.StaticText(self, label=name),))
  358. gridvals.append((text, 1, wx.EXPAND))
  359. self.grid.AddMany(gridvals)
  360. box = wx.BoxSizer(wx.VERTICAL)
  361. box.Add(self.grid, proportion=1, flag=wx.ALL | wx.EXPAND, border=15)
  362. self.SetSizer(box)
  363. self.Center()
  364. self.Show()
  365. self.Fit()
  366. self.Layout()
  367. def set_values(self, emu):
  368. p1regs = emu.get_port1_regs()
  369. if p1regs is not None:
  370. for i, reg in enumerate(self.p1_registers.values()):
  371. reg.SetValue(f"{p1regs[i]:08b}")
  372. class DrawRect(wx.Panel):
  373. """ class MyPanel creates a panel to draw on, inherits wx.Panel """
  374. RED = wx.Colour(255, 0, 0, wx.ALPHA_OPAQUE)
  375. GREEN = wx.Colour(0, 255, 0, wx.ALPHA_OPAQUE)
  376. def __init__(self, parent, id, **kwargs):
  377. # create a panel
  378. wx.Panel.__init__(self, parent, id, **kwargs)
  379. # self.SetBackgroundColour("white")
  380. self.Bind(wx.EVT_PAINT, self.OnPaint)
  381. self.power = False
  382. self.port1 = [False, False, False, False, False, False, False, False]
  383. self.image = wx.Bitmap(path.join(source_dir, "msp430.png"), wx.BITMAP_TYPE_PNG)
  384. def OnPaint(self, evt):
  385. """set up the device context (DC) for painting"""
  386. self.dc = wx.PaintDC(self)
  387. self.dc.DrawBitmap(self.image, 0, 0, True)
  388. if self.power:
  389. self.dc.SetPen(wx.Pen(self.GREEN, style=wx.TRANSPARENT))
  390. self.dc.SetBrush(wx.Brush(self.GREEN, wx.SOLID))
  391. # set x, y, w, h for rectangle
  392. self.dc.DrawRectangle(39, 110, 8, 15)
  393. if self.port1[6]:
  394. self.dc.SetPen(wx.Pen(self.GREEN, style=wx.TRANSPARENT))
  395. self.dc.SetBrush(wx.Brush(self.GREEN, wx.SOLID))
  396. # set x, y, w, h for rectangle
  397. self.dc.DrawRectangle(83, 356, 8, 15)
  398. if self.port1[0]:
  399. self.dc.SetPen(wx.Pen(self.RED, style=wx.TRANSPARENT))
  400. self.dc.SetBrush(wx.Brush(self.RED, wx.SOLID))
  401. self.dc.DrawRectangle(70, 356, 8, 15)
  402. del self.dc
  403. def run(load=None):
  404. app = wx.App(False)
  405. frame = EmulatorWindow(None, "MSP430 Emulator", load)
  406. app.MainLoop()