From a92b577bc1e85072f8b5e00c198e4583671fe8ae Mon Sep 17 00:00:00 2001 From: Hauke Petersen <hauke.petersen@fu-berlin.de> Date: Thu, 5 Apr 2018 10:58:52 +0200 Subject: [PATCH] cpu/nrf5x: added nrfble radio driver --- cpu/nrf5x_common/Makefile | 3 + cpu/nrf5x_common/include/nrfble.h | 55 ++++ cpu/nrf5x_common/radio/nrfble/Makefile | 3 + cpu/nrf5x_common/radio/nrfble/nrfble.c | 360 +++++++++++++++++++++++++ 4 files changed, 421 insertions(+) create mode 100644 cpu/nrf5x_common/include/nrfble.h create mode 100644 cpu/nrf5x_common/radio/nrfble/Makefile create mode 100644 cpu/nrf5x_common/radio/nrfble/nrfble.c diff --git a/cpu/nrf5x_common/Makefile b/cpu/nrf5x_common/Makefile index ef599b387d..41f24c0a2d 100644 --- a/cpu/nrf5x_common/Makefile +++ b/cpu/nrf5x_common/Makefile @@ -2,6 +2,9 @@ MODULE = cpu_common DIRS = periph # build one of the radio drivers, if enabled +ifneq (,$(filter nrfble,$(USEMODULE))) + DIRS += radio/nrfble +endif ifneq (,$(filter nrfmin,$(USEMODULE))) DIRS += radio/nrfmin endif diff --git a/cpu/nrf5x_common/include/nrfble.h b/cpu/nrf5x_common/include/nrfble.h new file mode 100644 index 0000000000..9c41b3ee44 --- /dev/null +++ b/cpu/nrf5x_common/include/nrfble.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017-2018 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_nrf5x_nrfble NRF BLE radio driver + * @ingroup drivers_netdev + * @brief Radio driver for nRF5x SoCs for using the radio in BLE mode + * @{ + * + * @file + * @brief Interface definition for the nrfble radio driver + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef NRFBLE_H +#define NRFBLE_H + +#include "net/netdev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief nrfble channel configuration + * @{ + */ +#define NRFBLE_CHAN_MIN (0U) +#define NRFBLE_CHAN_MAX (39U) +/** @} */ + +/** + * @brief Default transmission power used + */ +#define NRFBLE_TXPOWER_DEFAULT (0) /* 0dBm */ + +/** + * @brief Setup the device driver's data structures + * + * @return pointer to the device's netdev struct + */ +netdev_t *nrfble_setup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NRFBLE_H */ +/** @} */ diff --git a/cpu/nrf5x_common/radio/nrfble/Makefile b/cpu/nrf5x_common/radio/nrfble/Makefile new file mode 100644 index 0000000000..1f6a482e4f --- /dev/null +++ b/cpu/nrf5x_common/radio/nrfble/Makefile @@ -0,0 +1,3 @@ +MODULE = nrfble + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/nrf5x_common/radio/nrfble/nrfble.c b/cpu/nrf5x_common/radio/nrfble/nrfble.c new file mode 100644 index 0000000000..dd500cbde3 --- /dev/null +++ b/cpu/nrf5x_common/radio/nrfble/nrfble.c @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2017-2018 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. + */ + +/** + * @ingroup drivers_nrf5x_nrfble + * + * @note This driver is not thread safe and should only be used by a + * single thread at once! + * @{ + * + * @file + * @brief Bluetooth low energy radio driver for nRF5x SoCs + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <errno.h> + +#include "cpu.h" +#include "assert.h" + +#include "nrfble.h" +#include "net/netdev/ble.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* driver specific device configuration */ +#define CONF_MODE RADIO_MODE_MODE_Ble_1Mbit +#define CONF_LEN (8U) +#define CONF_S0 (1U) +#define CONF_S1 (0U) +#define CONF_STATLEN (0U) +#define CONF_BASE_ADDR_LEN (3U) +#define CONF_ENDIAN RADIO_PCNF1_ENDIAN_Little +#define CONF_WHITENING RADIO_PCNF1_WHITEEN_Enabled +#define CONF_TIFS (150U) +#define CONF_CRC_LEN (0X3 | RADIO_CRCCNF_SKIPADDR_Msk) +#define CONF_CRC_POLY (0x00065b) + +/* used shortcuts */ +#define SHORTS_BASE (RADIO_SHORTS_READY_START_Msk | \ + RADIO_SHORTS_END_DISABLE_Msk) +#define SHORTS_RX (SHORTS_BASE | RADIO_SHORTS_DISABLED_TXEN_Msk) +#define SHORTS_TX (SHORTS_BASE | RADIO_SHORTS_DISABLED_RXEN_Msk) + +/* interrupt masks */ +#define INT_DIS (RADIO_INTENCLR_DISABLED_Msk | \ + RADIO_INTENCLR_ADDRESS_Msk) +#define INT_EN (RADIO_INTENSET_DISABLED_Msk | \ + RADIO_INTENSET_ADDRESS_Msk) + +/* driver internal radio states */ +#define STATE_IDLE (0x00) +#define STATE_RX (0x01) +#define STATE_TX (0x02) +#define STATE_BUSY (0x80) + +/* forward declaration of the netdev driver struct */ +static const netdev_driver_t netdev_driver; + +/* current radio state */ +static volatile uint8_t _state = STATE_IDLE; + +/* active radio context */ +static netdev_ble_ctx_t *_ctx = NULL; + +/* allocate the netdev device descriptor */ +netdev_t _nrfble_dev; + +/* map logical BLE channel values to actual radio frequencies */ +static const uint8_t _ble_chan_map[40] = { + [ 0] = 4, + [ 1] = 6, + [ 2] = 8, + [ 3] = 10, + [ 4] = 12, + [ 5] = 14, + [ 6] = 16, + [ 7] = 18, + [ 8] = 20, + [ 9] = 22, + [10] = 24, + [11] = 28, + [12] = 30, + [13] = 32, + [14] = 34, + [15] = 36, + [16] = 38, + [17] = 40, + [18] = 42, + [19] = 44, + [20] = 46, + [21] = 48, + [22] = 50, + [23] = 52, + [24] = 54, + [25] = 56, + [26] = 58, + [27] = 60, + [28] = 62, + [29] = 64, + [30] = 66, + [31] = 68, + [32] = 70, + [33] = 72, + [34] = 74, + [35] = 76, + [36] = 78, + [37] = 2, + [38] = 26, + [39] = 80, +}; + +/** + * @brief Set radio into idle (DISABLED) state + */ +static void _go_idle(void) +{ + if (!(_state & STATE_BUSY)) { + NRF_RADIO->INTENCLR = INT_DIS; + NRF_RADIO->SHORTS = 0; + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + while (NRF_RADIO->EVENTS_DISABLED == 0) {} + _state = STATE_IDLE; + } +} + +/** + * @brief Set radio context (channel, CRC, whitening, and access address) + */ +static void _set_context(netdev_ble_ctx_t *ctx) +{ + if (ctx) { + assert(ctx->chan <= NRFBLE_CHAN_MAX); + + _ctx = ctx; + NRF_RADIO->FREQUENCY = _ble_chan_map[ctx->chan]; + NRF_RADIO->PREFIX0 = ctx->aa.raw[3]; + NRF_RADIO->BASE0 = (uint32_t)((ctx->aa.raw[0] << 8) | + (ctx->aa.raw[1] << 16) | + (ctx->aa.raw[2] << 24)); + NRF_RADIO->DATAWHITEIV = ctx->chan; + NRF_RADIO->CRCINIT = (ctx->crc & NETDEV_BLE_CRC_MASK); + } + else { + _go_idle(); + } +} + +static int16_t _nrfble_get_txpower(void) +{ + int8_t p = (int8_t)NRF_RADIO->TXPOWER; + if (p < 0) { + return (int16_t)(0xff00 | p); + } + return (int16_t)p; +} + +static void _nrfble_set_txpower(int16_t power) +{ + if (power > 2) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos4dBm; + } + else if (power > -2) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_0dBm; + } + else if (power > -6) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg4dBm; + } + else if (power > -10) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg8dBm; + } + else if (power > -14) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg12dBm; + } + else if (power > -18) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg16dBm; + } + else if (power > -25) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg20dBm; + } + else { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg30dBm; + } +} + +/** + * @brief Radio interrupt routine + */ +void isr_radio(void) +{ + if (NRF_RADIO->EVENTS_ADDRESS) { + NRF_RADIO->EVENTS_ADDRESS = 0; + _state |= STATE_BUSY; + } + + if (NRF_RADIO->EVENTS_DISABLED) { + NRF_RADIO->EVENTS_DISABLED = 0; + + if (_state == (STATE_BUSY | STATE_RX)) { + _state = STATE_TX; + if (NRF_RADIO->CRCSTATUS) { + _ctx->crc |= NETDEV_BLE_CRC_OK; + } + else { + _ctx->crc &= ~(NETDEV_BLE_CRC_OK); + } + _nrfble_dev.event_callback(&_nrfble_dev, NETDEV_EVENT_RX_COMPLETE); + } + else { /* on TX done */ + _state = STATE_RX; + _nrfble_dev.event_callback(&_nrfble_dev, NETDEV_EVENT_TX_COMPLETE); + } + } + + cortexm_isr_end(); +} + +netdev_t *nrfble_setup(void) +{ + _nrfble_dev.driver = &netdev_driver; + _nrfble_dev.event_callback = NULL; + _nrfble_dev.context = NULL; +#ifdef MODULE_NETSTATS_L2 + memset(&_nrfble_dev.stats, 0, sizeof(netstats_t));; +#endif + return &_nrfble_dev; +} + +static int _nrfble_init(netdev_t *dev) +{ + (void)dev; + assert(_nrfble_dev.driver && _nrfble_dev.event_callback); + + /* power on the NRFs radio */ + NRF_RADIO->POWER = 1; + /* configure variable parameters to default values */ + NRF_RADIO->TXPOWER = NRFBLE_TXPOWER_DEFAULT; + /* always send from and listen to logical address 0 */ + NRF_RADIO->TXADDRESS = 0x00UL; + NRF_RADIO->RXADDRESSES = 0x01UL; + /* load driver specific configuration */ + NRF_RADIO->MODE = CONF_MODE; + /* configure data fields and packet length whitening and endianess */ + NRF_RADIO->PCNF0 = ((CONF_S1 << RADIO_PCNF0_S1LEN_Pos) | + (CONF_S0 << RADIO_PCNF0_S0LEN_Pos) | + (CONF_LEN << RADIO_PCNF0_LFLEN_Pos)); + NRF_RADIO->PCNF1 = ((CONF_WHITENING << RADIO_PCNF1_WHITEEN_Pos) | + (CONF_ENDIAN << RADIO_PCNF1_ENDIAN_Pos) | + (CONF_BASE_ADDR_LEN << RADIO_PCNF1_BALEN_Pos) | + (CONF_STATLEN << RADIO_PCNF1_STATLEN_Pos) | + (NETDEV_BLE_PDU_MAXLEN << RADIO_PCNF1_MAXLEN_Pos)); + /* set inter frame spacing to 150us */ + NRF_RADIO->TIFS = CONF_TIFS; + /* configure CRC length and polynomial */ + NRF_RADIO->CRCCNF = CONF_CRC_LEN; + NRF_RADIO->CRCPOLY = CONF_CRC_POLY; + /* enable global interrupts, but mask all local interrupts for now */ + NRF_RADIO->INTENCLR = 0xffffffff; + NVIC_EnableIRQ(RADIO_IRQn); + + DEBUG("[nrfble] initialization successful\n"); + return 0; +} + +static int _nrfble_send(netdev_t *dev, const iolist_t *data) +{ + (void)dev; + assert(data); + + NRF_RADIO->PACKETPTR = (uint32_t)data->iol_base; + NRF_RADIO->SHORTS = SHORTS_TX; + + /* in case no trx sequence is active, we start a new one now */ + if (_state == STATE_IDLE) { + _state = STATE_TX; + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->INTENSET = INT_EN; + NRF_RADIO->TASKS_TXEN = 1; + } + + return 0; +} + +static int _nrfble_recv(netdev_t *dev, void *buf, size_t len, void *info) +{ + (void)dev; + (void)len; + (void)info; + assert(buf && (len == sizeof(netdev_ble_pkt_t))); + + NRF_RADIO->PACKETPTR = (uint32_t)buf; + NRF_RADIO->SHORTS = SHORTS_RX; + + /* in case no trx sequence is active, we start a new one now */ + if (_state == STATE_IDLE) { + _state = STATE_RX; + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->INTENSET = INT_EN; + NRF_RADIO->TASKS_RXEN = 1; + } + + return 0; +} + +static int _nrfble_get(netdev_t *dev, netopt_t opt, void *val, size_t max_len) +{ + (void)dev; + (void)max_len; + + switch (opt) { + case NETOPT_TX_POWER: + assert(max_len >= sizeof(int16_t)); + *((int16_t *)val) = _nrfble_get_txpower(); + return sizeof(int16_t); + case NETOPT_DEVICE_TYPE: + assert(max_len >= sizeof(uint16_t)); + *((uint16_t *)val) = NETDEV_TYPE_BLE; + return sizeof(uint16_t); + default: + return -ENOTSUP; + } +} + +static int _nrfble_set(netdev_t *dev, netopt_t opt, const void *val, size_t len) +{ + (void)dev; + (void)len; + + switch (opt) { + case NETOPT_BLE_CTX: + _set_context((netdev_ble_ctx_t *)val); + return sizeof(netdev_ble_ctx_t); + case NETOPT_TX_POWER: + assert(len == sizeof(int16_t)); + _nrfble_set_txpower(*((const int16_t *)val)); + return sizeof(int16_t); + default: + return -ENOTSUP; + } +} + +/* export of the netdev interface */ +static const netdev_driver_t netdev_driver = { + .send = _nrfble_send, + .recv = _nrfble_recv, + .init = _nrfble_init, + .isr = NULL, + .get = _nrfble_get, + .set = _nrfble_set +}; -- GitLab