Compare commits

..

7 commits

Author SHA1 Message Date
Marcus Kida
fa2ad5d0d2 Docs: document zone-based scanning; make version example non-drifting
The Scanning section only said 'per upstream OpenGD77', which left the
scan-list question unanswered. Explain that zones are the scan list, the
Zone Skip / All Skip flags, the start key and the Radio options settings.
Also replace the version example that goes stale every release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 17:37:14 +02:00
Marcus Kida
51a1c04b9b README: refresh screenshots and show messaging + menu screens
Replace the VFO/DMR screenshots with current captures and add the main
menu, the Messages menu and a received message. All shot with the
Neon 80s theme.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 17:34:38 +02:00
Marcus Kida
66aac3587f Release 0.11.0
CRT-style shut-off animation that collapses the current screen when powering
off (colour displays), replacing the 'Power Off' message.

Bump version to 0.11.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:51:39 +02:00
Marcus Kida
bc696ae5b2 Power off: CRT-style collapse animation
Replace the 'Power Off' / 73 message on colour displays with a shut-off
animation that collapses the current screen towards the middle (vertical
squeeze to a line, then horizontal collapse to a centre dot) like an old CRT.
Snapshots the live framebuffer into the (idle) notification offscreen buffer
so no extra RAM is needed; runs blocking with vTaskDelay between frames.
Mono displays keep the existing message. Docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:51:24 +02:00
Marcus Kida
91043a006a Release 0.10.1
Remove the muted 'M' status-bar icon that overlapped the GPS text; the
flashing red LED indicates mute.

Bump version to 0.10.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:28:09 +02:00
Marcus Kida
c770693848 Fix: remove muted 'M' header icon that overlapped GPS text
With GPS enabled, the bold 'M' mute indicator in the status bar collided
with the GPS text. Drop the header icon; the flashing red LED already
indicates the muted state. Docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:27:43 +02:00
Marcus Kida
d774604d74 Docs: document contact picker, combined Contacts list, build memory summary
- README: SMS bullet mentions picking a recipient from contacts.
- MANUAL: contact picker in the compose flow, a Contacts-list section (all
  types + '#' to cycle the filter), and a current version example.
- TOOLS: note the per-region memory-usage summary printed after each build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:12:42 +02:00
14 changed files with 177 additions and 36 deletions

View file

@ -9,6 +9,18 @@ single running list until the first versioned release.
_Nothing yet._ _Nothing yet._
## 0.11.0
- **CRT-style shut-off animation** — when powering off, the current screen
collapses towards the middle (squeezed to a line, then a dot) like an old CRT
switching off, replacing the "Power Off" message. (Colour displays only.)
## 0.10.1
- **Fix:** the muted **"M"** indicator in the status bar overlapped the GPS text
when GPS was enabled. The header icon is removed; the muted state is shown by
the flashing red LED alone.
## 0.10.0 ## 0.10.0
- **SMS recipient from contacts** — when composing a message, press **GREEN** in - **SMS recipient from contacts** — when composing a message, press **GREEN** in

View file

