bootloader.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #
  2. # Simple PWM controller
  3. # Remote control tool
  4. #
  5. # Copyright (c) 2018-2021 Michael Buesch <m@bues.ch>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. __all__ = [
  22. "SimplePWMBoot",
  23. ]
  24. from libsimplepwm.util import *
  25. from libsimplepwm.crc import *
  26. import intelhex
  27. import serial
  28. import time
  29. class SimplePWMBoot(object):
  30. BOOTCMD_EXIT = 0x00
  31. BOOTCMD_GETID = 0x01
  32. BOOTCMD_WRITEPAGE = 0x5A
  33. BOOTCMD_NOP = 0xFF
  34. BOOTRESULT_OK = 1
  35. BOOTRESULT_NOTOK = 2
  36. BOOT_WRITEPAGE_MAGIC = 0x97
  37. BOOT_WRITEPAGE_FLG_ERASEONLY = 0x01
  38. BOOT_WRITEPAGE_FLG_EEPROM = 0x02
  39. chipProperties = {
  40. 5 : { # ATMega 88
  41. "flashSize" : 0x2000,
  42. "eepromSize" : 0x200,
  43. "bootSize" : 0x400,
  44. "chunkSize" : 0x40,
  45. },
  46. 6 : { # ATMega 88P
  47. "flashSize" : 0x2000,
  48. "eepromSize" : 0x200,
  49. "bootSize" : 0x400,
  50. "chunkSize" : 0x40,
  51. },
  52. 7 : { # ATMega 328P
  53. "flashSize" : 0x8000,
  54. "eepromSize" : 0x400,
  55. "bootSize" : 0x1000,
  56. "chunkSize" : 0x80,
  57. },
  58. }
  59. def __init__(self, serialFd):
  60. self.__serial = serialFd
  61. self.__h = None
  62. def loadImage(self, imageFile):
  63. try:
  64. self.__h = intelhex.IntelHex(str(imageFile))
  65. self.__h.padding = 0xFF
  66. except intelhex.IntelHexError as e:
  67. raise SimplePWMError(f"Failed to parse hex file '{imageFile}':\n"
  68. f"{str(e)}")
  69. def __syncConnection(self, withUserHelp=False):
  70. try:
  71. self.__serial.timeout = 0.1
  72. begin = time.monotonic()
  73. loopTime = 7.0
  74. nextPrint = 1.0
  75. if withUserHelp:
  76. print(f"\n*** I need your help now!\n"
  77. f"*** Please reset/restart/powercycle the target device once "
  78. f"and then wait for the timer to expire...",
  79. end="", flush=True)
  80. while True:
  81. self.__bootCommand(self.BOOTCMD_NOP, count=0x100)
  82. now = time.monotonic()
  83. if not withUserHelp or now >= begin + loopTime:
  84. break
  85. if now - begin >= nextPrint:
  86. print(f" {round(loopTime - nextPrint)}", end="", flush=True)
  87. nextPrint += 1.0
  88. if withUserHelp:
  89. print(" 0")
  90. while self.__serial.read():
  91. pass
  92. self.__serial.timeout = 5.0
  93. except serial.SerialException as e:
  94. raise SimplePWMError(f"Serial bus exception:\n{e}")
  95. def __bootCommand(self, command, count=1, replyBytes=0):
  96. try:
  97. self.__serial.write(bytearray( (command, ) * count ))
  98. if replyBytes > 0:
  99. data = self.__serial.read(replyBytes)
  100. if not data:
  101. raise SimplePWMError(f"Command timeout.")
  102. return data
  103. except serial.SerialException as e:
  104. raise SimplePWMError(f"Serial bus exception:\n{e}")
  105. def __readChipProperties(self):
  106. chipId = self.__bootCommand(self.BOOTCMD_GETID, replyBytes=1)[0]
  107. if chipId <= 0:
  108. raise SimplePWMError(f"Failed to fetch chip ID.")
  109. prop = self.chipProperties.get(chipId, None)
  110. if not prop:
  111. raise SimplePWMError(f"Properties of chip ID={chipId} unknown.")
  112. return prop
  113. def __writeImage(self, eeprom=False):
  114. if self.__h is None:
  115. return
  116. try:
  117. self.__syncConnection()
  118. prop = self.__readChipProperties()
  119. except SimplePWMError as e:
  120. # Try harder.
  121. self.__syncConnection(withUserHelp=True)
  122. prop = self.__readChipProperties()
  123. # Calculate the image properties.
  124. if eeprom:
  125. imageSize = prop["eepromSize"]
  126. else:
  127. imageSize = prop["flashSize"] - prop["bootSize"]
  128. if len(self.__h) > imageSize:
  129. raise SimplePWMError(f"The provided image is too big.")
  130. imageData = memoryview(self.__h.tobinstr(size=imageSize))
  131. chunkSize = prop["chunkSize"]
  132. memtype = "EEPROM" if eeprom else "flash"
  133. print(f"Writing {memtype} image ...", end="", flush=True)
  134. for addr in range(0, imageSize, chunkSize):
  135. pageData = imageData[addr : addr + chunkSize]
  136. eraseOnly = all(d == 0xFF for d in pageData)
  137. flags = 0
  138. if eraseOnly:
  139. flags |= self.BOOT_WRITEPAGE_FLG_ERASEONLY
  140. if eeprom:
  141. flags |= self.BOOT_WRITEPAGE_FLG_EEPROM
  142. cmdData = bytearray( (
  143. self.BOOT_WRITEPAGE_MAGIC,
  144. flags,
  145. addr & 0xFF,
  146. (addr >> 8) & 0xFF,
  147. ) )
  148. if not eraseOnly:
  149. cmdData += pageData
  150. cmdData += bytearray( (crc8(cmdData), ) )
  151. self.__bootCommand(self.BOOTCMD_WRITEPAGE)
  152. try:
  153. self.__serial.write(cmdData)
  154. result = self.__serial.read(1)
  155. except serial.SerialException as e:
  156. raise SimplePWMError(f"Serial bus exception:\n{e}")
  157. if not result:
  158. raise SimplePWMError(f"BOOTCMD_WRITEPAGE timeout at 0x{addr:04X}.")
  159. if result[0] != self.BOOTRESULT_OK:
  160. raise SimplePWMError(f"BOOTCMD_WRITEPAGE error at 0x{addr:04X}. "
  161. f"Result: {result[0]}")
  162. print(".", end="", flush=True)
  163. self.__bootCommand(self.BOOTCMD_EXIT)
  164. print("\nWriting successful.")
  165. def writeFlash(self):
  166. self.__writeImage(eeprom=False)
  167. def writeEeprom(self):
  168. self.__writeImage(eeprom=True)
  169. # vim: ts=4 sw=4 expandtab