Skip to content

Commit 544adc6

Browse files
committed
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 4.9ms to 5.4ms (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
1 parent 615e25f commit 544adc6

25 files changed

+3669
-30
lines changed

Makefile.dep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ ifneq (,$(filter gnrc_netif,$(USEMODULE)))
164164
endif
165165
endif
166166

167-
ifneq (,$(filter ieee802154 nrfmin esp_now gnrc_sixloenc,$(USEMODULE)))
167+
ifneq (,$(filter ieee802154 nrfmin esp_now cc110x gnrc_sixloenc,$(USEMODULE)))
168168
ifneq (,$(filter gnrc_ipv6, $(USEMODULE)))
169169
USEMODULE += gnrc_sixlowpan
170170
endif

drivers/Makefile.dep

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ ifneq (,$(filter bm%280,$(USEMODULE)))
8181
USEMODULE += bmx280
8282
endif
8383

84+
ifneq (,$(filter cc110%,$(USEMODULE)))
85+
USEMODULE += cc110x
86+
USEMODULE += cc1xxx_common
87+
USEMODULE += luid
88+
USEMODULE += netif
89+
USEMODULE += xtimer
90+
FEATURES_REQUIRED += periph_gpio
91+
FEATURES_REQUIRED += periph_gpio_irq
92+
FEATURES_REQUIRED += periph_spi
93+
ifneq (,$(filter gnrc_ipv6,$(USEMODULE)))
94+
USEMODULE += gnrc_sixlowpan
95+
endif
96+
endif
97+
8498
ifneq (,$(filter cc2420,$(USEMODULE)))
8599
USEMODULE += xtimer
86100
USEMODULE += luid

drivers/Makefile.include

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ ifneq (,$(filter bmx280,$(USEMODULE)))
4242
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/bmx280/include
4343
endif
4444

45+
ifneq (,$(filter cc110x,$(USEMODULE)))
46+
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/cc110x/include
47+
endif
48+
4549
ifneq (,$(filter cc2420,$(USEMODULE)))
4650
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/cc2420/include
4751
endif

drivers/cc110x/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include $(RIOTBASE)/Makefile.base

drivers/cc110x/cc110x.c

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
9+
/**
10+
* @ingroup drivers_cc110x
11+
* @{
12+
*
13+
* @file
14+
* @brief Implementation for the "public" API of the CC1100/CC1101 driver
15+
*
16+
* @author Marian Buschsieweke <[email protected]>
17+
* @}
18+
*/
19+
20+
#include <errno.h>
21+
#include <string.h>
22+
23+
#include "cc110x.h"
24+
#include "cc110x_internal.h"
25+
26+
#define ENABLE_DEBUG (0)
27+
#include "debug.h"
28+
29+
int cc110x_setup(cc110x_t *dev, const cc110x_params_t *params)
30+
{
31+
if (!dev || !params) {
32+
return -EINVAL;
33+
}
34+
35+
/* Zero out everything but RIOT's driver interface, which should be
36+
* managed by RIOT
37+
*/
38+
memset((char *)dev + sizeof(netdev_t), 0x00,
39+
sizeof(cc110x_t) - sizeof(netdev_t));
40+
dev->params = *params;
41+
dev->netdev.driver = &cc110x_driver;
42+
dev->state = CC110X_STATE_OFF;
43+
return 0;
44+
}
45+
46+
int cc110x_apply_config(cc110x_t *dev, const cc110x_config_t *conf,
47+
const cc110x_chanmap_t *chanmap)
48+
{
49+
DEBUG("[cc110x] Applying new configuration\n");
50+
if (!dev || !chanmap) {
51+
return -EINVAL;
52+
}
53+
54+
if (cc110x_acquire(dev) != SPI_OK) {
55+
return -EIO;
56+
}
57+
58+
gpio_irq_disable(dev->params.gdo0);
59+
gpio_irq_disable(dev->params.gdo2);
60+
61+
/* Go to IDLE state to allow reconfiguration */
62+
cc110x_cmd(dev, CC110X_STROBE_IDLE);
63+
dev->state = CC110X_STATE_IDLE;
64+
65+
if (conf != NULL) {
66+
/* Write all three base frequency configuration bytes in one burst */
67+
cc110x_burst_write(dev, CC110X_REG_FREQ2, &conf->base_freq, 3);
68+
69+
cc110x_write(dev, CC110X_REG_FSCTRL1, conf->fsctrl1);
70+
cc110x_write(dev, CC110X_REG_MDMCFG4, conf->mdmcfg4);
71+
cc110x_write(dev, CC110X_REG_MDMCFG3, conf->mdmcfg3);
72+
cc110x_write(dev, CC110X_REG_DEVIATN, conf->deviatn);
73+
}
74+
75+
/* Set current channel to zero, as the new map might not support the current
76+
* virtual channel number. cc110x_full_calibration() will tune in that
77+
* channel after calibration.
78+
*/
79+
dev->channel = 0;
80+
dev->channels = chanmap;
81+
cc110x_release(dev);
82+
83+
/* prepare hopping will call cc110x_enter_rx_mode(), which restores the IRQs */
84+
return cc110x_full_calibration(dev);
85+
}
86+
87+
int cc110x_set_tx_power(cc110x_t *dev, cc110x_tx_power_t power)
88+
{
89+
DEBUG("[cc110x] Applying TX power setting at index %u\n", (unsigned)power);
90+
if (!dev) {
91+
return -EINVAL;
92+
}
93+
94+
if ((unsigned)power > 7) {
95+
return -ERANGE;
96+
}
97+
98+
if (cc110x_acquire(dev) != SPI_OK) {
99+
return -EIO;
100+
}
101+
102+
switch (dev->state) {
103+
case CC110X_STATE_IDLE:
104+
/* falls through */
105+
case CC110X_STATE_RX_MODE:
106+
break;
107+
default:
108+
cc110x_release(dev);
109+
return -EAGAIN;
110+
}
111+
112+
uint8_t frend0 = 0x10 | (uint8_t)power;
113+
cc110x_write(dev, CC110X_REG_FREND0, frend0);
114+
dev->tx_power = power;
115+
cc110x_release(dev);
116+
return 0;
117+
}
118+
119+
int cc110x_set_channel(cc110x_t *dev, uint8_t channel)
120+
{
121+
DEBUG("[cc110x] Hopping to channel %i\n", (int)channel);
122+
if (!dev) {
123+
return -EINVAL;
124+
}
125+
126+
if (cc110x_acquire(dev) != SPI_OK) {
127+
return -EIO;
128+
}
129+
130+
if ((channel >= CC110X_MAX_CHANNELS) || (dev->channels->map[channel] == 0xff)) {
131+
/* Channel out of range or not supported in current channel map */
132+
cc110x_release(dev);
133+
return -ERANGE;
134+
}
135+
136+
switch (dev->state) {
137+
case CC110X_STATE_IDLE:
138+
/* falls through */
139+
case CC110X_STATE_RX_MODE:
140+
/* falls through */
141+
case CC110X_STATE_FSTXON:
142+
/* Above states are fine for hopping */
143+
break;
144+
default:
145+
/* All other states do not allow hopping right now */
146+
cc110x_release(dev);
147+
return -EAGAIN;
148+
}
149+
150+
/* Disable IRQs, as e.g. PLL indicator will go LOW in IDLE state */
151+
gpio_irq_disable(dev->params.gdo0);
152+
gpio_irq_disable(dev->params.gdo2);
153+
154+
/* Go to IDLE state to disable frequency synchronizer */
155+
cc110x_cmd(dev, CC110X_STROBE_IDLE);
156+
157+
/* Upload new channel and corresponding calibration data */
158+
cc110x_write(dev, CC110X_REG_CHANNR, dev->channels->map[channel]);
159+
160+
uint8_t caldata[] = {
161+
dev->fscal.fscal3, dev->fscal.fscal2, dev->fscal.fscal1[channel]
162+
};
163+
cc110x_burst_write(dev, CC110X_REG_FSCAL3, caldata, sizeof(caldata));
164+
165+
/* Start listening on the new channel (restores IRQs) */
166+
cc110x_enter_rx_mode(dev);
167+
168+
dev->channel = channel;
169+
cc110x_release(dev);
170+
171+
dev->netdev.event_callback(&dev->netdev, NETDEV_EVENT_FHSS_CHANGE_CHANNEL);
172+
return 0;
173+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (C) 2018 Otto-von-Guericke-Universität Magdeburg
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
9+
/**
10+
* @ingroup drivers_cc110x
11+
* @{
12+
*
13+
* @file
14+
* @brief Implementation of the manual calibration facility of the
15+
* CC1100/CC1101 driver
16+
*
17+
* @author Marian Buschsieweke <[email protected]>
18+
* @}
19+
*/
20+
21+
#include <errno.h>
22+
#include <stdint.h>
23+
24+
#include "periph/gpio.h"
25+
#include "xtimer.h"
26+
27+
#include "cc110x.h"
28+
#include "cc110x_internal.h"
29+
30+
#define ENABLE_DEBUG (0)
31+
#include "debug.h"
32+
33+
/**
34+
* @brief Read the calibration data from the transceiver and store it
35+
*
36+
* @param dev Device descriptor of the transceiver
37+
*
38+
* @pre @p dev is acquired using @p cc110x_acquire
39+
*/
40+
static inline void get_calibration_data(cc110x_t *dev)
41+
{
42+
char caldata[3];
43+
44+
cc110x_burst_read(dev, CC110X_REG_FSCAL3, caldata, sizeof(caldata));
45+
dev->fscal.fscal3 = caldata[0];
46+
dev->fscal.fscal2 = caldata[1];
47+
dev->fscal.fscal1[dev->channel] = caldata[2];
48+
}
49+
50+
int cc110x_recalibrate(cc110x_t *dev)
51+
{
52+
/* Sadly we cannot use GDO0 to check for calibration, as it only
53+
* provides output in RX/TX state. But after successful manual
54+
* calibration, the device returns to IDLE state. Thus, we keep
55+
* calibrating until IDLE state is reached
56+
*/
57+
do {
58+
/* Start calibration */
59+
cc110x_cmd(dev, CC110X_STROBE_CALIBRATE);
60+
/* Release SPI interface to give other threads a chance to use it */
61+
cc110x_release(dev);
62+
/* Manual calibration take 735 micro seconds (see Table 34 on page
63+
* 54 in the date sheet). We'll wait 750 to be sure
64+
*/
65+
xtimer_usleep(750);
66+
67+
/* Re-acquire SPI interface in order to check if calibration
68+
* succeeded
69+
*/
70+
if (cc110x_acquire(dev) != SPI_OK) {
71+
return -EIO;
72+
}
73+
} while (cc110x_state_from_status(cc110x_status(dev)) != CC110X_STATE_IDLE);
74+
75+
get_calibration_data(dev);
76+
77+
return 0;
78+
}
79+
80+
int cc110x_full_calibration(cc110x_t *dev)
81+
{
82+
DEBUG("[cc110x] Obtaining calibration data for fast channel hopping\n");
83+
if (!dev) {
84+
return -EINVAL;
85+
}
86+
87+
if (cc110x_acquire(dev) != SPI_OK) {
88+
return -EIO;
89+
}
90+
91+
switch (dev->state) {
92+
case CC110X_STATE_IDLE:
93+
/* falls through */
94+
case CC110X_STATE_RX_MODE:
95+
/* falls through */
96+
case CC110X_STATE_FSTXON:
97+
/* Current state is fine for deliberate calibration */
98+
break;
99+
default:
100+
/* Current state prevents deliberate calibration */
101+
cc110x_release(dev);
102+
return -EAGAIN;
103+
}
104+
105+
uint8_t old_channel = dev->channel;
106+
107+
/* Disable interrupts on GDO pins */
108+
gpio_irq_disable(dev->params.gdo0);
109+
gpio_irq_disable(dev->params.gdo2);
110+
111+
/* While waiting for calibration to be done, another thread could
112+
* be scheduled. Setting the state should prevent other threads from
113+
* messing around with the driver
114+
*/
115+
dev->state = CC110X_STATE_CALIBRATE;
116+
117+
/* Go to IDLE to allow setting the channel */
118+
cc110x_cmd(dev, CC110X_STROBE_IDLE);
119+
120+
for (dev->channel = 0; dev->channel < CC110X_MAX_CHANNELS; dev->channel++) {
121+
uint8_t phy_chan = dev->channels->map[dev->channel];
122+
if (phy_chan == 0xff) {
123+
/* Channel not supported by channel map */
124+
continue;
125+
}
126+
/* Set the channel to calibrate for fast hopping */
127+
cc110x_write(dev, CC110X_REG_CHANNR, phy_chan);
128+
129+
if (cc110x_recalibrate(dev)) {
130+
/* cc110x_recalibrate() release device on error */
131+
return -EIO;
132+
}
133+
}
134+
135+
/* Update device to reflect current transceiver state */
136+
dev->state = CC110X_STATE_IDLE;
137+
cc110x_release(dev);
138+
139+
/* Hop back to old channel, IRQs are restored by cc110x_set_channel */
140+
return cc110x_set_channel(dev, old_channel);
141+
}

0 commit comments

Comments
 (0)