Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Eg. 0x00 = No GPS Fix, 0 Satellites0x1A = GPS Fix, 10 Satellites0x24 = DGPS Fix, 4 Satellites

Code Block
languagecpp
var checkGPS = function (data) {
   if(data !== null) {
     var connection_status = ((data & 0xf0) >> 4) > 0; // if theres at least 1 fix
     if (connection_status !== this.gps.status) { //if its a different status
       this.gps.status = connection_status;
       StatusManager.setStatusCode('GPS_LOST', !this.gps.status);
       if (this.gps.status === false) { //if it was set to false, start the timer
         this.gps.timeSinceLost = Date.now();
       }
       else {
         this.gps.timeSinceLost = null;
       }
     }
   }
  }.bind(this);
Code Block
languagecpp
var checkPlaneStatus = function (number) {
    if(number !== null){
      this.initializing = (number === 0);
      if (number === 1) { //only set armed to false if the number is ONLY 1
        this.armed = false;
      }
      this.armed = (number === 2);
      this.running = (number === 3);
      this.killModeWarning = (number === 4);
      this.killModeActive = (number === 5);

      StatusManager.setStatusCode('AIRCRAFT_INITIALIZE', this.initializing);
      StatusManager.setStatusCode('AIRCRAFT_UNARMED', !this.armed);
      StatusManager.setStatusCode('AIRCRAFT_ARMED', this.armed);
      StatusManager.setStatusCode('AIRCRAFT_RUNNING', this.running);
      StatusManager.setStatusCode('AIRCRAFT_KILLMODE_WARNING', this.killModeWarning);
      StatusManager.setStatusCode('AIRCRAFT_KILLMODE', this.killModeActive);
    }
  }.bind(this);
  ```
### Interpreting wireless connection

var checkUHFStatus = function (data) { if(data !== null){ var bitmask = new Bitmask(data); this.uhf.status = bitmask.getBit(1);

Code Block
languagecpp
  if (this.uhf.status) { //has been turned to true
    this.uhf.timeSinceLost = null;
  }
  else { //has been turned to false
    this.uhf.timeSinceLost = Date.now();
  }
  StatusManager.setStatusCode('UHF_LOST', !this.uhf.status);
}

...

var checkManualMode = function (data) { if(data !== null){ var bitmask = new Bitmask(data); this.manualMode = !bitmask.getBit(0); StatusManager.setStatusCode('MANUAL_MODE', this.manualMode); } }.bind(this);

Code Block
languagecpp
### Interpreting Autonotmous level
```javascript
if (alevel.getBit(0)) {
        this.ui.remote_pitch_type.text('Angle');
      }
      else {
        this.ui.remote_pitch_type.text('Rate');
      }
      if (alevel.getBit(1)) {
        this.ui.remote_pitch_source.text('Ground Station');
      }
      else {
        this.ui.remote_pitch_source.text('Controller');
      }
      if (alevel.getBit(2)) {
        this.ui.remote_roll_type.text('Angle');
      }
      else {
        this.ui.remote_roll_type.text('Rate');
      }
      if (alevel.getBit(3)) {
        this.ui.remote_roll_source.text('Ground Station');
      }
      else {
        this.ui.remote_roll_source.text('Controller');
      }
      if (alevel.getBit(5)) {
        this.ui.remote_throttle_source.text('Autopilot');
      }
      else if (alevel.getBit(4)) {
        this.ui.remote_throttle_source.text('Groundstation');
      }
      else {
        this.ui.remote_throttle_source.text('Controller');
      }
      if (alevel.getBit(6)) {
        this.ui.remote_altitude_source.text('Autopilot');
      }
      else {
        this.ui.remote_altitude_source.text('Groundstation');
      }
      if (alevel.getBit(7)) {
        this.ui.remote_altitude_toggle.text('On');
      }
      else {
        this.ui.remote_altitude_toggle.text('Off');
      }
      if (alevel.getBit(8)) {
        this.ui.remote_heading_source.text('Autopilot');
      }
      else {
        this.ui.remote_heading_source.text('Groundstation');
      }
      if (alevel.getBit(9)) {
        this.ui.remote_heading_toggle.text('On');
      }
      else {
        this.ui.remote_heading_toggle.text('Off');
      }
      if (alevel.getBit(11)) {
        this.ui.remote_flap_source.text('Autopilot');
      }
      else if (alevel.getBit(10)) {
        this.ui.remote_flap_source.text('Groundstation');
      }
      else {
        this.ui.remote_flap_source.text('Controller');
      }
    },

...

