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