@ -59,6 +59,11 @@ Optionally dims the backlight after inactivity to **100 / 50 / 30 / 15 %** of th
configured brightness (only in **Auto** backlight mode). Any activity restores full configured brightness (only in **Auto** backlight mode). Any activity restores full
brightness. Set it in **Menu → Options → Display Options → Screen Dim**. brightness. Set it in **Menu → Options → Display Options → Screen Dim**.
### Shut-off animation — 🟢 [FreeTRX]
Powering off collapses whatever is on screen towards the middle — squeezed to a
line, then a dot — like an old CRT switching off, in place of the "Power Off"
message. (Once the animation starts, the power-off is committed.)
### Theme presets — 🟢 [FreeTRX] ### Theme presets — 🟢 [FreeTRX]
Pick a colour theme on the radio at **Menu → Options → Display Options → "Theme"** Pick a colour theme on the radio at **Menu → Options → Display Options → "Theme"**
(◀/▶ to change; the screen updates live): (◀/▶ to change; the screen updates live):
@ -80,8 +85,7 @@ Editing colours in the CPS only affects **Custom**; the built-in presets are
fixed. Every element (S-meter, frequencies, menus) follows the selected theme. fixed. Every element (S-meter, frequencies, menus) follows the selected theme.
### Mute indicator — 🟢 [FreeTRX] ### Mute indicator — 🟢 [FreeTRX]
While muted, a bold **"M"** appears in the status bar (just left of the battery) While muted, the **red LED flashes** (0.5 s every 2 s). See *Mute* below.
and the **red LED flashes** (0.5 s every 2 s). See *Mute* below.
--- ---
@ -114,8 +118,21 @@ A real-time signal-strength-vs-frequency graph centred on the VFO frequency.
- The scope is **VFO-only** (not available in Channel mode). - The scope is **VFO-only** (not available in Channel mode).
### Scanning — ⚪ [OpenGD77] ### Scanning — ⚪ [OpenGD77]
Channel and VFO scanning with hold/pause behaviour, per the upstream OpenGD77 **There are no scan lists.** Unlike the vendor CPS, OpenGD77 scans **the channels
controls. of the currently selected zone** — so a zone *is* the scan list. To scan a
particular set of channels, put them in a zone and select it. The **All Channels**
virtual zone scans every channel in the codeplug.
- **Start/stop:** long-press **UP** on the Channel or VFO screen (**RED** stops).
- **Exclude a channel:** **Menu → Channel details** (or **SK2 + green** on the
Channel screen) → **Zone Skip** (skipped when scanning a normal zone) and
**All Skip** (skipped when scanning *All Channels*).
- **Settings:** **Menu → Options → Radio options****Scan delay**,
**Scan dwell**, **Scan mode** (Hold / Pause / Stop) and **Scan On Boot**.
Note: in the codeplug these two skip flags reuse the old "Auto Scan" and "Lone
Worker" bits, so a vendor CPS may label them differently — set them on the radio
to avoid confusion.
### Quick menu — ⚪ [OpenGD77] (with 🟢 [FreeTRX] additions) ### Quick menu — ⚪ [OpenGD77] (with 🟢 [FreeTRX] additions)
Press the **top / orange** button to open the quick menu for the current screen Press the **top / orange** button to open the quick menu for the current screen
@ -159,6 +176,11 @@ Talkgroups/contacts, timeslots, colour codes, RX groups, and hotspot mode, all p
upstream OpenGD77. The AMBE voice codec is merged in at flash time (see upstream OpenGD77. The AMBE voice codec is merged in at flash time (see
[`TOOLS.md`](TOOLS.md)). [`TOOLS.md`](TOOLS.md)).
### Contacts list — 🟢 [FreeTRX]
**Menu → Contacts** lists all contact types together under the title "Contacts"
(instead of defaulting to the group filter). Press **`#`** to cycle the filter:
all → groups → private → all-call → all.
### DMR reliability fix — 🟢 [FreeTRX] ### DMR reliability fix — 🟢 [FreeTRX]
FreeTRX resolves a hard-fault (freeze needing a battery pull) on the DMR path that FreeTRX resolves a hard-fault (freeze needing a battery pull) on the DMR path that
affected recent self-built OpenGD77 revisions — the codec is called through C affected recent self-built OpenGD77 revisions — the codec is called through C
@ -201,9 +223,10 @@ second or two a message is on air; hotspot mode is unaffected.
## About & versioning — 🟢 [FreeTRX] ## About & versioning — 🟢 [FreeTRX]
**Menu → Firmware info** shows the **FreeTRX version** (semantic versioning, e.g. **Menu → Firmware info** shows the **FreeTRX version** (semantic versioning,
`FreeTRX v0.4.0`) alongside the git commit ID. Pre-1.0 builds are published as `FreeTRX vMAJOR.MINOR.PATCH`) alongside the git commit ID. Pre-1.0 builds are
pre-releases. See [`CHANGELOG.md`](CHANGELOG.md) for the full history. published as pre-releases. See [`CHANGELOG.md`](CHANGELOG.md) for the full
history.
--- ---

View file

@ -5,9 +5,9 @@
#define _FREETRX_VERSION_H_ #define _FREETRX_VERSION_H_
#define FREETRX_VERSION_MAJOR 0 #define FREETRX_VERSION_MAJOR 0
#define FREETRX_VERSION_MINOR 10 #define FREETRX_VERSION_MINOR 11
#define FREETRX_VERSION_PATCH 0 #define FREETRX_VERSION_PATCH 0
#define FREETRX_VERSION "0.10.0" #define FREETRX_VERSION "0.11.0"
#endif // _FREETRX_VERSION_H_ #endif // _FREETRX_VERSION_H_

