UA1ZBE Custom Firmware v1.0

Features:
- VFO mode with direct frequency input
- POCSAG decoder (512/1200 baud) with BCH(31,21) correction
- Full-screen Spectrum analyzer
- FM Radio receiver
- RSSI signal indicator overlay
- Custom boot splash (UA1ZBE / POCSAG pager / build date)

Architecture:
- app/mode.c — mode dispatcher (VFO/POCSAG/Spectrum/FM)
- app/boot_splash.c — 2-second boot splash
- app/pocsag/ — POCSAG decoder + BCH correction
- app/display_rssi.c — RSSI indicator
- main.c — entry point with custom init
- syscalls.c — bare-metal _sbrk stub

Build: arm-none-eabi-gcc -Os -flto -Wall -Werror -Wextra
Size: 57.9KB Flash / 3.6KB RAM

Controls:
- 0-9: Direct frequency input (VFO)
- SK2: POCSAG mode
- SK1: Spectrum analyzer
- 0: FM Radio
- EXIT: Return to VFO
- F/*: Toggle 512/1200 baud (in POCSAG)
This commit is contained in:
2026-04-12 00:44:05 +03:00
parent 5c1229659b
commit a1206d6fd4
3435 changed files with 593221 additions and 2 deletions

24
utils/clean.bat Normal file
View File

@@ -0,0 +1,24 @@
@echo off
del /S /Q *.~*
del /S /Q *.map
del /S /Q *.tds
del /S /Q *.obj
del /S /Q *.db
del /S /Q *.ilc
del /S /Q *.ild
del /S /Q *.ilf
del /S /Q *.ils
del /S /Q *.dcu
del /S /Q *.dsk
rd /S /Q Debug
rd /S /Q Release
rd /S /Q ipch
del /S gain_table.c
del /S uv-k5_small.c
del /S uv-k5_small_bold.c
::pause
@echo on

473
utils/main.cpp Normal file
View File

@@ -0,0 +1,473 @@
/* Copyright 2023 OneOfEleven
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <vector>
// ************************************************************************
// create a front end gain table for the firmware
// <9:8> = LNA Gain Short
// 3 = 0dB < original value
// 2 = -24dB // was -11
// 1 = -30dB // was -16
// 0 = -33dB // was -19
//
// <7:5> = LNA Gain
// 7 = 0dB
// 6 = -2dB
// 5 = -4dB
// 4 = -6dB
// 3 = -9dB
// 2 = -14dB < original value
// 1 = -19dB
// 0 = -24dB
//
// <4:3> = MIXER Gain
// 3 = 0dB < original value
// 2 = -3dB
// 1 = -6dB
// 0 = -8dB
//
// <2:0> = PGA Gain
// 7 = 0dB
// 6 = -3dB < original value
// 5 = -6dB
// 4 = -9dB
// 3 = -15dB
// 2 = -21dB
// 1 = -27dB
// 0 = -33dB
typedef struct
{
uint8_t lna_short;
uint8_t lna;
uint8_t mixer;
uint8_t pga;
int16_t lna_short_dB;
int16_t lna_dB;
int16_t mixer_dB;
int16_t pga_dB;
int16_t sum_dB;
} t_gain_table;
void create_gain_table(const char *filename)
{
std::vector <t_gain_table> gain_table;
if (filename == NULL)
return;
// front end register dB values
// const int16_t lna_short_dB[4] = { (-19), (-16), (-11), (0)}; // was
const int16_t lna_short_dB[4] = { (-33), (-30), (-24), (0)}; // corrected
const int16_t lna_dB[8] = { (-24), (-19), (-14), (-9), (-6), (-4), (-2), (0)};
const int16_t mixer_dB[4] = { (-8), (-6), (-3), (0)};
const int16_t pga_dB[8] = { (-33), (-27), (-21), (-15), (-9), (-6), (-3), (0)};
const uint8_t orig_lna_short = 3;
const uint8_t orig_lna = 2;
const uint8_t orig_mixer = 3;
const uint8_t orig_pga = 6;
const int16_t orig_gain_dB =
lna_short_dB[orig_lna_short] +
lna_dB[orig_lna] +
mixer_dB[orig_mixer] +
pga_dB[orig_pga];
#if 1
// full table
const uint8_t lna_short_min = 0; // 0
const uint8_t lna_min = 0; // 0
const uint8_t mixer_min = 0; // 0
const uint8_t pga_min = 0; // 0
const uint8_t lna_short_max = 3; // 3
const uint8_t lna_max = 7; // 5
const uint8_t mixer_max = 3; // 3
const uint8_t pga_max = 7; // 7
#else
// just one register changes
const uint8_t lna_short_min = 0;
const uint8_t lna_min = 2;
const uint8_t mixer_min = 3;
const uint8_t pga_min = 6;
const uint8_t lna_short_max = 3;
const uint8_t lna_max = 2;
const uint8_t mixer_max = 3;
const uint8_t pga_max = 6;
#endif
uint8_t lna_short = lna_short_min;
uint8_t lna = lna_min;
uint8_t mixer = mixer_min;
uint8_t pga = pga_min;
unsigned int original_index = 0;
while (true)
{
t_gain_table entry;
entry.lna_short = lna_short;
entry.lna = lna;
entry.mixer = mixer;
entry.pga = pga;
entry.lna_short_dB = lna_short_dB[lna_short];
entry.lna_dB = lna_dB[lna];
entry.mixer_dB = mixer_dB[mixer];
entry.pga_dB = pga_dB[pga];
entry.sum_dB = lna_short_dB[lna_short] + lna_dB[lna] + mixer_dB[mixer] + pga_dB[pga];
if (entry.sum_dB != orig_gain_dB)
gain_table.push_back(entry);
else
if (lna_short == orig_lna_short && lna == orig_lna && mixer == orig_mixer && pga == orig_pga)
gain_table.push_back(entry);
if (++pga <= pga_max)
continue;
pga = pga_min;
if (++mixer <= mixer_max)
continue;
mixer = mixer_min;
if (++lna <= lna_max)
continue;
lna = lna_min;
if (++lna_short <= lna_short_max)
continue;
// lna_short = lna_short_min;
break;
}
// sort the table according top the sum dB
for (unsigned int i = 0; i < (gain_table.size() - 1); i++)
{
t_gain_table entry1 = gain_table[i];
for (unsigned int k = i + 1; k < gain_table.size(); k++)
{
t_gain_table entry2 = gain_table[k];
if (entry2.sum_dB < entry1.sum_dB)
{ // swap
const t_gain_table entry = entry1;
entry1 = entry2;
entry2 = entry;
gain_table[i] = entry1;
gain_table[k] = entry2;
}
}
}
{ // remove sum_dB duplicates
unsigned int i = 0;
while (i < gain_table.size())
{
const t_gain_table entry1 = gain_table[i++];
if (entry1.lna_short == orig_lna_short &&
entry1.lna == orig_lna &&
entry1.mixer == orig_mixer &&
entry1.pga == orig_pga)
continue; // leave the original inplace
while (i < gain_table.size())
{
const t_gain_table entry2 = gain_table[i];
if (entry2.lna_short == orig_lna_short &&
entry2.lna == orig_lna &&
entry2.mixer == orig_mixer &&
entry2.pga == orig_pga)
break; // leave the original inplace
if (entry2.sum_dB != entry1.sum_dB)
break;
gain_table.erase(gain_table.begin() + i, gain_table.begin() + i + 1);
}
}
}
// find the index for the original Quansheng register settings
for (int i = (int)gain_table.size() - 1; i >= 0; i--)
{
const t_gain_table entry = gain_table[i];
if (entry.sum_dB != orig_gain_dB)
continue;
if (entry.lna_short != orig_lna_short ||
entry.lna != orig_lna ||
entry.mixer != orig_mixer ||
entry.pga != orig_pga)
continue;
original_index = i;
break;
}
// ***************************
// save the table to a file
/*
typedef struct
{
#if 1
// bitfields take up less flash bytes
uint8_t lna_short:2; // 0 ~ 3
uint8_t lna:3; // 0 ~ 7
uint8_t mixer:2; // 0 ~ 3
uint8_t pga:3; // 0 ~ 7
#else
uint8_t lna_short; // 0 ~ 3
uint8_t lna; // 0 ~ 7
uint8_t mixer; // 0 ~ 3
uint8_t pga; // 0 ~ 7
#endif
} t_am_fix_gain_table;
//} __attribute__((packed)) t_am_fix_gain_table;
*/
FILE *file = fopen(filename, "w");
if (file == NULL)
return;
fprintf(file, "\n");
fprintf(file, "\tstatic const t_am_fix_gain_table am_fix_gain_table[] =\n");
fprintf(file, "\t{\n");
#if 0
fprintf(file, "\t\t{.lna_short = 3, .lna = 2, .mixer = 3, .pga = 6}, // 0 0dB -14dB 0dB -3dB .. -17dB original\n\n");
for (unsigned int i = 0; i < gain_table.size(); i++)
{
char s[1024];
const t_gain_table entry = gain_table[i];
sprintf(s, "\t\t{%u, %u, %u, %u}, // %3u .. %3ddB %3ddB %2ddB %3ddB .. %3ddB",
entry.lna_short,
entry.lna,
entry.mixer,
entry.pga,
1 + i,
entry.lna_short_dB,
entry.lna_dB,
entry.mixer_dB,
entry.pga_dB,
entry.sum_dB);
if (i == original_index)
strcat(s, " original");
fprintf(file, "%s\n", s);
}
#else
{
//BK4819_WriteRegister(BK4819_REG_13, ((uint16_t)gains.lna_short << 8) | ((uint16_t)gains.lna << 5) | ((uint16_t)gains.mixer << 3) | ((uint16_t)gains.pga << 0));
uint16_t reg_val;
int16_t sum_dB;
reg_val = ((uint16_t)orig_lna_short << 8) | ((uint16_t)orig_lna << 5) | ((uint16_t)orig_mixer << 3) | ((uint16_t)orig_pga << 0);
sum_dB = lna_short_dB[orig_lna_short] + lna_dB[orig_lna] + mixer_dB[orig_mixer] + pga_dB[orig_pga];
fprintf(file, "\t\t{0x%04X, %-3d}, // 0 .. %u %u %u %u .. 0dB -14dB 0dB -3dB .. -17dB original\n\n",
reg_val,
sum_dB,
orig_lna_short,
orig_lna,
orig_mixer,
orig_pga);
for (unsigned int i = 0; i < gain_table.size(); i++)
{
char s[1024];
const t_gain_table entry = gain_table[i];
reg_val = ((uint16_t)entry.lna_short << 8) | ((uint16_t)entry.lna << 5) | ((uint16_t)entry.mixer << 3) | ((uint16_t)entry.pga << 0);
sum_dB = lna_short_dB[entry.lna_short] + lna_dB[entry.lna] + mixer_dB[entry.mixer] + pga_dB[entry.pga];
sprintf(s, "\t\t{0x%04X, %-3d}, // %3u .. %u %u %u %u .. %3ddB %3ddB %2ddB %3ddB .. %3ddB",
reg_val,
sum_dB,
1 + i,
entry.lna_short,
entry.lna,
entry.mixer,
entry.pga,
entry.lna_short_dB,
entry.lna_dB,
entry.mixer_dB,
entry.pga_dB,
entry.sum_dB);
if (i == original_index)
strcat(s, " original");
fprintf(file, "%s\n", s);
}
}
#endif
fprintf(file, "\t};\n\n");
fprintf(file, "\tstatic const unsigned int original_index = %u;\n", 1 + original_index);
fclose(file);
}
// ************************************************************************
// "rotate_font()" has nothing to do with this program at all, I just needed
// to write a bit of code to rotate some fonts I've drawn
void rotate_font(const char *filename1, const char *filename2)
{
std::vector <uint8_t> data;
if (filename1 == NULL || filename2 == NULL)
return;
// ****************************
// load the file
FILE *file = fopen(filename1, "rb");
if (file == NULL)
return;
if (fseek(file, 0, SEEK_END) != 0)
{
fclose(file);
return;
}
const size_t file_size = ftell(file);
if (file_size <= 0)
{
fclose(file);
return;
}
if (fseek(file, 0, SEEK_SET) != 0)
{
fclose(file);
return;
}
data.resize(file_size);
const size_t bytes_loaded = fread(&data[0], 1, file_size, file);
fclose(file);
if (bytes_loaded != file_size)
return;
// ***************************
// rotate the font 90-deg clockwise
for (unsigned int i = 0; i <= (data.size() - 8); i += 8)
{
uint8_t c1[8];
uint8_t c2[8];
memcpy(c1, &data[i], 8);
memset(c2, 0, 8);
for (unsigned int k = 0; k < 8; k++)
{
uint8_t b = c1[k];
for (unsigned int m = 0; m < 8; m++)
{
if (b & 0x80)
c2[m] |= 1u << k;
b <<= 1;
}
}
memcpy(&data[i], c2, 8);
}
// ***************************
// save the file
file = fopen(filename2, "wt");
if (file == NULL)
return;
fprintf(file, "const uint8_t gFontSmall[95][7] =\n");
fprintf(file, "{\n");
for (unsigned int i = 0; i < data.size(); )
{
char s[1024];
memset(s, 0, sizeof(s));
// for (unsigned int k = 0; k < 8 && i < data.size(); k++)
for (unsigned int k = 0; k < 7 && i < data.size(); k++)
{
char s2[16];
sprintf(s2, "0x%02X", data[i++]);
if (k == 0)
strcat(s, "\t{");
// if (k < 7)
if (k < 6)
{
strcat(s, s2);
strcat(s, ", ");
}
else
{
strcat(s, s2);
strcat(s, "},\n");
}
}
i++;
fprintf(file, "%s", s);
}
fprintf(file, "};\n");
fclose(file);
// ***************************
}
#pragma argsused
int main(int argc, char* argv[])
{
create_gain_table("gain_table.c");
rotate_font("uv-k5_small.bin", "uv-k5_small.c");
rotate_font("uv-k5_small_bold.bin", "uv-k5_small_bold.c");
return 0;
}

