You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

549 lines
15 KiB

/*
* Copyright (C) 2015-2017 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_nrfmin
* @{
*
* @file
* @brief Implementation of the nrfmin radio driver for nRF51 radios
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <string.h>
#include <errno.h>
#include "cpu.h"
#include "mutex.h"
#include "assert.h"
#include "periph_conf.h"
#include "periph/cpuid.h"
#include "nrfmin.h"
#include "net/netdev.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/**
* @brief Driver specific device configuration
* @{
*/
#define CONF_MODE RADIO_MODE_MODE_Nrf_1Mbit
#define CONF_LEN (8U)
#define CONF_S0 (0U)
#define CONF_S1 (0U)
#define CONF_STATLEN (0U)
#define CONF_BASE_ADDR_LEN (4U)
#define CONF_ENDIAN RADIO_PCNF1_ENDIAN_Big
#define CONF_WHITENING RADIO_PCNF1_WHITEEN_Disabled
#define CONF_CRC_LEN (2U)
#define CONF_CRC_POLY (0x11021)
#define CONF_CRC_INIT (0xf0f0f0)
/** @} */
/**
* @brief Driver specific address configuration
* @{
*/
#define CONF_ADDR_PREFIX0 (0xe7e7e7e7)
#define CONF_ADDR_BASE (0xe7e70000)
#define CONF_ADDR_BCAST (CONF_ADDR_BASE | NRFMIN_ADDR_BCAST)
/** @} */
/**
* @brief We define a pseudo NID for compliance to 6LoWPAN
*/
#define CONF_PSEUDO_NID (0xaffe)
/**
* @brief Driver specific (interrupt) events (not all of them used currently)
* @{
*/
#define ISR_EVENT_RX_START (0x0001)
#define ISR_EVENT_RX_DONE (0x0002)
#define ISR_EVENT_TX_START (0x0004)
#define ISR_EVENT_TX_DONE (0x0008)
#define ISR_EVENT_WRONG_CHKSUM (0x0010)
/** @} */
/**
* @brief Possible internal device states
*/
typedef enum {
STATE_OFF, /**< device is powered off */
STATE_IDLE, /**< device is in idle mode */
STATE_RX, /**< device is in receive mode */
STATE_TX, /**< device is transmitting data */
} state_t;
/**
* @brief Since there can only be 1 nrfmin device, we allocate it right here
*/
netdev_t nrfmin_dev;
/**
* @brief For faster lookup we remember our own 16-bit address
*/
static uint16_t my_addr;
/**
* @brief We need to keep track of the radio state in SW (-> PAN ID 20)
*
* See nRF51822 PAN ID 20: RADIO State Register is not functional.
*/
static volatile state_t state = STATE_OFF;
/**
* @brief We also remember the 'long-term' state, so we can resume after TX
*/
static volatile state_t target_state = STATE_OFF;
/**
* @brief When sending out data, the data needs to be in one continuous memory
* region. So we need to buffer outgoing data on the driver level.
*/
static nrfmin_pkt_t tx_buf;
/**
* @brief As the device is memory mapped, we need some space to save incoming
* data to.
*
* @todo Improve the RX buffering to at least use double buffering
*/
static nrfmin_pkt_t rx_buf;
/**
* @brief While we listen for incoming data, we lock the RX buffer
*/
static volatile uint8_t rx_lock = 0;
/**
* @brief Set radio into idle (DISABLED) state
*/
static void go_idle(void)
{
/* set device into basic disabled state */
NRF_RADIO->EVENTS_DISABLED = 0;
NRF_RADIO->TASKS_DISABLE = 1;
while (NRF_RADIO->EVENTS_DISABLED == 0) {}
/* also release any existing lock on the RX buffer */
rx_lock = 0;
state = STATE_IDLE;
}
/**
* @brief Set radio into the target state as defined by `target_state`
*
* Trick here is, that the driver can go back to it's previous state after a
* send operation, so it can differentiate if the driver was in DISABLED or in
* RX mode before the send process had started.
*/
static void goto_target_state(void)
{
go_idle();
if ((target_state == STATE_RX) && (rx_buf.pkt.hdr.len == 0)) {
/* set receive buffer and our own address */
rx_lock = 1;
NRF_RADIO->PACKETPTR = (uint32_t)(&rx_buf);
NRF_RADIO->BASE0 = (CONF_ADDR_BASE | my_addr);
/* goto RX mode */
NRF_RADIO->TASKS_RXEN = 1;
state = STATE_RX;
}
if (target_state == STATE_OFF) {
NRF_RADIO->POWER = 0;
state = STATE_OFF;
}
}
void nrfmin_setup(void)
{
nrfmin_dev.driver = &nrfmin_netdev;
nrfmin_dev.event_callback = NULL;
nrfmin_dev.context = NULL;
#ifdef MODULE_NETSTATS_L2
memset(&nrfmin_dev.stats, 0, sizeof(netstats_t));;
#endif
}
uint16_t nrfmin_get_addr(void)
{
return my_addr;
}
void nrfmin_get_pseudo_long_addr(uint16_t *addr)
{
for (int i = 0; i < 4; i++) {
addr[i] = my_addr;
}
}
void nrfmin_get_iid(uint16_t *iid)
{
iid[0] = 0;
iid[1] = 0xff00;
iid[2] = 0x00fe;
iid[3] = my_addr;
}
uint16_t nrfmin_get_channel(void)
{
return (uint16_t)(NRF_RADIO->FREQUENCY >> 2);
}
netopt_state_t nrfmin_get_state(void)
{
switch (state) {
case STATE_OFF: return NETOPT_STATE_OFF;
case STATE_IDLE: return NETOPT_STATE_SLEEP;
case STATE_RX: return NETOPT_STATE_IDLE;
case STATE_TX: return NETOPT_STATE_TX;
default: return NETOPT_STATE_RESET; /* should never show */
}
}
int16_t nrfmin_get_txpower(void)
{
int8_t p = (int8_t)NRF_RADIO->TXPOWER;
if (p < 0) {
return (int16_t)(0xff00 | p);
}
return (int16_t)p;
}
void nrfmin_set_addr(uint16_t addr)
{
my_addr = addr;
goto_target_state();
}
int nrfmin_set_channel(uint16_t chan)
{
if (chan > NRFMIN_CHAN_MAX) {
return -EOVERFLOW;
}
NRF_RADIO->FREQUENCY = (chan << 2);
goto_target_state();
return sizeof(uint16_t);
}
void nrfmin_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;
}
}
int nrfmin_set_state(netopt_state_t val)
{
/* make sure radio is turned on and no transmission is in progress */
NRF_RADIO->POWER = 1;
switch (val) {
case NETOPT_STATE_OFF:
target_state = STATE_OFF;
break;
case NETOPT_STATE_SLEEP:
target_state = STATE_IDLE;
break;
case NETOPT_STATE_IDLE:
target_state = STATE_RX;
break;
default:
return -ENOTSUP;
}
goto_target_state();
return sizeof(netopt_state_t);
}
/**
* @brief Radio interrupt routine
*/
void isr_radio(void)
{
if (NRF_RADIO->EVENTS_END == 1) {
NRF_RADIO->EVENTS_END = 0;
/* did we just send or receive something? */
if (state == STATE_RX) {
/* drop packet on invalid CRC */
if ((NRF_RADIO->CRCSTATUS != 1) || !(nrfmin_dev.event_callback)) {
rx_buf.pkt.hdr.len = 0;
NRF_RADIO->TASKS_START = 1;
return;
}
rx_lock = 0;
nrfmin_dev.event_callback(&nrfmin_dev, NETDEV_EVENT_ISR);
}
else if (state == STATE_TX) {
goto_target_state();
}
}
cortexm_isr_end();
}
static int nrfmin_send(netdev_t *dev, const struct iovec *vector, unsigned count)
{
(void)dev;
assert((vector != NULL) && (count > 0) && (state != STATE_OFF));
/* wait for any ongoing transmission to finish and go into idle state */
while (state == STATE_TX) {}
go_idle();
/* copy packet data into the transmit buffer */
int pos = 0;
for (unsigned i = 0; i < count; i++) {
if ((pos + vector[i].iov_len) > NRFMIN_PKT_MAX) {
DEBUG("[nrfmin] send: unable to do so, packet is too large!\n");
return -EOVERFLOW;
}
memcpy(&tx_buf.raw[pos], vector[i].iov_base, vector[i].iov_len);
pos += vector[i].iov_len;
}
/* set output buffer and destination address */
nrfmin_hdr_t *hdr = (nrfmin_hdr_t *)vector[0].iov_base;
NRF_RADIO->PACKETPTR = (uint32_t)(&tx_buf);
NRF_RADIO->BASE0 = (CONF_ADDR_BASE | hdr->dst_addr);
/* trigger the actual transmission */
DEBUG("[nrfmin] send: putting %i byte into the ether\n", (int)hdr->len);
state = STATE_TX;
NRF_RADIO->TASKS_TXEN = 1;
return (int)count;
}
static int nrfmin_recv(netdev_t *dev, void *buf, size_t len, void *info)
{
(void)dev;
(void)info;
assert(state != STATE_OFF);
int pktlen = (int)rx_buf.pkt.hdr.len;
/* check if packet data is readable */
if (rx_lock || (pktlen == 0)) {
DEBUG("[nrfmin] recv: no packet data available\n");
return 0;
}
if (buf == NULL) {
if (len > 0) {
/* drop packet */
DEBUG("[nrfmin] recv: dropping packet of length %i\n", pktlen);
rx_buf.pkt.hdr.len = 0;
goto_target_state();
}
}
else {
DEBUG("[nrfmin] recv: reading packet of length %i\n", pktlen);
pktlen = (len < pktlen) ? len : pktlen;
memcpy(buf, rx_buf.raw, pktlen);
rx_buf.pkt.hdr.len = 0;
goto_target_state();
}
return pktlen;
}
static int nrfmin_init(netdev_t *dev)
{
uint8_t cpuid[CPUID_LEN];
/* check given device descriptor */
assert(dev);
/* initialize our own address from the CPU ID */
my_addr = 0;
cpuid_get(cpuid);
for (int i = 0; i < CPUID_LEN; i++) {
my_addr ^= cpuid[i] << (8 * (i & 0x01));
}
/* power on the NRFs radio */
NRF_RADIO->POWER = 1;
/* load driver specific configuration */
NRF_RADIO->MODE = CONF_MODE;
/* configure variable parameters to default values */
NRF_RADIO->TXPOWER = NRFMIN_TXPOWER_DEFAULT;
NRF_RADIO->FREQUENCY = NRFMIN_CHAN_DEFAULT;
/* pre-configure radio addresses */
NRF_RADIO->PREFIX0 = CONF_ADDR_PREFIX0;
NRF_RADIO->BASE0 = (CONF_ADDR_BASE | my_addr);
NRF_RADIO->BASE1 = CONF_ADDR_BCAST;
/* always send from logical address 0 */
NRF_RADIO->TXADDRESS = 0x00UL;
/* and listen to logical addresses 0 and 1 */
NRF_RADIO->RXADDRESSES = 0x03UL;
/* 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) |
(NRFMIN_PKT_MAX << RADIO_PCNF1_MAXLEN_Pos));
/* configure the CRC unit, we skip the address field as this seems to lead
* to wrong checksum calculation on nRF52 devices in some cases */
NRF_RADIO->CRCCNF = CONF_CRC_LEN | RADIO_CRCCNF_SKIPADDR_Msk;
NRF_RADIO->CRCPOLY = CONF_CRC_POLY;
NRF_RADIO->CRCINIT = CONF_CRC_INIT;
/* set shortcuts for more efficient transfer */
NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk;
/* enable interrupts */
NVIC_EnableIRQ(RADIO_IRQn);
/* enable END interrupt */
NRF_RADIO->EVENTS_END = 0;
NRF_RADIO->INTENSET = RADIO_INTENSET_END_Msk;
/* put device in receive mode */
target_state = STATE_RX;
goto_target_state();
DEBUG("[nrfmin] initialization successful\n");
return 0;
}
static void nrfmin_isr(netdev_t *dev)
{
if (nrfmin_dev.event_callback) {
nrfmin_dev.event_callback(dev, NETDEV_EVENT_RX_COMPLETE);
}
}
static int nrfmin_get(netdev_t *dev, netopt_t opt, void *val, size_t max_len)
{
(void)dev;
switch (opt) {
case NETOPT_CHANNEL:
assert(max_len >= sizeof(uint16_t));
*((uint16_t *)val) = nrfmin_get_channel();
return sizeof(uint16_t);
case NETOPT_ADDRESS:
assert(max_len >= sizeof(uint16_t));
*((uint16_t *)val) = nrfmin_get_addr();
return sizeof(uint16_t);
case NETOPT_STATE:
assert(max_len >= sizeof(netopt_state_t));
*((netopt_state_t *)val) = nrfmin_get_state();
return sizeof(netopt_state_t);
case NETOPT_TX_POWER:
assert(max_len >= sizeof(int16_t));
*((int16_t *)val) = nrfmin_get_txpower();
return sizeof(int16_t);
case NETOPT_ADDRESS_LONG:
assert(max_len >= sizeof(uint64_t));
nrfmin_get_pseudo_long_addr((uint16_t *)val);
return sizeof(uint64_t);
case NETOPT_ADDR_LEN:
assert(max_len >= sizeof(uint16_t));
*((uint16_t *)val) = 2;
return sizeof(uint16_t);
case NETOPT_NID:
assert(max_len >= sizeof(uint16_t));
*((uint16_t*)val) = CONF_PSEUDO_NID;
return sizeof(uint16_t);
case NETOPT_PROTO:
*((uint16_t *)val) = 809; /* TODO */
return 2;
case NETOPT_DEVICE_TYPE:
assert(max_len >= sizeof(uint16_t));
*((uint16_t *)val) = NETDEV_TYPE_NRFMIN;
return sizeof(uint16_t);
case NETOPT_IPV6_IID:
assert(max_len >= sizeof(uint64_t));
nrfmin_get_iid((uint16_t *)val);
return sizeof(uint64_t);
default:
return -ENOTSUP;
}
}
static int nrfmin_set(netdev_t *dev, netopt_t opt, void *val, size_t len)
{
(void)dev;
switch (opt) {
case NETOPT_CHANNEL:
assert(len == sizeof(uint16_t));
return nrfmin_set_channel(*((uint16_t *)val));
case NETOPT_ADDRESS:
assert(len == sizeof(uint16_t));
nrfmin_set_addr(*((uint16_t *)val));
return sizeof(uint16_t);
case NETOPT_ADDR_LEN:
case NETOPT_SRC_LEN:
assert(len == sizeof(uint16_t));
if (*((uint16_t *)val) != 2) {
return -EAFNOSUPPORT;
}
return sizeof(uint16_t);
case NETOPT_STATE:
assert(len == sizeof(netopt_state_t));
return nrfmin_set_state(*((netopt_state_t *)val));
case NETOPT_TX_POWER:
assert(len == sizeof(int16_t));
nrfmin_set_txpower(*((int16_t *)val));
return sizeof(int16_t);
default:
return -ENOTSUP;
}
}
/**
* @brief Export of the netdev interface
*/
const netdev_driver_t nrfmin_netdev = {
.send = nrfmin_send,
.recv = nrfmin_recv,
.init = nrfmin_init,
.isr = nrfmin_isr,
.get = nrfmin_get,
.set = nrfmin_set
};