stm32cubeprogrammer.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # Copyright (c) 2020 Teslabs Engineering S.L.
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. """Runner for flashing with STM32CubeProgrammer CLI, the official programming
  5. utility from ST Microelectronics.
  6. """
  7. import argparse
  8. from pathlib import Path
  9. import platform
  10. import os
  11. import shlex
  12. from typing import List, Optional, ClassVar, Dict
  13. from runners.core import ZephyrBinaryRunner, RunnerCaps, RunnerConfig
  14. class STM32CubeProgrammerBinaryRunner(ZephyrBinaryRunner):
  15. """Runner front-end for STM32CubeProgrammer CLI."""
  16. _RESET_MODES: ClassVar[Dict[str, str]] = {
  17. "sw": "SWrst",
  18. "hw": "HWrst",
  19. "core": "Crst",
  20. }
  21. """Reset mode argument mappings."""
  22. def __init__(
  23. self,
  24. cfg: RunnerConfig,
  25. port: str,
  26. frequency: Optional[int],
  27. reset_mode: Optional[str],
  28. conn_modifiers: Optional[str],
  29. cli: Optional[Path],
  30. use_elf: bool,
  31. erase: bool,
  32. tool_opt: List[str],
  33. ) -> None:
  34. super().__init__(cfg)
  35. self._port = port
  36. self._frequency = frequency
  37. self._reset_mode = reset_mode
  38. self._conn_modifiers = conn_modifiers
  39. self._cli = (
  40. cli or STM32CubeProgrammerBinaryRunner._get_stm32cubeprogrammer_path()
  41. )
  42. self._use_elf = use_elf
  43. self._erase = erase
  44. self._tool_opt: List[str] = list()
  45. for opts in [shlex.split(opt) for opt in tool_opt]:
  46. self._tool_opt += opts
  47. # add required library loader path to the environment (Linux only)
  48. if platform.system() == "Linux":
  49. os.environ["LD_LIBRARY_PATH"] = str(self._cli.parent / ".." / "lib")
  50. @staticmethod
  51. def _get_stm32cubeprogrammer_path() -> Path:
  52. """Obtain path of the STM32CubeProgrammer CLI tool."""
  53. if platform.system() == "Linux":
  54. return (
  55. Path.home()
  56. / "STMicroelectronics"
  57. / "STM32Cube"
  58. / "STM32CubeProgrammer"
  59. / "bin"
  60. / "STM32_Programmer_CLI"
  61. )
  62. if platform.system() == "Windows":
  63. cli = (
  64. Path("STMicroelectronics")
  65. / "STM32Cube"
  66. / "STM32CubeProgrammer"
  67. / "bin"
  68. / "STM32_Programmer_CLI.exe"
  69. )
  70. x86_path = Path(os.environ["PROGRAMFILES(X86)"]) / cli
  71. if x86_path.exists():
  72. return x86_path
  73. return Path(os.environ["PROGRAMFILES"]) / cli
  74. if platform.system() == "Darwin":
  75. return (
  76. Path("/Applications")
  77. / "STMicroelectronics"
  78. / "STM32Cube"
  79. / "STM32CubeProgrammer"
  80. / "STM32CubeProgrammer.app"
  81. / "Contents"
  82. / "MacOs"
  83. / "bin"
  84. / "STM32_Programmer_CLI"
  85. )
  86. raise NotImplementedError("Could not determine STM32_Programmer_CLI path")
  87. @classmethod
  88. def name(cls):
  89. return "stm32cubeprogrammer"
  90. @classmethod
  91. def capabilities(cls):
  92. return RunnerCaps(commands={"flash"}, erase=True)
  93. @classmethod
  94. def do_add_parser(cls, parser):
  95. parser.add_argument(
  96. "--port",
  97. type=str,
  98. required=True,
  99. help="Interface identifier, e.g. swd, jtag, /dev/ttyS0...",
  100. )
  101. parser.add_argument(
  102. "--frequency", type=int, required=False, help="Programmer frequency in KHz"
  103. )
  104. parser.add_argument(
  105. "--reset-mode",
  106. type=str,
  107. required=False,
  108. choices=["sw", "hw", "core"],
  109. help="Reset mode",
  110. )
  111. parser.add_argument(
  112. "--conn-modifiers",
  113. type=str,
  114. required=False,
  115. help="Additional options for the --connect argument",
  116. )
  117. parser.add_argument(
  118. "--cli",
  119. type=Path,
  120. required=False,
  121. help="STM32CubeProgrammer CLI tool path",
  122. )
  123. parser.add_argument(
  124. "--use-elf",
  125. action="store_true",
  126. required=False,
  127. help="Use ELF file when flashing instead of HEX file",
  128. )
  129. parser.add_argument(
  130. "--tool-opt",
  131. default=[],
  132. action="append",
  133. help="Additional options for STM32_Programmer_CLI",
  134. )
  135. @classmethod
  136. def do_create(
  137. cls, cfg: RunnerConfig, args: argparse.Namespace
  138. ) -> "STM32CubeProgrammerBinaryRunner":
  139. return STM32CubeProgrammerBinaryRunner(
  140. cfg,
  141. port=args.port,
  142. frequency=args.frequency,
  143. reset_mode=args.reset_mode,
  144. conn_modifiers=args.conn_modifiers,
  145. cli=args.cli,
  146. use_elf=args.use_elf,
  147. erase=args.erase,
  148. tool_opt=args.tool_opt,
  149. )
  150. def do_run(self, command: str, **kwargs):
  151. if command == "flash":
  152. self.flash(**kwargs)
  153. def flash(self, **kwargs) -> None:
  154. self.require(str(self._cli))
  155. # prepare base command
  156. cmd = [str(self._cli)]
  157. connect_opts = f"port={self._port}"
  158. if self._frequency:
  159. connect_opts += f" freq={self._frequency}"
  160. if self._reset_mode:
  161. reset_mode = STM32CubeProgrammerBinaryRunner._RESET_MODES[self._reset_mode]
  162. connect_opts += f" reset={reset_mode}"
  163. if self._conn_modifiers:
  164. connect_opts += f" {self._conn_modifiers}"
  165. cmd += ["--connect", connect_opts]
  166. cmd += self._tool_opt
  167. # erase first if requested
  168. if self._erase:
  169. self.check_call(cmd + ["--erase", "all"])
  170. # flash image and run application
  171. dl_file = self.cfg.elf_file if self._use_elf else self.cfg.hex_file
  172. if dl_file is None:
  173. raise RuntimeError(f'cannot flash; no download file was specified')
  174. elif not os.path.isfile(dl_file):
  175. raise RuntimeError(f'download file {dl_file} does not exist')
  176. self.check_call(cmd + ["--download", dl_file, "--start"])