3
utils/misc.bpf Normal file
View File

@@ -0,0 +1,3 @@
This file is used by the project manager only and should be treated like the project file
main

119
utils/misc.bpr Normal file
View File

@@ -0,0 +1,119 @@
<?xml version='1.0' encoding='utf-8' ?>
<!-- C++Builder XML Project -->
<PROJECT>
<MACROS>
<VERSION value="BCB.06.00"/>
<PROJECT value="misc.exe"/>
<OBJFILES value="main.obj"/>
<RESFILES value="misc.res"/>
<IDLFILES value=""/>
<IDLGENFILES value=""/>
<DEFFILE value=""/>
<RESDEPEN value="$(RESFILES)"/>
<LIBFILES value=""/>
<LIBRARIES value=""/>
<SPARELIBS value=""/>
<PACKAGES value="vcl.bpi rtl.bpi dbrtl.bpi vcldb.bpi vclx.bpi bdertl.bpi dsnap.bpi cds.bpi
bcbsmp.bpi vclie.bpi xmlrtl.bpi inet.bpi inetdbbde.bpi inetdbxpress.bpi
nmfast.bpi webdsnap.bpi bcbie.bpi dclocx.bpi dbexpress.bpi dbxcds.bpi
indy.bpi bcb2kaxserver.bpi adortl.bpi vcldbx.bpi ibxpress.bpi bdecds.bpi
qrpt.bpi teeui.bpi teedb.bpi tee.bpi dss.bpi teeqr.bpi visualclx.bpi
visualdbclx.bpi dsnapcrba.bpi dsnapcon.bpi inetdb.bpi websnap.bpi
soaprtl.bpi"/>
<PATHCPP value=".;"/>
<PATHPAS value=".;"/>
<PATHRC value=".;"/>
<PATHASM value=".;"/>
<DEBUGLIBPATH value="$(BCB)\lib\debug"/>
<RELEASELIBPATH value="$(BCB)\lib\release"/>
<LINKER value="ilink32"/>
<USERDEFINES value="_DEBUG"/>
<SYSDEFINES value="NO_STRICT;_NO_VCL;_RTLDLL;USEPACKAGES"/>
<MAINSOURCE value="misc.bpf"/>
<INCLUDEPATH value="..\..\uv-k5-firmware\uv-k5-firmware-custom\misc;$(BCB)\include;$(BCB)\include\vcl"/>
<LIBPATH value="&quot;..\..\uv-k5-firmware\uv-k5-firmware-custom\misc&quot;;$(BCB)\lib\obj;$(BCB)\lib"/>
<WARNINGS value="-w-par"/>
<OTHERFILES value=""/>
</MACROS>
<OPTIONS>
<IDLCFLAGS value="-I..\..\uv-k5-firmware\uv-k5-firmware-custom\misc -I$(BCB)\include
-I$(BCB)\include\vcl -src_suffix cpp -D_DEBUG -boa"/>
<CFLAG1 value="-Od -H=$(BCB)\lib\vcl60.csm -Hc -Vx -Ve -X- -r- -a8 -b- -k -y -v -vi- -tWC
-tWM -c"/>
<PFLAGS value="-$YD -$W -$O- -$A8 -v -JPHNE -M"/>
<RFLAGS value=""/>
<AFLAGS value="/mx /w2 /zd"/>
<LFLAGS value="-D&quot;&quot; -ap -Tpe -x -Gn -v"/>
<OTHERFILES value=""/>
</OPTIONS>
<LINKER>
<ALLOBJ value="c0x32.obj $(PACKAGES) $(OBJFILES)"/>
<ALLRES value="$(RESFILES)"/>
<ALLLIB value="$(LIBFILES) $(LIBRARIES) import32.lib cw32mti.lib"/>
<OTHERFILES value=""/>
</LINKER>
<FILELIST>
<FILE FILENAME="misc.res" FORMNAME="" UNITNAME="misc.res" CONTAINERID="ResTool" DESIGNCLASS="" LOCALCOMMAND=""/>
<FILE FILENAME="misc.bpf" FORMNAME="" UNITNAME="misc" CONTAINERID="BPF" DESIGNCLASS="" LOCALCOMMAND=""/>
<FILE FILENAME="main.cpp" FORMNAME="" UNITNAME="main" CONTAINERID="CCompiler" DESIGNCLASS="" LOCALCOMMAND=""/>
</FILELIST>
<BUILDTOOLS>
</BUILDTOOLS>
<IDEOPTIONS>
[Version Info]
IncludeVerInfo=1
AutoIncBuild=0
MajorVer=0
MinorVer=0
Release=1
Build=0
Debug=0
PreRelease=0
Special=0
Private=0
DLL=0
Locale=2057
CodePage=1252
[Version Info Keys]
CompanyName=
FileDescription=
FileVersion=0.0.1.0
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=1.0.0.0
Comments=
[Excluded Packages]
c:\program files (x86)\borland\cbuilder6\Bin\dclite60.bpl=Borland Integrated Translation Environment
[Debugging]
DebugSourceDirs=$(BCB)\source\vcl
[Parameters]
RunParams=
Launcher=
UseLauncher=0
DebugCWD=
HostApplication=
RemoteHost=
RemotePath=
RemoteLauncher=
RemoteCWD=
RemoteDebug=0
[Compiler]
ShowInfoMsgs=0
LinkDebugVcl=0
LinkCGLIB=0
[CORBA]
AddServerUnit=1
AddClientUnit=1
PrecompiledHeaders=1
</IDEOPTIONS>
</PROJECT>

BIN
utils/misc.exe Normal file

Binary file not shown.

BIN
utils/misc.res Normal file

Binary file not shown.

BIN
utils/uv-k5_small.bin Normal file

Binary file not shown.

BIN
utils/uv-k5_small.fon Normal file

Binary file not shown.

BIN
utils/uv-k5_small_bold.bin Normal file

Binary file not shown.

BIN
utils/uv-k5_small_bold.fon Normal file

Binary file not shown.