View file

@ -267,6 +267,9 @@ bool uiNotificationHasTimedOut(void);
bool uiNotificationIsVisible(void); bool uiNotificationIsVisible(void);
void uiNotificationHide(bool immediateRender); void uiNotificationHide(bool immediateRender);
uiNotificationID_t uiNotificationGetId(void); uiNotificationID_t uiNotificationGetId(void);
#if defined(HAS_COLOURS)
uint16_t *uiNotificationGetOffscreenBuffer(void); // full-screen scratch (borrowed by the shutdown animation)
#endif
#if defined(HAS_GPS) #if defined(HAS_GPS)

View file

@ -351,6 +351,15 @@ uiNotificationID_t uiNotificationGetId(void)
return notificationData.id; return notificationData.id;
} }
#if defined(HAS_COLOURS)
// Expose the full-screen offscreen buffer so the shutdown animation can borrow
// it as scratch (no notification is active while powering off).
uint16_t *uiNotificationGetOffscreenBuffer(void)
{
return screenNotificationBufData;
}
#endif
static void displayMessage(void) static void displayMessage(void)
{ {
char msg[SCREEN_LINE_BUFFER_SIZE]; char msg[SCREEN_LINE_BUFFER_SIZE];

View file

@ -25,18 +25,25 @@
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
#include <string.h>
#include "user_interface/uiGlobals.h" #include "user_interface/uiGlobals.h"
#include "user_interface/menuSystem.h" #include "user_interface/menuSystem.h"
#include "user_interface/uiLocalisation.h" #include "user_interface/uiLocalisation.h"
#include "functions/ticks.h"
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) #if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
#include "interfaces/batteryAndPowerManagement.h" #include "interfaces/batteryAndPowerManagement.h"
#include "hardware/radioHardwareInterface.h" #include "hardware/radioHardwareInterface.h"
#endif #endif
const uint32_t POWEROFF_DURATION_MILLISECONDS = 500;
#if defined(HAS_COLOURS)
static void crtShutdownAnimation(void);
#else
static uint32_t initialEventTime;
static void updateScreen(void); static void updateScreen(void);
static void handleEvent(uiEvent_t *ev); static void handleEvent(uiEvent_t *ev);
static uint32_t initialEventTime; #endif
const uint32_t POWEROFF_DURATION_MILLISECONDS = 500;
menuStatus_t uiPowerOff(uiEvent_t *ev, bool isFirstRun) menuStatus_t uiPowerOff(uiEvent_t *ev, bool isFirstRun)
{ {
@ -49,23 +56,103 @@ menuStatus_t uiPowerOff(uiEvent_t *ev, bool isFirstRun)
} }
#endif #endif
#if defined(HAS_COLOURS)
// Old-CRT-style collapse of whatever is currently on screen; replaces
// the "Power Off" / 73 message, then shuts down.
crtShutdownAnimation();
powerOffFinalStage(false, false);
#else
updateScreen(); updateScreen();
initialEventTime = ev->time; initialEventTime = ev->time;
#endif
} }
else else
{ {
#if !defined(HAS_COLOURS)
handleEvent(ev); handleEvent(ev);
#endif
} }
return MENU_STATUS_SUCCESS; return MENU_STATUS_SUCCESS;
} }
#if defined(HAS_COLOURS)
static void crtShutdownAnimation(void)
{
const int W = DISPLAY_SIZE_X;
const int H = DISPLAY_SIZE_Y;
const int cx = W / 2;
const int cy = H / 2;
const int VSTEPS = 12;
const int HSTEPS = 12;
const uint16_t white = 0xFFFF;
displayRestorePrimaryScreenBuffer();
uint16_t *fb = displayGetPrimaryScreenBuffer();
uint16_t *snap = uiNotificationGetOffscreenBuffer();
// snapshot the screen as it is right now
memcpy(snap, fb, W * H * sizeof(uint16_t));
// Phase 1: squeeze the whole image vertically into a thin central band.
for (int step = 1; step <= VSTEPS; step++)
{
int bandH = H - ((H - 2) * step) / VSTEPS; // 128 -> 2
int top = cy - (bandH / 2);
memset(fb, 0, W * H * sizeof(uint16_t));
for (int dy = 0; dy < bandH; dy++)
{
int srcY = (dy * H) / bandH;
if (srcY >= H)
{
srcY = H - 1;
}
memcpy(&fb[(top + dy) * W], &snap[srcY * W], W * sizeof(uint16_t));
}
displayRender();
vTaskDelay((15 / portTICK_PERIOD_MS));
}
// Phase 2: the band becomes a bright line that collapses horizontally.
for (int step = 1; step <= HSTEPS; step++)
{
int lineW = W - ((W - 2) * step) / HSTEPS; // 160 -> 2
int left = cx - (lineW / 2);
memset(fb, 0, W * H * sizeof(uint16_t));
for (int dx = 0; dx < lineW; dx++)
{
fb[((cy - 1) * W) + (left + dx)] = white;
fb[(cy * W) + (left + dx)] = white;
}
displayRender();
vTaskDelay((15 / portTICK_PERIOD_MS));
}
// Phase 3: a brief central dot, then black.
memset(fb, 0, W * H * sizeof(uint16_t));
fb[((cy - 1) * W) + (cx - 1)] = white;
fb[((cy - 1) * W) + cx] = white;
fb[(cy * W) + (cx - 1)] = white;
fb[(cy * W) + cx] = white;
displayRender();
vTaskDelay((70 / portTICK_PERIOD_MS));
memset(fb, 0, W * H * sizeof(uint16_t));
displayRender();
}
#endif
#if !defined(HAS_COLOURS)
static void updateScreen(void) static void updateScreen(void)
{ {
#if defined(HAS_COLOURS)
bool dblHeight = settingsIsOptionBitSet(BIT_UI_USES_DOUBLE_HEIGHT);
#else
const bool dblHeight = false; const bool dblHeight = false;
#endif
uint16_t fontHeight = (FONT_SIZE_3_HEIGHT * (dblHeight ? 2 : 1)); uint16_t fontHeight = (FONT_SIZE_3_HEIGHT * (dblHeight ? 2 : 1));
displayClearBuf(); displayClearBuf();
@ -104,3 +191,4 @@ static void handleEvent(uiEvent_t *ev)
} }
} }
#endif

