diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 532ad0fe3f..64a02edb1d 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -74,6 +74,7 @@ ifneq (,$(filter at86rf215%,$(USEMODULE))) DEFAULT_MODULE += netdev_ieee802154_oqpsk DEFAULT_MODULE += netdev_ieee802154_mr_oqpsk + DEFAULT_MODULE += netdev_ieee802154_mr_ofdm ifeq (,$(filter at86rf215m,$(USEMODULE))) DEFAULT_MODULE += at86rf215_24ghz diff --git a/drivers/at86rf215/at86rf215.c b/drivers/at86rf215/at86rf215.c index ae6428dd48..c96eae59a2 100644 --- a/drivers/at86rf215/at86rf215.c +++ b/drivers/at86rf215/at86rf215.c @@ -151,6 +151,10 @@ if (!IS_ACTIVE(CONFIG_AT86RF215_USE_CLOCK_OUTPUT)){ at86rf215_configure_OQPSK(dev, AT86RF215_DEFAULT_MR_OQPSK_CHIPS, AT86RF215_DEFAULT_MR_OQPSK_RATE); } + if (AT86RF215_DEFAULT_PHY_MODE == IEEE802154_PHY_MR_OFDM) { + at86rf215_configure_OFDM(dev, CONFIG_AT86RF215_DEFAULT_MR_OFDM_OPT, + CONFIG_AT86RF215_DEFAULT_MR_OFDM_MCS); + } /* set default channel */ at86rf215_set_chan(dev, dev->netdev.chan); diff --git a/drivers/at86rf215/at86rf215_netdev.c b/drivers/at86rf215/at86rf215_netdev.c index 8d34160608..b6facd775b 100644 --- a/drivers/at86rf215/at86rf215_netdev.c +++ b/drivers/at86rf215/at86rf215_netdev.c @@ -394,6 +394,18 @@ static int _get(netdev_t *netdev, netopt_t opt, void *val, size_t max_len) res = max_len; break; + case NETOPT_MR_OFDM_OPTION: + assert(max_len >= sizeof(int8_t)); + *((int8_t *)val) = at86rf215_OFDM_get_option(dev); + res = max_len; + break; + + case NETOPT_MR_OFDM_MCS: + assert(max_len >= sizeof(int8_t)); + *((int8_t *)val) = at86rf215_OFDM_get_scheme(dev); + res = max_len; + break; + case NETOPT_MR_OQPSK_CHIPS: assert(max_len >= sizeof(int16_t)); switch (at86rf215_OQPSK_get_chips(dev)) { @@ -574,11 +586,43 @@ static int _set(netdev_t *netdev, netopt_t opt, const void *val, size_t len) at86rf215_OQPSK_get_mode(dev)); res = sizeof(uint8_t); break; + case IEEE802154_PHY_MR_OFDM: + at86rf215_configure_OFDM(dev, + at86rf215_OFDM_get_option(dev), + at86rf215_OFDM_get_scheme(dev)); + res = sizeof(uint8_t); + break; default: return -ENOTSUP; } break; + case NETOPT_MR_OFDM_OPTION: + if (at86rf215_get_phy_mode(dev) != IEEE802154_PHY_MR_OFDM) { + return -ENOTSUP; + } + + assert(len <= sizeof(uint8_t)); + if (at86rf215_OFDM_set_option(dev, *((const uint8_t *)val)) == 0) { + res = sizeof(uint8_t); + } else { + res = -ERANGE; + } + break; + + case NETOPT_MR_OFDM_MCS: + if (at86rf215_get_phy_mode(dev) != IEEE802154_PHY_MR_OFDM) { + return -ENOTSUP; + } + + assert(len <= sizeof(uint8_t)); + if (at86rf215_OFDM_set_scheme(dev, *((const uint8_t *)val)) == 0) { + res = sizeof(uint8_t); + } else { + res = -ERANGE; + } + break; + case NETOPT_MR_OQPSK_CHIPS: if (at86rf215_get_phy_mode(dev) != IEEE802154_PHY_MR_OQPSK) { return -ENOTSUP; diff --git a/drivers/at86rf215/at86rf215_ofdm.c b/drivers/at86rf215/at86rf215_ofdm.c new file mode 100644 index 0000000000..8017818467 --- /dev/null +++ b/drivers/at86rf215/at86rf215_ofdm.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2019 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_at86rf215 + * @{ + * + * @file + * @brief Configuration of the MR-OFDM PHY on the AT86RF215 chip + * + * @author Benjamin Valentin + * @} + */ + +#include "at86rf215.h" +#include "at86rf215_internal.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* symbol time is always 120 µs for MR-OFDM */ +#define OFDM_SYMBOL_TIME_US 120 + +/* IEEE Std 802.15.4g™-2012 Amendment 3 + * Table 68d—Total number of channels and first channel center frequencies for SUN PHYs */ +static uint32_t _channel_spacing_kHz(uint8_t option) +{ + switch (option) { + case 1: return 1200; + case 2: return 800; + case 3: return 400; + case 4: return 200; + } + + return 0; +} + +/* IEEE Std 802.15.4g™-2012 Amendment 3 + * Table 68d—Total number of channels and first channel center frequencies for SUN PHYs */ +static uint32_t _channel_center_freq_kHz_868MHz(uint8_t option) +{ + switch (option) { + case 1: return 863625; + case 2: return 863425; + case 3: return 863225; + case 4: return 863125; + } + + return 0; +} + +/* IEEE Std 802.15.4g™-2012 Amendment 3 + * Table 68d—Total number of channels and first channel center frequencies for SUN PHYs */ +static uint32_t _channel_center_freq_kHz_2400MHz(uint8_t option) +{ + return 2400000 + _channel_spacing_kHz(option) - CCF0_24G_OFFSET; +} + +/* IEEE Std 802.15.4g™-2012 Amendment 3 + * Table 68d—Total number of channels and first channel center frequencies for SUN PHYs */ +static uint16_t _get_max_chan(at86rf215_t *dev, uint8_t option) +{ + if (is_subGHz(dev)) { + switch (option) { + case 1: return 5; + case 2: return 8; + case 3: return 17; + case 4: return 34; + } + } + else { + switch (option) { + case 1: return 64; + case 2: return 97; + case 3: return 207; + case 4: return 416; + } + } + + return 0; +} + +/* Table 6-90. Recommended Transmitter Frontend Configuration */ +static uint32_t _TXCUTC_LPFCUT(uint8_t option) +{ + switch (option) { + case 1: return 10 << TXCUTC_LPFCUT_SHIFT; + case 2: return 8 << TXCUTC_LPFCUT_SHIFT; + case 3: return 5 << TXCUTC_LPFCUT_SHIFT; + case 4: return 3 << TXCUTC_LPFCUT_SHIFT; + } + + return 0; +} + +/* Table 6-90. Recommended Transmitter Frontend Configuration */ +static uint32_t _TXDFE_SR(uint8_t option) +{ + switch (option) { + case 1: + case 2: return 3 << TXDFE_SR_SHIFT; + case 3: + case 4: return 6 << TXDFE_SR_SHIFT; + } + + return 0; +} + +/* Table 6-90. Recommended Transmitter Frontend Configuration */ +static uint32_t _TXDFE_RCUT(uint8_t option) +{ + switch (option) { + case 1: return 3 << TXDFE_RCUT_SHIFT; + case 2: + case 3: return 3 << TXDFE_RCUT_SHIFT; + case 4: return 2 << TXDFE_RCUT_SHIFT; + } + + return 0; +} + +/* Table 6-93. Recommended PHY Receiver and Digital Frontend Configuration */ +static uint32_t _RXDFE_RCUT(uint8_t option, bool superGHz) +{ + switch (option) { + case 1: return 4 << RXDFE_RCUT_SHIFT; + case 2: return 2 << RXDFE_RCUT_SHIFT; + case 3: return (2 + superGHz) << RXDFE_RCUT_SHIFT; + case 4: return 1 << RXDFE_RCUT_SHIFT; + } + + return 0; +} + +/* Table 6-93. Recommended PHY Receiver and Digital Frontend Configuration */ +static uint32_t _RXBWC_BW(uint8_t option, bool superGHz) +{ + switch (option) { + case 1: return (9 + superGHz) << RXBWC_BW_SHIFT; + case 2: return 7 << RXBWC_BW_SHIFT; + case 3: return (4 + superGHz) << RXBWC_BW_SHIFT; + case 4: return (2 + superGHz) << RXBWC_BW_SHIFT; + } + + return 0; +} + +/* Table 6-93. Recommended PHY Receiver and Digital Frontend Configuration */ +static uint32_t _RXBWC_IFS(uint8_t option, bool superGHz) +{ + switch (option) { + case 1: + case 2: return 1; + case 3: return superGHz; + case 4: return !superGHz; + } + + return 0; +} + +static void _set_option(at86rf215_t *dev, uint8_t option) +{ + const bool superGHz = !is_subGHz(dev); + + /* Set Receiver Bandwidth */ + at86rf215_reg_write(dev, dev->RF->RG_RXBWC, + _RXBWC_BW(option, superGHz) | _RXBWC_IFS(option, superGHz)); + /* Set fS (same as TX); fCUT for RX */ + at86rf215_reg_write(dev, dev->RF->RG_RXDFE, + _TXDFE_SR(option) | _RXDFE_RCUT(option, superGHz)); + /* Set Power Amplifier Ramp Time; fLPCUT */ + at86rf215_reg_write(dev, dev->RF->RG_TXCUTC, + RF_PARAMP8U | _TXCUTC_LPFCUT(option)); + /* Set fS; fCUT for TX */ + at86rf215_reg_write(dev, dev->RF->RG_TXDFE, + _TXDFE_SR(option) | _TXDFE_RCUT(option)); + + /* set channel spacing with 25 kHz resolution */ + at86rf215_reg_write(dev, dev->RF->RG_CS, _channel_spacing_kHz(option) / 25); + + /* set channel center frequency with 25 kHz resolution */ + if (superGHz) { + at86rf215_reg_write16(dev, dev->RF->RG_CCF0L, + 1 + _channel_center_freq_kHz_2400MHz(option) / 25); + } + else { + at86rf215_reg_write16(dev, dev->RF->RG_CCF0L, + 1 + _channel_center_freq_kHz_868MHz(option) / 25); + } + + at86rf215_reg_write(dev, dev->BBC->RG_OFDMC, option - 1); + + /* make sure channel config is still valid */ + dev->num_chans = _get_max_chan(dev, option); + dev->netdev.chan = at86rf215_chan_valid(dev, dev->netdev.chan); + at86rf215_reg_write16(dev, dev->RF->RG_CNL, dev->netdev.chan); +} + +static unsigned _get_frame_duration(uint8_t option, uint8_t scheme, + uint8_t bytes) +{ + /* Table 150 - phySymbolsPerOctet values for MR-OFDM PHY, IEEE 802.15.4g-2012 */ + static const uint8_t quot[] = { 3, 3, 6, 12, 18, 24, 36 }; + + --option; + /* phyMaxFrameDuration = phySHRDuration + phyPHRDuration + ceiling [(aMaxPHYPacketSize + 1) x phySymbolsPerOctet] */ + const unsigned phySHRDuration = 6; + const unsigned phyPHRDuration = option ? 6 : 3; + const unsigned phyPDUDuration = ((bytes + 1) * (1 << option) + quot[scheme] - 1) + / quot[scheme]; + + return (phySHRDuration + phyPHRDuration + phyPDUDuration) * OFDM_SYMBOL_TIME_US; +} + +static void _set_ack_timeout(at86rf215_t *dev, uint8_t option, uint8_t scheme) +{ + dev->ack_timeout_usec = dev->csma_backoff_period + + IEEE802154G_ATURNAROUNDTIME_US + + _get_frame_duration(option, scheme, + AT86RF215_ACK_PSDU_BYTES); + DEBUG("[%s] ACK timeout: %" PRIu32 " µs\n", "OFDM", dev->ack_timeout_usec); +} + +static bool _option_mcs_valid(uint8_t option, uint8_t mcs) +{ + if (option < 1 || option > 4) { + return false; + } + + if (mcs > BB_MCS_16QAM_3BY4) { + return false; + } + + if (mcs == BB_MCS_BPSK_REP4 && option > 2) { + return false; + } + + if (mcs == BB_MCS_BPSK_REP2 && option == 4) { + return false; + } + + return true; +} + +int at86rf215_configure_OFDM(at86rf215_t *dev, uint8_t option, uint8_t scheme) +{ + if (!_option_mcs_valid(option, scheme)) { + DEBUG("[%s] invalid option/MCS: %d | %d\n", __func__, option, scheme); + return -EINVAL; + } + + at86rf215_await_state_end(dev, RF_STATE_TX); + + /* disable radio */ + at86rf215_reg_write(dev, dev->BBC->RG_PC, 0); + + /* set receiver gain target according to data sheet */ + at86rf215_reg_write(dev, dev->RF->RG_AGCS, 3 << AGCS_TGT_SHIFT); + /* enable automatic receiver gain */ + at86rf215_reg_write(dev, dev->RF->RG_AGCC, AGCC_EN_MASK); + + _set_option(dev, option); + + at86rf215_reg_write(dev, dev->BBC->RG_OFDMPHRTX, scheme); + + dev->csma_backoff_period = AT86RF215_BACKOFF_PERIOD_IN_SYMBOLS * + OFDM_SYMBOL_TIME_US; + DEBUG("[%s] CSMA BACKOFF: %" PRIu32 " µs\n", "OFDM", + dev->csma_backoff_period); + + _set_ack_timeout(dev, option, scheme); + + at86rf215_enable_radio(dev, BB_MROFDM); + + return 0; +} + +int at86rf215_OFDM_set_scheme(at86rf215_t *dev, uint8_t scheme) +{ + uint8_t option = at86rf215_OFDM_get_option(dev); + + if (!_option_mcs_valid(option, scheme)) { + DEBUG("[%s] invalid MCS: %d\n", __func__, scheme); + return -1; + } + + at86rf215_await_state_end(dev, RF_STATE_TX); + + at86rf215_reg_write(dev, dev->BBC->RG_OFDMPHRTX, scheme); + _set_ack_timeout(dev, at86rf215_OFDM_get_option(dev), scheme); + + return 0; +} + +uint8_t at86rf215_OFDM_get_scheme(at86rf215_t *dev) +{ + return at86rf215_reg_read(dev, dev->BBC->RG_OFDMPHRTX) & OFDMPHRTX_MCS_MASK; +} + +int at86rf215_OFDM_set_option(at86rf215_t *dev, uint8_t option) +{ + uint8_t mcs = at86rf215_OFDM_get_scheme(dev); + + if (!_option_mcs_valid(option, mcs)) { + DEBUG("[%s] invalid option: %d\n", __func__, option); + return -1; + } + + at86rf215_await_state_end(dev, RF_STATE_TX); + + _set_option(dev, option); + _set_ack_timeout(dev, option, mcs); + + return 0; +} + +uint8_t at86rf215_OFDM_get_option(at86rf215_t *dev) +{ + return 1 + (at86rf215_reg_read(dev, dev->BBC->RG_OFDMC) & OFDMC_OPT_MASK); +} diff --git a/drivers/at86rf215/include/at86rf215_internal.h b/drivers/at86rf215/include/at86rf215_internal.h index 514f63f439..1d47f3a855 100644 --- a/drivers/at86rf215/include/at86rf215_internal.h +++ b/drivers/at86rf215/include/at86rf215_internal.h @@ -137,6 +137,64 @@ void at86rf215_filter_ack(at86rf215_t *dev, bool on); */ void at86rf215_get_random(at86rf215_t *dev, void *data, size_t len); +/** + * @brief Configure the radio to make use of OFDM modulation. + * There are 4 OFDM options, each with a different number of active tones. + * The device supports BPSK, QPSK and 16-QAM modulation and coding schemes (MCS) + * + * @param[in] dev device to configure + * @param[in] option modulation option, each increment halves the data rate + * @param[in] mcs modulation scheme, `BB_MCS_BPSK_REP4` … `BB_MCS_16QAM_3BY4` + * + * @return 0 on success, error otherwise + */ +int at86rf215_configure_OFDM(at86rf215_t *dev, uint8_t option, uint8_t mcs); + +/** + * @brief Set the current modulation and coding scheme (MCS) + * + * @param[in] dev device to configure + * @param[in] mcs modulation and coding scheme + * + * @return 0 on success, error otherwise + */ +int at86rf215_OFDM_set_scheme(at86rf215_t *dev, uint8_t mcs); + +/** + * @brief Get the current modulation and coding scheme (MCS) + * + * @param[in] dev device to read from + * + * @return the current modulation & coding scheme + */ +uint8_t at86rf215_OFDM_get_scheme(at86rf215_t *dev); + +/** + * @brief Set the current OFDM option + * + * @param[in] dev device to configure + * @param[in] option OFDM option + * + * @return 0 on success, error otherwise + */ +int at86rf215_OFDM_set_option(at86rf215_t *dev, uint8_t option); + +/** + * @brief Get the current OFDM option + * + * @param[in] dev device to read from + * + * @return the current OFDM option + */ +uint8_t at86rf215_OFDM_get_option(at86rf215_t *dev); + +/** @} */ + +/** + * @defgroup drivers_at86rf215_oqpsk AT86RF215 MR-O-QPSK PHY + * @{ + */ + /** * @brief Configure the radio to make use of O-QPSK modulation. * The rate mode may be diff --git a/drivers/include/at86rf215.h b/drivers/include/at86rf215.h index 786e9fb17a..fec7f0d76e 100644 --- a/drivers/include/at86rf215.h +++ b/drivers/include/at86rf215.h @@ -137,6 +137,24 @@ enum { #endif /** @} */ +/** + * @name Default MR-OFDM Option + * @{ + */ +#ifndef CONFIG_AT86RF215_DEFAULT_MR_OFDM_OPT +#define CONFIG_AT86RF215_DEFAULT_MR_OFDM_OPT (2) +#endif +/** @} */ + +/** + * @name Default MR-OFDM Modulation & Coding Scheme + * @{ + */ +#ifndef CONFIG_AT86RF215_DEFAULT_MR_OFDM_MCS +#define CONFIG_AT86RF215_DEFAULT_MR_OFDM_MCS (2) +#endif +/** @} */ + /** * @brief Default TX power (0dBm) */