Significant improvements to the Contiki X-MAC:

* the code is simplified, particularly the duty cycling logic
* old unused code has been removed
* Contiki X-MAC now listens before sending a strobe to avoid MAC-level
  collisions
* broadcasts are sent directly, without strobes, reducing the power
  consumption of receivers
* turn off radio for 1 ms between sending a strobe and expecting the
  strobe ack
This commit is contained in:
adamdunkels 2009-12-06 13:16:59 +00:00
parent cef2e65c9b
commit 822149f943
1 changed files with 141 additions and 171 deletions

View File

@ -28,7 +28,7 @@
* *
* This file is part of the Contiki operating system. * This file is part of the Contiki operating system.
* *
* $Id: xmac.c,v 1.43 2009/11/27 14:27:50 fros4943 Exp $ * $Id: xmac.c,v 1.44 2009/12/06 13:16:59 adamdunkels Exp $
*/ */
/** /**
@ -60,13 +60,24 @@
#include <string.h> #include <string.h>
#define WITH_CHANNEL_CHECK 0 /* Seems to work badly when enabled */ #ifndef WITH_TIMESYNCH
#define WITH_TIMESYNCH 0 #define WITH_TIMESYNCH 0
#define WITH_QUEUE 0 #endif
#ifndef WITH_ACK_OPTIMIZATION
#define WITH_ACK_OPTIMIZATION 1 #define WITH_ACK_OPTIMIZATION 1
#endif
#ifndef WITH_RANDOM_WAIT_BEFORE_SEND
#define WITH_RANDOM_WAIT_BEFORE_SEND 0 #define WITH_RANDOM_WAIT_BEFORE_SEND 0
#endif
#ifndef WITH_ENCOUNTER_OPTIMIZATION
#define WITH_ENCOUNTER_OPTIMIZATION 1 #define WITH_ENCOUNTER_OPTIMIZATION 1
#endif
#ifndef WITH_STREAMING
#define WITH_STREAMING 1 #define WITH_STREAMING 1
#endif
#ifndef WITH_STROBE_BROADCAST
#define WITH_STROBE_BROADCAST 0
#endif
struct announcement_data { struct announcement_data {
uint16_t id; uint16_t id;
@ -114,6 +125,8 @@ struct xmac_hdr {
#define DEFAULT_PERIOD (DEFAULT_OFF_TIME + DEFAULT_ON_TIME) #define DEFAULT_PERIOD (DEFAULT_OFF_TIME + DEFAULT_ON_TIME)
#define WAIT_TIME_BEFORE_STROBE_ACK RTIMER_ARCH_SECOND / 1000
/* On some platforms, we may end up with a DEFAULT_PERIOD that is 0 /* On some platforms, we may end up with a DEFAULT_PERIOD that is 0
which will make compilation fail due to a modulo operation in the which will make compilation fail due to a modulo operation in the
code. To ensure that DEFAULT_PERIOD is greater than zero, we use code. To ensure that DEFAULT_PERIOD is greater than zero, we use
@ -135,7 +148,7 @@ struct xmac_hdr {
struct xmac_config xmac_config = { struct xmac_config xmac_config = {
DEFAULT_ON_TIME, DEFAULT_ON_TIME,
DEFAULT_OFF_TIME, DEFAULT_OFF_TIME,
20 * DEFAULT_ON_TIME + DEFAULT_OFF_TIME, 4 * DEFAULT_ON_TIME + DEFAULT_OFF_TIME,
DEFAULT_STROBE_WAIT_TIME DEFAULT_STROBE_WAIT_TIME
}; };
@ -247,6 +260,38 @@ off(void)
} }
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
static char powercycle(struct rtimer *t, void *ptr);
static void
schedule_powercycle(struct rtimer *t, rtimer_clock_t time)
{
int r;
if(xmac_is_on) {
r = rtimer_set(t, RTIMER_TIME(t) + time, 1,
(void (*)(struct rtimer *, void *))powercycle, NULL);
if(r) {
PRINTF("schedule_powercycle: could not set rtimer\n");
}
}
}
static void
powercycle_turn_radio_off(void)
{
if(we_are_sending == 0 &&
waiting_for_packet == 0) {
off();
}
#if XMAC_CONF_COMPOWER
compower_accumulate(&compower_idle_activity);
#endif /* XMAC_CONF_COMPOWER */
}
static void
powercycle_turn_radio_on(void)
{
if(we_are_sending == 0 &&
waiting_for_packet == 0) {
on();
}
}
static char static char
powercycle(struct rtimer *t, void *ptr) powercycle(struct rtimer *t, void *ptr)
{ {
@ -272,74 +317,26 @@ powercycle(struct rtimer *t, void *ptr)
someone_is_sending--; someone_is_sending--;
} }
/* If there were a strobe in the air, turn radio on */
powercycle_turn_radio_on();
schedule_powercycle(t, xmac_config.on_time);
PT_YIELD(&pt);
if(xmac_config.off_time > 0) { if(xmac_config.off_time > 0) {
if(we_are_sending == 0) { powercycle_turn_radio_off();
if(waiting_for_packet == 0) { if(waiting_for_packet != 0) {
off(); waiting_for_packet++;
#if XMAC_CONF_COMPOWER if(waiting_for_packet > 2) {
compower_accumulate(&compower_idle_activity); /* We should not be awake for more than two consecutive
#endif /* XMAC_CONF_COMPOWER */ power cycles without having heard a packet, so we turn off
} else { the radio. */
waiting_for_packet++; waiting_for_packet = 0;
if(waiting_for_packet > 2) { powercycle_turn_radio_off();
/* We should not be awake for more than two consecutive
power cycles without having heard a packet, so we turn off
the radio. */
waiting_for_packet = 0;
if(we_are_sending == 0) {
off();
}
#if XMAC_CONF_COMPOWER
compower_accumulate(&compower_idle_activity);
#endif /* XMAC_CONF_COMPOWER */
}
} }
} }
schedule_powercycle(t, xmac_config.off_time);
#if WITH_TIMESYNCH
#define NUM_SLOTS 16
should_be = ((timesynch_rtimer_to_time(RTIMER_TIME(t)) +
xmac_config.off_time) &
~(xmac_config.off_time + xmac_config.on_time - 1)) +
(rimeaddr_node_addr.u8[0] % NUM_SLOTS *
((xmac_config.off_time + xmac_config.on_time) / NUM_SLOTS));
should_be = timesynch_time_to_rtimer(should_be);
if(should_be - RTIMER_TIME(t) > xmac_config.off_time) {
adjust = xmac_config.off_time / 2;
} else {
adjust = should_be - RTIMER_TIME(t);
}
if(xmac_is_on) {
r = rtimer_set(t, RTIMER_TIME(t) + adjust, 1,
(void (*)(struct rtimer *, void *))powercycle, ptr);
}
#else /* WITH_TIMESYNCH */
if(xmac_is_on) {
r = rtimer_set(t, RTIMER_TIME(t) + xmac_config.off_time, 1,
(void (*)(struct rtimer *, void *))powercycle, ptr);
}
#endif /* WITH_TIMESYNCH */
if(r) {
PRINTF("xmac: 1 could not set rtimer %d\n", r);
}
PT_YIELD(&pt); PT_YIELD(&pt);
} }
if(we_are_sending == 0 &&
waiting_for_packet == 0) {
on();
}
if(xmac_is_on) {
r = rtimer_set(t, RTIMER_TIME(t) + xmac_config.on_time, 1,
(void (*)(struct rtimer *, void *))powercycle, ptr);
}
if(r) {
PRINTF("xmac: 3 could not set rtimer %d\n", r);
}
PT_YIELD(&pt);
} }
PT_END(&pt); PT_END(&pt);
@ -460,6 +457,7 @@ send_packet(void)
struct encounter *e; struct encounter *e;
struct queuebuf *packet; struct queuebuf *packet;
int is_already_streaming = 0; int is_already_streaming = 0;
uint8_t collisions;
#if WITH_RANDOM_WAIT_BEFORE_SEND #if WITH_RANDOM_WAIT_BEFORE_SEND
{ {
@ -498,7 +496,7 @@ send_packet(void)
if(len == 0 || strobe_len > sizeof(strobe)) { if(len == 0 || strobe_len > sizeof(strobe)) {
/* Failed to send */ /* Failed to send */
PRINTF("xmac: send failed, too large header\n"); PRINTF("xmac: send failed, too large header\n");
return 0; return MAC_TX_ERR_FATAL;
} }
memcpy(strobe, packetbuf_hdrptr(), len); memcpy(strobe, packetbuf_hdrptr(), len);
strobe[len] = DISPATCH; /* dispatch */ strobe[len] = DISPATCH; /* dispatch */
@ -510,30 +508,9 @@ send_packet(void)
/* No buffer available */ /* No buffer available */
PRINTF("xmac: send failed, no queue buffer available (of %u)\n", PRINTF("xmac: send failed, no queue buffer available (of %u)\n",
QUEUEBUF_CONF_NUM); QUEUEBUF_CONF_NUM);
return 0; return MAC_TX_ERR;
} }
#if WITH_CHANNEL_CHECK
/* Check if there are other strobes in the air. */
if(!someone_is_sending) {
waiting_for_packet = 1;
on();
t0 = RTIMER_NOW();
while(RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + xmac_config.strobe_wait_time * 2)) {
len = radio->read(&strobe.hdr, sizeof(strobe.hdr));
if(len > 0) {
someone_is_sending = 1;
}
}
waiting_for_packet = 0;
}
while(someone_is_sending);
#endif /* WITH_CHANNEL_CHECK */
/* By setting we_are_sending to one, we ensure that the rtimer /* By setting we_are_sending to one, we ensure that the rtimer
powercycle interrupt do not interfere with us sending the packet. */ powercycle interrupt do not interfere with us sending the packet. */
we_are_sending = 1; we_are_sending = 1;
@ -580,7 +557,7 @@ send_packet(void)
now = RTIMER_NOW(); now = RTIMER_NOW();
wait = ((rtimer_clock_t)(e->time - now)) % (DEFAULT_PERIOD); wait = ((rtimer_clock_t)(e->time - now)) % (DEFAULT_PERIOD);
expected = now + wait - DEFAULT_ON_TIME * 2; expected = now + wait - 2 * DEFAULT_ON_TIME;
#if WITH_ACK_OPTIMIZATION #if WITH_ACK_OPTIMIZATION
/* Wait until the receiver is expected to be awake */ /* Wait until the receiver is expected to be awake */
@ -607,72 +584,77 @@ send_packet(void)
/* Send a train of strobes until the receiver answers with an ACK. */ /* Send a train of strobes until the receiver answers with an ACK. */
/* Turn on the radio to listen for the strobe ACK. */ /* Turn on the radio to listen for the strobe ACK. */
if(!is_broadcast) { on();
on(); collisions = 0;
}
if(!is_already_streaming) { if(!is_already_streaming) {
watchdog_stop();
watchdog_stop(); got_strobe_ack = 0;
got_strobe_ack = 0;
for(strobes = 0;
got_strobe_ack == 0 &&
RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + xmac_config.strobe_time);
strobes++) {
t = RTIMER_NOW(); t = RTIMER_NOW();
for(strobes = 0, collisions = 0;
got_strobe_ack == 0 && collisions == 0 &&
RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + xmac_config.strobe_time);
strobes++) {
/* Send the strobe packet. */ while(got_strobe_ack == 0 &&
radio->send(strobe, strobe_len); RTIMER_CLOCK_LT(RTIMER_NOW(), t + xmac_config.strobe_wait_time)) {
rtimer_clock_t now = RTIMER_NOW();
while(got_strobe_ack == 0 && /* See if we got an ACK */
RTIMER_CLOCK_LT(RTIMER_NOW(), t + xmac_config.strobe_wait_time)) { packetbuf_clear();
/* See if we got an ACK */ len = radio->read(packetbuf_dataptr(), PACKETBUF_SIZE);
if(!is_broadcast) {
packetbuf_clear();
len = radio->read(packetbuf_dataptr(), PACKETBUF_SIZE);
if(len > 0) { if(len > 0) {
packetbuf_set_datalen(len); packetbuf_set_datalen(len);
if(framer_get()->parse()) { if(framer_get()->parse()) {
hdr = packetbuf_dataptr(); hdr = packetbuf_dataptr();
if(hdr->dispatch == DISPATCH && hdr->type == TYPE_STROBE_ACK) {
if(rimeaddr_cmp(packetbuf_addr(PACKETBUF_ADDR_RECEIVER),
&rimeaddr_node_addr)) {
/* We got an ACK from the receiver, so we can immediately send
the packet. */
got_strobe_ack = 1;
encounter_time = now;
} else {
PRINTDEBUG("xmac: strobe ack for someone else\n");
}
} else /*if(hdr->dispatch == DISPATCH && hdr->type == TYPE_STROBE)*/ {
PRINTDEBUG("xmac: strobe from someone else\n");
collisions++;
/* } else {
PRINTDEBUG("xmac: ignored len %u\n", len);*/
}
} else {
PRINTF("xmac: send failed to parse %u\n", len);
}
}
}
if(hdr->dispatch == DISPATCH && hdr->type == TYPE_STROBE_ACK) { t = RTIMER_NOW();
if(rimeaddr_cmp(packetbuf_addr(PACKETBUF_ADDR_RECEIVER), /* Send the strobe packet. */
&rimeaddr_node_addr)) { if(got_strobe_ack == 0 && collisions == 0) {
/* We got an ACK from the receiver, so we can immediately send
the packet. */ if(is_broadcast) {
got_strobe_ack = 1; #if WITH_STROBE_BROADCAST
encounter_time = RTIMER_NOW(); radio->send(strobe, strobe_len);
} else { #else
PRINTDEBUG("xmac: strobe ack for someone else\n"); /* restore the packet to send */
} queuebuf_to_packetbuf(packet);
} else { radio->send(packetbuf_hdrptr(), packetbuf_totlen());
/* TODO One of the nodes should back-off since several #endif
nodes are sending at once. If possible and if the off();
packet is destined for this node: send a strobe ack } else {
if it is a strobe packet, queue the packet if it is a rtimer_clock_t wt;
data packet? */ radio->send(strobe, strobe_len);
PRINTDEBUG("xmac: ignored data %u\n", len); #if 1
#if DEBUG /* Turn off the radio for a while to let the other side
/* collisions++;*/ respond. We don't need to keep our radio on when we know
#endif /* DEBUG */ that the other side needs some time to produce a reply. */
} off();
} else { wt = RTIMER_NOW();
PRINTF("xmac: send failed to parse %u\n", len); while(RTIMER_CLOCK_LT(RTIMER_NOW(), wt + WAIT_TIME_BEFORE_STROBE_ACK));
} #endif /* 0 */
} on();
}
} }
} }
/* XXX: turn off radio if we haven't heard an ACK within a
specified time interval. */
/* if(got_strobe_ack == 0) {
off();
while(RTIMER_CLOCK_LT(RTIMER_NOW(), t + xmac_config.strobe_wait_time));
on();
}*/
}
} }
#if WITH_ACK_OPTIMIZATION #if WITH_ACK_OPTIMIZATION
@ -697,7 +679,7 @@ send_packet(void)
queuebuf_free(packet); queuebuf_free(packet);
/* Send the data packet. */ /* Send the data packet. */
if(is_broadcast || got_strobe_ack || is_streaming) { if((is_broadcast || got_strobe_ack || is_streaming) && collisions == 0) {
radio->send(packetbuf_hdrptr(), packetbuf_totlen()); radio->send(packetbuf_hdrptr(), packetbuf_totlen());
} }
@ -729,29 +711,23 @@ send_packet(void)
we_are_sending = 0; we_are_sending = 0;
LEDS_OFF(LEDS_BLUE); LEDS_OFF(LEDS_BLUE);
return 1; if(collisions == 0) {
return MAC_TX_OK;
} else {
someone_is_sending++;
return MAC_TX_COLLISION;
}
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
static struct queuebuf *queued_packet;
static int static int
qsend_packet(void) qsend_packet(void)
{ {
if(someone_is_sending) { if(someone_is_sending) {
PRINTF("xmac: should queue packet, now just dropping %d %d %d %d.\n", PRINTF("xmac: should queue packet, now just dropping %d %d %d %d.\n",
waiting_for_packet, someone_is_sending, we_are_sending, radio_is_on); waiting_for_packet, someone_is_sending, we_are_sending, radio_is_on);
if(queued_packet != NULL) { RIMESTATS_ADD(sendingdrop);
RIMESTATS_ADD(sendingdrop); return MAC_TX_COLLISION;
return 0;
} else {
#if WITH_QUEUE
queued_packet = queuebuf_new_from_packetbuf();
return 1;
#else
RIMESTATS_ADD(sendingdrop);
return 0;
#endif
}
} else { } else {
PRINTF("xmac: send immediately.\n"); PRINTF("xmac: send immediately.\n");
return send_packet(); return send_packet();
@ -814,12 +790,6 @@ read_packet(void)
waiting_for_packet = 0; waiting_for_packet = 0;
/* XXX should set timer to send queued packet later. */
if(queued_packet != NULL) {
queuebuf_free(queued_packet);
queued_packet = NULL;
}
PRINTDEBUG("xmac: data(%u)\n", packetbuf_datalen()); PRINTDEBUG("xmac: data(%u)\n", packetbuf_datalen());
return packetbuf_datalen(); return packetbuf_datalen();
} else { } else {