View file

@ -1853,12 +1853,12 @@ void uiUtilityRenderQSOData(void)
displayThemeResetToDefault(); displayThemeResetToDefault();
} }
// Toggle the audio mute (speaker) state. The header shows a mute icon and the LED // Toggle the audio mute (speaker) state. The flashing red LED (see the main
// flashes amber (see the main loop) while muted. // loop) indicates the muted state.
void toggleAudioMute(void) void toggleAudioMute(void)
{ {
audioAmpMute(!audioAmpIsMuted()); audioAmpMute(!audioAmpIsMuted());
headerRowIsDirty = true; // refresh the header so the mute icon appears/clears headerRowIsDirty = true;
} }
void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning, bool forceBatteryDisplay) void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning, bool forceBatteryDisplay)
@ -1878,8 +1878,6 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
static uint32_t blinkTime = 0; static uint32_t blinkTime = 0;
int radioMode = trxGetMode(); int radioMode = trxGetMode();
uint8_t powerLevel = trxGetPowerLevel(); uint8_t powerLevel = trxGetPowerLevel();
// Muted either explicitly (SK2) or by the volume knob turned fully down (-99).
bool audioIsMuted = (audioAmpIsMuted() || (lastVolume == -99));
bool isPerChannelPower = (currentChannelData->libreDMR_Power != 0x00); bool isPerChannelPower = (currentChannelData->libreDMR_Power != 0x00);
bool scanIsActive = (uiDataGlobal.Scan.active || uiDataGlobal.Scan.toneActive); bool scanIsActive = (uiDataGlobal.Scan.active || uiDataGlobal.Scan.toneActive);
bool batteryIsLow = batteryIsLowWarning(); bool batteryIsLow = batteryIsLowWarning();
@ -2000,7 +1998,7 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
displayPrintCore(MODE_TEXT_X_OFFSET, DISPLAY_Y_POS_HEADER, buffer, displayPrintCore(MODE_TEXT_X_OFFSET, DISPLAY_Y_POS_HEADER, buffer,
(((nonVolatileSettings.hotspotType != HOTSPOT_TYPE_OFF) && (uiDataGlobal.dmrDisabled == false)) ? FONT_SIZE_1_BOLD : FONT_SIZE_1), TEXT_ALIGN_LEFT, isInverted); (((nonVolatileSettings.hotspotType != HOTSPOT_TYPE_OFF) && (uiDataGlobal.dmrDisabled == false)) ? FONT_SIZE_1_BOLD : FONT_SIZE_1), TEXT_ALIGN_LEFT, isInverted);
// (mute is shown by the header "M" indicator, not a strike-through) // (mute is indicated by the flashing red LED, not a strike-through)
} }
if ((aprsIsTransmittingOnSameQRG == false) && (monitorModeData.isEnabled == false) && (isVFOSweepScanning == false) && (isVFODualWatchScanning == false) && if ((aprsIsTransmittingOnSameQRG == false) && (monitorModeData.isEnabled == false) && (isVFOSweepScanning == false) && (isVFODualWatchScanning == false) &&
@ -2068,7 +2066,7 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
displayPrintCore(MODE_TEXT_X_OFFSET, DISPLAY_Y_POS_HEADER, (isVFODualWatchScanning ? "[DW]" : "DMR"), ((nonVolatileSettings.hotspotType != HOTSPOT_TYPE_OFF) ? FONT_SIZE_1_BOLD : FONT_SIZE_1), TEXT_ALIGN_LEFT, isInverted); displayPrintCore(MODE_TEXT_X_OFFSET, DISPLAY_Y_POS_HEADER, (isVFODualWatchScanning ? "[DW]" : "DMR"), ((nonVolatileSettings.hotspotType != HOTSPOT_TYPE_OFF) ? FONT_SIZE_1_BOLD : FONT_SIZE_1), TEXT_ALIGN_LEFT, isInverted);
// (mute is shown by the header "M" indicator, not a strike-through) // (mute is indicated by the flashing red LED, not a strike-through)
} }
if (isVFODualWatchScanning == false) if (isVFODualWatchScanning == false)
@ -2180,13 +2178,8 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
bool displayTime = (settingsIsOptionBitSet(BIT_DISPLAY_TIME_IN_HEADER) && (forceBatteryDisplay == false) && (batteryIsLow == false)); bool displayTime = (settingsIsOptionBitSet(BIT_DISPLAY_TIME_IN_HEADER) && (forceBatteryDisplay == false) && (batteryIsLow == false));
#endif #endif
#endif #endif
// Mute indicator: bold "M" just left of the battery indicator. // Mute state is indicated by the flashing red LED only; no header icon
if (audioIsMuted) // (it collided with the GPS text in the status bar).
{
displayThemeApply(THEME_ITEM_FG_HEADER_TEXT, THEME_ITEM_BG_HEADER_TEXT);
displayPrintCore((DISPLAY_SIZE_X - 40), DISPLAY_Y_POS_HEADER, "M", FONT_SIZE_1_BOLD, TEXT_ALIGN_LEFT, false);
displayThemeResetToDefault();
}
// Display battery percentage/voltage // Display battery percentage/voltage
bool apoEnabled = (nonVolatileSettings.apo > 0); bool apoEnabled = (nonVolatileSettings.apo > 0);

