From 91787dcb5cd52809ab8fb6e9947fc0a0a6084865 Mon Sep 17 00:00:00 2001 From: Semjon Kerner <semjon.kerner@fu-berlin.de> Date: Mon, 4 Mar 2019 13:18:47 +0100 Subject: [PATCH] cpu/nrf52: add 802.15.4 radio driver --- cpu/nrf52/Makefile | 5 + cpu/nrf52/Makefile.dep | 6 + cpu/nrf52/Makefile.include | 4 + cpu/nrf52/include/nrf802154.h | 61 ++++ cpu/nrf52/radio/nrf802154/Makefile | 3 + cpu/nrf52/radio/nrf802154/nrf802154.c | 446 ++++++++++++++++++++++++++ 6 files changed, 525 insertions(+) create mode 100644 cpu/nrf52/Makefile.dep create mode 100644 cpu/nrf52/include/nrf802154.h create mode 100644 cpu/nrf52/radio/nrf802154/Makefile create mode 100644 cpu/nrf52/radio/nrf802154/nrf802154.c diff --git a/cpu/nrf52/Makefile b/cpu/nrf52/Makefile index 7600b58931..59c4f48c9b 100644 --- a/cpu/nrf52/Makefile +++ b/cpu/nrf52/Makefile @@ -7,4 +7,9 @@ DIRS = periph $(RIOTCPU)/cortexm_common $(RIOTCPU)/nrf5x_common # (file triggers compiler bug. see #5775) SRC_NOLTO += vectors.c +# build the nrf802154 driver if selected +ifneq (,$(filter nrf802154,$(USEMODULE))) + DIRS += radio/nrf802154 +endif + include $(RIOTBASE)/Makefile.base diff --git a/cpu/nrf52/Makefile.dep b/cpu/nrf52/Makefile.dep new file mode 100644 index 0000000000..d5e4d42993 --- /dev/null +++ b/cpu/nrf52/Makefile.dep @@ -0,0 +1,6 @@ +ifneq (,$(filter nrf802154,$(USEMODULE))) + FEATURES_REQUIRED += periph_timer + FEATURES_REQUIRED += radio_nrf802154 + USEMODULE += luid + USEMODULE += netdev_ieee802154 +endif diff --git a/cpu/nrf52/Makefile.include b/cpu/nrf52/Makefile.include index d6e26d6147..03647d823b 100644 --- a/cpu/nrf52/Makefile.include +++ b/cpu/nrf52/Makefile.include @@ -7,5 +7,9 @@ export MCUBOOT_SLOT0_SIZE = 0x8000 export MCUBOOT_SLOT1_SIZE = 0x3C000 export MCUBOOT_SLOT2_SIZE = 0x3C000 +ifneq (,$(filter nrf802154,$(USEMODULE))) + CFLAGS += -DGNRC_NETIF_MSG_QUEUE_SIZE=16 +endif + include $(RIOTCPU)/nrf5x_common/Makefile.include include $(RIOTMAKE)/arch/cortexm.inc.mk diff --git a/cpu/nrf52/include/nrf802154.h b/cpu/nrf52/include/nrf802154.h new file mode 100644 index 0000000000..de55018e77 --- /dev/null +++ b/cpu/nrf52/include/nrf802154.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * 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. + */ + +/** + * @defgroup drivers_nrf52_802154 IEEE802.15.4 Driver for nRF52840 SoCs + * @ingroup drivers_netdev + * @brief Driver for using the nRF52's radio in IEEE802.15.4 mode + * + * ## Implementation state ## + * Netdev events supported: + * + * - NETDEV_EVENT_RX_COMPLETE + * - NETDEV_EVENT_TX_COMPLETE + * + * Transmission options not yet impemented: + * - Send acknowledgement for packages + * - Request acknowledgement + * - Retransmit unacked packages + * - Carrier Sense Multiple Access (CSMA) and Implementation of Clear Channel + * Assessment Control (CCACTRL) + * + * @{ + * + * @file + * @brief Driver interface for using the nRF52 in IEEE802.15.4 mode + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * @author Semjon Kerner <semjon.kerner@fu-berlin.de> + */ + +#ifndef NRF802154_H +#define NRF802154_H + +#include "net/netdev/ieee802154.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Export the netdev device descriptor + */ +extern netdev_ieee802154_t nrf802154_dev; + +/** + * @brief IEEE 802.15.4 radio timer configuration + * + * this radio relies on a dedicated hardware timer to maintain IFS + * the default timer may be overwritten in the board configuration + */ +#ifndef NRF802154_TIMER +#define NRF802154_TIMER TIMER_DEV(1) +#endif + +#endif /* NRF802154_H */ +/** @} */ diff --git a/cpu/nrf52/radio/nrf802154/Makefile b/cpu/nrf52/radio/nrf802154/Makefile new file mode 100644 index 0000000000..b2dbcf3dfe --- /dev/null +++ b/cpu/nrf52/radio/nrf802154/Makefile @@ -0,0 +1,3 @@ +MODULE = nrf802154 + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/nrf52/radio/nrf802154/nrf802154.c b/cpu/nrf52/radio/nrf802154/nrf802154.c new file mode 100644 index 0000000000..b69499a84c --- /dev/null +++ b/cpu/nrf52/radio/nrf802154/nrf802154.c @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * 2019 HAW Hamburg + * + * 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_nrf52_802154 + * @{ + * + * @file + * @brief Implementation of the radio driver for nRF52 radios + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * @author Dimitri Nahm <dimitri.nahm@haw-hamburg.de> + * @author Semjon Kerner <semjon.kerner@fu-berlin.de> + * @} + */ + +#include <string.h> +#include <errno.h> + +#include "cpu.h" +#include "luid.h" +#include "mutex.h" + +#include "net/ieee802154.h" +#include "periph/timer.h" +#include "net/netdev/ieee802154.h" +#include "nrf802154.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static const netdev_driver_t nrf802154_netdev_driver; + +netdev_ieee802154_t nrf802154_dev = { + { + .driver = &nrf802154_netdev_driver, + .event_callback = NULL, + .context = NULL, + }, +#ifdef MODULE_GNRC +#ifdef MODULE_GNRC_SIXLOWPAN + .proto = GNRC_NETTYPE_SIXLOWPAN, +#else + .proto = GNRC_NETTYPE_UNDEF, +#endif +#endif + .pan = IEEE802154_DEFAULT_PANID, + .short_addr = { 0, 0 }, + .long_addr = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .chan = IEEE802154_DEFAULT_CHANNEL, + .flags = 0 +}; + +static uint8_t rxbuf[IEEE802154_FRAME_LEN_MAX + 3]; /* len PHR + PSDU + LQI */ +static uint8_t txbuf[IEEE802154_FRAME_LEN_MAX + 3]; /* len PHR + PSDU + LQI */ + +#define RX_COMPLETE (0x1) +#define TX_COMPLETE (0x2) +#define LIFS (40U) +#define SIFS (12U) +#define SIFS_MAXPKTSIZE (18U) +#define TIMER_FREQ (250000UL) +static volatile uint8_t _state; +static mutex_t _txlock; + +/** + * @brief Set radio into DISABLED state + */ +static void _disable(void) +{ + /* set device into DISABLED state */ + if (NRF_RADIO->STATE != RADIO_STATE_STATE_Disabled) { + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + while (!(NRF_RADIO->EVENTS_DISABLED)) {}; + DEBUG("[nrf802154] Device state: DISABLED\n"); + } +} + +/** + * @brief Set radio into RXIDLE state + */ +static void _enable_rx(void) +{ + DEBUG("[nrf802154] Set device state to RXIDLE\n"); + /* set device into RXIDLE state */ + if (NRF_RADIO->STATE != RADIO_STATE_STATE_RxIdle) { + _disable(); + } + NRF_RADIO->PACKETPTR = (uint32_t)rxbuf; + NRF_RADIO->EVENTS_RXREADY = 0; + NRF_RADIO->TASKS_RXEN = 1; + while (!(NRF_RADIO->EVENTS_RXREADY)) {}; + DEBUG("[nrf802154] Device state: RXIDLE\n"); +} + +/** + * @brief Set radio into TXIDLE state + */ +static void _enable_tx(void) +{ + DEBUG("[nrf802154] Set device state to TXIDLE\n"); + /* set device into TXIDLE state */ + if (NRF_RADIO->STATE != RADIO_STATE_STATE_TxIdle) { + _disable(); + } + NRF_RADIO->PACKETPTR = (uint32_t)txbuf; + NRF_RADIO->EVENTS_TXREADY = 0; + NRF_RADIO->TASKS_TXEN = 1; + while (!(NRF_RADIO->EVENTS_TXREADY)) {}; + DEBUG("[nrf802154] Device state: TXIDLE\n"); +} + +/** + * @brief Reset the RXIDLE state + */ + static void _reset_rx(void) + { + if (NRF_RADIO->STATE != RADIO_STATE_STATE_RxIdle) { + return; + } + + /* reset RX state and listen for new packets */ + _state &= ~RX_COMPLETE; + NRF_RADIO->TASKS_START = 1; + } + +static void _set_chan(uint16_t chan) +{ + assert((chan >= IEEE802154_CHANNEL_MIN) && (chan <= IEEE802154_CHANNEL_MAX)); + /* Channel map between 2400 MHZ ... 2500 MHz + * -> Frequency = 2400 + FREQUENCY (MHz) */ + NRF_RADIO->FREQUENCY = (chan - 10) * 5; + nrf802154_dev.chan = chan; +} + +static int16_t _get_txpower(void) +{ + int8_t txpower = (int8_t)NRF_RADIO->TXPOWER; + if (txpower < 0) { + return (int16_t)(0xff00 | txpower); + } + return (int16_t)txpower; +} + +static void _set_txpower(int16_t txpower) +{ + if (txpower > 8) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos8dBm; + } + if (txpower > 1) { + NRF_RADIO->TXPOWER = (uint32_t)txpower; + } + else if (txpower > -1) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_0dBm; + } + else if (txpower > -5) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg4dBm; + } + else if (txpower > -9) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg8dBm; + } + else if (txpower > -13) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg12dBm; + } + else if (txpower > -17) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg16dBm; + } + else if (txpower > -21) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg20dBm; + } + else { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg40dBm; + } +} + +static void _timer_cb(void *arg, int chan) +{ + (void)arg; + (void)chan; + mutex_unlock(&_txlock); + timer_stop(NRF802154_TIMER); + timer_clear(NRF802154_TIMER, 0); +} + +static int _init(netdev_t *dev) +{ + (void)dev; + + int result = timer_init(NRF802154_TIMER, TIMER_FREQ, _timer_cb, NULL); + assert(result >= 0); + (void)result; + + /* initialize local variables */ + mutex_init(&_txlock); + + /* reset buffer */ + rxbuf[0] = 0; + txbuf[0] = 0; + _state = 0; + + /* power on peripheral */ + NRF_RADIO->POWER = 1; + + /* make sure the radio is disabled/stopped */ + _disable(); + + /* we configure it to run in IEEE802.15.4 mode */ + NRF_RADIO->MODE = RADIO_MODE_MODE_Ieee802154_250Kbit; + /* and set some fitting configuration */ + NRF_RADIO->PCNF0 = ((8 << RADIO_PCNF0_LFLEN_Pos) | + (RADIO_PCNF0_PLEN_32bitZero << RADIO_PCNF0_PLEN_Pos) | + (RADIO_PCNF0_CRCINC_Include << RADIO_PCNF0_CRCINC_Pos)); + NRF_RADIO->PCNF1 = IEEE802154_FRAME_LEN_MAX; + /* set start frame delimiter */ + NRF_RADIO->SFD = IEEE802154_SFD; + /* set MHR filters */ + NRF_RADIO->MHRMATCHCONF = 0; /* Search Pattern Configuration */ + NRF_RADIO->MHRMATCHMAS = 0xff0007ff; /* Pattern mask */ + /* configure CRC conform to IEEE802154 */ + NRF_RADIO->CRCCNF = ((RADIO_CRCCNF_LEN_Two << RADIO_CRCCNF_LEN_Pos) | + (RADIO_CRCCNF_SKIPADDR_Ieee802154 << RADIO_CRCCNF_SKIPADDR_Pos)); + NRF_RADIO->CRCPOLY = 0x011021; + NRF_RADIO->CRCINIT = 0; + + /* assign default addresses */ + luid_get(nrf802154_dev.short_addr, IEEE802154_SHORT_ADDRESS_LEN); + luid_get(nrf802154_dev.long_addr, IEEE802154_LONG_ADDRESS_LEN); + + /* set default channel */ + _set_chan(nrf802154_dev.chan); + + /* configure some shortcuts */ + NRF_RADIO->SHORTS = RADIO_SHORTS_RXREADY_START_Msk | RADIO_SHORTS_TXREADY_START_Msk; + + /* enable interrupts */ + NVIC_EnableIRQ(RADIO_IRQn); + NRF_RADIO->INTENSET = RADIO_INTENSET_END_Msk; + + /* switch to RX mode */ + _enable_rx(); + + return 0; +} + +static int _send(netdev_t *dev, const iolist_t *iolist) +{ + (void)dev; + + DEBUG("[nrf802154] Send a packet\n"); + + assert(iolist); + + mutex_lock(&_txlock); + + /* copy packet data into the transmit buffer */ + unsigned int len = 0; + for (; iolist; iolist = iolist->iol_next) { + if ((IEEE802154_FCS_LEN + len + iolist->iol_len) > (IEEE802154_FRAME_LEN_MAX)) { + DEBUG("[nrf802154] send: unable to do so, packet is too large!\n"); + mutex_unlock(&_txlock); + return -EOVERFLOW; + } + memcpy(&txbuf[len + 1], iolist->iol_base, iolist->iol_len); + len += iolist->iol_len; + } + + /* specify the length of the package. */ + txbuf[0] = len + IEEE802154_FCS_LEN; + + /* trigger the actual transmission */ + _enable_tx(); + DEBUG("[nrf802154] send: putting %i byte into the ether\n", len); + + /* set interframe spacing based on packet size */ + unsigned int ifs = (len > SIFS_MAXPKTSIZE) ? LIFS : SIFS; + timer_set_absolute(NRF802154_TIMER, 0, ifs); + + return len; +} + +static int _recv(netdev_t *dev, void *buf, size_t len, void *info) +{ + (void)dev; + (void)info; + + size_t pktlen = (size_t)rxbuf[0] - IEEE802154_FCS_LEN; + + /* check if packet data is readable */ + if (!(_state & RX_COMPLETE)) { + DEBUG("[nrf802154] recv: no packet data available\n"); + return 0; + } + + if (buf == NULL) { + if (len > 0) { + /* drop packet */ + DEBUG("[nrf802154] recv: dropping packet of length %i\n", pktlen); + } + else { + /* return packet length */ + DEBUG("[nrf802154] recv: return packet length: %i\n", pktlen); + return pktlen; + } + } + else if (len < pktlen) { + DEBUG("[nrf802154] recv: buffer is to small\n"); + return -ENOBUFS; + } + else { + DEBUG("[nrf802154] recv: reading packet of length %i\n", pktlen); + memcpy(buf, &rxbuf[1], pktlen); + } + + _reset_rx(); + + return (int)pktlen; +} + +static void _isr(netdev_t *dev) +{ + if (!nrf802154_dev.netdev.event_callback) { + return; + } + if (_state & RX_COMPLETE) { + nrf802154_dev.netdev.event_callback(dev, NETDEV_EVENT_RX_COMPLETE); + } + if (_state & TX_COMPLETE) { + nrf802154_dev.netdev.event_callback(dev, NETDEV_EVENT_TX_COMPLETE); + _state &= ~TX_COMPLETE; + } +} + +static int _get(netdev_t *dev, netopt_t opt, void *value, size_t max_len) +{ + assert(dev); + +#ifdef MODULE_NETOPT + DEBUG("[nrf802154] get: %s\n", netopt2str(opt)); +#else + DEBUG("[nrf802154] get: %d\n", opt); +#endif + + switch (opt) { + case NETOPT_CHANNEL: + assert(max_len >= sizeof(uint16_t)); + *((uint16_t *)value) = nrf802154_dev.chan; + return sizeof(uint16_t); + case NETOPT_TX_POWER: + assert(max_len >= sizeof(int16_t)); + *((int16_t *)value) = _get_txpower(); + return sizeof(int16_t); + + default: + return netdev_ieee802154_get((netdev_ieee802154_t *)dev, + opt, value, max_len); + } +} + +static int _set(netdev_t *dev, netopt_t opt, + const void *value, size_t value_len) +{ + assert(dev); + +#ifdef MODULE_NETOPT + DEBUG("[nrf802154] set: %s\n", netopt2str(opt)); +#else + DEBUG("[nrf802154] set: %d\n", opt); +#endif + + switch (opt) { + case NETOPT_CHANNEL: + assert(value_len == sizeof(uint16_t)); + _set_chan(*((uint16_t *)value)); + return sizeof(uint16_t); + case NETOPT_TX_POWER: + assert(value_len == sizeof(int16_t)); + _set_txpower(*((int16_t *)value)); + return sizeof(int16_t); + + default: + return netdev_ieee802154_set((netdev_ieee802154_t *)dev, + opt, value, value_len); + } +} + +void isr_radio(void) +{ + /* Clear flag */ + if (NRF_RADIO->EVENTS_END) { + NRF_RADIO->EVENTS_END = 0; + + /* did we just send or receive something? */ + uint8_t state = (uint8_t)NRF_RADIO->STATE; + switch(state) { + case RADIO_STATE_STATE_RxIdle: + /* only process packet if event callback is set and CRC is valid */ + if ((nrf802154_dev.netdev.event_callback) && + (NRF_RADIO->CRCSTATUS == 1) && + (netdev_ieee802154_dst_filter(&nrf802154_dev, + &rxbuf[1]) == 0)) { + _state |= RX_COMPLETE; + } + else { + _reset_rx(); + } + break; + case RADIO_STATE_STATE_Tx: + case RADIO_STATE_STATE_TxIdle: + case RADIO_STATE_STATE_TxDisable: + timer_start(NRF802154_TIMER); + DEBUG("[nrf802154] TX state: %x\n", (uint8_t)NRF_RADIO->STATE); + _state |= TX_COMPLETE; + _enable_rx(); + break; + default: + DEBUG("[nrf802154] Unhandled state: %x\n", (uint8_t)NRF_RADIO->STATE); + } + if (_state) { + nrf802154_dev.netdev.event_callback(&nrf802154_dev.netdev, NETDEV_EVENT_ISR); + } + } + else { + DEBUG("[nrf802154] Unknown interrupt triggered\n"); + } + + cortexm_isr_end(); +} + +/** + * @brief Export of the netdev interface + */ +static const netdev_driver_t nrf802154_netdev_driver = { + .send = _send, + .recv = _recv, + .init = _init, + .isr = _isr, + .get = _get, + .set = _set +}; -- GitLab