mirror of
https://github.com/oliverschmidt/contiki.git
synced 2024-12-23 16:29:34 +00:00
1620 lines
47 KiB
C
1620 lines
47 KiB
C
/*
|
|
* Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/
|
|
* All rights reserved.
|
|
*
|
|
* 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 of the copyright holder 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
|
|
* ``AS IS'' 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 HOLDER 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.
|
|
*/
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \addtogroup rf-core
|
|
* @{
|
|
*
|
|
* \defgroup rf-core-ieee CC13xx/CC26xx IEEE mode driver
|
|
*
|
|
* @{
|
|
*
|
|
* \file
|
|
* Implementation of the CC13xx/CC26xx IEEE mode NETSTACK_RADIO driver
|
|
*/
|
|
/*---------------------------------------------------------------------------*/
|
|
#include "contiki.h"
|
|
#include "dev/radio.h"
|
|
#include "dev/cc26xx-uart.h"
|
|
#include "dev/oscillators.h"
|
|
#include "net/packetbuf.h"
|
|
#include "net/rime/rimestats.h"
|
|
#include "net/linkaddr.h"
|
|
#include "net/netstack.h"
|
|
#include "sys/energest.h"
|
|
#include "sys/clock.h"
|
|
#include "sys/rtimer.h"
|
|
#include "sys/ctimer.h"
|
|
#include "sys/cc.h"
|
|
#include "lpm.h"
|
|
#include "ti-lib.h"
|
|
#include "rf-core/rf-core.h"
|
|
#include "rf-core/rf-ble.h"
|
|
/*---------------------------------------------------------------------------*/
|
|
/* RF core and RF HAL API */
|
|
#include "hw_rfc_dbell.h"
|
|
#include "hw_rfc_pwr.h"
|
|
/*---------------------------------------------------------------------------*/
|
|
/* RF Core Mailbox API */
|
|
#include "rf-core/api/mailbox.h"
|
|
#include "rf-core/api/common_cmd.h"
|
|
#include "rf-core/api/ieee_cmd.h"
|
|
#include "rf-core/api/data_entry.h"
|
|
#include "rf-core/api/ieee_mailbox.h"
|
|
/*---------------------------------------------------------------------------*/
|
|
#include "smartrf-settings.h"
|
|
/*---------------------------------------------------------------------------*/
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
/*---------------------------------------------------------------------------*/
|
|
#define DEBUG 0
|
|
#if DEBUG
|
|
#define PRINTF(...) printf(__VA_ARGS__)
|
|
#else
|
|
#define PRINTF(...)
|
|
#endif
|
|
|
|
/* Configuration to enable/disable auto ACKs in IEEE mode */
|
|
#ifdef IEEE_MODE_CONF_AUTOACK
|
|
#define IEEE_MODE_AUTOACK IEEE_MODE_CONF_AUTOACK
|
|
#else
|
|
#define IEEE_MODE_AUTOACK 1
|
|
#endif /* IEEE_MODE_CONF_AUTOACK */
|
|
|
|
/* Configuration to enable/disable frame filtering in IEEE mode */
|
|
#ifdef IEEE_MODE_CONF_PROMISCOUS
|
|
#define IEEE_MODE_PROMISCOUS IEEE_MODE_CONF_PROMISCOUS
|
|
#else
|
|
#define IEEE_MODE_PROMISCOUS 0
|
|
#endif /* IEEE_MODE_CONF_PROMISCOUS */
|
|
|
|
#ifdef IEEE_MODE_CONF_RSSI_THRESHOLD
|
|
#define IEEE_MODE_RSSI_THRESHOLD IEEE_MODE_CONF_RSSI_THRESHOLD
|
|
#else
|
|
#define IEEE_MODE_RSSI_THRESHOLD 0xA6
|
|
#endif /* IEEE_MODE_CONF_RSSI_THRESHOLD */
|
|
/*---------------------------------------------------------------------------*/
|
|
#define STATUS_CRC_OK 0x80
|
|
#define STATUS_CORRELATION 0x7f
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Data entry status field constants */
|
|
#define DATA_ENTRY_STATUS_PENDING 0x00 /* Not in use by the Radio CPU */
|
|
#define DATA_ENTRY_STATUS_ACTIVE 0x01 /* Open for r/w by the radio CPU */
|
|
#define DATA_ENTRY_STATUS_BUSY 0x02 /* Ongoing r/w */
|
|
#define DATA_ENTRY_STATUS_FINISHED 0x03 /* Free to use and to free */
|
|
#define DATA_ENTRY_STATUS_UNFINISHED 0x04 /* Partial RX entry */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* RF stats data structure */
|
|
static uint8_t rf_stats[16] = { 0 };
|
|
/*---------------------------------------------------------------------------*/
|
|
/* The size of the RF commands buffer */
|
|
#define RF_CMD_BUFFER_SIZE 128
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Returns the current status of a running Radio Op command
|
|
* \param a A pointer with the buffer used to initiate the command
|
|
* \return The value of the Radio Op buffer's status field
|
|
*
|
|
* This macro can be used to e.g. return the status of a previously
|
|
* initiated background operation, or of an immediate command
|
|
*/
|
|
#define RF_RADIO_OP_GET_STATUS(a) (((rfc_radioOp_t *)a)->status)
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Special value returned by CMD_IEEE_CCA_REQ when an RSSI is not available */
|
|
#define RF_CMD_CCA_REQ_RSSI_UNKNOWN -128
|
|
|
|
/* Used for the return value of channel_clear */
|
|
#define RF_CCA_CLEAR 1
|
|
#define RF_CCA_BUSY 0
|
|
|
|
/* Used as an error return value for get_cca_info */
|
|
#define RF_GET_CCA_INFO_ERROR 0xFF
|
|
|
|
/*
|
|
* Values of the individual bits of the ccaInfo field in CMD_IEEE_CCA_REQ's
|
|
* status struct
|
|
*/
|
|
#define RF_CMD_CCA_REQ_CCA_STATE_IDLE 0 /* 00 */
|
|
#define RF_CMD_CCA_REQ_CCA_STATE_BUSY 1 /* 01 */
|
|
#define RF_CMD_CCA_REQ_CCA_STATE_INVALID 2 /* 10 */
|
|
|
|
#define RF_CMD_CCA_REQ_CCA_CORR_IDLE (0 << 4)
|
|
#define RF_CMD_CCA_REQ_CCA_CORR_BUSY (1 << 4)
|
|
#define RF_CMD_CCA_REQ_CCA_CORR_INVALID (3 << 4)
|
|
#define RF_CMD_CCA_REQ_CCA_CORR_MASK (3 << 4)
|
|
|
|
#define RF_CMD_CCA_REQ_CCA_SYNC_BUSY (1 << 6)
|
|
/*---------------------------------------------------------------------------*/
|
|
#define IEEE_MODE_CHANNEL_MIN 11
|
|
#define IEEE_MODE_CHANNEL_MAX 26
|
|
/*---------------------------------------------------------------------------*/
|
|
/* How long to wait for an ongoing ACK TX to finish before starting frame TX */
|
|
#define TX_WAIT_TIMEOUT (RTIMER_SECOND >> 11)
|
|
|
|
/* How long to wait for the RF to enter RX in rf_cmd_ieee_rx */
|
|
#define ENTER_RX_WAIT_TIMEOUT (RTIMER_SECOND >> 10)
|
|
/*---------------------------------------------------------------------------*/
|
|
/* TX Power dBm lookup table - values from SmartRF Studio */
|
|
typedef struct output_config {
|
|
radio_value_t dbm;
|
|
uint8_t register_ib;
|
|
uint8_t register_gc;
|
|
uint8_t temp_coeff;
|
|
} output_config_t;
|
|
|
|
static const output_config_t output_power[] = {
|
|
{ 5, 0x30, 0x00, 0x93 },
|
|
{ 4, 0x24, 0x00, 0x93 },
|
|
{ 3, 0x1c, 0x00, 0x5a },
|
|
{ 2, 0x18, 0x00, 0x4e },
|
|
{ 1, 0x14, 0x00, 0x42 },
|
|
{ 0, 0x21, 0x01, 0x31 },
|
|
{ -3, 0x18, 0x01, 0x25 },
|
|
{ -6, 0x11, 0x01, 0x1d },
|
|
{ -9, 0x0e, 0x01, 0x19 },
|
|
{-12, 0x0b, 0x01, 0x14 },
|
|
{-15, 0x0b, 0x03, 0x0c },
|
|
{-18, 0x09, 0x03, 0x0c },
|
|
{-21, 0x07, 0x03, 0x0c },
|
|
};
|
|
|
|
#define OUTPUT_CONFIG_COUNT (sizeof(output_power) / sizeof(output_config_t))
|
|
|
|
/* Max and Min Output Power in dBm */
|
|
#define OUTPUT_POWER_MIN (output_power[OUTPUT_CONFIG_COUNT - 1].dbm)
|
|
#define OUTPUT_POWER_MAX (output_power[0].dbm)
|
|
#define OUTPUT_POWER_UNKNOWN 0xFFFF
|
|
|
|
/* Default TX Power - position in output_power[] */
|
|
const output_config_t *tx_power_current = &output_power[0];
|
|
/*---------------------------------------------------------------------------*/
|
|
static volatile int8_t last_rssi = 0;
|
|
static volatile uint8_t last_corr_lqi = 0;
|
|
|
|
extern int32_t rat_offset;
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* SFD timestamp in RTIMER ticks */
|
|
static volatile uint32_t last_packet_timestamp = 0;
|
|
/* SFD timestamp in RAT ticks (but 64 bits) */
|
|
static uint64_t last_rat_timestamp64 = 0;
|
|
|
|
/* For RAT overflow handling */
|
|
static struct ctimer rat_overflow_timer;
|
|
static uint32_t rat_overflow_counter = 0;
|
|
static rtimer_clock_t last_rat_overflow = 0;
|
|
|
|
/* RAT has 32-bit register, overflows once 18 minutes */
|
|
#define RAT_RANGE 4294967296ull
|
|
/* approximate value */
|
|
#define RAT_OVERFLOW_PERIOD_SECONDS (60 * 18)
|
|
|
|
/* XXX: don't know what exactly is this, looks like the time to Tx 3 octets */
|
|
#define TIMESTAMP_OFFSET -(USEC_TO_RADIO(32 * 3) - 1) /* -95.75 usec */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Are we currently in poll mode? */
|
|
static uint8_t poll_mode = 0;
|
|
|
|
static rfc_CMD_IEEE_MOD_FILT_t filter_cmd;
|
|
/*---------------------------------------------------------------------------*/
|
|
/*
|
|
* Buffers used to send commands to the RF core (generic and IEEE commands).
|
|
* Some of those buffers are re-usable, some are not.
|
|
*
|
|
* If you are uncertain, declare a new buffer.
|
|
*/
|
|
/*
|
|
* A buffer to send a CMD_IEEE_RX and to subsequently monitor its status
|
|
* Do not use this buffer for any commands other than CMD_IEEE_RX
|
|
*/
|
|
static uint8_t cmd_ieee_rx_buf[RF_CMD_BUFFER_SIZE] CC_ALIGN(4);
|
|
/*---------------------------------------------------------------------------*/
|
|
#define DATA_ENTRY_LENSZ_NONE 0
|
|
#define DATA_ENTRY_LENSZ_BYTE 1
|
|
#define DATA_ENTRY_LENSZ_WORD 2 /* 2 bytes */
|
|
|
|
#define RX_BUF_SIZE 144
|
|
/* Four receive buffers entries with room for 1 IEEE802.15.4 frame in each */
|
|
static uint8_t rx_buf_0[RX_BUF_SIZE] CC_ALIGN(4);
|
|
static uint8_t rx_buf_1[RX_BUF_SIZE] CC_ALIGN(4);
|
|
static uint8_t rx_buf_2[RX_BUF_SIZE] CC_ALIGN(4);
|
|
static uint8_t rx_buf_3[RX_BUF_SIZE] CC_ALIGN(4);
|
|
|
|
/* The RX Data Queue */
|
|
static dataQueue_t rx_data_queue = { 0 };
|
|
|
|
/* Receive entry pointer to keep track of read items */
|
|
volatile static uint8_t *rx_read_entry;
|
|
/*---------------------------------------------------------------------------*/
|
|
/* The outgoing frame buffer */
|
|
#define TX_BUF_PAYLOAD_LEN 180
|
|
#define TX_BUF_HDR_LEN 2
|
|
|
|
static uint8_t tx_buf[TX_BUF_HDR_LEN + TX_BUF_PAYLOAD_LEN] CC_ALIGN(4);
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Overrides for IEEE 802.15.4, differential mode */
|
|
static uint32_t ieee_overrides[] = {
|
|
0x00354038, /* Synth: Set RTRIM (POTAILRESTRIM) to 5 */
|
|
0x4001402D, /* Synth: Correct CKVD latency setting (address) */
|
|
0x00608402, /* Synth: Correct CKVD latency setting (value) */
|
|
// 0x4001405D, /* Synth: Set ANADIV DIV_BIAS_MODE to PG1 (address) */
|
|
// 0x1801F800, /* Synth: Set ANADIV DIV_BIAS_MODE to PG1 (value) */
|
|
0x000784A3, /* Synth: Set FREF = 3.43 MHz (24 MHz / 7) */
|
|
0xA47E0583, /* Synth: Set loop bandwidth after lock to 80 kHz (K2) */
|
|
0xEAE00603, /* Synth: Set loop bandwidth after lock to 80 kHz (K3, LSB) */
|
|
0x00010623, /* Synth: Set loop bandwidth after lock to 80 kHz (K3, MSB) */
|
|
0x002B50DC, /* Adjust AGC DC filter */
|
|
0x05000243, /* Increase synth programming timeout */
|
|
0x002082C3, /* Increase synth programming timeout */
|
|
0xFFFFFFFF, /* End of override list */
|
|
};
|
|
/*---------------------------------------------------------------------------*/
|
|
static int on(void);
|
|
static int off(void);
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Checks whether the RFC domain is accessible and the RFC is in IEEE RX
|
|
* \return 1: RFC in RX mode (and therefore accessible too). 0 otherwise
|
|
*/
|
|
static uint8_t
|
|
rf_is_on(void)
|
|
{
|
|
if(!rf_core_is_accessible()) {
|
|
return 0;
|
|
}
|
|
|
|
return RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf) == RF_CORE_RADIO_OP_STATUS_ACTIVE;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Check the RF's TX status
|
|
* \return 1 RF is transmitting
|
|
* \return 0 RF is not transmitting
|
|
*
|
|
* TX mode may be triggered either by a CMD_IEEE_TX or by the automatic
|
|
* transmission of an ACK frame.
|
|
*/
|
|
static uint8_t
|
|
transmitting(void)
|
|
{
|
|
uint32_t cmd_status;
|
|
rfc_CMD_IEEE_CCA_REQ_t cmd;
|
|
|
|
/* If we are off, we are not in TX */
|
|
if(!rf_core_is_accessible()) {
|
|
return 0;
|
|
}
|
|
|
|
memset(&cmd, 0x00, sizeof(cmd));
|
|
|
|
cmd.commandNo = CMD_IEEE_CCA_REQ;
|
|
|
|
if(rf_core_send_cmd((uint32_t)&cmd, &cmd_status) == RF_CORE_CMD_ERROR) {
|
|
PRINTF("transmitting: CMDSTA=0x%08lx\n", cmd_status);
|
|
return 0;
|
|
}
|
|
|
|
if((cmd.currentRssi == RF_CMD_CCA_REQ_RSSI_UNKNOWN) &&
|
|
(cmd.ccaInfo.ccaEnergy == RF_CMD_CCA_REQ_CCA_STATE_BUSY)) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Returns CCA information
|
|
* \return RF_GET_CCA_INFO_ERROR if the RF was not on
|
|
* \return On success, the return value is formatted as per the ccaInfo field
|
|
* of CMD_IEEE_CCA_REQ
|
|
*
|
|
* It is the caller's responsibility to make sure the RF is on. This function
|
|
* will return RF_GET_CCA_INFO_ERROR if the RF is off
|
|
*
|
|
* This function will in fact wait for a valid RSSI signal
|
|
*/
|
|
static uint8_t
|
|
get_cca_info(void)
|
|
{
|
|
uint32_t cmd_status;
|
|
int8_t rssi;
|
|
rfc_CMD_IEEE_CCA_REQ_t cmd;
|
|
|
|
if(!rf_is_on()) {
|
|
PRINTF("get_cca_info: Not on\n");
|
|
return RF_GET_CCA_INFO_ERROR;
|
|
}
|
|
|
|
rssi = RF_CMD_CCA_REQ_RSSI_UNKNOWN;
|
|
|
|
while(rssi == RF_CMD_CCA_REQ_RSSI_UNKNOWN || rssi == 0) {
|
|
memset(&cmd, 0x00, sizeof(cmd));
|
|
cmd.commandNo = CMD_IEEE_CCA_REQ;
|
|
|
|
if(rf_core_send_cmd((uint32_t)&cmd, &cmd_status) == RF_CORE_CMD_ERROR) {
|
|
PRINTF("get_cca_info: CMDSTA=0x%08lx\n", cmd_status);
|
|
|
|
return RF_GET_CCA_INFO_ERROR;
|
|
}
|
|
|
|
rssi = cmd.currentRssi;
|
|
}
|
|
|
|
/* We have a valid RSSI signal. Return the CCA Info */
|
|
return *((uint8_t *)&cmd.ccaInfo);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Reads the current signal strength (RSSI)
|
|
* \return The current RSSI in dBm or CMD_GET_RSSI_UNKNOWN
|
|
*
|
|
* This function reads the current RSSI on the currently configured
|
|
* channel.
|
|
*/
|
|
static radio_value_t
|
|
get_rssi(void)
|
|
{
|
|
uint32_t cmd_status;
|
|
int8_t rssi;
|
|
uint8_t was_off = 0;
|
|
rfc_CMD_GET_RSSI_t cmd;
|
|
|
|
/* If we are off, turn on first */
|
|
if(!rf_is_on()) {
|
|
was_off = 1;
|
|
if(on() != RF_CORE_CMD_OK) {
|
|
PRINTF("get_rssi: on() failed\n");
|
|
return RF_CMD_CCA_REQ_RSSI_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
memset(&cmd, 0x00, sizeof(cmd));
|
|
cmd.commandNo = CMD_GET_RSSI;
|
|
|
|
rssi = RF_CMD_CCA_REQ_RSSI_UNKNOWN;
|
|
|
|
if(rf_core_send_cmd((uint32_t)&cmd, &cmd_status) == RF_CORE_CMD_OK) {
|
|
/* Current RSSI in bits 23:16 of cmd_status */
|
|
rssi = (cmd_status >> 16) & 0xFF;
|
|
}
|
|
|
|
/* If we were off, turn back off */
|
|
if(was_off) {
|
|
off();
|
|
}
|
|
|
|
return rssi;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Returns the current TX power in dBm */
|
|
static radio_value_t
|
|
get_tx_power(void)
|
|
{
|
|
return tx_power_current->dbm;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/*
|
|
* Set TX power to 'at least' power dBm
|
|
* This works with a lookup table. If the value of 'power' does not exist in
|
|
* the lookup table, TXPOWER will be set to the immediately higher available
|
|
* value
|
|
*/
|
|
static void
|
|
set_tx_power(radio_value_t power)
|
|
{
|
|
uint32_t cmd_status;
|
|
int i;
|
|
rfc_CMD_SET_TX_POWER_t cmd;
|
|
|
|
/* First, find the correct setting and save it */
|
|
for(i = OUTPUT_CONFIG_COUNT - 1; i >= 0; --i) {
|
|
if(power <= output_power[i].dbm) {
|
|
tx_power_current = &output_power[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the core is not accessible, the new setting will be applied next
|
|
* time we send CMD_RADIO_SETUP, so we don't need to do anything further.
|
|
* If the core is accessible, we can apply the new setting immediately with
|
|
* CMD_SET_TX_POWER
|
|
*/
|
|
if(rf_core_is_accessible() == RF_CORE_NOT_ACCESSIBLE) {
|
|
return;
|
|
}
|
|
|
|
memset(&cmd, 0x00, sizeof(cmd));
|
|
cmd.commandNo = CMD_SET_TX_POWER;
|
|
cmd.txPower.IB = output_power[i].register_ib;
|
|
cmd.txPower.GC = output_power[i].register_gc;
|
|
cmd.txPower.tempCoeff = output_power[i].temp_coeff;
|
|
|
|
if(rf_core_send_cmd((uint32_t)&cmd, &cmd_status) == RF_CORE_CMD_ERROR) {
|
|
PRINTF("set_tx_power: CMDSTA=0x%08lx\n", cmd_status);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static uint8_t
|
|
rf_radio_setup()
|
|
{
|
|
uint32_t cmd_status;
|
|
rfc_CMD_RADIO_SETUP_t cmd;
|
|
|
|
/* Create radio setup command */
|
|
rf_core_init_radio_op((rfc_radioOp_t *)&cmd, sizeof(cmd), CMD_RADIO_SETUP);
|
|
|
|
cmd.txPower.IB = tx_power_current->register_ib;
|
|
cmd.txPower.GC = tx_power_current->register_gc;
|
|
cmd.txPower.tempCoeff = tx_power_current->temp_coeff;
|
|
cmd.pRegOverride = ieee_overrides;
|
|
cmd.mode = 1;
|
|
|
|
/* Send Radio setup to RF Core */
|
|
if(rf_core_send_cmd((uint32_t)&cmd, &cmd_status) != RF_CORE_CMD_OK) {
|
|
PRINTF("rf_radio_setup: CMD_RADIO_SETUP, CMDSTA=0x%08lx, status=0x%04x\n",
|
|
cmd_status, cmd.status);
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
/* Wait until radio setup is done */
|
|
if(rf_core_wait_cmd_done(&cmd) != RF_CORE_CMD_OK) {
|
|
PRINTF("rf_radio_setup: CMD_RADIO_SETUP wait, CMDSTA=0x%08lx, status=0x%04x\n",
|
|
cmd_status, cmd.status);
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* \brief Set up radio in IEEE802.15.4 RX mode
|
|
*
|
|
* \return RF_CORE_CMD_OK Succeeded
|
|
* \return RF_CORE_CMD_ERROR Failed
|
|
*
|
|
* This function assumes that cmd_ieee_rx_buf has been previously populated
|
|
* with correct values. This can be done through init_rf_params (sets defaults)
|
|
* or through Contiki's extended RF API (set_value, set_object)
|
|
*/
|
|
static uint8_t
|
|
rf_cmd_ieee_rx()
|
|
{
|
|
uint32_t cmd_status;
|
|
rtimer_clock_t t0;
|
|
int ret;
|
|
|
|
ret = rf_core_send_cmd((uint32_t)cmd_ieee_rx_buf, &cmd_status);
|
|
|
|
if(ret != RF_CORE_CMD_OK) {
|
|
PRINTF("rf_cmd_ieee_rx: ret=%d, CMDSTA=0x%08lx, status=0x%04x\n",
|
|
ret, cmd_status, RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf));
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
t0 = RTIMER_NOW();
|
|
|
|
while(RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf) != RF_CORE_RADIO_OP_STATUS_ACTIVE &&
|
|
(RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + ENTER_RX_WAIT_TIMEOUT)));
|
|
|
|
/* Wait to enter RX */
|
|
if(RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf) != RF_CORE_RADIO_OP_STATUS_ACTIVE) {
|
|
PRINTF("rf_cmd_ieee_rx: CMDSTA=0x%08lx, status=0x%04x\n",
|
|
cmd_status, RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf));
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
init_rx_buffers(void)
|
|
{
|
|
rfc_dataEntry_t *entry;
|
|
|
|
entry = (rfc_dataEntry_t *)rx_buf_0;
|
|
entry->pNextEntry = rx_buf_1;
|
|
entry->config.lenSz = DATA_ENTRY_LENSZ_BYTE;
|
|
entry->length = sizeof(rx_buf_0) - 8;
|
|
|
|
entry = (rfc_dataEntry_t *)rx_buf_1;
|
|
entry->pNextEntry = rx_buf_2;
|
|
entry->config.lenSz = DATA_ENTRY_LENSZ_BYTE;
|
|
entry->length = sizeof(rx_buf_0) - 8;
|
|
|
|
entry = (rfc_dataEntry_t *)rx_buf_2;
|
|
entry->pNextEntry = rx_buf_3;
|
|
entry->config.lenSz = DATA_ENTRY_LENSZ_BYTE;
|
|
entry->length = sizeof(rx_buf_0) - 8;
|
|
|
|
entry = (rfc_dataEntry_t *)rx_buf_3;
|
|
entry->pNextEntry = rx_buf_0;
|
|
entry->config.lenSz = DATA_ENTRY_LENSZ_BYTE;
|
|
entry->length = sizeof(rx_buf_0) - 8;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
init_rf_params(void)
|
|
{
|
|
rfc_CMD_IEEE_RX_t *cmd = (rfc_CMD_IEEE_RX_t *)cmd_ieee_rx_buf;
|
|
|
|
memset(cmd_ieee_rx_buf, 0x00, RF_CMD_BUFFER_SIZE);
|
|
|
|
cmd->commandNo = CMD_IEEE_RX;
|
|
cmd->status = RF_CORE_RADIO_OP_STATUS_IDLE;
|
|
cmd->pNextOp = NULL;
|
|
cmd->startTime = 0x00000000;
|
|
cmd->startTrigger.triggerType = TRIG_NOW;
|
|
cmd->condition.rule = COND_NEVER;
|
|
cmd->channel = RF_CORE_CHANNEL;
|
|
|
|
cmd->rxConfig.bAutoFlushCrc = 1;
|
|
cmd->rxConfig.bAutoFlushIgn = 0;
|
|
cmd->rxConfig.bIncludePhyHdr = 0;
|
|
cmd->rxConfig.bIncludeCrc = 1;
|
|
cmd->rxConfig.bAppendRssi = 1;
|
|
cmd->rxConfig.bAppendCorrCrc = 1;
|
|
cmd->rxConfig.bAppendSrcInd = 0;
|
|
cmd->rxConfig.bAppendTimestamp = 1;
|
|
|
|
cmd->pRxQ = &rx_data_queue;
|
|
cmd->pOutput = (rfc_ieeeRxOutput_t *)rf_stats;
|
|
|
|
#if IEEE_MODE_PROMISCOUS
|
|
cmd->frameFiltOpt.frameFiltEn = 0;
|
|
#else
|
|
cmd->frameFiltOpt.frameFiltEn = 1;
|
|
#endif
|
|
|
|
cmd->frameFiltOpt.frameFiltStop = 1;
|
|
|
|
#if IEEE_MODE_AUTOACK
|
|
cmd->frameFiltOpt.autoAckEn = 1;
|
|
#else
|
|
cmd->frameFiltOpt.autoAckEn = 0;
|
|
#endif
|
|
|
|
cmd->frameFiltOpt.slottedAckEn = 0;
|
|
cmd->frameFiltOpt.autoPendEn = 0;
|
|
cmd->frameFiltOpt.defaultPend = 0;
|
|
cmd->frameFiltOpt.bPendDataReqOnly = 0;
|
|
cmd->frameFiltOpt.bPanCoord = 0;
|
|
cmd->frameFiltOpt.maxFrameVersion = 2;
|
|
cmd->frameFiltOpt.bStrictLenFilter = 0;
|
|
|
|
/* Receive all frame types */
|
|
cmd->frameTypes.bAcceptFt0Beacon = 1;
|
|
cmd->frameTypes.bAcceptFt1Data = 1;
|
|
cmd->frameTypes.bAcceptFt2Ack = 1;
|
|
cmd->frameTypes.bAcceptFt3MacCmd = 1;
|
|
cmd->frameTypes.bAcceptFt4Reserved = 1;
|
|
cmd->frameTypes.bAcceptFt5Reserved = 1;
|
|
cmd->frameTypes.bAcceptFt6Reserved = 1;
|
|
cmd->frameTypes.bAcceptFt7Reserved = 1;
|
|
|
|
/* Configure CCA settings */
|
|
cmd->ccaOpt.ccaEnEnergy = 1;
|
|
cmd->ccaOpt.ccaEnCorr = 1;
|
|
cmd->ccaOpt.ccaEnSync = 1;
|
|
cmd->ccaOpt.ccaCorrOp = 1;
|
|
cmd->ccaOpt.ccaSyncOp = 0;
|
|
cmd->ccaOpt.ccaCorrThr = 3;
|
|
|
|
cmd->ccaRssiThr = IEEE_MODE_RSSI_THRESHOLD;
|
|
|
|
cmd->numExtEntries = 0x00;
|
|
cmd->numShortEntries = 0x00;
|
|
cmd->pExtEntryList = 0;
|
|
cmd->pShortEntryList = 0;
|
|
|
|
cmd->endTrigger.triggerType = TRIG_NEVER;
|
|
cmd->endTime = 0x00000000;
|
|
|
|
/* set address filter command */
|
|
filter_cmd.commandNo = CMD_IEEE_MOD_FILT;
|
|
memcpy(&filter_cmd.newFrameFiltOpt, &cmd->frameFiltOpt, sizeof(cmd->frameFiltOpt));
|
|
memcpy(&filter_cmd.newFrameTypes, &cmd->frameTypes, sizeof(cmd->frameTypes));
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
rx_on(void)
|
|
{
|
|
int ret;
|
|
|
|
/* Get status of running IEEE_RX (if any) */
|
|
if(rf_is_on()) {
|
|
PRINTF("rx_on: We were on. PD=%u, RX=0x%04x \n", rf_core_is_accessible(),
|
|
RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf));
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
|
|
/* Put CPE in RX using the currently configured parameters */
|
|
ret = rf_cmd_ieee_rx();
|
|
|
|
if(ret) {
|
|
ENERGEST_ON(ENERGEST_TYPE_LISTEN);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
rx_off(void)
|
|
{
|
|
uint32_t cmd_status;
|
|
int ret;
|
|
|
|
/* If we are off, do nothing */
|
|
if(!rf_is_on()) {
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
|
|
/* Wait for ongoing ACK TX to finish */
|
|
while(transmitting());
|
|
|
|
/* Send a CMD_ABORT command to RF Core */
|
|
if(rf_core_send_cmd(CMDR_DIR_CMD(CMD_ABORT), &cmd_status) != RF_CORE_CMD_OK) {
|
|
PRINTF("RX off: CMD_ABORT status=0x%08lx\n", cmd_status);
|
|
/* Continue nonetheless */
|
|
}
|
|
|
|
while(rf_is_on());
|
|
|
|
if(RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf) == IEEE_DONE_STOPPED ||
|
|
RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf) == IEEE_DONE_ABORT) {
|
|
/* Stopped gracefully */
|
|
ret = RF_CORE_CMD_OK;
|
|
} else {
|
|
PRINTF("RX off: BG status=0x%04x\n", RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf));
|
|
ret = RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
|
|
|
|
return ret;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static uint8_t
|
|
request(void)
|
|
{
|
|
/*
|
|
* We rely on the RDC layer to turn us on and off. Thus, if we are on we
|
|
* will only allow sleep, standby otherwise
|
|
*/
|
|
if(rf_is_on()) {
|
|
return LPM_MODE_SLEEP;
|
|
}
|
|
|
|
return LPM_MODE_MAX_SUPPORTED;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
LPM_MODULE(cc26xx_rf_lpm_module, request, NULL, NULL, LPM_DOMAIN_NONE);
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
soft_off(void)
|
|
{
|
|
uint32_t cmd_status;
|
|
volatile rfc_radioOp_t *cmd = rf_core_get_last_radio_op();
|
|
|
|
if(!rf_core_is_accessible()) {
|
|
return;
|
|
}
|
|
|
|
PRINTF("soft_off: Aborting 0x%04x, Status=0x%04x\n", cmd->commandNo,
|
|
cmd->status);
|
|
|
|
/* Send a CMD_ABORT command to RF Core */
|
|
if(rf_core_send_cmd(CMDR_DIR_CMD(CMD_ABORT), &cmd_status) != RF_CORE_CMD_OK) {
|
|
PRINTF("soft_off: CMD_ABORT status=0x%08lx\n", cmd_status);
|
|
return;
|
|
}
|
|
|
|
while((cmd->status & RF_CORE_RADIO_OP_MASKED_STATUS) ==
|
|
RF_CORE_RADIO_OP_MASKED_STATUS_RUNNING);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static uint8_t
|
|
soft_on(void)
|
|
{
|
|
if(rf_radio_setup() != RF_CORE_CMD_OK) {
|
|
PRINTF("on: radio_setup() failed\n");
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
return rx_on();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static const rf_core_primary_mode_t mode_ieee = {
|
|
soft_off,
|
|
soft_on,
|
|
};
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
check_rat_overflow(bool first_time)
|
|
{
|
|
static uint32_t last_value;
|
|
uint32_t current_value;
|
|
uint8_t interrupts_enabled;
|
|
|
|
interrupts_enabled = ti_lib_int_master_disable();
|
|
if(first_time) {
|
|
last_value = HWREG(RFC_RAT_BASE + RATCNT);
|
|
} else {
|
|
current_value = HWREG(RFC_RAT_BASE + RATCNT);
|
|
if(current_value + RAT_RANGE / 4 < last_value) {
|
|
/* overflow detected */
|
|
last_rat_overflow = RTIMER_NOW();
|
|
rat_overflow_counter++;
|
|
}
|
|
last_value = current_value;
|
|
}
|
|
if(interrupts_enabled) {
|
|
ti_lib_int_master_enable();
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
handle_rat_overflow(void *unused)
|
|
{
|
|
uint8_t was_off = 0;
|
|
|
|
if(!rf_is_on()) {
|
|
was_off = 1;
|
|
if(on() != RF_CORE_CMD_OK) {
|
|
PRINTF("overflow: on() failed\n");
|
|
ctimer_set(&rat_overflow_timer, RAT_OVERFLOW_PERIOD_SECONDS * CLOCK_SECOND / 2,
|
|
handle_rat_overflow, NULL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
check_rat_overflow(false);
|
|
|
|
if(was_off) {
|
|
off();
|
|
}
|
|
|
|
ctimer_set(&rat_overflow_timer, RAT_OVERFLOW_PERIOD_SECONDS * CLOCK_SECOND / 2,
|
|
handle_rat_overflow, NULL);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
init(void)
|
|
{
|
|
lpm_register_module(&cc26xx_rf_lpm_module);
|
|
|
|
rf_core_set_modesel();
|
|
|
|
/* Initialise RX buffers */
|
|
memset(rx_buf_0, 0, RX_BUF_SIZE);
|
|
memset(rx_buf_1, 0, RX_BUF_SIZE);
|
|
memset(rx_buf_2, 0, RX_BUF_SIZE);
|
|
memset(rx_buf_3, 0, RX_BUF_SIZE);
|
|
|
|
/* Set of RF Core data queue. Circular buffer, no last entry */
|
|
rx_data_queue.pCurrEntry = rx_buf_0;
|
|
|
|
rx_data_queue.pLastEntry = NULL;
|
|
|
|
/* Initialize current read pointer to first element (used in ISR) */
|
|
rx_read_entry = rx_buf_0;
|
|
|
|
/* Populate the RF parameters data structure with default values */
|
|
init_rf_params();
|
|
|
|
if(on() != RF_CORE_CMD_OK) {
|
|
PRINTF("init: on() failed\n");
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
ENERGEST_ON(ENERGEST_TYPE_LISTEN);
|
|
|
|
rf_core_primary_mode_register(&mode_ieee);
|
|
|
|
check_rat_overflow(true);
|
|
ctimer_set(&rat_overflow_timer, RAT_OVERFLOW_PERIOD_SECONDS * CLOCK_SECOND / 2,
|
|
handle_rat_overflow, NULL);
|
|
|
|
process_start(&rf_core_process, NULL);
|
|
return 1;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
prepare(const void *payload, unsigned short payload_len)
|
|
{
|
|
int len = MIN(payload_len, TX_BUF_PAYLOAD_LEN);
|
|
|
|
memcpy(&tx_buf[TX_BUF_HDR_LEN], payload, len);
|
|
return 0;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
transmit(unsigned short transmit_len)
|
|
{
|
|
int ret;
|
|
uint8_t was_off = 0;
|
|
uint32_t cmd_status;
|
|
uint16_t stat;
|
|
uint8_t tx_active = 0;
|
|
rtimer_clock_t t0;
|
|
volatile rfc_CMD_IEEE_TX_t cmd;
|
|
uint8_t interrupts_enabled;
|
|
|
|
if(!rf_is_on()) {
|
|
was_off = 1;
|
|
if(on() != RF_CORE_CMD_OK) {
|
|
PRINTF("transmit: on() failed\n");
|
|
return RADIO_TX_ERR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We are certainly not TXing a frame as a result of CMD_IEEE_TX, but we may
|
|
* be in the process of TXing an ACK. In that case, wait for the TX to finish
|
|
* or return after approx TX_WAIT_TIMEOUT
|
|
*/
|
|
t0 = RTIMER_NOW();
|
|
|
|
do {
|
|
tx_active = transmitting();
|
|
} while(tx_active == 1 &&
|
|
(RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + TX_WAIT_TIMEOUT)));
|
|
|
|
if(tx_active) {
|
|
PRINTF("transmit: Already TXing and wait timed out\n");
|
|
|
|
if(was_off) {
|
|
off();
|
|
}
|
|
|
|
return RADIO_TX_COLLISION;
|
|
}
|
|
|
|
/* Send the CMD_IEEE_TX command */
|
|
rf_core_init_radio_op((rfc_radioOp_t *)&cmd, sizeof(cmd), CMD_IEEE_TX);
|
|
|
|
cmd.payloadLen = transmit_len;
|
|
cmd.pPayload = &tx_buf[TX_BUF_HDR_LEN];
|
|
|
|
cmd.startTime = 0;
|
|
cmd.startTrigger.triggerType = TRIG_NOW;
|
|
|
|
/* XXX: there seems to be no function that gets interrupt state w/o changing it */
|
|
interrupts_enabled = ti_lib_int_master_disable();
|
|
if(interrupts_enabled) {
|
|
ti_lib_int_master_enable();
|
|
}
|
|
|
|
/* Enable the LAST_FG_COMMAND_DONE interrupt, which will wake us up */
|
|
if(interrupts_enabled) {
|
|
rf_core_cmd_done_en(true, poll_mode);
|
|
}
|
|
|
|
ret = rf_core_send_cmd((uint32_t)&cmd, &cmd_status);
|
|
|
|
if(ret) {
|
|
/* If we enter here, TX actually started */
|
|
ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
|
|
ENERGEST_ON(ENERGEST_TYPE_TRANSMIT);
|
|
|
|
/* Idle away while the command is running */
|
|
while((cmd.status & RF_CORE_RADIO_OP_MASKED_STATUS)
|
|
== RF_CORE_RADIO_OP_MASKED_STATUS_RUNNING) {
|
|
/* Note: for now sleeping while Tx'ing in polling mode is disabled.
|
|
* To enable it:
|
|
* 1) make the `lpm_sleep()` call here unconditional;
|
|
* 2) change the radio ISR priority to allow radio ISR to interrupt rtimer ISR.
|
|
*/
|
|
if(interrupts_enabled) {
|
|
lpm_sleep();
|
|
}
|
|
}
|
|
|
|
stat = cmd.status;
|
|
|
|
if(stat == RF_CORE_RADIO_OP_STATUS_IEEE_DONE_OK) {
|
|
/* Sent OK */
|
|
RIMESTATS_ADD(lltx);
|
|
ret = RADIO_TX_OK;
|
|
} else {
|
|
/* Operation completed, but frame was not sent */
|
|
PRINTF("transmit: ret=%d, CMDSTA=0x%08lx, status=0x%04x\n", ret,
|
|
cmd_status, stat);
|
|
ret = RADIO_TX_ERR;
|
|
}
|
|
} else {
|
|
/* Failure sending the CMD_IEEE_TX command */
|
|
PRINTF("transmit: ret=%d, CMDSTA=0x%08lx, status=0x%04x\n",
|
|
ret, cmd_status, cmd.status);
|
|
|
|
ret = RADIO_TX_ERR;
|
|
}
|
|
|
|
/*
|
|
* Update ENERGEST state here, before a potential call to off(), which
|
|
* will correctly update it if required.
|
|
*/
|
|
ENERGEST_OFF(ENERGEST_TYPE_TRANSMIT);
|
|
ENERGEST_ON(ENERGEST_TYPE_LISTEN);
|
|
|
|
if(interrupts_enabled) {
|
|
/*
|
|
* Disable LAST_FG_COMMAND_DONE interrupt. We don't really care about it
|
|
* except when we are transmitting
|
|
*/
|
|
rf_core_cmd_done_dis(poll_mode);
|
|
}
|
|
|
|
if(was_off) {
|
|
off();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
send(const void *payload, unsigned short payload_len)
|
|
{
|
|
prepare(payload, payload_len);
|
|
return transmit(payload_len);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
release_data_entry(void)
|
|
{
|
|
rfc_dataEntryGeneral_t *entry = (rfc_dataEntryGeneral_t *)rx_read_entry;
|
|
|
|
/* Clear the length byte */
|
|
rx_read_entry[8] = 0;
|
|
|
|
/* Set status to 0 "Pending" in element */
|
|
entry->status = DATA_ENTRY_STATUS_PENDING;
|
|
rx_read_entry = entry->pNextEntry;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static uint32_t
|
|
calc_last_packet_timestamp(uint32_t rat_timestamp)
|
|
{
|
|
uint64_t rat_timestamp64;
|
|
uint32_t adjusted_overflow_counter = rat_overflow_counter;
|
|
|
|
/* if the timestamp is large and the last oveflow was recently,
|
|
assume that the timestamp refers to the time before the overflow */
|
|
if(rat_timestamp > (uint32_t)(RAT_RANGE * 3 / 4)) {
|
|
if(RTIMER_CLOCK_LT(RTIMER_NOW(),
|
|
last_rat_overflow + RAT_OVERFLOW_PERIOD_SECONDS * RTIMER_SECOND / 4)) {
|
|
adjusted_overflow_counter--;
|
|
}
|
|
}
|
|
|
|
/* add the overflowed time to the timestamp */
|
|
rat_timestamp64 = rat_timestamp + RAT_RANGE * adjusted_overflow_counter;
|
|
/* correct timestamp so that it refers to the end of the SFD */
|
|
rat_timestamp64 += TIMESTAMP_OFFSET;
|
|
|
|
last_rat_timestamp64 = rat_timestamp64 - rat_offset;
|
|
|
|
return RADIO_TO_RTIMER(rat_timestamp64 - rat_offset);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
read_frame(void *buf, unsigned short buf_len)
|
|
{
|
|
int len = 0;
|
|
rfc_dataEntryGeneral_t *entry = (rfc_dataEntryGeneral_t *)rx_read_entry;
|
|
uint32_t rat_timestamp;
|
|
|
|
if(rf_is_on()) {
|
|
check_rat_overflow(false);
|
|
}
|
|
|
|
/* wait for entry to become finished */
|
|
rtimer_clock_t t0 = RTIMER_NOW();
|
|
while(entry->status == DATA_ENTRY_STATUS_BUSY
|
|
&& RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + (RTIMER_SECOND / 250)));
|
|
|
|
if(entry->status != DATA_ENTRY_STATUS_FINISHED) {
|
|
/* No available data */
|
|
return 0;
|
|
}
|
|
|
|
if(rx_read_entry[8] < 4) {
|
|
PRINTF("RF: too short\n");
|
|
RIMESTATS_ADD(tooshort);
|
|
|
|
release_data_entry();
|
|
return 0;
|
|
}
|
|
|
|
len = rx_read_entry[8] - 8;
|
|
|
|
if(len > buf_len) {
|
|
PRINTF("RF: too long\n");
|
|
RIMESTATS_ADD(toolong);
|
|
|
|
release_data_entry();
|
|
return 0;
|
|
}
|
|
|
|
memcpy(buf, (char *)&rx_read_entry[9], len);
|
|
|
|
last_rssi = (int8_t)rx_read_entry[9 + len + 2];
|
|
last_corr_lqi = (uint8_t)rx_read_entry[9 + len + 2] & STATUS_CORRELATION;
|
|
|
|
/* get the timestamp */
|
|
memcpy(&rat_timestamp, (char *)rx_read_entry + 9 + len + 4, 4);
|
|
|
|
last_packet_timestamp = calc_last_packet_timestamp(rat_timestamp);
|
|
|
|
if(!poll_mode) {
|
|
/* Not in poll mode: packetbuf should not be accessed in interrupt context.
|
|
* In poll mode, the last packet RSSI and link quality can be obtained through
|
|
* RADIO_PARAM_LAST_RSSI and RADIO_PARAM_LAST_LINK_QUALITY */
|
|
packetbuf_set_attr(PACKETBUF_ATTR_RSSI, last_rssi);
|
|
packetbuf_set_attr(PACKETBUF_ATTR_LINK_QUALITY, last_corr_lqi);
|
|
}
|
|
RIMESTATS_ADD(llrx);
|
|
|
|
release_data_entry();
|
|
|
|
return len;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
channel_clear(void)
|
|
{
|
|
uint8_t was_off = 0;
|
|
uint8_t cca_info;
|
|
int ret = RF_CCA_CLEAR;
|
|
|
|
/*
|
|
* If we are in the middle of a BLE operation, we got called by ContikiMAC
|
|
* from within an interrupt context. Indicate a clear channel
|
|
*/
|
|
if(rf_ble_is_active() == RF_BLE_ACTIVE) {
|
|
PRINTF("channel_clear: Interrupt context but BLE in progress\n");
|
|
return RF_CCA_CLEAR;
|
|
}
|
|
|
|
if(rf_is_on()) {
|
|
/*
|
|
* Wait for potential leftover ACK still being sent.
|
|
* Strictly speaking, if we are TXing an ACK then the channel is not clear.
|
|
* However, channel_clear is only ever called to determine whether there is
|
|
* someone else's packet in the air, not ours.
|
|
*
|
|
* We could probably even simply return that the channel is clear
|
|
*/
|
|
while(transmitting());
|
|
} else {
|
|
was_off = 1;
|
|
if(on() != RF_CORE_CMD_OK) {
|
|
PRINTF("channel_clear: on() failed\n");
|
|
if(was_off) {
|
|
off();
|
|
}
|
|
return RF_CCA_CLEAR;
|
|
}
|
|
}
|
|
|
|
cca_info = get_cca_info();
|
|
|
|
if(cca_info == RF_GET_CCA_INFO_ERROR) {
|
|
PRINTF("channel_clear: CCA error\n");
|
|
ret = RF_CCA_CLEAR;
|
|
} else {
|
|
/*
|
|
* cca_info bits 1:0 - ccaStatus
|
|
* Return 1 (clear) if idle or invalid.
|
|
*/
|
|
ret = (cca_info & 0x03) != RF_CMD_CCA_REQ_CCA_STATE_BUSY;
|
|
}
|
|
|
|
if(was_off) {
|
|
off();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
receiving_packet(void)
|
|
{
|
|
uint8_t cca_info;
|
|
|
|
/*
|
|
* If we are in the middle of a BLE operation, we got called by ContikiMAC
|
|
* from within an interrupt context. We are not receiving
|
|
*/
|
|
if(rf_ble_is_active() == RF_BLE_ACTIVE) {
|
|
PRINTF("receiving_packet: Interrupt context but BLE in progress\n");
|
|
return 0;
|
|
}
|
|
|
|
/* If we are off, we are not receiving */
|
|
if(!rf_is_on()) {
|
|
PRINTF("receiving_packet: We were off\n");
|
|
return 0;
|
|
}
|
|
|
|
/* If we are transmitting (can only be an ACK here), we are not receiving */
|
|
if(transmitting()) {
|
|
PRINTF("receiving_packet: We were TXing\n");
|
|
return 0;
|
|
}
|
|
|
|
cca_info = get_cca_info();
|
|
|
|
/* If we can't read CCA info, return "not receiving" */
|
|
if(cca_info == RF_GET_CCA_INFO_ERROR) {
|
|
return 0;
|
|
}
|
|
|
|
/* If sync has been seen, return 1 (receiving) */
|
|
if(cca_info & RF_CMD_CCA_REQ_CCA_SYNC_BUSY) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
pending_packet(void)
|
|
{
|
|
volatile rfc_dataEntry_t *entry = (rfc_dataEntry_t *)rx_data_queue.pCurrEntry;
|
|
int rv = 0;
|
|
|
|
/* Go through all RX buffers and check their status */
|
|
do {
|
|
if(entry->status == DATA_ENTRY_STATUS_FINISHED
|
|
|| entry->status == DATA_ENTRY_STATUS_BUSY) {
|
|
rv = 1;
|
|
if(!poll_mode) {
|
|
process_poll(&rf_core_process);
|
|
}
|
|
}
|
|
|
|
entry = (rfc_dataEntry_t *)entry->pNextEntry;
|
|
} while(entry != (rfc_dataEntry_t *)rx_data_queue.pCurrEntry);
|
|
|
|
/* If we didn't find an entry at status finished, no frames are pending */
|
|
return rv;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
on(void)
|
|
{
|
|
/*
|
|
* If we are in the middle of a BLE operation, we got called by ContikiMAC
|
|
* from within an interrupt context. Abort, but pretend everything is OK.
|
|
*/
|
|
if(rf_ble_is_active() == RF_BLE_ACTIVE) {
|
|
PRINTF("on: Interrupt context but BLE in progress\n");
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
|
|
#if !CC2650_FAST_RADIO_STARTUP
|
|
/*
|
|
* Request the HF XOSC as the source for the HF clock. Needed before we can
|
|
* use the FS. This will only request, it will _not_ perform the switch.
|
|
*/
|
|
oscillators_request_hf_xosc();
|
|
#endif
|
|
|
|
if(rf_is_on()) {
|
|
PRINTF("on: We were on. PD=%u, RX=0x%04x \n", rf_core_is_accessible(),
|
|
RF_RADIO_OP_GET_STATUS(cmd_ieee_rx_buf));
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
|
|
init_rx_buffers();
|
|
|
|
/*
|
|
* Trigger a switch to the XOSC, so that we can subsequently use the RF FS
|
|
* This will block until the XOSC is actually ready, but give how we
|
|
* requested it early on, this won't be too long a wait.
|
|
* This should be done before starting the RAT.
|
|
*/
|
|
oscillators_switch_to_hf_xosc();
|
|
|
|
if(rf_core_boot() != RF_CORE_CMD_OK) {
|
|
PRINTF("on: rf_core_boot() failed\n");
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
rf_core_setup_interrupts(poll_mode);
|
|
|
|
if(rf_radio_setup() != RF_CORE_CMD_OK) {
|
|
PRINTF("on: radio_setup() failed\n");
|
|
return RF_CORE_CMD_ERROR;
|
|
}
|
|
|
|
return rx_on();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static int
|
|
off(void)
|
|
{
|
|
/*
|
|
* If we are in the middle of a BLE operation, we got called by ContikiMAC
|
|
* from within an interrupt context. Abort, but pretend everything is OK.
|
|
*/
|
|
if(rf_ble_is_active() == RF_BLE_ACTIVE) {
|
|
PRINTF("off: Interrupt context but BLE in progress\n");
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
|
|
while(transmitting());
|
|
|
|
/* stopping the rx explicitly results in lower sleep-mode power usage */
|
|
rx_off();
|
|
rf_core_power_down();
|
|
|
|
ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
|
|
|
|
#if !CC2650_FAST_RADIO_STARTUP
|
|
/* Switch HF clock source to the RCOSC to preserve power.
|
|
* This must be done after stopping RAT.
|
|
*/
|
|
oscillators_switch_to_hf_rc();
|
|
#endif
|
|
|
|
/* We pulled the plug, so we need to restore the status manually */
|
|
((rfc_CMD_IEEE_RX_t *)cmd_ieee_rx_buf)->status = RF_CORE_RADIO_OP_STATUS_IDLE;
|
|
|
|
/*
|
|
* Just in case there was an ongoing RX (which started after we begun the
|
|
* shutdown sequence), we don't want to leave the buffer in state == ongoing
|
|
*/
|
|
if(((rfc_dataEntry_t *)rx_buf_0)->status == DATA_ENTRY_STATUS_BUSY) {
|
|
((rfc_dataEntry_t *)rx_buf_0)->status = DATA_ENTRY_STATUS_PENDING;
|
|
}
|
|
if(((rfc_dataEntry_t *)rx_buf_1)->status == DATA_ENTRY_STATUS_BUSY) {
|
|
((rfc_dataEntry_t *)rx_buf_1)->status = DATA_ENTRY_STATUS_PENDING;
|
|
}
|
|
if(((rfc_dataEntry_t *)rx_buf_2)->status == DATA_ENTRY_STATUS_BUSY) {
|
|
((rfc_dataEntry_t *)rx_buf_2)->status = DATA_ENTRY_STATUS_PENDING;
|
|
}
|
|
if(((rfc_dataEntry_t *)rx_buf_3)->status == DATA_ENTRY_STATUS_BUSY) {
|
|
((rfc_dataEntry_t *)rx_buf_3)->status = DATA_ENTRY_STATUS_PENDING;
|
|
}
|
|
|
|
return RF_CORE_CMD_OK;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Enable or disable CCA before sending */
|
|
static radio_result_t
|
|
set_send_on_cca(uint8_t enable)
|
|
{
|
|
if(enable) {
|
|
/* this driver does not have support for CCA on Tx */
|
|
return RADIO_RESULT_NOT_SUPPORTED;
|
|
}
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static radio_result_t
|
|
get_value(radio_param_t param, radio_value_t *value)
|
|
{
|
|
rfc_CMD_IEEE_RX_t *cmd = (rfc_CMD_IEEE_RX_t *)cmd_ieee_rx_buf;
|
|
|
|
if(!value) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
switch(param) {
|
|
case RADIO_PARAM_POWER_MODE:
|
|
/* On / off */
|
|
*value = rf_is_on() ? RADIO_POWER_MODE_ON : RADIO_POWER_MODE_OFF;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_CHANNEL:
|
|
*value = (radio_value_t)cmd->channel;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_PAN_ID:
|
|
*value = (radio_value_t)cmd->localPanID;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_16BIT_ADDR:
|
|
*value = (radio_value_t)cmd->localShortAddr;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_RX_MODE:
|
|
*value = 0;
|
|
if(cmd->frameFiltOpt.frameFiltEn) {
|
|
*value |= RADIO_RX_MODE_ADDRESS_FILTER;
|
|
}
|
|
if(cmd->frameFiltOpt.autoAckEn) {
|
|
*value |= RADIO_RX_MODE_AUTOACK;
|
|
}
|
|
if(poll_mode) {
|
|
*value |= RADIO_RX_MODE_POLL_MODE;
|
|
}
|
|
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_TX_MODE:
|
|
*value = 0;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_TXPOWER:
|
|
*value = get_tx_power();
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_CCA_THRESHOLD:
|
|
*value = cmd->ccaRssiThr;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_RSSI:
|
|
*value = get_rssi();
|
|
|
|
if(*value == RF_CMD_CCA_REQ_RSSI_UNKNOWN) {
|
|
return RADIO_RESULT_ERROR;
|
|
} else {
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
case RADIO_CONST_CHANNEL_MIN:
|
|
*value = IEEE_MODE_CHANNEL_MIN;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_CONST_CHANNEL_MAX:
|
|
*value = IEEE_MODE_CHANNEL_MAX;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_CONST_TXPOWER_MIN:
|
|
*value = OUTPUT_POWER_MIN;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_CONST_TXPOWER_MAX:
|
|
*value = OUTPUT_POWER_MAX;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_LAST_RSSI:
|
|
*value = last_rssi;
|
|
return RADIO_RESULT_OK;
|
|
case RADIO_PARAM_LAST_LINK_QUALITY:
|
|
*value = last_corr_lqi;
|
|
return RADIO_RESULT_OK;
|
|
default:
|
|
return RADIO_RESULT_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static radio_result_t
|
|
set_value(radio_param_t param, radio_value_t value)
|
|
{
|
|
radio_result_t rv = RADIO_RESULT_OK;
|
|
rfc_CMD_IEEE_RX_t *cmd = (rfc_CMD_IEEE_RX_t *)cmd_ieee_rx_buf;
|
|
uint8_t old_poll_mode;
|
|
|
|
switch(param) {
|
|
case RADIO_PARAM_POWER_MODE:
|
|
if(value == RADIO_POWER_MODE_ON) {
|
|
if(on() != RF_CORE_CMD_OK) {
|
|
PRINTF("set_value: on() failed (1)\n");
|
|
return RADIO_RESULT_ERROR;
|
|
}
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
if(value == RADIO_POWER_MODE_OFF) {
|
|
off();
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
case RADIO_PARAM_CHANNEL:
|
|
if(value < IEEE_MODE_CHANNEL_MIN ||
|
|
value > IEEE_MODE_CHANNEL_MAX) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
/* Note: this return may lead to long periods when RAT and RTC are not resynchronized */
|
|
if(cmd->channel == (uint8_t)value) {
|
|
/* We already have that very same channel configured.
|
|
* Nothing to do here. */
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
|
|
cmd->channel = (uint8_t)value;
|
|
break;
|
|
case RADIO_PARAM_PAN_ID:
|
|
cmd->localPanID = (uint16_t)value;
|
|
break;
|
|
case RADIO_PARAM_16BIT_ADDR:
|
|
cmd->localShortAddr = (uint16_t)value;
|
|
break;
|
|
case RADIO_PARAM_RX_MODE:
|
|
{
|
|
if(value & ~(RADIO_RX_MODE_ADDRESS_FILTER |
|
|
RADIO_RX_MODE_AUTOACK | RADIO_RX_MODE_POLL_MODE)) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
cmd->frameFiltOpt.frameFiltEn = (value & RADIO_RX_MODE_ADDRESS_FILTER) != 0;
|
|
cmd->frameFiltOpt.frameFiltStop = 1;
|
|
cmd->frameFiltOpt.autoAckEn = (value & RADIO_RX_MODE_AUTOACK) != 0;
|
|
cmd->frameFiltOpt.slottedAckEn = 0;
|
|
cmd->frameFiltOpt.autoPendEn = 0;
|
|
cmd->frameFiltOpt.defaultPend = 0;
|
|
cmd->frameFiltOpt.bPendDataReqOnly = 0;
|
|
cmd->frameFiltOpt.bPanCoord = 0;
|
|
cmd->frameFiltOpt.bStrictLenFilter = 0;
|
|
|
|
old_poll_mode = poll_mode;
|
|
poll_mode = (value & RADIO_RX_MODE_POLL_MODE) != 0;
|
|
if(poll_mode == old_poll_mode) {
|
|
uint32_t cmd_status;
|
|
|
|
/* do not turn the radio on and off, just send an update command */
|
|
memcpy(&filter_cmd.newFrameFiltOpt, &cmd->frameFiltOpt, sizeof(cmd->frameFiltOpt));
|
|
|
|
if(rf_core_send_cmd((uint32_t)&filter_cmd, &cmd_status) == RF_CORE_CMD_ERROR) {
|
|
PRINTF("setting address filter failed: CMDSTA=0x%08lx\n", cmd_status);
|
|
return RADIO_RESULT_ERROR;
|
|
}
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RADIO_PARAM_TX_MODE:
|
|
if(value & ~(RADIO_TX_MODE_SEND_ON_CCA)) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
return set_send_on_cca((value & RADIO_TX_MODE_SEND_ON_CCA) != 0);
|
|
|
|
case RADIO_PARAM_TXPOWER:
|
|
if(value < OUTPUT_POWER_MIN || value > OUTPUT_POWER_MAX) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
set_tx_power(value);
|
|
|
|
return RADIO_RESULT_OK;
|
|
|
|
case RADIO_PARAM_CCA_THRESHOLD:
|
|
cmd->ccaRssiThr = (int8_t)value;
|
|
break;
|
|
|
|
default:
|
|
return RADIO_RESULT_NOT_SUPPORTED;
|
|
}
|
|
|
|
/* If off, the new configuration will be applied the next time radio is started */
|
|
if(!rf_is_on()) {
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
|
|
/* If we reach here we had no errors. Apply new settings */
|
|
if(rx_off() != RF_CORE_CMD_OK) {
|
|
PRINTF("set_value: rx_off() failed\n");
|
|
rv = RADIO_RESULT_ERROR;
|
|
}
|
|
|
|
/* Restart the radio timer (RAT).
|
|
This causes resynchronization between RAT and RTC: useful for TSCH. */
|
|
rf_core_restart_rat();
|
|
|
|
check_rat_overflow(false);
|
|
|
|
if(rx_on() != RF_CORE_CMD_OK) {
|
|
PRINTF("set_value: rx_on() failed\n");
|
|
rv = RADIO_RESULT_ERROR;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static radio_result_t
|
|
get_object(radio_param_t param, void *dest, size_t size)
|
|
{
|
|
uint8_t *target;
|
|
uint8_t *src;
|
|
int i;
|
|
rfc_CMD_IEEE_RX_t *cmd = (rfc_CMD_IEEE_RX_t *)cmd_ieee_rx_buf;
|
|
|
|
if(param == RADIO_PARAM_64BIT_ADDR) {
|
|
if(size != 8 || !dest) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
target = dest;
|
|
src = (uint8_t *)(&cmd->localExtAddr);
|
|
|
|
for(i = 0; i < 8; i++) {
|
|
target[i] = src[7 - i];
|
|
}
|
|
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
|
|
if(param == RADIO_PARAM_LAST_PACKET_TIMESTAMP) {
|
|
if(size != sizeof(rtimer_clock_t) || !dest) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
*(rtimer_clock_t *)dest = last_packet_timestamp;
|
|
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
|
|
return RADIO_RESULT_NOT_SUPPORTED;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static radio_result_t
|
|
set_object(radio_param_t param, const void *src, size_t size)
|
|
{
|
|
radio_result_t rv;
|
|
int i;
|
|
uint8_t *dst;
|
|
rfc_CMD_IEEE_RX_t *cmd = (rfc_CMD_IEEE_RX_t *)cmd_ieee_rx_buf;
|
|
|
|
if(param == RADIO_PARAM_64BIT_ADDR) {
|
|
if(size != 8 || !src) {
|
|
return RADIO_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
dst = (uint8_t *)(&cmd->localExtAddr);
|
|
|
|
for(i = 0; i < 8; i++) {
|
|
dst[i] = ((uint8_t *)src)[7 - i];
|
|
}
|
|
|
|
/* If off, the new configuration will be applied the next time radio is started */
|
|
if(!rf_is_on()) {
|
|
return RADIO_RESULT_OK;
|
|
}
|
|
|
|
if(rx_off() != RF_CORE_CMD_OK) {
|
|
PRINTF("set_object: rx_off() failed\n");
|
|
rv = RADIO_RESULT_ERROR;
|
|
}
|
|
|
|
if(rx_on() != RF_CORE_CMD_OK) {
|
|
PRINTF("set_object: rx_on() failed\n");
|
|
rv = RADIO_RESULT_ERROR;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
return RADIO_RESULT_NOT_SUPPORTED;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
const struct radio_driver ieee_mode_driver = {
|
|
init,
|
|
prepare,
|
|
transmit,
|
|
send,
|
|
read_frame,
|
|
channel_clear,
|
|
receiving_packet,
|
|
pending_packet,
|
|
on,
|
|
off,
|
|
get_value,
|
|
set_value,
|
|
get_object,
|
|
set_object,
|
|
};
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* @}
|
|
* @}
|
|
*/
|