diff --git a/bootloader/bootloader-display.c b/bootloader/bootloader-display.c
index abc8dfc209218c80046e46be7a12d1639f4644ec..7bdbc4e187454ec921aa4d6959bdd3919a57cf7a 100644
--- a/bootloader/bootloader-display.c
+++ b/bootloader/bootloader-display.c
@@ -1,6 +1,7 @@
 #include "bootloader.h"
 /* Autogenerated */
 #include "splash-screen.h"
+#include "card10-version.h"
 
 #include "gfx.h"
 #include "display.h"
@@ -46,8 +47,12 @@ void bootloader_display_init(void)
  */
 void bootloader_display_header(void)
 {
-	txt_puts(&display_textb, "Bootloader\n");
-	txt_puts(&display_textb, __DATE__ "\n");
+	gfx_clear(&display_screen);
+
+	Color white = gfx_color(&display_screen, WHITE);
+	bootloader_display_line(0, "Bootloader", white);
+	bootloader_display_line(1, __DATE__, white);
+	bootloader_display_line(2, CARD10_VERSION, white);
 }
 
 /*
diff --git a/bootloader/bootloader-usb.c b/bootloader/bootloader-usb.c
index 67007d17ab3da03faff1dcbd0bc8b31b3bc5308d..f6e5db41e32eb605f9d9f7f253427bbc306e82e9 100644
--- a/bootloader/bootloader-usb.c
+++ b/bootloader/bootloader-usb.c
@@ -123,12 +123,12 @@ void bootloader_stop(void)
 
 void bootloader_dirty(void)
 {
-	bootloader_display_line(3, "Writing.", 0xf000);
+	bootloader_display_line(4, "Writing.", 0xf000);
 }
 
 void bootloader_clean(void)
 {
-	bootloader_display_line(3, "Ready.  ", 0xffff);
+	bootloader_display_line(4, "Ready.  ", 0xffff);
 }
 
 /******************************************************************************/
diff --git a/bootloader/main.c b/bootloader/main.c
index d3577542ede90d0840f6606be5df36764903c333..2f42b94605fcf9a0357367d8ee189f3a904aa3d1 100644
--- a/bootloader/main.c
+++ b/bootloader/main.c
@@ -1,4 +1,5 @@
 #include "bootloader.h"
+#include "card10-version.h"
 
 #include "card10.h"
 #include "led.h"
