From f99511f49427f0163131c7d576a558ac98a70363 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Tue, 7 Jun 2016 00:09:02 +0300 Subject: [PATCH 01/14] srf06-cc26xx: add default TSCH configuration for the platform --- platform/srf06-cc26xx/contiki-conf.h | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/platform/srf06-cc26xx/contiki-conf.h b/platform/srf06-cc26xx/contiki-conf.h index 6e52b5c0c..62e807524 100644 --- a/platform/srf06-cc26xx/contiki-conf.h +++ b/platform/srf06-cc26xx/contiki-conf.h @@ -329,6 +329,55 @@ typedef uint32_t uip_stats_t; */ typedef uint32_t rtimer_clock_t; #define RTIMER_CLOCK_DIFF(a, b) ((int32_t)((a) - (b))) + +/* --------------------------------------------------------------------- */ +/* TSCH related defines */ + +/* Delay between GO signal and SFD */ +#define RADIO_DELAY_BEFORE_TX ((unsigned)US_TO_RTIMERTICKS(81)) +/* Delay between GO signal and start listening. + * This value is so small because the radio is constantly on within each timeslot. */ +#define RADIO_DELAY_BEFORE_RX ((unsigned)US_TO_RTIMERTICKS(15)) +/* Delay between the SFD finishes arriving and it is detected in software. + * Not important on this platform as it uses hardware timestamps for SFD */ +#define RADIO_DELAY_BEFORE_DETECT ((unsigned)US_TO_RTIMERTICKS(0)) + +/* Timer conversion; radio is running at 4 MHz */ +#define RADIO_TIMER_SECOND 4000000u +#if (RTIMER_SECOND % 256) || (RADIO_TIMER_SECOND % 256) +#error RADIO_TO_RTIMER macro must be fixed! +#endif +#define RADIO_TO_RTIMER(X) ((uint32_t)(((uint64_t)(X) * (RTIMER_SECOND / 256)) / (RADIO_TIMER_SECOND / 256))) +#define USEC_TO_RADIO(X) ((X) * 4) + +/* The PHY header (preamble + SFD, 4+1 bytes) duration is equivalent to 10 symbols */ +#define RADIO_IEEE_802154_PHY_HEADER_DURATION_USEC 160 + +/* Do not turn off TSCH within a timeslot: not enough time */ +#define TSCH_CONF_RADIO_ON_DURING_TIMESLOT 1 + +/* Disable TSCH frame filtering */ +#define TSCH_CONF_HW_FRAME_FILTERING 0 + +/* Disable turning off HF oscillator when radio is off; + enable this for TSCH, disable to save more energy. */ +#ifndef CC2650_FAST_RADIO_STARTUP +#define CC2650_FAST_RADIO_STARTUP 1 +#endif + +/* Use hardware timestamps */ +#ifndef TSCH_CONF_RESYNC_WITH_SFD_TIMESTAMPS +#define TSCH_CONF_RESYNC_WITH_SFD_TIMESTAMPS 1 +#define TSCH_CONF_TIMESYNC_REMOVE_JITTER 0 +#endif + +/* The drift compared to "true" 10ms slots. + Enable adaptive sync to enable compensation for this. */ +#define TSCH_CONF_BASE_DRIFT_PPM -977 + +/* 10 times per second */ +#define TSCH_CONF_ASSOCIATION_CHANNEL_SWITCH_FREQUENCY 10 + /** @} */ /*---------------------------------------------------------------------------*/ /* board.h assumes that basic configuration is done */ From 3ff8aa8ad84d3a25f97ed5b73dbdfccf2842126b Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 18:00:19 +0300 Subject: [PATCH 02/14] srf06-cc26xx: add node_id variable --- platform/srf06-cc26xx/contiki-main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platform/srf06-cc26xx/contiki-main.c b/platform/srf06-cc26xx/contiki-main.c index 9e91cec5d..b3d64f24a 100644 --- a/platform/srf06-cc26xx/contiki-main.c +++ b/platform/srf06-cc26xx/contiki-main.c @@ -59,6 +59,7 @@ #include "uart.h" #include "sys/clock.h" #include "sys/rtimer.h" +#include "sys/node-id.h" #include "lib/sensors.h" #include "button-sensor.h" #include "dev/serial-line.h" @@ -68,6 +69,8 @@ #include /*---------------------------------------------------------------------------*/ +unsigned short node_id = 0; +/*---------------------------------------------------------------------------*/ /** \brief Board specific iniatialisation */ void board_init(void); /*---------------------------------------------------------------------------*/ @@ -123,6 +126,10 @@ set_rf_params(void) printf("%02x\n", linkaddr_node_addr.u8[i]); } #endif + + /* also set the global node id */ + node_id = short_addr; + printf(" Node ID: %d\n", node_id); } /*---------------------------------------------------------------------------*/ /** From ac6f8008fd8e4cf9c3802353de1c1ddebd6b7d63 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 17:58:24 +0300 Subject: [PATCH 03/14] cc26xx: implement poll mode, hardware timestamps, and other minor changes in the IEEE radio driver --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 245 +++++++++++++++++++++----- cpu/cc26xx-cc13xx/rf-core/prop-mode.c | 6 +- cpu/cc26xx-cc13xx/rf-core/rf-core.c | 121 ++++++++++--- cpu/cc26xx-cc13xx/rf-core/rf-core.h | 28 ++- 4 files changed, 328 insertions(+), 72 deletions(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index f26d57939..9cc614433 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -51,6 +51,7 @@ #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" @@ -102,6 +103,9 @@ #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 */ @@ -186,6 +190,35 @@ static const output_config_t output_power[] = { /* 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. @@ -202,7 +235,7 @@ static uint8_t cmd_ieee_rx_buf[RF_CMD_BUFFER_SIZE] CC_ALIGN(4); #define DATA_ENTRY_LENSZ_BYTE 1 #define DATA_ENTRY_LENSZ_WORD 2 /* 2 bytes */ -#define RX_BUF_SIZE 140 +#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); @@ -544,7 +577,7 @@ init_rf_params(void) cmd->rxConfig.bAppendRssi = 1; cmd->rxConfig.bAppendCorrCrc = 1; cmd->rxConfig.bAppendSrcInd = 0; - cmd->rxConfig.bAppendTimestamp = 0; + cmd->rxConfig.bAppendTimestamp = 1; cmd->pRxQ = &rx_data_queue; cmd->pOutput = (rfc_ieeeRxOutput_t *)rf_stats; @@ -647,13 +680,14 @@ rx_off(void) 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 */ - ENERGEST_OFF(ENERGEST_TYPE_LISTEN); 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; } /*---------------------------------------------------------------------------*/ @@ -712,6 +746,55 @@ static const rf_core_primary_mode_t mode_ieee = { 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) { @@ -745,6 +828,10 @@ init(void) 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; } @@ -768,6 +855,7 @@ transmit(unsigned short transmit_len) 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; @@ -805,8 +893,19 @@ transmit(unsigned short transmit_len) 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 */ - rf_core_cmd_done_en(true); + if(interrupts_enabled) { + rf_core_cmd_done_en(true, poll_mode); + } ret = rf_core_send_cmd((uint32_t)&cmd, &cmd_status); @@ -818,7 +917,14 @@ transmit(unsigned short transmit_len) /* Idle away while the command is running */ while((cmd.status & RF_CORE_RADIO_OP_MASKED_STATUS) == RF_CORE_RADIO_OP_MASKED_STATUS_RUNNING) { - lpm_sleep(); + /* 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; @@ -848,12 +954,13 @@ transmit(unsigned short transmit_len) ENERGEST_OFF(ENERGEST_TYPE_TRANSMIT); ENERGEST_ON(ENERGEST_TYPE_LISTEN); - /* - * Disable LAST_FG_COMMAND_DONE interrupt. We don't really care about it - * except when we are transmitting - */ - rf_core_cmd_done_dis(); - + 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(); @@ -880,13 +987,48 @@ release_data_entry(void) /* 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) { - int8_t rssi; 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 */ @@ -901,7 +1043,7 @@ read_frame(void *buf, unsigned short buf_len) return 0; } - len = rx_read_entry[8] - 4; + len = rx_read_entry[8] - 8; if(len > buf_len) { PRINTF("RF: too long\n"); @@ -913,9 +1055,21 @@ read_frame(void *buf, unsigned short buf_len) memcpy(buf, (char *)&rx_read_entry[9], len); - rssi = (int8_t)rx_read_entry[9 + len + 2]; + last_rssi = (int8_t)rx_read_entry[9 + len + 2]; + last_corr_lqi = (uint8_t)rx_read_entry[9 + len + 2] & STATUS_CORRELATION; - packetbuf_set_attr(PACKETBUF_ATTR_RSSI, rssi); + /* 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(); @@ -983,9 +1137,7 @@ channel_clear(void) static int receiving_packet(void) { - int ret = 0; uint8_t cca_info; - uint8_t was_off = 0; /* * If we are in the middle of a BLE operation, we got called by ContikiMAC @@ -1035,7 +1187,9 @@ pending_packet(void) do { if(entry->status == DATA_ENTRY_STATUS_FINISHED) { rv = 1; - process_poll(&rf_core_process); + if(!poll_mode) { + process_poll(&rf_core_process); + } } entry = (rfc_dataEntry_t *)entry->pNextEntry; @@ -1076,7 +1230,7 @@ on(void) init_rx_buffers(); - rf_core_setup_interrupts(); + rf_core_setup_interrupts(poll_mode); /* * Trigger a switch to the XOSC, so that we can subsequently use the RF FS @@ -1162,6 +1316,9 @@ get_value(radio_param_t param, radio_value_t *value) 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_TXPOWER: @@ -1222,6 +1379,7 @@ set_value(radio_param_t param, radio_value_t value) 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. */ @@ -1239,7 +1397,7 @@ set_value(radio_param_t param, radio_value_t value) case RADIO_PARAM_RX_MODE: { if(value & ~(RADIO_RX_MODE_ADDRESS_FILTER | - RADIO_RX_MODE_AUTOACK)) { + RADIO_RX_MODE_AUTOACK | RADIO_RX_MODE_POLL_MODE)) { return RADIO_RESULT_INVALID_VALUE; } @@ -1252,6 +1410,8 @@ set_value(radio_param_t param, radio_value_t value) cmd->frameFiltOpt.bPendDataReqOnly = 0; cmd->frameFiltOpt.bPanCoord = 0; cmd->frameFiltOpt.bStrictLenFilter = 0; + + poll_mode = (value & RADIO_RX_MODE_POLL_MODE) != 0; break; } case RADIO_PARAM_TXPOWER: @@ -1269,30 +1429,28 @@ set_value(radio_param_t param, radio_value_t value) return RADIO_RESULT_NOT_SUPPORTED; } - /* If we reach here we had no errors. Apply new settings */ + /* If off, the new configuration will be applied the next time radio is started */ if(!rf_is_on()) { - was_off = 1; - if(on() != RF_CORE_CMD_OK) { - PRINTF("set_value: on() failed (2)\n"); - return RADIO_RESULT_ERROR; - } + 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; } - /* If we were off, turn back off */ - if(was_off) { - off(); - } - return rv; } /*---------------------------------------------------------------------------*/ @@ -1318,13 +1476,22 @@ get_object(radio_param_t param, void *dest, size_t size) 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) { - uint8_t was_off = 0; radio_result_t rv; int i; uint8_t *dst; @@ -1341,12 +1508,9 @@ set_object(radio_param_t param, const void *src, size_t size) 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()) { - was_off = 1; - if(on() != RF_CORE_CMD_OK) { - PRINTF("set_object: on() failed\n"); - return RADIO_RESULT_ERROR; - } + return RADIO_RESULT_OK; } if(rx_off() != RF_CORE_CMD_OK) { @@ -1359,11 +1523,6 @@ set_object(radio_param_t param, const void *src, size_t size) rv = RADIO_RESULT_ERROR; } - /* If we were off, turn back off */ - if(was_off) { - off(); - } - return rv; } return RADIO_RESULT_NOT_SUPPORTED; diff --git a/cpu/cc26xx-cc13xx/rf-core/prop-mode.c b/cpu/cc26xx-cc13xx/rf-core/prop-mode.c index 419a7b9d2..54102f164 100644 --- a/cpu/cc26xx-cc13xx/rf-core/prop-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/prop-mode.c @@ -683,7 +683,7 @@ transmit(unsigned short transmit_len) rx_off_prop(); /* Enable the LAST_COMMAND_DONE interrupt to wake us up */ - rf_core_cmd_done_en(false); + rf_core_cmd_done_en(false, false); ret = rf_core_send_cmd((uint32_t)cmd_tx_adv, &cmd_status); @@ -728,7 +728,7 @@ transmit(unsigned short transmit_len) * Disable LAST_FG_COMMAND_DONE interrupt. We don't really care about it * except when we are transmitting */ - rf_core_cmd_done_dis(); + rf_core_cmd_done_dis(false); /* Workaround. Set status to IDLE */ cmd_tx_adv->status = RF_CORE_RADIO_OP_STATUS_IDLE; @@ -933,7 +933,7 @@ on(void) } } - rf_core_setup_interrupts(); + rf_core_setup_interrupts(false); /* * Trigger a switch to the XOSC, so that we can subsequently use the RF FS diff --git a/cpu/cc26xx-cc13xx/rf-core/rf-core.c b/cpu/cc26xx-cc13xx/rf-core/rf-core.c index 8efe41dac..15fc8e3bc 100644 --- a/cpu/cc26xx-cc13xx/rf-core/rf-core.c +++ b/cpu/cc26xx-cc13xx/rf-core/rf-core.c @@ -92,6 +92,8 @@ #define ENABLED_IRQS (RX_FRAME_IRQ | ERROR_IRQ) #endif +#define ENABLED_IRQS_POLL_MODE (ENABLED_IRQS & ~(RX_FRAME_IRQ | ERROR_IRQ)) + #define cc26xx_rf_cpe0_isr RFCCPE0IntHandler #define cc26xx_rf_cpe1_isr RFCCPE1IntHandler /*---------------------------------------------------------------------------*/ @@ -101,6 +103,10 @@ static rfc_radioOp_t *last_radio_op = NULL; /* A struct holding pointers to the primary mode's abort() and restore() */ static const rf_core_primary_mode_t *primary_mode = NULL; /*---------------------------------------------------------------------------*/ +/* Radio timer (RAT) offset as compared to the rtimer counter (RTC) */ +int32_t rat_offset = 0; +static bool rat_offset_known = false; +/*---------------------------------------------------------------------------*/ PROCESS(rf_core_process, "CC13xx / CC26xx RF driver"); /*---------------------------------------------------------------------------*/ #define RF_CORE_CLOCKS_MASK (RFC_PWR_PWMCLKEN_RFC_M | RFC_PWR_PWMCLKEN_CPE_M \ @@ -265,6 +271,66 @@ rf_core_power_up() return RF_CORE_CMD_OK; } /*---------------------------------------------------------------------------*/ +uint8_t +rf_core_start_rat(void) +{ + uint32_t cmd_status; + rfc_CMD_SYNC_START_RAT_t cmd_start; + + /* Start radio timer (RAT) */ + rf_core_init_radio_op((rfc_radioOp_t *)&cmd_start, sizeof(cmd_start), CMD_SYNC_START_RAT); + + /* copy the value and send back */ + cmd_start.rat0 = rat_offset; + + if(rf_core_send_cmd((uint32_t)&cmd_start, &cmd_status) != RF_CORE_CMD_OK) { + PRINTF("rf_core_get_rat_rtc_offset: SYNC_START_RAT fail, CMDSTA=0x%08lx\n", + cmd_status); + return RF_CORE_CMD_ERROR; + } + + /* Wait until done (?) */ + if(rf_core_wait_cmd_done(&cmd_start) != RF_CORE_CMD_OK) { + PRINTF("rf_core_cmd_ok: SYNC_START_RAT wait, CMDSTA=0x%08lx, status=0x%04x\n", + cmd_status, cmd_start.status); + return RF_CORE_CMD_ERROR; + } + + return RF_CORE_CMD_OK; +} +/*---------------------------------------------------------------------------*/ +uint8_t +rf_core_stop_rat(void) +{ + rfc_CMD_SYNC_STOP_RAT_t cmd_stop; + uint32_t cmd_status; + + rf_core_init_radio_op((rfc_radioOp_t *)&cmd_stop, sizeof(cmd_stop), CMD_SYNC_STOP_RAT); + + int ret = rf_core_send_cmd((uint32_t)&cmd_stop, &cmd_status); + if(ret != RF_CORE_CMD_OK) { + PRINTF("rf_core_get_rat_rtc_offset: SYNC_STOP_RAT fail, ret %d CMDSTA=0x%08lx\n", + ret, cmd_status); + return ret; + } + + /* Wait until done */ + ret = rf_core_wait_cmd_done(&cmd_stop); + if(ret != RF_CORE_CMD_OK) { + PRINTF("rf_core_cmd_ok: SYNC_STOP_RAT wait, CMDSTA=0x%08lx, status=0x%04x\n", + cmd_status, cmd_stop.status); + return ret; + } + + if(!rat_offset_known) { + /* save the offset, but only if this is the first time */ + rat_offset_known = true; + rat_offset = cmd_stop.rat0; + } + + return RF_CORE_CMD_OK; +} +/*---------------------------------------------------------------------------*/ void rf_core_power_down() { @@ -280,6 +346,8 @@ rf_core_power_down() fs_powerdown(); } + rf_core_stop_rat(); + /* Shut down the RFCORE clock domain in the MCU VD */ ti_lib_prcm_domain_disable(PRCM_DOMAIN_RFCORE); ti_lib_prcm_load_set(); @@ -329,22 +397,6 @@ rf_core_set_modesel() } /*---------------------------------------------------------------------------*/ uint8_t -rf_core_start_rat() -{ - uint32_t cmd_status; - - /* Start radio timer (RAT) */ - if(rf_core_send_cmd(CMDR_DIR_CMD(CMD_START_RAT), &cmd_status) - != RF_CORE_CMD_OK) { - PRINTF("rf_core_apply_patches: START_RAT fail, CMDSTA=0x%08lx\n", - cmd_status); - return RF_CORE_CMD_ERROR; - } - - return RF_CORE_CMD_OK; -} -/*---------------------------------------------------------------------------*/ -uint8_t rf_core_boot() { if(rf_core_power_up() != RF_CORE_CMD_OK) { @@ -366,10 +418,31 @@ rf_core_boot() return RF_CORE_CMD_OK; } /*---------------------------------------------------------------------------*/ +uint8_t +rf_core_restart_rat(void) +{ + if(rf_core_stop_rat() != RF_CORE_CMD_OK) { + PRINTF("rf_core_restart_rat: rf_core_stop_rat() failed\n"); + + return RF_CORE_CMD_ERROR; + } + + if(rf_core_start_rat() != RF_CORE_CMD_OK) { + PRINTF("rf_core_restart_rat: rf_core_start_rat() failed\n"); + + rf_core_power_down(); + + return RF_CORE_CMD_ERROR; + } + + return RF_CORE_CMD_OK; +} +/*---------------------------------------------------------------------------*/ void -rf_core_setup_interrupts() +rf_core_setup_interrupts(bool poll_mode) { bool interrupts_disabled; + const uint32_t enabled_irqs = poll_mode ? ENABLED_IRQS_POLL_MODE : ENABLED_IRQS; /* We are already turned on by the caller, so this should not happen */ if(!rf_core_is_accessible()) { @@ -384,7 +457,7 @@ rf_core_setup_interrupts() HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEISL) = ERROR_IRQ; /* Acknowledge configured interrupts */ - HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIEN) = ENABLED_IRQS; + HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIEN) = enabled_irqs; /* Clear interrupt flags, active low clear(?) */ HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIFG) = 0x0; @@ -400,18 +473,20 @@ rf_core_setup_interrupts() } /*---------------------------------------------------------------------------*/ void -rf_core_cmd_done_en(bool fg) +rf_core_cmd_done_en(bool fg, bool poll_mode) { uint32_t irq = fg ? IRQ_LAST_FG_COMMAND_DONE : IRQ_LAST_COMMAND_DONE; + const uint32_t enabled_irqs = poll_mode ? ENABLED_IRQS_POLL_MODE : ENABLED_IRQS; - HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIFG) = ENABLED_IRQS; - HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIEN) = ENABLED_IRQS | irq; + HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIFG) = enabled_irqs; + HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIEN) = enabled_irqs | irq; } /*---------------------------------------------------------------------------*/ void -rf_core_cmd_done_dis() +rf_core_cmd_done_dis(bool poll_mode) { - HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIEN) = ENABLED_IRQS; + const uint32_t enabled_irqs = poll_mode ? ENABLED_IRQS_POLL_MODE : ENABLED_IRQS; + HWREG(RFC_DBELL_NONBUF_BASE + RFC_DBELL_O_RFCPEIEN) = enabled_irqs; } /*---------------------------------------------------------------------------*/ rfc_radioOp_t * diff --git a/cpu/cc26xx-cc13xx/rf-core/rf-core.h b/cpu/cc26xx-cc13xx/rf-core/rf-core.h index 42c6edb90..474de4a87 100644 --- a/cpu/cc26xx-cc13xx/rf-core/rf-core.h +++ b/cpu/cc26xx-cc13xx/rf-core/rf-core.h @@ -228,6 +228,9 @@ typedef struct rf_core_primary_mode_s { #define RF_CORE_COMMAND_PROTOCOL_IEEE 0x2000 #define RF_CORE_COMMAND_PROTOCOL_PROP 0x3000 /*---------------------------------------------------------------------------*/ +/* Radio timer register */ +#define RATCNT 0x00000004 +/*---------------------------------------------------------------------------*/ /* Make the main driver process visible to mode drivers */ PROCESS_NAME(rf_core_process); /*---------------------------------------------------------------------------*/ @@ -310,6 +313,24 @@ uint8_t rf_core_set_modesel(void); */ uint8_t rf_core_start_rat(void); +/** + * \brief Stop the CM0 RAT synchronously + * \return RF_CORE_CMD_OK or RF_CORE_CMD_ERROR + * + * This function is not strictly necessary, but through calling it it's possibly + * to learn the RAT / RTC offset, which useful to get accurate radio timestamps. + */ +uint8_t rf_core_stop_rat(void); + +/** + * \brief Restart the CM0 RAT + * \return RF_CORE_CMD_OK or RF_CORE_CMD_ERROR + * + * This function restarts the CM0 RAT and therefore resynchornizes it with RTC. + * To achieve good timing accuracy, it should be called periodically. + */ +uint8_t rf_core_restart_rat(void); + /** * \brief Boot the RF Core * \return RF_CORE_CMD_OK or RF_CORE_CMD_ERROR @@ -327,19 +348,20 @@ uint8_t rf_core_boot(void); /** * \brief Setup RF core interrupts */ -void rf_core_setup_interrupts(void); +void rf_core_setup_interrupts(bool poll_mode); /** * \brief Enable interrupt on command done. * \param fg set true to enable irq on foreground command done and false for * background commands or if not in ieee mode. + * \param poll_mode true if the driver is in poll mode * * This is used within TX routines in order to be able to sleep the CM3 and * wake up after TX has finished * * \sa rf_core_cmd_done_dis() */ -void rf_core_cmd_done_en(bool fg); +void rf_core_cmd_done_en(bool fg, bool poll_mode); /** * \brief Disable the LAST_CMD_DONE and LAST_FG_CMD_DONE interrupts. @@ -348,7 +370,7 @@ void rf_core_cmd_done_en(bool fg); * * \sa rf_core_cmd_done_en() */ -void rf_core_cmd_done_dis(void); +void rf_core_cmd_done_dis(bool poll_mode); /** * \brief Returns a pointer to the most recent proto-dependent Radio Op From 95b66657aab656e3ca9b2a44d3c2e8cf093ff4d2 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 17:57:46 +0300 Subject: [PATCH 04/14] cc26xx: add TSCH-related rtimer defines --- cpu/cc26xx-cc13xx/rtimer-arch.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cpu/cc26xx-cc13xx/rtimer-arch.h b/cpu/cc26xx-cc13xx/rtimer-arch.h index 0d54d227c..128461c8d 100644 --- a/cpu/cc26xx-cc13xx/rtimer-arch.h +++ b/cpu/cc26xx-cc13xx/rtimer-arch.h @@ -50,6 +50,21 @@ #define RTIMER_ARCH_SECOND 65536 /*---------------------------------------------------------------------------*/ rtimer_clock_t rtimer_arch_now(void); + +/* HW oscillator frequency is 32 kHz, not 64 kHz and RTIMER_NOW() never returns + * an odd value; so US_TO_RTIMERTICKS always rounds to the nearest even number. + */ +#define US_TO_RTIMERTICKS(US) (2 * ((US) >= 0 ? \ + (((int32_t)(US) * (RTIMER_ARCH_SECOND / 2) + 500000) / 1000000L) : \ + ((int32_t)(US) * (RTIMER_ARCH_SECOND / 2) - 500000) / 1000000L)) + +#define RTIMERTICKS_TO_US(T) ((T) >= 0 ? \ + (((int32_t)(T) * 1000000L + ((RTIMER_ARCH_SECOND) / 2)) / (RTIMER_ARCH_SECOND)) : \ + ((int32_t)(T) * 1000000L - ((RTIMER_ARCH_SECOND) / 2)) / (RTIMER_ARCH_SECOND)) + +/* A 64-bit version because the 32-bit one cannot handle T >= 4295 ticks. + Intended only for positive values of T. */ +#define RTIMERTICKS_TO_US_64(T) ((uint32_t)(((uint64_t)(T) * 1000000 + ((RTIMER_ARCH_SECOND) / 2)) / (RTIMER_ARCH_SECOND))) /*---------------------------------------------------------------------------*/ #endif /* RTIMER_ARCH_H_ */ /*---------------------------------------------------------------------------*/ From a47fb723e468368808f44e48712552c52519a31a Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 18:02:26 +0300 Subject: [PATCH 05/14] cc26xx: enable sync between radio timer and RTC, useful for TSCH --- cpu/cc26xx-cc13xx/clock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpu/cc26xx-cc13xx/clock.c b/cpu/cc26xx-cc13xx/clock.c index 4f6df056f..85ea6e444 100644 --- a/cpu/cc26xx-cc13xx/clock.c +++ b/cpu/cc26xx-cc13xx/clock.c @@ -118,6 +118,9 @@ clock_init(void) /* GPT0 / Timer B: One shot, PWM interrupt enable */ HWREG(GPT0_BASE + GPT_O_TBMR) = ((TIMER_CFG_B_ONE_SHOT >> 8) & 0xFF) | GPT_TBMR_TBPWMIE; + + /* enable sync with radio timer */ + HWREGBITW(AON_RTC_BASE + AON_RTC_O_CTL, AON_RTC_CTL_RTC_UPD_EN_BITN) = 1; } /*---------------------------------------------------------------------------*/ static void From 3a99639294bef07938ed9ccb54603060ef9226c1 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 18:08:34 +0300 Subject: [PATCH 06/14] cc26xx: implement CC2650_FAST_RADIO_STARTUP option, required for TSCH --- cpu/cc26xx-cc13xx/dev/soc-rtc.c | 12 +- cpu/cc26xx-cc13xx/lpm.c | 235 ++++++++++++++------------- platform/srf06-cc26xx/contiki-main.c | 5 + 3 files changed, 139 insertions(+), 113 deletions(-) diff --git a/cpu/cc26xx-cc13xx/dev/soc-rtc.c b/cpu/cc26xx-cc13xx/dev/soc-rtc.c index 08d9a7cce..398f14d6a 100644 --- a/cpu/cc26xx-cc13xx/dev/soc-rtc.c +++ b/cpu/cc26xx-cc13xx/dev/soc-rtc.c @@ -81,11 +81,13 @@ soc_rtc_init(void) ti_lib_aon_rtc_event_clear(AON_RTC_CH0); ti_lib_aon_rtc_event_clear(AON_RTC_CH1); + ti_lib_aon_rtc_event_clear(AON_RTC_CH2); /* Setup the wakeup event */ ti_lib_aon_event_mcu_wake_up_set(AON_EVENT_MCU_WU0, AON_EVENT_RTC_CH0); ti_lib_aon_event_mcu_wake_up_set(AON_EVENT_MCU_WU1, AON_EVENT_RTC_CH1); - ti_lib_aon_rtc_combined_event_config(AON_RTC_CH0 | AON_RTC_CH1); + ti_lib_aon_event_mcu_wake_up_set(AON_EVENT_MCU_WU2, AON_EVENT_RTC_CH2); + ti_lib_aon_rtc_combined_event_config(AON_RTC_CH0 | AON_RTC_CH1 | AON_RTC_CH2); HWREG(AON_RTC_BASE + AON_RTC_O_SEC) = SOC_RTC_START_TICK_COUNT; @@ -123,7 +125,7 @@ soc_rtc_get_next_trigger() void soc_rtc_schedule_one_shot(uint32_t channel, uint32_t ticks) { - if((channel != AON_RTC_CH0) && (channel != AON_RTC_CH1)) { + if((channel != AON_RTC_CH0) && (channel != AON_RTC_CH1) && (channel != AON_RTC_CH2)) { return; } @@ -170,6 +172,12 @@ soc_rtc_isr(void) rtimer_run_next(); } + if(ti_lib_aon_rtc_event_get(AON_RTC_CH2)) { + /* after sleep; since a rtimer is already scheduled, do nothing */ + ti_lib_aon_rtc_channel_disable(AON_RTC_CH2); + HWREG(AON_RTC_BASE + AON_RTC_O_EVFLAGS) = AON_RTC_EVFLAGS_CH2; + } + ENERGEST_OFF(ENERGEST_TYPE_IRQ); } /*---------------------------------------------------------------------------*/ diff --git a/cpu/cc26xx-cc13xx/lpm.c b/cpu/cc26xx-cc13xx/lpm.c index 686065c73..3253202c2 100644 --- a/cpu/cc26xx-cc13xx/lpm.c +++ b/cpu/cc26xx-cc13xx/lpm.c @@ -77,12 +77,13 @@ LIST(modules_list); * Don't consider standby mode if the next AON RTC event is scheduled to fire * in less than STANDBY_MIN_DURATION rtimer ticks */ -#define STANDBY_MIN_DURATION (RTIMER_SECOND >> 11) -#define MINIMAL_SAFE_SCHEDUAL 8u +#define STANDBY_MIN_DURATION (RTIMER_SECOND / 100) /* 10.0 ms */ + +/* Wake up this much time earlier before the next rtimer */ +#define SLEEP_GUARD_TIME (RTIMER_SECOND / 1000) /* 1.0 ms */ + #define MAX_SLEEP_TIME RTIMER_SECOND -#define DEFAULT_SLEEP_TIME RTIMER_SECOND -/*---------------------------------------------------------------------------*/ -#define CLK_TO_RT(c) ((c) * (RTIMER_SECOND / CLOCK_SECOND)) +#define MINIMAL_SAFE_SCHEDULE 8u /*---------------------------------------------------------------------------*/ /* Prototype of a function in clock.c. Called every time we come out of DS */ void clock_update(void); @@ -241,14 +242,124 @@ wake_up(void) module->wakeup(); } } + +#if CC2650_FAST_RADIO_STARTUP + /* + * Trigger a switch to the XOSC, so that we can subsequently use the RF FS + */ + oscillators_request_hf_xosc(); +#endif +} +/*---------------------------------------------------------------------------*/ +static int +setup_sleep_mode(rtimer_clock_t *next_timer) +{ + lpm_registered_module_t *module; + uint8_t max_pm = LPM_MODE_MAX_SUPPORTED; + + rtimer_clock_t now = RTIMER_NOW(); + const rtimer_clock_t max_sleep = now + MAX_SLEEP_TIME; + + /* next_timer will hold the time of the next system wakeup due to a timer*/ + *next_timer = max_sleep; + + /* Check if any events fired before we turned interrupts off. If so, abort */ + if(LPM_MODE_MAX_SUPPORTED == LPM_MODE_AWAKE || process_nevents()) { + return LPM_MODE_AWAKE; + } + + if(ti_lib_aon_rtc_channel_active(AON_RTC_CH0)) { + rtimer_clock_t next_rtimer; + /* find out the timer of the next rtimer interrupt */ + next_rtimer = ti_lib_aon_rtc_compare_value_get(AON_RTC_CH0); + if(RTIMER_CLOCK_LT(next_rtimer, now + 2)) { + return LPM_MODE_AWAKE; + } + if(RTIMER_CLOCK_LT(next_rtimer, now + STANDBY_MIN_DURATION)) { + return LPM_MODE_SLEEP; + } + *next_timer = next_rtimer; + } + + /* also find out the timer of the next etimer */ + if(etimer_pending()) { + int32_t until_next_etimer; + rtimer_clock_t next_etimer; + + until_next_etimer = (int32_t)etimer_next_expiration_time() - (int32_t)clock_time(); + if(until_next_etimer < 1) { + return LPM_MODE_AWAKE; + } + + next_etimer = soc_rtc_last_isr_time() + (until_next_etimer * (RTIMER_SECOND / CLOCK_SECOND)); + if(RTIMER_CLOCK_LT(next_etimer, now + STANDBY_MIN_DURATION)) { + /* ensure that we schedule sleep a minimal number of ticks into the + future */ + soc_rtc_schedule_one_shot(AON_RTC_CH1, now + MINIMAL_SAFE_SCHEDULE); + return LPM_MODE_SLEEP; + } + + if(RTIMER_CLOCK_LT(max_sleep, next_etimer)) { + /* if max_pm is LPM_MODE_SLEEP, we could trigger the watchdog if we slept + for too long. */ + if(RTIMER_CLOCK_LT(max_sleep, *next_timer)) { + soc_rtc_schedule_one_shot(AON_RTC_CH1, max_sleep); + } + } else { + /* Reschedule AON RTC CH1 to fire just in time for the next etimer event */ + soc_rtc_schedule_one_shot(AON_RTC_CH1, next_etimer); + } + + if(RTIMER_CLOCK_LT(next_etimer, *next_timer)) { + /* set `next_timer` to the time the first etimer fires */ + *next_timer = next_etimer; + } + } + + /* Collect max allowed PM permission from interested modules */ + for(module = list_head(modules_list); module != NULL; + module = module->next) { + if(module->request_max_pm) { + uint8_t module_pm = module->request_max_pm(); + if(module_pm < max_pm) { + max_pm = module_pm; + } + } + } + + return max_pm; +} +/*---------------------------------------------------------------------------*/ +void +lpm_sleep(void) +{ + ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM); + + /* We are only interested in IRQ energest while idle or in LPM */ + ENERGEST_IRQ_RESTORE(irq_energest); + + /* Just to be on the safe side, explicitly disable Deep Sleep */ + HWREG(NVIC_SYS_CTRL) &= ~(NVIC_SYS_CTRL_SLEEPDEEP); + + ti_lib_prcm_sleep(); + + /* Remember IRQ energest for next pass */ + ENERGEST_IRQ_SAVE(irq_energest); + + ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU); } /*---------------------------------------------------------------------------*/ static void -deep_sleep(void) +deep_sleep(rtimer_clock_t next_timer) { uint32_t domains = LOCKABLE_DOMAINS; lpm_registered_module_t *module; +#if CC2650_FAST_RADIO_STARTUP + /* schedule a wakeup briefly before the next etimer/rtimer to wake up the system */ + soc_rtc_schedule_one_shot(AON_RTC_CH2, next_timer - SLEEP_GUARD_TIME); +#endif + /* * Notify all registered modules that we are dropping to mode X. We do not * need to do this for simple sleep. @@ -304,7 +415,8 @@ deep_sleep(void) * turn back off. * * If the radio is on, we won't even reach here, and if it's off the HF - * clock source should already be the HF RC. + * clock source should already be the HF RC, unless CC2650_FAST_RADIO_STARTUP + * is defined. * * Nevertheless, request the switch to the HF RC explicitly here. */ @@ -370,131 +482,32 @@ deep_sleep(void) * unpending events so the handlers can fire */ wake_up(); -} -/*---------------------------------------------------------------------------*/ -static void -safe_schedule_rtimer(rtimer_clock_t time, rtimer_clock_t now, int pm) -{ - rtimer_clock_t min_sleep; - rtimer_clock_t max_sleep; - min_sleep = now + MINIMAL_SAFE_SCHEDUAL; - max_sleep = now + MAX_SLEEP_TIME; - - if(RTIMER_CLOCK_LT(time, min_sleep)) { - /* ensure that we schedule sleep a minimal number of ticks into the - future */ - soc_rtc_schedule_one_shot(AON_RTC_CH1, min_sleep); - } else if((pm == LPM_MODE_SLEEP) && RTIMER_CLOCK_LT(max_sleep, time)) { - /* if max_pm is LPM_MODE_SLEEP, we could trigger the watchdog if we slept - for too long. */ - soc_rtc_schedule_one_shot(AON_RTC_CH1, max_sleep); - } else { - soc_rtc_schedule_one_shot(AON_RTC_CH1, time); - } -} -/*---------------------------------------------------------------------------*/ -static int -setup_sleep_mode(void) -{ - rtimer_clock_t et_distance = 0; - - lpm_registered_module_t *module; - int max_pm; - int module_pm; - int etimer_is_pending; - rtimer_clock_t now; - rtimer_clock_t et_time; - rtimer_clock_t next_trig; - - max_pm = LPM_MODE_MAX_SUPPORTED; - now = RTIMER_NOW(); - - if((LPM_MODE_MAX_SUPPORTED == LPM_MODE_AWAKE) || process_nevents()) { - return LPM_MODE_AWAKE; - } - - etimer_is_pending = etimer_pending(); - - if(etimer_is_pending) { - et_distance = CLK_TO_RT(etimer_next_expiration_time() - clock_time()); - - if(RTIMER_CLOCK_LT(et_distance, 1)) { - /* there is an etimer which is already expired; we shouldn't go to - sleep at all */ - return LPM_MODE_AWAKE; - } - } - - next_trig = soc_rtc_get_next_trigger(); - if(RTIMER_CLOCK_LT(next_trig, now + STANDBY_MIN_DURATION)) { - return LPM_MODE_SLEEP; - } - - /* Collect max allowed PM permission from interested modules */ - for(module = list_head(modules_list); module != NULL; - module = module->next) { - if(module->request_max_pm) { - module_pm = module->request_max_pm(); - if(module_pm < max_pm) { - max_pm = module_pm; - } - } - } - - /* Reschedule AON RTC CH1 to fire just in time for the next etimer event */ - if(etimer_is_pending) { - et_time = soc_rtc_last_isr_time() + et_distance; - - safe_schedule_rtimer(et_time, now, max_pm); - } else { - /* set a maximal sleep period if no etimers are queued */ - soc_rtc_schedule_one_shot(AON_RTC_CH1, now + DEFAULT_SLEEP_TIME); - } - - return max_pm; + ti_lib_int_master_enable(); } /*---------------------------------------------------------------------------*/ void lpm_drop() { uint8_t max_pm; + rtimer_clock_t next_timer; /* Critical. Don't get interrupted! */ ti_lib_int_master_disable(); - max_pm = setup_sleep_mode(); + max_pm = setup_sleep_mode(&next_timer); /* Drop */ if(max_pm == LPM_MODE_SLEEP) { lpm_sleep(); } else if(max_pm == LPM_MODE_DEEP_SLEEP) { - deep_sleep(); + deep_sleep(next_timer); } ti_lib_int_master_enable(); } /*---------------------------------------------------------------------------*/ void -lpm_sleep(void) -{ - ENERGEST_SWITCH(ENERGEST_TYPE_CPU, ENERGEST_TYPE_LPM); - - /* We are only interested in IRQ energest while idle or in LPM */ - ENERGEST_IRQ_RESTORE(irq_energest); - - /* Just to be on the safe side, explicitly disable Deep Sleep */ - HWREG(NVIC_SYS_CTRL) &= ~(NVIC_SYS_CTRL_SLEEPDEEP); - - ti_lib_prcm_sleep(); - - /* Remember IRQ energest for next pass */ - ENERGEST_IRQ_SAVE(irq_energest); - - ENERGEST_SWITCH(ENERGEST_TYPE_LPM, ENERGEST_TYPE_CPU); -} -/*---------------------------------------------------------------------------*/ -void lpm_register_module(lpm_registered_module_t *module) { list_add(modules_list, module); @@ -512,7 +525,7 @@ lpm_init() list_init(modules_list); /* Always wake up on any DIO edge detection */ - ti_lib_aon_event_mcu_wake_up_set(AON_EVENT_MCU_WU2, AON_EVENT_IO); + ti_lib_aon_event_mcu_wake_up_set(AON_EVENT_MCU_WU3, AON_EVENT_IO); } /*---------------------------------------------------------------------------*/ void diff --git a/platform/srf06-cc26xx/contiki-main.c b/platform/srf06-cc26xx/contiki-main.c index b3d64f24a..b3f5c37f7 100644 --- a/platform/srf06-cc26xx/contiki-main.c +++ b/platform/srf06-cc26xx/contiki-main.c @@ -149,6 +149,11 @@ main(void) /* Set the LF XOSC as the LF system clock source */ oscillators_select_lf_xosc(); +#if CC2650_FAST_RADIO_STARTUP + /* Also request HF XOSC to start up */ + oscillators_request_hf_xosc(); +#endif + lpm_init(); board_init(); From e19fbc996e240c0222409ac2b51a44645f7caf89 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:10:38 +0300 Subject: [PATCH 07/14] cc26xx: use CMD_IEEE_MOD_FILT to change address filtering instead of fully restarting the radio --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index 9cc614433..03c4ab2fc 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -631,6 +631,11 @@ init_rf_params(void) 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 @@ -1355,9 +1360,9 @@ get_value(radio_param_t param, radio_value_t *value) static radio_result_t set_value(radio_param_t param, radio_value_t value) { - uint8_t was_off = 0; 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: @@ -1411,7 +1416,20 @@ set_value(radio_param_t param, radio_value_t value) 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_TXPOWER: From 97c1cfc3b4fe4ea430f5ed6413a5d9867ed6728d Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:22:20 +0300 Subject: [PATCH 08/14] cc26xx: return the expected value 0 from prepare() in ieee and prop mode radio --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 2 +- cpu/cc26xx-cc13xx/rf-core/prop-mode.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index 03c4ab2fc..f81faf1c3 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -847,7 +847,7 @@ 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 RF_CORE_CMD_OK; + return 0; } /*---------------------------------------------------------------------------*/ static int diff --git a/cpu/cc26xx-cc13xx/rf-core/prop-mode.c b/cpu/cc26xx-cc13xx/rf-core/prop-mode.c index 54102f164..94aef33b9 100644 --- a/cpu/cc26xx-cc13xx/rf-core/prop-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/prop-mode.c @@ -634,7 +634,7 @@ 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 RF_CORE_CMD_OK; + return 0; } /*---------------------------------------------------------------------------*/ static int From 25c5f0b744970343841edd3d7d26215d60fceefc Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:23:59 +0300 Subject: [PATCH 09/14] cc26xx: implement support for CC2650_FAST_RADIO_STARTUP --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index f81faf1c3..b67bddac0 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -1216,11 +1216,13 @@ on(void) 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(), @@ -1228,22 +1230,23 @@ on(void) 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; } - init_rx_buffers(); - rf_core_setup_interrupts(poll_mode); - /* - * 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/ - */ - oscillators_switch_to_hf_xosc(); - if(rf_radio_setup() != RF_CORE_CMD_OK) { PRINTF("on: radio_setup() failed\n"); return RF_CORE_CMD_ERROR; @@ -1272,8 +1275,12 @@ off(void) ENERGEST_OFF(ENERGEST_TYPE_LISTEN); - /* Switch HF clock source to the RCOSC to preserve power */ +#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; From 80aa30c5da2aeaf2f6688930c54d6a39cb47944b Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:25:38 +0300 Subject: [PATCH 10/14] cc26xx: add support for RADIO_PARAM_TX_MODE getting and setting and for LAST_RSSI and LAST_LQI reading --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index b67bddac0..3eb46ae78 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -1297,6 +1297,17 @@ off(void) 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) { @@ -1332,6 +1343,9 @@ get_value(radio_param_t param, radio_value_t *value) *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(); @@ -1359,6 +1373,12 @@ get_value(radio_param_t param, radio_value_t *value) 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; } @@ -1439,6 +1459,13 @@ set_value(radio_param_t param, radio_value_t value) } 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; @@ -1447,9 +1474,11 @@ set_value(radio_param_t param, radio_value_t 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; } From 54e4b5f3510ecb9f249eaf8e465aef3344c65b99 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:27:16 +0300 Subject: [PATCH 11/14] cc26xx: allow IEEE 802.15.4 frames with version 2, required for TSCH --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index 3eb46ae78..7d4de12be 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -601,7 +601,7 @@ init_rf_params(void) cmd->frameFiltOpt.defaultPend = 0; cmd->frameFiltOpt.bPendDataReqOnly = 0; cmd->frameFiltOpt.bPanCoord = 0; - cmd->frameFiltOpt.maxFrameVersion = 1; + cmd->frameFiltOpt.maxFrameVersion = 2; cmd->frameFiltOpt.bStrictLenFilter = 0; /* Receive all frame types */ From d85667d5356b3a08773adadce69eab552b43d805 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:29:35 +0300 Subject: [PATCH 12/14] cc26xx: packet queue changes: allow to read packets after radio has been turned off; make pending_packet() return true when there is a packets being received --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index 7d4de12be..af48a73bd 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -1190,7 +1190,8 @@ pending_packet(void) /* Go through all RX buffers and check their status */ do { - if(entry->status == DATA_ENTRY_STATUS_FINISHED) { + if(entry->status == DATA_ENTRY_STATUS_FINISHED + || entry->status == DATA_ENTRY_STATUS_BUSY) { rv = 1; if(!poll_mode) { process_poll(&rf_core_process); @@ -1289,10 +1290,18 @@ off(void) * 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 */ - ((rfc_dataEntry_t *)rx_buf_0)->status = DATA_ENTRY_STATUS_PENDING; - ((rfc_dataEntry_t *)rx_buf_1)->status = DATA_ENTRY_STATUS_PENDING; - ((rfc_dataEntry_t *)rx_buf_2)->status = DATA_ENTRY_STATUS_PENDING; - ((rfc_dataEntry_t *)rx_buf_3)->status = DATA_ENTRY_STATUS_PENDING; + 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; } From e233c3f27f9adc475e9d6d6d0b4cac560644eb0c Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Mon, 25 Apr 2016 19:31:04 +0300 Subject: [PATCH 13/14] cc26xx: change CCA logic to be standard-compatible; change receiving_packet to return true iff sync (SFD) has been seen --- cpu/cc26xx-cc13xx/rf-core/ieee-mode.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c index af48a73bd..08843d699 100644 --- a/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c +++ b/cpu/cc26xx-cc13xx/rf-core/ieee-mode.c @@ -146,6 +146,13 @@ static uint8_t rf_stats[16] = { 0 }; #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 @@ -617,9 +624,9 @@ init_rf_params(void) /* Configure CCA settings */ cmd->ccaOpt.ccaEnEnergy = 1; cmd->ccaOpt.ccaEnCorr = 1; - cmd->ccaOpt.ccaEnSync = 0; + cmd->ccaOpt.ccaEnSync = 1; cmd->ccaOpt.ccaCorrOp = 1; - cmd->ccaOpt.ccaSyncOp = 1; + cmd->ccaOpt.ccaSyncOp = 0; cmd->ccaOpt.ccaCorrThr = 3; cmd->ccaRssiThr = IEEE_MODE_RSSI_THRESHOLD; @@ -1167,19 +1174,17 @@ receiving_packet(void) cca_info = get_cca_info(); + /* If we can't read CCA info, return "not receiving" */ if(cca_info == RF_GET_CCA_INFO_ERROR) { - /* If we can't read CCA info, return "not receiving" */ - ret = 0; - } else { - /* Return 1 (receiving) if ccaState is busy */ - ret = (cca_info & 0x03) == RF_CMD_CCA_REQ_CCA_STATE_BUSY; + return 0; } - if(was_off) { - off(); + /* If sync has been seen, return 1 (receiving) */ + if(cca_info & RF_CMD_CCA_REQ_CCA_SYNC_BUSY) { + return 1; } - return ret; + return 0; } /*---------------------------------------------------------------------------*/ static int From 6800f9502e4e64d07cd0a43afc2d6df58aab63b7 Mon Sep 17 00:00:00 2001 From: Atis Elsts Date: Tue, 7 Jun 2016 01:06:15 +0300 Subject: [PATCH 14/14] Update the list of supported platforms in TSCH README file --- core/net/mac/tsch/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/net/mac/tsch/README.md b/core/net/mac/tsch/README.md index ae8f7dcba..0b7c54ef3 100644 --- a/core/net/mac/tsch/README.md +++ b/core/net/mac/tsch/README.md @@ -38,6 +38,7 @@ It has been tested on the following platforms: * Zolertia Z1 (`z1`, tested in cooja only) * CC2538DK (`cc2538dk`, tested on hardware) * Zolertia Zoul (`zoul`, tested on hardware) + * CC2650 (`srf06-cc26xx`, tested on hardware) This implementation was present at the ETSI Plugtest event in Prague in July 2015, and did successfully inter-operate with all @@ -78,7 +79,7 @@ Orchestra is implemented in: A simple TSCH+RPL example is included under `examples/ipv6/rpl-tsch`. To use TSCH, first make sure your platform supports it. -Currently, `jn516x`, `sky`, `z1`, `cc2538dk` and `zoul` are the supported platforms. +Currently, `jn516x`, `sky`, `z1`, `cc2538dk`, `zoul` and `srf06-cc26xx` are the supported platforms. To add your own, we refer the reader to the next section. To add TSCH to your application, first include the TSCH module from your makefile with: @@ -164,7 +165,7 @@ Finally, one can also implement his own scheduler, centralized or distributed, b ## Porting TSCH to a new platform Porting TSCH to a new platform requires a few new features in the radio driver, a number of timing-related configuration paramters. -The easiest is probably to start from one of the existing port: `jn516x`, `sky`, `z1`, `cc2538dk`, `zoul`. +The easiest is probably to start from one of the existing port: `jn516x`, `sky`, `z1`, `cc2538dk`, `zoul`, `srf06-cc26xx`. ### Radio features required for TSCH