mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
c3500eb6e0
This will not change generated binaries, only the Doxygen output and source code will have less typos.
324 lines
11 KiB
C
324 lines
11 KiB
C
/*
|
|
* 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;
|
|
|
|
cc110x_acquire(dev);
|
|
|
|
/* 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 upper layer, if needed */
|
|
if (post_isr_event != NETDEV_NO_EVENT) {
|
|
dev->netdev.event_callback(&dev->netdev, post_isr_event);
|
|
}
|
|
}
|