Basic MAC data interface for LoRa transceiver
Dependents: LoRaBaseStation LoRaTerminal
AlohaTransceiver.cpp
- Committer:
- rba90
- Date:
- 2016-09-14
- Revision:
- 41:37c1d616a848
- Parent:
- 40:271fa9e98589
File content as of revision 41:37c1d616a848:
#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 if ( 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 }