summaryrefslogtreecommitdiff
path: root/stacks/max25-bcpr/src/bcpr_ser12.c
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
commit04d965d67a7264a1c7c211494aebda1953df7603 (patch)
tree0ebd700a6e219f84a26a656f4bee8778bc75da7b /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.c246
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). */
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com