Mark Gilbert's Blog

Science and technology, served light and fluffy.

Come in ground control: Stratoballoon sketch updated for radio transmitter

It’s been a long time coming, but the main Balloon sketch has finally been updated to include code to control the radio transmitter.  And true to form, the transmitter had what I hope are the final few instrument pack monkey wrenches for us.

I detailed in Episode 22 the work needed to get the NTX2 board attached to the instrument pack.  Once I had that in place, I could incorporate the logic for transmitting over it from the RTTY test radio sketch we had been playing with for the last few months.

Monkey Wrench #1

Before I could drop those pieces in, however, I needed to relearn how to do string concatenation.  I wanted to transmit my call sign, a timestamp, altitude, latitude and longitude in a single comma-delimited string.  These values are a string, and four floating point values, respectively.  If this were straight C, I could use sprintf and format the floating point values to whatever precision I needed.  However, the Arduino C libraries don’t support sprintf for floats.

I could, however, use another built in function call "dtostrf()" which would convert my floats into a character array.  From there, I could use sprintf to concatenate the strings together into my "TransmitBuffer".

Monkey Wrench #2

Doing that, however preserved all of the whitespace in the character arrays.

KD8VZA,            21515.00,              314.28,              0.0000,              0.0000,*9395

It’s at points like these where I realize how spoiled I’ve gotten using a language like C# that supports things like ".Trim()".  Unfortunately, Arduino C doesn’t, so I had to roll my own.

What I ended up with was a routine that would copy one array into another, one byte at a time:

// This copies the SourceBuffer into DestBuffer starting at DestIndex,
// but trims out any leading spaces in SourceBuffer
int TransferBuffer(char* SourceBuffer, int SourceBufferSize, char* DestBuffer, int DestIndex, boolean ShouldAddComma) {
  int BufferIndex = 0;
 
  // Skip all characters from 0-32, but don’t count carriage returns, chr(13)
  while (BufferIndex < SourceBufferSize-1
          && SourceBuffer[BufferIndex] <= 32 && SourceBuffer[BufferIndex] != 13) { BufferIndex++; }
  while (BufferIndex < SourceBufferSize-1) {    
    DestBuffer[DestIndex] = SourceBuffer[BufferIndex];
    DestIndex++;
    BufferIndex++;
  } 
 
  if(ShouldAddComma) {
    TransmitBuffer[DestIndex] = ‘,’;
    DestIndex++;
  }

  return DestIndex;
}

The TransmitBuffer method takes the source and destination arrays, the size of the source array, and the current position in the destination array where it needs to start writing.

The method starts by skipping past any whitespace or non-printable characters in the source array, except for carriage returns (character 13).

Once I know where in the source array I need to start copying, I copy the bytes into the destination array one by one, until I reach the end of the source array.  In that main loop, I’m keeping track of where I am in the destination array – this index will be the function’s return value.

The very last thing I do it check to see if I should append a comma to the end of the new string.  That is determined by the last parameter to the TransferBuffer array, ShouldAddComma.

Putting Monkey Wrenches 1 and 2 together

My original version of the TransmitDataToGround function uses these two solutions – dtostrf and TransferBuffer – to combine the call sign, timestamp, altitude, latitude, and longitude into a single string that can be transmitted over the NTX2:

void TransmitDataToGround(float TimeStamp, float Altitude, float Latitude, float Longitude) {
 
  // Build the string to transmit
  dtostrf(TimeStamp, 20, 3, TimeStampBuffer);
  dtostrf(Altitude, 20, 3, AltitudeBuffer);
  dtostrf(Latitude, 20, 5, LatitudeBuffer);
  dtostrf(Longitude, 20, 5, LongitudeBuffer);

  int TransmitIndex = 0;
  TransmitIndex = TransferBuffer(CALL_SIGN, sizeof(CALL_SIGN), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(TimeStampBuffer, sizeof(TimeStampBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(AltitudeBuffer, sizeof(AltitudeBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(LatitudeBuffer, sizeof(LatitudeBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(LongitudeBuffer, sizeof(LongitudeBuffer), TransmitBuffer, TransmitIndex, true);

  unsigned int CHECKSUM = gps_CRC16_checksum(TransmitBuffer);  // Calculates the checksum for this datastring
  sprintf(TransmitBufferChecksum, "*%04X\n", CHECKSUM);
  TransmitIndex = TransferBuffer(TransmitBufferChecksum, sizeof(TransmitBufferChecksum), TransmitBuffer, TransmitIndex, false);
 
  rtty_txstring (TransmitBuffer);
}

The logic for calculating the checksum, and the rtty_txstring() function which does the actual transmission, come from the UKHAS site.

Monkey Wrench #3

Once I had TransmitDataToGround() in place, I needed to determine how frequently to call it.  I ultimately decided that I would transmit every 10th reading (where a "reading" is a sampling of the temperature, pressure, and GPS sensors).  I updated the last section of the main loop logic (which checked that it had a good temperature and GPS reading before logging the reading) to include this call. 

// Now that we have a good GPS and temperature reading, grab
// the rest of the data, and log it.
if(HasGoodGPSReading && HasGoodTempReading)
{
  CurrentPressure = GetPressure(bmp085ReadUT(), bmp085ReadUP());
  CurrentAltitude = CalculateAltitude(CurrentPressure);
 
  PrintToSerialOutput(CurrentTimeStamp, CurrentTemp, CurrentPressure, CurrentAltitude, CurrentLatitude, CurrentLongitude);
  WriteDataToLogger(CurrentTimeStamp, CurrentTemp, CurrentPressure, CurrentAltitude, CurrentLatitude, CurrentLongitude);

  if(TransmitNumber > TRANSMIT_EVERY_Nth_READING) {
    TransmitDataToGround(CurrentTimeStamp, CurrentAltitude, CurrentLatitude, CurrentLongitude);
    TransmitNumber = 0;
  } else {
    TransmitNumber++;
  }
 
  HasGoodGPSReading = false;
  HasGoodTempReading = false;
}

 

I added a new loop index called "TransmitNumber" that I would increment with every reading, and reset when I invoked TransmitDataToGround().

All appeared to be fine at first – the data was coming through exactly as I had expected.  However, I noticed that the radio wasn’t ever turning off in between transmissions.  During the nine readings that weren’t being transmitted, it would generate a solid tone.  I needed to be able to programmatically enable and disable the transmitter.  That led me to rewire the NTX2 – for full details, see the Episode 22 Addendum.

Once I had digital pin 5 wired up properly, I reworked TransmitDataToGround() to turn the transmitter at the beginning, and turn it off again at the end.

void TransmitDataToGround(float TimeStamp, float Altitude, float Latitude, float Longitude) {
 
  // Build the string to transmit
  digitalWrite(RADIO_ENABLE_PIN, HIGH);

  dtostrf(TimeStamp, 20, 3, TimeStampBuffer);
  dtostrf(Altitude, 20, 3, AltitudeBuffer);
  dtostrf(Latitude, 20, 5, LatitudeBuffer);
  dtostrf(Longitude, 20, 5, LongitudeBuffer);

  int TransmitIndex = 0;
  TransmitIndex = TransferBuffer(CALL_SIGN, sizeof(CALL_SIGN), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(TimeStampBuffer, sizeof(TimeStampBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(AltitudeBuffer, sizeof(AltitudeBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(LatitudeBuffer, sizeof(LatitudeBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(LongitudeBuffer, sizeof(LongitudeBuffer), TransmitBuffer, TransmitIndex, true);

  unsigned int CHECKSUM = gps_CRC16_checksum(TransmitBuffer);  // Calculates the checksum for this datastring
  sprintf(TransmitBufferChecksum, "*%04X\n", CHECKSUM);
  TransmitIndex = TransferBuffer(TransmitBufferChecksum, sizeof(TransmitBufferChecksum), TransmitBuffer, TransmitIndex, false);
 
  rtty_txstring (TransmitBuffer);
  digitalWrite(RADIO_ENABLE_PIN, LOW);
}

Monkey Wrench #4

I found that when I enabled the transmitter, I was losing several characters off of the beginning.  This was because dl-fldigi needed 2-3 seconds between when the transmitter booted up until it actually started sending data to find the signal again, and start decoding it.  I added a 3-second delay to TransmitDataToGround, right after I enabled the transmitter, to provide this buffer.

void TransmitDataToGround(float TimeStamp, float Altitude, float Latitude, float Longitude) {
 
  // Build the string to transmit
  digitalWrite(RADIO_ENABLE_PIN, HIGH);
  delay(3000);
  dtostrf(TimeStamp, 20, 3, TimeStampBuffer);
  dtostrf(Altitude, 20, 3, AltitudeBuffer);
  dtostrf(Latitude, 20, 5, LatitudeBuffer);
  dtostrf(Longitude, 20, 5, LongitudeBuffer);

  int TransmitIndex = 0;
  TransmitIndex = TransferBuffer(CALL_SIGN, sizeof(CALL_SIGN), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(TimeStampBuffer, sizeof(TimeStampBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(AltitudeBuffer, sizeof(AltitudeBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(LatitudeBuffer, sizeof(LatitudeBuffer), TransmitBuffer, TransmitIndex, true);
  TransmitIndex = TransferBuffer(LongitudeBuffer, sizeof(LongitudeBuffer), TransmitBuffer, TransmitIndex, true);

  unsigned int CHECKSUM = gps_CRC16_checksum(TransmitBuffer);  // Calculates the checksum for this datastring
  sprintf(TransmitBufferChecksum, "*%04X\n", CHECKSUM);
  TransmitIndex = TransferBuffer(TransmitBufferChecksum, sizeof(TransmitBufferChecksum), TransmitBuffer, TransmitIndex, false);
 
  rtty_txstring (TransmitBuffer);
  digitalWrite(RADIO_ENABLE_PIN, LOW);
}

Now when the radio turns on, the waterfall in dl-fldigi looks like this:

10

The blank horizontal line in the waterfall above shows this 3-second delay.  The red line that starts out horizontal, and then quickly curves to be vertical is the data coming over the airwaves.  That red line, and the accompanying one to the left, need to be lined up with the two thin red lines that make up the dl-fldigi cursor.  Until those two pairs of lines line up, nothing is decoded. 

I also found that the transmitter will drift slightly while it is transmitting, and that the dl-fldigi cursor will drift in between transmissions.  The 3-second delay gives me a chance to adjust the cursor if I need to when the next transmission starts.

Monkey Wrench #5

Even with the delay, I found I was still losing characters off the beginning of the transmission string.  I solved this by simply appending more than one copy of my call sign to the beginning.  As a result, the transmitted strings usually ends up looking like this now:

jf7&VZA-KD8VZA-KD8VZA,21515.00,314.28,0.0000,0.0000,*9395

Finally, I adjusted the squelch control in dl-fldigi to avoid having so much garbage between transmissions appear in the output window.  I still get some, but it’s not the constant barrage that it was when I started.

***

It feels good to be able to put this piece of the balloon to rest.  I may end refactoring the code a little, or adjusting it based on our instrument pack endurance/distance test, but otherwise I think its basically there.           

You can get the newly completed sketch from GitHub

Advertisements

July 8, 2014 - Posted by | Science

Sorry, the comment form is closed at this time.

%d bloggers like this: