forked from things-nyc/arduino-lmic
-
Notifications
You must be signed in to change notification settings - Fork 209
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d86e4c5
commit 1a1f316
Showing
1 changed file
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* | ||
Module: lmic_channelshuffle.c | ||
Function: | ||
Channel scheduling without replacement. | ||
Copyright and License: | ||
This file copyright (C) 2021 by | ||
MCCI Corporation | ||
3520 Krums Corners Road | ||
Ithaca, NY 14850 | ||
See accompanying LICENSE file for copyright and license information. | ||
Author: | ||
Terry Moore, MCCI Corporation April 2021 | ||
*/ | ||
|
||
#include "lmic.h" | ||
#include <string.h> | ||
|
||
/****************************************************************************\ | ||
| | ||
| Manifest constants and local declarations. | ||
| | ||
\****************************************************************************/ | ||
|
||
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries); | ||
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum); | ||
|
||
/****************************************************************************\ | ||
| | ||
| Read-only data. | ||
| | ||
\****************************************************************************/ | ||
|
||
/****************************************************************************\ | ||
| | ||
| Variables. | ||
| | ||
\****************************************************************************/ | ||
|
||
/* | ||
Name: LMIC_findNextChannel() | ||
Function: | ||
Scan a shuffle mask, and select a channel (without replacement). | ||
Definition: | ||
int LMIC_findNextChannel( | ||
uint16_t *pShuffleMask, | ||
const uint16_t *pEnableMask, | ||
uint16_t nEntries, | ||
int lastChannel | ||
); | ||
Description: | ||
pShuffleMask and pEnableMask are bit vectors. Channels correspond to | ||
bits in little-endian order; entry [0] has channels 0 through 15, entry | ||
[1] channels 16 through 31, and so forth. nEntries specifies the number | ||
of entries in the mask vectors. The enable mask is 1 for a given channel | ||
if that channel is eligible for selection, 0 otherwise. | ||
This routine selects channels from the shuffle mask until all entries | ||
are exhausted; it then refreshes the shuffle mask from the enable mask. | ||
If it refreshes the channel mask, lastChannel is taken as a channel number | ||
that is to be avoided in the next selection. (This is to avoid back-to-back | ||
use of a channel across a refresh boundary.) Otherwise lastChannel is | ||
ignored. This avoidance can be suppresed by setting lastChannel to -1. | ||
If only one channel is enabled, lastChannel is also ignored. If lastChannel | ||
is actually disabled, lastChannel is also ignored. | ||
Returns: | ||
A channel number, in 0 .. nEntries-1, or -1 if the enable mask is | ||
identically zero. | ||
Notes: | ||
This routine is somewhat optimized for AVR processors, which don't have | ||
multi-bit shifts. | ||
*/ | ||
|
||
int LMIC_findNextChannel( | ||
uint16_t *pShuffleMask, | ||
const uint16_t *pEnableMask, | ||
uint16_t nEntries, | ||
int lastChannel | ||
) { | ||
unsigned nSet16; | ||
uint16_t saveLastChannelVal; | ||
|
||
// in case someone has changed the enable mask, update | ||
// the shuffle mask so there are no disable bits set. | ||
for (unsigned i = 0; i < nEntries; ++i) { | ||
pShuffleMask[i] &= pEnableMask[i]; | ||
} | ||
|
||
// count the set bits in the shuffle mask (with a factor of 16 for speed) | ||
nSet16 = sidewaysSum16(pShuffleMask, nEntries); | ||
|
||
// if zero, copy the enable mask to the shuffle mask, and recount | ||
if (nSet16 == 0) { | ||
memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask)); | ||
nSet16 = sidewaysSum16(pShuffleMask, nEntries); | ||
} else { | ||
// don't try to skip the last channel becuase it can't be chosen. | ||
lastChannel = -1; | ||
} | ||
|
||
// if still zero, return -1. | ||
if (nSet16 == 0) { | ||
return -1; | ||
} | ||
|
||
// if we have to skip a channel, and we have more than one choice, turn off | ||
// the last channel bit. Post condition: if we really clered a bit, | ||
// saveLastChannelVal will be non-zero. | ||
saveLastChannelVal = 0; | ||
if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= nEntries * 16) { | ||
uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF)); | ||
|
||
saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask; | ||
pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask; | ||
|
||
// if we cleared a bit, reduce the count. | ||
if (saveLastChannelVal > 0) | ||
nSet16 -= 16; | ||
} | ||
|
||
if (saveLastChannelVal == 0) { | ||
// We didn't eliminate a channel, so we don't have to worry. | ||
lastChannel = -1; | ||
} | ||
|
||
// get a random number | ||
unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4); | ||
|
||
// choose a bit based on set bit | ||
unsigned channel = findNthSetBit(pShuffleMask, choice); | ||
pShuffleMask[channel / 16] ^= (1 << (channel & 0xF)); | ||
|
||
// handle channel skip | ||
if (lastChannel >= 0) { | ||
pShuffleMask[lastChannel >> 4] |= saveLastChannelVal; | ||
} | ||
return channel; | ||
} | ||
|
||
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) { | ||
unsigned result; | ||
|
||
result = 0; | ||
for (; nEntries > 0; --nEntries, ++pMask) | ||
{ | ||
uint16_t v = *pMask; | ||
|
||
// the following is an adaptation of Knuth 7.1.3 (62). To avoid | ||
// lots of shifts (slow on AVR, and code intensive) and table lookups, | ||
// we sum popc * 16, then divide by 16. | ||
|
||
// sum adjacent bits, making a series of 2-bit sums | ||
v = v - ((v >> 1) & 0x5555u); | ||
v = (v & 0x3333u) + ((v >> 2) & 0x3333u); | ||
// this assumes multiplies are essentialy free; | ||
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u); | ||
// Accumulate result, but note it's times 16. | ||
// AVR compiler should optimize the x8 shift. | ||
result += (v & 0xFF) + (v >> 8); | ||
} | ||
|
||
// | ||
return result; | ||
} | ||
|
||
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) { | ||
unsigned result; | ||
result = 0; | ||
bitnum = bitnum * 16; | ||
for (;; result += 16) { | ||
uint16_t m = *pMask++; | ||
if (m == 0) | ||
continue; | ||
uint16_t v = m - ((m >> 1) & 0x5555u); | ||
v = (v & 0x3333u) + ((v >> 2) & 0x3333u); | ||
// this assumes multiplies are essentialy free; | ||
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u); | ||
// Accumulate result, but note it's times 16. | ||
// AVR compiler should optimize the x8 shift. | ||
v = (v & 0xFF) + (v >> 8); | ||
if (v <= bitnum) | ||
bitnum -= v; | ||
else { | ||
// the selected bit is in this word. We need to count. | ||
while (bitnum > 0) { | ||
m &= m - 1; | ||
bitnum -= 16; | ||
} | ||
// now the lsb of m is our choice. | ||
// get a mask, then use Knuth 7.1.3 (59) to find the | ||
// bit number. | ||
m &= -m; | ||
result += ((m & 0x5555u) ? 0 : 1) | ||
+ ((m & 0x3333u) ? 0 : 2) | ||
+ ((m & 0x0F0Fu) ? 0 : 4) | ||
+ ((m & 0x00FFu) ? 0 : 8) | ||
; | ||
break; | ||
} | ||
} | ||
|
||
return result; | ||
} |