@@ -197,7 +198,7 @@ static void pmic_button(bool falling)
 /******************************************************************************/
 int main(void)
 {
-	printf("\n\nBootloader\n");
+	printf("\n\nBootloader " CARD10_VERSION "\n");
 	card10_init();
 
 	/*
@@ -210,8 +211,8 @@ int main(void)
 	// If the button is pressed, we go into MSC mode.
 	if (PB_Get(3)) {
 		bootloader_display_header();
-		bootloader_display_line(2, "USB activated.", 0xffff);
-		bootloader_display_line(3, "Ready.", 0xffff);
+		bootloader_display_line(3, "USB activated.", 0xffff);
+		bootloader_display_line(4, "Ready.", 0xffff);
 		run_usbmsc();
 
 		// If we return, don't try to boot. Maybe rather trigger a software reset.
@@ -229,7 +230,7 @@ int main(void)
 			printf("card10.bin CRC is invalid!\n");
 			bootloader_display_header();
 			bootloader_display_line(
-				2, "Integrity check failed", 0xffff
+				3, "Integrity check failed", 0xffff
 			);
 
 			bootloader_display_line(4, "Trying to boot", 0xffff);
@@ -239,7 +240,7 @@ int main(void)
 				printf("Trying to update firmware from external flash\n");
 				bootloader_display_header();
 				bootloader_display_line(
-					3, "Updating ...", 0xffff
+					4, "Updating ...", 0xffff
 				);
 				erase_partition();
 				flash_partition();
@@ -253,7 +254,7 @@ int main(void)
 	} else {
 		bootloader_display_header();
 		bootloader_display_line(
-			2, "Failed to mount filesystem", 0xffff
+			3, "Failed to mount filesystem", 0xffff
 		);
 		printf("Failed to mount the external flash\n");
 
diff --git a/bootloader/meson.build b/bootloader/meson.build
index 23c387943125a45c20b40b685edb22fc5f2936dc..5f2752b2ac41367d760c6632a2a38465b7e2d178 100644
--- a/bootloader/meson.build
+++ b/bootloader/meson.build
@@ -21,6 +21,7 @@ executable(
   'bootloader-usb.c',
   'crc16-ccitt.c',
   splash_screen,
+  version_hdr,
   dependencies: [
     libcard10,
     max32665_startup_boot,
diff --git a/epicardium/main.c b/epicardium/main.c
index 719bb3bba1f6666d8dc8f889b015a9c42e192378..f97d8f3d4b5cc045ff46e2163c2c291fc1f7d55e 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -21,6 +21,7 @@
 #include "Heart.h"
 #include "gfx.h"
 #include "display.h"
+#include "card10-version.h"
 
 #include "FreeRTOS.h"
 #include "task.h"
@@ -47,6 +48,7 @@ void vApiDispatcher(void *pvParameters)
 int main(void)
 {
 	LOG_INFO("startup", "Epicardium startup ...");
+	LOG_INFO("startup", "Version " CARD10_VERSION);
 
 	card10_init();
 	card10_diag();
diff --git a/epicardium/meson.build b/epicardium/meson.build
index 9c8b12b861200daf3edb667722b91cdde3bcd77b..4c32b0a08b69922979ece04781b3cf51c2f03a04 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -78,6 +78,7 @@ elf = executable(
   module_sources,
   l0der_sources,
   ble_sources,
+  version_hdr,
   dependencies: [libcard10, max32665_startup_core0, maxusb, libff13, ble],
   link_with: [api_dispatcher_lib, freertos],
   link_whole: [max32665_startup_core0_lib, board_card10_lib, newlib_heap_lib],
diff --git a/meson.build b/meson.build
index 4851b17596d54583608ef9cedcb890a367d89ed3..96a11fe4fbbb6d294c530a42102311989dbe542a 100644
--- a/meson.build
+++ b/meson.build
@@ -37,6 +37,15 @@ add_global_link_arguments(
 # python3 = import('python').find_installation('python3')
 python3 = 'python3'
 
+# Version Header
+version_hdr = custom_target(
+  'card10-version.h',
+  output: 'card10-version.h',
+  build_by_default: true,
+  build_always_stale: true,
+  command: [files('tools/version-header.sh'), '@OUTPUT@'],
+)
+
 subdir('lib/')
 subdir('bootloader/')
 
diff --git a/pycardium/main.c b/pycardium/main.c
index 4d7087274fab1fd78755459e094b1769ff222b47..d6fccc11d297036fd9fe2f7118129b884420d1e2 100644
--- a/pycardium/main.c
+++ b/pycardium/main.c
@@ -1,4 +1,5 @@
 #include "mphalport.h"
+#include "card10-version.h"
 
 #include "max32665.h"
 
@@ -13,8 +14,17 @@
 extern void *__StackTop, *__StackLimit;
 extern void *__HeapBase, *__HeapLimit;
 
+static const char header[] =
+	"--------------------------------\r\n"
+	"          Pycardium\r\n"
+	" Version: " CARD10_VERSION
+	"\r\n"
+	"--------------------------------\r\n";
+
 int main(void)
 {
+	epic_uart_write_str(header, sizeof(header));
+
 	pycardium_hal_init();
 
 	mp_stack_set_top(&__StackTop);
diff --git a/pycardium/meson.build b/pycardium/meson.build
index 0f460274f36f57760330df31f4336357b94e4538..8dafd3e0afa2341d913823719efffb888bea738e 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -78,6 +78,7 @@ elf = executable(
   frozen_source,
   modsrc,
   mp_headers,
+  version_hdr,
   include_directories: micropython_includes,
   dependencies: [max32665_startup_core1, periphdriver, api_caller],
   link_with: upy,
diff --git a/tools/version-header.sh b/tools/version-header.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8141763f1005b21abef4035f05c56d22cf22472a
--- /dev/null
+++ b/tools/version-header.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+#
+# Generate a version header which defines
+#
+#     CARD10_VERSION
+#     CARD10_GITHASH
+
+FW_REPO="$(dirname "$0")/.."
+HEADERFILE="$1"
+
+VERSION="$(git -C "$FW_REPO" describe --always --dirty)"
+GITHASH="$(git -C "$FW_REPO" rev-parse HEAD)"
+
+TMPHEADER="$(mktemp)"
+
+cat >"$TMPHEADER" <<EOF
+/* Autogenerated.  DO NOT EDIT */
+#ifndef _CARD10_VERSION
+#define _CARD10_VERSION
+
+#define CARD10_VERSION "$VERSION"
+#define CARD10_GITHASH "$GITHASH"
+
+#endif /* _CARD10_VERSION */
+EOF
+
+if ! cmp -s "$TMPHEADER" "$HEADERFILE"; then
+    cp "$TMPHEADER" "$HEADERFILE"
+fi
+
+rm "$TMPHEADER"