mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Mute APRS RX speaker by default, toggle it with key 1
This commit is contained in:
1 parent
92f793bb2b
commit
2b33f3ebdd
4 files changed
+27
-25
No files matched your search
@@ -11,7 +11,7 @@ Status:
|
|||||||
| Audio path an app can sample | **Done** by EPIRB 406: RX audio on PA4, ADC channel 4 at 9.6 kHz |
|
| Audio path an app can sample | **Done** by EPIRB 406: RX audio on PA4, ADC channel 4 at 9.6 kHz |
|
||||||
| Integer demodulator, modelled on synthetic audio (`test/model_rx.py`) | **Done** (see below) |
|
| Integer demodulator, modelled on synthetic audio (`test/model_rx.py`) | **Done** (see below) |
|
||||||
| Flipper Zero test transmitter (`test/flipper_aprs.py`) | **Done**, files in `test/flipper/` |
|
| Flipper Zero test transmitter (`test/flipper_aprs.py`) | **Done**, files in `test/flipper/` |
|
||||||
| Radio app (`aprsrx_app.c`) | **v0.1 receives the Flipper frames on the radio**; v0.3 decodes Mic-E (on air: F5RAV via F1PRY-14); v0.4 decodes continuously with 3 slicers (on air: F1PRY-14 via F5KTR-3); v0.5 shows standard positions, the frequency bottom right as Beacon, drops `pp` and the UP/DOWN/5 tuning offset to fit 4 KiB (builds: 3976 B, 120 B free; radio test pending) |
|
| Radio app (`aprsrx_app.c`) | **v0.1 receives the Flipper frames on the radio**; v0.3 decodes Mic-E (on air: F5RAV via F1PRY-14); v0.4 decodes continuously with 3 slicers (on air: F1PRY-14 via F5KTR-3); v0.5 shows standard positions, the frequency bottom right as Beacon, drops `pp` and the UP/DOWN/5 tuning offset to fit 4 KiB (3976 B); main-screen frequency, WAIT capsule (4068 B); v0.6: speaker off, key 1 toggles it, per-slicer counters dropped (not built yet) |
|
||||||
| Bench test with the Flipper on 433.650 MHz | **Done** (2026-09-30, v0.1: `_long` 10/10 in STD; `_badfcs` not shown) |
|
| Bench test with the Flipper on 433.650 MHz | **Done** (2026-09-30, v0.1: `_long` 10/10 in STD; `_badfcs` not shown) |
|
||||||
| Real station: FT3D beacon on 144.800 MHz | **Done** (2026-09-30, v0.1: Mic-E frame `>TXUPX9` received, FCS good, = 48°50.89' N 2°16.25' E) |
|
| Real station: FT3D beacon on 144.800 MHz | **Done** (2026-09-30, v0.1: Mic-E frame `>TXUPX9` received, FCS good, = 48°50.89' N 2°16.25' E) |
|
||||||
| Mic-E decoding (`test/mice.py`: spec encoder vs the app's decoder) | **Done** in the model: 6 cases + the FT3D frame |
|
| Mic-E decoding (`test/mice.py`: spec encoder vs the app's decoder) | **Done** in the model: 6 cases + the FT3D frame |
|
||||||
@@ -21,8 +21,9 @@ Status:
|
|||||||
1. Set the VFO to the APRS frequency, **FM**: 144.800 MHz, or 433.650 MHz for the
|
1. Set the VFO to the APRS frequency, **FM**: 144.800 MHz, or 433.650 MHz for the
|
||||||
Flipper test files.
|
Flipper test files.
|
||||||
2. Launch **APRS RX**.
|
2. Launch **APRS RX**.
|
||||||
3. Lower the volume: the RX audio must stay on (it is what reaches PA4), so the
|
3. The speaker is off (v0.6): the decoder does not need it, only the BK4829 AF
|
||||||
speaker plays the channel.
|
output (tested on the radio: PA4 joins the audio before the amplifier). Key 1
|
||||||
|
turns it on to listen to the channel, as FoxHunt's audio key.
|
||||||
|
|
||||||
Since v0.4 the app decodes **continuously**, as a TNC does: no RSSI trigger,
|
Since v0.4 the app decodes **continuously**, as a TNC does: no RSSI trigger,
|
||||||
the FCS and a UI-frame check (control 0x03, PID 0xF0) sort frames from noise.
|
the FCS and a UI-frame check (control 0x03, PID 0xF0) sort frames from noise.
|
||||||
@@ -38,17 +39,18 @@ The screen is redrawn after a new frame, a key, or every 5 s.
|
|||||||
|
|
||||||
| Screen | Content |
|
| Screen | Content |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Status bar | `APRS RX` title, then a `WAIT` capsule until the first frame |
|
| Status bar | `APRS RX` title, then a `WAIT` capsule until the first frame, and the speaker icon while the speaker is on |
|
||||||
| Line 0 | Source call and frame number, e.g. `F4HWN-7 #3` |
|
| Line 0 | Source call and frame number, e.g. `F4HWN-7 #3` |
|
||||||
| Tiny row y=8 | `>DEST,DIGI*,...` (as many path entries as fit on one row) |
|
| Tiny row y=8 | `>DEST,DIGI*,...` (as many path entries as fit on one row) |
|
||||||
| Tiny rows y=13/19/25 | Info field, 3 × 32 characters (non-ASCII shown as `.`); for Mic-E (most Yaesu/Kenwood beacons, v0.3): position `48 50.89N 002 16.25E`, then speed km/h, course, symbol and message type (`Off Duty`, `En Route`...), then altitude and comment without the device markers; for an uncompressed position (`!` `=` `/` `@`, v0.5): position, then the timestamp if any, the symbol (table + code) and the comment over two rows. Compressed positions stay raw text |
|
| Tiny rows y=13/19/25 | Info field, 3 × 32 characters (non-ASCII shown as `.`); for Mic-E (most Yaesu/Kenwood beacons, v0.3): position `48 50.89N 002 16.25E`, then speed km/h, course, symbol and message type (`Off Duty`, `En Route`...), then altitude and comment without the device markers; for an uncompressed position (`!` `=` `/` `@`, v0.5): position, then the timestamp if any, the symbol (table + code) and the comment over two rows. Compressed positions stay raw text |
|
||||||
| Lines 4-5 | Under a dotted separator (y=30), the frequency drawn as the main screen draws it (big digits up to the kHz, the last two in the small font), centred; same code as APRS TX |
|
| Lines 4-5 | Under a dotted separator (y=30), the frequency drawn as the main screen draws it (big digits up to the kHz, the last two in the small font), centred; same code as APRS TX |
|
||||||
| Bottom row y=49 | `ok 12 -89dBm sl 5/12/7`: frames received, RSSI at the end of the last frame, frames per slicer (space-favouring / neutral / mark-favouring; a frame found by several slicers counts in each, but once in `ok`) |
|
| Bottom row y=49 | `ok 12 -89dBm`: frames received, RSSI at the end of the last frame. (v0.4-v0.5 also showed frames per slicer, `sl a/b/c`: on air the outer slicers found frames too; dropped in v0.6 to fit the speaker key) |
|
||||||
|
|
||||||
Keys (UV-K5 and UV-K1):
|
Keys (UV-K5 and UV-K1):
|
||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| 1 | Speaker on/off (off at launch); FoxHunt's speaker icon in the status bar while on |
|
||||||
| MENU | Clear the last frame and the counters |
|
| MENU | Clear the last frame and the counters |
|
||||||
| EXIT | Quit |
|
| EXIT | Quit |
|
||||||
|
|
||||||
@@ -152,5 +154,4 @@ the standard position display against real frames;
|
|||||||
0.065 LSB/Hz), **measured 734-778** with the Flipper and **916** with a real
|
0.065 LSB/Hz), **measured 734-778** with the Flipper and **916** with a real
|
||||||
station (F1PRY-14): ~20 % of full scale, plenty of headroom, and the per-tone
|
station (F1PRY-14): ~20 % of full scale, plenty of headroom, and the per-tone
|
||||||
AGC makes the absolute level irrelevant.
|
AGC makes the absolute level irrelevant.
|
||||||
- The per-slicer counters show which decision threshold the stations need: if
|
|
||||||
one outer slicer does most of the work, the twist of the path is off-centre.
|
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
* are served every 50 ms only while no slicer is inside a preamble or a
|
* are served every 50 ms only while no slicer is inside a preamble or a
|
||||||
* plausible frame; the redraw follows a new frame, a key, or every 5 s.
|
* plausible frame; the redraw follows a new frame, a key, or every 5 s.
|
||||||
*
|
*
|
||||||
* Keys (UV-K5 and UV-K1): MENU clear · EXIT quit. The speaker plays the
|
* Keys (UV-K5 and UV-K1): 1 speaker on/off (off at launch: the decoder does
|
||||||
* channel: lower the volume.
|
* not need it) · MENU clear · EXIT quit.
|
||||||
* The loader re-runs RADIO_SetupRegisters on exit; the app restores the ADC,
|
* The loader re-runs RADIO_SetupRegisters on exit; the app restores the ADC,
|
||||||
* PA4, the DAC and its clock itself.
|
* PA4, the DAC and its clock itself.
|
||||||
*/
|
*/
|
||||||
@@ -128,11 +128,10 @@ static struct {
|
|||||||
uint32_t tPrev, tCyc; /* SysTick cycle counter */
|
uint32_t tPrev, tCyc; /* SysTick cycle counter */
|
||||||
uint32_t tFrame, tDraw; /* ticks_ms of the last frame, last redraw */
|
uint32_t tFrame, tDraw; /* ticks_ms of the last frame, last redraw */
|
||||||
uint32_t savedSqr3, savedSmpr3, savedModer, savedDac, savedRcc, savedDhr;
|
uint32_t savedSqr3, savedSmpr3, savedModer, savedDac, savedRcc, savedDhr;
|
||||||
uint8_t seq, prevKey, redraw;
|
uint8_t seq, prevKey, redraw, spk; /* spk: speaker amplifier on (key 1) */
|
||||||
bool running;
|
bool running;
|
||||||
int16_t rssi; /* RSSI at the end of the last frame */
|
int16_t rssi; /* RSSI at the end of the last frame */
|
||||||
uint16_t flen, nOk; /* frame length, frames */
|
uint16_t flen, nOk; /* frame length, frames */
|
||||||
uint16_t good[NSL]; /* frames per slicer (duplicates included) */
|
|
||||||
} g;
|
} g;
|
||||||
static char str[34];
|
static char str[34];
|
||||||
|
|
||||||
@@ -164,6 +163,7 @@ static char *puti(char *o,int32_t v){
|
|||||||
static void tiny(uint8_t y,char *end){ *end='\0'; g.A->print_tiny(str,0,y,false,true); }
|
static void tiny(uint8_t y,char *end){ *end='\0'; g.A->print_tiny(str,0,y,false,true); }
|
||||||
|
|
||||||
#define WAIT_CAPS_X 40u /* "WAIT" capsule in the status bar, after the title */
|
#define WAIT_CAPS_X 40u /* "WAIT" capsule in the status bar, after the title */
|
||||||
|
#define SPK_X 62u /* speaker icon (FoxHunt's), after the capsule */
|
||||||
|
|
||||||
/* Tiny rows under the source line: the path (capitals and digits: no
|
/* Tiny rows under the source line: the path (capitals and digits: no
|
||||||
* descenders), then 3 info rows 6 px apart (the 3x5 font is 5 px, 6 with
|
* descenders), then 3 info rows 6 px apart (the 3x5 font is 5 px, 6 with
|
||||||
@@ -334,12 +334,11 @@ static bool slice(sl_t *m,int32_t a,int32_t b){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- a good frame from slicer k: shown unless another slicer just gave it ---- */
|
/* ---- a good frame from a slicer: shown unless another slicer just gave it ---- */
|
||||||
static void accept(sl_t *m,uint8_t k){
|
static void accept(sl_t *m){
|
||||||
const app_api_t *A=g.A;
|
const app_api_t *A=g.A;
|
||||||
uint16_t n=m->n;
|
uint16_t n=m->n;
|
||||||
uint32_t t=A->ticks_ms();
|
uint32_t t=A->ticks_ms();
|
||||||
if(g.good[k]<COUNT_MAX) g.good[k]++;
|
|
||||||
if(!(n==g.flen && g.frm[n-1]==m->buf[n-1] && g.frm[n-2]==m->buf[n-2]
|
if(!(n==g.flen && g.frm[n-1]==m->buf[n-1] && g.frm[n-2]==m->buf[n-2]
|
||||||
&& t-g.tFrame<DUP_MS)){
|
&& t-g.tFrame<DUP_MS)){
|
||||||
memcpy(g.frm,m->buf,n);
|
memcpy(g.frm,m->buf,n);
|
||||||
@@ -484,6 +483,7 @@ static void draw(void){
|
|||||||
A->status_clear();
|
A->status_clear();
|
||||||
A->print_inverse(s+T_TITLE,2,0,true,true,(uint8_t)(2u+T_TITLE_CHARS*4u));
|
A->print_inverse(s+T_TITLE,2,0,true,true,(uint8_t)(2u+T_TITLE_CHARS*4u));
|
||||||
A->draw_battery();
|
A->draw_battery();
|
||||||
|
if(g.spk) A->asset_read(BMP_SPEAKER,A->status_line+SPK_X,BMP_SPEAKER_LEN);
|
||||||
|
|
||||||
if(!g.flen) /* no frame yet: a capsule, as APRS TX's TRANSMIT */
|
if(!g.flen) /* no frame yet: a capsule, as APRS TX's TRANSMIT */
|
||||||
A->print_inverse(s+T_WAIT,WAIT_CAPS_X,0,true,true,(uint8_t)(WAIT_CAPS_X+T_WAIT_CHARS*4u));
|
A->print_inverse(s+T_WAIT,WAIT_CAPS_X,0,true,true,(uint8_t)(WAIT_CAPS_X+T_WAIT_CHARS*4u));
|
||||||
@@ -517,11 +517,9 @@ static void draw(void){
|
|||||||
for(uint8_t x=0;x<128u;x+=2u) A->fb[3][x]|=0x40u;
|
for(uint8_t x=0;x<128u;x+=2u) A->fb[3][x]|=0x40u;
|
||||||
drawFreq(g.vfoFreq);
|
drawFreq(g.vfoFreq);
|
||||||
|
|
||||||
/* bottom line: "ok 12 -89dBm sl 5/12/7": frames, RSSI of the last one,
|
/* bottom line: "ok 12 -89dBm": frames, RSSI of the last one */
|
||||||
* frames per slicer (<= 31 characters) */
|
|
||||||
o=put(str,s+T_OK); o=puti(o,g.nOk); *o++=' '; *o++=' ';
|
o=put(str,s+T_OK); o=puti(o,g.nOk); *o++=' '; *o++=' ';
|
||||||
o=puti(o,g.rssi); o=put(o,s+T_DBM);
|
o=puti(o,g.rssi); o=put(o,s+T_DBM);
|
||||||
for(uint8_t k=0;k<NSL;k++){ if(k) *o++='/'; o=puti(o,g.good[k]); }
|
|
||||||
tiny(49,o);
|
tiny(49,o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,10 +531,8 @@ static void handleKeys(void){
|
|||||||
g.prevKey=key;
|
g.prevKey=key;
|
||||||
g.redraw=1;
|
g.redraw=1;
|
||||||
if(key==APP_KEY_EXIT) g.running=false;
|
if(key==APP_KEY_EXIT) g.running=false;
|
||||||
else if(key==APP_KEY_MENU){
|
else if(key==APP_KEY_MENU){ g.flen=0; g.nOk=0; g.seq=0; g.rssi=0; }
|
||||||
g.flen=0; g.nOk=0; g.seq=0; g.rssi=0;
|
else if(key==APP_KEY_1){ g.spk^=1u; A->audio_path(g.spk); } /* speaker, as FoxHunt's audio */
|
||||||
for(uint8_t k=0;k<NSL;k++) g.good[k]=0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- keys and screen, between frames ---- */
|
/* ---- keys and screen, between frames ---- */
|
||||||
@@ -587,7 +583,7 @@ static void listen(void){
|
|||||||
front(&d,adcRead(),&a,&b);
|
front(&d,adcRead(),&a,&b);
|
||||||
bool bz=false;
|
bool bz=false;
|
||||||
for(uint8_t k=0;k<NSL;k++){
|
for(uint8_t k=0;k<NSL;k++){
|
||||||
if(slice(&sl[k],a,b)) accept(&sl[k],k);
|
if(slice(&sl[k],a,b)) accept(&sl[k]);
|
||||||
bz|=busy(&sl[k]);
|
bz|=busy(&sl[k]);
|
||||||
}
|
}
|
||||||
if(++cnt<HOUSE_EVERY) continue;
|
if(++cnt<HOUSE_EVERY) continue;
|
||||||
@@ -612,7 +608,11 @@ void app_main(const app_api_t *api){
|
|||||||
biasOn();
|
biasOn();
|
||||||
api->backlight_on();
|
api->backlight_on();
|
||||||
g.vfoFreq=api->rx_freq();
|
g.vfoFreq=api->rx_freq();
|
||||||
api->audio_path(true);
|
/* Speaker amplifier (PA8) off: the decoder does not need it (tested on the
|
||||||
|
* radio, 2026-09-30): PA4, the voice-prompt DAC pin, joins the audio before
|
||||||
|
* the amplifier; only the BK4829 AF output must be on. Key 1 turns the
|
||||||
|
* speaker on to listen to the channel. */
|
||||||
|
api->audio_path(false);
|
||||||
api->set_af(APP_AF_FM);
|
api->set_af(APP_AF_FM);
|
||||||
api->delay_ms(50);
|
api->delay_ms(50);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
|
APP="$(basename "$PWD")" # breakout, foxhunt, beacon, fm, ...
|
||||||
APP_NAME="APRS RX" # <-- the only per-app line
|
APP_NAME="APRS RX" # <-- the only per-app line
|
||||||
APP_VER="0.5"
|
APP_VER="0.6"
|
||||||
APP_API_MIN=2
|
APP_API_MIN=2
|
||||||
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
|
APP_VMA=${APP_VMA:-0x20000280} # pinned overlay VMA (Core/py32f071xb.ld)
|
||||||
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
|
OUT="${APP_NAME// /}" # blob basename ("Broadcast FM" -> BroadcastFM)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ WAIT_CAPS = "WAIT" # status-bar capsule until a frame
|
|||||||
UI = [
|
UI = [
|
||||||
("T_TITLE", TITLE),
|
("T_TITLE", TITLE),
|
||||||
("T_WAIT", WAIT_CAPS),
|
("T_WAIT", WAIT_CAPS),
|
||||||
("T_DBM", "dBm sl "),
|
("T_DBM", "dBm"),
|
||||||
("T_OK", "ok "),
|
("T_OK", "ok "),
|
||||||
("T_KMH", "km/h "),
|
("T_KMH", "km/h "),
|
||||||
("T_CUSTOM", "Custom-"),
|
("T_CUSTOM", "Custom-"),
|
||||||
@@ -47,6 +47,7 @@ a.const("UI_SIZE", ui_size) # the UI block read by draw()
|
|||||||
a.const("T_TITLE_CHARS", len(TITLE))
|
a.const("T_TITLE_CHARS", len(TITLE))
|
||||||
a.const("T_WAIT_CHARS", len(WAIT_CAPS))
|
a.const("T_WAIT_CHARS", len(WAIT_CAPS))
|
||||||
a.table("T_MSG", MIC_MSG) # Mic-E standard messages
|
a.table("T_MSG", MIC_MSG) # Mic-E standard messages
|
||||||
|
a.u8("BMP_SPEAKER", [0x1c,0x1c,0x3e,0x7f,0x00,0x22,0x1c,0x41,0x22,0x1c]) # FoxHunt's
|
||||||
a.i8("COS1200", cos_table(1200)) # 8 entries
|
a.i8("COS1200", cos_table(1200)) # 8 entries
|
||||||
a.i8("COS2200", cos_table(2200)) # 48 entries
|
a.i8("COS2200", cos_table(2200)) # 48 entries
|
||||||
a.main()
|
a.main()
|
||||||
Reference in new issue
Block a user