mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
drivers/cc110x: Rewrite of the cc110x driver
The cc110x driver has been re-written from scratch to overcome the limitations of the old driver. The main motivation of the rewrite was to achieve better maintainability by a detailed documentation, reduce the complexity and the overhead of the SPI communication with the device, and to allow to simultaneously use transceivers with different configuration regarding the used base band, the channel bandwidth, the modulation rate, and the channel map. Features of this driver include: - Support for the CC1100, CC1101, and the CC1100e sub-gigahertz transceivers. - Detailed documentation of every aspect of this driver. - An easy to use configuration API that allows setting the transceiver configuration (modulation rate, channel bandwidth, base frequency) and the channel map. - Fast channel hopping by pre-calibration of the channels during device configuration (so that no calibration is needed during hopping). - Simplified SPI communication: Only during start-up the MCU has to wait for the transceiver to be ready (for the power regulators and the crystal to stabilize). The old driver did this for every SPI transfer, which resulted in complex communication code. This driver will wait on start up for the transceiver to power up and then use RIOT's SPI API like every other driver. (Not only the data sheet states that this is fine, it also proved to be reliable in practise.) - Greatly reduced latency: The RTT on the old driver (@150 kbps data rate) was about 16ms, the new driver (@250 kbps data rate) has as RTT of ~3ms (depending on SPI clock and on CPU performance) (measured with ping6). - Increased reliability: The preamble size and the sync word size have been doubled compared to the old driver (preamble: 8 bytes instead of 4, sync word: 4 byte instead of 2). The new values are the once recommended by the data sheet for reliable communication. - Basic diagnostic during driver initialization to detect common issues as SPI communication issues and GDO pin configuration/wiring issues. - TX power configuration with netdev_driver_t::set() API-integration - Calls to netdev_driver_t::send() block until the transmission has completed to ease the use of the API (implemented without busy waiting, so that the MCU can enter lower power states or other threads can be executed).
This commit is contained in:
parent
615e25f319
commit
972367432a
@ -164,7 +164,7 @@ ifneq (,$(filter gnrc_netif,$(USEMODULE)))
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter ieee802154 nrfmin esp_now gnrc_sixloenc,$(USEMODULE)))
|
||||
ifneq (,$(filter ieee802154 nrfmin esp_now cc110x gnrc_sixloenc,$(USEMODULE)))
|
||||
ifneq (,$(filter gnrc_ipv6, $(USEMODULE)))
|
||||
USEMODULE += gnrc_sixlowpan
|
||||
endif
|
||||
|
@ -81,6 +81,20 @@ ifneq (,$(filter bm%280,$(USEMODULE)))
|
||||
USEMODULE += bmx280
|
||||
endif
|
||||
|
||||
ifneq (,$(filter cc110%,$(USEMODULE)))
|
||||
USEMODULE += cc110x
|
||||
USEMODULE += cc1xxx_common
|
||||
USEMODULE += luid
|
||||
USEMODULE += netif
|
||||
USEMODULE += xtimer
|
||||
FEATURES_REQUIRED += periph_gpio
|
||||
FEATURES_REQUIRED += periph_gpio_irq
|
||||
FEATURES_REQUIRED += periph_spi
|
||||
ifneq (,$(filter gnrc_ipv6,$(USEMODULE)))
|
||||
USEMODULE += gnrc_sixlowpan
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter cc2420,$(USEMODULE)))
|
||||
USEMODULE += xtimer
|
||||
USEMODULE += luid
|
||||
|
@ -42,6 +42,10 @@ ifneq (,$(filter bmx280,$(USEMODULE)))
|
||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/bmx280/include
|
||||
endif
|
||||
|
||||
ifneq (,$(filter cc110x,$(USEMODULE)))
|
||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/cc110x/include
|
||||
endif
|
||||
|
||||
ifneq (,$(filter cc2420,$(USEMODULE)))
|
||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/cc2420/include
|
||||
endif
|
||||
|
1
drivers/cc110x/Makefile
Normal file
1
drivers/cc110x/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
173
drivers/cc110x/cc110x.c
Normal file
173
drivers/cc110x/cc110x.c
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation for the "public" API of the CC1100/CC1101 driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
int cc110x_setup(cc110x_t *dev, const cc110x_params_t *params)
|
||||
{
|
||||
if (!dev || !params) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Zero out everything but RIOT's driver interface, which should be
|
||||
* managed by RIOT
|
||||
*/
|
||||
memset((char *)dev + sizeof(netdev_t), 0x00,
|
||||
sizeof(cc110x_t) - sizeof(netdev_t));
|
||||
dev->params = *params;
|
||||
dev->netdev.driver = &cc110x_driver;
|
||||
dev->state = CC110X_STATE_OFF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cc110x_apply_config(cc110x_t *dev, const cc110x_config_t *conf,
|
||||
const cc110x_chanmap_t *chanmap)
|
||||
{
|
||||
DEBUG("[cc110x] Applying new configuration\n");
|
||||
if (!dev || !chanmap) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
|
||||
/* Go to IDLE state to allow reconfiguration */
|
||||
cc110x_cmd(dev, CC110X_STROBE_IDLE);
|
||||
dev->state = CC110X_STATE_IDLE;
|
||||
|
||||
if (conf != NULL) {
|
||||
/* Write all three base frequency configuration bytes in one burst */
|
||||
cc110x_burst_write(dev, CC110X_REG_FREQ2, &conf->base_freq, 3);
|
||||
|
||||
cc110x_write(dev, CC110X_REG_FSCTRL1, conf->fsctrl1);
|
||||
cc110x_write(dev, CC110X_REG_MDMCFG4, conf->mdmcfg4);
|
||||
cc110x_write(dev, CC110X_REG_MDMCFG3, conf->mdmcfg3);
|
||||
cc110x_write(dev, CC110X_REG_DEVIATN, conf->deviatn);
|
||||
}
|
||||
|
||||
/* Set current channel to zero, as the new map might not support the current
|
||||
* virtual channel number. cc110x_full_calibration() will tune in that
|
||||
* channel after calibration.
|
||||
*/
|
||||
dev->channel = 0;
|
||||
dev->channels = chanmap;
|
||||
cc110x_release(dev);
|
||||
|
||||
/* prepare hopping will call cc110x_enter_rx_mode(), which restores the IRQs */
|
||||
return cc110x_full_calibration(dev);
|
||||
}
|
||||
|
||||
int cc110x_set_tx_power(cc110x_t *dev, cc110x_tx_power_t power)
|
||||
{
|
||||
DEBUG("[cc110x] Applying TX power setting at index %u\n", (unsigned)power);
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((unsigned)power >= CC110X_TX_POWER_NUMOF) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
switch (dev->state) {
|
||||
case CC110X_STATE_IDLE:
|
||||
/* falls through */
|
||||
case CC110X_STATE_RX_MODE:
|
||||
break;
|
||||
default:
|
||||
cc110x_release(dev);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
uint8_t frend0 = 0x10 | (uint8_t)power;
|
||||
cc110x_write(dev, CC110X_REG_FREND0, frend0);
|
||||
dev->tx_power = power;
|
||||
cc110x_release(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cc110x_set_channel(cc110x_t *dev, uint8_t channel)
|
||||
{
|
||||
DEBUG("[cc110x] Hopping to channel %i\n", (int)channel);
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if ((channel >= CC110X_MAX_CHANNELS) || (dev->channels->map[channel] == 0xff)) {
|
||||
/* Channel out of range or not supported in current channel map */
|
||||
cc110x_release(dev);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
switch (dev->state) {
|
||||
case CC110X_STATE_IDLE:
|
||||
/* falls through */
|
||||
case CC110X_STATE_RX_MODE:
|
||||
/* falls through */
|
||||
case CC110X_STATE_FSTXON:
|
||||
/* Above states are fine for hopping */
|
||||
break;
|
||||
default:
|
||||
/* All other states do not allow hopping right now */
|
||||
cc110x_release(dev);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/* Disable IRQs, as e.g. PLL indicator will go LOW in IDLE state */
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
|
||||
/* Go to IDLE state to disable frequency synchronizer */
|
||||
cc110x_cmd(dev, CC110X_STROBE_IDLE);
|
||||
|
||||
/* Upload new channel and corresponding calibration data */
|
||||
cc110x_write(dev, CC110X_REG_CHANNR, dev->channels->map[channel]);
|
||||
|
||||
uint8_t caldata[] = {
|
||||
dev->fscal.fscal3, dev->fscal.fscal2, dev->fscal.fscal1[channel]
|
||||
};
|
||||
cc110x_burst_write(dev, CC110X_REG_FSCAL3, caldata, sizeof(caldata));
|
||||
|
||||
/* Start listening on the new channel (restores IRQs) */
|
||||
cc110x_enter_rx_mode(dev);
|
||||
|
||||
dev->channel = channel;
|
||||
cc110x_release(dev);
|
||||
|
||||
dev->netdev.event_callback(&dev->netdev, NETDEV_EVENT_FHSS_CHANGE_CHANNEL);
|
||||
return 0;
|
||||
}
|
141
drivers/cc110x/cc110x_calibration.c
Normal file
141
drivers/cc110x/cc110x_calibration.c
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation of the manual calibration facility of the
|
||||
* CC1100/CC1101 driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "periph/gpio.h"
|
||||
#include "xtimer.h"
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief Read the calibration data from the transceiver and store it
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
*
|
||||
* @pre @p dev is acquired using @p cc110x_acquire
|
||||
*/
|
||||
static inline void get_calibration_data(cc110x_t *dev)
|
||||
{
|
||||
char caldata[3];
|
||||
|
||||
cc110x_burst_read(dev, CC110X_REG_FSCAL3, caldata, sizeof(caldata));
|
||||
dev->fscal.fscal3 = caldata[0];
|
||||
dev->fscal.fscal2 = caldata[1];
|
||||
dev->fscal.fscal1[dev->channel] = caldata[2];
|
||||
}
|
||||
|
||||
int cc110x_recalibrate(cc110x_t *dev)
|
||||
{
|
||||
/* Sadly we cannot use GDO0 to check for calibration, as it only
|
||||
* provides output in RX/TX state. But after successful manual
|
||||
* calibration, the device returns to IDLE state. Thus, we keep
|
||||
* calibrating until IDLE state is reached
|
||||
*/
|
||||
do {
|
||||
/* Start calibration */
|
||||
cc110x_cmd(dev, CC110X_STROBE_CALIBRATE);
|
||||
/* Release SPI interface to give other threads a chance to use it */
|
||||
cc110x_release(dev);
|
||||
/* Manual calibration take 735 micro seconds (see Table 34 on page
|
||||
* 54 in the date sheet). We'll wait 750 to be sure
|
||||
*/
|
||||
xtimer_usleep(750);
|
||||
|
||||
/* Re-acquire SPI interface in order to check if calibration
|
||||
* succeeded
|
||||
*/
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
return -EIO;
|
||||
}
|
||||
} while (cc110x_state_from_status(cc110x_status(dev)) != CC110X_STATE_IDLE);
|
||||
|
||||
get_calibration_data(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cc110x_full_calibration(cc110x_t *dev)
|
||||
{
|
||||
DEBUG("[cc110x] Obtaining calibration data for fast channel hopping\n");
|
||||
if (!dev) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
switch (dev->state) {
|
||||
case CC110X_STATE_IDLE:
|
||||
/* falls through */
|
||||
case CC110X_STATE_RX_MODE:
|
||||
/* falls through */
|
||||
case CC110X_STATE_FSTXON:
|
||||
/* Current state is fine for deliberate calibration */
|
||||
break;
|
||||
default:
|
||||
/* Current state prevents deliberate calibration */
|
||||
cc110x_release(dev);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
uint8_t old_channel = dev->channel;
|
||||
|
||||
/* Disable interrupts on GDO pins */
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
|
||||
/* While waiting for calibration to be done, another thread could
|
||||
* be scheduled. Setting the state should prevent other threads from
|
||||
* messing around with the driver
|
||||
*/
|
||||
dev->state = CC110X_STATE_CALIBRATE;
|
||||
|
||||
/* Go to IDLE to allow setting the channel */
|
||||
cc110x_cmd(dev, CC110X_STROBE_IDLE);
|
||||
|
||||
for (dev->channel = 0; dev->channel < CC110X_MAX_CHANNELS; dev->channel++) {
|
||||
uint8_t phy_chan = dev->channels->map[dev->channel];
|
||||
if (phy_chan == 0xff) {
|
||||
/* Channel not supported by channel map */
|
||||
continue;
|
||||
}
|
||||
/* Set the channel to calibrate for fast hopping */
|
||||
cc110x_write(dev, CC110X_REG_CHANNR, phy_chan);
|
||||
|
||||
if (cc110x_recalibrate(dev)) {
|
||||
/* cc110x_recalibrate() release device on error */
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update device to reflect current transceiver state */
|
||||
dev->state = CC110X_STATE_IDLE;
|
||||
cc110x_release(dev);
|
||||
|
||||
/* Hop back to old channel, IRQs are restored by cc110x_set_channel */
|
||||
return cc110x_set_channel(dev, old_channel);
|
||||
}
|
74
drivers/cc110x/cc110x_chanmaps.c
Normal file
74
drivers/cc110x/cc110x_chanmaps.c
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Channel maps that translate "virtual" channels to "physical"
|
||||
* channels.
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
const cc110x_chanmap_t cc110x_chanmap_433mhz_300khz = {
|
||||
.map = {
|
||||
0, /*< base + 0.000MHz = 433.225 MHz (up to 350 kHz wide channel) */
|
||||
7, /*< base + 0.350MHz = 433.575 MHz (up to 350 kHz wide channel) */
|
||||
14, /*< base + 0.700MHz = 433.925 MHz (up to 350 kHz wide channel) */
|
||||
21, /*< base + 1.050MHz = 434.275 MHz (up to 350 kHz wide channel) */
|
||||
28, /*< base + 1.400MHz = 434.625 MHz (only up to *330* kHz wide) */
|
||||
255, /*< License free range (433.05 MHz - 434.79 MHz) exhausted :-( */
|
||||
255, /*< License free range (433.05 MHz - 434.79 MHz) exhausted :-( */
|
||||
255, /*< License free range (433.05 MHz - 434.79 MHz) exhausted :-( */
|
||||
}
|
||||
};
|
||||
|
||||
const cc110x_chanmap_t cc110x_chanmap_433mhz_50khz = {
|
||||
.map = {
|
||||
0, /*< base + 0.000MHz = 433.100 MHz (LDP433 Channel 2) */
|
||||
4, /*< base + 0.200MHz = 433.300 MHz (LDP433 Channel 10)*/
|
||||
8, /*< base + 0.400MHz = 433.500 MHz (LDP433 Channel 18) */
|
||||
12, /*< base + 0.600MHz = 433.700 MHz (LDP433 Channel 26) */
|
||||
16, /*< base + 0.800MHz = 433.900 MHz (LDP433 Channel 34) */
|
||||
20, /*< base + 1.000MHz = 434.100 MHz (LDP433 Channel 42) */
|
||||
24, /*< base + 1.200MHz = 434.300 MHz (LDP433 Channel 50) */
|
||||
28, /*< base + 1.400MHz = 434.500 MHz (LDP433 Channel 58) */
|
||||
}
|
||||
};
|
||||
|
||||
const cc110x_chanmap_t cc110x_chanmap_433mhz_50khz_alt = {
|
||||
.map = {
|
||||
2, /*< base + 0.100MHz = 433.200 MHz (LDP433 Channel 6) */
|
||||
6, /*< base + 0.300MHz = 433.400 MHz (LDP433 Channel 14)*/
|
||||
10, /*< base + 0.500MHz = 433.600 MHz (LDP433 Channel 22) */
|
||||
14, /*< base + 0.700MHz = 433.800 MHz (LDP433 Channel 30) */
|
||||
18, /*< base + 0.900MHz = 434.000 MHz (LDP433 Channel 38) */
|
||||
22, /*< base + 1.100MHz = 434.200 MHz (LDP433 Channel 46) */
|
||||
26, /*< base + 1.300MHz = 434.400 MHz (LDP433 Channel 54) */
|
||||
30, /*< base + 1.500MHz = 434.600 MHz (LDP433 Channel 62) */
|
||||
}
|
||||
};
|
||||
|
||||
const cc110x_chanmap_t cc110x_chanmap_868mhz_lora = {
|
||||
.map = {
|
||||
0, /*< base + 0.000MHz = 865.20 MHz = LoRa868 Channel 10 */
|
||||
6, /*< base + 0.300MHz = 865.50 MHz = LoRa868 Channel 11 */
|
||||
12, /*< base + 0.600MHz = 865.80 MHz = LoRa868 Channel 12 */
|
||||
18, /*< base + 0.900MHz = 866.10 MHz = LoRa868 Channel 13 */
|
||||
24, /*< base + 1.200MHz = 866.40 MHz = LoRa868 Channel 14 */
|
||||
30, /*< base + 1.500MHz = 866.70 MHz = LoRa868 Channel 15 */
|
||||
36, /*< base + 1.800MHz = 867.00 MHz = LoRa868 Channel 16 */
|
||||
56, /*< base + 2.800MHz = 868.00 MHz = LoRa868 Channel 17 */
|
||||
}
|
||||
};
|
115
drivers/cc110x/cc110x_communication.c
Normal file
115
drivers/cc110x/cc110x_communication.c
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Functions to communicate with the CC1100/CC1101 transceiver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/spi.h"
|
||||
#include "xtimer.h"
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_constants.h"
|
||||
|
||||
int cc110x_power_on(cc110x_t *dev)
|
||||
{
|
||||
gpio_t cs = dev->params.cs;
|
||||
|
||||
if (gpio_init(cs, GPIO_OUT)) {
|
||||
return -EIO;
|
||||
}
|
||||
gpio_clear(cs);
|
||||
xtimer_usleep(150);
|
||||
gpio_set(cs);
|
||||
spi_init_cs(dev->params.spi, dev->params.cs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t cc110x_read(cc110x_t *dev, uint8_t addr, uint8_t *dest)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
addr |= CC110X_SINGLE_BYTE_READ;
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, true,
|
||||
&addr, &status, 1);
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, false, NULL, dest, 1);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t cc110x_read_reliable(cc110x_t *dev, uint8_t addr, uint8_t *dest)
|
||||
{
|
||||
uint8_t status, tmp;
|
||||
|
||||
do {
|
||||
cc110x_read(dev, addr, &tmp);
|
||||
status = cc110x_read(dev, addr, dest);
|
||||
} while (*dest != tmp);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t cc110x_write(cc110x_t *dev, uint8_t addr, uint8_t data)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
addr |= CC110X_SINGLE_BYTE_WRITE;
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, true,
|
||||
&addr, &status, 1);
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, false, &data, NULL, 1);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t cc110x_burst_read(cc110x_t *dev, uint8_t addr, void *dest, size_t len)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
addr |= CC110X_BURST_READ;
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, true,
|
||||
&addr, &status, 1);
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, false, NULL, dest, len);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t cc110x_burst_write(cc110x_t *dev, uint8_t addr,
|
||||
const void *src, size_t len)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
addr |= CC110X_BURST_WRITE;
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, true,
|
||||
&addr, &status, 1);
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, false, src, NULL, len);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t cc110x_cmd(cc110x_t *dev, uint8_t cmd_strobe)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
spi_transfer_bytes(dev->params.spi, dev->params.cs, false,
|
||||
&cmd_strobe, &status, 1);
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t cc110x_status(cc110x_t *dev)
|
||||
{
|
||||
uint8_t status;
|
||||
|
||||
do {
|
||||
status = cc110x_cmd(dev, CC110X_STROBE_STATUS);
|
||||
} while (status != cc110x_cmd(dev, CC110X_STROBE_STATUS));
|
||||
return status;
|
||||
}
|
65
drivers/cc110x/cc110x_configs.c
Normal file
65
drivers/cc110x/cc110x_configs.c
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Transceiver configuration for different base bands, modulation
|
||||
* rate and channel bandwidth.
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
const cc110x_config_t cc110x_config_433mhz_250kbps_300khz = {
|
||||
/* 0x10A99A * 26MHz / 65536 = 433.2252 MHz (LPD433 Channel 7)
|
||||
*
|
||||
* This is 175 kHz above lower end of the license free range, thus up to 5
|
||||
* 350 kHz wide channels can be used (see @ref cc110x_conf_t::deviatn).
|
||||
*/
|
||||
.base_freq = { 0x10, 0xA9, 0x9A },
|
||||
/* Intermediate frequency: 0x0C * 26MHz / 1024 = 304.7kHz */
|
||||
.fsctrl1 = 0x0C,
|
||||
/* 541.67 kHz channel filter bandwidth */
|
||||
.mdmcfg4 = 0x2D,
|
||||
/* 250 kBaud symbol rate ==> 250 kbps data rate with GFSK */
|
||||
.mdmcfg3 = 0x3B,
|
||||
/* Deviation of +- 126.953 kHz ==> channel bandwidth about 300 kHz */
|
||||
.deviatn = 0x62,
|
||||
};
|
||||
|
||||
const cc110x_config_t cc110x_config_433mhz_38kbps_50khz = {
|
||||
/* 0x10A85F * 26MHz / 65536 = 433.1002 MHz (LPD433 Channel 2) */
|
||||
.base_freq = { 0x10, 0xA8, 0x5F },
|
||||
/* Intermediate frequency: 0x06 * 26000kHz / 1024 = 152.3kHz */
|
||||
.fsctrl1 = 0x06,
|
||||
/* 101.5625 kHz channel filter bandwidth */
|
||||
.mdmcfg4 = 0xCA,
|
||||
/* 38.38 kBaud symbol rate ==> 38.38 kbps data rate with GFSK */
|
||||
.mdmcfg3 = 0x83,
|
||||
/* Deviation of +- 20.63 kHz ==> channel bandwidth about 50 kHz */
|
||||
.deviatn = 0x35,
|
||||
};
|
||||
|
||||
const cc110x_config_t cc110x_config_868mhz_250kbps_300khz = {
|
||||
/* 0x2146e4 * 26MHz / 65536 = 865.1998 MHz (LoRa 868 Channel 10) */
|
||||
.base_freq = { 0x21, 0x46, 0xE4 },
|
||||
/* Intermediate frequency: 0x0C * 26MHz / 1024 = 304.7kHz */
|
||||
.fsctrl1 = 0x0C,
|
||||
/* 541.67 kHz channel filter bandwidth */
|
||||
.mdmcfg4 = 0x2D,
|
||||
/* 250 kBaud symbol rate ==> 250 kbps data rate with GFSK */
|
||||
.mdmcfg3 = 0x3B,
|
||||
/* Deviation of +- 126.953 kHz ==> channel bandwidth about 300 kHz */
|
||||
.deviatn = 0x62,
|
||||
};
|
623
drivers/cc110x/cc110x_netdev.c
Normal file
623
drivers/cc110x/cc110x_netdev.c
Normal file
@ -0,0 +1,623 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Implementation of RIOT's netdev_driver API for the CC1100/CC1101
|
||||
* transceiver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "assert.h"
|
||||
#include "iolist.h"
|
||||
#include "irq.h"
|
||||
#include "luid.h"
|
||||
#include "mutex.h"
|
||||
#include "net/eui64.h"
|
||||
#include "net/netdev.h"
|
||||
#include "xtimer.h"
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
static int cc110x_init(netdev_t *netdev);
|
||||
static int cc110x_recv(netdev_t *netdev, void *buf, size_t len, void *info);
|
||||
static int cc110x_send(netdev_t *netdev, const iolist_t *iolist);
|
||||
static int cc110x_get(netdev_t *netdev, netopt_t opt,
|
||||
void *val, size_t max_len);
|
||||
static int cc110x_set(netdev_t *netdev, netopt_t opt,
|
||||
const void *val, size_t len);
|
||||
|
||||
/**
|
||||
* @brief A lookup table to convert from dBm value to the best matching
|
||||
* @ref cc110x_tx_power_t value
|
||||
*/
|
||||
static const int8_t tx_power_from_dbm[] = {
|
||||
[CC110X_TX_POWER_MINUS_30_DBM] = -25,
|
||||
[CC110X_TX_POWER_MINUS_20_DBM] = -17,
|
||||
[CC110X_TX_POWER_MINUS_15_DBM] = -12,
|
||||
[CC110X_TX_POWER_MINUS_10_DBM] = -5,
|
||||
[CC110X_TX_POWER_0_DBM] = 3,
|
||||
[CC110X_TX_POWER_PLUS_5_DBM] = 6,
|
||||
[CC110X_TX_POWER_PLUS_7_DBM] = 9,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A lookup table to convert an @ref cc110x_tx_power_t value to dBm
|
||||
*/
|
||||
static const int8_t dbm_from_tx_power[] = {
|
||||
[CC110X_TX_POWER_MINUS_30_DBM] = -30,
|
||||
[CC110X_TX_POWER_MINUS_20_DBM] = -20,
|
||||
[CC110X_TX_POWER_MINUS_15_DBM] = -15,
|
||||
[CC110X_TX_POWER_MINUS_10_DBM] = -10,
|
||||
[CC110X_TX_POWER_0_DBM] = 0,
|
||||
[CC110X_TX_POWER_PLUS_5_DBM] = 5,
|
||||
[CC110X_TX_POWER_PLUS_7_DBM] = 7,
|
||||
[CC110X_TX_POWER_PLUS_10_DBM] = 10,
|
||||
};
|
||||
|
||||
const netdev_driver_t cc110x_driver = {
|
||||
.init = cc110x_init,
|
||||
.recv = cc110x_recv,
|
||||
.send = cc110x_send,
|
||||
.isr = cc110x_isr,
|
||||
.get = cc110x_get,
|
||||
.set = cc110x_set,
|
||||
};
|
||||
|
||||
void cc110x_on_gdo(void *_dev)
|
||||
{
|
||||
cc110x_t *dev = _dev;
|
||||
if ((dev->state & 0x07) == CC110X_STATE_TX_MODE) {
|
||||
/* Unlock mutex to unblock netdev thread */
|
||||
mutex_unlock(&dev->isr_signal);
|
||||
}
|
||||
else {
|
||||
dev->netdev.event_callback(&dev->netdev, NETDEV_EVENT_ISR);
|
||||
}
|
||||
}
|
||||
|
||||
static int identify_device(cc110x_t *dev)
|
||||
{
|
||||
uint8_t partnum, version, status;
|
||||
int is_ready;
|
||||
cc110x_state_t state;
|
||||
|
||||
cc110x_read(dev, CC110X_REG_VERSION, &version);
|
||||
/* Retrieving the status is reliable for non-transient status */
|
||||
status = cc110x_read(dev, CC110X_REG_PARTNUM, &partnum);
|
||||
state = cc110x_state_from_status(status);
|
||||
/* Most significant bit should be zero, otherwise chip is not ready */
|
||||
is_ready = cc110x_is_ready_from_status(status);
|
||||
|
||||
DEBUG("[cc110x] PARTNUM = %i, VERSION = %i, STATUS = 0x%02x, READY = %i\n",
|
||||
(int)partnum, (int)version, (int)status, is_ready);
|
||||
|
||||
if ((state != CC110X_STATE_IDLE) || (!is_ready)) {
|
||||
DEBUG("[cc110x] IC not ready or in invalid state\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Source: https://e2e.ti.com/support/wireless-connectivity/other-wireless/f/667/t/370643 */
|
||||
if (partnum != 0) {
|
||||
DEBUG("[cc110x] Device not a CC110x transceiver\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
case 3:
|
||||
DEBUG("[cc110x] Detected CC1100 transceiver\n");
|
||||
/* RSSI offset is 78dBm @ 868MHz & 250kBaud.
|
||||
* Depends on the symbol rate and base band and ranges from
|
||||
* 74dBm to 79dBm.
|
||||
*/
|
||||
dev->rssi_offset = 78;
|
||||
return 0;
|
||||
case 5:
|
||||
DEBUG("[cc110x] Detected CC1100E transceiver\n");
|
||||
/* RSSI offset is 79 dBm @ 250kbps & 250 kbps.
|
||||
* Depends on base band and symbol rate and ranges from
|
||||
* 75dBm to 79dBm
|
||||
*/
|
||||
dev->rssi_offset = 79;
|
||||
return 0;
|
||||
case 4:
|
||||
/* falls through */
|
||||
case 14:
|
||||
/* falls through */
|
||||
case 20:
|
||||
/* RSSI offset for the CC1101 is independent of symbol rate and
|
||||
* base 74 dBm
|
||||
*/
|
||||
dev->rssi_offset = 74;
|
||||
DEBUG("[cc110x] Detected CC1101 transceiver\n");
|
||||
return 0;
|
||||
default:
|
||||
DEBUG("[cc110x] Device not a CC110x transceiver\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int check_config(cc110x_t *dev)
|
||||
{
|
||||
char buf[CC110X_CONF_SIZE];
|
||||
|
||||
/* Verify content of main config registers */
|
||||
cc110x_burst_read(dev, CC110X_CONF_START, buf, sizeof(buf));
|
||||
if (memcmp(buf, cc110x_conf, sizeof(buf))) {
|
||||
DEBUG("[cc110x] ERROR: Verification of main config registers failed\n"
|
||||
" Check SPI wiring or reduce SPI clock\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify content of "magic number" config registers */
|
||||
cc110x_burst_read(dev, CC110X_REG_TEST2, buf,
|
||||
sizeof(cc110x_magic_registers));
|
||||
if (memcmp(buf, cc110x_magic_registers, sizeof(cc110x_magic_registers))) {
|
||||
DEBUG("[cc110x] ERROR: Verification of \"magic\" registers failed\n"
|
||||
" Check SPI wiring or reduce SPI clock\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify content of PA_TABLE */
|
||||
cc110x_burst_read(dev, CC110X_MULTIREG_PATABLE, buf, CC110X_PATABLE_LEN);
|
||||
if (memcmp(buf, dev->params.patable->data, CC110X_PATABLE_LEN)) {
|
||||
DEBUG("[cc110x] ERROR: Verification of PA_TABLE failed\n"
|
||||
" Check SPI wiring or reduce SPI clock\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG("[cc110x] Content of configuration registers verified\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_gdo_pins(cc110x_t *dev)
|
||||
{
|
||||
/* GPIOs connected to GDOs are not yet configured, so we do this now.
|
||||
* This configuration is just temporarily for testing, as gpio_init_int()
|
||||
* will reconfigure them later again. We do not want to set up the
|
||||
* interrupts before validating the transceiver, so we effectively configure
|
||||
* the GPIOs twice.
|
||||
*/
|
||||
if (gpio_init(dev->params.gdo2, GPIO_IN)) {
|
||||
DEBUG("[cc110x] Configuring GDO2 failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gpio_init(dev->params.gdo0, GPIO_IN)) {
|
||||
DEBUG("[cc110x] Configuring GDO0 failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate that GDO2 responds to configuration updates */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_HIGH);
|
||||
if (!gpio_read(dev->params.gdo2)) {
|
||||
DEBUG("[cc110x] GDO2 does not respond (check wiring!)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_LOW);
|
||||
if (gpio_read(dev->params.gdo2)) {
|
||||
DEBUG("[cc110x] GDO2 does not respond (check wiring!)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate that GDO0 responds to configuration updates */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG0, CC110X_GDO_CONSTANT_HIGH);
|
||||
if (!gpio_read(dev->params.gdo0)) {
|
||||
DEBUG("[cc110x] GDO0 does not respond (check wiring!)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cc110x_write(dev, CC110X_REG_IOCFG0, CC110X_GDO_CONSTANT_LOW);
|
||||
if (gpio_read(dev->params.gdo0)) {
|
||||
DEBUG("[cc110x] GDO0 does not respond (check wiring!)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Restore default GDO2 & GDO0 config */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, cc110x_conf[CC110X_REG_IOCFG2]);
|
||||
cc110x_write(dev, CC110X_REG_IOCFG0, cc110x_conf[CC110X_REG_IOCFG0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cc110x_init(netdev_t *netdev)
|
||||
{
|
||||
cc110x_t *dev = (cc110x_t *)netdev;
|
||||
/* Use locked mutex to block thread on TX and un-block from ISR */
|
||||
mutex_init(&dev->isr_signal);
|
||||
mutex_lock(&dev->isr_signal);
|
||||
|
||||
/* Make sure the crystal is stable and the chip ready. This is needed as
|
||||
* the reset is done via an SPI command, but the SPI interface must not be
|
||||
* used unless the chip is ready according to the data sheet. After the
|
||||
* reset, a second call to cc110x_power_on() is needed to finally have
|
||||
* the transceiver in a known state and ready for SPI communication.
|
||||
*/
|
||||
if (cc110x_power_on(dev)) {
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Failed to pull CS pin low\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Failed to setup/acquire SPI "
|
||||
"interface\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Performing a reset of the transceiver to get it in a known state */
|
||||
cc110x_cmd(dev, CC110X_STROBE_RESET);
|
||||
cc110x_release(dev);
|
||||
|
||||
/* Again, make sure the crystal is stable and the chip ready */
|
||||
if (cc110x_power_on(dev)) {
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Failed to pull CS pin low "
|
||||
"after reset\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Failed to setup/acquire SPI "
|
||||
"interface after reset\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (identify_device(dev)) {
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Device identification failed\n");
|
||||
cc110x_release(dev);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Upload the main configuration */
|
||||
cc110x_burst_write(dev, CC110X_CONF_START, cc110x_conf, CC110X_CONF_SIZE);
|
||||
/* Set TX power to match uploaded configuration */
|
||||
dev->tx_power = CC110X_TX_POWER_0_DBM;
|
||||
|
||||
/* Upload the poorly documented magic numbers obtained via SmartRF Studio */
|
||||
cc110x_burst_write(dev, CC110X_REG_TEST2, cc110x_magic_registers,
|
||||
sizeof(cc110x_magic_registers));
|
||||
|
||||
/* Setup the selected PA_TABLE */
|
||||
cc110x_burst_write(dev, CC110X_MULTIREG_PATABLE,
|
||||
dev->params.patable->data, CC110X_PATABLE_LEN);
|
||||
|
||||
/* Verify main config, magic numbers and PA_TABLE correctly uploaded */
|
||||
if (check_config(dev)) {
|
||||
cc110x_release(dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Verify that pins GDO2 and GDO0 are correctly connected */
|
||||
if (check_gdo_pins(dev)) {
|
||||
cc110x_release(dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Setup the layer 2 address, but do not accept CC110X_L2ADDR_AUTO (which
|
||||
* has the value 0x00 and is used for broadcast)
|
||||
*/
|
||||
dev->addr = dev->params.l2addr;
|
||||
while (dev->addr == CC110X_L2ADDR_AUTO) {
|
||||
luid_get(&dev->addr, 1);
|
||||
}
|
||||
cc110x_write(dev, CC110X_REG_ADDR, dev->addr);
|
||||
|
||||
/* Setup interrupt on GDO0 */
|
||||
if (gpio_init_int(dev->params.gdo0, GPIO_IN, GPIO_BOTH,
|
||||
cc110x_on_gdo, dev)) {
|
||||
cc110x_release(dev);
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Failed to setup interrupt on "
|
||||
"GDO0 pin\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Setup interrupt on GDO2 */
|
||||
if (gpio_init_int(dev->params.gdo2, GPIO_IN, GPIO_BOTH,
|
||||
cc110x_on_gdo, dev)) {
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
cc110x_release(dev);
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Failed to setup interrupt on "
|
||||
"GDO2 pin\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Update the state of the driver/transceiver */
|
||||
dev->state = CC110X_STATE_IDLE;
|
||||
cc110x_release(dev);
|
||||
|
||||
|
||||
int retval; /*< Store return value to be able to pass through error code */
|
||||
/* Apply configuration (if non-NULL) and channel map, which also calls
|
||||
* cc110x_full_calibration
|
||||
*/
|
||||
retval = cc110x_apply_config(dev, dev->params.config, dev->params.channels);
|
||||
if (retval) {
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): cc110x_apply_config() "
|
||||
"failed\n");
|
||||
/* Pass through received error code */
|
||||
return retval;
|
||||
}
|
||||
else {
|
||||
DEBUG("[cc110x] netdev_driver_t::init(): Success\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cc110x_recv(netdev_t *netdev, void *buf, size_t len, void *info)
|
||||
{
|
||||
cc110x_t *dev = (cc110x_t *)netdev;
|
||||
|
||||
/* Call to cc110x_enter_rx_mode() will clear dev->buf.len, so back up it first */
|
||||
int size = dev->buf.len;
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
DEBUG("[cc110x] netdev_driver_t::recv(): cc110x_acquire() "
|
||||
"failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Copy RX info on last frame (if requested) */
|
||||
if (info != NULL) {
|
||||
*((cc1xxx_rx_info_t *)info) = dev->rx_info;
|
||||
}
|
||||
|
||||
if (!buf) {
|
||||
/* Get the size of the frame; if len > 0 then also drop the frame */
|
||||
if (len > 0) {
|
||||
/* Drop frame requested */
|
||||
cc110x_enter_rx_mode(dev);
|
||||
}
|
||||
cc110x_release(dev);
|
||||
return size;
|
||||
}
|
||||
|
||||
if (len < (size_t)size) {
|
||||
/* Drop frame and return -ENOBUFS */
|
||||
cc110x_enter_rx_mode(dev);
|
||||
cc110x_release(dev);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
memcpy(buf, dev->buf.data, (size_t)size);
|
||||
|
||||
cc110x_enter_rx_mode(dev);
|
||||
cc110x_release(dev);
|
||||
return size;
|
||||
}
|
||||
|
||||
static int cc110x_send(netdev_t *netdev, const iolist_t *iolist)
|
||||
{
|
||||
cc110x_t *dev = (cc110x_t *)netdev;
|
||||
|
||||
/* assert that cc110x_send was called with valid parameters */
|
||||
assert(netdev && iolist && (iolist->iol_len == sizeof(cc1xxx_l2hdr_t)));
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
DEBUG("[cc110x] netdev_driver_t::send(): cc110x_acquire() failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (dev->state) {
|
||||
case CC110X_STATE_FSTXON:
|
||||
/* falls through */
|
||||
case CC110X_STATE_RX_MODE:
|
||||
break;
|
||||
case CC110X_STATE_RECEIVING:
|
||||
cc110x_release(dev);
|
||||
DEBUG("[cc110x] netdev_driver_t::send(): Refusing to send while "
|
||||
"receiving a frame\n");
|
||||
return -EBUSY;
|
||||
default:
|
||||
cc110x_release(dev);
|
||||
DEBUG("[cc110x] netdev_driver_t::send(): Driver state %i prevents "
|
||||
"sending\n", (int)dev->state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Copy data to send into frame buffer */
|
||||
size_t size = sizeof(cc1xxx_l2hdr_t);
|
||||
memcpy(dev->buf.data, iolist->iol_base, sizeof(cc1xxx_l2hdr_t));
|
||||
|
||||
for (const iolist_t *iol = iolist->iol_next; iol; iol = iol->iol_next) {
|
||||
if (iol->iol_len) {
|
||||
if (size + iol->iol_len > CC110X_MAX_FRAME_SIZE) {
|
||||
cc110x_release(dev);
|
||||
DEBUG("[cc110x] netdev_driver_t::send(): Frame size of %uB "
|
||||
"exceeds maximum supported size of %uB\n",
|
||||
(unsigned)(size + iol->iol_len),
|
||||
(unsigned)CC110X_MAX_FRAME_SIZE);
|
||||
return -1;
|
||||
}
|
||||
memcpy(dev->buf.data + size, iol->iol_base, iol->iol_len);
|
||||
size += iol->iol_len;
|
||||
}
|
||||
}
|
||||
|
||||
dev->buf.len = (uint8_t)size;
|
||||
|
||||
/* Disable IRQs, as GDO configuration will be changed now */
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
|
||||
/* Fill the TX FIFO: First write the length, then the frame */
|
||||
dev->buf.pos = (size > CC110X_FIFO_SIZE - 1) ? CC110X_FIFO_SIZE - 1 : size;
|
||||
/* cc110x_framebuf_t has the same memory layout as the device expects */
|
||||
cc110x_burst_write(dev, CC110X_MULTIREG_FIFO,
|
||||
&dev->buf, dev->buf.pos + 1);
|
||||
|
||||
/* Go to TX */
|
||||
cc110x_cmd(dev, CC110X_STROBE_TX);
|
||||
|
||||
/* Configure GDO2 and update state */
|
||||
if (dev->buf.pos < dev->buf.len) {
|
||||
/* We need to keep feeding TX FIFO */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_ON_TX_DATA);
|
||||
dev->state = CC110X_STATE_TX_MODE;
|
||||
}
|
||||
else {
|
||||
/* All data in TX FIFO, just waiting for transceiver to finish */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_LOW);
|
||||
dev->state = CC110X_STATE_TX_COMPLETING;
|
||||
}
|
||||
|
||||
cc110x_release(dev);
|
||||
|
||||
/* Restore IRQs */
|
||||
gpio_irq_enable(dev->params.gdo0);
|
||||
gpio_irq_enable(dev->params.gdo2);
|
||||
|
||||
while ((dev->state & 0x07) == CC110X_STATE_TX_MODE) {
|
||||
/* Block until mutex is unlocked from ISR */
|
||||
mutex_lock(&dev->isr_signal);
|
||||
cc110x_isr(&dev->netdev);
|
||||
}
|
||||
|
||||
return (int)size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate an IPv6 interface identifier for a CC110X transceiver
|
||||
*
|
||||
* @param dev Transceiver to create the IPv6 interface identifier (IID)
|
||||
* @param iid Store the generated IID here
|
||||
*
|
||||
* @return Returns the size of @ref eui64_t to confirm with the API
|
||||
* in @ref netdev_driver_t::get
|
||||
*/
|
||||
static int cc110x_get_iid(cc110x_t *dev, eui64_t *iid)
|
||||
{
|
||||
static const eui64_t empty_iid = {
|
||||
.uint8 = { 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00 }
|
||||
};
|
||||
|
||||
*iid = empty_iid;
|
||||
iid->uint8[7] = dev->addr;
|
||||
return sizeof(eui64_t);
|
||||
}
|
||||
|
||||
static int cc110x_get(netdev_t *netdev, netopt_t opt,
|
||||
void *val, size_t max_len)
|
||||
{
|
||||
cc110x_t *dev = (cc110x_t *)netdev;
|
||||
|
||||
switch (opt) {
|
||||
case NETOPT_DEVICE_TYPE:
|
||||
assert(max_len == sizeof(uint16_t));
|
||||
*((uint16_t *)val) = NETDEV_TYPE_CC110X;
|
||||
return sizeof(uint16_t);
|
||||
case NETOPT_PROTO:
|
||||
assert(max_len == sizeof(gnrc_nettype_t));
|
||||
*((gnrc_nettype_t *)val) = CC110X_DEFAULT_PROTOCOL;
|
||||
return sizeof(gnrc_nettype_t);
|
||||
case NETOPT_MAX_PACKET_SIZE:
|
||||
assert(max_len == sizeof(uint16_t));
|
||||
*((uint16_t *)val) = CC110X_MAX_FRAME_SIZE - sizeof(cc1xxx_l2hdr_t);
|
||||
return sizeof(uint16_t);
|
||||
case NETOPT_ADDR_LEN:
|
||||
/* falls through */
|
||||
case NETOPT_SRC_LEN:
|
||||
assert(max_len == sizeof(uint16_t));
|
||||
*((uint16_t *)val) = CC1XXX_ADDR_SIZE;
|
||||
return sizeof(uint16_t);
|
||||
case NETOPT_ADDRESS:
|
||||
assert(max_len >= CC1XXX_ADDR_SIZE);
|
||||
*((uint8_t *)val) = dev->addr;
|
||||
return CC1XXX_ADDR_SIZE;
|
||||
case NETOPT_IPV6_IID:
|
||||
if (max_len < sizeof(eui64_t)) {
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
return cc110x_get_iid(dev, val);
|
||||
case NETOPT_CHANNEL:
|
||||
assert(max_len == sizeof(uint16_t));
|
||||
*((uint16_t *)val) = dev->channel;
|
||||
return sizeof(uint16_t);
|
||||
case NETOPT_TX_POWER:
|
||||
assert(max_len == sizeof(uint16_t));
|
||||
*((uint16_t *)val) = dbm_from_tx_power[dev->tx_power];
|
||||
return sizeof(uint16_t);
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the given address as the device's layer 2 address
|
||||
*
|
||||
* @param dev Device descripter of the transceiver
|
||||
* @param addr Address to set
|
||||
*/
|
||||
static int cc110x_set_addr(cc110x_t *dev, uint8_t addr)
|
||||
{
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev->addr = addr;
|
||||
cc110x_write(dev, CC110X_REG_ADDR, addr);
|
||||
cc110x_release(dev);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cc110x_set(netdev_t *netdev, netopt_t opt,
|
||||
const void *val, size_t len)
|
||||
{
|
||||
(void)len;
|
||||
cc110x_t *dev = (cc110x_t *)netdev;
|
||||
|
||||
switch (opt) {
|
||||
case NETOPT_ADDRESS:
|
||||
assert(len == CC1XXX_ADDR_SIZE);
|
||||
return cc110x_set_addr(dev, *((uint8_t *)val));
|
||||
case NETOPT_CHANNEL:
|
||||
{
|
||||
assert(len == sizeof(uint16_t));
|
||||
int retval;
|
||||
uint16_t channel = *((uint16_t *)val);
|
||||
if (channel >= CC110X_MAX_CHANNELS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
if ((retval = cc110x_set_channel(dev, (uint8_t)channel))) {
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
return sizeof(uint16_t);
|
||||
case NETOPT_TX_POWER:
|
||||
{
|
||||
assert(len == sizeof(int16_t));
|
||||
int16_t dbm = *((int16_t *)val);
|
||||
cc110x_tx_power_t power = CC110X_TX_POWER_MINUS_30_DBM;
|
||||
for ( ; power < CC110X_TX_POWER_PLUS_10_DBM; power++) {
|
||||
if ((int16_t)tx_power_from_dbm[power] >= dbm) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cc110x_set_tx_power(dev, power)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return sizeof(uint16_t);
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
67
drivers/cc110x/cc110x_patables.c
Normal file
67
drivers/cc110x/cc110x_patables.c
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Default configuration of the transceiver output power for the
|
||||
* different frequency bands
|
||||
*
|
||||
* See Section "24 Output Power Programming" on page 59 in the data sheet for
|
||||
* details.
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
const cc110x_patable_t cc110x_patable_433mhz = {
|
||||
.data = {
|
||||
/* Settings for 433 MHz */
|
||||
0x12, /*< -30 dBm */
|
||||
0x0E, /*< -20 dBm */
|
||||
0x1D, /*< -15 dBm */
|
||||
0x34, /*< -10 dBm */
|
||||
0x60, /*< 0 dBm */
|
||||
0x84, /*< 5 dBm */
|
||||
0xC8, /*< 7 dBm */
|
||||
0xC0, /*< 10 dBm */
|
||||
}
|
||||
};
|
||||
|
||||
const cc110x_patable_t cc110x_patable_868mhz = {
|
||||
.data = {
|
||||
/* Settings for 868 MHz */
|
||||
0x03, /*< -30 dBm */
|
||||
0x0F, /*< -20 dBm */
|
||||
0x1E, /*< -15 dBm */
|
||||
0x27, /*< -10 dBm */
|
||||
0x50, /*< 0 dBm */
|
||||
0x81, /*< 5 dBm */
|
||||
0xCB, /*< 7 dBm */
|
||||
0xC2, /*< 10 dBm */
|
||||
}
|
||||
};
|
||||
|
||||
const cc110x_patable_t cc110x_patable_915mhz = {
|
||||
.data = {
|
||||
/* Settings for 915 MHz */
|
||||
0x03, /*< -30 dBm */
|
||||
0x0E, /*< -20 dBm */
|
||||
0x1E, /*< -15 dBm */
|
||||
0x27, /*< -10 dBm */
|
||||
0x8E, /*< 0 dBm */
|
||||
0xCD, /*< 5 dBm */
|
||||
0xC7, /*< 7 dBm */
|
||||
0xC0, /*< 10 dBm */
|
||||
}
|
||||
};
|
326
drivers/cc110x/cc110x_rx_tx.c
Normal file
326
drivers/cc110x/cc110x_rx_tx.c
Normal file
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Functions to manage sending/receiving frames with the CC110x
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "xtimer.h"
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
/* Use NETDEV_EVENT_ISR to indicate that no event needs to be passed to upper
|
||||
* layer at end of ISR, as ISR will never need this event
|
||||
*/
|
||||
#define NETDEV_NO_EVENT NETDEV_EVENT_ISR
|
||||
|
||||
void cc110x_enter_rx_mode(cc110x_t *dev)
|
||||
{
|
||||
DEBUG("[cc110x] Going to RX\n");
|
||||
/* bring device to IDLE state and flush FIFOs (just in case) */
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
cc110x_cmd(dev, CC110X_STROBE_IDLE);
|
||||
cc110x_cmd(dev, CC110X_STROBE_FLUSH_RX);
|
||||
cc110x_cmd(dev, CC110X_STROBE_FLUSH_TX);
|
||||
dev->buf.pos = dev->buf.len = 0;
|
||||
/* Apply GDO2 config and go to RX */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_ON_RX_DATA);
|
||||
cc110x_write(dev, CC110X_REG_IOCFG0, CC110X_GDO_ON_TRANSMISSION);
|
||||
cc110x_cmd(dev, CC110X_STROBE_RX);
|
||||
dev->state = CC110X_STATE_RX_MODE;
|
||||
gpio_irq_enable(dev->params.gdo2);
|
||||
gpio_irq_enable(dev->params.gdo0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function to run when frame is fully received
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
*
|
||||
* Intended to be called from @ref cc110x_rx_continue
|
||||
*/
|
||||
static netdev_event_t cc110x_rx_done(cc110x_t *dev)
|
||||
{
|
||||
uint8_t lqi_crc;
|
||||
int8_t rssi;
|
||||
|
||||
cc110x_read(dev, CC110X_REG_LQI, &lqi_crc);
|
||||
cc110x_read(dev, CC110X_REG_RSSI, (uint8_t *)&rssi);
|
||||
|
||||
/* CRC_OK bit is most significant bit, see page 92 in the data sheet */
|
||||
if (!(lqi_crc & 0x80)) {
|
||||
DEBUG("[cc110x] ISR: CRC error, dropping frame\n");
|
||||
/* Drop frame and go back to RX */
|
||||
cc110x_enter_rx_mode(dev);
|
||||
return NETDEV_EVENT_CRC_ERROR;
|
||||
}
|
||||
|
||||
/* Copy all but the CRC_OK bit */
|
||||
dev->rx_info.lqi = (uint8_t)lqi_crc & 0x7f;
|
||||
|
||||
/* Use the formula in section 17.3 on page 44 in the data sheet to obtain
|
||||
* the correct RSSI value in dBm.
|
||||
*/
|
||||
dev->rx_info.rssi = (int16_t)(rssi / 2) - (int16_t)dev->rssi_offset;
|
||||
|
||||
/* Transceiver has automatically gone to IDLE. We keep it in IDLE until
|
||||
* upper layer fetched the frame
|
||||
*/
|
||||
dev->state = CC110X_STATE_FRAME_READY;
|
||||
return NETDEV_EVENT_RX_COMPLETE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a chunk of data from the RX-FIFO
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
*
|
||||
* This function should be called from the ISR when data in the RX-FIFO is
|
||||
* available or the last byte of the frame was received
|
||||
*/
|
||||
static netdev_event_t cc110x_rx_continue(cc110x_t *dev)
|
||||
{
|
||||
uint8_t in_fifo;
|
||||
netdev_event_t retval = NETDEV_NO_EVENT;
|
||||
|
||||
while (gpio_read(dev->params.gdo2)) {
|
||||
cc110x_read_reliable(dev, CC110X_REG_RXBYTES, &in_fifo);
|
||||
|
||||
if (in_fifo & 0x80) {
|
||||
/* RXFIFO_OVERFLOW bit is set (see RXBYTES on page 94) */
|
||||
DEBUG("[cc110x] ISR: RX-FIFO overflown, ISR too slow\n");
|
||||
/* Drop frame and go to RX */
|
||||
cc110x_enter_rx_mode(dev);
|
||||
return NETDEV_EVENT_RX_TIMEOUT;
|
||||
}
|
||||
|
||||
if (!in_fifo) {
|
||||
/* GDO2 will be high when data is present *or* at end of packet */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle first read from RX FIFO differently from subsequent reads, as
|
||||
* in first reads the Length Field is read as well
|
||||
*/
|
||||
if (!dev->buf.len) {
|
||||
if (in_fifo < sizeof(cc1xxx_l2hdr_t) + 1) {
|
||||
/* At least a frame header + Length Field (1B) is expected */
|
||||
DEBUG("[cc110x] ISR: Incoming frame smaller than header "
|
||||
"--> drop\n");
|
||||
cc110x_enter_rx_mode(dev);
|
||||
/* Not exactly CRC, but incorrect CRC indicates a broken frame*/
|
||||
return NETDEV_EVENT_CRC_ERROR;
|
||||
}
|
||||
cc110x_burst_read(dev, CC110X_MULTIREG_FIFO, &dev->buf,
|
||||
in_fifo - 1);
|
||||
/* Update read position in payload, that is number of bytes read
|
||||
* minus the Length Filed and minus the byte left in the FIFO to not
|
||||
* trigger a silicon bug
|
||||
*/
|
||||
dev->buf.pos = in_fifo - 2;
|
||||
retval = NETDEV_EVENT_RX_STARTED;
|
||||
}
|
||||
else {
|
||||
/* Prevent overflow of buffer */
|
||||
if (dev->buf.pos + in_fifo > CC110X_MAX_FRAME_SIZE) {
|
||||
DEBUG("[cc110x] ISR: Incoming frame exceeds maximum size\n");
|
||||
cc110x_enter_rx_mode(dev);
|
||||
/* Not exactly CRC, but incorrect CRC indicates a broken frame */
|
||||
return NETDEV_EVENT_CRC_ERROR;
|
||||
}
|
||||
|
||||
if (dev->buf.pos + in_fifo < dev->buf.len) {
|
||||
/* Frame not fully received yet, keeping one byte in RX FIFO
|
||||
* to prevent triggering a silicon bug
|
||||
*/
|
||||
in_fifo--;
|
||||
}
|
||||
|
||||
/* Continue reading data */
|
||||
cc110x_burst_read(dev, CC110X_MULTIREG_FIFO,
|
||||
dev->buf.data + dev->buf.pos, in_fifo);
|
||||
dev->buf.pos += in_fifo;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->buf.pos > dev->buf.len) {
|
||||
DEBUG("[cc110x] ISR: Incoming frame larger than Length Field "
|
||||
"--> drop\n");
|
||||
cc110x_enter_rx_mode(dev);
|
||||
/* Not exactly CRC, but incorrect CRC indicates a broken frame */
|
||||
return NETDEV_EVENT_CRC_ERROR;
|
||||
}
|
||||
|
||||
if (!gpio_read(dev->params.gdo0)) {
|
||||
/* GDO0 is low when transmission is over ==> RX complete or corrupt
|
||||
frame */
|
||||
if (dev->buf.pos == dev->buf.len) {
|
||||
return cc110x_rx_done(dev);
|
||||
}
|
||||
else {
|
||||
DEBUG("[cc110x] ISR: Incoming frame smaller than Length Field "
|
||||
"--> drop\n");
|
||||
cc110x_enter_rx_mode(dev);
|
||||
/* Not exactly CRC, but incorrect CRC indicates a broken frame */
|
||||
return NETDEV_EVENT_CRC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function to run when frame is fully send
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
*/
|
||||
static netdev_event_t cc110x_tx_done(cc110x_t *dev)
|
||||
{
|
||||
uint8_t status = cc110x_status(dev);
|
||||
cc110x_state_t state = cc110x_state_from_status(status);
|
||||
switch (state){
|
||||
case CC110X_STATE_SETTLING:
|
||||
case CC110X_STATE_CALIBRATE:
|
||||
case CC110X_STATE_TX_MODE:
|
||||
/* TX still in progress, or hasn't even started yet */
|
||||
return NETDEV_NO_EVENT;
|
||||
case CC110X_STATE_IDLE:
|
||||
cc110x_enter_rx_mode(dev);
|
||||
return NETDEV_EVENT_TX_COMPLETE;
|
||||
case CC110X_STATE_TXFIFO_UNDERFLOW:
|
||||
DEBUG("[cc110x] ISR: TX FIFO underflown.\n");
|
||||
break;
|
||||
default:
|
||||
DEBUG("[cc110x] ISR: Unknown state during TX.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
cc110x_enter_rx_mode(dev);
|
||||
/* TX timeout is the only TX-related error event known to RIOT */
|
||||
return NETDEV_EVENT_TX_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Refill the TX-FIFO
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
*/
|
||||
static netdev_event_t cc110x_tx_continue(cc110x_t *dev)
|
||||
{
|
||||
uint8_t in_fifo;
|
||||
|
||||
cc110x_read_reliable(dev, CC110X_REG_TXBYTES, &in_fifo);
|
||||
|
||||
/* most significant bit indicates TXFIFO underflow, see page 94 in the
|
||||
* data sheet
|
||||
*/
|
||||
if (in_fifo & 0x80) {
|
||||
DEBUG("[cc110x] ISR: ERROR: TX-FIFO underflown, ISR too slow\n");
|
||||
/* Abort: Flush TX and go back to RX */
|
||||
cc110x_cmd(dev, CC110X_STROBE_IDLE);
|
||||
cc110x_cmd(dev, CC110X_STROBE_FLUSH_TX);
|
||||
cc110x_enter_rx_mode(dev);
|
||||
return NETDEV_EVENT_TX_TIMEOUT;
|
||||
}
|
||||
|
||||
uint8_t to_write = CC110X_FIFO_SIZE - in_fifo;
|
||||
|
||||
if (to_write == 0) {
|
||||
/* ISR came to early, nothing to do yet */
|
||||
return NETDEV_NO_EVENT;
|
||||
}
|
||||
|
||||
uint8_t left = dev->buf.len - dev->buf.pos;
|
||||
to_write = (left < to_write) ? left : to_write;
|
||||
|
||||
cc110x_burst_write(dev, CC110X_MULTIREG_FIFO,
|
||||
dev->buf.data + dev->buf.pos, to_write);
|
||||
dev->buf.pos += to_write;
|
||||
|
||||
if (dev->buf.pos == dev->buf.len) {
|
||||
/* All data send to the transceiver, now waiting for transceiver to
|
||||
* complete transmission
|
||||
*/
|
||||
dev->state = CC110X_STATE_TX_COMPLETING;
|
||||
/* Disable GDO2, as we do not need to further feed TX FIFO */
|
||||
cc110x_write(dev, CC110X_REG_IOCFG2, CC110X_GDO_CONSTANT_LOW);
|
||||
}
|
||||
|
||||
return NETDEV_NO_EVENT;
|
||||
}
|
||||
|
||||
void cc110x_isr(netdev_t *netdev)
|
||||
{
|
||||
cc110x_t *dev = (cc110x_t *)netdev;
|
||||
/* We don't want to create events while device descriptor is acquired, to
|
||||
* prevent a dead lock. (Currently e.g. on NETDEV_EVENT_RX_COMPLETE the
|
||||
* upper layer will immediately call netdev_driver_t::recv(), which in
|
||||
* turn wants to operate on the device descriptor. We could rely on this
|
||||
* behaviour by skipping cc110x_acquire() there, but the driver would break
|
||||
* when upper layer behaviour is changed. By moving the event notification
|
||||
* at the end of the ISR (end after cc110x_release()), the driver becomes
|
||||
* agnostic to which behaviour the upper layer implements.)
|
||||
*/
|
||||
netdev_event_t post_isr_event = NETDEV_NO_EVENT;
|
||||
|
||||
if (cc110x_acquire(dev) != SPI_OK) {
|
||||
DEBUG("[cc110x] ISR: CRITICAL ERROR: Couldn't acquire device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Disable IRQs in a coarse manner, instead of doing so any time the
|
||||
* IOCFGx configuration registers are changed. (This should be less
|
||||
* bug prone.)
|
||||
*/
|
||||
gpio_irq_disable(dev->params.gdo0);
|
||||
gpio_irq_disable(dev->params.gdo2);
|
||||
|
||||
switch (dev->state) {
|
||||
case CC110X_STATE_RX_MODE:
|
||||
if (gpio_read(dev->params.gdo0) || gpio_read(dev->params.gdo2)) {
|
||||
dev->state = CC110X_STATE_RECEIVING;
|
||||
dev->buf.pos = dev->buf.len = 0;
|
||||
}
|
||||
break;
|
||||
case CC110X_STATE_RECEIVING:
|
||||
post_isr_event = cc110x_rx_continue(dev);
|
||||
break;
|
||||
case CC110X_STATE_TX_MODE:
|
||||
post_isr_event = cc110x_tx_continue(dev);
|
||||
break;
|
||||
case CC110X_STATE_TX_COMPLETING:
|
||||
post_isr_event = cc110x_tx_done(dev);
|
||||
break;
|
||||
default:
|
||||
DEBUG("[cc110x] ISR: CRITICAL ERROR: No interrupt expected "
|
||||
"for current state\n");
|
||||
/* Go back to RX and pray that solved the problem */
|
||||
cc110x_enter_rx_mode(dev);
|
||||
}
|
||||
|
||||
/* Re-enable IRQs again, unless device state */
|
||||
gpio_irq_enable(dev->params.gdo0);
|
||||
gpio_irq_enable(dev->params.gdo2);
|
||||
cc110x_release(dev);
|
||||
/* Pass event to uper layer, if needed */
|
||||
if (post_isr_event != NETDEV_NO_EVENT) {
|
||||
dev->netdev.event_callback(&dev->netdev, post_isr_event);
|
||||
}
|
||||
}
|
330
drivers/cc110x/cc110x_settings.c
Normal file
330
drivers/cc110x/cc110x_settings.c
Normal file
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief On-chip settings for the TI CC1100/CC1101 transceiver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_internal.h"
|
||||
|
||||
const char cc110x_conf[CC110X_CONF_SIZE] = {
|
||||
/*
|
||||
* IOCFG2; default: 0x29 (CHIP_RDYn)
|
||||
* Invert GDO2: off,
|
||||
* GDO2: Go high when RX data should be read
|
||||
*
|
||||
* Why not default?
|
||||
* GDO2 will be used to be notified about FIFO events (e.g. refilling TX
|
||||
* FIFO is needed during transmission, reading from RX FIFO is needed
|
||||
* during reception)
|
||||
*/
|
||||
CC110X_GDO_ON_RX_DATA,
|
||||
/*
|
||||
* IOCFG1; default: 0x2E (3-state)
|
||||
* Invert GDO1: off,
|
||||
* GDO1: 3-state (required when SPI interface is shared with other devices)
|
||||
*/
|
||||
0x2E,
|
||||
/*
|
||||
* IOCFG0; default: 0x3F (CLK_XOSC/192)
|
||||
* Invert GDO0: off,
|
||||
* GDO0: Go high on PLL in lock
|
||||
*
|
||||
* Why not default?
|
||||
* GDO0 will be used to be notified when a packet is coming while in RX
|
||||
* mode (will go high during transmission) and when sending is completed
|
||||
* while in TX (remains high during transmission and will go back low when
|
||||
* done).
|
||||
*/
|
||||
CC110X_GDO_ON_TRANSMISSION,
|
||||
/*
|
||||
* FIFOTHR; default: 0x07
|
||||
* TEST1 = 0x31 and TEST2 = 0x88 when waking up from SLEEP,
|
||||
* 0dB RX attenuation,
|
||||
* threshold for FIFOs: TX FIFO = 33, RX FIFO = 32
|
||||
*/
|
||||
0x07,
|
||||
/*
|
||||
* SYNC1, SYNC0; defaults: 0xD3, 0x91
|
||||
* Use 0xD3,0x91 as sync word
|
||||
*/
|
||||
0xD3, /*< SYNC1 */
|
||||
0x91, /*< SYNC0 */
|
||||
/*
|
||||
* PKTLEN; default: 0xFF
|
||||
* Packet length in bytes in fixed length mode, else maximum length
|
||||
*/
|
||||
0xff,
|
||||
/*
|
||||
* PKTCTRL1; default: 0x04
|
||||
* PQT: Accept all sync words, regardless of preamble quality
|
||||
* CRC_AUTOFLUSH: Do not auto-flush RX FIFO on incorrect CRC
|
||||
* APPEND_STATUS: Do not add 2 bytes of status information in RX FIFO
|
||||
* ADDR_CHK: Filter incoming frames in hardware by address: Only frames
|
||||
* with destination address 0x00 (broadcast) or with with the
|
||||
* layer-2 address of the transceiver are accepted.
|
||||
*
|
||||
* Why not default?
|
||||
* - The RSSI, LQI and CRC info are also available via status registers.
|
||||
* Thus, it is not worth to sacrifice two bytes of RX FIFO for it.
|
||||
* - Hardware address filtering could reduce the number IRQs generated
|
||||
* (e.g. a huge frame is dropped before it fully received) which reduces
|
||||
* the system's load. Thus, it is enabled.
|
||||
*/
|
||||
0x02,
|
||||
/*
|
||||
* PKTCTRL0; default: 0x45
|
||||
* Data whitening enabled, use RX/TX FIFOs, CRC enabled,
|
||||
* variable packet length
|
||||
*/
|
||||
0x45,
|
||||
/*
|
||||
* ADDR; default: 0x00
|
||||
* Address will overwritten later
|
||||
*
|
||||
* Why not default?
|
||||
* 0x00 is used as broadcast address. Using it would increase chance to
|
||||
* receive message during device initialization and thus power consumption.
|
||||
*/
|
||||
0xFF,
|
||||
/*
|
||||
* CHANNR; default: 0x00
|
||||
* Channel number 0 by default
|
||||
*/
|
||||
0x00,
|
||||
/*
|
||||
* FSCTRL1; default: 0x0C
|
||||
* Intermediate frequency: 0x0C * 26MHz / 1024 = 304.7kHz
|
||||
*
|
||||
* Why not defaults?
|
||||
* See MDMCFG4, MDMCFG3
|
||||
*/
|
||||
0x0C,
|
||||
/*
|
||||
* FSCTRL0; default: 0x00
|
||||
* Frequency offset to base frequency: 0kHz
|
||||
*/
|
||||
0x00,
|
||||
/*
|
||||
* FREQ2, FREQ1, FREQ0; defaults: 0x1E, 0xC4, 0xEC
|
||||
* 0x2146E4 * 26MHz / 65536 = 865.1998 MHz (LoRa Channel 10)
|
||||
*
|
||||
* Why not defaults?
|
||||
* Default is 800.000 MHz, which is not in a license free frequency band.
|
||||
* Using LoRa channel 10 instead.
|
||||
*/
|
||||
0x21, /*< FREQ2 */
|
||||
0x46, /*< FREQ1 */
|
||||
0xE4, /*< FREQ0 */
|
||||
/*
|
||||
* MDMCFG4, MDMCFG3; defaults: 0x8C, 0x22
|
||||
* 541.67 kHz channel filter bandwidth,
|
||||
* 249.94 kBaud symbol rate
|
||||
*
|
||||
* Why not defaults?
|
||||
* Using 250 kBaud (==> 250kBit/s when 2-FSK/GFSK/ASK) to compete with
|
||||
* 802.15.4 in data rate.
|
||||
* Using settings listed in Table 7 (pages 12ff in the data sheet):
|
||||
* - 250 kBaud
|
||||
* - GFSK modulation
|
||||
* - 304 kHz IF frequency
|
||||
* - 540 kHz channel filter bandwidth
|
||||
* - 127 kHz deviation
|
||||
*/
|
||||
0x2D, /*< MDMCFG4 */
|
||||
0x3B, /*< MDMCFG3 */
|
||||
/*
|
||||
* MDMCFG2; default: 0x02
|
||||
* DC blocking filter on,
|
||||
* GFSK modulation,
|
||||
* no manchester code,
|
||||
* Enable RX when 30 bits of the 32 bits sync word are received correctly
|
||||
*
|
||||
* Why not default?
|
||||
* Default expects all 16 bits of a two byte sync word to be correctly
|
||||
* received. The data sheet recommends to expect 30 bits of a four byte
|
||||
* sync word to be correctly received instead (see section 15 on page 37),
|
||||
* so we go for this.
|
||||
*
|
||||
* Using GFSK instead of 2-FSK reduces the modulated spectrum width and is
|
||||
* suggested in Table 7 of the datasheet, see MDMCFG4, MDMCFG3
|
||||
*/
|
||||
0x13,
|
||||
/*
|
||||
* MDMCFG1, MDMCFG0; defaults: 0x22, 0xF8
|
||||
* FEC disabled,
|
||||
* 4 preamble bytes,
|
||||
* 49.99 kHz distance between channel centre frequencies (closest to 50kHz)
|
||||
*
|
||||
* Why not defaults?
|
||||
* This driver uses an translation layer between physical channels (with
|
||||
* 50 kHz distance) and "virtual" channel numbers as seen outside of the
|
||||
* driver. This allows to set the frequency in a range of 12.75 MHz with
|
||||
* a resolution of 50kHz - this seems to allow to configure all desired
|
||||
* channel layouts.
|
||||
*/
|
||||
0x20, /*< MDMCFG1 */
|
||||
0xF8, /*< MDMCFG0 */
|
||||
/*
|
||||
* DEVIATN; default: 0x47
|
||||
* Deviation of frequency to encode data: +- 126.953kHz in 2-FSK/4-FSK/GFSK
|
||||
*
|
||||
* Why not default?
|
||||
* Higher deviation required for reliable operation at 250 kbps data rate.
|
||||
*/
|
||||
0x62,
|
||||
/*
|
||||
* MCSM2; default: 0x07
|
||||
* No direct RX termination on RSSI measurement (only for ASK/OOK),
|
||||
* on RX timeout check for sync word and ignore PQI,
|
||||
* no RX timeout
|
||||
*/
|
||||
0x07,
|
||||
/*
|
||||
* MCSM1; default: 0x30
|
||||
* CCA: Enter TX even when channel is detected busy
|
||||
* go to idle after packet received,
|
||||
* go to idle after packet sent
|
||||
*
|
||||
* Why not default?
|
||||
* By default the transceiver refuses to enter TX when the channel is
|
||||
* detected busy. While this is desired, checking if TX was successfully
|
||||
* entered is too slow and generated interrupts on the GDO2 pin are easily
|
||||
* missed. However, reading the carrier sense value in the PKTSTATUS
|
||||
* registers allows to implement this feature in software in a faster
|
||||
* way. In addition to not missing GDO2 interrupts, this allows the send
|
||||
* function to give feedback right away when the channel is busy.
|
||||
*/
|
||||
0x00,
|
||||
/*
|
||||
* MCSM0; default: 0x04
|
||||
* Auto calibration: Disabled, driver has to manage
|
||||
* delay to allow supply voltage to stabilize: 149-155 microseconds
|
||||
* pin radio control option is off,
|
||||
* oscillator is off in sleep state
|
||||
*
|
||||
* Why not default?
|
||||
* Using 149µs-155µs instead of the default 37µs-39µs as PO_TIMEOUT is
|
||||
* encouraged by the data sheet for robust operation
|
||||
*/
|
||||
0x08,
|
||||
/*
|
||||
* FOCCFG; default: 0x36
|
||||
* Freeze frequency offset compensation etc until CS high: yes
|
||||
* frequency compensation loop gain before sync word: 3K
|
||||
* frequency compensation loop gain after sync word: K/2
|
||||
* saturation point for frequency offset compensation: 25% channel bandwidth
|
||||
* (incompatible with ASK/OOK)
|
||||
*/
|
||||
0x36,
|
||||
/*
|
||||
* BSCFG; default: 0x6C
|
||||
* Clock recovery feedback loop integral gain before sync word: 2K_i,
|
||||
* clock recovery feedback loop proportional gain before sync word: 3K_p,
|
||||
* clock recovery feedback loop integral gain after sync word: K_i/2
|
||||
* clock recovery feedback loop proportional gain after sync word: K_p,
|
||||
* data rate offset compensation: Disabled
|
||||
*/
|
||||
0x6C,
|
||||
/*
|
||||
* AGCCTRL2; default: 0x03
|
||||
* Maximum allowable DVGA gain: No limitation, maximum DVGA gain can be used
|
||||
* Maximum allowable LNA + LNA2 gain: No limitation
|
||||
* target amplitude from channel filter: 33 dB (default)
|
||||
*/
|
||||
0x03,
|
||||
/*
|
||||
* AGCCTRL1; default: 0x40
|
||||
* LNA priority: Decrease LNA gain first, start decreasing LNA2 gain when
|
||||
* LNA gain reached minimum
|
||||
* Relative carrier sense threshold: Disabled
|
||||
* Absolute carrier sense threshold: At MAGN_TARGET
|
||||
*/
|
||||
0x40,
|
||||
/*
|
||||
* AGCCTRL0; default: 0x91
|
||||
* HYST_LEVEL: Medium hysteresis, medium asymmetric dead zone, medium gain
|
||||
* Adjust gain after how many channel filter samples: After 16 samples
|
||||
* Freeze AGC gain: Never; perform gain adjustments as required
|
||||
* FILTER_LENGTH:
|
||||
* - 16 channel filter samples when 2-FSK, 4-FSK, or MSK is used
|
||||
* - 8 dB decision baundry when OOK/ASK is used
|
||||
*/
|
||||
0x91,
|
||||
/*
|
||||
* WOREVT1, WOREVT0, WORCTRL; defaults: 0x87, 0x6B, 0xF8
|
||||
* Event0 Timeout: 1.000 seconds
|
||||
* RC_PD: 1 (The datasheet is quite cryptic regarding this setting)
|
||||
* Event1 Timeout: 1.333ms - 1.385ms (depending on crystal frequency)
|
||||
* RC oscillator calibration: Enabled
|
||||
* WOR_RES: 0 (Relevant for Event0 resolution and maximum timeout)
|
||||
*/
|
||||
0x87, /*< WOREVT1 */
|
||||
0x6B, /*< WOREVT0 */
|
||||
0xF8, /*< WORCTRL */
|
||||
/*
|
||||
* FREND1; default: 0x56
|
||||
* LNA_CURRENT: 0b01
|
||||
* LNA2MIX_CURRENT: 0b01
|
||||
* LODIV_BUF_CURRENT_RW: 0b01
|
||||
* MIX_CURRENT: 0b10
|
||||
*/
|
||||
0x56,
|
||||
/*
|
||||
* FREND0; default: 0x10
|
||||
* LODIV_BUF_CURRENT_TX: 0b01
|
||||
* Index in PA_POWER table (in 0..7, default is 0): 4 (0dBm)
|
||||
*
|
||||
* Why not default:
|
||||
* Use a reasonable TX power level instead of the lowest.
|
||||
*/
|
||||
0x14,
|
||||
/*
|
||||
* FSCAL3, FSCAL2, FSCAL1, FSCAL0; defaults: 0xA9, 0x0A, 0x20, 0x0d
|
||||
* These values store calibration date of the CC1100/CC1101 transceiver.
|
||||
* Once the transceiver performs a calibration, those registers are updated
|
||||
* with the new calibration data. In a "stable" environment (e.g. constant
|
||||
* channel/frequency, stable humidity, temperature, supply voltage etc.)
|
||||
* the obtained values could be written to the transceiver and calibration
|
||||
* could be turned off completely.
|
||||
*
|
||||
* Fast channel hopping could be performed by obtaining the FSCAL1
|
||||
* calibration data for each channel and storing it in the MCU's RAM.
|
||||
* The other calibration values is frequency independent according to the
|
||||
* data sheet, but depends on temperature etc.
|
||||
*
|
||||
* Once the FSCAL1 values for each channel are stored, the calibration can
|
||||
* be disabled and the stored FSCAL1 data can be uploaded for each channel
|
||||
* hop. A re-calibration from time to time is suggested to cope with changes
|
||||
* in the environment, e.g. in temperature or supply voltage.
|
||||
*
|
||||
* Why not defaults?
|
||||
* Using "magic" values obtained with SmartRF Studio software for 868 MHz
|
||||
* band.
|
||||
*/
|
||||
0xEA, /*< FSCAL3: charge pump current calibration, frequency independent */
|
||||
0x2A, /*< FSCAL2: VCO current calibration, frequency independent */
|
||||
0x00, /*< FSCAL1: VCO capacitance calibration, frequency dependent */
|
||||
0x1F, /*< FSCAL0: "Magic number", use SmartRF Studio to obtain */
|
||||
/*
|
||||
* RCCTRL1, RCCTRL0; defaults: 0x41, 0x00
|
||||
* RC oscillator configuration, no explanation given in data sheet.
|
||||
*/
|
||||
0x41, /*< RCCTRL1 */
|
||||
0x00, /*< RCCTRL0 */
|
||||
};
|
||||
|
||||
const char cc110x_magic_registers[3] = { 0x88, 0x31, 0x09 };
|
49
drivers/cc110x/include/cc110x_calibration.h
Normal file
49
drivers/cc110x/include/cc110x_calibration.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Calibration related functions of the CC110x transceiver driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_CALIBRATION_H
|
||||
#define CC110X_CALIBRATION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Perform a recalibration of the transceiver
|
||||
*
|
||||
* @param dev The device descriptor of the transceiver
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EIO Failed
|
||||
*
|
||||
* @pre @p dev has been acquired using @ref cc110x_acquire
|
||||
* @pre Transceiver is in IDLE state
|
||||
* @post On success @p dev is still acquired, the caller has to release
|
||||
* it. On failure the SPI bus is **already** **released**
|
||||
* @post Transceiver is again in IDLE state, calibration has been
|
||||
* performed and calibration data has been backed up on MCU.
|
||||
*/
|
||||
int cc110x_recalibrate(cc110x_t *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_CALIBRATION_H */
|
||||
/** @} */
|
213
drivers/cc110x/include/cc110x_communication.h
Normal file
213
drivers/cc110x/include/cc110x_communication.h
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Functions to communicate with the CC1100/CC1101 transceiver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_COMMUNICATION_H
|
||||
#define CC110X_COMMUNICATION_H
|
||||
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/spi.h"
|
||||
#include "cc110x.h"
|
||||
#include "cc110x_constants.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Acquire the SPI interface of the transceiver and configure it
|
||||
*
|
||||
* @retval SPI_OK Success
|
||||
* @retval SPI_NOMODE SPI mode 0 not supported by MCU
|
||||
* @retval SPI_NOCLK SPI clock given in @ref cc110x_params_t is not supported
|
||||
*
|
||||
* @pre @ref cc110x_power_on has be called before calling this function.
|
||||
* (Only needed *once* when the driver initializes.)
|
||||
*/
|
||||
static inline int cc110x_acquire(cc110x_t *dev)
|
||||
{
|
||||
return spi_acquire(dev->params.spi, dev->params.cs, SPI_MODE_0,
|
||||
dev->params.spi_clk);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release the SPI interface of the transceiver
|
||||
*/
|
||||
static inline void cc110x_release(cc110x_t *dev)
|
||||
{
|
||||
spi_release(dev->params.spi);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a single configuration/status register from the transceiver
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to read the register from
|
||||
* @param addr Address of the register to read
|
||||
* @param dest Where to store the received register content
|
||||
*
|
||||
* @return The received status byte
|
||||
*
|
||||
* @pre @p dest points to one byte of writeable memory
|
||||
* @warning Race condition: SPI access to status registers can occur while
|
||||
* their content is changed, resulting in corrupted data being
|
||||
* retrieved. @ref cc110x_read_reliable provides reliable access
|
||||
* to status registers and should be used to read the TXBYTES,
|
||||
* RXBYTES, MARCSTATE, LQI, RSSI, WORTIME1 and WORTIME0 status
|
||||
* registers. (See Silicon Errata from 2015 at pages 4ff.)
|
||||
* (In IDLE state LQI and RSSI can be read safely using this
|
||||
* function.)
|
||||
* @warning The received status byte is occasionally corrupted. (See
|
||||
* Silicon Errata from 2015 at pages 4ff.) Use @ref cc110x_status
|
||||
* To get the status byte in a reliable way.
|
||||
*/
|
||||
uint8_t cc110x_read(cc110x_t *dev, uint8_t addr, uint8_t *dest);
|
||||
|
||||
/**
|
||||
* @brief Read a single status register from the transceiver reliable
|
||||
*
|
||||
* This function has more overhead than @ref cc110x_read, but it is the only
|
||||
* reliable way to access frequently updated status registers.
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to read the register from
|
||||
* @param addr Address of the register to read
|
||||
* @param dest Where to store the received register content
|
||||
*
|
||||
* @return The received status byte
|
||||
*
|
||||
* @pre @p dest points to one byte of writeable memory
|
||||
* @warning The received status byte is occasionally corrupted. (See
|
||||
* Silicon Errata from 2015 at pages 4ff.) Use @ref cc110x_status
|
||||
* To get the status byte in a reliable way.
|
||||
*/
|
||||
uint8_t cc110x_read_reliable(cc110x_t *dev, uint8_t addr, uint8_t *dest);
|
||||
|
||||
/**
|
||||
* @brief Write to a single configuration register on the transceiver
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to write byte to
|
||||
* @param addr Address of the register to write to
|
||||
* @param data Data to write
|
||||
*
|
||||
* @return The received status byte
|
||||
*
|
||||
* @pre @p addr <= 0x2e (@ref CC110X_REG_TEST0)
|
||||
* @warning Writing to status registers is impossible (==> precondition)
|
||||
* @warning The received status byte is occasionally corrupted. (See
|
||||
* Silicon Errata from 2015 at pages 4ff.) Use @ref cc110x_status
|
||||
* To get the status byte in a reliable way.
|
||||
*/
|
||||
uint8_t cc110x_write(cc110x_t *dev, uint8_t addr, uint8_t data);
|
||||
|
||||
/**
|
||||
* @brief Burst-read a bunch of configuration registers from the transceiver
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to read from
|
||||
* @param addr Address to start reading from
|
||||
* @param dest Destination buffer to store the received data to
|
||||
* @param len Number of bytes to read starting from @p addr
|
||||
*
|
||||
* @return The received status byte
|
||||
*
|
||||
* @pre @p dest points to a pre-allocated buffer of >= @p len bytes
|
||||
* @pre @p addr + @p len <= 0x2e (@ref CC110X_REG_TEST0)
|
||||
* @warning Burst read access from status registers is impossible
|
||||
* (==> second precondition)
|
||||
* @warning The received status byte is occasionally corrupted. (See
|
||||
* Silicon Errata from 2015 at pages 4ff.) Use @ref cc110x_status
|
||||
* To get the status byte in a reliable way.
|
||||
*/
|
||||
uint8_t cc110x_burst_read(cc110x_t *dev, uint8_t addr, void *dest, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Burst-write to a bunch of configuration registers on the transceiver
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to write
|
||||
* @param addr Address to start writing to
|
||||
* @param src Buffer holding the configuration to write
|
||||
* @param len Number of registers to write to
|
||||
*
|
||||
* @return The received status byte
|
||||
*
|
||||
* @pre @p src points to @p len bytes of readable memory
|
||||
* @pre @p addr + @p len <= 0x2e (@ref CC110X_REG_TEST0)
|
||||
* @warning Writes to status registers is impossible
|
||||
* (==> second precondition)
|
||||
* @warning The received status byte is occasionally corrupted. (See
|
||||
* Silicon Errata from 2015 at pages 4ff.) Use @ref cc110x_status
|
||||
* to get the status byte in a reliable way.
|
||||
*/
|
||||
uint8_t cc110x_burst_write(cc110x_t *dev, uint8_t addr,
|
||||
const void *src, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Send a command to the transceiver
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to send the command to
|
||||
* @param cmd_strobe Command to send
|
||||
*
|
||||
* @return The received status byte
|
||||
*
|
||||
* @warning The received status byte is occasionally corrupted. (See
|
||||
* Silicon Errata from 2015 at pages 4ff.) Use
|
||||
* @ref cc110x_status to get the status byte in a reliable
|
||||
* way.
|
||||
*/
|
||||
uint8_t cc110x_cmd(cc110x_t *dev, uint8_t cmd_strobe);
|
||||
|
||||
/**
|
||||
* @brief Get the transceivers status byte in a reliable way
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver to get the status from
|
||||
*
|
||||
* @return The received status byte
|
||||
*/
|
||||
uint8_t cc110x_status(cc110x_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Wakes up the transceiver from "Sleep" or "Crystal oscillator off"
|
||||
* state and waits until the crystal has stabilized
|
||||
*
|
||||
* Thus function clears the CS pin, which triggers a transition from the "Sleep"
|
||||
* or "Crystal oscillator off" states (see Figure 13 on page 28 in the data
|
||||
* sheet).
|
||||
*
|
||||
* If the crystal was off (only in above mentioned states), the MCU must wait
|
||||
* for the transceiver to become ready *before* any SPI transfer is initiated.
|
||||
* In all other states, CS pin can be pulled low and SPI transfer can start
|
||||
* right away (see section 10 on page 29 in the data sheet). This driver will
|
||||
* never disable the transceivers crystal, so this function has to be called
|
||||
* only once when the transceiver is powered on.
|
||||
*
|
||||
* The transceiver will signal that it is available by pulling the MISO pin
|
||||
* low (section 10 on page 29 in the data sheet), which does not take longer
|
||||
* than 150 microseconds (see Table 22 on page 30 in the data sheet). Instead
|
||||
* of messing with the SPI interface, this driver simply waits for this upper
|
||||
* bound, as suggested in the note below Table 22 on page 30 in the data sheet.
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EIO Couldn't pull the CS pin down (@ref cc110x_params_t::cs)
|
||||
*/
|
||||
int cc110x_power_on(cc110x_t *dev);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_COMMUNICATION_H */
|
||||
/** @} */
|
522
drivers/cc110x/include/cc110x_constants.h
Normal file
522
drivers/cc110x/include/cc110x_constants.h
Normal file
@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Constants for the CC1100/CC1101 driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_CONSTANTS_H
|
||||
#define CC110X_CONSTANTS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Command strobes of the CC1100/CC1101 transceiver
|
||||
*
|
||||
* See Table 42 on page 67 in the data sheet. Only values relevant to the
|
||||
* driver are listed.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Reset chip (SRES)
|
||||
*/
|
||||
#define CC110X_STROBE_RESET 0x30
|
||||
|
||||
/**
|
||||
* @brief Calibrate frequency synthesizer and turn it off (SCAL)
|
||||
*/
|
||||
#define CC110X_STROBE_CALIBRATE 0x33
|
||||
|
||||
/**
|
||||
* @brief Go to RX state (SRX)
|
||||
*
|
||||
* Requires frequency calibration first
|
||||
*/
|
||||
#define CC110X_STROBE_RX 0x34
|
||||
|
||||
/**
|
||||
* @brief Go to TX state (STX)
|
||||
*
|
||||
* Requires frequency calibration first
|
||||
*/
|
||||
#define CC110X_STROBE_TX 0x35
|
||||
|
||||
/**
|
||||
* @brief Go to IDLE state (SIDLE)
|
||||
*/
|
||||
#define CC110X_STROBE_IDLE 0x36
|
||||
|
||||
/**
|
||||
* @brief Go to power down state once CS goes high (SPWD)
|
||||
*/
|
||||
#define CC110X_STROBE_OFF 0x39
|
||||
|
||||
/**
|
||||
* @brief Flush RX fifo (SFRX)
|
||||
*
|
||||
* Only valid in IDLE or in RXFIO_OVERFLOW states
|
||||
*/
|
||||
#define CC110X_STROBE_FLUSH_RX 0x3A
|
||||
|
||||
/**
|
||||
* @brief Flush TX fifo (SFTX)
|
||||
*
|
||||
* Only valid in IDLE or in TXFIO_OVERFLOW states
|
||||
*/
|
||||
#define CC110X_STROBE_FLUSH_TX 0x3B
|
||||
|
||||
/**
|
||||
* @brief Get the status byte (SNOP)
|
||||
*/
|
||||
#define CC110X_STROBE_STATUS 0x3D
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Access modifies for accessing configuration/status registers
|
||||
*
|
||||
* See Table 45 on pages 69ff in the data sheet. These modifies need to be
|
||||
* xor'ed with the address of the register.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Access modifier to write a single byte to a configuration register
|
||||
*
|
||||
* | read bit (`0x80`) | burst access bit (`0x40`) |
|
||||
* |-------------------|---------------------------|
|
||||
* | `0` (= write) | `0` (= no burst access) |
|
||||
*/
|
||||
#define CC110X_SINGLE_BYTE_WRITE 0x00
|
||||
|
||||
/**
|
||||
* @brief Access modifier to write multiple bytes at once to configuration
|
||||
* registers
|
||||
*
|
||||
* | read bit (`0x80`) | burst access bit (`0x40`) |
|
||||
* |-------------------|---------------------------|
|
||||
* | `0` (= write) | `1` (= burst access) |
|
||||
*/
|
||||
#define CC110X_BURST_WRITE 0x40
|
||||
|
||||
/**
|
||||
* @brief Access modifier to read a single byte from a configuration register
|
||||
*
|
||||
* | read bit (`0x80`) | burst access bit (`0x40`) |
|
||||
* |-------------------|---------------------------|
|
||||
* | `1` (= read) | `0` (= no burst access) |
|
||||
*/
|
||||
#define CC110X_SINGLE_BYTE_READ 0x80
|
||||
|
||||
/**
|
||||
* @brief Access modifier to read multiple bytes at once from configuration
|
||||
* registers
|
||||
*
|
||||
* | read bit (`0x80`) | burst access bit (`0x40`) |
|
||||
* |-------------------|---------------------------|
|
||||
* | `1` (= read) | `1` (= burst access) |
|
||||
*/
|
||||
#define CC110X_BURST_READ 0xC0
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name "Multi byte registers" of the CC1100/CC1101 transceiver
|
||||
*
|
||||
* See Table 45 on pages 69ff in the data sheet. These multi byte registers
|
||||
* have a special semantics, which is documented for each multi byte register
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Access to the PATABLE as multi byte register
|
||||
*
|
||||
* It is most convenient to read/write the whole 8 bytes of the PATABLE using a
|
||||
* burst access. The first single byte access after the CS pin is pulled low
|
||||
* will read from / write to the first byte, the second access the second byte,
|
||||
* and so on. As @ref cc110x_read and @ref cc110x_write pull the CS pin high
|
||||
* after the access, all but the first byte are only accessible using burst
|
||||
* access in this driver.
|
||||
*/
|
||||
#define CC110X_MULTIREG_PATABLE 0x3E
|
||||
|
||||
/**
|
||||
* @brief Access to the TX and RX FIFO as multi byte register
|
||||
*
|
||||
* A single byte read using @ref cc110x_read from the FIFO multi byte register
|
||||
* will retrieve and remove the next byte from the RX FIFO. A burst of *n* bytes
|
||||
* using @ref cc110x_burst_read will retrieve and remove the next *n* bytes.
|
||||
*
|
||||
* A single byte write using @ref cc110x_write will push one byte of data into
|
||||
* the TX FIFO. A multi byte write of *n* byte using @ref cc110x_burst_write
|
||||
* will push *n* bytes into the TX FIFO.
|
||||
*
|
||||
* @warning Reading the last byte from the RX-FIFO results in data corruption,
|
||||
* unless the whole frame was received. Thus, read all but the last
|
||||
* byte from the FIFO until the whole frame was received.
|
||||
*/
|
||||
#define CC110X_MULTIREG_FIFO 0x3F
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Configuration registers of the CC1100/CC1101 transceiver
|
||||
*
|
||||
* See Table 43 on pages 68ff in the data sheet. Only values relevant to the
|
||||
* driver are listed.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief First configuration register on the transceiver, used for burst
|
||||
* access to the whole configuration
|
||||
*/
|
||||
#define CC110X_CONF_START 0x00
|
||||
|
||||
/**
|
||||
* @brief GDO2 output pin configuration
|
||||
*/
|
||||
#define CC110X_REG_IOCFG2 0x00
|
||||
|
||||
/**
|
||||
* @brief GDO1 output pin configuration
|
||||
*/
|
||||
#define CC110X_REG_IOCFG1 0x01
|
||||
|
||||
/**
|
||||
* @brief GDO0 output pin configuration
|
||||
*/
|
||||
#define CC110X_REG_IOCFG0 0x02
|
||||
|
||||
/**
|
||||
* @brief Device address
|
||||
*/
|
||||
#define CC110X_REG_ADDR 0x09
|
||||
|
||||
/**
|
||||
* @brief Channel number
|
||||
*/
|
||||
#define CC110X_REG_CHANNR 0x0A
|
||||
|
||||
/**
|
||||
* @brief Intermediate frequency to use
|
||||
*/
|
||||
#define CC110X_REG_FSCTRL1 0x0B
|
||||
|
||||
/**
|
||||
* @brief Frequency control word, high byte
|
||||
*/
|
||||
#define CC110X_REG_FREQ2 0x0D
|
||||
|
||||
/**
|
||||
* @brief Frequency control word, middle byte
|
||||
*/
|
||||
#define CC110X_REG_FREQ1 0x0E
|
||||
|
||||
/**
|
||||
* @brief Frequency control word, low byte
|
||||
*/
|
||||
#define CC110X_REG_FREQ0 0x0F
|
||||
|
||||
/**
|
||||
* @brief Modem configuration (channel filter bandwidth and data rate)
|
||||
*/
|
||||
#define CC110X_REG_MDMCFG4 0x10
|
||||
|
||||
/**
|
||||
* @brief Modem configuration (data rate)
|
||||
*/
|
||||
#define CC110X_REG_MDMCFG3 0x11
|
||||
|
||||
/**
|
||||
* @brief Modem deviation setting
|
||||
*/
|
||||
#define CC110X_REG_DEVIATN 0x15
|
||||
|
||||
/**
|
||||
* @brief Front End TX Configuration
|
||||
*
|
||||
* Least three significant bits contain the current PA power setting.
|
||||
*/
|
||||
#define CC110X_REG_FREND0 0x22
|
||||
|
||||
/**
|
||||
* @brief Charge pump current calibration
|
||||
*
|
||||
* This value depends on the environment (e.g. temperature, supply voltage,
|
||||
* etc.), but not on the frequency. Thus, this value does not become obsolete
|
||||
* when changing the channel.
|
||||
*/
|
||||
#define CC110X_REG_FSCAL3 0x23
|
||||
|
||||
/**
|
||||
* @brief VCO current calibration
|
||||
*
|
||||
* This value depends on the environment (e.g. temperature, supply voltage,
|
||||
* etc.), but not on the frequency. Thus, this value does not become obsolete
|
||||
* when changing the channel.
|
||||
*/
|
||||
#define CC110X_REG_FSCAL2 0x24
|
||||
|
||||
/**
|
||||
* @brief VCO capacitance calibration
|
||||
*
|
||||
* This value is frequency depended. Thus, for fast channel hopping it has to
|
||||
* be obtained for each channel (by performing a calibration on that channel
|
||||
* and reading it out). Than the stored calibration data can be written to the
|
||||
* register when changing the channel.
|
||||
*/
|
||||
#define CC110X_REG_FSCAL1 0x25
|
||||
|
||||
/**
|
||||
* @brief Undocumented frequency calibration value
|
||||
*
|
||||
* For fast channel hopping this value can be ignored (see page 64ff in the
|
||||
* data sheet) - so it has to be frequency independent.
|
||||
*/
|
||||
#define CC110X_REG_FSCAL0 0x26
|
||||
|
||||
/**
|
||||
* @brief Unlock the temperature sensor by writing 0xBF to it
|
||||
*
|
||||
* Intended for production test, but who would complain about getting an
|
||||
* temperature sensor for free :-)
|
||||
*
|
||||
* @see @ref CC110X_GDO0_ANALOG_TEMPERATURE for details
|
||||
*/
|
||||
#define CC110X_REG_PTEST 0x2A
|
||||
|
||||
/**
|
||||
* @brief Magic value obtained with SmartRF Studio software
|
||||
*/
|
||||
#define CC110X_REG_TEST2 0x2C
|
||||
|
||||
/**
|
||||
* @brief Magic value obtained with SmartRF Studio software
|
||||
*/
|
||||
#define CC110X_REG_TEST1 0x2D
|
||||
|
||||
/**
|
||||
* @brief Magic value obtained with SmartRF Studio software
|
||||
*/
|
||||
#define CC110X_REG_TEST0 0x2E
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Status registers of the CC1100/CC1101 transceiver
|
||||
*
|
||||
* See Table 43 on pages 68ff in the data sheet. Only values relevant to the
|
||||
* driver are listed.
|
||||
*
|
||||
* @warning The burst access bit of these registers has to be set to distinguish
|
||||
* between command strobes and status registers. Thus, no burst access
|
||||
* to status registers is possible.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Part number
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
*/
|
||||
#define CC110X_REG_PARTNUM (0x30 | 0x40)
|
||||
|
||||
/**
|
||||
* @brief Version
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
*/
|
||||
#define CC110X_REG_VERSION (0x31 | 0x40)
|
||||
|
||||
/**
|
||||
* @brief Estimated link quality
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
*/
|
||||
#define CC110X_REG_LQI (0x33 | 0x40)
|
||||
|
||||
/**
|
||||
* @brief Received signal strength indication
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
*/
|
||||
#define CC110X_REG_RSSI (0x34 | 0x40)
|
||||
|
||||
/**
|
||||
* @brief Packet status, GDOx status
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
*/
|
||||
#define CC110X_REG_PKTSTATUS (0x38 | 0x40)
|
||||
|
||||
/**
|
||||
* @brief Number of bytes in the TX FIFO
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
* @warning The received value could be corrupted when reading it while it is
|
||||
* updated. Reading it out twice until both reads return the same
|
||||
* value is the suggested workaround for this hardware bug.
|
||||
*/
|
||||
#define CC110X_REG_TXBYTES (0x3A | 0x40)
|
||||
|
||||
/**
|
||||
* @brief Number of bytes available in the RX FIFO
|
||||
*
|
||||
* @warning Not accessible using burst reads
|
||||
* @warning The received value could be corrupted when reading it while it is
|
||||
* updated. Reading it out twice until both reads return the same
|
||||
* value is the suggested workaround for this hardware bug.
|
||||
*/
|
||||
#define CC110X_REG_RXBYTES (0x3B | 0x40)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Possible values for the IOCFG2, IOCFG1, and IOCFG0 configuration registers
|
||||
*
|
||||
* See Table 41 on page 62 in the data sheet. Only values relevant to the
|
||||
* driver are listed.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief GDOx goes HIGH when data has to be read from RX FIFO or when a packet
|
||||
* is fully received
|
||||
*
|
||||
* Depends on the threshold set for the RX-FIFO in the FIFOTHR configuration
|
||||
* register
|
||||
*/
|
||||
#define CC110X_GDO_ON_RX_DATA 0x01
|
||||
|
||||
/**
|
||||
* @brief GDOx goes LOW when data should be written to the TX FIFO
|
||||
*
|
||||
* Depends on the threshold set for the TX-FIFO in the FIFOTHR configuration
|
||||
* register
|
||||
*/
|
||||
#define CC110X_GDO_ON_TX_DATA 0x02
|
||||
|
||||
/**
|
||||
* @brief GDOx goes HIGH when a packet is received/send and back LOW when the
|
||||
* transmission is completed/aborted (e.g. wrong destination address)
|
||||
*/
|
||||
#define CC110X_GDO_ON_TRANSMISSION 0x06
|
||||
|
||||
/**
|
||||
* @brief GDOx goes HIGH when channel is clear for sending
|
||||
*
|
||||
* Depends on the CCA_MODE setting in the MCSM1 configuration register
|
||||
*/
|
||||
#define CC110X_GDO_ON_CHANNEL_CLEAR 0x09
|
||||
|
||||
/**
|
||||
* @brief GDOx goes HIGH when PLL is in lock
|
||||
*
|
||||
*/
|
||||
#define CC110X_GDO_ON_PLL_IN_LOCK 0x0A
|
||||
|
||||
|
||||
/**
|
||||
* @brief GDOx remains constantly LOW
|
||||
*/
|
||||
#define CC110X_GDO_CONSTANT_LOW 0x2F
|
||||
|
||||
/**
|
||||
* @brief GDOx remains constantly HIGH
|
||||
*/
|
||||
#define CC110X_GDO_CONSTANT_HIGH 0x6F
|
||||
|
||||
/**
|
||||
* @brief Repurpose GDO0 as analog temperature sensor in IDLE state
|
||||
*
|
||||
* This only works with GDO0 and only in IDLE state! Additionally, 0xBF has
|
||||
* to be written to configuration register PTEST when in IDLE state. Before
|
||||
* leaving IDLE state, PTEST should be restored to 0x7F.
|
||||
*/
|
||||
#define CC110X_GDO0_ANALOG_TEMPERATURE 0x80
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Bitmasks to access entries in the PKTSTATUS status register
|
||||
*
|
||||
* See page 94 in the data sheet.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Bitmask to get the GDO0 state from the PKTSTATUS status register value
|
||||
*/
|
||||
#define CC110X_PKTSTATUS_GDO0 0x01
|
||||
/**
|
||||
* @brief Bitmask to get the GDO2 state from the PKTSTATUS status register value
|
||||
*/
|
||||
#define CC110X_PKTSTATUS_GDO2 0x04
|
||||
/**
|
||||
* @brief Bitmask to get the SFD bit from the PKTSTATUS status register value
|
||||
* which is set while receiving a frame
|
||||
*/
|
||||
#define CC110X_PKTSTATUS_RECEIVING 0x08
|
||||
/**
|
||||
* @brief Bitmask to get the CCA bit from the PKTSTATUS status register value
|
||||
*/
|
||||
#define CC110X_PKTSTATUS_CCA 0x10
|
||||
/**
|
||||
* @brief Bitmask to get the Carrier Sense bit from the PKTSTATUS status
|
||||
* register value
|
||||
*/
|
||||
#define CC110X_PKTSTATUS_CS 0x40
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Values to write into the PTEST configuration register
|
||||
*
|
||||
* See page 91 in the data sheet. Only the two documented values are specified.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Default value of the PTEST configuration register.
|
||||
*/
|
||||
#define CC110X_PTEST_DEFAULT 0x7F
|
||||
/**
|
||||
* @brief Value to write in PTEST when reading the temperature.
|
||||
*
|
||||
* @see CC110X_GDO0_ANALOG_TEMPERATURE
|
||||
*/
|
||||
#define CC110X_PTEST_TEMPERATURE 0xBF
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Size of the RX and TX FIFO
|
||||
*/
|
||||
#define CC110X_FIFO_SIZE 64
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_CONSTANTS_H */
|
||||
/** @} */
|
83
drivers/cc110x/include/cc110x_internal.h
Normal file
83
drivers/cc110x/include/cc110x_internal.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Internal functions of the CC110x transceiver driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_INTERNAL_H
|
||||
#define CC110X_INTERNAL_H
|
||||
|
||||
#include "cc110x_calibration.h"
|
||||
#include "cc110x_communication.h"
|
||||
#include "cc110x_constants.h"
|
||||
#include "cc110x_netdev.h"
|
||||
#include "cc110x_rx_tx.h"
|
||||
#include "cc110x_settings.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Extract the device state from the status register value
|
||||
*
|
||||
* @param status Contents of the CC110x's status register
|
||||
* @return The state encoded in @p status
|
||||
*
|
||||
* The status register contains the device state at the bits 1 - 3
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* Format of the status byte
|
||||
* 0 1 2 3 4 5 6 7
|
||||
* +-+-+-+-+-+-+-+-+
|
||||
* |R|STATE| FIFO |
|
||||
* +-+-+-+-+-+-+-+-+
|
||||
*
|
||||
* R = Chip Ready bit (0 = ready, 1 = power and crystal are not yet stable)
|
||||
* STATE = The device state
|
||||
* FIFO = Number of bytes available in RX FIFO or (in TX mode) number of free
|
||||
* bytes
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Note: The FIFO has a size of 64 bytes. If more than 15 bytes are available
|
||||
* for reading in the FIFO (or more than 15 bytes can be written in TX), the
|
||||
* value will still show 15. This driver never uses this information, but
|
||||
* accesses a dedicated register for that.
|
||||
*/
|
||||
static inline cc110x_state_t cc110x_state_from_status(uint8_t status)
|
||||
{
|
||||
return (cc110x_state_t)((status >> 4) & 0x7);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Figure out of the transceiver is ready or still powering up
|
||||
* @param status Contents of the CC110x's status register
|
||||
* @retval 1 Transceiver is ready
|
||||
* @retval 0 *NOT* ready, still powering up
|
||||
*
|
||||
* @see cc110x_state_from_status
|
||||
*/
|
||||
static inline int cc110x_is_ready_from_status(uint8_t status)
|
||||
{
|
||||
return (status & 0x80) ? 0: 1;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_INTERNAL_H */
|
||||
/** @} */
|
47
drivers/cc110x/include/cc110x_netdev.h
Normal file
47
drivers/cc110x/include/cc110x_netdev.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Functions related to the netdev interface of the CC110x driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_NETDEV_H
|
||||
#define CC110X_NETDEV_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief RIOT's API to interact with the CC1100/CC1101 driver
|
||||
*/
|
||||
extern const netdev_driver_t cc110x_driver;
|
||||
|
||||
/**
|
||||
* @brief Interrupt handler to call on both edges of the GDO0 and GDO2 pins
|
||||
*
|
||||
* @param dev The device descriptor of the transceiver
|
||||
*
|
||||
* This interrupt handler requests that the cc110x ISR is called in thread
|
||||
* context
|
||||
*/
|
||||
void cc110x_on_gdo(void *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_NETDEV_H */
|
||||
/** @} */
|
99
drivers/cc110x/include/cc110x_params.h
Normal file
99
drivers/cc110x/include/cc110x_params.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief cc110x board specific configuration
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#ifndef CC110X_PARAMS_H
|
||||
#define CC110X_PARAMS_H
|
||||
|
||||
#include "board.h"
|
||||
#include "cc110x_settings.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Default parameters for the cc110x driver
|
||||
*
|
||||
* These values are based on the msba2 board
|
||||
* @{
|
||||
*/
|
||||
#ifndef CC110X_PARAM_SPI
|
||||
#define CC110X_PARAM_SPI SPI_DEV(0)
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_CS
|
||||
#define CC110X_PARAM_CS GPIO_PIN(1, 21)
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_GDO0
|
||||
#define CC110X_PARAM_GDO0 GPIO_PIN(0, 27)
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_GDO2
|
||||
#define CC110X_PARAM_GDO2 GPIO_PIN(0, 28)
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_SPI_CLOCK
|
||||
#define CC110X_PARAM_SPI_CLOCK SPI_CLK_5MHZ
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_L2ADDR
|
||||
#define CC110X_PARAM_L2ADDR CC110X_L2ADDR_AUTO
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_PATABLE
|
||||
#define CC110X_PARAM_PATABLE (&cc110x_patable_868mhz)
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_CONFIG
|
||||
#define CC110X_PARAM_CONFIG NULL
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAM_CHANNELS
|
||||
#define CC110X_PARAM_CHANNELS (&cc110x_chanmap_868mhz_lora)
|
||||
#endif
|
||||
|
||||
#ifndef CC110X_PARAMS
|
||||
#define CC110X_PARAMS { \
|
||||
.spi = CC110X_PARAM_SPI, \
|
||||
.spi_clk = CC110X_PARAM_SPI_CLOCK, \
|
||||
.cs = CC110X_PARAM_CS, \
|
||||
.gdo0 = CC110X_PARAM_GDO0, \
|
||||
.gdo2 = CC110X_PARAM_GDO2, \
|
||||
.l2addr = CC110X_PARAM_L2ADDR, \
|
||||
.patable = CC110X_PARAM_PATABLE, \
|
||||
.config = CC110X_PARAM_CONFIG, \
|
||||
.channels = CC110X_PARAM_CHANNELS, \
|
||||
}
|
||||
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name CC110X configuration
|
||||
* @brief Specifies the SPI bus and GPIOs connected to the CC110X transceiver
|
||||
*/
|
||||
static const cc110x_params_t cc110x_params[] = {
|
||||
CC110X_PARAMS
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* CC110X_PARAMS_H */
|
||||
/** @} */
|
47
drivers/cc110x/include/cc110x_rx_tx.h
Normal file
47
drivers/cc110x/include/cc110x_rx_tx.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Functions to related to RX/TX of the CC110x transceiver driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_RX_TX_H
|
||||
#define CC110X_RX_TX_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ISR to be called via @ref netdev_driver_t::isr
|
||||
*/
|
||||
void cc110x_isr(netdev_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Bring transceiver into RX mode
|
||||
*
|
||||
* @param dev The device descriptor of the transceiver
|
||||
*
|
||||
* @pre @p dev has been acquired using @ref cc110x_acquire
|
||||
* @post @p dev is still acquired, the caller has to release it
|
||||
*/
|
||||
void cc110x_enter_rx_mode(cc110x_t *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_RX_TX_H */
|
||||
/** @} */
|
170
drivers/cc110x/include/cc110x_settings.h
Normal file
170
drivers/cc110x/include/cc110x_settings.h
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Default settings of the TI CC1100/CC1101 transceiver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifndef CC110X_SETTINGS_H
|
||||
#define CC110X_SETTINGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The size of the configuration array for CC110X in bytes
|
||||
*/
|
||||
#define CC110X_CONF_SIZE 41
|
||||
|
||||
/**
|
||||
* @brief Length of the PATABLE
|
||||
*/
|
||||
#define CC110X_PATABLE_LEN 8
|
||||
|
||||
/**
|
||||
* @brief Configuration register values for CC1100/CC1101 transceivers
|
||||
*/
|
||||
extern const char cc110x_conf[CC110X_CONF_SIZE];
|
||||
|
||||
/**
|
||||
* @brief Magic numbers to write to the TEST2, TEST1 and TEST0 configuration
|
||||
* registers
|
||||
*
|
||||
* Those registers are poorly documented. The data sheet refers to SmartRF
|
||||
* Studio to obtain the correct magic numbers. Those values were identical
|
||||
* for the 433MHz, 868MHz and 915MHz band at GFSK, 250kBaud and 127kHz
|
||||
* deviation. Maybe by luck? So when adding a new base band or adjusting the
|
||||
* transceiver configuration, those numbers should be checked again with the
|
||||
* SmartRF Studio
|
||||
*/
|
||||
extern const char cc110x_magic_registers[3];
|
||||
|
||||
/**
|
||||
* @name Configuration data that specify the 8 available output power levels
|
||||
*
|
||||
* Source: Table 39 in the data sheet on page 60.
|
||||
*
|
||||
* | Output Power [dBm] | Setting 433 MHz | Setting 868 MHz | Setting 915 MHz |
|
||||
* |--------------------|-----------------|-----------------|-----------------|
|
||||
* | -30 | 0x12 | 0x03 | 0x03 |
|
||||
* | -20 | 0x0E | 0x0F | 0x0E |
|
||||
* | -15 | 0x1D | 0x1E | 0x1E |
|
||||
* | -10 | 0x34 | 0x27 | 0x27 |
|
||||
* | 0 | 0x60 | 0x50 | 0x8E |
|
||||
* | 5 | 0x84 | 0x81 | 0xCD |
|
||||
* | 7 | 0xC8 | 0xCB | 0xC7 |
|
||||
* | 10 | 0xC0 | 0xC2 | 0xC0 |
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief PATABLE values for the 433 MHz band
|
||||
*
|
||||
* Source: Table 39 in the data sheet on page 60.
|
||||
*/
|
||||
extern const cc110x_patable_t cc110x_patable_433mhz;
|
||||
/**
|
||||
* @brief PATABLE values for the 868 MHz band
|
||||
*
|
||||
* @note This is the default PATABLE. There is no need to upload this table
|
||||
* unless switching between different PATABLEs is required.
|
||||
*
|
||||
* Source: Table 39 in the data sheet on page 60.
|
||||
*/
|
||||
extern const cc110x_patable_t cc110x_patable_868mhz;
|
||||
/**
|
||||
* @brief PATABLE values for the 915 MHz band
|
||||
*
|
||||
* Source: Table 39 in the data sheet on page 60.
|
||||
*/
|
||||
extern const cc110x_patable_t cc110x_patable_915mhz;
|
||||
/** @} */
|
||||
|
||||
|
||||
/**
|
||||
* @name Channel mappings for CC110x transceivers
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Channel map for 50 kHz channels with a distance of 200 kHz in the
|
||||
* 433 MHz band
|
||||
*
|
||||
* This channel mapping assumes @ref cc110x_config_433mhz_38kbps_50khz is
|
||||
* used as configuration. @ref cc110x_chanmap_433mhz_50khz_alt is an alternative
|
||||
* channel map that does not overlap with this map. (This means devices with
|
||||
* the alternative channel map cannot communicate with devices using this one,
|
||||
* but also no interference is expected.)
|
||||
*/
|
||||
extern const cc110x_chanmap_t cc110x_chanmap_433mhz_50khz;
|
||||
/**
|
||||
* @brief Alternative channel map for 50 kHz channels with a distance of 200 kHz
|
||||
* in the 433 MHz band
|
||||
* @see cc110x_chanmap_433mhz_50khz
|
||||
*
|
||||
* This channel mapping assumes @ref cc110x_config_433mhz_38kbps_50khz is
|
||||
* used as configuration.
|
||||
*/
|
||||
extern const cc110x_chanmap_t cc110x_chanmap_433mhz_50khz_alt;
|
||||
/**
|
||||
* @brief Channel map for 5 non-overlapping 300 kHz channels in the 433 MHz band
|
||||
*
|
||||
* This channel mapping assumes @ref cc110x_config_433mhz_250kbps_300khz is
|
||||
* used as configuration. (Note: The distance between channels is in fact
|
||||
* 350 kHz instead of 300. This increased distance allows to cover the full
|
||||
* license free range and reduce the possibility of interference of adjacent
|
||||
* channels. Reducing the distance to 300kHz would still allow only 5 channels.)
|
||||
*/
|
||||
extern const cc110x_chanmap_t cc110x_chanmap_433mhz_300khz;
|
||||
/**
|
||||
* @brief Channel map for LoRa 868MHz channels 10 to 17 (available as 0 to 7).
|
||||
*
|
||||
* This channel mapping assumes @ref cc110x_config_868mhz_250kbps_300khz is used
|
||||
* as configuration.
|
||||
*/
|
||||
extern const cc110x_chanmap_t cc110x_chanmap_868mhz_lora;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Base frequency, channel bandwidth and data rate configurations for CC110x transceivers
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief CC110x configuration: 433MHz band, 38.4kbps data rate, 50 kHz channel
|
||||
* width
|
||||
*/
|
||||
extern const cc110x_config_t cc110x_config_433mhz_38kbps_50khz;
|
||||
/**
|
||||
* @brief CC110x configuration: 433MHz band, 250kbps data rate, 300 kHz channel
|
||||
* width
|
||||
*/
|
||||
extern const cc110x_config_t cc110x_config_433mhz_250kbps_300khz;
|
||||
/**
|
||||
* @brief CC110x configuration: 868MHz band, 250kbps data rate, 300 kHz channel
|
||||
* width
|
||||
*/
|
||||
extern const cc110x_config_t cc110x_config_868mhz_250kbps_300khz;
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_SETTINGS_H */
|
||||
/** @} */
|
633
drivers/include/cc110x.h
Normal file
633
drivers/include/cc110x.h
Normal file
@ -0,0 +1,633 @@
|
||||
/*
|
||||
* Copyright (C) 2013 INRIA
|
||||
* 2014 Freie Universität Berlin
|
||||
* 2015 Kaspar Schleiser <kaspar@schleiser.de>
|
||||
* 2018,2019 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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_cc110x CC1100/CC1100e/CC1101 Sub-GHz transceiver driver
|
||||
* @ingroup drivers_netdev
|
||||
*
|
||||
* This module contains the driver for the TI CC1100/CC110e/CC1101 Sub-GHz
|
||||
* transceivers.
|
||||
*
|
||||
* @warning How the CC1100/CC1101 operate can be configured quite sophistically.
|
||||
* This has the drawback, that configurations breaking laws and rules
|
||||
* are complete possible. Please make sure that the configured output
|
||||
* power, duty cycle, frequency range, etc. conform to the rules,
|
||||
* standards and laws that apply in your use case.
|
||||
*
|
||||
*
|
||||
* Supported Hardware and how to obtain
|
||||
* ====================================
|
||||
*
|
||||
* This driver has been developed for the CC1101 and the older CC1100
|
||||
* transceiver and tested for both. However, it should work with the CC1100e
|
||||
* as well - but this has *NOT* been tested at all.
|
||||
*
|
||||
* It is suggested to go for the CC1101 when considering to buy one of the
|
||||
* supported transceivers. The easiest way is to obtain CC1101 break out boards
|
||||
* with a complete antenna circuit & antenna that can be connected via jumper
|
||||
* wires using an 8 pin DIP pin header. These are sold in various flavours start
|
||||
* from less than 2€ at quantity one at your favourite Far East store. Beware
|
||||
* that while the CC1101 chip can operate a various base frequencies, the
|
||||
* antenna circuit will only work for a single frequency band. Most break out
|
||||
* boards will operate at 433 MHz, which is license free in many countries (but
|
||||
* verify that for your country before buying!). EU citizens might prefer the
|
||||
* 868 MHz band over the 433 MHz, as more license free bandwidth is available
|
||||
* in the 868 MHz band in the EU. (But when deploying only a few dozens of
|
||||
* devices, the 433 MHz band is also fine for EU citizens.) US citizens should
|
||||
* go for the 900 MHz band (as 868 MHz is not license free in the USA), which
|
||||
* even contains more bandwidth than the 868 MHz band. (However, the 900 MHz
|
||||
* band has not been tested, as using it would be illegal in the EU.)
|
||||
*
|
||||
*
|
||||
* Packet Format
|
||||
* =============
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* 0 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Preamble (4 bytes, handled by hardware, see MDMCFG1) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Sync Word (4 bytes, handled by hardware, see MDMCFG2) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Length Field | Destination | Source | Payload...
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ... (see Length Field) | CRC (handled by hardware) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* | Field | Description |
|
||||
* |--------------|------------------------------------------------------------|
|
||||
* | Preamble | 4 bytes, handled by hardware |
|
||||
* | Sync Word | 4 bytes, handled by hardware |
|
||||
* | Length Field | Handled by software & hardware, length of payload + 2 |
|
||||
* | Destination | Handled by software & hardware, destination MAC address |
|
||||
* | Source | Handled by software only, source MAC address |
|
||||
* | Payload | Handled by software only, the payload to send |
|
||||
* | CRC | 2 bytes, handled by hardware |
|
||||
*
|
||||
* The Length Field contains the length of the driver supplied data in bytes,
|
||||
* not counting the Length Field. Thus, it contains the length of the payload
|
||||
* plus the length of the Destination and Source address.
|
||||
*
|
||||
*
|
||||
* Layer-2 Addresses
|
||||
* -----------------
|
||||
*
|
||||
* The layer 2 addresses of the CC110x transceivers is a single byte long and
|
||||
* the special value `0x00` for the destination address is used in broadcast
|
||||
* transmissions. The transceiver is configured by this driver to ignore all
|
||||
* packets unless the destination address matches the address of the transceiver
|
||||
* or the destination address is `0x00`.
|
||||
*
|
||||
* Please note that the layer 2 address by default is derived from the CPU ID.
|
||||
* Due to the birthday paradox with only 20 devices the probability of a
|
||||
* collision is already bigger than 50%. Thus, manual address assignment is
|
||||
* supported by defining `C110X_PARAM_L2ADDR`.
|
||||
*
|
||||
*
|
||||
* Base Band, Data Rate, Channel Bandwidth and Channel Map Configuration
|
||||
* =====================================================================
|
||||
*
|
||||
* This driver allows to configure the base band, the data rate and the channel
|
||||
* bandwidth using an @ref cc110x_config_t data structure. Default
|
||||
* configurations are supplied and name using the following scheme:
|
||||
* `cc110x_config_<BASE-BAND>_<DATA-RATE>_<CHANNEL-BANDWIDTH>`. (E.g.
|
||||
* @ref cc110x_config_868mhz_250kbps_300khz is the default configuration used by
|
||||
* the MSB-A2 and the MSB-IoT boards.)
|
||||
*
|
||||
* Using the @ref cc110x_chanmap_t data structure the channel layout can be
|
||||
* defined. This map contains 8 entries, each defines the offset from the base
|
||||
* frequency defined in the @ref cc110x_config_t data structure for each
|
||||
* channel in steps of 50kHz. E.g. @ref cc110x_chanmap_868mhz_lora provides
|
||||
* the LoRa channels 10 to 17 in the 868MHz band. (The RIOT channel numbers
|
||||
* will always start from 0, and currently only up to eight channels are
|
||||
* supported. A special value of 255 as offset from the base frequency in the
|
||||
* channel map is used mark the channel as disabled. This can be used if less
|
||||
* than 8 non-overlapping channels are possible in the license free band.)
|
||||
*
|
||||
* Please note that the channel map (@ref cc110x_chanmap_t) must match the
|
||||
* base configuration (@ref cc110x_config_t), as the channel map is relative
|
||||
* to the configured base frequency. Also, the distance between the channels
|
||||
* in the channel map should match the channel bandwidth of the configuration,
|
||||
* as otherwise channels could overlap.
|
||||
*
|
||||
* Both configuration and matching channel map can be applied using
|
||||
* @ref cc110x_apply_config. Please consider this as a slow operation, as the
|
||||
* transceiver needs to be calibrated for each channel in the channel map.
|
||||
*
|
||||
*
|
||||
* Calibration of the Frequency Generator
|
||||
* ======================================
|
||||
*
|
||||
* The CC110x transceivers use a voltage controlled oscillator (VCO) and a
|
||||
* phase locked loop (PLL) for frequency generation. However, they need to be
|
||||
* calibrated to work correctly with the given supply voltage and the current
|
||||
* temperature. The driver will perform this calibration during startup, but
|
||||
* when the supply voltage or the temperature is not stable, a recalibration is
|
||||
* required whenever the supply voltage of temperature has changed too much since
|
||||
* the last calibration. This can be done by calling
|
||||
* @ref cc110x_full_calibration. It is left to the application developer to
|
||||
* perform this calibration when needed. During a test of about 2 hours of
|
||||
* operation in an in-door environment with a stable temperature the CC1101 has
|
||||
* worked reliable without any calibration at all (except for the automatic
|
||||
* calibration at start up). So there are use cases which do not require any
|
||||
* recalibration at all.
|
||||
*
|
||||
*
|
||||
* Troubleshooting
|
||||
* ===============
|
||||
*
|
||||
* The Driver Does Not Initialize Properly
|
||||
* ---------------------------------------
|
||||
* Set `ENABLE_DEBUG` in `cc110x_netdev.c` to `1` to get debug output, which
|
||||
* will likely tell you what is going wrong. There are basically two things
|
||||
* that can fail:
|
||||
*
|
||||
* Upon initialization the driver will read out the part number and version of
|
||||
* the transceiver. If those do not match the ones expected for the CC1100,
|
||||
* CC1100E, or the CC1101 the driver will refuse to initialize. If this fails,
|
||||
* most likely incorrect values are read out and the SPI communication does not
|
||||
* work correctly. However, future revisions of the CC110X transceivers might
|
||||
* be produced and might have different values for the part number or version.
|
||||
* If this should happen and they remain compatible with the driver, their
|
||||
* part number & revision needs to be added to the driver.
|
||||
*
|
||||
* After uploading the configuration, the driver will read back the
|
||||
* configuration to verify it. If the SPI communication is not reliable (e.g.
|
||||
* sporadically bits flip), this will fail from time to time. E.g. on the
|
||||
* MSB-IoT boards this is the case when the SPI interface operates at a clock of
|
||||
* 5MHz, but it becomes reliable when clocked at 1MHz.
|
||||
*
|
||||
* The Driver Initializes, but Communication Is Impossible
|
||||
* -------------------------------------------------------
|
||||
* If two transceivers are too close to each other and TX power is at maximum,
|
||||
* the signal is just too strong to be received correctly. Reducing TX power
|
||||
* or increasing the distance (about half a meter should be fine) will solve
|
||||
* this issue.
|
||||
*
|
||||
* While the chips can operate at any base frequency offered by the driver,
|
||||
* the circuit the chip is connected to and the antenna are build for a single
|
||||
* base band. Check if your configuration matches the frequency range the
|
||||
* board is build for. E.g. most break out boards operate at 433MHz, but there
|
||||
* are also boards for 868MHz.
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Interface definition for the CC1100/CC1101 driver
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
* @author Oliver Hahm <oliver.hahm@inria.fr>
|
||||
* @author Fabian Nack <nack@inf.fu-berlin.de>
|
||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||
*/
|
||||
|
||||
#ifndef CC110X_H
|
||||
#define CC110X_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cc1xxx_common.h"
|
||||
#include "mutex.h"
|
||||
#include "net/gnrc/nettype.h"
|
||||
#include "net/netdev.h"
|
||||
#include "periph/adc.h"
|
||||
#include "periph/gpio.h"
|
||||
#include "periph/spi.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Length of a layer 2 frame
|
||||
*
|
||||
* This does not include the preamble, sync word, CRC field, and length field.
|
||||
*/
|
||||
#define CC110X_MAX_FRAME_SIZE 0xFF
|
||||
|
||||
/**
|
||||
* @brief Maximum (layer 2) payload size supported by the driver
|
||||
*/
|
||||
#define CC110X_MAX_PAYLOAD_SIZE (CC110X_MAX_FRAME_SIZE - CC1XXX_HEADER_SIZE)
|
||||
|
||||
/**
|
||||
* @brief Maximum number of channels supported by the driver
|
||||
*/
|
||||
#define CC110X_MAX_CHANNELS 8
|
||||
|
||||
/**
|
||||
* @brief Special value to indicate that layer 2 address should be derived
|
||||
* from the CPU-ID
|
||||
*/
|
||||
#define CC110X_L2ADDR_AUTO 0x00
|
||||
|
||||
/**
|
||||
* @brief Default protocol for data that is coming in
|
||||
*/
|
||||
#ifdef MODULE_GNRC_SIXLOWPAN
|
||||
#define CC110X_DEFAULT_PROTOCOL (GNRC_NETTYPE_SIXLOWPAN)
|
||||
#else
|
||||
#define CC110X_DEFAULT_PROTOCOL (GNRC_NETTYPE_UNDEF)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The state of the CC1100/CC1101 transceiver
|
||||
*
|
||||
* The three least significant bytes match the representation of the matching
|
||||
* transceiver state given in the status byte of the hardware. See Table 32 on
|
||||
* page 31 in the data sheet for the possible states in the status byte.
|
||||
*/
|
||||
typedef enum {
|
||||
CC110X_STATE_IDLE = 0b00000000, /**< IDLE state */
|
||||
/**
|
||||
* @brief Frame received, waiting for upper layer to retrieve it
|
||||
*
|
||||
* Transceiver is in IDLE state.
|
||||
*/
|
||||
CC110X_STATE_FRAME_READY = 0b00001000,
|
||||
/**
|
||||
* @brief Frame received, waiting for upper layer to retrieve it
|
||||
*
|
||||
* Transceiver is in SLEEP state. There is no matching representation in the
|
||||
* status byte, as reading the status byte will power up the transceiver in
|
||||
* bring it in the IDLE state. Thus, we set the three least significant bits
|
||||
* to the IDLE state
|
||||
*/
|
||||
CC110X_STATE_OFF = 0b00010000,
|
||||
CC110X_STATE_RX_MODE = 0b00000001, /**< Listening for frames */
|
||||
/**
|
||||
* @brief Receiving a frame just now
|
||||
*
|
||||
* Transceiver is in RX state.
|
||||
*/
|
||||
CC110X_STATE_RECEIVING = 0b00001001,
|
||||
CC110X_STATE_TX_MODE = 0b00000010, /**< Transmit mode */
|
||||
/**
|
||||
* @brief Waiting for transceiver to complete outgoing transmission
|
||||
*
|
||||
* Transceiver is in TX state
|
||||
*/
|
||||
CC110X_STATE_TX_COMPLETING = 0b00001010,
|
||||
CC110X_STATE_FSTXON = 0b00000011, /**< Fast TX ready */
|
||||
CC110X_STATE_CALIBRATE = 0b00000100, /**< Device is calibrating */
|
||||
CC110X_STATE_SETTLING = 0b00000101, /**< PLL is settling */
|
||||
CC110X_STATE_RXFIFO_OVERFLOW = 0b00000110, /**< RX FIFO overflown */
|
||||
CC110X_STATE_TXFIFO_UNDERFLOW = 0b00000111, /**< TX FIFO underflown */
|
||||
} cc110x_state_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration over the possible TX power settings the driver offers
|
||||
*/
|
||||
typedef enum {
|
||||
CC110X_TX_POWER_MINUS_30_DBM, /**< -30 dBm */
|
||||
CC110X_TX_POWER_MINUS_20_DBM, /**< -20 dBm */
|
||||
CC110X_TX_POWER_MINUS_15_DBM, /**< -15 dBm */
|
||||
CC110X_TX_POWER_MINUS_10_DBM, /**< -10 dBm */
|
||||
CC110X_TX_POWER_0_DBM, /**< 0 dBm */
|
||||
CC110X_TX_POWER_PLUS_5_DBM, /**< 5 dBm */
|
||||
CC110X_TX_POWER_PLUS_7_DBM, /**< 7 dBm */
|
||||
CC110X_TX_POWER_PLUS_10_DBM, /**< 1 dBm */
|
||||
CC110X_TX_POWER_NUMOF, /**< Number of TX power options */
|
||||
} cc110x_tx_power_t;
|
||||
|
||||
/**
|
||||
* @brief Structure that holds the PATABLE, which allows to configure the
|
||||
* 8 available output power levels using a magic number for each level.
|
||||
*
|
||||
* See Section "24 Output Power Programming" on page 59ff in the data sheet.
|
||||
* The values suggested in Table 39 on page 60 in the data sheet are already
|
||||
* available by this driver, but will only be linked in (8 bytes of ROM size)
|
||||
* when they are referenced.
|
||||
*
|
||||
* @see cc110x_patable_433mhz
|
||||
* @see cc110x_patable_868mhz
|
||||
* @see cc110x_patable_915mhz
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t data[8]; /**< Magic number to store in the configuration register */
|
||||
} cc110x_patable_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Configuration of the transceiver to use
|
||||
*
|
||||
* @warning Two transceivers with different configurations will be unable
|
||||
* to communicate.
|
||||
*
|
||||
* The data uploaded into configuration registers are stored in
|
||||
* @ref cc110x_conf. Most of them cannot be changed, as the driver relies on
|
||||
* their values. However, the base frequency, the symbol rate (which equals
|
||||
* the bit rate for the chosen modulation and error correction) and the
|
||||
* channel bandwidth can be configured using this data structure.
|
||||
*
|
||||
* Please note that while the CC1100/CC1101 chip is compatible with a huge
|
||||
* frequency range (300 MHz - 928 MHz), the complete circuit is optimized to
|
||||
* a narrow frequency band. So make sure the configured base frequency is within
|
||||
* that frequency band that is compatible with that circuit. (Most break out
|
||||
* board will operate at the 433 MHz band. In the EU the 868 MHz band would be
|
||||
* more interesting, but 433 MHz is license free as well. In the USA the 915 MHz
|
||||
* band is license free.
|
||||
*
|
||||
* Please verify that the driver is configured in a way that allows legal
|
||||
* operation according to rules and laws that apply for you.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t base_freq[3]; /**< Base frequency to use */
|
||||
/**
|
||||
* @brief FSCTRL1 configuration register value that affects the
|
||||
* intermediate frequency of the transceiver to use
|
||||
* @note The 4 most significant bits have to be 0.
|
||||
*
|
||||
* Assuming a 26 MHz crystal the IF is calculated as follows (in kHz):
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* double intermediate_frequency = 26000 / 1024 * fsctrl1;
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
uint8_t fsctrl1;
|
||||
/**
|
||||
* @brief MDMCFG4 configuration register value that affects channel filter
|
||||
* bandwidth and the data rate
|
||||
*
|
||||
* See page 76 in the data sheet.
|
||||
*
|
||||
* Assuming a 26 MHz crystal the channel filter bandwidth is calculated
|
||||
* as follows (in kHz):
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* uint8_t exponent = mdmcfg4 >> 6;
|
||||
* uint8_t mantissa = (mdmcfg4 >> 4) & 0x03;
|
||||
* double bandwidth = 26000.0 / (8 * (4 + mantissa) * (1L << exponent));
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
uint8_t mdmcfg4;
|
||||
/**
|
||||
* @brief MDMCFG3 configuration register value that affects the data rate
|
||||
*
|
||||
* @see cc110x_config_t::mdmcfg4
|
||||
*
|
||||
* See page 76 in the data sheet.
|
||||
*
|
||||
* Assuming a 26 MHz crystal the symbol rate of the transceiver is calculated
|
||||
* as fallows (in kBaud):
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* uint8_t exponent = mdmcfg4 & 0x0f;
|
||||
* int32_t mantissa = mdmcfg3;
|
||||
* double baudrate = (256 + mantissa) * 26000.0 / (1L << (28 - exponent));
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
uint8_t mdmcfg3;
|
||||
/**
|
||||
* @brief DEVIANT configuration register that affects the amount by which
|
||||
* the radio frequency is shifted in FSK/GFSK modulation
|
||||
*
|
||||
* @see cc110x_config_t::mdmcfg4
|
||||
*
|
||||
* See page 79 in the data sheet.
|
||||
*
|
||||
* In an ideal world the channel bandwidth would be twice the channel
|
||||
* deviation. In the real world the used channel bandwidth is higher.
|
||||
* Assuming a 26 MHz crystal and GFSK modulation (the driver will configure
|
||||
* the transceiver to use GFSK) the deviation
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* uint8_t exponent = (deviatn >> 4) & 0x07;
|
||||
* int32_t mantissa = deviatn & 0x07;
|
||||
* double deviation = (8 + mantissa) * 26000.0 / (1L << (17 - exponent));
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* For reliable operation at high symbol rates, the deviation has to be
|
||||
* increased as well.
|
||||
*/
|
||||
uint8_t deviatn;
|
||||
} cc110x_config_t;
|
||||
|
||||
/**
|
||||
* @brief Structure to hold mapping between virtual and physical channel numbers
|
||||
*
|
||||
* This driver will provide "virtual" channel numbers 0 to 7, which will be
|
||||
* translated to "physical" channel numbers before being send to the
|
||||
* transceiver. This is used to overcome the following limitations:
|
||||
*
|
||||
* - The transceiver does not support channel maps with varying distance between
|
||||
* channels. However, e.g. the LoRa channels 10 - 16 in the 868 MHz band have
|
||||
* a distance of 300 kHz, but channel 16 and 17 have a distance of 1 MHz.
|
||||
* - The transceiver does not supports channel distances higher than 405.46 kHz.
|
||||
*
|
||||
* This mapping overcomes both limitations be using 50kHz physical channel
|
||||
* spacing and use the map to translate to the correct physical channel. This
|
||||
* also allows to keep the same MDMCFG1 and MDMCFG0 configuration register
|
||||
* values for all channel layouts. Finally, different channel sets can be
|
||||
* used by different groups of IoT device in the same environment to limit
|
||||
* collisions between those groups - assuming that enough non-overlapping
|
||||
* channels are available.
|
||||
*
|
||||
* The "virtual" channel (the channel number presented to RIOT) will be used
|
||||
* as index in @ref cc110x_chanmap_t::map, the value in there will give the
|
||||
* corresponding "physical" channel number, or 255 if this virtual channel
|
||||
* number is not available.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t map[CC110X_MAX_CHANNELS]; /**< "Physical" channel numbers */
|
||||
} cc110x_chanmap_t;
|
||||
|
||||
/**
|
||||
* @brief Structure holding all parameter for driver initialization
|
||||
*/
|
||||
typedef struct {
|
||||
const cc110x_patable_t *patable; /**< Pointer to the PATABLE to use */
|
||||
/**
|
||||
* @brief Pointer to the configuration of the base frequency, data rate and
|
||||
* channel bandwidth; or `NULL` to keep the default.
|
||||
*/
|
||||
const cc110x_config_t *config;
|
||||
const cc110x_chanmap_t *channels; /**< Pointer to the default channel map */
|
||||
spi_t spi; /**< SPI bus connected to the device */
|
||||
spi_clk_t spi_clk; /**< SPI clock to use (max 6.5 MHz) */
|
||||
spi_cs_t cs; /**< GPIO pin connected to chip select */
|
||||
gpio_t gdo0; /**< GPIO pin connected to GDO0 */
|
||||
gpio_t gdo2; /**< GPIO pin connected to GDO2 */
|
||||
/**
|
||||
* @brief Layer-2 address to use or `CC110X_L2ADDR_AUTO` to derive it from
|
||||
* the CPU ID
|
||||
*/
|
||||
uint8_t l2addr;
|
||||
} cc110x_params_t;
|
||||
|
||||
/**
|
||||
* @brief Structure holding the calibration data of the frequency synthesizer
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief VCO capacitance calibration, which depends on the frequency and,
|
||||
* thus, has to be stored for each channel
|
||||
*/
|
||||
char fscal1[CC110X_MAX_CHANNELS];
|
||||
char fscal2; /**< VCO current calibration, independent of channel */
|
||||
char fscal3; /**< charge pump current calibration, independent of channel */
|
||||
} cc110x_fs_calibration_t;
|
||||
|
||||
/**
|
||||
* @brief Buffer to temporary store incoming/outgoing packet
|
||||
*
|
||||
* The CC1100/CC1101 transceiver's FIFO sadly is only 64 bytes in size. To
|
||||
* support frames bigger than that, chunks of the frame have to be
|
||||
* transferred between the MCU and the CC1100/CC1101 transceiver while the
|
||||
* frame is in transit.
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t len; /**< Length of the frame in bytes */
|
||||
/**
|
||||
* @brief The payload data of the frame
|
||||
*/
|
||||
uint8_t data[CC110X_MAX_FRAME_SIZE];
|
||||
/**
|
||||
* @brief Index of the next @ref cc110x_framebuf_t::data element to transfer
|
||||
*
|
||||
* In RX mode: Index of the next @ref cc110x_framebuf_t::data element to store
|
||||
* data read from the RX-FIFO into.
|
||||
*
|
||||
* In TX mode: Index of the next @ref cc110x_framebuf_t::data element to write
|
||||
* to the TX-FIFO.
|
||||
*/
|
||||
uint8_t pos;
|
||||
} cc110x_framebuf_t;
|
||||
|
||||
/**
|
||||
* @brief Device descriptor for CC1100/CC1101 transceivers
|
||||
*/
|
||||
typedef struct {
|
||||
netdev_t netdev; /**< RIOT's interface to this driver */
|
||||
uint8_t addr; /**< Layer 2 address of this device */
|
||||
/* Keep above in sync with cc1xx_t members, as they must overlap! */
|
||||
cc110x_state_t state; /**< State of the transceiver */
|
||||
cc110x_tx_power_t tx_power; /**< TX power of the receiver */
|
||||
uint8_t channel; /**< Currently tuned (virtual) channel */
|
||||
/* Struct packing: addr, state, tx_power and channel add up to 32 bit */
|
||||
const cc110x_chanmap_t *channels; /**< Pointer to the channel map to use. */
|
||||
cc110x_params_t params; /**< Configuration of the driver */
|
||||
cc110x_framebuf_t buf; /**< Temporary frame buffer */
|
||||
/**
|
||||
* @brief RSSI and LQI of the last received frame
|
||||
*/
|
||||
cc1xxx_rx_info_t rx_info;
|
||||
/**
|
||||
* @brief Frequency synthesizer calibration data
|
||||
*/
|
||||
cc110x_fs_calibration_t fscal;
|
||||
/**
|
||||
* @brief Use mutex to block during TX and unblock from ISR when ISR
|
||||
* needs to be handled from thread-context
|
||||
*
|
||||
* Blocking during TX within the driver prevents the upper layers from
|
||||
* calling @ref netdev_driver_t::send while already transmitting a frame.
|
||||
*/
|
||||
mutex_t isr_signal;
|
||||
uint8_t rssi_offset; /**< dBm to subtract from raw RSSI data */
|
||||
} cc110x_t;
|
||||
|
||||
/**
|
||||
* @brief Setup the CC1100/CC1101 driver, but perform no initialization
|
||||
*
|
||||
* @ref netdev_driver_t::init can be used after this call to initialize the
|
||||
* transceiver.
|
||||
*
|
||||
* @param dev Device descriptor to use
|
||||
* @param params Parameter of the device to setup
|
||||
*
|
||||
* @retval 0 Device successfully set up
|
||||
* @retval -EINVAL @p dev or @p params is `NULL`, or @p params is invalid
|
||||
*/
|
||||
int cc110x_setup(cc110x_t *dev, const cc110x_params_t *params);
|
||||
|
||||
/**
|
||||
* @brief Apply the given configuration and the given channel map and performs
|
||||
* a recalibration
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
* @param conf Configuration to apply or `NULL` to only change channel map
|
||||
* @param chanmap Channel map to apply (must be compatible with @p conf)
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid argument
|
||||
* @retval -EIO Communication with the transceiver failed
|
||||
*
|
||||
* @pre The application developer checked in the documentation that the channel
|
||||
* map in @p chanmap is compatible with the configuration in @p conf
|
||||
*
|
||||
* Because the configuration (potentially) changes the channel bandwidth, the
|
||||
* old channel map is rendered invalid. This API therefore asks for both to make
|
||||
* sure an application developer does not forget to update the channel map.
|
||||
* Because the old calibration data is also rendered invalid,
|
||||
* @ref cc110x_full_calibration is called to update it.
|
||||
*/
|
||||
int cc110x_apply_config(cc110x_t *dev, const cc110x_config_t *conf,
|
||||
const cc110x_chanmap_t *chanmap);
|
||||
|
||||
/**
|
||||
* @brief Perform a calibration of the frequency generator for each supported
|
||||
* channel
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with invalid argument
|
||||
* @retval -EAGAIN Current state prevents deliberate calibration
|
||||
* @retval -EIO Communication with the transceiver failed
|
||||
*
|
||||
* Tunes in each supported channel and calibrates the transceiver. The
|
||||
* calibration data is stored so that @ref cc110x_set_channel can skip the
|
||||
* calibration phase and use the stored calibration data instead.
|
||||
*/
|
||||
int cc110x_full_calibration(cc110x_t *dev);
|
||||
|
||||
/**
|
||||
* @brief Hops to the specified channel
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
* @param channel Channel to hop to
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with `NULL` as @p dev
|
||||
* @retval -ERANGE Channel out of range or not supported by channel map
|
||||
* @retval -EAGAIN Currently in a state that does not allow hopping, e.g.
|
||||
* sending/receiving a packet, calibrating or handling
|
||||
* transmission errors
|
||||
* @retval -EIO Communication with the transceiver failed
|
||||
*
|
||||
* This function implements the fact channel hopping approach outlined in
|
||||
* section 28.2 on page 64 in the data sheet, which skips the calibration phase
|
||||
* by storing the calibration date for each channel in the driver.
|
||||
*/
|
||||
int cc110x_set_channel(cc110x_t *dev, uint8_t channel);
|
||||
|
||||
/**
|
||||
* @brief Set the TX power to the specified value
|
||||
*
|
||||
* @param dev Device descriptor of the transceiver
|
||||
* @param power Output power to apply
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Called with `NULL` as @p dev
|
||||
* @retval -ERANGE Called with an invalid value for @p power
|
||||
* @retval -EAGAIN Changing the TX power is in the current state not possible
|
||||
* @retval -EIO Communication with the transceiver failed
|
||||
*/
|
||||
int cc110x_set_tx_power(cc110x_t *dev, cc110x_tx_power_t power);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CC110X_H */
|
||||
/** @} */
|
@ -97,6 +97,11 @@ PSEUDOMODULES += adc121c
|
||||
# full featured version of CCS811 driver as pseudo module
|
||||
PSEUDOMODULES += ccs811_full
|
||||
|
||||
# include variants of CC110X drivers as pseudo modules
|
||||
PSEUDOMODULES += cc1100
|
||||
PSEUDOMODULES += cc1100e
|
||||
PSEUDOMODULES += cc1101
|
||||
|
||||
# include variants of SX127X drivers as pseudo modules
|
||||
PSEUDOMODULES += sx1272
|
||||
PSEUDOMODULES += sx1276
|
||||
|
@ -206,6 +206,11 @@ void auto_init(void)
|
||||
auto_init_mrf24j40();
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_CC110X
|
||||
extern void auto_init_cc110x(void);
|
||||
auto_init_cc110x();
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_CC2420
|
||||
extern void auto_init_cc2420(void);
|
||||
auto_init_cc2420();
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de>
|
||||
* 2016 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
|
||||
@ -7,62 +8,63 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* @ingroup sys_auto_init_gnrc_netif
|
||||
/**
|
||||
* @ingroup sys_auto_init_gnrc_netif
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Auto initialization for cc110x network interfaces
|
||||
* @brief Auto initialization for cc110x network interfaces
|
||||
*
|
||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||
* @author Kaspar Schleiser <kaspar@schleiser.de>
|
||||
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#ifdef MODULE_CC110X
|
||||
|
||||
#include "log.h"
|
||||
#include "debug.h"
|
||||
#include "board.h"
|
||||
#include "gnrc_netif_cc110x.h"
|
||||
#include "cc110x-netdev.h"
|
||||
#include "net/gnrc.h"
|
||||
|
||||
#include "cc110x.h"
|
||||
#include "cc1xxx_common.h"
|
||||
#include "cc110x_params.h"
|
||||
#define ENABLE_DEBUG (0)
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief Define stack parameters for the MAC layer thread
|
||||
* @{
|
||||
* @brief Calculate the stack size for the MAC layer thread(s)
|
||||
*/
|
||||
#define CC110X_MAC_STACKSIZE (THREAD_STACKSIZE_DEFAULT + DEBUG_EXTRA_STACKSIZE)
|
||||
#define CC110X_MAC_STACKSIZE (THREAD_STACKSIZE_DEFAULT + DEBUG_EXTRA_STACKSIZE)
|
||||
#ifndef CC110X_MAC_PRIO
|
||||
#define CC110X_MAC_PRIO (GNRC_NETIF_PRIO)
|
||||
/**
|
||||
* @brief The priority of the MAC layer thread
|
||||
*/
|
||||
#define CC110X_MAC_PRIO (GNRC_NETIF_PRIO)
|
||||
#endif
|
||||
|
||||
#define CC110X_NUM ARRAY_SIZE(cc110x_params)
|
||||
/**
|
||||
* @brief Calculate the number of configured CC1100/CC1101 transceivers
|
||||
*/
|
||||
#define CC110X_NUM ARRAY_SIZE(cc110x_params)
|
||||
|
||||
static netdev_cc110x_t cc110x_devs[CC110X_NUM];
|
||||
static char _stacks[CC110X_NUM][CC110X_MAC_STACKSIZE];
|
||||
/**
|
||||
* @brief Statically allocate memory for device descriptors
|
||||
*/
|
||||
cc110x_t _cc110x_devs[CC110X_NUM];
|
||||
/**
|
||||
* @brief Statically allocate memory for the MAC layer thread(s)
|
||||
*/
|
||||
static char stacks[CC110X_NUM][CC110X_MAC_STACKSIZE];
|
||||
|
||||
void auto_init_cc110x(void)
|
||||
{
|
||||
for (unsigned i = 0; i < CC110X_NUM; i++) {
|
||||
const cc110x_params_t *p = &cc110x_params[i];
|
||||
|
||||
LOG_DEBUG("[auto_init_netif] initializing cc110x #%u\n", i);
|
||||
|
||||
int res = netdev_cc110x_setup(&cc110x_devs[i], p);
|
||||
if (res < 0) {
|
||||
LOG_ERROR("[auto_init_netif] error initializing cc110x #%u\n", i);
|
||||
}
|
||||
else {
|
||||
gnrc_netif_cc110x_create(_stacks[i], CC110X_MAC_STACKSIZE,
|
||||
CC110X_MAC_PRIO, "cc110x",
|
||||
(netdev_t *)&cc110x_devs[i]);
|
||||
}
|
||||
cc110x_setup(&_cc110x_devs[i], &cc110x_params[i]);
|
||||
gnrc_netif_cc1xxx_create(stacks[i], CC110X_MAC_STACKSIZE, CC110X_MAC_PRIO,
|
||||
"cc110x", (netdev_t *)&_cc110x_devs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
typedef int dont_be_pedantic;
|
||||
#endif /* MODULE_CC110X */
|
||||
|
||||
/** @} */
|
||||
|
Loading…
Reference in New Issue
Block a user