mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Fix Spectrum3D audio flutter while listening, trim overlay code size
This commit is contained in:
1 parent
2aff80cc0e
commit
712e4fbb65
1 file changed
+176
-87
@@ -52,6 +52,7 @@
|
||||
|
||||
#define W 128u
|
||||
#define FB_H 56u /* frame-buffer rows (below the status line) */
|
||||
#define PAGES (FB_H / 8u) /* frame-buffer pages (LCD lines) */
|
||||
#define BINS 64u /* points per sweep, 2 px apart */
|
||||
#define LINES 12u
|
||||
#define ACC LINES /* history row of the next line */
|
||||
@@ -79,15 +80,15 @@
|
||||
* to a signal gets a millisecond before its RSSI is confirmed, which prevents
|
||||
* a strong station from being smeared across the following frequencies. */
|
||||
#define SIGNAL_SETTLE_MS 1u
|
||||
#define PARKED 0xFFu /* measure(): receiver not moved */
|
||||
/* Level above the floor that counts as a signal for the settle choice. The
|
||||
* display's NOISE_GATE is too low for this: noise alone crosses it on many
|
||||
* points and each crossing cost two full waits. */
|
||||
#define SIGNAL_DB 10u
|
||||
/* Listening to the strongest peak: audio path settle (as FoxHunt), RSSI check
|
||||
* period (also a landscape line, as a sweep would be) and how many quiet
|
||||
* checks in a row close it (about a second). */
|
||||
/* Listening to the strongest peak: audio path settle (as FoxHunt) and how
|
||||
* many quiet RSSI checks in a row close it (about a second: a check, also a
|
||||
* landscape line as a sweep would be, comes every four frames). */
|
||||
#define AUDIO_SETTLE_MS 60u
|
||||
#define LISTEN_TICK_MS 100u
|
||||
#define LISTEN_HANG 8u
|
||||
/* Half width, in points, of the station's shape redrawn while listening. */
|
||||
#define SHAPE_R 4u
|
||||
@@ -131,6 +132,7 @@ struct globals {
|
||||
int8_t yaw;
|
||||
uint8_t pitch;
|
||||
uint8_t head, sweeps, prev_key, gate, close_lvl, quiet, peak_bin;
|
||||
uint8_t page; /* next page feed() sends, PAGES = status line */
|
||||
bool running, hold, saver, abort_sweep, farm, listen, listening;
|
||||
uint8_t listen_ref; /* first listening level, 0 = none */
|
||||
/* the span's SPAN_REC record, read whole: same order and sizes */
|
||||
@@ -142,9 +144,9 @@ struct globals {
|
||||
/* words: Thumb-1 loads them with an immediate offset, while a signed
|
||||
halfword needs a register offset or an extra sign extension */
|
||||
int32_t ys, yc, ps, pc; /* view angles (Q8) for project() */
|
||||
int32_t side[4]; /* frame: left x, y, right x, y */
|
||||
const app_api_t *api;
|
||||
state_t *st;
|
||||
uint8_t *front; /* last complete frame, while listening */
|
||||
uint32_t centre, first, last_ok; /* requested centre, window start, region top */
|
||||
uint32_t peak_f; /* strongest point of the sweep (at peak_bin), 0 = none */
|
||||
/* the listened station's levels around peak_bin in the sweep that found
|
||||
@@ -176,13 +178,23 @@ void *memset(void *dst, int value, size_t size)
|
||||
/* Division through the resident helper (API level 2) instead of libgcc's
|
||||
* 468-byte signed division. GCC calls __aeabi_idiv for `/` and
|
||||
* __aeabi_idivmod for `%`: both find the quotient in r0 (and the remainder in
|
||||
* r1), which is how the resident helper returns them. */
|
||||
* r1), which is how the resident helper returns them. put_freq() calls it
|
||||
* too, for both at once: never inlined there. */
|
||||
__attribute__((noinline))
|
||||
uint64_t __aeabi_idivmod(int32_t n, int32_t d)
|
||||
{
|
||||
return A->idivmod(n, d);
|
||||
}
|
||||
uint64_t __aeabi_idiv(int32_t n, int32_t d) __attribute__((alias("__aeabi_idivmod")));
|
||||
|
||||
/* A->asset_read from a single place: its slot lies past the 124-byte reach of
|
||||
* a load offset, so each direct call spent 8 bytes on it, a call here 4. */
|
||||
__attribute__((noinline))
|
||||
static void asset(uint16_t offset, void *buf, uint16_t len)
|
||||
{
|
||||
A->asset_read(offset, buf, len);
|
||||
}
|
||||
|
||||
static uint8_t slen(const char *s)
|
||||
{
|
||||
uint8_t n = 0;
|
||||
@@ -199,7 +211,12 @@ static char *put_freq(char *o, int32_t v)
|
||||
{
|
||||
char t[10];
|
||||
uint8_t n = 0;
|
||||
do { t[n++] = (char)('0' + v % 10); v /= 10; } while (v || n < 6u);
|
||||
do {
|
||||
/* one call for both: `%` and `/` would each call the helper */
|
||||
const uint64_t qr = __aeabi_idivmod(v, 10);
|
||||
t[n++] = (char)('0' + (int32_t)(qr >> 32));
|
||||
v = (int32_t)qr;
|
||||
} while (v || n < 6u);
|
||||
while (n) {
|
||||
*o++ = t[--n];
|
||||
if (n == 5u)
|
||||
@@ -214,10 +231,12 @@ static char *put_freq(char *o, int32_t v)
|
||||
* stays in the VFO's band, whose path the loader already selected. */
|
||||
static void tune(uint32_t f)
|
||||
{
|
||||
A->bk_write(REG_FREQ_LO, (uint16_t)f);
|
||||
A->bk_write(REG_FREQ_HI, (uint16_t)(f >> 16));
|
||||
A->bk_write(REG_CTRL, 0);
|
||||
A->bk_write(REG_CTRL, g.scan_ctrl);
|
||||
/* held in a register: A->bk_write would be reloaded after every call */
|
||||
void (*const write)(uint8_t, uint16_t) = A->bk_write;
|
||||
write(REG_FREQ_LO, (uint16_t)f);
|
||||
write(REG_FREQ_HI, (uint16_t)(f >> 16));
|
||||
write(REG_CTRL, 0);
|
||||
write(REG_CTRL, g.scan_ctrl);
|
||||
}
|
||||
|
||||
/* Park the receiver on the sweep's first point ahead of time. The jump back
|
||||
@@ -231,15 +250,19 @@ static void park_start(void)
|
||||
|
||||
/* dBm + 160 after `ms` of settling. With no fixed delay, poll the BK4829
|
||||
* glitch indicator as the resident fast scanner does, then discard the first
|
||||
* RSSI value because it may still belong to the previous frequency. */
|
||||
* RSSI value because it may still belong to the previous frequency. PARKED
|
||||
* (listening, the receiver has not moved): a plain read. */
|
||||
static uint8_t measure(uint8_t ms)
|
||||
{
|
||||
const app_api_t *const a = A; /* one load for all the calls */
|
||||
if (ms != PARKED) {
|
||||
if (ms)
|
||||
A->delay_ms(ms);
|
||||
for (uint8_t guard = 50u; guard && (A->bk_read(REG_GLITCH) & 0xFFu) >= 200u; guard--)
|
||||
a->delay_ms(ms);
|
||||
for (uint8_t guard = 50u; guard && (a->bk_read(REG_GLITCH) & 0xFFu) >= 200u; guard--)
|
||||
;
|
||||
A->bk_read(REG_RSSI); /* first read may still move */
|
||||
const int16_t dbm = A->rssi_dbm();
|
||||
a->bk_read(REG_RSSI); /* first read may still move */
|
||||
}
|
||||
const int16_t dbm = a->rssi_dbm();
|
||||
return (uint8_t)(dbm < -160 ? 0 : dbm > 95 ? 255 : dbm + 160);
|
||||
}
|
||||
|
||||
@@ -248,7 +271,7 @@ static uint8_t measure(uint8_t ms)
|
||||
static void place_window(void)
|
||||
{
|
||||
uint32_t edge[2u * REGION_COUNT]; /* inclusive (lo, hi) pairs */
|
||||
A->asset_read(REGION, edge, sizeof(edge));
|
||||
asset(REGION, edge, sizeof(edge));
|
||||
uint32_t lo = 0, hi = 0; /* in no region: all points flat */
|
||||
for (uint8_t r = 0; r < 2u * REGION_COUNT; r += 2u)
|
||||
if (g.centre >= edge[r] && g.centre <= edge[r + 1u]) {
|
||||
@@ -269,7 +292,7 @@ static void place_window(void)
|
||||
|
||||
static void restart(void)
|
||||
{
|
||||
A->asset_read(SPAN_REC + g.span * SPAN_REC_SIZE, &g.step, SPAN_REC_SIZE);
|
||||
asset(SPAN_REC + g.span * SPAN_REC_SIZE, &g.step, SPAN_REC_SIZE);
|
||||
A->bk_write(REG_RX_BW, g.scan_bw); /* filter matched to the spacing */
|
||||
place_window();
|
||||
memset(S, 0, sizeof(*S));
|
||||
@@ -366,7 +389,7 @@ static void sweep(void)
|
||||
park_start(); /* the window may have moved too */
|
||||
return;
|
||||
}
|
||||
if (b && !g.saver)
|
||||
if (b) /* (the saver returned above) */
|
||||
draw(b >> 4); /* in-between frame: 1/4, 2/4, 3/4 */
|
||||
}
|
||||
/* past the region's edge nothing is measured: the point stays flat */
|
||||
@@ -407,15 +430,17 @@ static void plot(uint8_t x, int32_t y)
|
||||
A->fb[y >> 3][x] |= (uint8_t)(1u << (y & 7));
|
||||
}
|
||||
|
||||
/* 3x5 capsule on fb line 0. The frame starts at x - 2, so x >= 2 (x = 1
|
||||
* wrapped to fb[0][255], i.e. a stray mark at fb[1][127]); the landscape
|
||||
* under it is cleared first so the XOR inversion stays clean. */
|
||||
static void capsule(const char *s, uint8_t x)
|
||||
/* 3x5 capsule on fb line 0, or on the status line (the title). The frame
|
||||
* starts at x - 2, so x >= 2 (x = 1 wrapped to fb[0][255], i.e. a stray mark
|
||||
* at fb[1][127]); the landscape under it is cleared first so the XOR
|
||||
* inversion stays clean. For the title, that fb clear is wiped anyway: the
|
||||
* frame is cleared right after the status line is built. */
|
||||
static void capsule(const char *s, uint8_t x, bool status)
|
||||
{
|
||||
const uint8_t end = (uint8_t)(x + slen(s) * 4u);
|
||||
for (uint8_t i = (uint8_t)(x - 2u); i <= end; i++)
|
||||
A->fb[0][i] = 0;
|
||||
A->print_inverse(s, x, 0, false, true, end);
|
||||
A->print_inverse(s, x, 0, status, true, end);
|
||||
}
|
||||
|
||||
/* Screen point p = {x, y} of the grid point (x, z) raised by h quarter units. */
|
||||
@@ -446,17 +471,55 @@ static void line(const int32_t *a, const int32_t *b, bool dotted)
|
||||
}
|
||||
}
|
||||
|
||||
/* The frame's sides from the last depth down to z; the new corners are kept
|
||||
* for the next stretch. Painter's order: each stretch goes before the line in
|
||||
* front of it, which then hides what lies behind. */
|
||||
static void frame_sides(int32_t z)
|
||||
/* Exchange n bytes (bytewise: the frame buffer is not word aligned). */
|
||||
static void swap(uint8_t *a, uint8_t *b, uint32_t n)
|
||||
{
|
||||
while (n--) {
|
||||
const uint8_t t = *a;
|
||||
*a++ = *b;
|
||||
*b++ = t;
|
||||
}
|
||||
}
|
||||
|
||||
/* While a station is heard, the LCD gets one page per stretch of the frame's
|
||||
* sides (14 per frame, about every 3 ms) and the frames follow each other
|
||||
* with no idle wait: the display traffic is an even flow of short transfers,
|
||||
* as in the resident spectrum. Full-screen transfers (about 11 ms of SPI)
|
||||
* between idle waits made a burst pattern at the frame rate (~18 Hz) and the
|
||||
* check rate (~5 Hz), heard in the audio as a "helicopter" flutter. The
|
||||
* pages come from `front`, the last complete frame, swapped into the frame
|
||||
* buffer only for their transfer so that the frame being drawn there stays
|
||||
* intact; the status line goes out as an eighth page. */
|
||||
static void feed(void)
|
||||
{
|
||||
const uint8_t p = g.page;
|
||||
g.page = (uint8_t)(p < PAGES ? p + 1u : 0u);
|
||||
if (p == PAGES) {
|
||||
A->blit_status();
|
||||
return;
|
||||
}
|
||||
uint8_t *const f = g.front + p * W, *const fb = A->fb[p];
|
||||
swap(f, fb, W);
|
||||
A->blit_line(p);
|
||||
swap(f, fb, W);
|
||||
}
|
||||
|
||||
/* The frame's sides from the last depth down to z; the new corners are kept
|
||||
* in `side` (left x, y, right x, y: on draw()'s stack, no literal to reach
|
||||
* it) for the next stretch. Painter's order: each stretch goes before the
|
||||
* line in front of it, which then hides what lies behind. A stretch comes
|
||||
* before each line and before the front edge, evenly spaced: the pace of
|
||||
* feed(). */
|
||||
static void frame_sides(int32_t z, int32_t *side)
|
||||
{
|
||||
if (g.listening)
|
||||
feed();
|
||||
for (uint8_t s = 0; s < 4u; s += 2u) {
|
||||
int32_t p[2];
|
||||
project(s ? BOARD_X : -BOARD_X, z, 0, p);
|
||||
line(g.side + s, p, false);
|
||||
g.side[s] = p[0];
|
||||
g.side[s + 1u] = p[1];
|
||||
line(side + s, p, false);
|
||||
side[s] = p[0];
|
||||
side[s + 1u] = p[1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +528,7 @@ static void frame_sides(int32_t z)
|
||||
static void view(void)
|
||||
{
|
||||
int16_t sinq[SINQ_LEN / 2u]; /* sin() Q8, -45..135 deg by 5 deg */
|
||||
A->asset_read(SINQ, sinq, sizeof(sinq));
|
||||
asset(SINQ, sinq, sizeof(sinq));
|
||||
g.ys = sinq[SINQ_ZERO + g.yaw];
|
||||
g.yc = sinq[SINQ_ZERO + 18u - g.yaw]; /* cos = sin(90 - angle) */
|
||||
g.ps = sinq[SINQ_ZERO + g.pitch];
|
||||
@@ -476,19 +539,22 @@ static void view(void)
|
||||
static void draw(uint8_t quarter)
|
||||
{
|
||||
char buf[BUF_LEN];
|
||||
int32_t side[4]; /* frame corners, see frame_sides() */
|
||||
|
||||
/* Status line: title (or HOLD), the F-armed and listen icons, the
|
||||
* battery. Only on a sweep's main frame: the in-between frames only move
|
||||
* the landscape, and the LCD keeps the status line meanwhile. */
|
||||
* the landscape, and the LCD keeps the status line meanwhile. While
|
||||
* listening, feed() sends it with the pages. */
|
||||
if (!quarter) {
|
||||
A->status_clear();
|
||||
A->asset_read(g.hold ? T_HOLD : T_TITLE, buf, TEXT_MAX);
|
||||
A->print_inverse(buf, 2, 0, true, true, (uint8_t)(2u + slen(buf) * 4u));
|
||||
asset(g.hold ? T_HOLD : T_TITLE, buf, TEXT_MAX);
|
||||
capsule(buf, 2, true);
|
||||
if (g.farm)
|
||||
A->asset_read(BMP_F, A->status_line + 70, BMP_F_LEN);
|
||||
asset(BMP_F, A->status_line + 70, BMP_F_LEN);
|
||||
if (g.listen)
|
||||
A->asset_read(BMP_SPEAKER, A->status_line + 55, BMP_SPEAKER_LEN);
|
||||
asset(BMP_SPEAKER, A->status_line + 55, BMP_SPEAKER_LEN);
|
||||
A->draw_battery();
|
||||
if (!g.listening)
|
||||
A->blit_status();
|
||||
}
|
||||
|
||||
@@ -507,9 +573,9 @@ static void draw(uint8_t quarter)
|
||||
|
||||
/* Far to near: the back corners and edge, a stretch of the sides before
|
||||
* each line, then the nearest stretch and the front edge. */
|
||||
project(-BOARD_X, BOARD_Z, 0, g.side);
|
||||
project(BOARD_X, BOARD_Z, 0, g.side + 2);
|
||||
line(g.side, g.side + 2, true); /* back: dotted */
|
||||
project(-BOARD_X, BOARD_Z, 0, side);
|
||||
project(BOARD_X, BOARD_Z, 0, side + 2);
|
||||
line(side, side + 2, true); /* back: dotted */
|
||||
for (uint8_t i = 0; i <= LINES; i++) { /* oldest (far) to ACC (near) */
|
||||
uint8_t row = ACC, fl = acc_floor;
|
||||
if (i < LINES) {
|
||||
@@ -519,7 +585,7 @@ static void draw(uint8_t quarter)
|
||||
fl = S->floor[row];
|
||||
}
|
||||
const int32_t z = ((int32_t)(LINES - 1u) - 2 * i) * 4 + off;
|
||||
frame_sides(z);
|
||||
frame_sides(z, side);
|
||||
int32_t pt[BINS][2];
|
||||
bool lit[BINS]; /* point above the noise gate */
|
||||
for (uint8_t b = 0; b < BINS; b++) {
|
||||
@@ -571,24 +637,31 @@ static void draw(uint8_t quarter)
|
||||
prev = y;
|
||||
}
|
||||
}
|
||||
frame_sides(-BOARD_Z);
|
||||
line(g.side, g.side + 2, false); /* front */
|
||||
frame_sides(-BOARD_Z, side);
|
||||
line(side, side + 2, false); /* front */
|
||||
|
||||
/* 3x5 capsules over the landscape: the frequency (centre, or the station
|
||||
* listened to) left, the span right */
|
||||
*put_freq(buf, (int32_t)(g.listening ? g.peak_f : g.centre)) = '\0';
|
||||
capsule(buf, 2);
|
||||
capsule(g.label, (uint8_t)(W - 2u - SPAN_LABEL_LEN * 4u));
|
||||
capsule(buf, 2, false);
|
||||
capsule(g.label, (uint8_t)(W - 2u - SPAN_LABEL_LEN * 4u), false);
|
||||
|
||||
if (g.listening) {
|
||||
/* the frame is complete: feed() sends it from its first page on */
|
||||
swap(g.front, A->fb[0], PAGES * W);
|
||||
g.page = 0;
|
||||
} else {
|
||||
A->blit_full();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- input --------------------------------------------------------------- */
|
||||
|
||||
/* The plain `key ==` tests alternate with the bounded ones on purpose: GCC
|
||||
* turns a run of plain tests into a jump table plus a libgcc helper, larger
|
||||
* than the comparisons themselves. */
|
||||
static void key_press(uint8_t key)
|
||||
{
|
||||
const int8_t yaw = g.yaw;
|
||||
const uint8_t pitch = g.pitch;
|
||||
const int8_t nav = A->nav_dir(key); /* UP/DOWN (UV-K1: LEFT/RIGHT) */
|
||||
const bool shifted = g.farm;
|
||||
g.farm = key == APP_KEY_F && !shifted; /* F arms the next key */
|
||||
@@ -597,32 +670,35 @@ static void key_press(uint8_t key)
|
||||
move_centre(nav > 0, g.step); /* one point: the landscape slides */
|
||||
} else if (key == APP_KEY_1) {
|
||||
move_centre(!shifted, CENTRE_STEP);
|
||||
} else if (key == APP_KEY_EXIT) {
|
||||
g.running = false;
|
||||
} else if (key == APP_KEY_4 && g.yaw > -YAW_MAX) {
|
||||
g.yaw--;
|
||||
view(); /* the view turned */
|
||||
} else if (key == APP_KEY_EXIT) {
|
||||
g.running = false;
|
||||
} else if (key == APP_KEY_6 && g.yaw < YAW_MAX) {
|
||||
g.yaw++;
|
||||
} else if (key == APP_KEY_2 && g.pitch < PITCH_MAX) {
|
||||
g.pitch++;
|
||||
} else if (key == APP_KEY_8 && g.pitch > PITCH_MIN) {
|
||||
g.pitch--;
|
||||
} else if (key == APP_KEY_5) {
|
||||
g.yaw = 0;
|
||||
g.pitch = PITCH_DEF;
|
||||
view();
|
||||
} else if (key == APP_KEY_0) {
|
||||
g.listen = !g.listen;
|
||||
} else if (key == APP_KEY_2 && g.pitch < PITCH_MAX) {
|
||||
g.pitch++;
|
||||
view(); /* the view tilted */
|
||||
} else if (key == APP_KEY_MENU) {
|
||||
g.hold = !g.hold;
|
||||
} else if (key == APP_KEY_8 && g.pitch > PITCH_MIN) {
|
||||
g.pitch--;
|
||||
view();
|
||||
} else if (key == APP_KEY_STAR) {
|
||||
g.speed = (uint8_t)(g.speed + 1u < SPEED_COUNT ? g.speed + 1u : 0u);
|
||||
g.sweeps = 0; /* keeps the scroll within one spacing */
|
||||
} else if (key == APP_KEY_3 && (uint8_t)(g.span + dir) < SPAN_COUNT) {
|
||||
g.span = (uint8_t)(g.span + dir);
|
||||
restart(); /* keeps the centre */
|
||||
} else if (key == APP_KEY_5) {
|
||||
g.yaw = 0;
|
||||
g.pitch = PITCH_DEF;
|
||||
view();
|
||||
}
|
||||
if (g.yaw != yaw || g.pitch != pitch)
|
||||
view(); /* the view turned or tilted */
|
||||
}
|
||||
|
||||
static void poll_keys(void)
|
||||
@@ -648,7 +724,9 @@ static void poll_keys(void)
|
||||
|
||||
/* Last frequency from f, going up or down, whose level stays at or above
|
||||
* `level`: whole REFINE_STEPs while it holds (at most REFINE_REACH), then
|
||||
* halving steps down to REFINE_MIN to place the edge. */
|
||||
* halving steps down to REFINE_MIN to place the edge. Not inlined: GCC put
|
||||
* a copy of the loop at each of its two calls. */
|
||||
__attribute__((noinline))
|
||||
static uint32_t edge(uint32_t f, uint8_t level, bool up)
|
||||
{
|
||||
uint32_t d = REFINE_STEP;
|
||||
@@ -720,9 +798,10 @@ static void start_listen(void)
|
||||
const uint8_t *ref = S->line[g.sweeps ? ACC : (g.head ? g.head - 1u : LINES - 1u)];
|
||||
const uint8_t lit = (uint8_t)(g.gate - SIGNAL_DB + NOISE_GATE);
|
||||
memset(g.shape, 0, sizeof(g.shape));
|
||||
g.shape[SHAPE_R] = ref[g.peak_bin];
|
||||
/* k = 0 is the peak itself, which stands above the gate in ref (the
|
||||
sweep peak-held it there): both directions keep it */
|
||||
for (int8_t dir = -1; dir <= 1; dir += 2)
|
||||
for (uint8_t k = 1; k <= SHAPE_R; k++) {
|
||||
for (uint8_t k = 0; k <= SHAPE_R; k++) {
|
||||
const uint8_t b = (uint8_t)(g.peak_bin + dir * (int8_t)k);
|
||||
if (b >= BINS || ref[b] <= lit)
|
||||
break; /* off the station (or the sweep) */
|
||||
@@ -738,20 +817,24 @@ static void start_listen(void)
|
||||
g.close_lvl = (uint8_t)(g.close_db + g.gate - SIGNAL_DB);
|
||||
g.quiet = 0;
|
||||
g.listening = true;
|
||||
A->set_agc(true);
|
||||
A->bk_write(REG_CTRL, g.scan_ctrl | CTRL_AF_DAC);
|
||||
A->audio_path(true);
|
||||
A->delay_ms(AUDIO_SETTLE_MS);
|
||||
A->set_af(APP_AF_FM);
|
||||
const app_api_t *const a = A; /* one load for the calls below */
|
||||
/* feed() starts from the frame on screen, left complete by the sweep */
|
||||
swap(g.front, a->fb[0], PAGES * W);
|
||||
a->set_agc(true);
|
||||
a->bk_write(REG_CTRL, g.scan_ctrl | CTRL_AF_DAC);
|
||||
a->audio_path(true);
|
||||
a->delay_ms(AUDIO_SETTLE_MS);
|
||||
a->set_af(APP_AF_FM);
|
||||
}
|
||||
|
||||
static void stop_listen(void)
|
||||
{
|
||||
A->set_af(APP_AF_MUTE);
|
||||
A->audio_path(false);
|
||||
const app_api_t *const a = A; /* one load for all the calls */
|
||||
a->set_af(APP_AF_MUTE);
|
||||
a->audio_path(false);
|
||||
g.listening = false;
|
||||
A->set_agc(false); /* back to the sweep's fixed gain */
|
||||
A->bk_write(REG_RX_BW, g.scan_bw);
|
||||
a->set_agc(false); /* back to the sweep's fixed gain */
|
||||
a->bk_write(REG_RX_BW, g.scan_bw);
|
||||
park_start(); /* also restores REG_30 without the AF DAC */
|
||||
}
|
||||
|
||||
@@ -759,24 +842,28 @@ __attribute__((section(".text.entry"), used))
|
||||
void app_main(const app_api_t *api)
|
||||
{
|
||||
state_t state; /* ~0.8 KiB of history, on the stack */
|
||||
uint8_t front[PAGES * W]; /* 0.9 KiB: the frame feed() sends */
|
||||
A = api;
|
||||
S = &state;
|
||||
g.front = front;
|
||||
|
||||
A->cfg_load(&g.magic, CFG_DEFAULT_LEN);
|
||||
/* The setup calls go through the parameter, which stays in a register:
|
||||
through A, each call would reload the table pointer first. */
|
||||
api->cfg_load(&g.magic, CFG_DEFAULT_LEN);
|
||||
if (g.magic != CFG_MAGIC || g.span >= SPAN_COUNT || g.speed >= SPEED_COUNT ||
|
||||
(uint8_t)(g.yaw + YAW_MAX) > 2u * YAW_MAX ||
|
||||
(uint8_t)(g.pitch - PITCH_MIN) > PITCH_MAX - PITCH_MIN)
|
||||
A->asset_read(CFG_DEFAULT, &g.magic, CFG_DEFAULT_LEN);
|
||||
asset(CFG_DEFAULT, &g.magic, CFG_DEFAULT_LEN);
|
||||
g.running = true; /* the rest of g starts zeroed (.bss) */
|
||||
view();
|
||||
g.prev_key = A->get_key();
|
||||
A->backlight_on();
|
||||
A->audio_path(false);
|
||||
A->set_af(APP_AF_MUTE);
|
||||
A->set_agc(false); /* fixed gain: no pumping by a strong signal */
|
||||
g.saved_bw = A->bk_read(REG_RX_BW);
|
||||
g.scan_ctrl = A->bk_read(REG_CTRL) & ~CTRL_AF_DAC;
|
||||
g.centre = A->rx_freq();
|
||||
g.prev_key = api->get_key();
|
||||
api->backlight_on();
|
||||
api->audio_path(false);
|
||||
api->set_af(APP_AF_MUTE);
|
||||
api->set_agc(false); /* fixed gain: no pumping by a strong signal */
|
||||
g.saved_bw = api->bk_read(REG_RX_BW);
|
||||
g.scan_ctrl = api->bk_read(REG_CTRL) & ~CTRL_AF_DAC;
|
||||
g.centre = api->rx_freq();
|
||||
restart();
|
||||
|
||||
while (g.running) {
|
||||
@@ -793,16 +880,18 @@ void app_main(const app_api_t *api)
|
||||
continue;
|
||||
}
|
||||
if (g.listening) {
|
||||
for (uint8_t q = 1u; q < 4u; q++) { /* in-between frames */
|
||||
A->delay_ms(LISTEN_TICK_MS / 4u);
|
||||
if (!g.saver)
|
||||
/* The in-between frames, back to back: no idle wait, so that the
|
||||
display traffic stays an even flow (see feed()). The saver
|
||||
cannot start before the next poll_keys(). */
|
||||
for (uint8_t q = 1u; q < 4u; q++)
|
||||
draw(q);
|
||||
}
|
||||
A->delay_ms(LISTEN_TICK_MS / 4u);
|
||||
poll_keys();
|
||||
/* Close only after the app's measured level stays below its
|
||||
filter-adjusted threshold for the configured hang time. */
|
||||
const uint8_t m = measure(0);
|
||||
filter-adjusted threshold for the configured hang time. The
|
||||
receiver stays parked: a plain RSSI read, with no settling to
|
||||
wait for (on a noisy station, the glitch wait can spin up to
|
||||
50 register reads, a burst on the BK4829 bus). */
|
||||
const uint8_t m = measure(PARKED);
|
||||
g.quiet = m > g.close_lvl ? 0u : (uint8_t)(g.quiet + 1u);
|
||||
if (g.quiet >= LISTEN_HANG || !g.listen || g.hold || g.saver || g.abort_sweep) {
|
||||
stop_listen();
|
||||
|
||||
Reference in new issue
Block a user