1936 lines
58 KiB
C
1936 lines
58 KiB
C
/****************************************************************************
|
|
* drivers/wireless/xbee/drivers/xbee.c
|
|
*
|
|
* Copyright (C) 2017 Verge Inc. All rights reserved.
|
|
* Author: Anthony Merlino <anthony@vergeaero.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/mm/iob.h>
|
|
|
|
#include "xbee.h"
|
|
#include "xbee_mac.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define XBEE_ATQUERY_TIMEOUT MSEC2TICK(100)
|
|
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
#define XBEE_LOCKUP_QUERYTIME MSEC2TICK(500)
|
|
#define XBEE_LOCKUP_QUERYATTEMPTS 20
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int xbee_interrupt(int irq, FAR void *context, FAR void *arg);
|
|
static void xbee_attnworker(FAR void *arg);
|
|
static bool xbee_validate_apiframe(uint8_t frametype, uint16_t framelen);
|
|
static bool xbee_verify_checksum(FAR const struct iob_s *iob);
|
|
static void xbee_process_apiframes(FAR struct xbee_priv_s *priv,
|
|
FAR struct iob_s *iob);
|
|
static void xbee_process_txstatus(FAR struct xbee_priv_s *priv,
|
|
uint8_t frameid, uint8_t status);
|
|
static void xbee_process_rxframe(FAR struct xbee_priv_s *priv,
|
|
FAR struct iob_s *frame,
|
|
enum ieee802154_addrmode_e addrmode);
|
|
static void xbee_notify(FAR struct xbee_priv_s *priv,
|
|
FAR struct ieee802154_primitive_s *primitive);
|
|
static void xbee_notify_worker(FAR void *arg);
|
|
static void xbee_atquery_timeout(int argc, uint32_t arg, ...);
|
|
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
static void xbee_lockupcheck_timeout(int argc, uint32_t arg, ...);
|
|
static void xbee_lockupcheck_worker(FAR void *arg);
|
|
static void xbee_backup_worker(FAR void *arg);
|
|
static void xbee_lockupcheck_reschedule(FAR struct xbee_priv_s *priv);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_interrupt
|
|
*
|
|
* Description:
|
|
* Hardware interrupt handler
|
|
*
|
|
* Input Parameters:
|
|
* irq - Number of the IRQ that generated the interrupt
|
|
* context - Interrupt register state save info (architecture-specific)
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int xbee_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
priv->attn_latched = true;
|
|
|
|
/* In complex environments, we cannot do SPI transfers from the interrupt
|
|
* handler because semaphores are probably used to lock the SPI bus. In
|
|
* this case, we will defer processing to the worker thread. This is also
|
|
* much kinder in the use of system resources and is, therefore, probably
|
|
* a good thing to do in any event.
|
|
*/
|
|
|
|
if (work_available(&priv->attnwork))
|
|
{
|
|
return work_queue(HPWORK, &priv->attnwork, xbee_attnworker,
|
|
(FAR void *)priv, 0);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_attnworker
|
|
*
|
|
* Description:
|
|
* Perform interrupt handling (Attention) logic outside of the interrupt
|
|
* handler (on the work queue thread).
|
|
*
|
|
* Input Parameters:
|
|
* arg - The reference to the driver structure (cast to void*)
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_attnworker(FAR void *arg)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
FAR struct iob_s *iobhead = NULL;
|
|
FAR struct iob_s *iob = NULL;
|
|
FAR struct iob_s *previob = NULL;
|
|
uint16_t rxframelen = 0;
|
|
|
|
DEBUGASSERT(priv);
|
|
DEBUGASSERT(priv->spi);
|
|
|
|
/* Allocate an IOB for the incoming data. */
|
|
|
|
iob = iob_alloc(false, IOBUSER_WIRELESS_RAD802154);
|
|
iob->io_flink = NULL;
|
|
iob->io_len = 0;
|
|
iob->io_offset = 0;
|
|
iob->io_pktlen = 0;
|
|
|
|
/* Keep a reference to the first IOB. If we need to allocate more than
|
|
* one to hold each API frame, then we will still have this reference to
|
|
* the head of the list
|
|
*/
|
|
|
|
iobhead = iob;
|
|
|
|
/* NOTE: There is a helpful side-effect to trying to get the SPI Lock here
|
|
* even when there is a write going on. That is, if the SPI write are on a
|
|
* thread with lower priority, trying to get the lock here should boost
|
|
* the priority of that thread, helping move along the low-level driver
|
|
* work that really should be happening in a high priority way anyway.
|
|
*/
|
|
|
|
SPI_LOCK(priv->spi, 1);
|
|
SPI_SETBITS(priv->spi, 8);
|
|
SPI_SETMODE(priv->spi, SPIDEV_MODE0);
|
|
SPI_SETFREQUENCY(priv->spi, CONFIG_IEEE802154_XBEE_FREQUENCY);
|
|
|
|
/* Assert CS */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), true);
|
|
|
|
/* Check to make sure all the data hasn't already been clocked in and
|
|
* we just need to process it.
|
|
*/
|
|
|
|
if (priv->attn_latched && !priv->lower->poll(priv->lower))
|
|
{
|
|
priv->attn_latched = false;
|
|
}
|
|
|
|
if (priv->attn_latched)
|
|
{
|
|
while (priv->lower->poll(priv->lower) && iob != NULL)
|
|
{
|
|
DEBUGASSERT(iob->io_len <= CONFIG_IOB_BUFSIZE);
|
|
|
|
SPI_RECVBLOCK(priv->spi, &iob->io_data[iob->io_len], 1);
|
|
|
|
switch (iob->io_len)
|
|
{
|
|
case XBEE_APIFRAMEINDEX_STARTBYTE:
|
|
{
|
|
if (iob->io_data[iob->io_len] == XBEE_STARTBYTE)
|
|
{
|
|
iob->io_len++;
|
|
}
|
|
}
|
|
break;
|
|
case XBEE_APIFRAMEINDEX_LENGTHMSB:
|
|
{
|
|
rxframelen = iob->io_data[iob->io_len++] << 8;
|
|
}
|
|
break;
|
|
case XBEE_APIFRAMEINDEX_LENGTHLSB:
|
|
{
|
|
rxframelen |= iob->io_data[iob->io_len++];
|
|
rxframelen += XBEE_APIFRAME_OVERHEAD;
|
|
}
|
|
break;
|
|
case XBEE_APIFRAMEINDEX_TYPE:
|
|
{
|
|
/* Check that the length and frame type make sense
|
|
* together.
|
|
*/
|
|
|
|
if (!xbee_validate_apiframe(iob->io_data[iob->io_len],
|
|
rxframelen -
|
|
XBEE_APIFRAME_OVERHEAD))
|
|
{
|
|
wlwarn("invalid length on incoming API frame. "
|
|
"Dropping!\n");
|
|
iob->io_len = 0;
|
|
}
|
|
else
|
|
{
|
|
iob->io_len++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
if (iob->io_len == rxframelen - 1)
|
|
{
|
|
iob->io_len++;
|
|
if (xbee_verify_checksum(iob))
|
|
{
|
|
/* This API frame is complete. Allocate a new IOB
|
|
* and link it to the existing one. When we are all
|
|
* finished we will pass this IOB list along for
|
|
* processing.
|
|
*/
|
|
|
|
iob->io_flink =
|
|
iob_tryalloc(false, IOBUSER_WIRELESS_RAD802154);
|
|
|
|
iob = iob->io_flink;
|
|
|
|
if (iob != NULL)
|
|
{
|
|
iob->io_flink = NULL;
|
|
iob->io_len = 0;
|
|
iob->io_offset = 0;
|
|
iob->io_pktlen = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wlwarn("invalid checksum on incoming API frame. "
|
|
"Dropping!\n");
|
|
iob->io_len = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iob->io_len++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!priv->lower->poll(priv->lower))
|
|
{
|
|
priv->attn_latched = false;
|
|
}
|
|
}
|
|
|
|
/* The last IOB in the list (or the only one) may be able to be freed
|
|
* since it may not have any valid data. If it contains some data, but not
|
|
* a whole API frame, something is wrong, so we just warn the user and
|
|
* drop the data. If the data was valid, the ATTN line should have stayed
|
|
* asserted until all the data was clocked in. So if we don't have a full
|
|
* frame, we can only drop it.
|
|
*/
|
|
|
|
if (iob != NULL)
|
|
{
|
|
if (iob->io_len < XBEE_APIFRAME_OVERHEAD || iob->io_len != rxframelen)
|
|
{
|
|
if (iobhead == iob)
|
|
{
|
|
iobhead = NULL;
|
|
}
|
|
else
|
|
{
|
|
previob = iobhead;
|
|
while (previob->io_flink != iob)
|
|
{
|
|
previob = previob->io_flink;
|
|
}
|
|
|
|
previob->io_flink = NULL;
|
|
}
|
|
|
|
if (iob->io_len > 0)
|
|
{
|
|
wlwarn("Partial API frame clocked in. Dropping!\n");
|
|
}
|
|
|
|
iob_free(iob, IOBUSER_WIRELESS_RAD802154);
|
|
}
|
|
}
|
|
|
|
if (iobhead != NULL)
|
|
{
|
|
if (priv->rx_apiframes == NULL)
|
|
{
|
|
priv->rx_apiframes = iobhead;
|
|
}
|
|
else
|
|
{
|
|
iob = priv->rx_apiframes;
|
|
while (iob->io_flink != NULL)
|
|
{
|
|
iob = iob->io_flink;
|
|
}
|
|
|
|
iob->io_flink = iobhead;
|
|
}
|
|
}
|
|
|
|
/* Before unlocking the SPI bus, we "detach" the IOB list from the private
|
|
* struct and keep a copy. When the SPI bus becomes free, more data can
|
|
* be clocked in from an SPI write. By detaching the IOB list, we can
|
|
* process the incoming data without holding up the SPI bus
|
|
*/
|
|
|
|
iobhead = priv->rx_apiframes;
|
|
priv->rx_apiframes = NULL;
|
|
|
|
/* De-assert CS */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), false);
|
|
|
|
/* Relinquish control of the SPI Bus */
|
|
|
|
SPI_LOCK(priv->spi, 0);
|
|
|
|
if (iobhead != NULL)
|
|
{
|
|
xbee_process_apiframes(priv, iobhead);
|
|
}
|
|
|
|
/* If an interrupt occurred while the worker was running, it was not
|
|
* scheduled since there is a good chance this function has already handled
|
|
* it as part of the previous ATTN assertion. Therefore, if the ATTN
|
|
* line is asserted again reschedule the work.
|
|
*/
|
|
|
|
if (priv->attn_latched)
|
|
{
|
|
work_queue(HPWORK, &priv->attnwork, xbee_attnworker,
|
|
(FAR void *)priv, 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_validate_apiframe
|
|
*
|
|
* Description:
|
|
* Verifies that the API frame type is known and that the length makes
|
|
* sense for that frame type.
|
|
*
|
|
* Input Parameters:
|
|
* frame - pointer to the frame data
|
|
* datalen - The size of the data section of the frame. This is the value
|
|
* included as the second and third byte of the frame.
|
|
*
|
|
* Returned Value:
|
|
* true - Frame type is known and length is logical
|
|
* false - Frame type is unknown or length is invalid for frame type
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool xbee_validate_apiframe(uint8_t frametype, uint16_t datalen)
|
|
{
|
|
switch (frametype)
|
|
{
|
|
case XBEE_APIFRAME_MODEMSTATUS:
|
|
{
|
|
if (datalen != 2)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case XBEE_APIFRAME_ATRESPONSE:
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case XBEE_APIFRAME_TXSTATUS:
|
|
{
|
|
if (datalen != 3)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case XBEE_APIFRAME_RX_EADDR:
|
|
{
|
|
if (datalen < 14)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case XBEE_APIFRAME_RX_SADDR:
|
|
{
|
|
if (datalen < 8)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_verify_checksum
|
|
*
|
|
* Description:
|
|
* Verifies API frame checksum.
|
|
*
|
|
* Input Parameters:
|
|
* frame - pointer to the frame data
|
|
* framelen - size of the overall frame. NOT the data length field
|
|
*
|
|
* Returned Value:
|
|
* true - Checksum is valid
|
|
* false - Checksum is invalid
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool xbee_verify_checksum(FAR const struct iob_s *iob)
|
|
{
|
|
int i;
|
|
uint8_t checksum = 0;
|
|
|
|
DEBUGASSERT(iob->io_len > XBEE_APIFRAME_OVERHEAD);
|
|
|
|
/* Skip the start byte and frame length, but include the checksum */
|
|
|
|
for (i = 3; i < iob->io_len; i++)
|
|
{
|
|
checksum += iob->io_data[i];
|
|
}
|
|
|
|
if (checksum != 0xff)
|
|
{
|
|
wlwarn("Invalid checksum\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_process_apiframes
|
|
*
|
|
* Description:
|
|
* Processes a list of complete API frames.
|
|
*
|
|
* Assumptions:
|
|
* Frame has already been validated using frame type and frame length and
|
|
* the checksum has also been verified.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_process_apiframes(FAR struct xbee_priv_s *priv,
|
|
FAR struct iob_s *framelist)
|
|
{
|
|
FAR struct ieee802154_primitive_s *primitive;
|
|
FAR struct iob_s *frame;
|
|
FAR struct iob_s *nextframe;
|
|
FAR char *command;
|
|
|
|
DEBUGASSERT(framelist != NULL);
|
|
|
|
frame = framelist;
|
|
|
|
/* At the end of each iteration, frame is set to the next frame in the list
|
|
* and the IOB is freed. If the IOB is not supposed to be freed, per the
|
|
* API frame type, the logic must update the frame to the next frame in the
|
|
* list and use continue to skip freeing the IOB.
|
|
*/
|
|
|
|
while (frame)
|
|
{
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
/* Any time we receive an API frame from the XBee we reschedule our
|
|
* lockup timeout. Receiving an API frame is an indication that the
|
|
* XBee is not locked up.
|
|
*/
|
|
|
|
xbee_lockupcheck_reschedule(priv);
|
|
#endif
|
|
|
|
/* Skip over start byte and length */
|
|
|
|
frame->io_offset += XBEE_APIFRAMEINDEX_TYPE;
|
|
|
|
switch (frame->io_data[frame->io_offset++])
|
|
{
|
|
case XBEE_APIFRAME_MODEMSTATUS:
|
|
{
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
/* If the Modem Status indicates that the coordinator has just
|
|
* formed a new network, we know that the channel and PAN ID
|
|
* are locked in. In case we need to reset the radio due to a
|
|
* lockup, we tell the XBee to write the parameters to non-
|
|
* volatile memory so that upon reset, we can resume operation.
|
|
*/
|
|
|
|
if (frame->io_data[frame->io_offset] == 0x06)
|
|
{
|
|
if (work_available(&priv->backupwork))
|
|
{
|
|
work_queue(LPWORK, &priv->backupwork,
|
|
xbee_backup_worker, (FAR void *)priv, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
wlinfo("Modem Status: %d\n",
|
|
frame->io_data[frame->io_offset++]);
|
|
}
|
|
break;
|
|
|
|
case XBEE_APIFRAME_ATRESPONSE:
|
|
{
|
|
frame->io_offset++; /* Skip over frame index */
|
|
|
|
command = (FAR char *)&frame->io_data[frame->io_offset];
|
|
frame->io_offset += 2;
|
|
|
|
wlinfo("AT Response Received: %.*s\n", 2, command);
|
|
|
|
/* Make sure the command status is OK=0 */
|
|
|
|
if (frame->io_data[frame->io_offset])
|
|
{
|
|
wlwarn("AT Command Error: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else
|
|
{
|
|
frame->io_offset++;
|
|
|
|
if (memcmp(command, "ID", 2) == 0)
|
|
{
|
|
priv->addr.panid[1] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.panid[0] =
|
|
frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "SH", 2) == 0)
|
|
{
|
|
priv->addr.eaddr[7] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.eaddr[6] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.eaddr[5] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.eaddr[4] =
|
|
frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "SL", 2) == 0)
|
|
{
|
|
priv->addr.eaddr[3] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.eaddr[2] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.eaddr[1] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.eaddr[0] =
|
|
frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "MY", 2) == 0)
|
|
{
|
|
priv->addr.saddr[1] =
|
|
frame->io_data[frame->io_offset++];
|
|
priv->addr.saddr[0] =
|
|
frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "CH", 2) == 0)
|
|
{
|
|
priv->chan = frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "VR", 2) == 0)
|
|
{
|
|
priv->firmwareversion =
|
|
frame->io_data[frame->io_offset++] << 8;
|
|
priv->firmwareversion |=
|
|
frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "AI", 2) == 0)
|
|
{
|
|
wlinfo("Association Indication: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
|
|
/* 0xff = No association status determined yet. */
|
|
|
|
if (frame->io_data[frame->io_offset] != 0xff &&
|
|
frame->io_data[frame->io_offset] != 0x13)
|
|
{
|
|
wd_cancel(priv->assocwd);
|
|
priv->associating = false;
|
|
|
|
primitive = ieee802154_primitive_allocate();
|
|
primitive->type = IEEE802154_PRIMITIVE_CONF_ASSOC;
|
|
|
|
if (frame->io_data[frame->io_offset] == 0)
|
|
{
|
|
primitive->u.assocconf.status =
|
|
IEEE802154_STATUS_SUCCESS;
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
/* Upon successful association, we know that
|
|
* the channel and PAN ID give us a valid
|
|
* connection. In case we need to reset the
|
|
* radio due to a lockup, we tell the XBee to
|
|
* write the parameters to non-volatile memory
|
|
* so that upon reset, we can resume operation.
|
|
*/
|
|
|
|
if (work_available(&priv->backupwork))
|
|
{
|
|
work_queue(LPWORK, &priv->backupwork,
|
|
xbee_backup_worker,
|
|
(FAR void *)priv, 0);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
primitive->u.assocconf.status =
|
|
IEEE802154_STATUS_FAILURE;
|
|
}
|
|
|
|
xbee_notify(priv, primitive);
|
|
}
|
|
}
|
|
else if (memcmp(command, "A1", 2) == 0)
|
|
{
|
|
wlinfo("Endpoint Association: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else if (memcmp(command, "A2", 2) == 0)
|
|
{
|
|
wlinfo("Coordinator Association: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else if (memcmp(command, "CE", 2) == 0)
|
|
{
|
|
wlinfo("Coordinator Enable: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else if (memcmp(command, "SP", 2) == 0)
|
|
{
|
|
wlinfo("Sleep Period: %dsec\n",
|
|
frame->io_data[frame->io_offset] / 100);
|
|
}
|
|
else if (memcmp(command, "RR", 2) == 0)
|
|
{
|
|
wlinfo("XBee Retries: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else if (memcmp(command, "MM", 2) == 0)
|
|
{
|
|
wlinfo("Mac Mode: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else if (memcmp(command, "PL", 2) == 0)
|
|
{
|
|
wlinfo("Power Level: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
priv->pwrlvl = frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "PM", 2) == 0)
|
|
{
|
|
wlinfo("Boost Mode (Power Mode): %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
priv->boostmode = frame->io_data[frame->io_offset++];
|
|
}
|
|
else if (memcmp(command, "WR", 2) == 0)
|
|
{
|
|
wlinfo("Write Complete: %d\n",
|
|
frame->io_data[frame->io_offset]);
|
|
}
|
|
else
|
|
{
|
|
wlwarn("Unhandled AT Response: %.*s\n", 2, command);
|
|
}
|
|
|
|
/* If this is the command we are querying for */
|
|
|
|
if ((priv->querycmd[0] == *command) &&
|
|
(priv->querycmd[1] == *(command + 1)))
|
|
{
|
|
wd_cancel(priv->atquery_wd);
|
|
priv->querydone = true;
|
|
nxsem_post(&priv->atresp_sem);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case XBEE_APIFRAME_TXSTATUS:
|
|
{
|
|
xbee_process_txstatus(priv, frame->io_data[frame->io_offset],
|
|
frame->io_data[frame->io_offset + 1]);
|
|
}
|
|
break;
|
|
|
|
case XBEE_APIFRAME_RX_EADDR:
|
|
{
|
|
nextframe = frame->io_flink;
|
|
frame->io_flink = NULL;
|
|
xbee_process_rxframe(priv, frame,
|
|
IEEE802154_ADDRMODE_EXTENDED);
|
|
frame = nextframe;
|
|
|
|
/* xbee_process_rxframe takes care of freeing the IOB or
|
|
* passing it along to the next highest layer.
|
|
*/
|
|
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case XBEE_APIFRAME_RX_SADDR:
|
|
{
|
|
nextframe = frame->io_flink;
|
|
frame->io_flink = NULL;
|
|
xbee_process_rxframe(priv, frame, IEEE802154_ADDRMODE_SHORT);
|
|
frame = nextframe;
|
|
|
|
/* xbee_process_rxframe takes care of freeing the IOB or
|
|
* passing it along to the next highest layer.
|
|
*/
|
|
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
/* This really should never happen since xbee_validateframe
|
|
* should have caught it.
|
|
*/
|
|
|
|
wlwarn("Unknown frame type: %d\n",
|
|
frame[XBEE_APIFRAMEINDEX_TYPE]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* IOB free logic assumes that a valid io_flink entry in the IOB that
|
|
* is being freed indicates that the IOB is a part of an IOB chain.
|
|
* Since that is not the case here and we are just using the io_flink
|
|
* field as a way of managing a list of independent frames, we set the
|
|
* io_flink to NULL prior to freeing it.
|
|
*/
|
|
|
|
nextframe = frame->io_flink;
|
|
frame->io_flink = NULL;
|
|
iob_free(frame, IOBUSER_WIRELESS_RAD802154);
|
|
frame = nextframe;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_process_rxframe
|
|
*
|
|
* Description:
|
|
* Process an incoming RX frame.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_process_rxframe(FAR struct xbee_priv_s *priv,
|
|
FAR struct iob_s *frame,
|
|
enum ieee802154_addrmode_e addrmode)
|
|
{
|
|
FAR struct ieee802154_primitive_s *primitive;
|
|
FAR struct ieee802154_data_ind_s *dataind;
|
|
|
|
DEBUGASSERT(frame != NULL);
|
|
|
|
primitive = ieee802154_primitive_allocate();
|
|
dataind = &primitive->u.dataind;
|
|
|
|
primitive->type = IEEE802154_PRIMITIVE_IND_DATA;
|
|
|
|
dataind->frame = frame;
|
|
|
|
if (addrmode == IEEE802154_ADDRMODE_EXTENDED)
|
|
{
|
|
dataind->dest.mode = IEEE802154_ADDRMODE_EXTENDED;
|
|
dataind->src.mode = IEEE802154_ADDRMODE_EXTENDED;
|
|
dataind->src.eaddr[7] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[6] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[5] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[4] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[3] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[2] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[1] = frame->io_data[frame->io_offset++];
|
|
dataind->src.eaddr[0] = frame->io_data[frame->io_offset++];
|
|
}
|
|
else
|
|
{
|
|
dataind->src.mode = IEEE802154_ADDRMODE_SHORT;
|
|
dataind->src.saddr[1] = frame->io_data[frame->io_offset++];
|
|
dataind->src.saddr[0] = frame->io_data[frame->io_offset++];
|
|
}
|
|
|
|
/* The XBee does not give us information about how the device was
|
|
* addressed. It only indicates the source mode. Therefore, we make the
|
|
* assumption that if the saddr is set, we we're addressed using the
|
|
* saddr, otherwise we must have been addressed using the eaddr.
|
|
*/
|
|
|
|
memcpy(&dataind->dest, &priv->addr, sizeof(struct ieee802154_addr_s));
|
|
|
|
if (IEEE802154_SADDRCMP(priv->addr.saddr, &IEEE802154_SADDR_BCAST) ||
|
|
IEEE802154_SADDRCMP(priv->addr.saddr, &IEEE802154_SADDR_UNSPEC))
|
|
{
|
|
dataind->dest.mode = IEEE802154_ADDRMODE_EXTENDED;
|
|
}
|
|
else
|
|
{
|
|
dataind->dest.mode = IEEE802154_ADDRMODE_SHORT;
|
|
}
|
|
|
|
dataind->rssi = frame->io_data[frame->io_offset++];
|
|
|
|
frame->io_offset++; /* Skip options byte */
|
|
|
|
frame->io_len--; /* Remove the checksum */
|
|
|
|
xbee_notify(priv, primitive);
|
|
|
|
wlinfo("Received frame. RSSI: -%d dbm\n", dataind->rssi);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_process_txstatus
|
|
*
|
|
* Description:
|
|
* Process an incoming TX status message. This searches the list of pending
|
|
* tx requests and notifies the
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_process_txstatus(FAR struct xbee_priv_s *priv,
|
|
uint8_t frameid, uint8_t status)
|
|
{
|
|
FAR struct ieee802154_primitive_s *primitive;
|
|
|
|
primitive = ieee802154_primitive_allocate();
|
|
|
|
primitive->type = IEEE802154_PRIMITIVE_CONF_DATA;
|
|
|
|
switch (status)
|
|
{
|
|
case 0x00:
|
|
primitive->u.dataconf.status =
|
|
IEEE802154_STATUS_SUCCESS;
|
|
break;
|
|
|
|
case 0x01:
|
|
case 0x21:
|
|
primitive->u.dataconf.status =
|
|
IEEE802154_STATUS_NO_ACK;
|
|
break;
|
|
|
|
case 0x02:
|
|
primitive->u.dataconf.status =
|
|
IEEE802154_STATUS_CHANNEL_ACCESS_FAILURE;
|
|
break;
|
|
|
|
default:
|
|
primitive->u.dataconf.status =
|
|
IEEE802154_STATUS_FAILURE;
|
|
break;
|
|
}
|
|
|
|
if (status != IEEE802154_STATUS_SUCCESS)
|
|
{
|
|
wlwarn("TX done. Frame ID: %d Status: 0x%02x\n", frameid, status);
|
|
}
|
|
else
|
|
{
|
|
wlinfo("TX done. Frame ID: %d Status: 0x%02x\n", frameid, status);
|
|
}
|
|
|
|
xbee_notify(priv, primitive);
|
|
|
|
/* If this is the frame we are currently waiting on, cancel the timeout,
|
|
* and notify the waiting thread that the transmit is done
|
|
*/
|
|
|
|
if (priv->frameid == frameid)
|
|
{
|
|
wd_cancel(priv->reqdata_wd);
|
|
priv->txdone = true;
|
|
nxsem_post(&priv->txdone_sem);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_notify
|
|
*
|
|
* Description:
|
|
* Queue the primitive in the queue and queue work on the LPWORK
|
|
* queue if is not already scheduled.
|
|
*
|
|
* Assumptions:
|
|
* Called with the MAC locked
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_notify(FAR struct xbee_priv_s *priv,
|
|
FAR struct ieee802154_primitive_s *primitive)
|
|
{
|
|
while (nxsem_wait(&priv->primitive_sem) < 0);
|
|
|
|
sq_addlast((FAR sq_entry_t *)primitive, &priv->primitive_queue);
|
|
nxsem_post(&priv->primitive_sem);
|
|
|
|
if (work_available(&priv->notifwork))
|
|
{
|
|
work_queue(LPWORK, &priv->notifwork, xbee_notify_worker,
|
|
(FAR void *)priv, 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_notify_worker
|
|
*
|
|
* Description:
|
|
* Pop each primitive off the queue and call the registered
|
|
* callbacks. There is special logic for handling ieee802154_data_ind_s.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_notify_worker(FAR void *arg)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
FAR struct xbee_maccb_s *cb;
|
|
FAR struct ieee802154_primitive_s *primitive;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
while (nxsem_wait(&priv->primitive_sem) < 0);
|
|
primitive =
|
|
(FAR struct ieee802154_primitive_s *)sq_remfirst(&priv->primitive_queue);
|
|
nxsem_post(&priv->primitive_sem);
|
|
|
|
while (primitive != NULL)
|
|
{
|
|
/* Data indications are a special case since the frame can only be
|
|
* passed to one place. The return value of the notify call is used to
|
|
* accept or reject the primitive. In the case of the data indication,
|
|
* there can only be one accept. Callbacks are stored in order of
|
|
* there receiver priority ordered when the callbacks are bound in
|
|
* mac802154_bind().
|
|
*/
|
|
|
|
if (primitive->type == IEEE802154_PRIMITIVE_IND_DATA)
|
|
{
|
|
bool dispose = true;
|
|
|
|
primitive->nclients = 1;
|
|
|
|
for (cb = priv->cb; cb != NULL; cb = cb->flink)
|
|
{
|
|
if (cb->notify != NULL)
|
|
{
|
|
ret = cb->notify(cb, primitive);
|
|
if (ret >= 0)
|
|
{
|
|
/* The receiver accepted and disposed of the frame and
|
|
* it's meta-data. We are done.
|
|
*/
|
|
|
|
dispose = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dispose)
|
|
{
|
|
iob_free(primitive->u.dataind.frame,
|
|
IOBUSER_WIRELESS_RAD802154);
|
|
ieee802154_primitive_free(primitive);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Set the number of clients count so that the primitive resources
|
|
* will be preserved until all clients are finished with it.
|
|
*/
|
|
|
|
primitive->nclients = priv->nclients;
|
|
|
|
/* Try to notify every registered MAC client */
|
|
|
|
for (cb = priv->cb; cb != NULL; cb = cb->flink)
|
|
{
|
|
if (cb->notify != NULL)
|
|
{
|
|
ret = cb->notify(cb, primitive);
|
|
if (ret < 0)
|
|
{
|
|
ieee802154_primitive_free(primitive);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ieee802154_primitive_free(primitive);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get the next primitive then loop */
|
|
|
|
while (nxsem_wait(&priv->primitive_sem) < 0);
|
|
|
|
primitive = (FAR struct ieee802154_primitive_s *)
|
|
sq_remfirst(&priv->primitive_queue);
|
|
nxsem_post(&priv->primitive_sem);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_atquery_timeout
|
|
*
|
|
* Description:
|
|
* This function runs when an AT Query has timed out waiting for a response
|
|
* from the XBee module. This really should never happen, but if it does,
|
|
* handle it gracefully by retrying the query.
|
|
*
|
|
* Input Parameters:
|
|
* argc - The number of available arguments
|
|
* arg - The first argument
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_atquery_timeout(int argc, uint32_t arg, ...)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
wlwarn("AT Query timeout\n");
|
|
|
|
/* Wake the pending query thread so it can retry */
|
|
|
|
nxsem_post(&priv->atresp_sem);
|
|
}
|
|
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
/****************************************************************************
|
|
* Name: xbee_lockupcheck_timeout
|
|
*
|
|
* Description:
|
|
* This function runs when a query to the XBee has not been issued for
|
|
* awhile. We query periodically in an effort to detect if the XBee has
|
|
* locked up.
|
|
*
|
|
* Input Parameters:
|
|
* argc - The number of available arguments
|
|
* arg - The first argument
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_lockupcheck_timeout(int argc, uint32_t arg, ...)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
if (work_available(&priv->lockupwork))
|
|
{
|
|
work_queue(LPWORK, &priv->lockupwork, xbee_lockupcheck_worker,
|
|
(FAR void *)priv, 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_lockupcheck_worker
|
|
*
|
|
* Description:
|
|
* Perform an AT query to make sure the XBee is still responsive. If we've
|
|
* gotten here, it means we haven't talked to the XBee in a while. This
|
|
* is to workaround an issue where the XBee locks up. In this condition,
|
|
* most of the time, querying it kicks it out of the state. In some
|
|
* conditions though, the XBee locks up to the point where it needs to be
|
|
* reset. This will be handled inside xbee_atquery; if we don't get a
|
|
* response we will reset the XBee.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_lockupcheck_worker(FAR void *arg)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
DEBUGASSERT(priv != NULL);
|
|
wlinfo("Issuing query to detect lockup\n");
|
|
xbee_query_chan(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_backup_worker
|
|
*
|
|
* Description:
|
|
* In the case that we need to reset the XBee to bring it out of a locked
|
|
* up state, we need to be able to restore it's previous state seamlessly
|
|
* to resume operation. In order to achieve this, we backup the
|
|
* parameters using the "WR" AT command. We do this at strategic points
|
|
* as we don't know what type of memory technology is being used and
|
|
* writing too often may reduce the lifetime of the device. The two key
|
|
* points chosen are upon association for endpoint nodes, and upon
|
|
* creating a new network for coordinator nodes. These conditions
|
|
* indicate that the node is actively communicating on the network and
|
|
* that the channel and pan ID are now set for the network.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void xbee_backup_worker(FAR void *arg)
|
|
{
|
|
FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg;
|
|
DEBUGASSERT(priv != NULL);
|
|
xbee_save_params(priv);
|
|
}
|
|
|
|
static void xbee_lockupcheck_reschedule(FAR struct xbee_priv_s *priv)
|
|
{
|
|
wd_cancel(priv->lockup_wd);
|
|
|
|
/* Kickoff the watchdog timer that will query the XBee periodically (if
|
|
* naturally occurring queries do not occur). We query periodically to
|
|
* make sure the XBee is still responsive. If during any query, the XBee
|
|
* does not respond after multiple attempts, we restart the XBee to get
|
|
* it back in a working state
|
|
*/
|
|
|
|
wd_start(priv->lockup_wd, XBEE_LOCKUP_QUERYTIME, xbee_lockupcheck_timeout,
|
|
1, (wdparm_t)priv);
|
|
}
|
|
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_init
|
|
*
|
|
* Description:
|
|
* Initialize an XBee driver. The XBee device is assumed to be
|
|
* in the post-reset state upon entry to this function.
|
|
*
|
|
* Input Parameters:
|
|
* spi - A reference to the platform's SPI driver for the XBee
|
|
* lower - The MCU-specific interrupt used to control low-level MCU
|
|
* functions (i.e., XBee GPIO interrupts).
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
XBEEHANDLE xbee_init(FAR struct spi_dev_s *spi,
|
|
FAR const struct xbee_lower_s *lower)
|
|
{
|
|
FAR struct xbee_priv_s *priv;
|
|
|
|
/* Allocate object */
|
|
|
|
priv = (FAR struct xbee_priv_s *) kmm_zalloc(sizeof(struct xbee_priv_s));
|
|
if (priv == NULL)
|
|
{
|
|
wlinfo("Failed allocation xbee_priv_s structure\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Attach irq */
|
|
|
|
if (lower->attach(lower, xbee_interrupt, priv) != OK)
|
|
{
|
|
wlinfo("Failed to attach IRQ with XBee lower half\n");
|
|
kmm_free(priv);
|
|
return NULL;
|
|
}
|
|
|
|
priv->lower = lower;
|
|
priv->spi = spi;
|
|
|
|
nxsem_init(&priv->primitive_sem, 0, 1);
|
|
nxsem_init(&priv->atquery_sem, 0, 1);
|
|
nxsem_init(&priv->tx_sem, 0, 1);
|
|
nxsem_init(&priv->txdone_sem, 0, 0);
|
|
nxsem_set_protocol(&priv->txdone_sem, SEM_PRIO_NONE);
|
|
|
|
ieee802154_primitivepool_initialize();
|
|
|
|
sq_init(&priv->primitive_queue);
|
|
|
|
priv->assocwd = wd_create();
|
|
priv->atquery_wd = wd_create();
|
|
priv->reqdata_wd = wd_create();
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
priv->lockup_wd = wd_create();
|
|
#endif
|
|
|
|
priv->frameid = 0; /* Frame ID should never be 0, but it is incremented
|
|
* in xbee_next_frameid before being used so it will be 1 */
|
|
priv->querycmd[0] = 0;
|
|
priv->querycmd[1] = 0;
|
|
|
|
/* Initialize the saddr */
|
|
|
|
IEEE802154_SADDRCOPY(priv->addr.saddr, &IEEE802154_SADDR_UNSPEC);
|
|
|
|
/* Reset the XBee radio */
|
|
|
|
priv->lower->reset(priv->lower);
|
|
|
|
/* Enable interrupts */
|
|
|
|
priv->lower->enable(priv->lower, true);
|
|
|
|
/* Trigger a query to tell the XBee to operate in SPI mode. By default the
|
|
* XBee uses the UART interface. It switches automatically when a valid
|
|
* SPI frame is received.
|
|
*
|
|
* Use this opportunity to pull the extended address
|
|
*/
|
|
|
|
xbee_query_eaddr(priv);
|
|
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
xbee_lockupcheck_reschedule(priv);
|
|
#endif
|
|
|
|
return (XBEEHANDLE)priv;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_send_apiframe
|
|
*
|
|
* Description:
|
|
* Write an api frame over SPI
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_send_apiframe(FAR struct xbee_priv_s *priv,
|
|
FAR const uint8_t *frame, uint16_t framelen)
|
|
{
|
|
FAR struct iob_s *iob;
|
|
FAR struct iob_s *previob;
|
|
FAR struct iob_s *iobhead;
|
|
uint16_t rxframelen = 0;
|
|
int i;
|
|
|
|
/* Get access to SPI bus, set relevant settings */
|
|
|
|
SPI_LOCK(priv->spi, 1);
|
|
SPI_SETBITS(priv->spi, 8);
|
|
SPI_SETMODE(priv->spi, SPIDEV_MODE0);
|
|
SPI_SETFREQUENCY(priv->spi, CONFIG_IEEE802154_XBEE_FREQUENCY);
|
|
|
|
/* Assert CS */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), true);
|
|
|
|
/* Allocate an IOB for the incoming data. The XBee supports full-duplex
|
|
* SPI communication. This means that the MISO data can become valid at any
|
|
* time. This requires us to process incoming MISO data to see if it is
|
|
* valid.
|
|
*
|
|
* If we can't allocate an IOB, then we have to just drop the incoming
|
|
* data.
|
|
*/
|
|
|
|
iob = iob_tryalloc(false, IOBUSER_WIRELESS_RAD802154);
|
|
iob->io_flink = NULL;
|
|
iob->io_len = 0;
|
|
iob->io_offset = 0;
|
|
iob->io_pktlen = 0;
|
|
|
|
/* Keep a reference to the first IOB. If we need to allocate more than
|
|
* one to hold each API frame, then we will still have this reference to
|
|
* the head of the list
|
|
*/
|
|
|
|
iobhead = iob;
|
|
|
|
i = 0;
|
|
while (i < framelen || (priv->lower->poll(priv->lower) && iob != NULL))
|
|
{
|
|
if (i < framelen)
|
|
{
|
|
if (iob)
|
|
{
|
|
iob->io_data[iob->io_len] = SPI_SEND(priv->spi, frame[i++]);
|
|
}
|
|
else
|
|
{
|
|
SPI_SEND(priv->spi, frame[i++]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SPI_RECVBLOCK(priv->spi, &iob->io_data[iob->io_len], 1);
|
|
}
|
|
|
|
/* attn_latched should be set true immediately from the interrupt. Any
|
|
* data prior to that can be completely ignored.
|
|
*/
|
|
|
|
if (priv->attn_latched && iob != NULL)
|
|
{
|
|
DEBUGASSERT(iob->io_len <= CONFIG_IOB_BUFSIZE);
|
|
|
|
switch (iob->io_len)
|
|
{
|
|
case XBEE_APIFRAMEINDEX_STARTBYTE:
|
|
{
|
|
if (iob->io_data[iob->io_len] == XBEE_STARTBYTE)
|
|
{
|
|
iob->io_len++;
|
|
}
|
|
}
|
|
break;
|
|
case XBEE_APIFRAMEINDEX_LENGTHMSB:
|
|
{
|
|
rxframelen = iob->io_data[iob->io_len++] << 8;
|
|
}
|
|
break;
|
|
case XBEE_APIFRAMEINDEX_LENGTHLSB:
|
|
{
|
|
rxframelen |= iob->io_data[iob->io_len++];
|
|
rxframelen += XBEE_APIFRAME_OVERHEAD;
|
|
}
|
|
break;
|
|
case XBEE_APIFRAMEINDEX_TYPE:
|
|
{
|
|
/* Check that the length and frame type make sense
|
|
* together.
|
|
*/
|
|
|
|
if (!xbee_validate_apiframe(iob->io_data[iob->io_len],
|
|
rxframelen -
|
|
XBEE_APIFRAME_OVERHEAD))
|
|
{
|
|
wlwarn("invalid length on incoming API frame. "
|
|
"Dropping!\n");
|
|
iob->io_len = 0;
|
|
}
|
|
else
|
|
{
|
|
iob->io_len++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
if (iob->io_len == rxframelen - 1)
|
|
{
|
|
iob->io_len++;
|
|
if (xbee_verify_checksum(iob))
|
|
{
|
|
/* This API frame is complete. Allocate a new IOB
|
|
* and link it to the existing one. When we are all
|
|
* finished we will pass this IOB list along for
|
|
* processing.
|
|
*/
|
|
|
|
iob->io_flink =
|
|
iob_tryalloc(false, IOBUSER_WIRELESS_RAD802154);
|
|
iob = iob->io_flink;
|
|
|
|
if (iob != NULL)
|
|
{
|
|
iob->io_flink = NULL;
|
|
iob->io_len = 0;
|
|
iob->io_offset = 0;
|
|
iob->io_pktlen = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wlwarn("invalid checksum on incoming API frame. "
|
|
"Dropping!\n");
|
|
iob->io_len = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iob->io_len++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The last IOB in the list (or the only one) may be able to be freed
|
|
* since it may not have any valid data. If it contains some data, but not
|
|
* a whole API frame, something is wrong, so we just warn the user and
|
|
* drop the data. If the data was valid, the ATTN line should have stayed
|
|
* asserted until all the data was clocked in. So if we don't have a full
|
|
* frame, we can only drop it.
|
|
*/
|
|
|
|
if (iob != NULL)
|
|
{
|
|
if (iob->io_len < XBEE_APIFRAME_OVERHEAD || iob->io_len != rxframelen)
|
|
{
|
|
if (iobhead == iob)
|
|
{
|
|
iobhead = NULL;
|
|
}
|
|
else
|
|
{
|
|
previob = iobhead;
|
|
while (previob->io_flink != iob)
|
|
{
|
|
previob = previob->io_flink;
|
|
}
|
|
|
|
previob->io_flink = NULL;
|
|
}
|
|
|
|
if (iob->io_len > 0)
|
|
{
|
|
wlwarn("Partial API frame clocked in. Dropping!\n");
|
|
}
|
|
|
|
iob_free(iob, IOBUSER_WIRELESS_RAD802154);
|
|
}
|
|
}
|
|
|
|
if (iobhead != NULL)
|
|
{
|
|
if (priv->rx_apiframes == NULL)
|
|
{
|
|
priv->rx_apiframes = iobhead;
|
|
}
|
|
else
|
|
{
|
|
iob = priv->rx_apiframes;
|
|
while (iob->io_flink != NULL)
|
|
{
|
|
iob = iob->io_flink;
|
|
}
|
|
|
|
iob->io_flink = iobhead;
|
|
}
|
|
}
|
|
|
|
/* De-assert CS */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), false);
|
|
|
|
/* Relinquish control of the SPI Bus */
|
|
|
|
SPI_LOCK(priv->spi, 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_atquery
|
|
*
|
|
* Description:
|
|
* Sends AT Query and waits for response from device
|
|
*
|
|
****************************************************************************/
|
|
|
|
int xbee_atquery(FAR struct xbee_priv_s *priv, FAR const char *atcommand)
|
|
{
|
|
int ret;
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
int retries = XBEE_LOCKUP_QUERYATTEMPTS;
|
|
#endif
|
|
|
|
/* Only allow one query at a time */
|
|
|
|
ret = nxsem_wait(&priv->atquery_sem);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
priv->querydone = false;
|
|
|
|
/* We reinitialize this every time, in case something gets out of phase
|
|
* with the timeout and the received response.
|
|
*/
|
|
|
|
nxsem_init(&priv->atresp_sem, 0, 0);
|
|
nxsem_set_protocol(&priv->atresp_sem, SEM_PRIO_NONE);
|
|
|
|
do
|
|
{
|
|
/* There is a note in the XBee datasheet: Once you issue a WR command,
|
|
* do not send any additional characters to the device until after
|
|
* you receive the OK response.
|
|
*
|
|
* If we are issuing a WR command, don't set a timeout. We will have
|
|
* to rely on the XBee getting back to us reliably.
|
|
*/
|
|
|
|
if (memcmp(atcommand, "WR", 2) != 0)
|
|
{
|
|
/* Setup a timeout */
|
|
|
|
wd_start(priv->atquery_wd, XBEE_ATQUERY_TIMEOUT,
|
|
xbee_atquery_timeout, 1, (wdparm_t)priv);
|
|
}
|
|
|
|
/* Send the query */
|
|
|
|
priv->querycmd[0] = *atcommand;
|
|
priv->querycmd[1] = *(atcommand + 1);
|
|
xbee_send_atquery(priv, atcommand);
|
|
|
|
/* Wait for the response to be received */
|
|
|
|
ret = nxsem_wait(&priv->atresp_sem);
|
|
if (ret < 0)
|
|
{
|
|
wd_cancel(priv->atquery_wd);
|
|
priv->querycmd[0] = 0;
|
|
priv->querycmd[1] = 0;
|
|
nxsem_post(&priv->atquery_sem);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_XBEE_LOCKUP_WORKAROUND
|
|
if (--retries == 0 && !priv->querydone)
|
|
{
|
|
wlerr("XBee not responding. Resetting.\n");
|
|
priv->lower->reset(priv->lower);
|
|
retries = XBEE_LOCKUP_QUERYATTEMPTS;
|
|
}
|
|
#endif
|
|
}
|
|
while (!priv->querydone);
|
|
|
|
nxsem_post(&priv->atquery_sem);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_send_atquery
|
|
*
|
|
* Description:
|
|
* Helper function to send the AT query to the XBee
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_send_atquery(FAR struct xbee_priv_s *priv,
|
|
FAR const char *atcommand)
|
|
{
|
|
uint8_t frame[8];
|
|
|
|
wlinfo("AT Query: %c%c\n", *atcommand, *(atcommand + 1));
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 4;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 1;
|
|
frame[5] = *atcommand;
|
|
frame[6] = *(atcommand + 1);
|
|
|
|
xbee_insert_checksum(frame, 8);
|
|
|
|
xbee_send_apiframe(priv, frame, 8);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_panid
|
|
*
|
|
* Description:
|
|
* Sends API frame with AT command request in order to set the PAN ID
|
|
* (Network ID) of the device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_panid(FAR struct xbee_priv_s *priv, FAR const uint8_t *panid)
|
|
{
|
|
uint8_t frame[10];
|
|
|
|
IEEE802154_PANIDCOPY(priv->addr.panid, panid);
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 6;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'I';
|
|
frame[6] = 'D';
|
|
frame[7] = *(panid + 1);
|
|
frame[8] = *(panid);
|
|
|
|
xbee_insert_checksum(frame, 10);
|
|
|
|
xbee_send_apiframe(priv, frame, 10);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_saddr
|
|
*
|
|
* Description:
|
|
* Sends API frame with AT command request in order to set the Short
|
|
* Address (Source Address (MY)) of the device
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_saddr(FAR struct xbee_priv_s *priv, FAR const uint8_t *saddr)
|
|
{
|
|
uint8_t frame[10];
|
|
|
|
IEEE802154_SADDRCOPY(priv->addr.saddr, saddr);
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 6;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'M';
|
|
frame[6] = 'Y';
|
|
frame[7] = *(saddr + 1);
|
|
frame[8] = *(saddr);
|
|
|
|
xbee_insert_checksum(frame, 10);
|
|
|
|
xbee_send_apiframe(priv, frame, 10);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_chan
|
|
*
|
|
* Description:
|
|
* Sends API frame with AT command request in order to set the RF channel
|
|
* (Operatin Channel) of the device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_chan(FAR struct xbee_priv_s *priv, uint8_t chan)
|
|
{
|
|
uint8_t frame[9];
|
|
|
|
priv->chan = chan;
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 5;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'C';
|
|
frame[6] = 'H';
|
|
frame[7] = chan;
|
|
|
|
xbee_insert_checksum(frame, 9);
|
|
|
|
xbee_send_apiframe(priv, frame, 9);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_powerlevel
|
|
*
|
|
* Description:
|
|
* Sends API frame with AT command request in order to set the RF power
|
|
* level of the device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_powerlevel(FAR struct xbee_priv_s *priv, uint8_t level)
|
|
{
|
|
uint8_t frame[9];
|
|
|
|
priv->pwrlvl = level;
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 5;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'P';
|
|
frame[6] = 'L';
|
|
frame[7] = level;
|
|
|
|
xbee_insert_checksum(frame, 9);
|
|
|
|
xbee_send_apiframe(priv, frame, 9);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_boostmode
|
|
*
|
|
* Description:
|
|
* Sends API frame with AT command request in order to set the Boost mode
|
|
* setting of the device. NOTE: XBee Pro always enables boost mode and
|
|
* does not support this command
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_boostmode(FAR struct xbee_priv_s *priv, uint8_t mode)
|
|
{
|
|
uint8_t frame[9];
|
|
|
|
if (mode > 1) return;
|
|
|
|
priv->boostmode = mode;
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 5;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'P';
|
|
frame[6] = 'M';
|
|
frame[7] = mode;
|
|
|
|
xbee_insert_checksum(frame, 9);
|
|
|
|
xbee_send_apiframe(priv, frame, 9);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_epassocflags
|
|
*
|
|
* Description:
|
|
* Set flags in 'A1' command register to determine how endpoint behaves
|
|
* with regards to association.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_epassocflags(FAR struct xbee_priv_s *priv, uint8_t flags)
|
|
{
|
|
uint8_t frame[9];
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 5;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'A';
|
|
frame[6] = '1';
|
|
frame[7] = flags;
|
|
|
|
xbee_insert_checksum(frame, 9);
|
|
|
|
xbee_send_apiframe(priv, frame, 9);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_coordassocflags
|
|
*
|
|
* Description:
|
|
* Set flags in 'A2' command register to determine how coordinator behaves
|
|
* with regards to association.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_coordassocflags(FAR struct xbee_priv_s *priv, uint8_t flags)
|
|
{
|
|
uint8_t frame[9];
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 5;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'A';
|
|
frame[6] = '2';
|
|
frame[7] = flags;
|
|
|
|
xbee_insert_checksum(frame, 9);
|
|
|
|
xbee_send_apiframe(priv, frame, 9);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_set_sleepperiod
|
|
*
|
|
* Description:
|
|
* Set Cyclic Sleep Period using 'SP' AT command.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_set_sleepperiod(FAR struct xbee_priv_s *priv, uint16_t period)
|
|
{
|
|
uint8_t frame[10];
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 6;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'S';
|
|
frame[6] = 'P';
|
|
frame[7] = period >> 8;
|
|
frame[8] = period;
|
|
|
|
xbee_insert_checksum(frame, 10);
|
|
|
|
xbee_send_apiframe(priv, frame, 10);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_enable_coord
|
|
*
|
|
* Description:
|
|
* Enables/Disables coordinator mode using 'CE' command
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_enable_coord(FAR struct xbee_priv_s *priv, bool enable)
|
|
{
|
|
uint8_t frame[9];
|
|
|
|
frame[0] = XBEE_STARTBYTE;
|
|
frame[1] = 0;
|
|
frame[2] = 5;
|
|
frame[3] = XBEE_APIFRAME_ATCOMMMAND;
|
|
frame[4] = 0;
|
|
frame[5] = 'C';
|
|
frame[6] = 'E';
|
|
frame[7] = enable;
|
|
|
|
xbee_insert_checksum(frame, 9);
|
|
|
|
xbee_send_apiframe(priv, frame, 9);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: xbee_regdump
|
|
*
|
|
* Description:
|
|
* Perform a series of queries updating struct and printing settings to
|
|
* SYSLOG.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void xbee_regdump(FAR struct xbee_priv_s *priv)
|
|
{
|
|
xbee_query_firmwareversion(priv);
|
|
|
|
wlinfo("XBee Firmware Version: %04x\n", priv->firmwareversion);
|
|
|
|
xbee_send_atquery(priv, "CE");
|
|
xbee_send_atquery(priv, "A1");
|
|
xbee_send_atquery(priv, "A2");
|
|
xbee_send_atquery(priv, "AI");
|
|
xbee_send_atquery(priv, "SP");
|
|
xbee_send_atquery(priv, "RR");
|
|
xbee_send_atquery(priv, "MM");
|
|
xbee_send_atquery(priv, "PL");
|
|
xbee_send_atquery(priv, "PM");
|
|
}
|