diff --git a/bootloader/build_image.sh b/bootloader/build_image.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6d3e53ef85d247062c3b066c3b7170091c44a0e3
--- /dev/null
+++ b/bootloader/build_image.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+
+PYTHON="$1"
+ELF="$2"
+BIN="$3"
+
+arm-none-eabi-objcopy -O binary "$ELF" "$BIN"
+"$PYTHON" "$(dirname "$0")/crc_patch.py" "$BIN"
diff --git a/bootloader/meson.build b/bootloader/meson.build
index 04a4f73c15a936ae700155a07d281bfebc2a068d..d97a221787468c619ad732344865f363dd0f1f5f 100644
--- a/bootloader/meson.build
+++ b/bootloader/meson.build
@@ -17,3 +17,6 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+# build_image.sh
+build_image = [files('./build_image.sh'), python3]
diff --git a/hw-tests/bmatest/meson.build b/hw-tests/bmatest/meson.build
index 0f3899a241f43665aa20c5708f20a5bb97548f7f..9559f67daab678362be407db8375d8d93d5fb57b 100644
--- a/hw-tests/bmatest/meson.build
+++ b/hw-tests/bmatest/meson.build
@@ -1,6 +1,6 @@
 name = 'bmatest'
 
-executable(
+elf = executable(
   name + '.elf',
   'main.c',
   dependencies: [libcard10, max32665_startup],
@@ -9,3 +9,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)
diff --git a/hw-tests/bmetest/meson.build b/hw-tests/bmetest/meson.build
index ed1e72b98faa0f931ea3484bad18b201db2c6bb5..86e15050123c0ed45d5e9d06a4f7452c11ffc129 100644
--- a/hw-tests/bmetest/meson.build
+++ b/hw-tests/bmetest/meson.build
@@ -1,6 +1,6 @@
 name = 'bmetest'
 
-executable(
+elf = executable(
   name + '.elf',
   'main.c',
   dependencies: [libcard10, max32665_startup],
@@ -9,3 +9,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)
diff --git a/hw-tests/ecgtest/meson.build b/hw-tests/ecgtest/meson.build
index 7e4f32d00573a1a2105dc9023848c9295904d0de..9b2f80f3f1a7671d0fb44ec9e0395b9b0119d17a 100644
--- a/hw-tests/ecgtest/meson.build
+++ b/hw-tests/ecgtest/meson.build
@@ -1,6 +1,6 @@
 name = 'ecgtest'
 
-executable(
+elf = executable(
   name + '.elf',
   'main.c',
   dependencies: [libcard10, max32665_startup],
@@ -9,3 +9,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)
diff --git a/hw-tests/hello-freertos/meson.build b/hw-tests/hello-freertos/meson.build
index fae1dbd229d6223d59a409503b8801edfce7a1e2..457a676815e787efba07fe1d445802e38286d521 100644
--- a/hw-tests/hello-freertos/meson.build
+++ b/hw-tests/hello-freertos/meson.build
@@ -16,7 +16,7 @@ sources = files(
   'main.c',
 )
 
-executable(
+elf = executable(
   name + '.elf',
   sources,
   include_directories: freertos_sdk_includes,
@@ -27,3 +27,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)
diff --git a/hw-tests/hello-world/meson.build b/hw-tests/hello-world/meson.build
index 7519ca62510228c5a911a369a2ffbb9887abc4f0..2c687cb0291b8943f465fa5ce7441516cec3330d 100644
--- a/hw-tests/hello-world/meson.build
+++ b/hw-tests/hello-world/meson.build
@@ -1,6 +1,6 @@
 name = 'hello-world'
 
-executable(
+elf = executable(
   name + '.elf',
   'main.c',
   dependencies: [libcard10, max32665_startup],
@@ -9,3 +9,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)
diff --git a/hw-tests/imutest/meson.build b/hw-tests/imutest/meson.build
index 18825ee67a0f7e04269bf8a610c14401417d2343..d87e43d01b47c275f61f8fdf203e4b1d59199530 100644
--- a/hw-tests/imutest/meson.build
+++ b/hw-tests/imutest/meson.build
@@ -1,6 +1,6 @@
 name = 'imutest'
 
-executable(
+elf = executable(
   name + '.elf',
   'main.c',
   dependencies: [libcard10, max32665_startup],
@@ -9,3 +9,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)
diff --git a/hw-tests/ips/meson.build b/hw-tests/ips/meson.build
index 10ff3cf7731acececa4a83f338b8b5519ed63007..d4c782f47720cc7bc675cdf9d69b39b60ff34178 100644
--- a/hw-tests/ips/meson.build
+++ b/hw-tests/ips/meson.build
@@ -1,6 +1,6 @@
 name = 'ips'
 
-executable(
+elf = executable(
   name + '.elf',
   'main.c',
   'image/image.c',
@@ -11,3 +11,11 @@ executable(
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
 )
+
+custom_target(
+  name + '.bin',
+  build_by_default: true,
+  output: name + '.bin',
+  input: elf,
+  command: [build_image, '@INPUT@', '@OUTPUT0@'],
+)