mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Refine multiboot presets, UI and build output
This commit is contained in:
1 parent
a2551d97dc
commit
919a687936
4 files changed
+318
-204
No files matched your search
+53
-36
@@ -41,52 +41,32 @@ static void mb_copy_label(char *dst, uint8_t cap, const char *src, uint8_t src_c
|
|||||||
dst[n] = 0;
|
dst[n] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mb_format_slot_label(char *dst, uint8_t cap, const mb_slot_header_t *header)
|
static uint8_t mb_copy_slot_version(char *dst, uint8_t cap, const mb_slot_header_t *header)
|
||||||
{
|
{
|
||||||
const char *version = NULL;
|
|
||||||
uint8_t version_cap = 0;
|
|
||||||
uint8_t version_len = 0;
|
|
||||||
uint8_t name_limit = cap - 1u;
|
|
||||||
uint8_t n = 0;
|
uint8_t n = 0;
|
||||||
|
|
||||||
if (!header->name[0])
|
dst[0] = 0;
|
||||||
{
|
|
||||||
mb_copy_label(dst, cap, header->fw_version, MB_VERSION_LEN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i + 1u < MB_VERSION_LEN && header->fw_version[i]; i++)
|
for (uint8_t i = 0; i + 1u < MB_VERSION_LEN && header->fw_version[i]; i++)
|
||||||
{
|
{
|
||||||
if (header->fw_version[i] == 'v' &&
|
if (header->fw_version[i] == 'v' &&
|
||||||
header->fw_version[i + 1u] >= '0' &&
|
header->fw_version[i + 1u] >= '0' &&
|
||||||
header->fw_version[i + 1u] <= '9')
|
header->fw_version[i + 1u] <= '9')
|
||||||
{
|
{
|
||||||
version = &header->fw_version[i + 1u];
|
i++;
|
||||||
version_cap = MB_VERSION_LEN - i - 1u;
|
while (n + 1u < cap && i < MB_VERSION_LEN)
|
||||||
|
{
|
||||||
|
const char c = header->fw_version[i];
|
||||||
|
if ((c < '0' || c > '9') && c != '.')
|
||||||
|
break;
|
||||||
|
dst[n++] = c;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version)
|
|
||||||
{
|
|
||||||
while (version_len < version_cap && version[version_len])
|
|
||||||
version_len++;
|
|
||||||
if (version_len + 1u < cap)
|
|
||||||
name_limit = cap - version_len - 2u;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (n < name_limit && n < MB_NAME_LEN && header->name[n])
|
|
||||||
{
|
|
||||||
dst[n] = header->name[n];
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
if (version && n + version_len + 1u < cap)
|
|
||||||
{
|
|
||||||
dst[n++] = ' ';
|
|
||||||
for (uint8_t i = 0; i < version_len; i++)
|
|
||||||
dst[n++] = version[i];
|
|
||||||
}
|
|
||||||
dst[n] = 0;
|
dst[n] = 0;
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "MULTIBOOT" mode banner in the top status bar, shown on every screen - the
|
/* "MULTIBOOT" mode banner in the top status bar, shown on every screen - the
|
||||||
@@ -116,6 +96,17 @@ static void mb_key_hints(const char *act_menu, const char *act_exit)
|
|||||||
GUI_DisplaySmallest(act_exit, (uint8_t)(xe + 16u + sp), 49, false, true);
|
GUI_DisplaySmallest(act_exit, (uint8_t)(xe + 16u + sp), 49, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void mb_invert_rounded_row(uint8_t line)
|
||||||
|
{
|
||||||
|
gFrameBuffer[line][0] ^= 0x7Fu;
|
||||||
|
for (uint8_t x = 1u; x < LCD_WIDTH - 1u; x++)
|
||||||
|
{
|
||||||
|
gFrameBuffer[line][x] ^= 0xFFu;
|
||||||
|
gFrameBuffer[line - 1u][x] ^= 0x80u;
|
||||||
|
}
|
||||||
|
gFrameBuffer[line][LCD_WIDTH - 1u] ^= 0x7Fu;
|
||||||
|
}
|
||||||
|
|
||||||
static void mb_show_message(const char *line1, const char *line2, const char *line3)
|
static void mb_show_message(const char *line1, const char *line2, const char *line3)
|
||||||
{
|
{
|
||||||
UI_DisplayClear();
|
UI_DisplayClear();
|
||||||
@@ -171,6 +162,7 @@ static void mb_render_slots(uint8_t selected,
|
|||||||
const uint8_t status[MB_SLOT_COUNT])
|
const uint8_t status[MB_SLOT_COUNT])
|
||||||
{
|
{
|
||||||
char line[19]; /* 18 glyphs max: 18 * 7 px fits from x=2 to x=126. */
|
char line[19]; /* 18 glyphs max: 18 * 7 px fits from x=2 to x=126. */
|
||||||
|
char version[MB_VERSION_LEN];
|
||||||
|
|
||||||
UI_DisplayClear();
|
UI_DisplayClear();
|
||||||
mb_status_bar();
|
mb_status_bar();
|
||||||
@@ -178,23 +170,48 @@ static void mb_render_slots(uint8_t selected,
|
|||||||
for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++)
|
for (uint8_t slot = 0; slot < MB_SLOT_COUNT; slot++)
|
||||||
{
|
{
|
||||||
const uint8_t fbLine = (uint8_t)(slot + 1u); /* page 1 stays blank */
|
const uint8_t fbLine = (uint8_t)(slot + 1u); /* page 1 stays blank */
|
||||||
|
uint8_t version_len = 0;
|
||||||
|
uint8_t version_x = 0;
|
||||||
|
|
||||||
memset(line, 0, sizeof(line));
|
memset(line, 0, sizeof(line));
|
||||||
|
memset(version, 0, sizeof(version));
|
||||||
line[0] = (char)('0' + slot);
|
line[0] = (char)('0' + slot);
|
||||||
line[1] = ' ';
|
line[1] = ' ';
|
||||||
line[2] = ' ';
|
line[2] = ' ';
|
||||||
|
|
||||||
if (status[slot] == MB_OK)
|
if (status[slot] == MB_OK)
|
||||||
mb_format_slot_label(&line[3], sizeof(line) - 3u, &headers[slot]);
|
{
|
||||||
|
uint8_t name_cap = sizeof(line) - 3u;
|
||||||
|
|
||||||
|
version_len = mb_copy_slot_version(version, sizeof(version), &headers[slot]);
|
||||||
|
if (version_len)
|
||||||
|
{
|
||||||
|
const uint8_t name_x = 2u + 3u * 7u;
|
||||||
|
uint8_t available;
|
||||||
|
|
||||||
|
version_x = (uint8_t)(LCD_WIDTH - 2u - version_len * 7u);
|
||||||
|
available = version_x > name_x
|
||||||
|
? (uint8_t)((version_x - name_x) / 7u)
|
||||||
|
: 0u;
|
||||||
|
if (available + 1u < name_cap)
|
||||||
|
name_cap = available + 1u;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers[slot].name[0])
|
||||||
|
mb_copy_label(&line[3], name_cap, headers[slot].name, MB_NAME_LEN);
|
||||||
|
else if (!version_len)
|
||||||
|
mb_copy_label(&line[3], name_cap, headers[slot].fw_version, MB_VERSION_LEN);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
mb_copy_label(&line[3], sizeof(line) - 3u, mb_error_text(status[slot]), 20u);
|
mb_copy_label(&line[3], sizeof(line) - 3u, mb_error_text(status[slot]), 20u);
|
||||||
|
|
||||||
UI_PrintStringSmallNormal(line, 2, 0, fbLine);
|
UI_PrintStringSmallNormal(line, 2, 0, fbLine);
|
||||||
|
if (version_len)
|
||||||
|
UI_PrintStringSmallNormal(version, version_x, 0, fbLine);
|
||||||
|
|
||||||
/* Selected row: full-width inverse bar, like the firmware menu list. */
|
/* Selected row: full-width rounded inverse capsule. */
|
||||||
if (slot == selected)
|
if (slot == selected)
|
||||||
for (uint8_t x = 0; x < LCD_WIDTH; x++)
|
mb_invert_rounded_row(fbLine);
|
||||||
gFrameBuffer[fbLine][x] ^= 0xFFu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mb_key_hints("SELECT", "QUIT");
|
mb_key_hints("SELECT", "QUIT");
|
||||||
|
|||||||
+52
-119
@@ -99,99 +99,6 @@
|
|||||||
"TARGET": "f4hwn.custom"
|
"TARGET": "f4hwn.custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Bandscope",
|
|
||||||
"inherits": "default",
|
|
||||||
"cacheVariables": {
|
|
||||||
"ENABLE_SPECTRUM": true,
|
|
||||||
"ENABLE_FMRADIO": false,
|
|
||||||
"ENABLE_VOX": false,
|
|
||||||
"ENABLE_AIRCOPY": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_RESCUE_OPS": false,
|
|
||||||
"EDITION_STRING": "Bandscope",
|
|
||||||
"TARGET": "f4hwn.bandscope"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Broadcast",
|
|
||||||
"inherits": "default",
|
|
||||||
"cacheVariables": {
|
|
||||||
"ENABLE_SPECTRUM": false,
|
|
||||||
"ENABLE_FMRADIO": true,
|
|
||||||
"ENABLE_VOX": true,
|
|
||||||
"ENABLE_AIRCOPY": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_RESCUE_OPS": false,
|
|
||||||
"EDITION_STRING": "Broadcast",
|
|
||||||
"TARGET": "f4hwn.broadcast"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Basic",
|
|
||||||
"inherits": "default",
|
|
||||||
"cacheVariables": {
|
|
||||||
"ENABLE_SPECTRUM": true,
|
|
||||||
"ENABLE_FMRADIO": true,
|
|
||||||
"ENABLE_VOX": false,
|
|
||||||
"ENABLE_AIRCOPY": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_K5VIEWER": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_SPECTRUM": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
|
||||||
"ENABLE_AUDIO_BAR": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_RESUME_STATE": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_CHARGING_C": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_INV": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_CTR": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_NARROWER": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_RESCUE_OPS": false,
|
|
||||||
"EDITION_STRING": "Basic",
|
|
||||||
"TARGET": "f4hwn.basic"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "RescueOps",
|
|
||||||
"inherits": "default",
|
|
||||||
"cacheVariables": {
|
|
||||||
"ENABLE_SPECTRUM": false,
|
|
||||||
"ENABLE_FMRADIO": false,
|
|
||||||
"ENABLE_VOX": true,
|
|
||||||
"ENABLE_AIRCOPY": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
|
||||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
|
||||||
"ENABLE_NOAA": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_RESCUE_OPS": true,
|
|
||||||
"EDITION_STRING": "RescueOps",
|
|
||||||
"TARGET": "f4hwn.rescueops"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Game",
|
|
||||||
"inherits": "default",
|
|
||||||
"cacheVariables": {
|
|
||||||
"ENABLE_SPECTRUM": false,
|
|
||||||
"ENABLE_FMRADIO": true,
|
|
||||||
"ENABLE_VOX": false,
|
|
||||||
"ENABLE_AIRCOPY": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GAME": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_RESCUE_OPS": false,
|
|
||||||
"EDITION_STRING": "Game",
|
|
||||||
"TARGET": "f4hwn.game"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Fusion",
|
"name": "Fusion",
|
||||||
"inherits": "default",
|
"inherits": "default",
|
||||||
@@ -199,12 +106,12 @@
|
|||||||
"ENABLE_SPECTRUM": true,
|
"ENABLE_SPECTRUM": true,
|
||||||
"ENABLE_FMRADIO": true,
|
"ENABLE_FMRADIO": true,
|
||||||
"ENABLE_VOX": true,
|
"ENABLE_VOX": true,
|
||||||
"ENABLE_AIRCOPY": true,
|
"ENABLE_AIRCOPY": false,
|
||||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
||||||
"ENABLE_FEAT_F4HWN_GAME": true,
|
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
||||||
"ENABLE_FEAT_F4HWN_RESCUE_OPS": true,
|
"ENABLE_FEAT_F4HWN_RESCUE_OPS": false,
|
||||||
"ENABLE_FEAT_F4HWN_VOL": true,
|
"ENABLE_FEAT_F4HWN_VOL": true,
|
||||||
"ENABLE_FEAT_F4HWN_AUDIO": true,
|
"ENABLE_FEAT_F4HWN_AUDIO": true,
|
||||||
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
|
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
|
||||||
@@ -212,9 +119,8 @@
|
|||||||
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
|
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
|
||||||
"ENABLE_FEAT_F4HWN_SCAN_RSSI": true,
|
"ENABLE_FEAT_F4HWN_SCAN_RSSI": true,
|
||||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
|
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
|
||||||
"ENABLE_FEAT_F4HWN_BEAM": true,
|
"ENABLE_FEAT_F4HWN_BEAM": false,
|
||||||
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
|
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
|
||||||
"ENABLE_FEAT_F4HWN_ACTION_PICKER": true,
|
|
||||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
|
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
|
||||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
|
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
|
||||||
@@ -223,10 +129,45 @@
|
|||||||
"ENABLE_FEAT_F4HWN_LOGO": true,
|
"ENABLE_FEAT_F4HWN_LOGO": true,
|
||||||
"ENABLE_FEAT_F4HWN_LOGO_SAV": true,
|
"ENABLE_FEAT_F4HWN_LOGO_SAV": true,
|
||||||
"ENABLE_FEAT_F4HWN_MENU_CAT": true,
|
"ENABLE_FEAT_F4HWN_MENU_CAT": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_ACTION_PICKER": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_MULTIBOOT": true,
|
||||||
"ENABLE_SWD": true,
|
"ENABLE_SWD": true,
|
||||||
"EDITION_STRING": "Fusion",
|
"EDITION_STRING": "Fusion",
|
||||||
"TARGET": "f4hwn.fusion"
|
"TARGET": "f4hwn.fusion"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Transfer",
|
||||||
|
"inherits": "Fusion",
|
||||||
|
"cacheVariables": {
|
||||||
|
"ENABLE_AIRCOPY": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_BEAM": true,
|
||||||
|
"EDITION_STRING": "Transfer",
|
||||||
|
"TARGET": "f4hwn.transfer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Field",
|
||||||
|
"inherits": "Fusion",
|
||||||
|
"cacheVariables": {
|
||||||
|
"ENABLE_FEAT_F4HWN_RESCUE_OPS": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
|
||||||
|
"EDITION_STRING": "Field",
|
||||||
|
"TARGET": "f4hwn.field"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Extended",
|
||||||
|
"inherits": "Fusion",
|
||||||
|
"cacheVariables": {
|
||||||
|
"ENABLE_AIRCOPY": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_GAME": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_RESCUE_OPS": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_BEAM": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
|
||||||
|
"EDITION_STRING": "Extended",
|
||||||
|
"TARGET": "f4hwn.extended"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buildPresets": [
|
"buildPresets": [
|
||||||
@@ -234,29 +175,21 @@
|
|||||||
"name": "Custom",
|
"name": "Custom",
|
||||||
"configurePreset": "Custom"
|
"configurePreset": "Custom"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Bandscope",
|
|
||||||
"configurePreset": "Bandscope"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Broadcast",
|
|
||||||
"configurePreset": "Broadcast"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Basic",
|
|
||||||
"configurePreset": "Basic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "RescueOps",
|
|
||||||
"configurePreset": "RescueOps"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Game",
|
|
||||||
"configurePreset": "Game"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Fusion",
|
"name": "Fusion",
|
||||||
"configurePreset": "Fusion"
|
"configurePreset": "Fusion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Transfer",
|
||||||
|
"configurePreset": "Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Field",
|
||||||
|
"configurePreset": "Field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Extended",
|
||||||
|
"configurePreset": "Extended"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -59,18 +59,23 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
|
|||||||
|
|
||||||
### Fusion edition
|
### Fusion edition
|
||||||
|
|
||||||
Fusion is the reference edition of the project. It provides an all-in-one firmware for the UV-K1 and UV-K5 V3, including:
|
Fusion is the generic reference edition for the UV-K1 and UV-K5 V3. It is intended
|
||||||
|
for everyday use and is the base inherited by the specialized editions. It includes:
|
||||||
|
|
||||||
- Fagci's spectrum analyzer,
|
- Fagci's spectrum analyzer,
|
||||||
- broadcast FM radio,
|
- broadcast FM radio and VOX,
|
||||||
- VOX and AirCopy,
|
|
||||||
- BEAM wireless channel transfer,
|
|
||||||
- [UV Studio](https://armel.github.io/uvstudio/) with integrated K5Viewer screen mirroring, screenshots and remote keyboard control,
|
- [UV Studio](https://armel.github.io/uvstudio/) with integrated K5Viewer screen mirroring, screenshots and remote keyboard control,
|
||||||
- advanced RX audio profiles and Audio Scope,
|
- advanced RX audio profiles and Audio Scope,
|
||||||
- first-responder-oriented controls,
|
|
||||||
- the Breakout game,
|
- the Breakout game,
|
||||||
- automatic RX/TX activity logging with RF Log,
|
- automatic RX/TX activity logging with RF Log,
|
||||||
- full Fox Hunt and Morse Beacon support.
|
- multiboot support.
|
||||||
|
|
||||||
|
Specialized presets extend Fusion for specific uses:
|
||||||
|
|
||||||
|
- **Transfer** adds AirCopy and BEAM wireless channel transfer.
|
||||||
|
- **Field** adds first-responder controls, Fox Hunt and Morse Beacon support.
|
||||||
|
- **Extended** combines the Transfer and Field feature sets without claiming to include every optional feature.
|
||||||
|
- **Custom** remains a manually configured build based directly on the hidden technical default.
|
||||||
|
|
||||||
### Radio and signal handling
|
### Radio and signal handling
|
||||||
|
|
||||||
@@ -324,9 +329,10 @@ But, they are nice toys for the price, fun to play with.
|
|||||||
|
|
||||||
## Compiling and Building from Docker
|
## Compiling and Building from Docker
|
||||||
|
|
||||||
This project provides a Docker-based build system to compile the Fusion firmware for the UV-K1 and UV-K5 V3. Everything is handled through the `compile-with-docker.sh` helper script.
|
This project provides a Docker-based build system for the UV-K1 and UV-K5 V3.
|
||||||
|
Everything is handled through the `compile-with-docker.sh` helper script. Fusion is
|
||||||
The documented build output is generated inside `build/Fusion`, using the CMake presets defined in `CMakePresets.json`.
|
the default generic preset, while specialized builds are generated in their own
|
||||||
|
`build/<Preset>` directories.
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
@@ -335,29 +341,37 @@ The documented build output is generated inside `build/Fusion`, using the CMake
|
|||||||
|
|
||||||
### Build Script Overview
|
### Build Script Overview
|
||||||
|
|
||||||
The script `compile-with-docker.sh` performs the following actions:
|
The script `compile-with-docker.sh`:
|
||||||
|
|
||||||
1. Builds the Docker image (`uvk1-uvk5v3`) if it does not already exist.
|
1. Builds the Docker image (`uvk1-uvk5v3`) if it does not already exist.
|
||||||
2. Removes any previous `build` directory to ensure a clean configuration.
|
2. Configures the selected preset with `cmake --fresh`.
|
||||||
3. Runs CMake using the `Fusion` preset inside the Docker container.
|
3. Builds the firmware and outputs matching `.elf`, `.bin` and `.hex` files.
|
||||||
4. Builds the firmware and outputs `.elf`, `.bin` and `.hex` files.
|
4. Displays Flash and RAM usage; `All` keeps the individual build logs quiet.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./compile-with-docker.sh Fusion [extra CMake options]
|
./compile-with-docker.sh [Preset] [extra CMake options]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Documented Preset
|
The default preset is **Fusion**. Available presets are:
|
||||||
|
|
||||||
|
- **Custom**
|
||||||
- **Fusion**
|
- **Fusion**
|
||||||
|
- **Transfer**
|
||||||
|
- **Field**
|
||||||
|
- **Extended**
|
||||||
|
- **All** (Fusion, Transfer, Field and Extended)
|
||||||
|
|
||||||
### Examples
|
Examples:
|
||||||
|
|
||||||
Build Fusion:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
./compile-with-docker.sh
|
||||||
./compile-with-docker.sh Fusion
|
./compile-with-docker.sh Fusion
|
||||||
|
./compile-with-docker.sh Transfer
|
||||||
|
./compile-with-docker.sh Field
|
||||||
|
./compile-with-docker.sh Extended
|
||||||
|
./compile-with-docker.sh All
|
||||||
```
|
```
|
||||||
|
|
||||||
### Passing Additional CMake Options
|
### Passing Additional CMake Options
|
||||||
|
|||||||
+181
-31
@@ -5,16 +5,21 @@ set -euo pipefail
|
|||||||
# Usage:
|
# Usage:
|
||||||
# ./compile-with-docker.sh [Preset] [CMake options...]
|
# ./compile-with-docker.sh [Preset] [CMake options...]
|
||||||
# Examples:
|
# Examples:
|
||||||
# ./compile-with-docker.sh Custom
|
# ./compile-with-docker.sh Fusion
|
||||||
# ./compile-with-docker.sh Bandscope -DENABLE_SPECTRUM=ON
|
# ./compile-with-docker.sh Transfer
|
||||||
# ./compile-with-docker.sh Broadcast -DENABLE_FEAT_F4HWN_GAME=ON -DENABLE_NOAA=ON
|
# ./compile-with-docker.sh Field -DENABLE_NOAA=ON
|
||||||
|
# ./compile-with-docker.sh Custom -DENABLE_SPECTRUM=ON
|
||||||
# ./compile-with-docker.sh Fusion -DDEV=ON
|
# ./compile-with-docker.sh Fusion -DDEV=ON
|
||||||
# ./compile-with-docker.sh All
|
# ./compile-with-docker.sh All
|
||||||
# Default preset: "Custom"
|
# Default preset: "Fusion"
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
IMAGE=uvk1-uvk5v3
|
IMAGE=uvk1-uvk5v3
|
||||||
PRESET=${1:-Custom}
|
RELEASE_PRESETS=(Fusion Transfer Field Extended)
|
||||||
|
FLASH_LIMIT=$((118 * 1024))
|
||||||
|
RAM_LIMIT=$((16 * 1024))
|
||||||
|
|
||||||
|
PRESET=${1:-Fusion}
|
||||||
shift || true # remove preset from arguments if present
|
shift || true # remove preset from arguments if present
|
||||||
|
|
||||||
# Any remaining args will be treated as CMake cache variables
|
# Any remaining args will be treated as CMake cache variables
|
||||||
@@ -23,53 +28,198 @@ EXTRA_ARGS=("$@")
|
|||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
# Validate preset name
|
# Validate preset name
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
if [[ ! "$PRESET" =~ ^(Custom|Bandscope|Broadcast|Basic|RescueOps|Game|Fusion|All)$ ]]; then
|
if [[ ! "$PRESET" =~ ^(Custom|Fusion|Transfer|Field|Extended|All)$ ]]; then
|
||||||
echo "❌ Unknown preset: '$PRESET'"
|
echo "❌ Unknown preset: '$PRESET'"
|
||||||
echo "Valid presets are: Custom, Bandscope, Broadcast, Basic, RescueOps, Game, Fusion, All"
|
echo "Valid presets are: Custom, Fusion, Transfer, Field, Extended, All"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
QUIET=0
|
||||||
|
if [[ "$PRESET" == "All" ]]; then
|
||||||
|
QUIET=1
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
# Build the Docker image (only needed once)
|
# Build the Docker image (only needed once)
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
if [[ "$(docker images -q $IMAGE)" == "" ]]; then
|
if [[ "$(docker images -q "$IMAGE")" == "" ]]; then
|
||||||
echo "Building Docker image..."
|
echo "Building Docker image..."
|
||||||
docker build -t "$IMAGE" .
|
docker build -q -t "$IMAGE" . >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------
|
|
||||||
# Clean existing CMake cache to ensure toolchain reload
|
|
||||||
# ---------------------------------------------
|
|
||||||
rm -rf build
|
|
||||||
export MSYS_NO_PATHCONV=1
|
export MSYS_NO_PATHCONV=1
|
||||||
# ---------------------------------------------
|
|
||||||
# Function to build one preset
|
RESULT_PRESETS=()
|
||||||
# ---------------------------------------------
|
RESULT_FLASH_SIZES=()
|
||||||
|
RESULT_RAM_SIZES=()
|
||||||
|
|
||||||
|
run_preset_build() {
|
||||||
|
local preset="$1"
|
||||||
|
|
||||||
|
docker run --rm \
|
||||||
|
-u "$(id -u):$(id -g)" \
|
||||||
|
-v "$PWD":/src -w /src "$IMAGE" \
|
||||||
|
bash -c 'which arm-none-eabi-gcc && arm-none-eabi-gcc --version &&
|
||||||
|
cmake --fresh --preset "$1" "${@:2}" &&
|
||||||
|
cmake --build --preset "$1" -j' \
|
||||||
|
bash "$preset" "${EXTRA_ARGS[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
build_preset() {
|
build_preset() {
|
||||||
local preset="$1"
|
local preset="$1"
|
||||||
|
local preset_slug log_file bin_file flash_size ram_size status
|
||||||
|
|
||||||
|
preset_slug="${preset,,}"
|
||||||
|
log_file="$(mktemp)"
|
||||||
|
bin_file=""
|
||||||
|
|
||||||
|
find "build/${preset}" -maxdepth 1 -type f -name 'f4hwn.*' -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
if (( QUIET )); then
|
||||||
|
printf "Building %-10s ... " "$preset"
|
||||||
|
if run_preset_build "$preset" >"$log_file" 2>&1; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "=== 🚀 Building preset: ${preset} ==="
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
if run_preset_build "$preset" 2>&1 | tee "$log_file" | awk '
|
||||||
|
/^Memory region[[:space:]]+Used Size[[:space:]]+Region Size/ { next }
|
||||||
|
/^[[:space:]]+RAM:[[:space:]]/ { next }
|
||||||
|
/^[[:space:]]+FLASH:[[:space:]]/ { next }
|
||||||
|
{ print; fflush() }
|
||||||
|
'; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( status != 0 )); then
|
||||||
|
if (( QUIET )); then
|
||||||
|
printf "FAILED\n\n"
|
||||||
|
cat "$log_file"
|
||||||
|
else
|
||||||
|
echo "Failed: ${preset}"
|
||||||
|
fi
|
||||||
|
rm -f -- "$log_file"
|
||||||
|
return "$status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bin_file="build/${preset}/f4hwn.${preset_slug}.bin"
|
||||||
|
if [[ -z "$bin_file" || ! -f "$bin_file" ]]; then
|
||||||
|
if (( QUIET )); then
|
||||||
|
printf "FAILED\n"
|
||||||
|
fi
|
||||||
|
echo "Expected binary not found: $bin_file"
|
||||||
|
if (( QUIET )); then
|
||||||
|
cat "$log_file"
|
||||||
|
fi
|
||||||
|
rm -f -- "$log_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
flash_size="$(wc -c < "$bin_file")"
|
||||||
|
ram_size="$(awk '$1 == "RAM:" {
|
||||||
|
value = $2
|
||||||
|
if ($3 == "KB") value *= 1024
|
||||||
|
else if ($3 == "MB") value *= 1024 * 1024
|
||||||
|
printf "%.0f\n", value
|
||||||
|
}' "$log_file" | tail -n 1)"
|
||||||
|
|
||||||
|
if [[ ! "$ram_size" =~ ^[0-9]+$ ]]; then
|
||||||
|
if (( QUIET )); then
|
||||||
|
printf "FAILED\n"
|
||||||
|
fi
|
||||||
|
echo "Could not read RAM usage from linker output"
|
||||||
|
if (( QUIET )); then
|
||||||
|
cat "$log_file"
|
||||||
|
fi
|
||||||
|
rm -f -- "$log_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESULT_PRESETS+=("$preset")
|
||||||
|
RESULT_FLASH_SIZES+=("$flash_size")
|
||||||
|
RESULT_RAM_SIZES+=("$ram_size")
|
||||||
|
|
||||||
|
if (( QUIET )); then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
echo "✅ Done: ${preset}"
|
||||||
|
fi
|
||||||
|
rm -f -- "$log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
local i
|
||||||
|
local flash_used flash_free flash_bp flash_pct
|
||||||
|
local flash_used_kib100 flash_free_kib100 flash_used_kib flash_free_kib
|
||||||
|
local ram_used ram_free ram_bp ram_pct
|
||||||
|
local ram_used_kib100 ram_free_kib100 ram_used_kib ram_free_kib
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== 🚀 Building preset: ${preset} ==="
|
printf "Flash (limit: %d B / %d.00 KiB)\n" "$FLASH_LIMIT" "$((FLASH_LIMIT / 1024))"
|
||||||
echo "---------------------------------------------"
|
printf "%-10s | %10s | %10s | %10s | %10s | %7s\n" \
|
||||||
docker run --rm \
|
"Preset" "Used (B)" "Used (KiB)" "Free (B)" "Free (KiB)" "Usage"
|
||||||
-u $(id -u):$(id -g) \
|
printf "%-10s-+-%10s-+-%10s-+-%10s-+-%10s-+-%7s\n" \
|
||||||
-it -v "$PWD":/src -w /src "$IMAGE" \
|
"----------" "----------" "----------" "----------" "----------" "-------"
|
||||||
bash -c 'which arm-none-eabi-gcc && arm-none-eabi-gcc --version &&
|
|
||||||
cmake --preset "$1" "${@:2}" &&
|
for i in "${!RESULT_PRESETS[@]}"; do
|
||||||
cmake --build --preset "$1" -j' \
|
flash_used="${RESULT_FLASH_SIZES[$i]}"
|
||||||
bash "${preset}" ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}
|
flash_free=$((FLASH_LIMIT - flash_used))
|
||||||
echo "✅ Done: ${preset}"
|
flash_bp=$(((flash_used * 10000 + FLASH_LIMIT / 2) / FLASH_LIMIT))
|
||||||
|
flash_used_kib100=$(((flash_used * 100 + 512) / 1024))
|
||||||
|
flash_free_kib100=$(((flash_free * 100 + 512) / 1024))
|
||||||
|
printf -v flash_pct "%d.%02d%%" "$((flash_bp / 100))" "$((flash_bp % 100))"
|
||||||
|
printf -v flash_used_kib "%d.%02d" "$((flash_used_kib100 / 100))" "$((flash_used_kib100 % 100))"
|
||||||
|
printf -v flash_free_kib "%d.%02d" "$((flash_free_kib100 / 100))" "$((flash_free_kib100 % 100))"
|
||||||
|
|
||||||
|
printf "%-10s | %10d | %10s | %10d | %10s | %7s\n" \
|
||||||
|
"${RESULT_PRESETS[$i]}" "$flash_used" "$flash_used_kib" \
|
||||||
|
"$flash_free" "$flash_free_kib" "$flash_pct"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
printf "RAM (limit: %d B / %d.00 KiB)\n" "$RAM_LIMIT" "$((RAM_LIMIT / 1024))"
|
||||||
|
printf "%-10s | %10s | %10s | %10s | %10s | %7s\n" \
|
||||||
|
"Preset" "Used (B)" "Used (KiB)" "Free (B)" "Free (KiB)" "Usage"
|
||||||
|
printf "%-10s-+-%10s-+-%10s-+-%10s-+-%10s-+-%7s\n" \
|
||||||
|
"----------" "----------" "----------" "----------" "----------" "-------"
|
||||||
|
|
||||||
|
for i in "${!RESULT_PRESETS[@]}"; do
|
||||||
|
ram_used="${RESULT_RAM_SIZES[$i]}"
|
||||||
|
ram_free=$((RAM_LIMIT - ram_used))
|
||||||
|
ram_bp=$(((ram_used * 10000 + RAM_LIMIT / 2) / RAM_LIMIT))
|
||||||
|
ram_used_kib100=$(((ram_used * 100 + 512) / 1024))
|
||||||
|
ram_free_kib100=$(((ram_free * 100 + 512) / 1024))
|
||||||
|
printf -v ram_pct "%d.%02d%%" "$((ram_bp / 100))" "$((ram_bp % 100))"
|
||||||
|
printf -v ram_used_kib "%d.%02d" "$((ram_used_kib100 / 100))" "$((ram_used_kib100 % 100))"
|
||||||
|
printf -v ram_free_kib "%d.%02d" "$((ram_free_kib100 / 100))" "$((ram_free_kib100 % 100))"
|
||||||
|
|
||||||
|
printf "%-10s | %10d | %10s | %10d | %10s | %7s\n" \
|
||||||
|
"${RESULT_PRESETS[$i]}" "$ram_used" "$ram_used_kib" \
|
||||||
|
"$ram_free" "$ram_free_kib" "$ram_pct"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if (( ${#RESULT_PRESETS[@]} == 1 )); then
|
||||||
|
echo "🎉 Preset ${RESULT_PRESETS[0]} built successfully!"
|
||||||
|
else
|
||||||
|
echo "🎉 All presets built successfully!"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
# Handle 'All' preset
|
# Handle 'All' preset
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
if [[ "$PRESET" == "All" ]]; then
|
if [[ "$PRESET" == "All" ]]; then
|
||||||
PRESETS=(Bandscope Broadcast Basic RescueOps Game Fusion)
|
for p in "${RELEASE_PRESETS[@]}"; do
|
||||||
for p in "${PRESETS[@]}"; do
|
|
||||||
build_preset "$p"
|
build_preset "$p"
|
||||||
done
|
done
|
||||||
echo ""
|
|
||||||
echo "🎉 All presets built successfully!"
|
|
||||||
else
|
else
|
||||||
build_preset "$PRESET"
|
build_preset "$PRESET"
|
||||||
fi
|
fi
|
||||||
|
print_summary
|
||||||
Reference in new issue
Block a user