From 88d45af641514df8d3da5c1b59d1b42e059d8b54 Mon Sep 17 00:00:00 2001 From: Jan Janak Date: Sun, 24 Jul 2022 10:05:46 +0200 Subject: [PATCH] Clean up AT$CW and AT$CM This commit includes the following changes: * Emit +EVENT=9,0 at the end of AT$CW transmission * Emit +EVENT=9,1 at the end of AT$CM transmission * Check parameters supplied by the application/user * Re-order initialization statements in AT$CM * Switch to Radio.* API where possible (instead of SX1276) * Acess DIO* via the global variable SX1276 --- src/cmd.c | 138 ++++++++++++++++++++++++++++++++++++------------------ src/cmd.h | 7 +-- 2 files changed, 96 insertions(+), 49 deletions(-) diff --git a/src/cmd.c b/src/cmd.c index 51ac2f9..ea0a9d0 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1,7 +1,6 @@ #include "cmd.h" #include #include -#include #include #include #include @@ -1120,13 +1119,29 @@ static void cw(atci_param_t *param) log_debug("$CW: freq=%ld Hz power=%ld dBm timeout=%ld s", freq, power, timeout); - MlmeReq_t mlr = { .Type = MLME_TXCW }; - mlr.Req.TxCw.Timeout = timeout; - mlr.Req.TxCw.Frequency = freq; - mlr.Req.TxCw.Power = power; - - abort_on_error(LoRaMacMlmeRequest(&mlr)); - + // We could have invoked Radio.SetTxContinuousWave directly here. The + // benefit of invoking the function via the MIB is that it forces the MAC + // into a LORAMAC_TX_RUNNING state for the duration of the transmission, + // which will prevent other transmission attempts from disrupting the + // ongoing carrier wave transmission. There appears to be no other way to + // transition into that state manually. + + MlmeReq_t r = { + .Type = MLME_TXCW, + .Req = { .TxCw = { + .Timeout = timeout, + .Frequency = freq, + .Power = power + }}}; + abort_on_error(LoRaMacMlmeRequest(&r)); + + lrw_event_subtype = CMD_CERT_CW_ENDED; + + // Continuous carrier wave transmission internally reconfigures some of the + // SX1276 DIO pins and interrupts. Rather than trying to restore everything + // to a functioning state, we automatically perform a modem reset after the + // transmission has ended. This command is for certification purposes only, + // so this behavior is fine. schedule_reset = true; OK_(); } @@ -1135,85 +1150,116 @@ static void cw(atci_param_t *param) static void cm_clk_irq_handler(void* context) { (void) context; + static uint32_t i; - static uint32_t i = 0; - i++; - gpio_write(RADIO_DIO_2_PORT, RADIO_DIO_2_PIN, (i % 2) == 0); + // AT$CM generates a continuous stream of ones and zeros modulated with FSK. + // This interrupt handler is invoked on the falling edge of the clock signal + // generated on DIO1. It alternates the state of DIO2 in order to generate + // the sequence of ones and zeroes. + GpioWrite(&SX1276.DIO2, (i++ % 2) == 0); } static void cm(atci_param_t *param) { - // Since we're switching the SX1276 radio to a continuous mode, the preamble - // is technically needed. The radio only transmits the preamble in the - // packet mode. However, SX1276SetTxConfig requires a preamble, so we - // configure a dummy value here. -#define PREAMBLE 0x05 - - // Example freq 868.3 MHz, 250 kHz deviation, datarate 4800, timeout 2 seconds + // Example with 868.3 MHz center frequency, 250 kHz deviation, 4800 Bd data + // rate, transmission power -10 dBm, and 2 second timeout: // AT$CM 868300000,250000,4800,-10,2 uint32_t freq, timeout, fdev, datarate; int32_t power; if (!atci_param_get_uint(param, &freq)) abort(ERR_PARAM); if (!atci_param_is_comma(param)) abort(ERR_PARAM); + if (!atci_param_get_uint(param, &fdev)) abort(ERR_PARAM); if (!atci_param_is_comma(param)) abort(ERR_PARAM); + if (!atci_param_get_uint(param, &datarate)) abort(ERR_PARAM); if (!atci_param_is_comma(param)) abort(ERR_PARAM); + if (!atci_param_get_int(param, &power)) abort(ERR_PARAM); + if (power < INT8_MIN || power > INT8_MAX) abort(ERR_PARAM); + if (!atci_param_is_comma(param)) abort(ERR_PARAM); if (!atci_param_get_uint(param, &timeout)) abort(ERR_PARAM); + if (timeout > UINT16_MAX) abort(ERR_PARAM); - log_debug("$CM: freq=%ld Hz fdev=%ld Hz datarate=%ld Bd power=%ld dBm timeout=%ld s", freq, fdev, datarate, power, timeout); + // Make sure there are no additional parameters that we don't understand. + if (param->offset != param->length) abort(ERR_PARAM); - // Set MacState to LORAMAC_TX_RUNNING to keep TX running indefinitely - MlmeReq_t mlr = { .Type = MLME_TXCW }; - mlr.Req.TxCw.Timeout = timeout; - mlr.Req.TxCw.Frequency = freq; - mlr.Req.TxCw.Power = power; - abort_on_error(LoRaMacMlmeRequest(&mlr)); + log_debug("$CM: freq=%ld Hz fdev=%ld Hz datarate=%ld Bd power=%ld dBm timeout=%ld s", + freq, fdev, datarate, power, timeout); + + // Rewire SX1276 interrupt handlers. We disable everything but the interrupt + // handler on DIO1, which points to our continuous mode interrupt handler. + DioIrqHandler *irq[] = { NULL, cm_clk_irq_handler, NULL, NULL, NULL, NULL }; + SX1276IoIrqInit(irq); + + // Invoke the continuous carrier wave MIB request. This is the same + // operation that AT$CW performs. We technically don't need to invoke this + // command here since the SetTxConfig command invoked below resets most of + // the settings performed by TXCW and stops the radio again. We primarily + // invoke the MIB command here to move the MAC into LORAMAC_TX_RUNNING + // state. This solution is a bit hackish, but LoRaMac-node does not seem to + // provide any other API. + + MlmeReq_t r = { + .Type = MLME_TXCW, + .Req = { .TxCw = { + .Timeout = timeout, + .Frequency = freq, + .Power = power + }}}; + abort_on_error(LoRaMacMlmeRequest(&r)); + schedule_reset = true; - timeout = (uint32_t)timeout * 1000; + timeout *= 1000; - SX1276SetStby(); - SX1276SetChannel(freq); - SX1276SetTxConfig(MODEM_FSK, power, fdev, 0, datarate, 0, PREAMBLE, false, false, 0, 0, 0, timeout); + // Configure the radio in FSK mode with the selected transmission power, FSK + // deviation, and data rate. We provide 0x5 as a dummy preamble, but since + // we're operating in the continuous mode here, the preamble won't be used + // (it's only used in the packet mode). Internally, SetTXConfig switches the + // radio into the packet mode and puts on stand by. + Radio.SetTxConfig(MODEM_FSK, power, fdev, 0, datarate, 0, 5, false, false, 0, 0, 0, timeout); - // Disable the SX1276 packet mode + // Since SetTxConfig internally forces the radio into packet mode, we need + // to switch to the continous mode here. SX1276Write(REG_PACKETCONFIG2, SX1276Read(REG_PACKETCONFIG2) & RF_PACKETCONFIG2_DATAMODE_MASK); - // Disable radio interrupts, enable the SX1276 modulator clock on DIO1 + // Disable DIO0, enable modulator clock on DIO1 SX1276Write(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_11 | RF_DIOMAPPING1_DIO1_00); - SX1276Write(REG_DIOMAPPING2, RF_DIOMAPPING2_DIO4_10 | RF_DIOMAPPING2_DIO5_10); - // Generate interrupts on the falling clock edge. SX1276 samples on the rising edge. - GPIO_InitTypeDef cfg_dio1 = { + // Generate falling edge interrupts on DIO1. We have configured the radio to + // generate a FSK modulator clock on this pin. The radio samples on the + // rising edge which means that we can modify the state of DIO2 on the + // falling edge. + GPIO_InitTypeDef dio1 = { .Mode = GPIO_MODE_IT_FALLING, .Pull = GPIO_PULLUP, .Speed = GPIO_SPEED_HIGH }; - gpio_init(RADIO_DIO_1_PORT, RADIO_DIO_1_PIN, &cfg_dio1); + gpio_init(SX1276.DIO1.port, SX1276.DIO1.pinIndex, &dio1); - // Configure DIO2 GPIO as an output - GPIO_InitTypeDef cfg_dio2 = { + // Configure DIO2 GPIO as output + GPIO_InitTypeDef dio2 = { .Mode = GPIO_MODE_OUTPUT_PP, .Pull = GPIO_NOPULL, .Speed = GPIO_SPEED_HIGH }; - gpio_init(RADIO_DIO_2_PORT, RADIO_DIO_2_PIN, &cfg_dio2); + gpio_init(SX1276.DIO2.port, SX1276.DIO2.pinIndex, &dio2); - // Rewire SX1276 interrupts - - DioIrqHandler *DioIrq[] = { NULL, cm_clk_irq_handler, NULL, NULL, NULL, NULL }; - SX1276IoIrqInit(DioIrq); + // Radio.SetTxConfig we call above puts the modem into a standby mode again + // and resets the TX timeout timer. Thus, we need to invoke SX1276SetTx here + // to start transmitting and to reset the TX timeout timer. SX1276SetTx(timeout); - // Since we currently have no code to restore the original settings, the - // modem must be rebooted after AT$CM has ended to restore the original - // configuration. - schedule_reset = true; + lrw_event_subtype = CMD_CERT_CM_ENDED; + // Reboot the modem once the transmission has finished. Since the AT$CW and + // AT$CM commands are primarily for certification, we don't bother restoring + // DIO port configuration and interrupt handlers and instead force the modem + // to reboot. + schedule_reset = true; OK_(); } diff --git a/src/cmd.h b/src/cmd.h index 1f49b77..fb0a514 100644 --- a/src/cmd.h +++ b/src/cmd.h @@ -8,7 +8,7 @@ enum cmd_event { CMD_EVENT_MODULE = 0, CMD_EVENT_JOIN = 1, CMD_EVENT_NETWORK = 2, - CMD_EVENT_CW = 50 + CMD_EVENT_CERT = 9 }; @@ -33,8 +33,9 @@ enum cmd_event_net { }; -enum cmd_event_cw { - CMD_CW_END = 0 +enum cmd_event_cert { + CMD_CERT_CW_ENDED = 0, + CMD_CERT_CM_ENDED = 1 };