Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: LoRaBaseStation LoRaTerminal
AlohaTransceiver.cpp
- Committer:
- rba90
- Date:
- 2016-09-14
- Revision:
- 40:271fa9e98589
- Parent:
- 39:0da3ee6a297d
- Child:
- 41:37c1d616a848
File content as of revision 40:271fa9e98589:
#include "AlohaTransceiver.h"
#include "mbed.h"
#include "radio.h"
#include "debug.h"
#include "AlohaFrame.h"
#include "RingBuffer.h"
#define ALLOW_CALLBACK_DEBUG 1
#define CSMA_BACKOFF_BASE 900 // in ms
#define CSMA_BACKOFF_RANGE 500 // in ms
#define CSMA_CA_CHANNEL_THRESHOLD -80 // in dbm
#define SET_FLAG(t, x) (t) |= 1 << (x)
#define CLEAR_FLAG(t, x) (t) &= ~(1 << (x))
#define TOGGLE_FLAG(t, x) (t) ^= 1 << (x)
#define CHECK_FLAG(t, x) (t) >> (x) & 1
// declear the type of radio state
typedef enum
{
LOWPOWER = 0,
IDLE,
RX,
RX_TIMEOUT,
RX_ERROR,
TX,
TX_TIMEOUT,
CAD,
CAD_DONE
}AppStates_t;
// radio driver related variables
static int16_t RssiValue;
static int8_t SnrValue;
static volatile AppStates_t State;
static RadioEvents_t RadioEvents;
// rx queue
CircularBuffer<AlohaFrame *> AlohaRxQueue(10);
// callback functions for radio driver
void OnTxDone();
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
void OnTxTimeout();
void OnRxTimeout();
void OnRxError();
void OnFhssChangeChannel( uint8_t channelIndex );
void OnCadDone();
// radio driver
#ifdef DRIVER_SX1276
SX1276MB1xAS Radio( NULL );
#endif
#ifdef DRIVER_INAIR
SX1276inAir Radio( NULL );
#endif
/*
* Abstract interface for accessing radio driver
*/
AlohaTransceiver::AlohaTransceiver(uint8_t id):
AlohaTxQueue(32)
{
// store unique device id
deviceId = id;
// initialize sequenceid
memset(seqid, 0x0, sizeof(seqid));
// clear tx done state
isTxDone = true;
// assume all station is clear to transmit
isAcked = 0xffff;
// reset CSMA backoff timer
CSMABackoffTimer.reset();
// reset CSMA backoff state
isBackoff = false;
// set default CSMA backoff period
CSMABackoffPeriod = 1000;
// configure properties
#if USE_MODEM_LORA == 1
Settings.Power = TX_OUTPUT_POWER;
Settings.Bandwidth = LORA_BANDWIDTH;
Settings.Datarate = LORA_SPREADING_FACTOR;
Settings.Coderate = LORA_CODINGRATE;
Settings.PreambleLen = LORA_PREAMBLE_LENGTH;
Settings.SymbolTimeout = LORA_SYMBOL_TIMEOUT;
Settings.FixLen = LORA_FIX_LENGTH_PAYLOAD_ON;
Settings.PayloadLen = 0;
Settings.CrcOn = LORA_CRC_ENABLED;
Settings.FreqHopOn = LORA_FHSS_ENABLED;
Settings.HopPeriod = LORA_NB_SYMB_HOP;
Settings.IqInverted = LORA_IQ_INVERSION_ON;
Settings.RxContinuous = true;
Settings.TxTimeout = TX_TIMEOUT_VALUE;
#elif USE_MODEM_FSK == 1
// TODO: Complete settings for FSK mode
#error "FSK not implemented"
#else
#error "Please define a modem in the compiler options."
#endif
}
AlohaTransceiver::~AlohaTransceiver()
{
}
void AlohaTransceiver::boardInit()
{
// configure callback functions
RadioEvents.TxDone = OnTxDone;
RadioEvents.RxDone = OnRxDone;
RadioEvents.RxError = OnRxError;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxTimeout = OnRxTimeout;
Radio.Init( &RadioEvents );
// verify the connection with the board
while( Radio.Read( REG_VERSION ) == 0x00 )
{
#ifdef DEBUG_ALOHA
debug( "Radio could not be detected!\n\r" );
#endif
wait( 1 );
}
#ifdef DEBUG_ALOHA
printf("RadioRegVersion: %d\r\n", Radio.Read( REG_VERSION ));
#endif
}
void AlohaTransceiver::updateSettings()
{
Radio.SetChannel( RF_FREQUENCY );
#if USE_MODEM_LORA == 1
Radio.SetTxConfig( MODEM_LORA, Settings.Power, 0, Settings.Bandwidth,
Settings.Datarate, Settings.Coderate,
Settings.PreambleLen, Settings.FixLen,
Settings.CrcOn, Settings.FreqHopOn, Settings.HopPeriod,
Settings.IqInverted, Settings.TxTimeout );
Radio.SetRxConfig( MODEM_LORA, Settings.Bandwidth, Settings.Datarate,
Settings.Coderate, 0, Settings.PreambleLen,
Settings.SymbolTimeout, Settings.FixLen, Settings.PayloadLen,
Settings.CrcOn, Settings.FreqHopOn, Settings.HopPeriod,
Settings.IqInverted, Settings.RxContinuous );
#elif USE_MODEM_FSK == 1
#error "FSK not implemented"
#else
#error "Please define a modem in the compiler options."
#endif
}
void AlohaTransceiver::enable()
{
// entering passive receiver mode
Radio.Rx( 0 );
}
void AlohaTransceiver::poll()
{
switch( State )
{
case RX:
{
// process packet if received
// it is noticed that the rx handler does not queue the received packet
// due to the hardware limitation
while (AlohaRxQueue.getCounter() > 0)
{
// pop from queue
AlohaFrame *frame = AlohaRxQueue.dequeue();
// check destination
// if the destination is the device id, then processing, otherwise drop the packet and continue
// listening
if (frame->getDestinationAddress() == (deviceId & 0x0f))
{
uint8_t type = frame->getType();
// schedule the ack frame immediatly after the data frame is received
if (type == AlohaFrame::Aloha_Data)
{
#ifdef DEBUG_ALOHA
printf("\r\nRXDATA::SRC_ADDR:0x%x,SEQID:0x%x\r\n", frame->getSourceAddress(), frame->getSequenceID());
#endif
sendAck(frame);
}
else if (type == AlohaFrame::Aloha_ACK)
{
#ifdef DEBUG_ALOHA
printf("\r\nRXACK::SRC_ADDR:0x%x,SEQID:0x%x\r\n", frame->getSourceAddress(), frame->getSequenceID());
#endif
// clear the ack state
uint8_t src_addr = frame->getSourceAddress();
uint8_t foreign_seqid = frame->getSequenceID();
// compare the local sequence id and foreign sequence id for specific host
if (seqid[src_addr] <= foreign_seqid)
{
setAckedFlag(src_addr);
}
#ifdef DEBUG_ALOHA
printf("ACK::frame_seqid: %d, local_seqid: %d\r\n", frame->getSequenceID(), seqid[frame->getSourceAddress()]);
#endif
}
// check registered callback function
// execute callback functions if registered
if (AlohaTypeCallbackTable[type] != NULL)
{
uint8_t payload_length = frame->getPayloadLength();
uint8_t payload[payload_length];
uint8_t src_addr = frame->getSourceAddress();
// extract payload
for (uint8_t i = 0; i < payload_length; i++)
{
payload[i] = frame->getPayload(i);
}
// execute callback function
AlohaTypeCallbackTable[type](payload, payload_length, src_addr);
}
}
// free memory
delete frame;
}
Radio.Rx( 0 );
State = LOWPOWER;
break;
}
case TX:
{
// set tx to done, allow next transmission
setTxDoneFlag();
Radio.Rx( 0 );
State = LOWPOWER;
break;
}
case RX_TIMEOUT:
{
Radio.Rx( 0 );
State = LOWPOWER;
break;
}
case RX_ERROR:
{
Radio.Rx( 0 );
State = LOWPOWER;
break;
}
case TX_TIMEOUT:
{
// set tx to done, allow next transmission
setTxDoneFlag();
Radio.Rx( 0 );
State = LOWPOWER;
break;
}
case LOWPOWER:
{
// transmit packet when the radio is free
if (getTxDoneFlag())
{
// find next available packet to transmit
while( AlohaTxQueue.getCounter() > 0)
{
// perform CSMA backoff routine
if (isBackoff == true) // if the transceiver is already in the backoff state, then wait until timer expires
{
if (CSMABackoffTimer.read_ms() > CSMABackoffPeriod)
{
isBackoff = false;
CSMABackoffTimer.stop();
}
}
if (isBackoff == false) // if the transceiver is not in backoff state, then attempt to transmit
{
if (Radio.IsChannelFree(MODEM_LORA, RF_FREQUENCY, CSMA_CA_CHANNEL_THRESHOLD))
{
#ifdef DEBUG_ALOHA
printf(" CSMA/CA::Channel is free, ready to transmit\r\n");
#endif
AlohaFrame *frame = AlohaTxQueue.dequeue();
// determined by the type of the frame, only data frame need to be cleared before transmitting
switch (frame->getType())
{
// Data frame need proper clearance
case AlohaFrame::Aloha_Data:
{
uint8_t dest_addr = frame->getDestinationAddress();
// depending on the availability of the destination host,
// the radio will on transmit the packet when acked.
//
// in the test build, we are not going to enable this feature
// if (getAckedFlag(dest_addr))
if (true)
{
sendFrame(frame);
// block the next transmission until previous transmission is done
clearTxDoneFlag();
// block the next transmission of the same host until an acked packet
// is received
clearAckedFlag(dest_addr);
// free memory
delete frame;
}
// otherwise put the packet back to the end of queue and wait for its being acked
else
{
AlohaTxQueue.enqueue(frame);
}
break;
}
default:
{
sendFrame(frame);
// block the next transmission until previous transmission is done
clearTxDoneFlag();
// free memory
delete frame;
break;
}
}
}
else // if channel if not free, then start the timer, set the backoff state to true
{
isBackoff = true;
// generate random backoff delay
CSMABackoffPeriod = Radio.Random() % CSMA_BACKOFF_RANGE + CSMA_BACKOFF_BASE;
#ifdef DEBUG_ALOHA
printf("CSMA/CA::Channel is not free, wait for %d ms\r\n", CSMABackoffPeriod);
#endif
CSMABackoffTimer.reset();
CSMABackoffTimer.start();
}
}
}
}
break;
}
default:
{
State = LOWPOWER;
break;
}
}
}
void AlohaTransceiver::sendFrame(AlohaFrame *frame)
{
// create a buffer for transmit
uint8_t frame_length = frame->getPayloadLength() + FIXED_BYTE;
uint8_t buffer[frame_length]; // 4 fix fields
memset(buffer, 0x0, sizeof(buffer));
// copy content to buffer
frame->serialize(buffer);
// send to radio
Radio.Send(buffer, frame_length);
}
bool AlohaTransceiver::send(BasicPacket *packet)
{
// for the reason that we only transmit basic packet format, the
// length is always 8
uint8_t payload_length = 8;
// get destination address
uint8_t destination_address = findNextHop(packet->getDestinationID());
// serialize the packet from upper layer
uint8_t buffer[payload_length];
memset(buffer, 0x0, sizeof(buffer));
// copy bytes into buffer
packet->serialize(buffer);
// create a new frame
AlohaFrame *frame = new AlohaFrame();
// set properfies
// set payload as data
frame->setType(AlohaFrame::Aloha_Data);
// set payload length (8 bytes for BasicPacket)
frame->setPayloadLength(payload_length);
// set mac layer device id
frame->setSourceAddress(deviceId);
// set mac layer destination id
// for multiple hop system, the destination is the next hop
// in this case, we use destination id from upper layer
frame->setDestinationAddress(packet->getDestinationID());
// set full message flag (always true in this case)
frame->setFullMessageFlag(0x1);
// use dest_addr as key for accessing sequence id
// the seqid should increase as it successfully transmit the packet
frame->setSequenceID(seqid[destination_address]++);
// set payload
for (uint8_t i = 0; i < payload_length; i++)
{
frame->setPayload(i, buffer[i]);
}
// calculate crc
frame->generateCrc();
// push frame to the back of queue
AlohaTxQueue.enqueue(frame);
// debug
#ifdef DEBUG_ALOHA
printf("\r\nTXDATA::DEST_ADDR:0x%x,SEQID:0x%x\r\n", frame->getDestinationAddress(), frame->getSequenceID());
#endif
return true;
}
void AlohaTransceiver::sendAck(AlohaFrame *inFrame)
{
// create a new frame by calling it's constructor
AlohaFrame *outFrame = new AlohaFrame();
// set frame type
outFrame->setType(AlohaFrame::Aloha_ACK);
// set payload length (0 byte payload for ack frame)
outFrame->setPayloadLength(0);
// set mac layer device id
outFrame->setSourceAddress(deviceId);
// set mac layer destination id
outFrame->setDestinationAddress(inFrame->getSourceAddress());
// set full message flag
outFrame->setFullMessageFlag(0x1);
// set seqid
// the sequence id is always the source id + 1
outFrame->setSequenceID(inFrame->getSequenceID() + 1);
// no payload
// generate cec
outFrame->generateCrc();
// push frame to the back of queue
AlohaTxQueue.enqueue(outFrame);
// debug
#ifdef DEBUG_ALOHA
printf("\r\nTXACK::DEST_ADDR:0x%x,SEQID:0x%x\r\n", outFrame->getDestinationAddress(), outFrame->getSequenceID());
#endif
}
bool AlohaTransceiver::getTxDoneFlag()
{
return isTxDone;
}
void AlohaTransceiver::setTxDoneFlag()
{
isTxDone = true;
}
void AlohaTransceiver::clearTxDoneFlag()
{
isTxDone = false;
}
bool AlohaTransceiver::getAckedFlag(uint8_t addr)
{
return CHECK_FLAG(isAcked, addr);
}
void AlohaTransceiver::setAckedFlag(uint8_t addr)
{
SET_FLAG(isAcked, addr);
}
void AlohaTransceiver::clearAckedFlag(uint8_t addr)
{
CLEAR_FLAG(isAcked, addr);
}
void AlohaTransceiver::registerType(AlohaFrame::AlohaType_t type, aloha_callback_func f)
{
AlohaTypeCallbackTable[type] = f;
}
void AlohaTransceiver::deRegisterType(AlohaFrame::AlohaType_t type, aloha_callback_func f)
{
AlohaTypeCallbackTable[type] = NULL;
}
int16_t AlohaTransceiver::getRssi()
{
return RssiValue;
}
int8_t AlohaTransceiver::getSnr()
{
return SnrValue;
}
uint8_t AlohaTransceiver::getDeviceID()
{
return deviceId;
}
void AlohaTransceiver::setDeviceID(uint8_t id)
{
deviceId = id;
}
uint8_t AlohaTransceiver::findNextHop(uint8_t addr)
{
// TODO: maintain a routing lookup table for choosing shortest path
return addr;
}
#if USE_MODEM_LORA == 1
AlohaTransceiver::LoRaSettings_t *AlohaTransceiver::getSettings()
{
return &Settings;
}
#elif USE_MODEM_FSK == 1
AlohaTransceiver::FskSettings_t *AlohaTransceiver::getSettings()
{
return &Settings;
}
#else
#error "Please define a modem in the compiler options."
#endif
void OnTxDone( void )
{
Radio.Sleep( );
State = TX;
#ifdef DEBUG_ALOHA
debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnTxDone\n\r" );
#endif
}
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
uint8_t payload_length;
Radio.Sleep( );
// safeguard: if size exceeded maximum buffer size, it will cause memory overflow
payload_length = size ? BUFFER_SIZE : size <= BUFFER_SIZE;
// create a new frame instance
AlohaFrame *frame = new AlohaFrame(payload, payload_length);
// push onto the end of queue
AlohaRxQueue.enqueue(frame);
RssiValue = rssi;
SnrValue = snr;
State = RX;
#ifdef DEBUG_ALOHA
debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnRxDone, RSSI=%d, SNR=%d\n\r", rssi, snr );
#endif
}
void OnTxTimeout( void )
{
Radio.Sleep( );
State = TX_TIMEOUT;
#ifdef DEBUG_ALOHA
debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnTxTimeout\r\n" );
#endif
}
void OnRxTimeout( void )
{
Radio.Sleep( );
State = RX_TIMEOUT;
#ifdef DEBUG_ALOHA
debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnRxTimeout\r\n" );
#endif
}
void OnRxError( void )
{
Radio.Sleep( );
State = RX_ERROR;
#ifdef DEBUG_ALOHA
debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnRxError\r\n" );
#endif
}