diff options
| author | info@mode42.com <info@mode42.com> | 2026-08-07 18:25:13 +0000 |
|---|---|---|
| committer | info@mode42.com <info@mode42.com> | 2026-08-07 18:25:13 +0000 |
| commit | 04d965d67a7264a1c7c211494aebda1953df7603 (patch) | |
| tree | 0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/max25-bcpr/src/bcpr_ser12.c | |
Initial push
Diffstat (limited to 'stacks/max25-bcpr/src/bcpr_ser12.c')
| -rw-r--r-- | stacks/max25-bcpr/src/bcpr_ser12.c | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/stacks/max25-bcpr/src/bcpr_ser12.c b/stacks/max25-bcpr/src/bcpr_ser12.c new file mode 100644 index 0000000..32091e0 --- /dev/null +++ b/stacks/max25-bcpr/src/bcpr_ser12.c @@ -0,0 +1,246 @@ +#include "bcpr/bcpr_ser12.h" + +#include <stdlib.h> +#include <string.h> + +void bcpr_ser12_set_mode(bcpr_ser12_t *s, const char *mode, unsigned *baud_out) +{ + unsigned baud = 1200; + if (mode && strncmp(mode, "ser", 3) == 0) { + unsigned n = (unsigned)strtoul(mode + 3, NULL, 10); + if (n >= 3 && n <= 48) { + baud = n * 100u; + } + } + if (baud_out) { + *baud_out = baud; + } + s->baud = baud; + s->baud_us = 1000000u / baud; + if (mode && strchr(mode, '*')) { + s->opt_dcd = 0; + } else if (mode && strchr(mode, '+')) { + s->opt_dcd = -1; + } else { + s->opt_dcd = 1; + } +} + +void bcpr_ser12_init(bcpr_ser12_t *s, unsigned baud, int opt_dcd) +{ + memset(s, 0, sizeof(*s)); + s->baud = baud ? baud : 1200; + s->baud_us = 1000000u / s->baud; + s->opt_dcd = opt_dcd; + s->shreg = 0x10000; + s->dcd_sum0 = 2; + s->dcd_time = 120; + s->cal_mode = BCPR_CAL_OFF; + /* FlexNet SER12.doc defaults until bcpr_ser12_set_ptt_wd(). */ + s->ptt_wd = 1; + s->ptt_wd_key_us = 14500u * 1000u; + s->ptt_wd_pause_us = 500u * 1000u; +} + +void bcpr_ser12_set_ptt_wd(bcpr_ser12_t *s, int enable, int key_ms, int pause_ms) +{ + if (!s) { + return; + } + s->ptt_wd = enable ? 1 : 0; + if (key_ms < 1000) { + key_ms = 1000; + } + if (pause_ms < 50) { + pause_ms = 50; + } + s->ptt_wd_key_us = (unsigned)key_ms * 1000u; + s->ptt_wd_pause_us = (unsigned)pause_ms * 1000u; + s->ptt_wd_pausing = 0; + s->ptt_wd_phase_start_us = 0; +} + +void bcpr_ser12_force_unkey(bcpr_ser12_t *s) +{ + if (!s) { + return; + } + s->ptt_hw = 0; + s->ptt_wd_pausing = 0; + s->ptt_wd_phase_start_us = 0; + s->txshreg = 0; +} + +void bcpr_ser12_set_cal(bcpr_ser12_t *s, int cal_mode) +{ + if (!s) { + return; + } + if (cal_mode < BCPR_CAL_OFF || cal_mode > BCPR_CAL_ALT) { + cal_mode = BCPR_CAL_OFF; + } + s->cal_mode = cal_mode; + if (cal_mode != BCPR_CAL_OFF) { + s->ptt_hw = 1; + if (cal_mode == BCPR_CAL_HIGH) { + s->tx_bit = 1; + } else if (cal_mode == BCPR_CAL_LOW) { + s->tx_bit = 0; + } else { + s->tx_bit = 0; /* alt starts low, toggles each tick */ + } + s->txshreg = 1; + s->ptt_wd_pausing = 0; + s->ptt_wd_phase_start_us = 0; + } +} + +/* + * FlexNet SER12.doc / PAR96: during long calibrate, drop PTT every ~14.5 s for + * ~500 ms so the radio PTT watchdog can discharge. Mirror for --cal and any + * sustained key. Logical ptt_hw stays 1 (keep baud divisor / telemetry); only + * MCR RTS is cleared for the pause window. + */ +static void ser12_apply_ptt_wd(bcpr_ser12_t *s, int *mcr_out, unsigned now_us) +{ + unsigned elapsed; + + if (!s->ptt_wd || !s->ptt_hw) { + s->ptt_wd_pausing = 0; + s->ptt_wd_phase_start_us = 0; + return; + } + if (s->ptt_wd_phase_start_us == 0) { + s->ptt_wd_phase_start_us = now_us ? now_us : 1u; + s->ptt_wd_pausing = 0; + return; + } + elapsed = now_us - s->ptt_wd_phase_start_us; + if (!s->ptt_wd_pausing) { + if (elapsed >= s->ptt_wd_key_us) { + s->ptt_wd_pausing = 1; + s->ptt_wd_phase_start_us = now_us ? now_us : 1u; + *mcr_out = 0x0d; /* RTS clear — PTT off */ + } + return; + } + /* In pause: force idle MCR regardless of cal/HDLC bit. */ + *mcr_out = 0x0d; + if (elapsed >= s->ptt_wd_pause_us) { + s->ptt_wd_pausing = 0; + s->ptt_wd_phase_start_us = now_us ? now_us : 1u; + /* Next tick restores keyed MCR from cal/HDLC path. */ + } +} + +static void ser12_rx(bcpr_ser12_t *s, bcpr_hdlc_t *h, unsigned curs, + unsigned now_us) +{ + int timediff; + int bdus8 = (int)(s->baud_us >> 3); + int bdus4 = (int)(s->baud_us >> 2); + int bdus2 = (int)(s->baud_us >> 1); + + timediff = (int)(now_us - s->pll_time); + /* wrap handling for us modulo ~1s window */ + while (timediff >= 500000) { + timediff -= 1000000; + } + while (timediff <= -500000) { + timediff += 1000000; + } + while (timediff >= bdus2) { + timediff -= (int)s->baud_us; + s->pll_time += s->baud_us; + s->dcd_time--; + if (s->shreg & 1) { + bcpr_hdlc_putbits(h, (s->shreg >> 1) ^ 0xffffu); + s->shreg = 0x10000; + } + s->shreg >>= 1; + } + if (s->dcd_time <= 0) { + if (!s->opt_dcd) { + h->dcd = (s->dcd_sum0 + s->dcd_sum1 + s->dcd_sum2) < 0; + } + s->dcd_sum2 = s->dcd_sum1; + s->dcd_sum1 = s->dcd_sum0; + s->dcd_sum0 = 2; + s->dcd_time += 120; + } + if (s->last_rxbit != (unsigned char)curs) { + s->last_rxbit = (unsigned char)curs; + s->shreg |= 0x10000; + if (timediff > 0) { + s->pll_time += (unsigned)bdus8; + } else { + s->pll_time += 1000000u - (unsigned)bdus8; + } + if (abs(timediff) > bdus4) { + s->dcd_sum0 += 4; + } else { + s->dcd_sum0--; + } + } + while (s->pll_time >= 1000000u) { + s->pll_time -= 1000000u; + } +} + +void bcpr_ser12_tick(bcpr_ser12_t *s, bcpr_hdlc_t *h, int cts, int *mcr_out, + int *do_thr00, unsigned now_us) +{ + *do_thr00 = 1; + + /* DOS cal.exe: continuous PTT + sticky/toggle DTR; no HDLC. */ + if (s->cal_mode != BCPR_CAL_OFF) { + s->ptt_hw = 1; + if (s->cal_mode == BCPR_CAL_HIGH) { + s->tx_bit = 1; + } else if (s->cal_mode == BCPR_CAL_LOW) { + s->tx_bit = 0; + } else { + s->tx_bit = (unsigned char)!s->tx_bit; + } + *mcr_out = 0x0e | (!!s->tx_bit); + ser12_apply_ptt_wd(s, mcr_out, now_us); + return; + } + + /* + * While PTT: skip Soft-DCD RX PLL — PC-COM charge-pump needs unbroken + * THR 0x00 @ baud; RX work causes multi-ms TXD gaps → underpowered AFSK. + */ + if (!s->ptt_hw) { + ser12_rx(s, h, cts ? 1u : 0u, now_us); + } + + if (s->ptt_hw) { + *mcr_out = 0x0e | (!!s->tx_bit); + } else { + *mcr_out = 0x0d; + } + + if (s->ptt_hw) { + if (s->txshreg <= 1) { + s->txshreg = 0x10000u | bcpr_hdlc_getbits(h); + if (!bcpr_hdlc_ptt(h)) { + s->ptt_hw = 0; + *mcr_out = 0x0d; + ser12_apply_ptt_wd(s, mcr_out, now_us); + return; + } + } + s->tx_bit = (unsigned char)(!(s->tx_bit ^ (s->txshreg & 1))); + s->txshreg >>= 1; + } else { + bcpr_hdlc_arbitrate(h); + if (bcpr_hdlc_ptt(h)) { + s->txshreg = 1; + s->ptt_hw = 1; + } + } + bcpr_hdlc_transmitter(h); + ser12_apply_ptt_wd(s, mcr_out, now_us); + /* Receiver drain is owned by bcpr_engine (RX callback). */ +} |