View file

@ -15,9 +15,15 @@ tagged 🟢 **[FreeTRX]** (new/changed here) or ⚪ **[OpenGD77]** (inherited).
## Screenshots ## Screenshots
| Segmented S-meter & DMR call | 7-segment frequency display | | Segmented S-meter & DMR call | 7-segment frequency display | Main menu |
| --- | --- | --- |
| ![DMR channel showing the talkgroup, repeater name and the block S-meter](docs/images/screenshot-dmr.png) | ![VFO with the 7-segment frequency font](docs/images/screenshot-vfo.png) | ![Main menu with the Messages entry highlighted](docs/images/screenshot-menu.png) |
| DMR text messages | Reading a received message |
| --- | --- | | --- | --- |
| ![DMR call with the block S-meter](docs/images/screenshot-dmr.png) | ![VFO with the 7-segment frequency font](docs/images/screenshot-vfo.png) | | ![Messages menu: quick text, options, new message, inbox and sent](docs/images/screenshot-messages.png) | ![A received weather-report message from a BrandMeister service](docs/images/screenshot-message-view.png) |
*Screens shown with the **Neon 80s** theme.*
## What's different from OpenGD77 ## What's different from OpenGD77
@ -25,8 +31,8 @@ FreeTRX keeps OpenGD77's core (DMR/FM, hotspot, CPS compatibility) and adds:
- **Audio mute — new SK2 short-tap.** A short tap of **SK2** on the VFO/Channel - **Audio mute — new SK2 short-tap.** A short tap of **SK2** on the VFO/Channel
screen toggles the speaker mute on and off. Turning the **volume knob fully screen toggles the speaker mute on and off. Turning the **volume knob fully
down** counts as muted too. While muted, a bold **"M"** appears in the status down** counts as muted too. While muted, the **red LED flashes** (0.5 s every
bar (left of the battery) and the **red LED flashes** (0.5 s every 2 s). SK2's 2 s). SK2's
existing roles are unchanged: held as a modifier, extra-long-press for existing roles are unchanged: held as a modifier, extra-long-press for
monitor/squelch-off, and — while a DMR QSO is on screen — a tap still sets the monitor/squelch-off, and — while a DMR QSO is on screen — a tap still sets the
TX talkgroup to the incoming one. TX talkgroup to the incoming one.
@ -36,15 +42,18 @@ FreeTRX keeps OpenGD77's core (DMR/FM, hotspot, CPS compatibility) and adds:
- **Screen dimming** — optionally dim the backlight to 100 / 50 / 30 / 15 % after - **Screen dimming** — optionally dim the backlight to 100 / 50 / 30 / 15 % after
inactivity (Auto backlight mode); new setting in Display Options. inactivity (Auto backlight mode); new setting in Display Options.
- **DMR text messages (SMS)** — send and receive text messages on the radio, - **DMR text messages (SMS)** — send and receive text messages on the radio,
compatible with BrandMeister, hotspots and OEM radios. Compose on the keypad, compatible with BrandMeister, hotspots and OEM radios. Compose on the keypad
inbox/sent boxes, quick-text templates, delivery confirmation and an (or pick the recipient from your contacts), inbox/sent boxes, quick-text
incoming-message popup (see the manual for details). templates, delivery confirmation and an incoming-message popup (see the manual
for details).
- **Theme presets** — pick a colour theme on the radio: **Custom** (CPS-editable, - **Theme presets** — pick a colour theme on the radio: **Custom** (CPS-editable,
default), **BASIC 80s** (Commodore 64), **Terminal** (green-on-black), default), **BASIC 80s** (Commodore 64), **Terminal** (green-on-black),
**Vertexic** (amber/orange), **Red**, **Neon 80s** (multi-colour synthwave), **Vertexic** (amber/orange), **Red**, **Neon 80s** (multi-colour synthwave),
or **Pride** (rainbow background). CPS edits only affect Custom. or **Pride** (rainbow background). CPS edits only affect Custom.
- **On-radio channel management** — create and delete channels directly from the - **On-radio channel management** — create and delete channels directly from the
Channel quick menu, no PC/CPS required (see [Managing channels](#managing-channels-on-the-radio)). Channel quick menu, no PC/CPS required (see [Managing channels](#managing-channels-on-the-radio)).
- **CRT-style shut-off animation** — the current screen collapses to a line and
then a dot, like an old CRT switching off (replaces the "Power Off" message).
- **DMR reliability fix** — resolves a hard-fault (freeze needing a battery pull) - **DMR reliability fix** — resolves a hard-fault (freeze needing a battery pull)
on the DMR path that affected recent self-built OpenGD77 revisions. on the DMR path that affected recent self-built OpenGD77 revisions.

View file

@ -34,6 +34,10 @@ Output: `MDUV380_firmware/build/Open<platform>.bin` (the flashable, FM-only imag
codec is merged in at flash time — see below). The build is always clean (`rm -rf build`), codec is merged in at flash time — see below). The build is always clean (`rm -rf build`),
so building one platform overwrites the other's output. so building one platform overwrites the other's output.
Each build finishes with a **memory-usage summary** — used, size and free bytes for the
FLASH (code), RAM and CCMRAM regions — so the remaining headroom for new features is
visible at a glance.
--- ---
## 3. Flashing ## 3. Flashing

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB