Skip to content

Commit af302c0

Browse files
committed
Merge branch 'feat/add_merged_bin_cmd' into 'master'
feat(tools): Add idf.py merge-bin command and cmake target See merge request espressif/esp-idf!29996
2 parents 22f88a2 + 0dec6fe commit af302c0

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

components/esptool_py/project_include.cmake

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,35 @@ add_custom_target(uf2-app
230230
VERBATIM
231231
)
232232

233+
set(MERGE_BIN_ARGS merge_bin)
234+
if(DEFINED ENV{ESP_MERGE_BIN_OUTPUT})
235+
list(APPEND MERGE_BIN_ARGS "-o" "$ENV{ESP_MERGE_BIN_OUTPUT}")
236+
else()
237+
if(DEFINED ENV{ESP_MERGE_BIN_FORMAT} AND "$ENV{ESP_MERGE_BIN_FORMAT}" STREQUAL "hex")
238+
list(APPEND MERGE_BIN_ARGS "-o" "${CMAKE_CURRENT_BINARY_DIR}/merged-binary.hex")
239+
else()
240+
list(APPEND MERGE_BIN_ARGS "-o" "${CMAKE_CURRENT_BINARY_DIR}/merged-binary.bin")
241+
endif()
242+
endif()
243+
244+
if(DEFINED ENV{ESP_MERGE_BIN_FORMAT})
245+
list(APPEND MERGE_BIN_ARGS "-f" "$ENV{ESP_MERGE_BIN_FORMAT}")
246+
endif()
247+
248+
list(APPEND MERGE_BIN_ARGS "@${CMAKE_CURRENT_BINARY_DIR}/flash_args")
249+
250+
add_custom_target(merge-bin
251+
COMMAND ${CMAKE_COMMAND}
252+
-D "IDF_PATH=${idf_path}"
253+
-D "SERIAL_TOOL=${ESPTOOLPY}"
254+
-D "SERIAL_TOOL_ARGS=${MERGE_BIN_ARGS}"
255+
-D "WORKING_DIRECTORY=${CMAKE_CURRENT_BINARY_DIR}"
256+
-P run_serial_tool.cmake
257+
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
258+
DEPENDS gen_project_binary bootloader
259+
USES_TERMINAL
260+
VERBATIM
261+
)
233262

234263
set(MONITOR_ARGS "")
235264

docs/en/api-guides/tools/idf-py.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ This command automatically builds the project if necessary, and then flash it to
124124

125125
Similarly to the ``build`` command, the command can be run with ``app``, ``bootloader`` and ``partition-table`` arguments to flash only the app, bootloader or partition table as applicable.
126126

127+
.. _merging-binaries:
128+
129+
Merge binaries: ``merge-bin``
130+
-----------------------------
131+
132+
.. code-block:: bash
133+
134+
idf.py merge-bin [-o output-file] [-f format] [<format-specific-options>]
135+
136+
There are some situations, e.g. transferring the file to another machine and flashing it without ESP-IDF, where it is convenient to have only one file for flashing instead the several file output of ``idf.py build``.
137+
138+
The command ``idf.py merge-bin`` will merge the bootloader, partition table, the application itself, and other partitions (if there are any) according to the project configuration and create a single binary file ``merged-binary.[bin|hex]`` in the build folder, which can then be flashed later.
139+
140+
It is possible to output merged file in binary (raw), IntelHex (hex) and UF2 (uf2) formats.
141+
142+
The uf2 binary can also be generated by :ref:`idf.py uf2 <generate-uf2-binary>`. The ``idf.py uf2`` is functionally equivalent to ``idf.py merge-bin -f uf2``. However, the ``idf.py merge-bin`` command provides more flexibility and options for merging binaries into various formats described above.
143+
144+
Example usage:
145+
146+
.. code-block:: bash
147+
148+
idf.py merge-bin -o my-merged-binary.bin -f raw
149+
150+
There are also some format specific options, which are listed below:
151+
152+
- Only for raw format:
153+
- ``--flash-offset``: This option will create a merged binary that should be flashed at the specified offset, instead of at the standard offset of 0x0.
154+
- ``--fill-flash-size``: If set, the final binary file will be padded with FF bytes up to this flash size in order to fill the full flash content with the image and re-write the whole flash chip upon flashing.
155+
- Only for uf2 format:
156+
- ``--md5-disable``: This option will disable MD5 checksums at the end of each block. This can be useful for integration with e.g. `tinyuf2 <https://github.com/adafruit/tinyuf2>`__.
157+
127158
Hints on How to Resolve Errors
128159
==============================
129160

@@ -203,6 +234,8 @@ Clean the Python Byte Code: ``python-clean``
203234
204235
This command deletes generated python byte code from the ESP-IDF directory. The byte code may cause issues when switching between ESP-IDF and Python versions. It is advised to run this target after switching versions of Python.
205236

237+
.. _generate-uf2-binary:
238+
206239
Generate a UF2 Binary: ``uf2``
207240
------------------------------
208241

@@ -216,6 +249,8 @@ This UF2 file can be copied to a USB mass storage device exposed by another ESP
216249

217250
To generate a UF2 binary for the application only (not including the bootloader and partition table), use the ``uf2-app`` command.
218251

252+
The ``idf.py uf2`` command is functionally equivalent to ``idf.py merge-bin -f uf2`` described :ref:`above <merging-binaries>`. However, the ``idf.py merge-bin`` command provides more flexibility and options for merging binaries into various formats, not only uf2.
253+
219254
.. code-block:: bash
220255
221256
idf.py uf2-app

tools/idf_py_actions/serial_ext.py

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1-
# SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
3-
43
import json
54
import os
65
import shlex
76
import signal
87
import sys
9-
from typing import Any, Dict, List, Optional
8+
from typing import Any
9+
from typing import Dict
10+
from typing import List
11+
from typing import Optional
1012

1113
import click
1214
from idf_py_actions.global_options import global_options
13-
from idf_py_actions.tools import (PropertyDict, RunTool, ensure_build_directory, get_default_serial_port,
14-
get_sdkconfig_value, run_target)
15+
from idf_py_actions.tools import ensure_build_directory
16+
from idf_py_actions.tools import get_default_serial_port
17+
from idf_py_actions.tools import get_sdkconfig_value
18+
from idf_py_actions.tools import PropertyDict
19+
from idf_py_actions.tools import run_target
20+
from idf_py_actions.tools import RunTool
1521

1622
PYTHON = sys.executable
1723

@@ -34,7 +40,7 @@
3440
}
3541

3642

37-
def yellow_print(message, newline='\n'): # type: (str, Optional[str]) -> None
43+
def yellow_print(message: str, newline: Optional[str]='\n') -> None:
3844
"""Print a message to stderr with yellow highlighting """
3945
sys.stderr.write('%s%s%s%s' % ('\033[0;33m', message, '\033[0m', newline))
4046
sys.stderr.flush()
@@ -212,6 +218,47 @@ def ota_targets(target_name: str, ctx: click.core.Context, args: PropertyDict) -
212218
ensure_build_directory(args, ctx.info_name)
213219
run_target(target_name, args, {'ESPBAUD': str(args.baud), 'ESPPORT': args.port})
214220

221+
def merge_bin(action: str,
222+
ctx: click.core.Context,
223+
args: PropertyDict,
224+
output: str,
225+
format: str,
226+
md5_disable: str,
227+
flash_offset: str,
228+
fill_flash_size: str) -> None:
229+
ensure_build_directory(args, ctx.info_name)
230+
project_desc = _get_project_desc(ctx, args)
231+
merge_bin_args = [PYTHON, '-m', 'esptool']
232+
target = project_desc['target']
233+
merge_bin_args += ['--chip', target]
234+
merge_bin_args += ['merge_bin'] # needs to be after the --chip option
235+
if not output:
236+
if format in ('raw', 'uf2'):
237+
output = 'merged-binary.bin'
238+
elif format == 'hex':
239+
output = 'merged-binary.hex'
240+
merge_bin_args += ['-o', output]
241+
if format:
242+
merge_bin_args += ['-f', format]
243+
if md5_disable:
244+
if format != 'uf2':
245+
yellow_print('idf.py merge-bin: --md5-disable is only valid for UF2 format. Option will be ignored.')
246+
else:
247+
merge_bin_args += ['--md5-disable']
248+
if flash_offset:
249+
if format != 'raw':
250+
yellow_print('idf.py merge-bin: --flash-offset is only valid for RAW format. Option will be ignored.')
251+
else:
252+
merge_bin_args += ['-t', flash_offset]
253+
if fill_flash_size:
254+
if format != 'raw':
255+
yellow_print('idf.py merge-bin: --fill-flash-size is only valid for RAW format, option will be ignored.')
256+
else:
257+
merge_bin_args += ['--fill-flash-size', fill_flash_size]
258+
merge_bin_args += ['@flash_args']
259+
print(f'Merged binary {output} will be created in the build directory...')
260+
RunTool('merge_bin', merge_bin_args, args.build_dir, build_dir=args.build_dir, hints=not args.no_hints)()
261+
215262
BAUD_AND_PORT = [BAUD_RATE, PORT]
216263
flash_options = BAUD_AND_PORT + [
217264
{
@@ -252,6 +299,37 @@ def ota_targets(target_name: str, ctx: click.core.Context, args: PropertyDict) -
252299
'help': 'Erase entire flash chip.',
253300
'options': BAUD_AND_PORT,
254301
},
302+
'merge-bin': {
303+
'callback': merge_bin,
304+
'options': [
305+
{
306+
'names': ['--output', '-o'],
307+
'help': ('Output filename'),
308+
'type': click.Path(),
309+
},
310+
{
311+
'names': ['--format', '-f'],
312+
'help': ('Format of the output file'),
313+
'type': click.Choice(['hex', 'uf2', 'raw']),
314+
'default': 'raw',
315+
},
316+
{
317+
'names': ['--md5-disable'],
318+
'is_flag': True,
319+
'help': ('[ONLY UF2] Disable MD5 checksum in UF2 output.'),
320+
},
321+
{
322+
'names': ['--flash-offset', '-t'],
323+
'help': ('[ONLY RAW] Flash offset where the output file will be flashed.'),
324+
},
325+
{
326+
'names': ['--fill-flash-size'],
327+
'help': ('[ONLY RAW] If set, the final binary file will be padded with FF bytes up to this flash size.'),
328+
'type': click.Choice(['256KB', '512KB', '1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB']),
329+
},
330+
],
331+
'dependencies': ['all'], # all = build
332+
},
255333
'monitor': {
256334
'callback':
257335
monitor,

tools/test_build_system/test_common.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,13 @@ def test_save_defconfig_check(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
305305
'Missing CONFIG_IDF_TARGET="esp32c3" in sdkconfig.defaults'
306306
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_PARTITION_TABLE_OFFSET=0x8001'), \
307307
'Missing CONFIG_PARTITION_TABLE_OFFSET=0x8001 in sdkconfig.defaults'
308+
309+
310+
def test_merge_bin_cmd(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
311+
logging.info('Test if merge-bin command works correctly')
312+
idf_py('merge-bin')
313+
assert (test_app_copy / 'build' / 'merged-binary.bin').is_file()
314+
idf_py('merge-bin', '--output', 'merged-binary-2.bin')
315+
assert (test_app_copy / 'build' / 'merged-binary-2.bin').is_file()
316+
idf_py('merge-bin', '--format', 'hex')
317+
assert (test_app_copy / 'build' / 'merged-binary.hex').is_file()

0 commit comments

Comments
 (0)