Data is exported to the data link at a certain frequency (according to a clock). This is done by calling writeDatalink(frequency), where frequency is the time between packets. This subroutine creates a structure (defined in net.h) which contains memory locations for every variable. This data is then pushed to be processed in net_outbound.c.

Code Block
languagecpp
if (time - lastTime > frequency) {

    lastTime = time;

    struct telem_block* statusData = createTelemetryBlock();

    statusData->lat = gps_Latitude;

    statusData->lon = gps_Longitude;

    ...

return pushOutboundTelemetryQueue(statusData);

}

When all the data is assembled in the struct, pushOutboundTelemetryQueue(statusData) is called. This pushes the data onto a queue to be processed later:

Code Block
languagecpp
int pushOutboundTelemetryQueue(struct telem_block *telem) {

    if (getOutboundQueueLength() >= OUTBOUND_QUEUE_SIZE) {

        return -1;

    }

    outBuffer[outbuff_end] = telem;

    outbuff_end++;

    outbuff_end = outbuff_end % OUTBOUND_QUEUE_SIZE;

    return getOutboundQueueLength();

}

...

Every once in a while, the data accumulated must be processed. As a result, every iteration of the program runs a subroutine to maintain and cleanup the circular buffer. For the outgoing buffer, this method is outboundBufferMaintenance():

Code Block
languagecpp
if ( stagingBuffer.sendIndex >= PACKET_LENGTH ) {

    destroyTelemetryBlock(stagingBuffer.telemetry.asStruct);

    if ( getOutboundQueueLength() ) {

        stageTelemetryBlock(popOutboundTelemetryQueue());

    }

} else if ( stagingBuffer.telemetry.asStruct == 0 && getOutboundQueueLength() ) {

    stageTelemetryBlock(popOutboundTelemetryQueue());

}

Note that the structure of stagingBuffer is as follows:

Code Block
languagecpp
struct telem_buffer {

    unsigned int sendIndex;             // index into telemetry to send

    unsigned char header[API_HEADER_LENGTH];    // The header for the telem

    union {

        struct telem_block *asStruct;   // The telemetry block being sent

        unsigned char *asArray;         // The telemetry intepreted as an array

    } telemetry;

    unsigned char checksum;             // The checksum so far

};

...

After sufficient error checking (making sure sendIndex is less than the allowed packet size), stageTelemetryBlock(popOutboundTelemetryQueue()) is called. This method takes (pops) the next struct of data and stages it to be sent. stageTelemetryBlock() is responsible for converting the telemetry data into a telem_buffer structure.

Code Block
languagecpp
void stageTelemetryBlock(struct telem_block *telem) {

    stagingBuffer.telemetry.asStruct = telem;

    generateApiHeader(stagingBuffer.header, 0);

    stagingBuffer.checksum = 0;

    // Send index should be reset last for reasons

    stagingBuffer.sendIndex = 0;

    sendNextByte();

}

The first line of the subroutine adds the data into the packet. The second line (generateApiHeader(stagingBuffer.header,0)) creates an appropriate header in the stagingBuffer.header memory address with a data frame of 0. (See the XBEE section for the datasheet). The API header includes information involving which device the packet should be sent to, the length of the packet, as well as acknowledgement options, and packet types (data packet, configuration packet, status packet). After the checksum and sendIndex are explicitly reset, the sending process begins with sendNextByte():

Code Block
languagecpp
void sendNextByte(void) {

    unsigned char sendByte; // The byte to send

    if ( stagingBuffer.sendIndex < API_HEADER_LENGTH ) {

        //while (U2STAbits.TRMT == 0);

        sendByte = stagingBuffer.header[stagingBuffer.sendIndex] & 0xFF;

        // Compute checksum

        if (stagingBuffer.sendIndex >= 3) {

            stagingBuffer.checksum += sendByte & 0xFF;

        }

    } else if ( stagingBuffer.sendIndex < PACKET_LENGTH - 1 ) {

        sendByte = stagingBuffer.telemetry.asArray[stagingBuffer.sendIndex - API_HEADER_LENGTH] & 0xFF;

        stagingBuffer.checksum += sendByte & 0xFF;

    } else if ( stagingBuffer.sendIndex == PACKET_LENGTH - 1) {

        sendByte = 0xFF - (stagingBuffer.checksum & 0xFF);

    } else {

        IFS1bits.U2TXIF = 0;

        return;

    }

    stagingBuffer.sendIndex++;

    IFS1bits.U2TXIF = 0;

    U2TXREG = sendByte;

}

All the "if" statements above, compile the header, the data and the checksum together. Note that the checksum is the bitwise inverse of the actual sum: sendByte = 0xFF - (stagingBuffer.checksum & 0xFF). The most important part of this process is the last line, where each byte is sent to the UART transmit buffer. Since the UART transmit process is interrupt-based, each interrupt keeps calling sendNextByte(), until there is no more data left:

Code Block
languagecpp
void __attribute__((__interrupt__, no_auto_psv)) _U2TXInterrupt(void) {

    // Short circuit if nothing in the staging area yet

    if ( stagingBuffer.telemetry.asStruct == 0 ) {

        IFS1bits.U2TXIF = 0;

        return;

    }

    sendNextByte();

}

...

Once every iteration, a command is read from the uplink queue. This is done by calling readDatalink(). The command popCommand() is called. If any new commands have been received, popCommand() will return a command struct (defined in net.h):

Code Block
languagecpp
struct command {

    unsigned char cmd;

    unsigned char data\_length;

    unsigned char data[101];

};

It is fairly straight forward. The structure contains a cmd.cmd _which indicates the command ID. This ID corresponds to a certain function that needs to be completed. Following the pop command are a series of case statements (one for each command ID). For instance, if the command ID is 30, the following command is run (in _net_inbound.c):

Code Block
languagecpp
struct command* cmd = popCommand();

//TODO: Add rudimentary input validation

if ( cmd ) {

    if (lastCommandSentCode == cmd->cmd){

        lastCommandSentCode++;

    }

    else{

        lastCommandSentCode = cmd->cmd * 100;

    }

    switch (cmd->cmd) {

        ...

        case SET_THROTTLE:

            sp_ThrottleRate = (int)(*(int*)(&cmd->data) / 100.0  * (890 - 454) + 454);

            break;

        ...
    }

}

...

In order for the command structure to exist, the U2RXInterrupt must have been triggered. This occurs when new data is sent.

Code Block
languagecpp
void __attribute__((__interrupt__, no_auto_psv)) _U2RXInterrupt(void) {

    unsigned char data = U2RXREG;

    if ( rawPacketStatus[packetPos] != BUSY ) {    // no buffer available to write

        packetPos = ( packetPos + 1  ) % RAW_PACKET_BUFFER_SIZE;

        IFS1bits.U2RXIF = 0;

        return;

    }

    switch ( payloadPos ) {

        case 0:

            if ( data != START_DELIMITER ) {

                IFS1bits.U2RXIF = 0;

                return;

            }

            break;

        case 1:

            if ( data != 0 ) {

                payloadPos = 0;

                IFS1bits.U2RXIF = 0;

                return;                 // packet length < 100 bytes, so msb == 0

            }

            break;

        case 2:

            payloadLength[packetPos] = data;

            break;

        default:        // Normally, don't do anything special

            break;

    }

    rawPackets[packetPos][payloadPos++] = data;

    if ( payloadPos && payloadPos == payloadLength[packetPos] + 3 + 1) {   // at end of packet

        rawPacketStatus[packetPos] = READY;

        payloadPos = 0;

        packetPos = ( packetPos + 1  ) % RAW_PACKET_BUFFER_SIZE;

        if ( rawPacketStatus[packetPos] == EMPTY ) {

            rawPacketStatus[packetPos] = BUSY;

        }

    }

    IFS1bits.U2RXIF = 0;

}

...

Firstly, the start delimiter is looked for using a case statement. Until the start delimiter is found, nothing happens. Secondly, for case 1 and 2, the length of the packet is check and recorded. Once the length of the packet is known, the data is read into a 2d array called rawPackets. This array contains each byte of every packet in the circular buffer. Once all the data is copied into the array, the packet is marked as READY, and the next one is marked BUSY if it is EMPTY, and the processing of the data begins on the next maintenance cycle when inboundBufferMaintenance() is called from main.c:

Code Block
languagecpp
void inboundBufferMaintenance(void) {

    int i;

    for ( i = 0; i < RAW_PACKET_BUFFER_SIZE; i++ ) {

        if ( rawPacketStatus[i] == READY && checkPacket(rawPackets[i]) ) {

            struct command\* cmd = createCommand( rawPackets[i] );

            if ( cmd ) {            // create command was successful ?

                pushCommand( cmd ); // queue it up

                rawPacketStatus[i] = EMPTY;         // buffer is now good for writing another packet

            }

        }

    }

    if ( rawPacketStatus[0] == EMPTY ) {

        rawPacketStatus[0] = BUSY;

    }

}

...